diff options
Diffstat (limited to 'drivers/tty/serial/sifive.c')
| -rw-r--r-- | drivers/tty/serial/sifive.c | 224 |
1 files changed, 136 insertions, 88 deletions
diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index 0b5110dad051..110d67613192 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,13 +134,14 @@ */ /** - * 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 + * @console_line_ended: indicate that the console line is fully written * * Configuration data specific to this SiFive UART. */ @@ -158,10 +149,10 @@ 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; + bool console_line_ended; }; /* @@ -299,33 +290,12 @@ static void __ssp_transmit_char(struct sifive_serial_port *ssp, int ch) */ static void __ssp_transmit_chars(struct sifive_serial_port *ssp) { - struct circ_buf *xmit = &ssp->port.state->xmit; - int count; - - if (ssp->port.x_char) { - __ssp_transmit_char(ssp, ssp->port.x_char); - ssp->port.icount.tx++; - ssp->port.x_char = 0; - return; - } - if (uart_circ_empty(xmit) || uart_tx_stopped(&ssp->port)) { - sifive_serial_stop_tx(&ssp->port); - return; - } - count = SIFIVE_TX_FIFO_DEPTH; - do { - __ssp_transmit_char(ssp, xmit->buf[xmit->tail]); - xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - ssp->port.icount.tx++; - if (uart_circ_empty(xmit)) - break; - } while (--count > 0); - - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(&ssp->port); + u8 ch; - if (uart_circ_empty(xmit)) - sifive_serial_stop_tx(&ssp->port); + uart_port_tx_limited(&ssp->port, ch, SIFIVE_TX_FIFO_DEPTH, + true, + __ssp_transmit_char(ssp, ch), + ({})); } /** @@ -434,9 +404,9 @@ static char __ssp_receive_char(struct sifive_serial_port *ssp, char *is_empty) */ static void __ssp_receive_chars(struct sifive_serial_port *ssp) { - unsigned char ch; char is_empty; int c; + u8 ch; for (c = SIFIVE_RX_FIFO_DEPTH; c > 0; --c) { ch = __ssp_receive_char(ssp, &is_empty); @@ -444,12 +414,11 @@ static void __ssp_receive_chars(struct sifive_serial_port *ssp) break; ssp->port.icount.rx++; - uart_insert_char(&ssp->port, 0, 0, ch, TTY_NORMAL); + if (!uart_prepare_sysrq_char(&ssp->port, ch)) + 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 +433,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); } @@ -555,11 +524,11 @@ static irqreturn_t sifive_serial_irq(int irq, void *dev_id) struct sifive_serial_port *ssp = dev_id; u32 ip; - spin_lock(&ssp->port.lock); + uart_port_lock(&ssp->port); ip = __ssp_readl(ssp, SIFIVE_SERIAL_IP_OFFS); if (!ip) { - spin_unlock(&ssp->port.lock); + uart_port_unlock(&ssp->port); return IRQ_NONE; } @@ -568,7 +537,7 @@ static irqreturn_t sifive_serial_irq(int irq, void *dev_id) if (ip & SIFIVE_SERIAL_IP_TXWM_MASK) __ssp_transmit_chars(ssp); - spin_unlock(&ssp->port.lock); + uart_unlock_and_check_sysrq(&ssp->port); return IRQ_HANDLED; } @@ -596,8 +565,11 @@ static void sifive_serial_break_ctl(struct uart_port *port, int break_state) static int sifive_serial_startup(struct uart_port *port) { struct sifive_serial_port *ssp = port_to_sifive_serial_port(port); + unsigned long flags; + uart_port_lock_irqsave(&ssp->port, &flags); __ssp_enable_rxwm(ssp); + uart_port_unlock_irqrestore(&ssp->port, flags); return 0; } @@ -605,9 +577,12 @@ static int sifive_serial_startup(struct uart_port *port) static void sifive_serial_shutdown(struct uart_port *port) { struct sifive_serial_port *ssp = port_to_sifive_serial_port(port); + unsigned long flags; + uart_port_lock_irqsave(&ssp->port, &flags); __ssp_disable_rxwm(ssp); __ssp_disable_txwm(ssp); + uart_port_unlock_irqrestore(&ssp->port, flags); } /** @@ -649,8 +624,8 @@ static int sifive_serial_clk_notifier(struct notifier_block *nb, 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; + if (event == POST_RATE_CHANGE && ssp->port.uartclk != cnd->new_rate) { + ssp->port.uartclk = cnd->new_rate; __ssp_update_div(ssp); } @@ -659,7 +634,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; @@ -667,22 +642,27 @@ 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); + uart_port_lock_irqsave(&ssp->port, &flags); /* Update the per-port timeout */ uart_update_timeout(port, termios->c_cflag, rate); @@ -699,7 +679,7 @@ static void sifive_serial_set_termios(struct uart_port *port, if (v != old_v) __ssp_writel(v, SIFIVE_SERIAL_RXCTRL_OFFS, ssp); - spin_unlock_irqrestore(&ssp->port.lock, flags); + uart_port_unlock_irqrestore(&ssp->port, flags); } static void sifive_serial_release_port(struct uart_port *port) @@ -757,7 +737,7 @@ static void sifive_serial_poll_put_char(struct uart_port *port, */ #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) @@ -789,7 +769,7 @@ static int __init early_sifive_serial_setup(struct earlycon_device *dev, } OF_EARLYCON_DECLARE(sifive, "sifive,uart0", early_sifive_serial_setup); -OF_EARLYCON_DECLARE(sifive, "sifive,fu540-c000-uart0", +OF_EARLYCON_DECLARE(sifive, "sifive,fu540-c000-uart", early_sifive_serial_setup); #endif /* CONFIG_SERIAL_EARLYCON */ @@ -801,46 +781,97 @@ 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); __ssp_wait_for_xmitr(ssp); __ssp_transmit_char(ssp, ch); + + ssp->console_line_ended = (ch == '\n'); } -static void sifive_serial_console_write(struct console *co, const char *s, - unsigned int count) +static void sifive_serial_device_lock(struct console *co, unsigned long *flags) +{ + struct uart_port *up = &sifive_serial_console_ports[co->index]->port; + + __uart_port_lock_irqsave(up, flags); +} + +static void sifive_serial_device_unlock(struct console *co, unsigned long flags) +{ + struct uart_port *up = &sifive_serial_console_ports[co->index]->port; + + __uart_port_unlock_irqrestore(up, flags); +} + +static void sifive_serial_console_write_atomic(struct console *co, + struct nbcon_write_context *wctxt) { struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index]; - unsigned long flags; + struct uart_port *port = &ssp->port; unsigned int ier; - int locked = 1; if (!ssp) return; - local_irq_save(flags); - if (ssp->port.sysrq) - locked = 0; - else if (oops_in_progress) - locked = spin_trylock(&ssp->port.lock); - else - spin_lock(&ssp->port.lock); + if (!nbcon_enter_unsafe(wctxt)) + return; ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS); __ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp); - uart_console_write(&ssp->port, s, count, sifive_serial_console_putchar); + if (!ssp->console_line_ended) + uart_console_write(port, "\n", 1, sifive_serial_console_putchar); + uart_console_write(port, wctxt->outbuf, wctxt->len, + sifive_serial_console_putchar); __ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp); - if (locked) - spin_unlock(&ssp->port.lock); - local_irq_restore(flags); + nbcon_exit_unsafe(wctxt); } -static int __init sifive_serial_console_setup(struct console *co, char *options) +static void sifive_serial_console_write_thread(struct console *co, + struct nbcon_write_context *wctxt) +{ + struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index]; + struct uart_port *port = &ssp->port; + unsigned int ier; + + if (!ssp) + return; + + if (!nbcon_enter_unsafe(wctxt)) + return; + + ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS); + __ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp); + + if (nbcon_exit_unsafe(wctxt)) { + int len = READ_ONCE(wctxt->len); + int i; + + for (i = 0; i < len; i++) { + if (!nbcon_enter_unsafe(wctxt)) + break; + + uart_console_write(port, wctxt->outbuf + i, 1, + sifive_serial_console_putchar); + + if (!nbcon_exit_unsafe(wctxt)) + break; + } + } + + while (!nbcon_enter_unsafe(wctxt)) + nbcon_reacquire_nobuf(wctxt); + + __ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp); + + nbcon_exit_unsafe(wctxt); +} + +static int sifive_serial_console_setup(struct console *co, char *options) { struct sifive_serial_port *ssp; int baud = SIFIVE_DEFAULT_BAUD_RATE; @@ -855,6 +886,8 @@ static int __init sifive_serial_console_setup(struct console *co, char *options) if (!ssp) return -ENODEV; + ssp->console_line_ended = true; + if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); @@ -865,10 +898,13 @@ static struct uart_driver sifive_serial_uart_driver; static struct console sifive_serial_console = { .name = SIFIVE_TTY_PREFIX, - .write = sifive_serial_console_write, + .write_atomic = sifive_serial_console_write_atomic, + .write_thread = sifive_serial_console_write_thread, + .device_lock = sifive_serial_device_lock, + .device_unlock = sifive_serial_device_unlock, .device = uart_console_device, .setup = sifive_serial_console_setup, - .flags = CON_PRINTBUFFER, + .flags = CON_PRINTBUFFER | CON_NBCON, .index = -1, .data = &sifive_serial_uart_driver, }; @@ -883,13 +919,12 @@ console_initcall(sifive_console_init); static void __ssp_add_console_port(struct sifive_serial_port *ssp) { - spin_lock_init(&ssp->port.lock); sifive_serial_console_ports[ssp->port.line] = 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) @@ -947,14 +982,11 @@ static int sifive_serial_probe(struct platform_device *pdev) if (irq < 0) return -EPROBE_DEFER; - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(base)) { - dev_err(&pdev->dev, "could not acquire device memory\n"); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(base)) 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); @@ -998,7 +1030,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); @@ -1040,7 +1072,7 @@ probe_out1: return r; } -static int sifive_serial_remove(struct platform_device *dev) +static void sifive_serial_remove(struct platform_device *dev) { struct sifive_serial_port *ssp = platform_get_drvdata(dev); @@ -1048,12 +1080,27 @@ static int sifive_serial_remove(struct platform_device *dev) uart_remove_one_port(&sifive_serial_uart_driver, &ssp->port); free_irq(ssp->port.irq, ssp); clk_notifier_unregister(ssp->clk, &ssp->clk_notifier); +} - return 0; +static int sifive_serial_suspend(struct device *dev) +{ + struct sifive_serial_port *ssp = dev_get_drvdata(dev); + + return uart_suspend_port(&sifive_serial_uart_driver, &ssp->port); } +static int sifive_serial_resume(struct device *dev) +{ + struct sifive_serial_port *ssp = dev_get_drvdata(dev); + + return uart_resume_port(&sifive_serial_uart_driver, &ssp->port); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sifive_uart_pm_ops, sifive_serial_suspend, + sifive_serial_resume); + static const struct of_device_id sifive_serial_of_match[] = { - { .compatible = "sifive,fu540-c000-uart0" }, + { .compatible = "sifive,fu540-c000-uart" }, { .compatible = "sifive,uart0" }, {}, }; @@ -1064,7 +1111,8 @@ static struct platform_driver sifive_serial_platform_driver = { .remove = sifive_serial_remove, .driver = { .name = SIFIVE_SERIAL_NAME, - .of_match_table = of_match_ptr(sifive_serial_of_match), + .pm = pm_sleep_ptr(&sifive_uart_pm_ops), + .of_match_table = sifive_serial_of_match, }, }; |
