summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/sifive.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serial/sifive.c')
-rw-r--r--drivers/tty/serial/sifive.c55
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 = {