diff options
Diffstat (limited to 'drivers/tty/serial/sifive.c')
-rw-r--r-- | drivers/tty/serial/sifive.c | 55 |
1 files changed, 51 insertions, 4 deletions
diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index d5f81b98e4d7..13eadcb8aec4 100644 --- a/drivers/tty/serial/sifive.c +++ b/drivers/tty/serial/sifive.c @@ -618,10 +618,10 @@ static void sifive_serial_shutdown(struct uart_port *port) * * On the V0 SoC, the UART IP block is derived from the CPU clock source * after a synchronous divide-by-two divider, so any CPU clock rate change - * requires the UART baud rate to be updated. This presumably could corrupt any - * serial word currently being transmitted or received. It would probably - * be better to stop receives and transmits, then complete the baud rate - * change, then re-enable them. + * requires the UART baud rate to be updated. This presumably corrupts any + * serial word currently being transmitted or received. In order to avoid + * corrupting the output data stream, we drain the transmit queue before + * allowing the clock's rate to be changed. */ static int sifive_serial_clk_notifier(struct notifier_block *nb, unsigned long event, void *data) @@ -629,6 +629,26 @@ static int sifive_serial_clk_notifier(struct notifier_block *nb, struct clk_notifier_data *cnd = data; struct sifive_serial_port *ssp = notifier_to_sifive_serial_port(nb); + if (event == PRE_RATE_CHANGE) { + /* + * The TX watermark is always set to 1 by this driver, which + * means that the TX busy bit will lower when there are 0 bytes + * left in the TX queue -- in other words, when the TX FIFO is + * empty. + */ + __ssp_wait_for_xmitr(ssp); + /* + * On the cycle the TX FIFO goes empty there is still a full + * UART frame left to be transmitted in the shift register. + * The UART provides no way for software to directly determine + * when that last frame has been transmitted, so we just sleep + * here instead. As we're not tracking the number of stop bits + * they're just worst cased here. The rest of the serial + * framing parameters aren't configurable by software. + */ + udelay(DIV_ROUND_UP(12 * 1000 * 1000, ssp->baud_rate)); + } + if (event == POST_RATE_CHANGE && ssp->clkin_rate != cnd->new_rate) { ssp->clkin_rate = cnd->new_rate; __ssp_update_div(ssp); @@ -709,6 +729,29 @@ static const char *sifive_serial_type(struct uart_port *port) return port->type == PORT_SIFIVE_V0 ? "SiFive UART v0" : NULL; } +#ifdef CONFIG_CONSOLE_POLL +static int sifive_serial_poll_get_char(struct uart_port *port) +{ + struct sifive_serial_port *ssp = port_to_sifive_serial_port(port); + char is_empty, ch; + + ch = __ssp_receive_char(ssp, &is_empty); + if (is_empty) + return NO_POLL_CHAR; + + return ch; +} + +static void sifive_serial_poll_put_char(struct uart_port *port, + unsigned char c) +{ + struct sifive_serial_port *ssp = port_to_sifive_serial_port(port); + + __ssp_wait_for_xmitr(ssp); + __ssp_transmit_char(ssp, c); +} +#endif /* CONFIG_CONSOLE_POLL */ + /* * Early console support */ @@ -877,6 +920,10 @@ static const struct uart_ops sifive_serial_uops = { .request_port = sifive_serial_request_port, .config_port = sifive_serial_config_port, .verify_port = sifive_serial_verify_port, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = sifive_serial_poll_get_char, + .poll_put_char = sifive_serial_poll_put_char, +#endif }; static struct uart_driver sifive_serial_uart_driver = { |