diff options
Diffstat (limited to 'drivers/tty/serial/sifive.c')
-rw-r--r-- | drivers/tty/serial/sifive.c | 99 |
1 files changed, 69 insertions, 30 deletions
diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index d5f81b98e4d7..7fb6760b5c37 100644 --- a/drivers/tty/serial/sifive.c +++ b/drivers/tty/serial/sifive.c @@ -4,16 +4,6 @@ * Copyright (C) 2018 Paul Walmsley <paul@pwsan.com> * Copyright (C) 2018-2019 SiFive * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * * Based partially on: * - drivers/tty/serial/pxa.c * - drivers/tty/serial/amba-pl011.c @@ -144,12 +134,12 @@ */ /** - * sifive_serial_port - driver-specific data extension to struct uart_port + * struct sifive_serial_port - driver-specific data extension to struct uart_port * @port: struct uart_port embedded in this struct * @dev: struct device * * @ier: shadowed copy of the interrupt enable register - * @clkin_rate: input clock to the UART IP block. * @baud_rate: UART serial line rate (e.g., 115200 baud) + * @clk: reference to this device's clock * @clk_notifier: clock rate change notifier for upstream clock changes * * Configuration data specific to this SiFive UART. @@ -158,7 +148,6 @@ struct sifive_serial_port { struct uart_port port; struct device *dev; unsigned char ier; - unsigned long clkin_rate; unsigned long baud_rate; struct clk *clk; struct notifier_block clk_notifier; @@ -447,9 +436,7 @@ static void __ssp_receive_chars(struct sifive_serial_port *ssp) uart_insert_char(&ssp->port, 0, 0, ch, TTY_NORMAL); } - spin_unlock(&ssp->port.lock); tty_flip_buffer_push(&ssp->port.state->port); - spin_lock(&ssp->port.lock); } /** @@ -464,7 +451,7 @@ static void __ssp_update_div(struct sifive_serial_port *ssp) { u16 div; - div = DIV_ROUND_UP(ssp->clkin_rate, ssp->baud_rate) - 1; + div = DIV_ROUND_UP(ssp->port.uartclk, ssp->baud_rate) - 1; __ssp_writel(div, SIFIVE_SERIAL_DIV_OFFS, ssp); } @@ -618,10 +605,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,8 +616,28 @@ 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 == POST_RATE_CHANGE && ssp->clkin_rate != cnd->new_rate) { - ssp->clkin_rate = cnd->new_rate; + 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->port.uartclk != cnd->new_rate) { + ssp->port.uartclk = cnd->new_rate; __ssp_update_div(ssp); } @@ -639,7 +646,7 @@ static int sifive_serial_clk_notifier(struct notifier_block *nb, static void sifive_serial_set_termios(struct uart_port *port, struct ktermios *termios, - struct ktermios *old) + const struct ktermios *old) { struct sifive_serial_port *ssp = port_to_sifive_serial_port(port); unsigned long flags; @@ -647,19 +654,24 @@ static void sifive_serial_set_termios(struct uart_port *port, int rate; char nstop; - if ((termios->c_cflag & CSIZE) != CS8) + if ((termios->c_cflag & CSIZE) != CS8) { dev_err_once(ssp->port.dev, "only 8-bit words supported\n"); + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + } if (termios->c_iflag & (INPCK | PARMRK)) dev_err_once(ssp->port.dev, "parity checking not supported\n"); if (termios->c_iflag & BRKINT) dev_err_once(ssp->port.dev, "BREAK detection not supported\n"); + termios->c_iflag &= ~(INPCK|PARMRK|BRKINT); /* Set number of stop bits */ nstop = (termios->c_cflag & CSTOPB) ? 2 : 1; __ssp_set_stop_bits(ssp, nstop); /* Set line rate */ - rate = uart_get_baud_rate(port, termios, old, 0, ssp->clkin_rate / 16); + rate = uart_get_baud_rate(port, termios, old, 0, + ssp->port.uartclk / 16); __ssp_update_baud_rate(ssp, rate); spin_lock_irqsave(&ssp->port.lock, flags); @@ -709,12 +721,35 @@ 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 */ #ifdef CONFIG_SERIAL_EARLYCON -static void early_sifive_serial_putc(struct uart_port *port, int c) +static void early_sifive_serial_putc(struct uart_port *port, unsigned char c) { while (__ssp_early_readl(port, SIFIVE_SERIAL_TXDATA_OFFS) & SIFIVE_SERIAL_TXDATA_FULL_MASK) @@ -758,7 +793,7 @@ OF_EARLYCON_DECLARE(sifive, "sifive,fu540-c000-uart0", static struct sifive_serial_port *sifive_serial_console_ports[SIFIVE_SERIAL_MAX_PORTS]; -static void sifive_serial_console_putchar(struct uart_port *port, int ch) +static void sifive_serial_console_putchar(struct uart_port *port, unsigned char ch) { struct sifive_serial_port *ssp = port_to_sifive_serial_port(port); @@ -845,7 +880,7 @@ static void __ssp_add_console_port(struct sifive_serial_port *ssp) static void __ssp_remove_console_port(struct sifive_serial_port *ssp) { - sifive_serial_console_ports[ssp->port.line] = 0; + sifive_serial_console_ports[ssp->port.line] = NULL; } #define SIFIVE_SERIAL_CONSOLE (&sifive_serial_console) @@ -877,6 +912,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 = { @@ -906,7 +945,7 @@ static int sifive_serial_probe(struct platform_device *pdev) return PTR_ERR(base); } - clk = devm_clk_get(&pdev->dev, NULL); + clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(clk)) { dev_err(&pdev->dev, "unable to find controller clock\n"); return PTR_ERR(clk); @@ -950,7 +989,7 @@ static int sifive_serial_probe(struct platform_device *pdev) } /* Set up clock divider */ - ssp->clkin_rate = clk_get_rate(ssp->clk); + ssp->port.uartclk = clk_get_rate(ssp->clk); ssp->baud_rate = SIFIVE_DEFAULT_BAUD_RATE; __ssp_update_div(ssp); |