Skip to content

Commit 868f3ee

Browse files
fancergregkh
authored andcommitted
serial: 8250: Add 8250 port clock update method
Some platforms can be designed in a way so the UART port reference clock might be asynchronously changed at some point. In Baikal-T1 SoC this may happen due to the reference clock being shared between two UART ports, on the Allwinner SoC the reference clock is derived from the CPU clock, so any CPU frequency change should get to be known/reflected by/in the UART controller as well. But it's not enough to just update the uart_port->uartclk field of the corresponding UART port, the 8250 controller reference clock divisor should be altered so to preserve current baud rate setting. All of these things is done in a coherent way by calling the serial8250_update_uartclk() method provided in this patch. Though note that it isn't supposed to be called from within the UART port callbacks because the locks using to the protect the UART port data are already taken in there. Signed-off-by: Serge Semin <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 699cc4d commit 868f3ee

File tree

2 files changed

+42
-0
lines changed

2 files changed

+42
-0
lines changed

drivers/tty/serial/8250/8250_port.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2632,6 +2632,46 @@ static unsigned int serial8250_get_baud_rate(struct uart_port *port,
26322632
(port->uartclk + tolerance) / 16);
26332633
}
26342634

2635+
/*
2636+
* Note in order to avoid the tty port mutex deadlock don't use the next method
2637+
* within the uart port callbacks. Primarily it's supposed to be utilized to
2638+
* handle a sudden reference clock rate change.
2639+
*/
2640+
void serial8250_update_uartclk(struct uart_port *port, unsigned int uartclk)
2641+
{
2642+
struct uart_8250_port *up = up_to_u8250p(port);
2643+
unsigned int baud, quot, frac = 0;
2644+
struct ktermios *termios;
2645+
unsigned long flags;
2646+
2647+
mutex_lock(&port->state->port.mutex);
2648+
2649+
if (port->uartclk == uartclk)
2650+
goto out_lock;
2651+
2652+
port->uartclk = uartclk;
2653+
termios = &port->state->port.tty->termios;
2654+
2655+
baud = serial8250_get_baud_rate(port, termios, NULL);
2656+
quot = serial8250_get_divisor(port, baud, &frac);
2657+
2658+
serial8250_rpm_get(up);
2659+
spin_lock_irqsave(&port->lock, flags);
2660+
2661+
uart_update_timeout(port, termios->c_cflag, baud);
2662+
2663+
serial8250_set_divisor(port, baud, quot, frac);
2664+
serial_port_out(port, UART_LCR, up->lcr);
2665+
serial8250_out_MCR(up, UART_MCR_DTR | UART_MCR_RTS);
2666+
2667+
spin_unlock_irqrestore(&port->lock, flags);
2668+
serial8250_rpm_put(up);
2669+
2670+
out_lock:
2671+
mutex_unlock(&port->state->port.mutex);
2672+
}
2673+
EXPORT_SYMBOL_GPL(serial8250_update_uartclk);
2674+
26352675
void
26362676
serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios,
26372677
struct ktermios *old)

include/linux/serial_8250.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ extern int early_serial_setup(struct uart_port *port);
155155

156156
extern int early_serial8250_setup(struct earlycon_device *device,
157157
const char *options);
158+
extern void serial8250_update_uartclk(struct uart_port *port,
159+
unsigned int uartclk);
158160
extern void serial8250_do_set_termios(struct uart_port *port,
159161
struct ktermios *termios, struct ktermios *old);
160162
extern void serial8250_do_set_ldisc(struct uart_port *port,

0 commit comments

Comments
 (0)