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.c224
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,
},
};