From 6a1c34f4fd17ba647665fab1a8be6f22ae1f2ca8 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Thu, 8 Jan 2015 14:19:49 +1000 Subject: serial: mcf: change email address to gerg@uclinux.org Change my email contact address from the obsolete gerg@snapgear.com to gerg@uclinux.org. Signed-off-by: Greg Ungerer Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mcf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mcf.c b/drivers/tty/serial/mcf.c index 10496672dfd..1eb02bd7415 100644 --- a/drivers/tty/serial/mcf.c +++ b/drivers/tty/serial/mcf.c @@ -3,7 +3,7 @@ /* * mcf.c -- Freescale ColdFire UART driver * - * (C) Copyright 2003-2007, Greg Ungerer + * (C) Copyright 2003-2007, Greg Ungerer * * 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 @@ -702,7 +702,7 @@ static void __exit mcf_exit(void) module_init(mcf_init); module_exit(mcf_exit); -MODULE_AUTHOR("Greg Ungerer "); +MODULE_AUTHOR("Greg Ungerer "); MODULE_DESCRIPTION("Freescale ColdFire UART driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:mcfuart"); -- cgit v1.2.3-70-g09d2 From ebffbc6cdb8126f1034f5e0e6cd844efb1c3f03f Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Thu, 8 Jan 2015 14:19:50 +1000 Subject: serial: mcf: fix compile warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trivially fix the following compile time warnings: CC drivers/tty/serial/mcf.o drivers/tty/serial/mcf.c: In function ‘mcf_set_termios’: drivers/tty/serial/mcf.c:201:19: warning: unused variable ‘pp’ drivers/tty/serial/mcf.c: In function ‘mcf_config_rs485’: drivers/tty/serial/mcf.c:444:19: warning: unused variable ‘pp’ Signed-off-by: Greg Ungerer Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mcf.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mcf.c b/drivers/tty/serial/mcf.c index 1eb02bd7415..35f4719b2c0 100644 --- a/drivers/tty/serial/mcf.c +++ b/drivers/tty/serial/mcf.c @@ -198,7 +198,6 @@ static void mcf_shutdown(struct uart_port *port) static void mcf_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { - struct mcf_uart *pp = container_of(port, struct mcf_uart, port); unsigned long flags; unsigned int baud, baudclk; #if defined(CONFIG_M5272) @@ -441,7 +440,6 @@ static int mcf_verify_port(struct uart_port *port, struct serial_struct *ser) /* Enable or disable the RS485 support */ static int mcf_config_rs485(struct uart_port *port, struct serial_rs485 *rs485) { - struct mcf_uart *pp = container_of(port, struct mcf_uart, port); unsigned char mr1, mr2; /* Get mode registers */ -- cgit v1.2.3-70-g09d2 From 201d8975ae5ada760bf29edd837da039f494709a Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Thu, 8 Jan 2015 14:19:51 +1000 Subject: serial: mcf: fix no driver name in probe output The current output of the device probing looks like this: ColdFire internal UART serial driver (NULL device *): ttyS0 at MMIO 0xfc060000 (irq = 90, base_baud = 5208333) is a ColdFire UART console [ttyS0] enabled (NULL device *): ttyS1 at MMIO 0xfc064000 (irq = 91, base_baud = 5208333) is a ColdFire UART (NULL device *): ttyS2 at MMIO 0xfc068000 (irq = 92, base_baud = 5208333) is a ColdFire UART Fix the port device linkage at probing time so that the device name is printed out properly, and not "(NULL device *)". Signed-off-by: Greg Ungerer Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mcf.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/tty/serial/mcf.c b/drivers/tty/serial/mcf.c index 35f4719b2c0..a9b0ab38a68 100644 --- a/drivers/tty/serial/mcf.c +++ b/drivers/tty/serial/mcf.c @@ -629,6 +629,7 @@ static int mcf_probe(struct platform_device *pdev) port->mapbase = platp[i].mapbase; port->membase = (platp[i].membase) ? platp[i].membase : (unsigned char __iomem *) platp[i].mapbase; + port->dev = &pdev->dev; port->iotype = SERIAL_IO_MEM; port->irq = platp[i].irq; port->uartclk = MCF_BUSCLK; -- cgit v1.2.3-70-g09d2 From 00648d0282dcca1da453c0b20d27e24bf901a854 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Thu, 18 Dec 2014 18:47:12 +0100 Subject: tty: serial: 8250: omap: add ttySx console if the user didn't This patch invokes add_preferred_console() with ttyS based on ttyO arguments if the user didn't specify it on its own. This ensures that the user will see the kernel booting on his serial console in case he forgot to update the command line. Signed-off-by: Sebastian Andrzej Siewior Acked-by: Alan Cox Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 40 +++++++++++++++++++++++++++++++++++++ drivers/tty/serial/8250/Kconfig | 19 ++++++++++++++++++ 2 files changed, 59 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 96b69bfd773..273f37c6b49 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -1248,6 +1248,46 @@ static int omap8250_runtime_resume(struct device *dev) } #endif +#ifdef CONFIG_SERIAL_8250_OMAP_TTYO_FIXUP +static int __init omap8250_console_fixup(void) +{ + char *omap_str; + char *options; + u8 idx; + + if (strstr(boot_command_line, "console=ttyS")) + /* user set a ttyS based name for the console */ + return 0; + + omap_str = strstr(boot_command_line, "console=ttyO"); + if (!omap_str) + /* user did not set ttyO based console, so we don't care */ + return 0; + + omap_str += 12; + if ('0' <= *omap_str && *omap_str <= '9') + idx = *omap_str - '0'; + else + return 0; + + omap_str++; + if (omap_str[0] == ',') { + omap_str++; + options = omap_str; + } else { + options = NULL; + } + + add_preferred_console("ttyS", idx, options); + pr_err("WARNING: Your 'console=ttyO%d' has been replaced by 'ttyS%d'\n", + idx, idx); + pr_err("This ensures that you still see kernel messages. Please\n"); + pr_err("update your kernel commandline.\n"); + return 0; +} +console_initcall(omap8250_console_fixup); +#endif + static const struct dev_pm_ops omap8250_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(omap8250_suspend, omap8250_resume) SET_RUNTIME_PM_OPS(omap8250_runtime_suspend, diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index 0fcbcd29502..6f7f2d753de 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -308,6 +308,25 @@ config SERIAL_8250_OMAP This driver uses ttyS instead of ttyO. +config SERIAL_8250_OMAP_TTYO_FIXUP + bool "Replace ttyO with ttyS" + depends on SERIAL_8250_OMAP=y && SERIAL_8250_CONSOLE + default y + help + This option replaces the "console=ttyO" argument with the matching + ttyS argument if the user did not specified it on the command line. + This ensures that the user can see the kernel output during boot + which he wouldn't see otherwise. The getty has still to be configured + for ttyS instead of ttyO regardless of this option. + This option is intended for people who "automatically" enable this + driver without knowing that this driver requires a different console= + argument. If you read this, please keep this option disabled and + instead update your kernel command line. If you prepare a kernel for a + distribution or other kind of larger user base then you probably want + to keep this option enabled. Otherwise people might complain about a + not booting kernel because the serial console remains silent in case + they forgot to update the command line. + config SERIAL_8250_FINTEK tristate "Support for Fintek F81216A LPC to 4 UART" depends on SERIAL_8250 && PNP -- cgit v1.2.3-70-g09d2 From fbf7ebe4d9faf2225d15edb9ebc66bf99800776a Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 11 Dec 2014 22:44:26 +0100 Subject: trivial fixes for omap-serial Trivial fixes for omap-serial. Signed-off-by: Pavel Machek index 18c30ca..f43ed2c 100644 Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/omap-serial.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c index 2e1073da671..b1cf9a3b673 100644 --- a/drivers/tty/serial/omap-serial.c +++ b/drivers/tty/serial/omap-serial.c @@ -63,7 +63,7 @@ #define UART_ERRATA_i202_MDR1_ACCESS BIT(0) #define UART_ERRATA_i291_DMA_FORCEIDLE BIT(1) -#define DEFAULT_CLK_SPEED 48000000 /* 48Mhz*/ +#define DEFAULT_CLK_SPEED 48000000 /* 48Mhz */ /* SCR register bitmasks */ #define OMAP_UART_SCR_RX_TRIG_GRANU1_MASK (1 << 7) @@ -93,7 +93,7 @@ /* WER = 0x7F * Enable module level wakeup in WER reg */ -#define OMAP_UART_WER_MOD_WKUP 0X7F +#define OMAP_UART_WER_MOD_WKUP 0x7F /* Enable XON/XOFF flow control on output */ #define OMAP_UART_SW_TX 0x08 @@ -114,7 +114,7 @@ struct uart_omap_dma { dma_addr_t tx_buf_dma_phys; unsigned int uart_base; /* - * Buffer for rx dma.It is not required for tx because the buffer + * Buffer for rx dma. It is not required for tx because the buffer * comes from port structure. */ unsigned char *rx_buf; @@ -151,7 +151,7 @@ struct uart_omap_port { int use_dma; /* * Some bits in registers are cleared on a read, so they must - * be saved whenever the register is read but the bits will not + * be saved whenever the register is read, but the bits will not * be immediately processed. */ unsigned int lsr_break_flag; -- cgit v1.2.3-70-g09d2 From 4bf4ea9dca4ba1984abeb592f429265b9bacac42 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 30 Dec 2014 20:28:15 -0500 Subject: serial: omap_8250: Fix RTS handling The OMAP3 UART ignores MCR[1] (ie., UART_MCR_RTS) when in autoRTS mode (UPF_HARD_FLOW + CRTSCTS). This makes it impossible for either the serial core or userspace to manually flow control the sender. Disable autoRTS mode when RTS is lowered and restore the previous mode when RTS is raised. Note that the OMAP3 UART provides no mechanism for switching from autoRTS mode without corrupting incoming data; to access the necessary register, the line control settings must be set to 8-e-2 and thus any data received during that time will be interpreted with those settings. This corruption has been observed in practice. Cc: Sebastian Andrzej Siewior Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 12 +++++++++++- drivers/tty/serial/8250/8250_omap.c | 25 ++++++++++++++++++++++--- include/linux/serial_8250.h | 1 + include/linux/serial_core.h | 1 + 4 files changed, 35 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 11c66856ba2..3bfcfdb57d0 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -1924,7 +1924,7 @@ static unsigned int serial8250_get_mctrl(struct uart_port *port) return ret; } -static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl) +void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl) { struct uart_8250_port *up = up_to_u8250p(port); unsigned char mcr = 0; @@ -1944,6 +1944,14 @@ static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl) serial_port_out(port, UART_MCR, mcr); } +EXPORT_SYMBOL_GPL(serial8250_do_set_mctrl); + +static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + if (port->set_mctrl) + return port->set_mctrl(port, mctrl); + return serial8250_do_set_mctrl(port, mctrl); +} static void serial8250_break_ctl(struct uart_port *port, int break_state) { @@ -3605,6 +3613,8 @@ int serial8250_register_8250_port(struct uart_8250_port *up) /* Possibly override set_termios call */ if (up->port.set_termios) uart->port.set_termios = up->port.set_termios; + if (up->port.set_mctrl) + uart->port.set_mctrl = up->port.set_mctrl; if (up->port.startup) uart->port.startup = up->port.startup; if (up->port.shutdown) diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 273f37c6b49..227033db214 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -106,6 +106,27 @@ static u32 uart_read(struct uart_8250_port *up, u32 reg) return readl(up->port.membase + (reg << up->port.regshift)); } +static void omap8250_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct uart_8250_port *up = up_to_u8250p(port); + struct omap8250_priv *priv = up->port.private_data; + u8 lcr; + + serial8250_do_set_mctrl(port, mctrl); + + /* + * Turn off autoRTS if RTS is lowered and restore autoRTS setting + * if RTS is raised + */ + lcr = serial_in(up, UART_LCR); + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); + if (mctrl & TIOCM_RTS) + serial_out(up, UART_EFR, priv->efr); + else + serial_out(up, UART_EFR, priv->efr & ~UART_EFR_RTS); + serial_out(up, UART_LCR, lcr); +} + /* * Work Around for Errata i202 (2430, 3430, 3630, 4430 and 4460) * The access to uart register after MDR1 Access @@ -400,9 +421,6 @@ static void omap_8250_set_termios(struct uart_port *port, if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) { /* Enable AUTORTS and AUTOCTS */ priv->efr |= UART_EFR_CTS | UART_EFR_RTS; - - /* Ensure MCR RTS is asserted */ - up->mcr |= UART_MCR_RTS; } else if (up->port.flags & UPF_SOFT_FLOW) { /* * IXON Flag: @@ -1007,6 +1025,7 @@ static int omap8250_probe(struct platform_device *pdev) up.capabilities |= UART_CAP_RPM; #endif up.port.set_termios = omap_8250_set_termios; + up.port.set_mctrl = omap8250_set_mctrl; up.port.pm = omap_8250_pm; up.port.startup = omap_8250_startup; up.port.shutdown = omap_8250_shutdown; diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index e02acf0a0ec..245b959f1ff 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -126,6 +126,7 @@ extern int serial8250_do_startup(struct uart_port *port); extern void serial8250_do_shutdown(struct uart_port *port); extern void serial8250_do_pm(struct uart_port *port, unsigned int state, unsigned int oldstate); +extern void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl); extern int fsl8250_handle_irq(struct uart_port *port); int serial8250_handle_irq(struct uart_port *port, unsigned int iir); unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 057038cf278..a0c7033d5f9 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -123,6 +123,7 @@ struct uart_port { void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); + void (*set_mctrl)(struct uart_port *, unsigned int); int (*startup)(struct uart_port *port); void (*shutdown)(struct uart_port *port); void (*throttle)(struct uart_port *port); -- cgit v1.2.3-70-g09d2 From 3e29af27c4686d9cb401a63c7eeb6376662b13c1 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 31 Dec 2014 16:32:49 -0500 Subject: serial: omap_8250: Fix RTS handling, part B Because the OMAP3 UART ignores MCR[1] (RTS) in autoRTS mode, autoRTS mode must not be enabled unless RTS is set (or port->mctrl & TIOCM_RTS, which is equivalent). Fixes premature raising of RTS in omap_8250_set_termios() -- RTS was raised even before UART mode was selected. Fixes raise of RTS after port has been shutdown; omap_8250_pm() re-enabled RTS after omap_8250_shutdown(). Cc: Sebastian Andrzej Siewior Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 227033db214..7c485eaf7dd 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -272,7 +272,10 @@ static void omap8250_restore_regs(struct uart_8250_port *up) serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); serial_dl_write(up, priv->quot); - serial_out(up, UART_EFR, priv->efr); + if (up->port.mctrl & TIOCM_RTS) + serial_out(up, UART_EFR, priv->efr); + else + serial_out(up, UART_EFR, priv->efr & ~UART_EFR_RTS); /* Configure flow control */ serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); @@ -468,18 +471,18 @@ static void omap_8250_set_termios(struct uart_port *port, static void omap_8250_pm(struct uart_port *port, unsigned int state, unsigned int oldstate) { - struct uart_8250_port *up = - container_of(port, struct uart_8250_port, port); - struct omap8250_priv *priv = up->port.private_data; + struct uart_8250_port *up = up_to_u8250p(port); + u8 efr; pm_runtime_get_sync(port->dev); serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - serial_out(up, UART_EFR, priv->efr | UART_EFR_ECB); + efr = serial_in(up, UART_EFR); + serial_out(up, UART_EFR, efr | UART_EFR_ECB); serial_out(up, UART_LCR, 0); serial_out(up, UART_IER, (state != 0) ? UART_IERX_SLEEP : 0); serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - serial_out(up, UART_EFR, priv->efr); + serial_out(up, UART_EFR, efr); serial_out(up, UART_LCR, 0); pm_runtime_mark_last_busy(port->dev); -- cgit v1.2.3-70-g09d2 From 11387b0a46a4b19f3aa8c7b85faff31d9bc1e471 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 27 Nov 2014 17:08:30 -0200 Subject: serial: mxs-auart: Remove unneeded goto label Instead of jumping to the 'out' label, let's return the error immediately, which makes the code shorter and cleaner. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index ec553f8eb21..a9e7fa68b75 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1232,10 +1232,8 @@ static int mxs_auart_probe(struct platform_device *pdev) struct resource *r; s = kzalloc(sizeof(struct mxs_auart_port), GFP_KERNEL); - if (!s) { - ret = -ENOMEM; - goto out; - } + if (!s) + return -ENOMEM; ret = serial_mxs_probe_dt(s, pdev); if (ret > 0) @@ -1314,7 +1312,6 @@ out_free_clk: clk_put(s->clk); out_free: kfree(s); -out: return ret; } -- cgit v1.2.3-70-g09d2 From 46778bcad73d0546c6fac24110dba5110902141f Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 27 Nov 2014 17:08:31 -0200 Subject: serial: mxs-auart: Use devm_kzalloc() By using devm_kzalloc() we can have a shorter and cleaner code. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index a9e7fa68b75..0b5f0ead6b7 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1231,7 +1231,7 @@ static int mxs_auart_probe(struct platform_device *pdev) int ret = 0; struct resource *r; - s = kzalloc(sizeof(struct mxs_auart_port), GFP_KERNEL); + s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); if (!s) return -ENOMEM; @@ -1239,7 +1239,7 @@ static int mxs_auart_probe(struct platform_device *pdev) if (ret > 0) s->port.line = pdev->id < 0 ? 0 : pdev->id; else if (ret < 0) - goto out_free; + return ret; if (of_id) { pdev->id_entry = of_id->data; @@ -1247,10 +1247,8 @@ static int mxs_auart_probe(struct platform_device *pdev) } s->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(s->clk)) { - ret = PTR_ERR(s->clk); - goto out_free; - } + if (IS_ERR(s->clk)) + return PTR_ERR(s->clk); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r) { @@ -1310,8 +1308,6 @@ out_free_irq: free_irq(s->irq, s); out_free_clk: clk_put(s->clk); -out_free: - kfree(s); return ret; } @@ -1326,7 +1322,6 @@ static int mxs_auart_remove(struct platform_device *pdev) mxs_auart_free_gpio_irq(s); clk_put(s->clk); free_irq(s->irq, s); - kfree(s); return 0; } -- cgit v1.2.3-70-g09d2 From 75beb268550d11e66c372dd6da309dce659da7f7 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 27 Nov 2014 17:08:32 -0200 Subject: serial: mxs-auart: Use devm_clk_get() By using devm_clk_get() we can have a shorter and cleaner code. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index 0b5f0ead6b7..9309082e493 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1246,15 +1246,14 @@ static int mxs_auart_probe(struct platform_device *pdev) s->devtype = pdev->id_entry->driver_data; } - s->clk = clk_get(&pdev->dev, NULL); + s->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(s->clk)) return PTR_ERR(s->clk); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r) { - ret = -ENXIO; - goto out_free_clk; - } + if (!r) + return -ENXIO; + s->port.mapbase = r->start; s->port.membase = ioremap(r->start, resource_size(r)); @@ -1271,7 +1270,7 @@ static int mxs_auart_probe(struct platform_device *pdev) s->port.irq = s->irq; ret = request_irq(s->irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s); if (ret) - goto out_free_clk; + return ret; platform_set_drvdata(pdev, s); @@ -1306,8 +1305,6 @@ out_free_gpio_irq: out_free_irq: auart_port[pdev->id] = NULL; free_irq(s->irq, s); -out_free_clk: - clk_put(s->clk); return ret; } @@ -1320,7 +1317,6 @@ static int mxs_auart_remove(struct platform_device *pdev) auart_port[pdev->id] = NULL; mxs_auart_free_gpio_irq(s); - clk_put(s->clk); free_irq(s->irq, s); return 0; -- cgit v1.2.3-70-g09d2 From 9e5df9f88c5d874a9154e137a2e99413750540cd Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 27 Nov 2014 17:08:33 -0200 Subject: serial: mxs-auart: Use devm_request_irq() By using devm_request_irq() we can have a shorter and cleaner code. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index 9309082e493..1e9fb370600 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1268,7 +1268,8 @@ static int mxs_auart_probe(struct platform_device *pdev) s->irq = platform_get_irq(pdev, 0); s->port.irq = s->irq; - ret = request_irq(s->irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s); + ret = devm_request_irq(&pdev->dev, s->irq, mxs_auart_irq_handle, 0, + dev_name(&pdev->dev), s); if (ret) return ret; @@ -1283,7 +1284,7 @@ static int mxs_auart_probe(struct platform_device *pdev) */ ret = mxs_auart_request_gpio_irq(s); if (ret) - goto out_free_irq; + return ret; auart_port[s->port.line] = s; @@ -1302,9 +1303,7 @@ static int mxs_auart_probe(struct platform_device *pdev) out_free_gpio_irq: mxs_auart_free_gpio_irq(s); -out_free_irq: auart_port[pdev->id] = NULL; - free_irq(s->irq, s); return ret; } @@ -1313,11 +1312,8 @@ static int mxs_auart_remove(struct platform_device *pdev) struct mxs_auart_port *s = platform_get_drvdata(pdev); uart_remove_one_port(&auart_driver, &s->port); - auart_port[pdev->id] = NULL; - mxs_auart_free_gpio_irq(s); - free_irq(s->irq, s); return 0; } -- cgit v1.2.3-70-g09d2 From 9bdd6369aa64fade997c6f11325f7c7b60ec3f6a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 28 Nov 2014 16:17:11 +0200 Subject: serial: 8250_dma: terminate transfer explicitly when timeout There is no need to terminate transfer in the __dma_rx_complete() since it's called at the end of transfer. Special case when serial timeout occurs is handled separately where we have to terminate transfer. Signed-off-by: Andy Shevchenko Reviewed-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c index fcd7ac6af2f..e508939daea 100644 --- a/drivers/tty/serial/8250/8250_dma.c +++ b/drivers/tty/serial/8250/8250_dma.c @@ -59,7 +59,6 @@ static void __dma_rx_complete(void *param) dma->rx_running = 0; dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state); - dmaengine_terminate_all(dma->rxchan); count = dma->rx_size - state.residue; @@ -131,6 +130,7 @@ int serial8250_rx_dma(struct uart_8250_port *p, unsigned int iir) if (dma->rx_running) { dmaengine_pause(dma->rxchan); __dma_rx_complete(p); + dmaengine_terminate_all(dma->rxchan); } return -ETIMEDOUT; default: -- cgit v1.2.3-70-g09d2 From 5e1aeea52f6a0763e79473b1767401fda88eb7e1 Mon Sep 17 00:00:00 2001 From: Feng Kan Date: Fri, 5 Dec 2014 17:45:57 -0800 Subject: serial: 8250: add support for ACPI-probed serial port for X-Gene platform Enable APM X-Gene SoC serial port functionality when using ACPI table to initialize serial port. Signed-off-by: Feng Kan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dw.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 555de07db59..e6011623583 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -351,10 +351,20 @@ static int dw8250_probe_of(struct uart_port *p, static int dw8250_probe_acpi(struct uart_8250_port *up, struct dw8250_data *data) { + const struct acpi_device_id *id; struct uart_port *p = &up->port; dw8250_setup_port(up); + id = acpi_match_device(p->dev->driver->acpi_match_table, p->dev); + if (!id) + return -ENODEV; + + if (!p->uartclk) + if (device_property_read_u32(p->dev, "clock-frequency", + &p->uartclk)) + return -EINVAL; + p->iotype = UPIO_MEM32; p->serial_in = dw8250_serial_in32; p->serial_out = dw8250_serial_out32; @@ -577,6 +587,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = { { "INT3435", 0 }, { "80860F0A", 0 }, { "8086228A", 0 }, + { "APMC0D08", 0}, { }, }; MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match); -- cgit v1.2.3-70-g09d2 From 2e4081367bb108db0b20e1cbb09fcb8885f894b3 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Dec 2014 22:14:41 +0100 Subject: tty: serial: 8250: drop owner assignment from platform_drivers This platform_driver does not need to set an owner, it will be populated by the driver core. Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 7c485eaf7dd..9191d05cfaf 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -1331,7 +1331,6 @@ static struct platform_driver omap8250_platform_driver = { .name = "omap8250", .pm = &omap8250_dev_pm_ops, .of_match_table = omap8250_dt_ids, - .owner = THIS_MODULE, }, .probe = omap8250_probe, .remove = omap8250_remove, -- cgit v1.2.3-70-g09d2 From eef15e2a54fad4c2ce3f0a81485dc591ce678f4f Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 30 Dec 2014 07:11:11 -0500 Subject: tty: Remove warning in tty_lock_slave() Commit 2aff5e2bc62db43e05c814461a08aff0fc2b7fe5 ('tty: Change tty lock order to master->slave') added a warning which is broken and unnecessary now that the tty lock has fixed lock subclasses, added in commit 2febdb632bb96235b94b8fccaf882a78f8f4b2bb ('tty: Preset lock subclass for nested tty locks'). Reported-by: Dan Carpenter Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_mutex.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/tty_mutex.c b/drivers/tty/tty_mutex.c index 4486741190c..a872389dc0b 100644 --- a/drivers/tty/tty_mutex.c +++ b/drivers/tty/tty_mutex.c @@ -46,12 +46,8 @@ EXPORT_SYMBOL(tty_unlock); void __lockfunc tty_lock_slave(struct tty_struct *tty) { - if (tty && tty != tty->link) { - WARN_ON(!mutex_is_locked(&tty->link->legacy_mutex) || - !tty->driver->type == TTY_DRIVER_TYPE_PTY || - !tty->driver->type == PTY_TYPE_SLAVE); + if (tty && tty != tty->link) tty_lock(tty); - } } void __lockfunc tty_unlock_slave(struct tty_struct *tty) -- cgit v1.2.3-70-g09d2 From 057badd6885b7149a0d083b35ffaac0c00937309 Mon Sep 17 00:00:00 2001 From: Barry Song Date: Sat, 3 Jan 2015 17:02:57 +0800 Subject: serial: sirf: rename marco to atlas7 MARCO will not be supported any more and the project was dropped. it has been replaced by CSR atlas7. Signed-off-by: Barry Song Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/serial/sirf-uart.txt | 4 +-- drivers/tty/serial/sirfsoc_uart.c | 40 +++++++++++----------- drivers/tty/serial/sirfsoc_uart.h | 4 +-- 3 files changed, 24 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/serial/sirf-uart.txt b/Documentation/devicetree/bindings/serial/sirf-uart.txt index 3acdd969edf..f0c39261c5d 100644 --- a/Documentation/devicetree/bindings/serial/sirf-uart.txt +++ b/Documentation/devicetree/bindings/serial/sirf-uart.txt @@ -2,7 +2,7 @@ Required properties: - compatible : Should be "sirf,prima2-uart", "sirf, prima2-usp-uart", - "sirf,marco-uart" or "sirf,marco-bt-uart" which means + "sirf,atlas7-uart" or "sirf,atlas7-bt-uart" which means uart located in BT module and used for BT. - reg : Offset and length of the register set for the device - interrupts : Should contain uart interrupt @@ -37,7 +37,7 @@ usp@b0090000 { for uart use in BT module, uart6: uart@11000000 { cell-index = <6>; - compatible = "sirf,marco-bt-uart", "sirf,marco-uart"; + compatible = "sirf,atlas7-bt-uart", "sirf,atlas7-uart"; reg = <0x11000000 0x1000>; interrupts = <0 100 0>; clocks = <&clks 138>, <&clks 140>, <&clks 141>; diff --git a/drivers/tty/serial/sirfsoc_uart.c b/drivers/tty/serial/sirfsoc_uart.c index b269f6bd16d..27ed0e96088 100644 --- a/drivers/tty/serial/sirfsoc_uart.c +++ b/drivers/tty/serial/sirfsoc_uart.c @@ -177,7 +177,7 @@ static void sirfsoc_uart_stop_tx(struct uart_port *port) dmaengine_pause(sirfport->tx_dma_chan); sirfport->tx_dma_state = TX_DMA_PAUSE; } else { - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) & ~uint_en->sirfsoc_txfifo_empty_en); @@ -186,7 +186,7 @@ static void sirfsoc_uart_stop_tx(struct uart_port *port) uint_en->sirfsoc_txfifo_empty_en); } } else { - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) & ~uint_en->sirfsoc_txfifo_empty_en); @@ -217,7 +217,7 @@ static void sirfsoc_uart_tx_with_dma(struct sirfsoc_uart_port *sirfport) } if (sirfport->tx_dma_state == TX_DMA_RUNNING) return; - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg)& ~(uint_en->sirfsoc_txfifo_empty_en)); @@ -244,7 +244,7 @@ static void sirfsoc_uart_tx_with_dma(struct sirfsoc_uart_port *sirfport) } if (tran_size < 4) sirfsoc_uart_pio_tx_chars(sirfport, tran_size); - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg)| uint_en->sirfsoc_txfifo_empty_en); @@ -293,7 +293,7 @@ static void sirfsoc_uart_start_tx(struct uart_port *port) sirfsoc_uart_pio_tx_chars(sirfport, SIRFSOC_UART_IO_TX_REASONABLE_CNT); wr_regl(port, ureg->sirfsoc_tx_fifo_op, SIRFUART_FIFO_START); - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg)| uint_en->sirfsoc_txfifo_empty_en); @@ -311,7 +311,7 @@ static void sirfsoc_uart_stop_rx(struct uart_port *port) wr_regl(port, ureg->sirfsoc_rx_fifo_op, 0); if (sirfport->rx_dma_chan) { - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) & ~(SIRFUART_RX_DMA_INT_EN(port, uint_en) | @@ -322,7 +322,7 @@ static void sirfsoc_uart_stop_rx(struct uart_port *port) uint_en->sirfsoc_rx_done_en); dmaengine_terminate_all(sirfport->rx_dma_chan); } else { - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg)& ~(SIRFUART_RX_IO_INT_EN(port, uint_en))); @@ -344,7 +344,7 @@ static void sirfsoc_uart_disable_ms(struct uart_port *port) if (sirfport->uart_reg->uart_type == SIRF_REAL_UART) { wr_regl(port, ureg->sirfsoc_afc_ctrl, rd_regl(port, ureg->sirfsoc_afc_ctrl) & ~0x3FF); - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg)& ~uint_en->sirfsoc_cts_en); @@ -380,7 +380,7 @@ static void sirfsoc_uart_enable_ms(struct uart_port *port) wr_regl(port, ureg->sirfsoc_afc_ctrl, rd_regl(port, ureg->sirfsoc_afc_ctrl) | SIRFUART_AFC_TX_EN | SIRFUART_AFC_RX_EN); - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) | uint_en->sirfsoc_cts_en); @@ -544,7 +544,7 @@ static void sirfsoc_rx_tmo_process_tl(unsigned long param) sirfport->rx_io_count = 0; wr_regl(port, ureg->sirfsoc_int_st_reg, uint_st->sirfsoc_rx_done); - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) & ~(uint_en->sirfsoc_rx_done_en)); @@ -555,7 +555,7 @@ static void sirfsoc_rx_tmo_process_tl(unsigned long param) } else { wr_regl(port, ureg->sirfsoc_int_st_reg, uint_st->sirfsoc_rx_done); - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) | (uint_en->sirfsoc_rx_done_en)); @@ -578,7 +578,7 @@ static void sirfsoc_uart_handle_rx_tmo(struct sirfsoc_uart_port *sirfport) dmaengine_terminate_all(sirfport->rx_dma_chan); sirfport->rx_dma_items[sirfport->rx_issued].xmit.head = SIRFSOC_RX_DMA_BUF_SIZE - tx_state.residue; - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) & ~(uint_en->sirfsoc_rx_timeout_en)); @@ -598,7 +598,7 @@ static void sirfsoc_uart_handle_rx_done(struct sirfsoc_uart_port *sirfport) sirfsoc_uart_pio_rx_chars(port, 4 - sirfport->rx_io_count); if (sirfport->rx_io_count == 4) { sirfport->rx_io_count = 0; - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) & ~(uint_en->sirfsoc_rx_done_en)); @@ -748,7 +748,7 @@ static void sirfsoc_uart_start_next_rx_dma(struct uart_port *port) for (i = 0; i < SIRFSOC_RX_LOOP_BUF_CNT; i++) sirfsoc_rx_submit_one_dma_desc(port, i); sirfport->rx_completed = sirfport->rx_issued = 0; - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) | SIRFUART_RX_DMA_INT_EN(port, uint_en)); @@ -770,7 +770,7 @@ static void sirfsoc_uart_start_rx(struct uart_port *port) if (sirfport->rx_dma_chan) sirfsoc_uart_start_next_rx_dma(port); else { - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, rd_regl(port, ureg->sirfsoc_int_en_reg) | SIRFUART_RX_IO_INT_EN(port, uint_en)); @@ -1124,7 +1124,7 @@ static void sirfsoc_uart_shutdown(struct uart_port *port) { struct sirfsoc_uart_port *sirfport = to_sirfport(port); struct sirfsoc_register *ureg = &sirfport->uart_reg->uart_reg; - if (!sirfport->is_marco) + if (!sirfport->is_atlas7) wr_regl(port, ureg->sirfsoc_int_en_reg, 0); else wr_regl(port, SIRFUART_INT_EN_CLR, ~0UL); @@ -1271,7 +1271,7 @@ static struct uart_driver sirfsoc_uart_drv = { static struct of_device_id sirfsoc_uart_ids[] = { { .compatible = "sirf,prima2-uart", .data = &sirfsoc_uart,}, - { .compatible = "sirf,marco-uart", .data = &sirfsoc_uart}, + { .compatible = "sirf,atlas7-uart", .data = &sirfsoc_uart}, { .compatible = "sirf,prima2-usp-uart", .data = &sirfsoc_usp}, {} }; @@ -1350,8 +1350,8 @@ static int sirfsoc_uart_probe(struct platform_device *pdev) gpio_direction_output(sirfport->rts_gpio, 1); } usp_no_flow_control: - if (of_device_is_compatible(pdev->dev.of_node, "sirf,marco-uart")) - sirfport->is_marco = true; + if (of_device_is_compatible(pdev->dev.of_node, "sirf,atlas7-uart")) + sirfport->is_atlas7 = true; if (of_property_read_u32(pdev->dev.of_node, "fifosize", @@ -1393,7 +1393,7 @@ usp_no_flow_control: goto err; } port->uartclk = clk_get_rate(sirfport->clk); - if (of_device_is_compatible(pdev->dev.of_node, "sirf,marco-bt-uart")) { + if (of_device_is_compatible(pdev->dev.of_node, "sirf,atlas7-bt-uart")) { sirfport->clk_general = devm_clk_get(&pdev->dev, "general"); if (IS_ERR(sirfport->clk_general)) { ret = PTR_ERR(sirfport->clk_general); diff --git a/drivers/tty/serial/sirfsoc_uart.h b/drivers/tty/serial/sirfsoc_uart.h index 275d0389399..727eb6b88ff 100644 --- a/drivers/tty/serial/sirfsoc_uart.h +++ b/drivers/tty/serial/sirfsoc_uart.h @@ -421,8 +421,8 @@ struct sirfsoc_uart_port { bool is_bt_uart; struct clk *clk_general; struct clk *clk_noc; - /* for SiRFmarco, there are SET/CLR for UART_INT_EN */ - bool is_marco; + /* for SiRFatlas7, there are SET/CLR for UART_INT_EN */ + bool is_atlas7; struct sirfsoc_uart_register *uart_reg; struct dma_chan *rx_dma_chan; struct dma_chan *tx_dma_chan; -- cgit v1.2.3-70-g09d2 From 485819b5b9ed82372dacae775998f3c33717f7fe Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Tue, 9 Dec 2014 14:31:32 +0100 Subject: tty/serial: at91: use correct type for dma_sync_*_for_cpu() and dma_sync_*_for_device() dma_sync_*_for_cpu() and dma_sync_*_for_device() use 'enum dma_data_direction', not 'enum dma_transfer_direction' Signed-off-by: Cyrille Pitchen Acked-by: Nicolas Ferre Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 4d848a29e22..756f567527c 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -794,7 +794,7 @@ static void atmel_tx_dma(struct uart_port *port) return; } - dma_sync_sg_for_device(port->dev, sg, 1, DMA_MEM_TO_DEV); + dma_sync_sg_for_device(port->dev, sg, 1, DMA_TO_DEVICE); atmel_port->desc_tx = desc; desc->callback = atmel_complete_tx_dma; @@ -927,7 +927,7 @@ static void atmel_rx_from_dma(struct uart_port *port) dma_sync_sg_for_cpu(port->dev, &atmel_port->sg_rx, 1, - DMA_DEV_TO_MEM); + DMA_FROM_DEVICE); /* * ring->head points to the end of data already written by the DMA. @@ -974,7 +974,7 @@ static void atmel_rx_from_dma(struct uart_port *port) dma_sync_sg_for_device(port->dev, &atmel_port->sg_rx, 1, - DMA_DEV_TO_MEM); + DMA_FROM_DEVICE); /* * Drop the lock here since it might end up calling -- cgit v1.2.3-70-g09d2 From d4f641876a68d1961e30c202709cc2d484f69f6f Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Tue, 9 Dec 2014 14:31:33 +0100 Subject: tty/serial: at91: enable peripheral clock before accessing I/O registers atmel_serial_probe() calls atmel_init_port(). In turn, atmel_init_port() calls clk_disable_unprepare() to disable the peripheral clock before returning. Later atmel_serial_probe() accesses some I/O registers such as the Mode and Control registers for RS485 support then the Name and Version registers, through a call to atmel_get_ip_name(), but at that moment the peripheral clock was still disabled. Signed-off-by: Cyrille Pitchen Acked-by: Nicolas Ferre Cc: # 3.12+ Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 756f567527c..c6621dc296c 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -2596,6 +2596,12 @@ static int atmel_serial_probe(struct platform_device *pdev) device_init_wakeup(&pdev->dev, 1); platform_set_drvdata(pdev, port); + /* + * The peripheral clock has been disabled by atmel_init_port(): + * enable it before accessing I/O registers + */ + clk_prepare_enable(port->clk); + if (rs485_enabled) { UART_PUT_MR(&port->uart, ATMEL_US_USMODE_NORMAL); UART_PUT_CR(&port->uart, ATMEL_US_RTSEN); @@ -2606,6 +2612,12 @@ static int atmel_serial_probe(struct platform_device *pdev) */ atmel_get_ip_name(&port->uart); + /* + * The peripheral clock can now safely be disabled till the port + * is used + */ + clk_disable_unprepare(port->clk); + return 0; err_add_port: -- cgit v1.2.3-70-g09d2 From 6fbb9bdf0f3fbe23aeff806489791aa876adaffb Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Tue, 9 Dec 2014 14:31:34 +0100 Subject: tty/serial: at91: fix error handling in atmel_serial_probe() -EDEFER error wasn't handle properly by atmel_serial_probe(). As an example, when atmel_serial_probe() is called for the first time, we pass the test_and_set_bit() test to check whether the port has already been initalized. Then we call atmel_init_port(), which may return -EDEFER, possibly returned before by clk_get(). Consequently atmel_serial_probe() used to return this error code WITHOUT clearing the port bit in the "atmel_ports_in_use" mask. When atmel_serial_probe() was called for the second time, it used to fail on the test_and_set_bit() function then returning -EBUSY. When atmel_serial_probe() fails, this patch make it clear the port bit in the "atmel_ports_in_use" mask, if needed, before returning the error code. Signed-off-by: Cyrille Pitchen Acked-by: Nicolas Ferre Cc: # 3.12+ Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index c6621dc296c..92a8b263735 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -2565,7 +2565,7 @@ static int atmel_serial_probe(struct platform_device *pdev) ret = atmel_init_port(port, pdev); if (ret) - goto err; + goto err_clear_bit; if (!atmel_use_pdc_rx(&port->uart)) { ret = -ENOMEM; @@ -2628,6 +2628,8 @@ err_alloc_ring: clk_put(port->clk); port->clk = NULL; } +err_clear_bit: + clear_bit(port->uart.line, atmel_ports_in_use); err: return ret; } -- cgit v1.2.3-70-g09d2 From 1cf6e8fc8341e54f28aad706544a1d50c3a82ddc Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Tue, 9 Dec 2014 14:31:35 +0100 Subject: tty/serial: at91: fix RTS line management when hardware handshake is enabled This patch fixes many bugs in the code dealing with the hardware handshake. As an example, in atmel_set_termios(), we used to test whether the CRTSCTS c_cflag was set. If so, we selected the "Hardware Handshake" mode through the Mode Register. However, few lines below the mode was reset to "Normal" (0). So there was no way to select the "Hardware Handshake" mode. To fix this issue, we moved the CRTSCRTS c_cflag test AFTER the mode has been reset to "Normal". Also setting the RTSEN and RTSDIS bits in the Control Register has different results whether the USART is set in "Normal" or "Hardware Handshake" mode: 1) "Normal" mode - the RTSEN bit forces the RTS line to low level, which tells the remote peer that we are ready to received new data. - the RTSDIS bit forces the RTS line to high level, which tells the remote peer to stop sending new data. 2) "Hardware Handshake" mode - the RTSEN bit forces the RTS line to high level. - the RTSDIS bit lets the hardware control the RTS line. WARNING: when FIFOs are not available or not enabled, the RTS line is controlled by the PDC. This is why using the Hardware Handshake mode requires using the PDC channel for reception. However the Hardware Handshake mode DOES NOT work with DMA controller since it cannot control the RTS line. Future designs with FIFOs will introduce a new feature: the RTS line will be controlled by the RX FIFO using thresholds. This patch was tested with this new design. Signed-off-by: Cyrille Pitchen Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 91 ++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 92a8b263735..9e0c63676f0 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -341,13 +341,37 @@ static u_int atmel_tx_empty(struct uart_port *port) static void atmel_set_mctrl(struct uart_port *port, u_int mctrl) { unsigned int control = 0; - unsigned int mode; + unsigned int mode = UART_GET_MR(port); + unsigned int rts_paused, rts_ready; struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + /* override mode to RS485 if needed, otherwise keep the current mode */ + if (port->rs485.flags & SER_RS485_ENABLED) { + if ((port->rs485.delay_rts_after_send) > 0) + UART_PUT_TTGR(port, port->rs485.delay_rts_after_send); + mode &= ~ATMEL_US_USMODE; + mode |= ATMEL_US_USMODE_RS485; + } + + /* set the RTS line state according to the mode */ + if ((mode & ATMEL_US_USMODE) == ATMEL_US_USMODE_HWHS) { + /* force RTS line to high level */ + rts_paused = ATMEL_US_RTSEN; + + /* give the control of the RTS line back to the hardware */ + rts_ready = ATMEL_US_RTSDIS; + } else { + /* force RTS line to high level */ + rts_paused = ATMEL_US_RTSDIS; + + /* force RTS line to low level */ + rts_ready = ATMEL_US_RTSEN; + } + if (mctrl & TIOCM_RTS) - control |= ATMEL_US_RTSEN; + control |= rts_ready; else - control |= ATMEL_US_RTSDIS; + control |= rts_paused; if (mctrl & TIOCM_DTR) control |= ATMEL_US_DTREN; @@ -359,23 +383,12 @@ static void atmel_set_mctrl(struct uart_port *port, u_int mctrl) mctrl_gpio_set(atmel_port->gpios, mctrl); /* Local loopback mode? */ - mode = UART_GET_MR(port) & ~ATMEL_US_CHMODE; + mode &= ~ATMEL_US_CHMODE; if (mctrl & TIOCM_LOOP) mode |= ATMEL_US_CHMODE_LOC_LOOP; else mode |= ATMEL_US_CHMODE_NORMAL; - /* Resetting serial mode to RS232 (0x0) */ - mode &= ~ATMEL_US_USMODE; - - if (port->rs485.flags & SER_RS485_ENABLED) { - dev_dbg(port->dev, "Setting UART to RS485\n"); - if ((port->rs485.delay_rts_after_send) > 0) - UART_PUT_TTGR(port, port->rs485.delay_rts_after_send); - mode |= ATMEL_US_USMODE_RS485; - } else { - dev_dbg(port->dev, "Setting UART to RS232\n"); - } UART_PUT_MR(port, mode); } @@ -1921,12 +1934,14 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { unsigned long flags; - unsigned int mode, imr, quot, baud; + unsigned int old_mode, mode, imr, quot, baud; + + /* save the current mode register */ + mode = old_mode = UART_GET_MR(port); - /* Get current mode register */ - mode = UART_GET_MR(port) & ~(ATMEL_US_USCLKS | ATMEL_US_CHRL - | ATMEL_US_NBSTOP | ATMEL_US_PAR - | ATMEL_US_USMODE); + /* reset the mode, clock divisor, parity, stop bits and data size */ + mode &= ~(ATMEL_US_USCLKS | ATMEL_US_CHRL | ATMEL_US_NBSTOP | + ATMEL_US_PAR | ATMEL_US_USMODE); baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); quot = uart_get_divisor(port, baud); @@ -1971,12 +1986,6 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios, } else mode |= ATMEL_US_PAR_NONE; - /* hardware handshake (RTS/CTS) */ - if (termios->c_cflag & CRTSCTS) - mode |= ATMEL_US_USMODE_HWHS; - else - mode |= ATMEL_US_USMODE_NORMAL; - spin_lock_irqsave(&port->lock, flags); port->read_status_mask = ATMEL_US_OVRE; @@ -2020,18 +2029,40 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios, /* disable receiver and transmitter */ UART_PUT_CR(port, ATMEL_US_TXDIS | ATMEL_US_RXDIS); - /* Resetting serial mode to RS232 (0x0) */ - mode &= ~ATMEL_US_USMODE; - + /* mode */ if (port->rs485.flags & SER_RS485_ENABLED) { if ((port->rs485.delay_rts_after_send) > 0) UART_PUT_TTGR(port, port->rs485.delay_rts_after_send); mode |= ATMEL_US_USMODE_RS485; + } else if (termios->c_cflag & CRTSCTS) { + /* RS232 with hardware handshake (RTS/CTS) */ + mode |= ATMEL_US_USMODE_HWHS; + } else { + /* RS232 without hadware handshake */ + mode |= ATMEL_US_USMODE_NORMAL; } - /* set the parity, stop bits and data size */ + /* set the mode, clock divisor, parity, stop bits and data size */ UART_PUT_MR(port, mode); + /* + * when switching the mode, set the RTS line state according to the + * new mode, otherwise keep the former state + */ + if ((old_mode & ATMEL_US_USMODE) != (mode & ATMEL_US_USMODE)) { + unsigned int rts_state; + + if ((mode & ATMEL_US_USMODE) == ATMEL_US_USMODE_HWHS) { + /* let the hardware control the RTS line */ + rts_state = ATMEL_US_RTSDIS; + } else { + /* force RTS line to low level */ + rts_state = ATMEL_US_RTSEN; + } + + UART_PUT_CR(port, rts_state); + } + /* set the baud rate */ UART_PUT_BRGR(port, quot); UART_PUT_CR(port, ATMEL_US_RSTSTA | ATMEL_US_RSTRX); -- cgit v1.2.3-70-g09d2 From 1842dc2e29855ef2d8ec2e5ff9f29add7e999ce8 Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Tue, 9 Dec 2014 14:31:36 +0100 Subject: tty/serial: at91: fix typo and indentation Signed-off-by: Cyrille Pitchen Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 40 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 9e0c63676f0..846552bff67 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -738,7 +738,11 @@ static void atmel_complete_tx_dma(void *arg) if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); - /* Do we really need this? */ + /* + * xmit is a circular buffer so, if we have just send data from + * xmit->tail to the end of xmit->buf, now we have to transmit the + * remaining data from the beginning of xmit->buf to xmit->head. + */ if (!uart_circ_empty(xmit)) tasklet_schedule(&atmel_port->tasklet); @@ -797,11 +801,11 @@ static void atmel_tx_dma(struct uart_port *port) BUG_ON(!sg_dma_len(sg)); desc = dmaengine_prep_slave_sg(chan, - sg, - 1, - DMA_MEM_TO_DEV, - DMA_PREP_INTERRUPT | - DMA_CTRL_ACK); + sg, + 1, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | + DMA_CTRL_ACK); if (!desc) { dev_err(port->dev, "Failed to send via dma!\n"); return; @@ -1025,13 +1029,13 @@ static int atmel_prepare_rx_dma(struct uart_port *port) /* UART circular rx buffer is an aligned page. */ BUG_ON((int)port->state->xmit.buf & ~PAGE_MASK); sg_set_page(&atmel_port->sg_rx, - virt_to_page(ring->buf), - ATMEL_SERIAL_RINGSIZE, - (int)ring->buf & ~PAGE_MASK); - nent = dma_map_sg(port->dev, - &atmel_port->sg_rx, - 1, - DMA_FROM_DEVICE); + virt_to_page(ring->buf), + ATMEL_SERIAL_RINGSIZE, + (int)ring->buf & ~PAGE_MASK); + nent = dma_map_sg(port->dev, + &atmel_port->sg_rx, + 1, + DMA_FROM_DEVICE); if (!nent) { dev_dbg(port->dev, "need to release resource of dma\n"); @@ -1060,11 +1064,11 @@ static int atmel_prepare_rx_dma(struct uart_port *port) * each one is half ring buffer size */ desc = dmaengine_prep_dma_cyclic(atmel_port->chan_rx, - sg_dma_address(&atmel_port->sg_rx), - sg_dma_len(&atmel_port->sg_rx), - sg_dma_len(&atmel_port->sg_rx)/2, - DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT); + sg_dma_address(&atmel_port->sg_rx), + sg_dma_len(&atmel_port->sg_rx), + sg_dma_len(&atmel_port->sg_rx)/2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); desc->callback = atmel_complete_rx_dma; desc->callback_param = port; atmel_port->desc_rx = desc; -- cgit v1.2.3-70-g09d2 From 55d8693acd65b1c14e011cbcbfad2a15626472cd Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:22 +0900 Subject: serial: imx: add CREAD flag support Add CREAD flag hanlding in set_termios and UART DMA mode which ignores all received chars when CREAD flag cleared. Signed-off-by: Jiada Wang Signed-off-by: Anton Bondarenko Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 4c5e9092e2d..eb2210fa972 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -74,6 +74,7 @@ #define IMX21_UTS 0xb4 /* UART Test Register on all other i.mx*/ /* UART Control Register Bit Fields.*/ +#define URXD_DUMMY_READ (1<<16) #define URXD_CHARRDY (1<<15) #define URXD_ERR (1<<14) #define URXD_OVRRUN (1<<13) @@ -710,6 +711,9 @@ static irqreturn_t imx_rxint(int irq, void *dev_id) #endif } + if (sport->port.ignore_status_mask & URXD_DUMMY_READ) + goto out; + tty_insert_flip_char(port, rx, flg); } @@ -910,7 +914,8 @@ static void dma_rx_callback(void *data) dev_dbg(sport->port.dev, "We get %d bytes.\n", count); if (count) { - tty_insert_flip_string(port, sport->rx_buf, count); + if (!(sport->port.ignore_status_mask & URXD_DUMMY_READ)) + tty_insert_flip_string(port, sport->rx_buf, count); tty_flip_buffer_push(port); start_rx_dma(sport); @@ -1330,6 +1335,9 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, sport->port.ignore_status_mask |= URXD_OVRRUN; } + if ((termios->c_cflag & CREAD) == 0) + sport->port.ignore_status_mask |= URXD_DUMMY_READ; + /* * Update the per-port timeout. */ -- cgit v1.2.3-70-g09d2 From 73631813c1ed17437e9de86f82a477fa4bac90c1 Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:23 +0900 Subject: serial: imx: use locking to stop concurrent access of UCR1 Several places are accessing the UCR1 register without locking. This probably will cause a race issue when another thread is accessing the same register. Add locking to preventing concurrent access of the UCR1 register. Signed-off-by: Jiada Wang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index eb2210fa972..c851247a27c 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -731,6 +731,9 @@ static int start_rx_dma(struct imx_port *sport); static void imx_dma_rxint(struct imx_port *sport) { unsigned long temp; + unsigned long flags; + + spin_lock_irqsave(&sport->port.lock, flags); temp = readl(sport->port.membase + USR2); if ((temp & USR2_RDR) && !sport->dma_is_rxing) { @@ -744,6 +747,8 @@ static void imx_dma_rxint(struct imx_port *sport) /* tell the DMA to receive the data. */ start_rx_dma(sport); } + + spin_unlock_irqrestore(&sport->port.lock, flags); } static irqreturn_t imx_int(int irq, void *dev_id) @@ -873,6 +878,9 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode) static void imx_rx_dma_done(struct imx_port *sport) { unsigned long temp; + unsigned long flags; + + spin_lock_irqsave(&sport->port.lock, flags); /* Enable this interrupt when the RXFIFO is empty. */ temp = readl(sport->port.membase + UCR1); @@ -884,6 +892,8 @@ static void imx_rx_dma_done(struct imx_port *sport) /* Is the shutdown waiting for us? */ if (waitqueue_active(&sport->dma_wait)) wake_up(&sport->dma_wait); + + spin_unlock_irqrestore(&sport->port.lock, flags); } /* @@ -1194,9 +1204,11 @@ static void imx_shutdown(struct uart_port *port) dmaengine_terminate_all(sport->dma_chan_tx); dmaengine_terminate_all(sport->dma_chan_rx); } + spin_lock_irqsave(&sport->port.lock, flags); imx_stop_tx(port); imx_stop_rx(port); imx_disable_dma(sport); + spin_unlock_irqrestore(&sport->port.lock, flags); imx_uart_dma_exit(sport); } -- cgit v1.2.3-70-g09d2 From 55c3cb1358e57280c54e3578afc76ce8190dd087 Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Tue, 9 Dec 2014 18:11:24 +0900 Subject: serial: imx: remove unneeded imx_transmit_buffer() from imx_start_tx() Use imx_start_tx() just to enable the TX interrupt. It's the job of the TX interrupt ISR to fill the transmit buffer, then. If the transmit buffer is empty, the TX interrupt should be executed as soon as the start_tx() enables the interrupt, so there is no reason for the extra imx_transmit_buffer() call, here. Remove it. Signed-off-by: Dirk Behme Signed-off-by: Andy Lowe Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index c851247a27c..146663c100f 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -621,9 +621,6 @@ static void imx_start_tx(struct uart_port *port) imx_dma_tx(sport); return; } - - if (readl(sport->port.membase + uts_reg(sport)) & UTS_TXEMPTY) - imx_transmit_buffer(sport); } static irqreturn_t imx_rtsint(int irq, void *dev_id) -- cgit v1.2.3-70-g09d2 From 7942f8577f2a63b7a6b8cd70f5354a608b30fead Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Tue, 9 Dec 2014 18:11:25 +0900 Subject: serial: imx: TX DMA: clean up sg initialization Inverting the logic of the if statement for the sg initialization makes the if statement easier and better to read. No functional change. Signed-off-by: Dirk Behme Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 146663c100f..5cea7d25742 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -539,15 +539,15 @@ static void imx_dma_tx(struct imx_port *sport) sport->tx_bytes = uart_circ_chars_pending(xmit); - if (xmit->tail > xmit->head && xmit->head > 0) { + if (xmit->tail < xmit->head) { + sport->dma_tx_nents = 1; + sg_init_one(sgl, xmit->buf + xmit->tail, sport->tx_bytes); + } else { sport->dma_tx_nents = 2; sg_init_table(sgl, 2); sg_set_buf(sgl, xmit->buf + xmit->tail, UART_XMIT_SIZE - xmit->tail); sg_set_buf(sgl + 1, xmit->buf, xmit->head); - } else { - sport->dma_tx_nents = 1; - sg_init_one(sgl, xmit->buf + xmit->tail, sport->tx_bytes); } ret = dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); -- cgit v1.2.3-70-g09d2 From 2464982174a1886b56b445b0503b331008a626fe Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Tue, 9 Dec 2014 18:11:26 +0900 Subject: serial: imx: unmap sg in case of dmaengine_prep_slave_sg() failure In case dmaengine_prep_slave_sg() fails, add the missing dma_unmap_sg(). Signed-off-by: Dirk Behme Signed-off-by: Anton Bondarenko Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 5cea7d25742..b4cf24c64a1 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -558,6 +558,8 @@ static void imx_dma_tx(struct imx_port *sport) desc = dmaengine_prep_slave_sg(chan, sgl, sport->dma_tx_nents, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); if (!desc) { + dma_unmap_sg(dev, sgl, sport->dma_tx_nents, + DMA_TO_DEVICE); dev_err(dev, "We cannot prepare for the TX slave dma!\n"); return; } @@ -947,6 +949,7 @@ static int start_rx_dma(struct imx_port *sport) desc = dmaengine_prep_slave_sg(chan, sgl, 1, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { + dma_unmap_sg(dev, sgl, 1, DMA_FROM_DEVICE); dev_err(dev, "We cannot prepare for the RX slave dma!\n"); return -EINVAL; } -- cgit v1.2.3-70-g09d2 From 82e86ae941194e6b6278ff67aad257b018e0684f Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Tue, 9 Dec 2014 18:11:27 +0900 Subject: serial: imx: unmap scatter gather list in imx_flush_buffer First, reformat the code to exit immediately. This allows us to add more code in more readable format. In case the TX DMA was still running, remove and disable it's resources. Signed-off-by: Dirk Behme Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index b4cf24c64a1..952730ab906 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -1250,10 +1250,17 @@ static void imx_shutdown(struct uart_port *port) static void imx_flush_buffer(struct uart_port *port) { struct imx_port *sport = (struct imx_port *)port; + struct scatterlist *sgl = &sport->tx_sgl[0]; - if (sport->dma_is_enabled) { - sport->tx_bytes = 0; - dmaengine_terminate_all(sport->dma_chan_tx); + if (!sport->dma_chan_tx) + return; + + sport->tx_bytes = 0; + dmaengine_terminate_all(sport->dma_chan_tx); + if (sport->dma_is_txing) { + dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, + DMA_TO_DEVICE); + sport->dma_is_txing = false; } } -- cgit v1.2.3-70-g09d2 From 42f752b3fbcfee9c27e4f6f6216e60e130ba98c8 Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Tue, 9 Dec 2014 18:11:28 +0900 Subject: serial: imx: use dma_is_txing to synchronize dma_tx_callback and imx_dma_tx To synchronize between dma_tx_callback() and imx_dma_tx() use the same variable, dma_is_txing. This prevents any race between these two functions and ensures that a new DMA can start only after the first has been finished. Before the new DMA can be set up, update the circular buffer logic, first. Therefore, change dma_is_txing after that update, instead of before. While doing this, in dma_tx_callback() extend the locking to dma_unmap_sg() and the update of dma_is_txing. Signed-off-by: Dirk Behme Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 952730ab906..1c4f367b6fb 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -502,18 +502,20 @@ static void dma_tx_callback(void *data) struct circ_buf *xmit = &sport->port.state->xmit; unsigned long flags; - dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); + spin_lock_irqsave(&sport->port.lock, flags); - sport->dma_is_txing = 0; + dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); /* update the stat */ - spin_lock_irqsave(&sport->port.lock, flags); xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1); sport->port.icount.tx += sport->tx_bytes; - spin_unlock_irqrestore(&sport->port.lock, flags); dev_dbg(sport->port.dev, "we finish the TX DMA.\n"); + sport->dma_is_txing = 0; + + spin_unlock_irqrestore(&sport->port.lock, flags); + uart_write_wakeup(&sport->port); if (waitqueue_active(&sport->dma_wait)) { @@ -530,11 +532,9 @@ static void imx_dma_tx(struct imx_port *sport) struct dma_async_tx_descriptor *desc; struct dma_chan *chan = sport->dma_chan_tx; struct device *dev = sport->port.dev; - enum dma_status status; int ret; - status = dmaengine_tx_status(chan, (dma_cookie_t)0, NULL); - if (DMA_IN_PROGRESS == status) + if (sport->dma_is_txing) return; sport->tx_bytes = uart_circ_chars_pending(xmit); -- cgit v1.2.3-70-g09d2 From d64b8607aceb9bd3b3dabddfc4f76458e5a8489d Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:29 +0900 Subject: Revert "serial: imx: always wake up the processes in the TX callback" This reverts commit 2ad28e3efee21a5bbf940c83d1f0395b76bd3efb. Instead of always wake up write_wait process in TX callback, TX callback should call imx_dma_tx() again, and let imx_dma_tx transfer the remaining data in circle buffer. The issue with commit 2ad28e3 is, in case there is remaining data in circle buffer, but no process is waiting on write_wait queue, then as no following uart_write() will be called after uart_write_wakeup(), thus cause data loss. Moreover according to Documentation/serial/driver, uart_write_wakeup() should be called in case the transmit buffer have dropped below a threshold. Signed-off-by: Jiada Wang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 1c4f367b6fb..73158d96822 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -516,7 +516,8 @@ static void dma_tx_callback(void *data) spin_unlock_irqrestore(&sport->port.lock, flags); - uart_write_wakeup(&sport->port); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&sport->port); if (waitqueue_active(&sport->dma_wait)) { wake_up(&sport->dma_wait); -- cgit v1.2.3-70-g09d2 From 0bbc9b81c1638f6e75c8ed423b5e6ae1a2035d79 Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:30 +0900 Subject: serial: imx: call imx_dma_tx() again in dma_tx_callback Currently in dma_tx_callback(), no matter if there is still remaining data pending in circle buffer or not, DMA transmit will be terminated. This will result in some data never get transmitted. In order to fix this issue, call imx_dma_tx() again in dma_tx_callback, when there is pending data and uart hasn't been stopped. Signed-off-by: Jiada Wang Signed-off-by: Dirk Behme Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 73158d96822..0e64f18f8c4 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -495,6 +495,7 @@ static inline void imx_transmit_buffer(struct imx_port *sport) imx_stop_tx(&sport->port); } +static void imx_dma_tx(struct imx_port *sport); static void dma_tx_callback(void *data) { struct imx_port *sport = data; @@ -524,6 +525,11 @@ static void dma_tx_callback(void *data) dev_dbg(sport->port.dev, "exit in %s.\n", __func__); return; } + + spin_lock_irqsave(&sport->port.lock, flags); + if (!uart_circ_empty(xmit) && !uart_tx_stopped(&sport->port)) + imx_dma_tx(sport); + spin_unlock_irqrestore(&sport->port.lock, flags); } static void imx_dma_tx(struct imx_port *sport) -- cgit v1.2.3-70-g09d2 From a2c718ce6a61f759fc47d632bcc3d8cf6b244581 Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Tue, 9 Dec 2014 18:11:31 +0900 Subject: serial: imx: disable TDMAEN in imx_flush_buffer() Terminating the DMA, make sure the interrupt is disabled, too. This fixes random kernel Oops due to dma_tx_call() called for invalid transmissions. If we disable the TDMAEN, make sure it's enabled again if a TX DMA is started. Signed-off-by: Jiada Wang Signed-off-by: Dirk Behme Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 0e64f18f8c4..d097169e85d 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -502,11 +502,16 @@ static void dma_tx_callback(void *data) struct scatterlist *sgl = &sport->tx_sgl[0]; struct circ_buf *xmit = &sport->port.state->xmit; unsigned long flags; + unsigned long temp; spin_lock_irqsave(&sport->port.lock, flags); dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); + temp = readl(sport->port.membase + UCR1); + temp &= ~UCR1_TDMAEN; + writel(temp, sport->port.membase + UCR1); + /* update the stat */ xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1); sport->port.icount.tx += sport->tx_bytes; @@ -539,6 +544,7 @@ static void imx_dma_tx(struct imx_port *sport) struct dma_async_tx_descriptor *desc; struct dma_chan *chan = sport->dma_chan_tx; struct device *dev = sport->port.dev; + unsigned long temp; int ret; if (sport->dma_is_txing) @@ -575,6 +581,11 @@ static void imx_dma_tx(struct imx_port *sport) dev_dbg(dev, "TX: prepare to send %lu bytes by DMA.\n", uart_circ_chars_pending(xmit)); + + temp = readl(sport->port.membase + UCR1); + temp |= UCR1_TDMAEN; + writel(temp, sport->port.membase + UCR1); + /* fire it */ sport->dma_is_txing = 1; dmaengine_submit(desc); @@ -1258,6 +1269,7 @@ static void imx_flush_buffer(struct uart_port *port) { struct imx_port *sport = (struct imx_port *)port; struct scatterlist *sgl = &sport->tx_sgl[0]; + unsigned long temp; if (!sport->dma_chan_tx) return; @@ -1267,6 +1279,9 @@ static void imx_flush_buffer(struct uart_port *port) if (sport->dma_is_txing) { dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE); + temp = readl(sport->port.membase + UCR1); + temp &= ~UCR1_TDMAEN; + writel(temp, sport->port.membase + UCR1); sport->dma_is_txing = false; } } -- cgit v1.2.3-70-g09d2 From 068500e08dc87ea9a453cc4a500cf3ab28d0f936 Mon Sep 17 00:00:00 2001 From: Anton Bondarenko Date: Tue, 9 Dec 2014 18:11:32 +0900 Subject: serial: imx: initialized DMA w/o HW flow enabled DMA mode for UART can be used even w/o HW flow control with RTS/CTS. So it need to be initialized and enabled earlier. Signed-off-by: Anton Bondarenko Signed-off-by: Jiada Wang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index d097169e85d..ab958a59cae 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -1141,12 +1141,20 @@ static int imx_startup(struct uart_port *port) while (!(readl(sport->port.membase + UCR2) & UCR2_SRST) && (--i > 0)) udelay(1); + /* Can we enable the DMA support? */ + if (is_imx6q_uart(sport) && !uart_console(port) && + !sport->dma_is_inited) + imx_uart_dma_init(sport); + spin_lock_irqsave(&sport->port.lock, flags); /* * Finally, clear and enable interrupts */ writel(USR1_RTSD, sport->port.membase + USR1); + if (sport->dma_is_inited && !sport->dma_is_enabled) + imx_enable_dma(sport); + temp = readl(sport->port.membase + UCR1); temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN; @@ -1326,11 +1334,6 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, if (sport->have_rtscts) { ucr2 &= ~UCR2_IRTS; ucr2 |= UCR2_CTSC; - - /* Can we enable the DMA support? */ - if (is_imx6q_uart(sport) && !uart_console(port) - && !sport->dma_is_inited) - imx_uart_dma_init(sport); } else { termios->c_cflag &= ~CRTSCTS; } @@ -1452,8 +1455,6 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, if (UART_ENABLE_MS(&sport->port, termios->c_cflag)) imx_enable_ms(&sport->port); - if (sport->dma_is_inited && !sport->dma_is_enabled) - imx_enable_dma(sport); spin_unlock_irqrestore(&sport->port.lock, flags); } -- cgit v1.2.3-70-g09d2 From ee5e7c1091a703c6a465d8145e9b55e09adb6f37 Mon Sep 17 00:00:00 2001 From: Robin Gong Date: Tue, 9 Dec 2014 18:11:33 +0900 Subject: serial: imx: start rx_dma once RXFIFO is not empty Start rx_dma once RXFIFO is not empty that can avoid dma request lost and causes data delay issue. Signed-off-by: Robin Gong Signed-off-by: Fugang Duan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index ab958a59cae..d61b89e4a96 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -946,8 +946,21 @@ static void dma_rx_callback(void *data) tty_flip_buffer_push(port); start_rx_dma(sport); - } else + } else if (readl(sport->port.membase + USR2) & USR2_RDR) { + /* + * start rx_dma directly once data in RXFIFO, more efficient + * than before: + * 1. call imx_rx_dma_done to stop dma if no data received + * 2. wait next RDR interrupt to start dma transfer. + */ + start_rx_dma(sport); + } else { + /* + * stop dma to prevent too many IDLE event trigged if no data + * in RXFIFO + */ imx_rx_dma_done(sport); + } } static int start_rx_dma(struct imx_port *sport) -- cgit v1.2.3-70-g09d2 From 6f026d6b7cb6e019b6352ed7fb71497c787fd6d7 Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:34 +0900 Subject: serial: imx: Enable UCR4_OREN in startup interface Other than enable Receiver Overrun Interrupt Enable (UCR4_OREN) in start_tx interface, UCR4_OREN should be enabled before enable of Receiver. Signed-off-by: Jiada Wang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index d61b89e4a96..79f485b99c3 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -611,13 +611,6 @@ static void imx_start_tx(struct uart_port *port) temp &= ~(UCR1_RRDYEN); writel(temp, sport->port.membase + UCR1); } - /* Clear any pending ORE flag before enabling interrupt */ - temp = readl(sport->port.membase + USR2); - writel(temp | USR2_ORE, sport->port.membase + USR2); - - temp = readl(sport->port.membase + UCR4); - temp |= UCR4_OREN; - writel(temp, sport->port.membase + UCR4); if (!sport->dma_is_enabled) { temp = readl(sport->port.membase + UCR1); @@ -1178,6 +1171,14 @@ static int imx_startup(struct uart_port *port) writel(temp, sport->port.membase + UCR1); + /* Clear any pending ORE flag before enabling interrupt */ + temp = readl(sport->port.membase + USR2); + writel(temp | USR2_ORE, sport->port.membase + USR2); + + temp = readl(sport->port.membase + UCR4); + temp |= UCR4_OREN; + writel(temp, sport->port.membase + UCR4); + temp = readl(sport->port.membase + UCR2); temp |= (UCR2_RXEN | UCR2_TXEN); if (!sport->have_rtscts) -- cgit v1.2.3-70-g09d2 From 7e2fb5aa8d819e8cc0b6815ad627b7e911fabfd9 Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:35 +0900 Subject: serial: imx: Fix issue in software flow control After send out x_char in UART driver, x_char needs to be cleared by UART driver itself, otherwise data in TXFIFO can no longer be sent out. Also tx counter needs to be increased to keep track of correct number of transmitted data. Signed-off-by: Jiada Wang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 79f485b99c3..5a7da019f01 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -471,6 +471,8 @@ static inline void imx_transmit_buffer(struct imx_port *sport) if (sport->port.x_char) { /* Send next char */ writel(sport->port.x_char, sport->port.membase + URTX0); + sport->port.icount.tx++; + sport->port.x_char = 0; return; } -- cgit v1.2.3-70-g09d2 From 91a1a909f9217abb16c5bb3ec2ae606a9fc085e8 Mon Sep 17 00:00:00 2001 From: Jiada Wang Date: Tue, 9 Dec 2014 18:11:36 +0900 Subject: serial: imx: Support sw flow control in DMA mode This patch adds Software flow control support in DMA mode. Signed-off-by: Jiada Wang Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 5a7da019f01..72b800b296b 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -464,9 +464,11 @@ static void imx_enable_ms(struct uart_port *port) mod_timer(&sport->timer, jiffies); } +static void imx_dma_tx(struct imx_port *sport); static inline void imx_transmit_buffer(struct imx_port *sport) { struct circ_buf *xmit = &sport->port.state->xmit; + unsigned long temp; if (sport->port.x_char) { /* Send next char */ @@ -481,6 +483,22 @@ static inline void imx_transmit_buffer(struct imx_port *sport) return; } + if (sport->dma_is_enabled) { + /* + * We've just sent a X-char Ensure the TX DMA is enabled + * and the TX IRQ is disabled. + **/ + temp = readl(sport->port.membase + UCR1); + temp &= ~UCR1_TXMPTYEN; + if (sport->dma_is_txing) { + temp |= UCR1_TDMAEN; + writel(temp, sport->port.membase + UCR1); + } else { + writel(temp, sport->port.membase + UCR1); + imx_dma_tx(sport); + } + } + while (!uart_circ_empty(xmit) && !(readl(sport->port.membase + uts_reg(sport)) & UTS_TXFULL)) { /* send xmit->buf[xmit->tail] @@ -497,7 +515,6 @@ static inline void imx_transmit_buffer(struct imx_port *sport) imx_stop_tx(&sport->port); } -static void imx_dma_tx(struct imx_port *sport); static void dma_tx_callback(void *data) { struct imx_port *sport = data; @@ -630,7 +647,16 @@ static void imx_start_tx(struct uart_port *port) } if (sport->dma_is_enabled) { - /* FIXME: port->x_char must be transmitted if != 0 */ + if (sport->port.x_char) { + /* We have X-char to send, so enable TX IRQ and + * disable TX DMA to let TX interrupt to send X-char */ + temp = readl(sport->port.membase + UCR1); + temp &= ~UCR1_TDMAEN; + temp |= UCR1_TXMPTYEN; + writel(temp, sport->port.membase + UCR1); + return; + } + if (!uart_circ_empty(&port->state->xmit) && !uart_tx_stopped(port)) imx_dma_tx(sport); -- cgit v1.2.3-70-g09d2 From 7bb6b2f65e6ce9743df42fabbf124e4ef9dc226b Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 10 Dec 2014 12:49:22 +0100 Subject: serial: samsung: add struct s3c24xx_uart_dma This struct contains DMA configuration for each serial port. It will be used in next commits adding DMA support in driver. Based on previous work of Sylwester Nawrocki and Lukasz Czerwinski. Signed-off-by: Robert Baldyga Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.h b/drivers/tty/serial/samsung.h index eb071dd19b2..8dc250c6b47 100644 --- a/drivers/tty/serial/samsung.h +++ b/drivers/tty/serial/samsung.h @@ -12,6 +12,8 @@ * published by the Free Software Foundation. */ +#include + struct s3c24xx_uart_info { char *name; unsigned int type; @@ -41,6 +43,40 @@ struct s3c24xx_serial_drv_data { unsigned int fifosize[CONFIG_SERIAL_SAMSUNG_UARTS]; }; +struct s3c24xx_uart_dma { + dma_filter_fn fn; + void *rx_param; + void *tx_param; + + unsigned int rx_chan_id; + unsigned int tx_chan_id; + + struct dma_slave_config rx_conf; + struct dma_slave_config tx_conf; + + struct dma_chan *rx_chan; + struct dma_chan *tx_chan; + + dma_addr_t rx_addr; + dma_addr_t tx_addr; + + dma_cookie_t rx_cookie; + dma_cookie_t tx_cookie; + + char *rx_buf; + + dma_addr_t tx_transfer_addr; + + size_t rx_size; + size_t tx_size; + + struct dma_async_tx_descriptor *tx_desc; + struct dma_async_tx_descriptor *rx_desc; + + int tx_bytes_requested; + int rx_bytes_requested; +}; + struct s3c24xx_uart_port { unsigned char rx_claimed; unsigned char tx_claimed; @@ -59,6 +95,8 @@ struct s3c24xx_uart_port { /* reference to platform data */ struct s3c2410_uartcfg *cfg; + struct s3c24xx_uart_dma *dma; + #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; #endif -- cgit v1.2.3-70-g09d2 From 658c9d2b735fbc066a6608754b0eda77c92e637b Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 10 Dec 2014 12:49:23 +0100 Subject: serial: samsung: alloc dma stucture in ourport When we have "dmas" property in serial node in device-tree, we do memory alocation for dma structure which will be used in DMA handling code. Based on previous work of Sylwester Nawrocki and Lukasz Czerwinski. Signed-off-by: Robert Baldyga Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 19273e31d22..7196a3e586c 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -1209,6 +1209,18 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport, ret = platform_get_irq(platdev, 1); if (ret > 0) ourport->tx_irq = ret; + /* + * DMA is currently supported only on DT platforms, if DMA properties + * are specified. + */ + if (platdev->dev.of_node && of_find_property(platdev->dev.of_node, + "dmas", NULL)) { + ourport->dma = devm_kzalloc(port->dev, + sizeof(*ourport->dma), + GFP_KERNEL); + if (!ourport->dma) + return -ENOMEM; + } ourport->clk = clk_get(&platdev->dev, "uart"); if (IS_ERR(ourport->clk)) { -- cgit v1.2.3-70-g09d2 From 62c37eedb74c8fbf3c92f8f28a871e6bd80c181a Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 10 Dec 2014 12:49:25 +0100 Subject: serial: samsung: add dma reqest/release functions Add functions requesting and releasing RX and TX DMA channels. This function are called only when "dmas" property in serial device-tree node is defined. Based on previous work of Sylwester Nawrocki and Lukasz Czerwinski. Signed-off-by: Robert Baldyga Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.c | 101 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 7196a3e586c..825c8192b94 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -28,6 +28,9 @@ #define SUPPORT_SYSRQ #endif +#include +#include +#include #include #include #include @@ -453,6 +456,93 @@ static void s3c24xx_serial_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&port->lock, flags); } +static int s3c24xx_serial_request_dma(struct s3c24xx_uart_port *p) +{ + struct s3c24xx_uart_dma *dma = p->dma; + dma_cap_mask_t mask; + unsigned long flags; + + /* Default slave configuration parameters */ + dma->rx_conf.direction = DMA_DEV_TO_MEM; + dma->rx_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma->rx_conf.src_addr = p->port.mapbase + S3C2410_URXH; + dma->rx_conf.src_maxburst = 16; + + dma->tx_conf.direction = DMA_MEM_TO_DEV; + dma->tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + dma->tx_conf.dst_addr = p->port.mapbase + S3C2410_UTXH; + if (dma_get_cache_alignment() >= 16) + dma->tx_conf.dst_maxburst = 16; + else + dma->tx_conf.dst_maxburst = 1; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + dma->rx_chan = dma_request_slave_channel_compat(mask, dma->fn, + dma->rx_param, p->port.dev, "rx"); + if (!dma->rx_chan) + return -ENODEV; + + dmaengine_slave_config(dma->rx_chan, &dma->rx_conf); + + dma->tx_chan = dma_request_slave_channel_compat(mask, dma->fn, + dma->tx_param, p->port.dev, "tx"); + if (!dma->tx_chan) { + dma_release_channel(dma->rx_chan); + return -ENODEV; + } + + dmaengine_slave_config(dma->tx_chan, &dma->tx_conf); + + /* RX buffer */ + dma->rx_size = PAGE_SIZE; + + dma->rx_buf = kmalloc(dma->rx_size, GFP_KERNEL); + + if (!dma->rx_buf) { + dma_release_channel(dma->rx_chan); + dma_release_channel(dma->tx_chan); + return -ENOMEM; + } + + dma->rx_addr = dma_map_single(dma->rx_chan->device->dev, dma->rx_buf, + dma->rx_size, DMA_FROM_DEVICE); + + spin_lock_irqsave(&p->port.lock, flags); + + /* TX buffer */ + dma->tx_addr = dma_map_single(dma->tx_chan->device->dev, + p->port.state->xmit.buf, + UART_XMIT_SIZE, DMA_TO_DEVICE); + + spin_unlock_irqrestore(&p->port.lock, flags); + + return 0; +} + +static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p) +{ + struct s3c24xx_uart_dma *dma = p->dma; + + if (dma->rx_chan) { + dmaengine_terminate_all(dma->rx_chan); + dma_unmap_single(dma->rx_chan->device->dev, dma->rx_addr, + dma->rx_size, DMA_FROM_DEVICE); + kfree(dma->rx_buf); + dma_release_channel(dma->rx_chan); + dma->rx_chan = NULL; + } + + if (dma->tx_chan) { + dmaengine_terminate_all(dma->tx_chan); + dma_unmap_single(dma->tx_chan->device->dev, dma->tx_addr, + UART_XMIT_SIZE, DMA_TO_DEVICE); + dma_release_channel(dma->tx_chan); + dma->tx_chan = NULL; + } +} + static void s3c24xx_serial_shutdown(struct uart_port *port) { struct s3c24xx_uart_port *ourport = to_ourport(port); @@ -478,6 +568,10 @@ static void s3c24xx_serial_shutdown(struct uart_port *port) wr_regl(port, S3C64XX_UINTP, 0xf); wr_regl(port, S3C64XX_UINTM, 0xf); } + + if (ourport->dma) + s3c24xx_serial_release_dma(ourport); + } static int s3c24xx_serial_startup(struct uart_port *port) @@ -535,6 +629,13 @@ static int s3c64xx_serial_startup(struct uart_port *port) port, (unsigned long long)port->mapbase, port->membase); wr_regl(port, S3C64XX_UINTM, 0xf); + if (ourport->dma) { + ret = s3c24xx_serial_request_dma(ourport); + if (ret < 0) { + dev_warn(port->dev, "DMA request failed\n"); + return ret; + } + } ret = request_irq(port->irq, s3c64xx_serial_handle_irq, IRQF_SHARED, s3c24xx_serial_portname(port), ourport); -- cgit v1.2.3-70-g09d2 From 29bef79908459d20a7a3db654bceb8f3ce4601dc Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 10 Dec 2014 12:49:26 +0100 Subject: serial: samsung: add DMA support for TX Add TX DMA transfers support for samsung serial driver. It's enabled when "dmas" property is defined in serial device-tree node, otherwise TX transfers are prerformed using PIO. TX DMA is used for data segments larger than fifosize to reduce number of interrupts during data transmission. For buffers shorter than fifosize PIO mode is selected. Data blocks for DMA transfers are aligned to cache line size to avoid problems with coherency (some areas of TX circ buffer can be used by CPU during DMA transaction, so we have to ensure that our data is always consistent). Based on previous work of Sylwester Nawrocki and Lukasz Czerwinski. Signed-off-by: Robert Baldyga Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.c | 238 ++++++++++++++++++++++++++++++++++++++++--- drivers/tty/serial/samsung.h | 3 + 2 files changed, 225 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 825c8192b94..c04618052e5 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -81,6 +81,8 @@ static void dbg(const char *fmt, ...) #define S3C24XX_SERIAL_MAJOR 204 #define S3C24XX_SERIAL_MINOR 64 +#define S3C24XX_TX_PIO 1 +#define S3C24XX_TX_DMA 2 /* macros to change one thing to another */ #define tx_enabled(port) ((port)->unused[0]) @@ -157,33 +159,217 @@ static void s3c24xx_serial_rx_disable(struct uart_port *port) static void s3c24xx_serial_stop_tx(struct uart_port *port) { struct s3c24xx_uart_port *ourport = to_ourport(port); + struct s3c24xx_uart_dma *dma = ourport->dma; + struct circ_buf *xmit = &port->state->xmit; + struct dma_tx_state state; + int count; - if (tx_enabled(port)) { - if (s3c24xx_serial_has_interrupt_mask(port)) - __set_bit(S3C64XX_UINTM_TXD, - portaddrl(port, S3C64XX_UINTM)); - else - disable_irq_nosync(ourport->tx_irq); - tx_enabled(port) = 0; - if (port->flags & UPF_CONS_FLOW) - s3c24xx_serial_rx_enable(port); + if (!tx_enabled(port)) + return; + + if (s3c24xx_serial_has_interrupt_mask(port)) + __set_bit(S3C64XX_UINTM_TXD, + portaddrl(port, S3C64XX_UINTM)); + else + disable_irq_nosync(ourport->tx_irq); + + if (dma && dma->tx_chan && ourport->tx_in_progress == S3C24XX_TX_DMA) { + dmaengine_pause(dma->tx_chan); + dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state); + dmaengine_terminate_all(dma->tx_chan); + dma_sync_single_for_cpu(ourport->port.dev, + dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE); + async_tx_ack(dma->tx_desc); + count = dma->tx_bytes_requested - state.residue; + xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1); + port->icount.tx += count; } + + tx_enabled(port) = 0; + ourport->tx_in_progress = 0; + + if (port->flags & UPF_CONS_FLOW) + s3c24xx_serial_rx_enable(port); + + ourport->tx_mode = 0; +} + +static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport); + +static void s3c24xx_serial_tx_dma_complete(void *args) +{ + struct s3c24xx_uart_port *ourport = args; + struct uart_port *port = &ourport->port; + struct circ_buf *xmit = &port->state->xmit; + struct s3c24xx_uart_dma *dma = ourport->dma; + struct dma_tx_state state; + unsigned long flags; + int count; + + + dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state); + count = dma->tx_bytes_requested - state.residue; + async_tx_ack(dma->tx_desc); + + dma_sync_single_for_cpu(ourport->port.dev, dma->tx_transfer_addr, + dma->tx_size, DMA_TO_DEVICE); + + spin_lock_irqsave(&port->lock, flags); + + xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1); + port->icount.tx += count; + ourport->tx_in_progress = 0; + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + s3c24xx_serial_start_next_tx(ourport); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void enable_tx_dma(struct s3c24xx_uart_port *ourport) +{ + struct uart_port *port = &ourport->port; + u32 ucon; + + /* Mask Tx interrupt */ + if (s3c24xx_serial_has_interrupt_mask(port)) + __set_bit(S3C64XX_UINTM_TXD, + portaddrl(port, S3C64XX_UINTM)); + else + disable_irq_nosync(ourport->tx_irq); + + /* Enable tx dma mode */ + ucon = rd_regl(port, S3C2410_UCON); + ucon &= ~(S3C64XX_UCON_TXBURST_MASK | S3C64XX_UCON_TXMODE_MASK); + ucon |= (dma_get_cache_alignment() >= 16) ? + S3C64XX_UCON_TXBURST_16 : S3C64XX_UCON_TXBURST_1; + ucon |= S3C64XX_UCON_TXMODE_DMA; + wr_regl(port, S3C2410_UCON, ucon); + + ourport->tx_mode = S3C24XX_TX_DMA; +} + +static void enable_tx_pio(struct s3c24xx_uart_port *ourport) +{ + struct uart_port *port = &ourport->port; + u32 ucon, ufcon; + + /* Set ufcon txtrig */ + ourport->tx_in_progress = S3C24XX_TX_PIO; + ufcon = rd_regl(port, S3C2410_UFCON); + wr_regl(port, S3C2410_UFCON, ufcon); + + /* Enable tx pio mode */ + ucon = rd_regl(port, S3C2410_UCON); + ucon &= ~(S3C64XX_UCON_TXMODE_MASK); + ucon |= S3C64XX_UCON_TXMODE_CPU; + wr_regl(port, S3C2410_UCON, ucon); + + /* Unmask Tx interrupt */ + if (s3c24xx_serial_has_interrupt_mask(port)) + __clear_bit(S3C64XX_UINTM_TXD, + portaddrl(port, S3C64XX_UINTM)); + else + enable_irq(ourport->tx_irq); + + ourport->tx_mode = S3C24XX_TX_PIO; } -static void s3c24xx_serial_start_tx(struct uart_port *port) +static void s3c24xx_serial_start_tx_pio(struct s3c24xx_uart_port *ourport) +{ + if (ourport->tx_mode != S3C24XX_TX_PIO) + enable_tx_pio(ourport); +} + +static int s3c24xx_serial_start_tx_dma(struct s3c24xx_uart_port *ourport, + unsigned int count) +{ + struct uart_port *port = &ourport->port; + struct circ_buf *xmit = &port->state->xmit; + struct s3c24xx_uart_dma *dma = ourport->dma; + + + if (ourport->tx_mode != S3C24XX_TX_DMA) + enable_tx_dma(ourport); + + while (xmit->tail & (dma_get_cache_alignment() - 1)) { + if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) + return 0; + wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + count--; + } + + dma->tx_size = count & ~(dma_get_cache_alignment() - 1); + dma->tx_transfer_addr = dma->tx_addr + xmit->tail; + + dma_sync_single_for_device(ourport->port.dev, dma->tx_transfer_addr, + dma->tx_size, DMA_TO_DEVICE); + + dma->tx_desc = dmaengine_prep_slave_single(dma->tx_chan, + dma->tx_transfer_addr, dma->tx_size, + DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); + if (!dma->tx_desc) { + dev_err(ourport->port.dev, "Unable to get desc for Tx\n"); + return -EIO; + } + + dma->tx_desc->callback = s3c24xx_serial_tx_dma_complete; + dma->tx_desc->callback_param = ourport; + dma->tx_bytes_requested = dma->tx_size; + + ourport->tx_in_progress = S3C24XX_TX_DMA; + dma->tx_cookie = dmaengine_submit(dma->tx_desc); + dma_async_issue_pending(dma->tx_chan); + return 0; +} + +static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport) +{ + struct uart_port *port = &ourport->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned long count; + + /* Get data size up to the end of buffer */ + count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE); + + if (!count) { + s3c24xx_serial_stop_tx(port); + return; + } + + if (!ourport->dma || !ourport->dma->tx_chan || count < port->fifosize) + s3c24xx_serial_start_tx_pio(ourport); + else + s3c24xx_serial_start_tx_dma(ourport, count); +} + +void s3c24xx_serial_start_tx(struct uart_port *port) { struct s3c24xx_uart_port *ourport = to_ourport(port); + struct circ_buf *xmit = &port->state->xmit; if (!tx_enabled(port)) { if (port->flags & UPF_CONS_FLOW) s3c24xx_serial_rx_disable(port); - if (s3c24xx_serial_has_interrupt_mask(port)) - __clear_bit(S3C64XX_UINTM_TXD, - portaddrl(port, S3C64XX_UINTM)); - else - enable_irq(ourport->tx_irq); tx_enabled(port) = 1; + if (!ourport->dma || !ourport->dma->tx_chan) { + if (s3c24xx_serial_has_interrupt_mask(port)) + __clear_bit(S3C64XX_UINTM_TXD, + portaddrl(port, S3C64XX_UINTM)); + else + enable_irq(ourport->tx_irq); + + s3c24xx_serial_start_tx_pio(ourport); + } + } + + if (ourport->dma && ourport->dma->tx_chan) { + if (!uart_circ_empty(xmit) && !ourport->tx_in_progress) + s3c24xx_serial_start_next_tx(ourport); } } @@ -333,10 +519,17 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) struct uart_port *port = &ourport->port; struct circ_buf *xmit = &port->state->xmit; unsigned long flags; - int count = port->fifosize; + int count; spin_lock_irqsave(&port->lock, flags); + count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE); + + if (ourport->dma && ourport->dma->tx_chan && count >= port->fifosize) { + s3c24xx_serial_start_tx_dma(ourport, count); + goto out; + } + if (port->x_char) { wr_regb(port, S3C2410_UTXH, port->x_char); port->icount.tx++; @@ -355,6 +548,7 @@ static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) /* try and drain the buffer... */ + count = port->fifosize; while (!uart_circ_empty(xmit) && count-- > 0) { if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) break; @@ -572,6 +766,7 @@ static void s3c24xx_serial_shutdown(struct uart_port *port) if (ourport->dma) s3c24xx_serial_release_dma(ourport); + ourport->tx_in_progress = 0; } static int s3c24xx_serial_startup(struct uart_port *port) @@ -650,8 +845,19 @@ static int s3c64xx_serial_startup(struct uart_port *port) tx_enabled(port) = 0; ourport->tx_claimed = 1; + spin_lock_irqsave(&port->lock, flags); + + ufcon = rd_regl(port, S3C2410_UFCON); + ufcon |= S3C2410_UFCON_RESETRX | S3C2410_UFCON_RESETTX; + wr_regl(port, S3C2410_UFCON, ufcon); + + enable_rx_pio(ourport); + + spin_unlock_irqrestore(&port->lock, flags); + /* Enable Rx Interrupt */ __clear_bit(S3C64XX_UINTM_RXD, portaddrl(port, S3C64XX_UINTM)); + dbg("s3c64xx_serial_startup ok\n"); return ret; } diff --git a/drivers/tty/serial/samsung.h b/drivers/tty/serial/samsung.h index 8dc250c6b47..e60011c591e 100644 --- a/drivers/tty/serial/samsung.h +++ b/drivers/tty/serial/samsung.h @@ -86,6 +86,9 @@ struct s3c24xx_uart_port { unsigned int rx_irq; unsigned int tx_irq; + unsigned int tx_in_progress; + unsigned int tx_mode; + struct s3c24xx_uart_info *info; struct clk *clk; struct clk *baudclk; -- cgit v1.2.3-70-g09d2 From b543c301c2c5fbf24ed160a0c227122f09f38e5f Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 10 Dec 2014 12:49:27 +0100 Subject: serial: samsung: add DMA support for RX Add RX DMA transfers support for samsung serial driver. It's enabled when DMA controller for RX channel is specified in device-tree. DMA transactions are started when number of bytes in RX FIFO reaches trigger level, otherwise PIO mode is used. DMA transfer size is always PAGE_SIZE which can cause large latency when smaller data amount is transferred, so we always terminate DMA transaction on RX timeout interrupt. Timeout interval is set to 64 frame times. Based on previous work of Sylwester Nawrocki and Lukasz Czerwinski. Signed-off-by: Robert Baldyga Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.c | 233 ++++++++++++++++++++++++++++++++++++++++++- drivers/tty/serial/samsung.h | 1 + 2 files changed, 231 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index c04618052e5..dae3c16ece0 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -83,6 +83,8 @@ static void dbg(const char *fmt, ...) #define S3C24XX_TX_PIO 1 #define S3C24XX_TX_DMA 2 +#define S3C24XX_RX_PIO 1 +#define S3C24XX_RX_DMA 2 /* macros to change one thing to another */ #define tx_enabled(port) ((port)->unused[0]) @@ -373,9 +375,65 @@ void s3c24xx_serial_start_tx(struct uart_port *port) } } +static void s3c24xx_uart_copy_rx_to_tty(struct s3c24xx_uart_port *ourport, + struct tty_port *tty, int count) +{ + struct s3c24xx_uart_dma *dma = ourport->dma; + int copied; + + if (!count) + return; + + dma_sync_single_for_cpu(ourport->port.dev, dma->rx_addr, + dma->rx_size, DMA_FROM_DEVICE); + + ourport->port.icount.rx += count; + if (!tty) { + dev_err(ourport->port.dev, "No tty port\n"); + return; + } + copied = tty_insert_flip_string(tty, + ((unsigned char *)(ourport->dma->rx_buf)), count); + if (copied != count) { + WARN_ON(1); + dev_err(ourport->port.dev, "RxData copy to tty layer failed\n"); + } +} + +static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport, + unsigned long ufstat); + +static void uart_rx_drain_fifo(struct s3c24xx_uart_port *ourport) +{ + struct uart_port *port = &ourport->port; + struct tty_port *tty = &port->state->port; + unsigned int ch, ufstat; + unsigned int count; + + ufstat = rd_regl(port, S3C2410_UFSTAT); + count = s3c24xx_serial_rx_fifocnt(ourport, ufstat); + + if (!count) + return; + + while (count-- > 0) { + ch = rd_regb(port, S3C2410_URXH); + + ourport->port.icount.rx++; + tty_insert_flip_char(tty, ch, TTY_NORMAL); + } + + tty_flip_buffer_push(tty); +} + static void s3c24xx_serial_stop_rx(struct uart_port *port) { struct s3c24xx_uart_port *ourport = to_ourport(port); + struct s3c24xx_uart_dma *dma = ourport->dma; + struct tty_port *t = &port->state->port; + struct dma_tx_state state; + enum dma_status dma_status; + unsigned int received; if (rx_enabled(port)) { dbg("s3c24xx_serial_stop_rx: port=%p\n", port); @@ -386,6 +444,17 @@ static void s3c24xx_serial_stop_rx(struct uart_port *port) disable_irq_nosync(ourport->rx_irq); rx_enabled(port) = 0; } + if (dma && dma->rx_chan) { + dmaengine_pause(dma->tx_chan); + dma_status = dmaengine_tx_status(dma->rx_chan, + dma->rx_cookie, &state); + if (dma_status == DMA_IN_PROGRESS || + dma_status == DMA_PAUSED) { + received = dma->rx_bytes_requested - state.residue; + dmaengine_terminate_all(dma->rx_chan); + s3c24xx_uart_copy_rx_to_tty(ourport, t, received); + } + } } static inline struct s3c24xx_uart_info @@ -417,12 +486,157 @@ static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport, return (ufstat & info->rx_fifomask) >> info->rx_fifoshift; } +static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport); +static void s3c24xx_serial_rx_dma_complete(void *args) +{ + struct s3c24xx_uart_port *ourport = args; + struct uart_port *port = &ourport->port; + + struct s3c24xx_uart_dma *dma = ourport->dma; + struct tty_port *t = &port->state->port; + struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port); + + struct dma_tx_state state; + unsigned long flags; + int received; + + dmaengine_tx_status(dma->rx_chan, dma->rx_cookie, &state); + received = dma->rx_bytes_requested - state.residue; + async_tx_ack(dma->rx_desc); + + spin_lock_irqsave(&port->lock, flags); + + if (received) + s3c24xx_uart_copy_rx_to_tty(ourport, t, received); + + if (tty) { + tty_flip_buffer_push(t); + tty_kref_put(tty); + } + + s3c64xx_start_rx_dma(ourport); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport) +{ + struct s3c24xx_uart_dma *dma = ourport->dma; + + dma_sync_single_for_device(ourport->port.dev, dma->rx_addr, + dma->rx_size, DMA_FROM_DEVICE); + + dma->rx_desc = dmaengine_prep_slave_single(dma->rx_chan, + dma->rx_addr, dma->rx_size, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!dma->rx_desc) { + dev_err(ourport->port.dev, "Unable to get desc for Rx\n"); + return; + } + + dma->rx_desc->callback = s3c24xx_serial_rx_dma_complete; + dma->rx_desc->callback_param = ourport; + dma->rx_bytes_requested = dma->rx_size; + + dma->rx_cookie = dmaengine_submit(dma->rx_desc); + dma_async_issue_pending(dma->rx_chan); +} /* ? - where has parity gone?? */ #define S3C2410_UERSTAT_PARITY (0x1000) -static irqreturn_t -s3c24xx_serial_rx_chars(int irq, void *dev_id) +static void enable_rx_dma(struct s3c24xx_uart_port *ourport) +{ + struct uart_port *port = &ourport->port; + unsigned int ucon; + + /* set Rx mode to DMA mode */ + ucon = rd_regl(port, S3C2410_UCON); + ucon &= ~(S3C64XX_UCON_RXBURST_MASK | + S3C64XX_UCON_TIMEOUT_MASK | + S3C64XX_UCON_EMPTYINT_EN | + S3C64XX_UCON_DMASUS_EN | + S3C64XX_UCON_TIMEOUT_EN | + S3C64XX_UCON_RXMODE_MASK); + ucon |= S3C64XX_UCON_RXBURST_16 | + 0xf << S3C64XX_UCON_TIMEOUT_SHIFT | + S3C64XX_UCON_EMPTYINT_EN | + S3C64XX_UCON_TIMEOUT_EN | + S3C64XX_UCON_RXMODE_DMA; + wr_regl(port, S3C2410_UCON, ucon); + + ourport->rx_mode = S3C24XX_RX_DMA; +} + +static void enable_rx_pio(struct s3c24xx_uart_port *ourport) +{ + struct uart_port *port = &ourport->port; + unsigned int ucon; + + /* set Rx mode to DMA mode */ + ucon = rd_regl(port, S3C2410_UCON); + ucon &= ~(S3C64XX_UCON_TIMEOUT_MASK | + S3C64XX_UCON_EMPTYINT_EN | + S3C64XX_UCON_DMASUS_EN | + S3C64XX_UCON_TIMEOUT_EN | + S3C64XX_UCON_RXMODE_MASK); + ucon |= 0xf << S3C64XX_UCON_TIMEOUT_SHIFT | + S3C64XX_UCON_TIMEOUT_EN | + S3C64XX_UCON_RXMODE_CPU; + wr_regl(port, S3C2410_UCON, ucon); + + ourport->rx_mode = S3C24XX_RX_PIO; +} + +static irqreturn_t s3c24xx_serial_rx_chars_dma(int irq, void *dev_id) +{ + unsigned int utrstat, ufstat, received; + struct s3c24xx_uart_port *ourport = dev_id; + struct uart_port *port = &ourport->port; + struct s3c24xx_uart_dma *dma = ourport->dma; + struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port); + struct tty_port *t = &port->state->port; + unsigned long flags; + struct dma_tx_state state; + + utrstat = rd_regl(port, S3C2410_UTRSTAT); + ufstat = rd_regl(port, S3C2410_UFSTAT); + + spin_lock_irqsave(&port->lock, flags); + + if (!(utrstat & S3C2410_UTRSTAT_TIMEOUT)) { + s3c64xx_start_rx_dma(ourport); + if (ourport->rx_mode == S3C24XX_RX_PIO) + enable_rx_dma(ourport); + goto finish; + } + + if (ourport->rx_mode == S3C24XX_RX_DMA) { + dmaengine_pause(dma->rx_chan); + dmaengine_tx_status(dma->rx_chan, dma->rx_cookie, &state); + dmaengine_terminate_all(dma->rx_chan); + received = dma->rx_bytes_requested - state.residue; + s3c24xx_uart_copy_rx_to_tty(ourport, t, received); + + enable_rx_pio(ourport); + } + + uart_rx_drain_fifo(ourport); + + if (tty) { + tty_flip_buffer_push(t); + tty_kref_put(tty); + } + + wr_regl(port, S3C2410_UTRSTAT, S3C2410_UTRSTAT_TIMEOUT); + +finish: + spin_unlock_irqrestore(&port->lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t s3c24xx_serial_rx_chars_pio(int irq, void *dev_id) { struct s3c24xx_uart_port *ourport = dev_id; struct uart_port *port = &ourport->port; @@ -513,6 +727,16 @@ out: return IRQ_HANDLED; } + +static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id) +{ + struct s3c24xx_uart_port *ourport = dev_id; + + if (ourport->dma && ourport->dma->rx_chan) + return s3c24xx_serial_rx_chars_dma(irq, dev_id); + return s3c24xx_serial_rx_chars_pio(irq, dev_id); +} + static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) { struct s3c24xx_uart_port *ourport = id; @@ -818,6 +1042,8 @@ err: static int s3c64xx_serial_startup(struct uart_port *port) { struct s3c24xx_uart_port *ourport = to_ourport(port); + unsigned long flags; + unsigned int ufcon; int ret; dbg("s3c64xx_serial_startup: port=%p (%08llx,%p)\n", @@ -848,7 +1074,8 @@ static int s3c64xx_serial_startup(struct uart_port *port) spin_lock_irqsave(&port->lock, flags); ufcon = rd_regl(port, S3C2410_UFCON); - ufcon |= S3C2410_UFCON_RESETRX | S3C2410_UFCON_RESETTX; + ufcon |= S3C2410_UFCON_RESETRX | S3C2410_UFCON_RESETTX | + S5PV210_UFCON_RXTRIG8; wr_regl(port, S3C2410_UFCON, ufcon); enable_rx_pio(ourport); diff --git a/drivers/tty/serial/samsung.h b/drivers/tty/serial/samsung.h index e60011c591e..d275032aa68 100644 --- a/drivers/tty/serial/samsung.h +++ b/drivers/tty/serial/samsung.h @@ -88,6 +88,7 @@ struct s3c24xx_uart_port { unsigned int tx_in_progress; unsigned int tx_mode; + unsigned int rx_mode; struct s3c24xx_uart_info *info; struct clk *clk; -- cgit v1.2.3-70-g09d2 From 47ef6df27e0b420b44966043935ca96791184782 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Tue, 16 Dec 2014 15:27:46 +0800 Subject: serial: altera-juart: fix NULL device in log message Add device pointer to port->dev. Before: "(NULL device *): ttyJ0 at MMIO 0x1008 (irq = 2, base_baud = 0) is a Altera JTAG UART" After: "altera_jtaguart 1008.serial: ttyJ0 at MMIO 0x1008 (irq = 2, base_baud = 0) is a Altera JTAG UART" Signed-off-by: Ley Foon Tan Acked-by: Tobias Klauser Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/altera_jtaguart.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/tty/serial/altera_jtaguart.c b/drivers/tty/serial/altera_jtaguart.c index 192d0435bb8..0fefdd8931a 100644 --- a/drivers/tty/serial/altera_jtaguart.c +++ b/drivers/tty/serial/altera_jtaguart.c @@ -441,6 +441,7 @@ static int altera_jtaguart_probe(struct platform_device *pdev) port->iotype = SERIAL_IO_MEM; port->ops = &altera_jtaguart_ops; port->flags = UPF_BOOT_AUTOCONF; + port->dev = &pdev->dev; uart_add_one_port(&altera_jtaguart_driver, port); -- cgit v1.2.3-70-g09d2 From b820cd7643d5fdf05d6f5d32507d330609113248 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Tue, 16 Dec 2014 15:28:34 +0800 Subject: serial: altera-uart: fix NULL device in log message Add device pointer to port->dev. Before: "(NULL device *): ttyAL0 at MMIO 0x2020 (irq = 6, base_baud = 3125000) is a Altera UART" After: "altera_uart 2020.serial: ttyAL0 at MMIO 0x2020 (irq = 6, base_baud = 3125000) is a Altera UART" Signed-off-by: Ley Foon Tan Acked-by: Tobias Klauser Acked-by: Pavel Machek Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/altera_uart.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/tty/serial/altera_uart.c b/drivers/tty/serial/altera_uart.c index eb15a50623c..b2859fe07e1 100644 --- a/drivers/tty/serial/altera_uart.c +++ b/drivers/tty/serial/altera_uart.c @@ -589,6 +589,7 @@ static int altera_uart_probe(struct platform_device *pdev) port->iotype = SERIAL_IO_MEM; port->ops = &altera_uart_ops; port->flags = UPF_BOOT_AUTOCONF; + port->dev = &pdev->dev; platform_set_drvdata(pdev, port); -- cgit v1.2.3-70-g09d2 From 8d267fd98b7706a6296409b69bf2cca42087a9ef Mon Sep 17 00:00:00 2001 From: Eric Nelson Date: Thu, 18 Dec 2014 12:37:13 -0700 Subject: serial: imx: preserve characters with parity or framing errors If IGNPAR/INPCK are clear in termios->c_iflag, characters received with parity or framing errors should be preserved and passed to the upper layers of the tty stack. Specifically, the decision of whether to set the character value to zero should be made by n_tty.c/n_tty_receive_parity_error(). Signed-off-by: Eric Nelson Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 72b800b296b..6ac22d75a4b 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -733,7 +733,7 @@ static irqreturn_t imx_rxint(int irq, void *dev_id) continue; } - rx &= sport->port.read_status_mask; + rx &= (sport->port.read_status_mask | 0xFF); if (rx & URXD_BRK) flg = TTY_BREAK; -- cgit v1.2.3-70-g09d2 From 865cea8589546025759689e034b950d1c59ed291 Mon Sep 17 00:00:00 2001 From: Eric Nelson Date: Thu, 18 Dec 2014 12:37:14 -0700 Subject: serial: imx: ignore framing errors when IGNPAR is set. When IGNPAR is set in termios->c_iflag, characters with framing errors should be ignored. Signed-off-by: Eric Nelson Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 6ac22d75a4b..59d9ef10b08 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -1410,7 +1410,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, */ sport->port.ignore_status_mask = 0; if (termios->c_iflag & IGNPAR) - sport->port.ignore_status_mask |= URXD_PRERR; + sport->port.ignore_status_mask |= URXD_PRERR | URXD_FRMERR; if (termios->c_iflag & IGNBRK) { sport->port.ignore_status_mask |= URXD_BRK; /* -- cgit v1.2.3-70-g09d2 From 5930cb3511dfb4392de1180c892b353316ef29ec Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Thu, 18 Dec 2014 21:45:24 +0200 Subject: serial: driver for Conexant Digicolor USART Signed-off-by: Baruch Siach Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 15 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/digicolor-usart.c | 563 +++++++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 582 insertions(+) create mode 100644 drivers/tty/serial/digicolor-usart.c (limited to 'drivers') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index c79b43cd601..96ec6cfd74e 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1549,6 +1549,21 @@ config SERIAL_FSL_LPUART_CONSOLE If you have enabled the lpuart serial port on the Freescale SoCs, you can make it the console by answering Y to this option. +config SERIAL_CONEXANT_DIGICOLOR + tristate "Conexant Digicolor CX92xxx USART serial port support" + depends on OF + select SERIAL_CORE + help + Support for the on-chip USART on Conexant Digicolor SoCs. + +config SERIAL_CONEXANT_DIGICOLOR_CONSOLE + bool "Console on Conexant Digicolor serial port" + depends on SERIAL_CONEXANT_DIGICOLOR=y + select SERIAL_CORE_CONSOLE + help + If you have enabled the USART serial port on Conexant Digicolor + SoCs, you can make it the console by answering Y to this option. + config SERIAL_ST_ASC tristate "ST ASC serial port support" select SERIAL_CORE diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 9a548acf5fd..770a19bb7fc 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_SERIAL_EFM32_UART) += efm32-uart.o obj-$(CONFIG_SERIAL_ARC) += arc_uart.o obj-$(CONFIG_SERIAL_RP2) += rp2.o obj-$(CONFIG_SERIAL_FSL_LPUART) += fsl_lpuart.o +obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o # GPIOLIB helpers for modem control lines diff --git a/drivers/tty/serial/digicolor-usart.c b/drivers/tty/serial/digicolor-usart.c new file mode 100644 index 00000000000..09ce0b3764e --- /dev/null +++ b/drivers/tty/serial/digicolor-usart.c @@ -0,0 +1,563 @@ +/* + * Driver for Conexant Digicolor serial ports (USART) + * + * Author: Baruch Siach + * + * Copyright (C) 2014 Paradox Innovation Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UA_ENABLE 0x00 +#define UA_ENABLE_ENABLE BIT(0) + +#define UA_CONTROL 0x01 +#define UA_CONTROL_RX_ENABLE BIT(0) +#define UA_CONTROL_TX_ENABLE BIT(1) +#define UA_CONTROL_SOFT_RESET BIT(2) + +#define UA_STATUS 0x02 +#define UA_STATUS_PARITY_ERR BIT(0) +#define UA_STATUS_FRAME_ERR BIT(1) +#define UA_STATUS_OVERRUN_ERR BIT(2) +#define UA_STATUS_TX_READY BIT(6) + +#define UA_CONFIG 0x03 +#define UA_CONFIG_CHAR_LEN BIT(0) +#define UA_CONFIG_STOP_BITS BIT(1) +#define UA_CONFIG_PARITY BIT(2) +#define UA_CONFIG_ODD_PARITY BIT(4) + +#define UA_EMI_REC 0x04 + +#define UA_HBAUD_LO 0x08 +#define UA_HBAUD_HI 0x09 + +#define UA_STATUS_FIFO 0x0a +#define UA_STATUS_FIFO_RX_EMPTY BIT(2) +#define UA_STATUS_FIFO_RX_INT_ALMOST BIT(3) +#define UA_STATUS_FIFO_TX_FULL BIT(4) +#define UA_STATUS_FIFO_TX_INT_ALMOST BIT(7) + +#define UA_CONFIG_FIFO 0x0b +#define UA_CONFIG_FIFO_RX_THRESH 7 +#define UA_CONFIG_FIFO_RX_FIFO_MODE BIT(3) +#define UA_CONFIG_FIFO_TX_FIFO_MODE BIT(7) + +#define UA_INTFLAG_CLEAR 0x1c +#define UA_INTFLAG_SET 0x1d +#define UA_INT_ENABLE 0x1e +#define UA_INT_STATUS 0x1f + +#define UA_INT_TX BIT(0) +#define UA_INT_RX BIT(1) + +#define DIGICOLOR_USART_NR 3 + +/* + * We use the 16 bytes hardware FIFO to buffer Rx traffic. Rx interrupt is + * only produced when the FIFO is filled more than a certain configurable + * threshold. Unfortunately, there is no way to set this threshold below half + * FIFO. This means that we must periodically poll the FIFO status register to + * see whether there are waiting Rx bytes. + */ + +struct digicolor_port { + struct uart_port port; + struct delayed_work rx_poll_work; +}; + +static struct uart_port *digicolor_ports[DIGICOLOR_USART_NR]; + +static bool digicolor_uart_tx_full(struct uart_port *port) +{ + return !!(readb_relaxed(port->membase + UA_STATUS_FIFO) & + UA_STATUS_FIFO_TX_FULL); +} + +static bool digicolor_uart_rx_empty(struct uart_port *port) +{ + return !!(readb_relaxed(port->membase + UA_STATUS_FIFO) & + UA_STATUS_FIFO_RX_EMPTY); +} + +static void digicolor_uart_stop_tx(struct uart_port *port) +{ + u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE); + + int_enable &= ~UA_INT_TX; + writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE); +} + +static void digicolor_uart_start_tx(struct uart_port *port) +{ + u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE); + + int_enable |= UA_INT_TX; + writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE); +} + +static void digicolor_uart_stop_rx(struct uart_port *port) +{ + u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE); + + int_enable &= ~UA_INT_RX; + writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE); +} + +static void digicolor_rx_poll(struct work_struct *work) +{ + struct digicolor_port *dp = + container_of(to_delayed_work(work), + struct digicolor_port, rx_poll_work); + + if (!digicolor_uart_rx_empty(&dp->port)) + /* force RX interrupt */ + writeb_relaxed(UA_INT_RX, dp->port.membase + UA_INTFLAG_SET); + + schedule_delayed_work(&dp->rx_poll_work, msecs_to_jiffies(100)); +} + +static void digicolor_uart_rx(struct uart_port *port) +{ + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + + while (1) { + u8 status, ch; + unsigned int ch_flag; + + if (digicolor_uart_rx_empty(port)) + break; + + ch = readb_relaxed(port->membase + UA_EMI_REC); + status = readb_relaxed(port->membase + UA_STATUS); + + port->icount.rx++; + ch_flag = TTY_NORMAL; + + if (status) { + if (status & UA_STATUS_PARITY_ERR) + port->icount.parity++; + else if (status & UA_STATUS_FRAME_ERR) + port->icount.frame++; + else if (status & UA_STATUS_OVERRUN_ERR) + port->icount.overrun++; + + status &= port->read_status_mask; + + if (status & UA_STATUS_PARITY_ERR) + ch_flag = TTY_PARITY; + else if (status & UA_STATUS_FRAME_ERR) + ch_flag = TTY_FRAME; + else if (status & UA_STATUS_OVERRUN_ERR) + ch_flag = TTY_OVERRUN; + } + + if (uart_handle_sysrq_char(port, ch)) + continue; + + if (status & port->ignore_status_mask) + continue; + + uart_insert_char(port, status, UA_STATUS_OVERRUN_ERR, ch, + ch_flag); + } + + spin_unlock_irqrestore(&port->lock, flags); + + tty_flip_buffer_push(&port->state->port); +} + +static void digicolor_uart_tx(struct uart_port *port) +{ + struct circ_buf *xmit = &port->state->xmit; + unsigned long flags; + + if (digicolor_uart_tx_full(port)) + return; + + spin_lock_irqsave(&port->lock, flags); + + if (port->x_char) { + writeb_relaxed(port->x_char, port->membase + UA_EMI_REC); + port->icount.tx++; + port->x_char = 0; + goto out; + } + + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + digicolor_uart_stop_tx(port); + goto out; + } + + while (!uart_circ_empty(xmit)) { + writeb(xmit->buf[xmit->tail], port->membase + UA_EMI_REC); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + + if (digicolor_uart_tx_full(port)) + break; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + +out: + spin_unlock_irqrestore(&port->lock, flags); +} + +static irqreturn_t digicolor_uart_int(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + u8 int_status = readb_relaxed(port->membase + UA_INT_STATUS); + + writeb_relaxed(UA_INT_RX | UA_INT_TX, + port->membase + UA_INTFLAG_CLEAR); + + if (int_status & UA_INT_RX) + digicolor_uart_rx(port); + if (int_status & UA_INT_TX) + digicolor_uart_tx(port); + + return IRQ_HANDLED; +} + +static unsigned int digicolor_uart_tx_empty(struct uart_port *port) +{ + u8 status = readb_relaxed(port->membase + UA_STATUS); + + return (status & UA_STATUS_TX_READY) ? TIOCSER_TEMT : 0; +} + +static unsigned int digicolor_uart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CTS; +} + +static void digicolor_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static void digicolor_uart_break_ctl(struct uart_port *port, int state) +{ +} + +static int digicolor_uart_startup(struct uart_port *port) +{ + struct digicolor_port *dp = + container_of(port, struct digicolor_port, port); + + writeb_relaxed(UA_ENABLE_ENABLE, port->membase + UA_ENABLE); + writeb_relaxed(UA_CONTROL_SOFT_RESET, port->membase + UA_CONTROL); + writeb_relaxed(0, port->membase + UA_CONTROL); + + writeb_relaxed(UA_CONFIG_FIFO_RX_FIFO_MODE + | UA_CONFIG_FIFO_TX_FIFO_MODE | UA_CONFIG_FIFO_RX_THRESH, + port->membase + UA_CONFIG_FIFO); + writeb_relaxed(UA_STATUS_FIFO_RX_INT_ALMOST, + port->membase + UA_STATUS_FIFO); + writeb_relaxed(UA_CONTROL_RX_ENABLE | UA_CONTROL_TX_ENABLE, + port->membase + UA_CONTROL); + writeb_relaxed(UA_INT_TX | UA_INT_RX, + port->membase + UA_INT_ENABLE); + + schedule_delayed_work(&dp->rx_poll_work, msecs_to_jiffies(100)); + + return 0; +} + +static void digicolor_uart_shutdown(struct uart_port *port) +{ + struct digicolor_port *dp = + container_of(port, struct digicolor_port, port); + + writeb_relaxed(0, port->membase + UA_ENABLE); + cancel_delayed_work_sync(&dp->rx_poll_work); +} + +static void digicolor_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned int baud, divisor; + u8 config = 0; + unsigned long flags; + + /* Mask termios capabilities we don't support */ + termios->c_cflag &= ~CMSPAR; + termios->c_iflag &= ~(BRKINT | IGNBRK); + + /* Limit baud rates so that we don't need the fractional divider */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / (0x10000*16), + port->uartclk / 256); + divisor = uart_get_divisor(port, baud) - 1; + + switch (termios->c_cflag & CSIZE) { + case CS7: + break; + case CS8: + default: + config |= UA_CONFIG_CHAR_LEN; + break; + } + + if (termios->c_cflag & CSTOPB) + config |= UA_CONFIG_STOP_BITS; + + if (termios->c_cflag & PARENB) { + config |= UA_CONFIG_PARITY; + if (termios->c_cflag & PARODD) + config |= UA_CONFIG_ODD_PARITY; + } + + /* Set read status mask */ + port->read_status_mask = UA_STATUS_OVERRUN_ERR; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UA_STATUS_PARITY_ERR + | UA_STATUS_FRAME_ERR; + + /* Set status ignore mask */ + port->ignore_status_mask = 0; + if (!(termios->c_cflag & CREAD)) + port->ignore_status_mask |= UA_STATUS_OVERRUN_ERR + | UA_STATUS_PARITY_ERR | UA_STATUS_FRAME_ERR; + + spin_lock_irqsave(&port->lock, flags); + + uart_update_timeout(port, termios->c_cflag, baud); + + writeb_relaxed(config, port->membase + UA_CONFIG); + writeb_relaxed(divisor & 0xff, port->membase + UA_HBAUD_LO); + writeb_relaxed(divisor >> 8, port->membase + UA_HBAUD_HI); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *digicolor_uart_type(struct uart_port *port) +{ + return (port->type == PORT_DIGICOLOR) ? "DIGICOLOR USART" : NULL; +} + +static void digicolor_uart_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_DIGICOLOR; +} + +static void digicolor_uart_release_port(struct uart_port *port) +{ +} + +static int digicolor_uart_request_port(struct uart_port *port) +{ + return 0; +} + +static const struct uart_ops digicolor_uart_ops = { + .tx_empty = digicolor_uart_tx_empty, + .set_mctrl = digicolor_uart_set_mctrl, + .get_mctrl = digicolor_uart_get_mctrl, + .stop_tx = digicolor_uart_stop_tx, + .start_tx = digicolor_uart_start_tx, + .stop_rx = digicolor_uart_stop_rx, + .break_ctl = digicolor_uart_break_ctl, + .startup = digicolor_uart_startup, + .shutdown = digicolor_uart_shutdown, + .set_termios = digicolor_uart_set_termios, + .type = digicolor_uart_type, + .config_port = digicolor_uart_config_port, + .release_port = digicolor_uart_release_port, + .request_port = digicolor_uart_request_port, +}; + +static void digicolor_uart_console_putchar(struct uart_port *port, int ch) +{ + while (digicolor_uart_tx_full(port)) + cpu_relax(); + + writeb_relaxed(ch, port->membase + UA_EMI_REC); +} + +static void digicolor_uart_console_write(struct console *co, const char *c, + unsigned n) +{ + struct uart_port *port = digicolor_ports[co->index]; + u8 status; + unsigned long flags; + int locked = 1; + + if (port->sysrq || oops_in_progress) + locked = spin_trylock_irqsave(&port->lock, flags); + else + spin_lock_irqsave(&port->lock, flags); + + uart_console_write(port, c, n, digicolor_uart_console_putchar); + + if (locked) + spin_unlock_irqrestore(&port->lock, flags); + + /* Wait for transmitter to become empty */ + do { + status = readb_relaxed(port->membase + UA_STATUS); + } while ((status & UA_STATUS_TX_READY) == 0); +} + +static int digicolor_uart_console_setup(struct console *co, char *options) +{ + int baud = 115200, bits = 8, parity = 'n', flow = 'n'; + struct uart_port *port; + + if (co->index < 0 || co->index >= DIGICOLOR_USART_NR) + return -EINVAL; + + port = digicolor_ports[co->index]; + if (!port) + return -ENODEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static struct console digicolor_console = { + .name = "ttyS", + .device = uart_console_device, + .write = digicolor_uart_console_write, + .setup = digicolor_uart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +static struct uart_driver digicolor_uart = { + .driver_name = "digicolor-usart", + .dev_name = "ttyS", + .nr = DIGICOLOR_USART_NR, +}; + +static int digicolor_uart_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret, index; + struct digicolor_port *dp; + struct resource *res; + struct clk *uart_clk; + + if (!np) { + dev_err(&pdev->dev, "Missing device tree node\n"); + return -ENXIO; + } + + index = of_alias_get_id(np, "serial"); + if (index < 0 || index >= DIGICOLOR_USART_NR) + return -EINVAL; + + dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + uart_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(uart_clk)) + return PTR_ERR(uart_clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dp->port.mapbase = res->start; + dp->port.membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dp->port.membase)) + return PTR_ERR(dp->port.membase); + + dp->port.irq = platform_get_irq(pdev, 0); + if (IS_ERR_VALUE(dp->port.irq)) + return dp->port.irq; + + dp->port.iotype = UPIO_MEM; + dp->port.uartclk = clk_get_rate(uart_clk); + dp->port.fifosize = 16; + dp->port.dev = &pdev->dev; + dp->port.ops = &digicolor_uart_ops; + dp->port.line = index; + dp->port.type = PORT_DIGICOLOR; + spin_lock_init(&dp->port.lock); + + digicolor_ports[index] = &dp->port; + platform_set_drvdata(pdev, &dp->port); + + INIT_DELAYED_WORK(&dp->rx_poll_work, digicolor_rx_poll); + + ret = devm_request_irq(&pdev->dev, dp->port.irq, digicolor_uart_int, 0, + dev_name(&pdev->dev), &dp->port); + if (ret) + return ret; + + return uart_add_one_port(&digicolor_uart, &dp->port); +} + +static int digicolor_uart_remove(struct platform_device *pdev) +{ + struct uart_port *port = platform_get_drvdata(pdev); + + uart_remove_one_port(&digicolor_uart, port); + + return 0; +} + +static const struct of_device_id digicolor_uart_dt_ids[] = { + { .compatible = "cnxt,cx92755-usart", }, + { } +}; +MODULE_DEVICE_TABLE(of, digicolor_uart_dt_ids); + +static struct platform_driver digicolor_uart_platform = { + .driver = { + .name = "digicolor-usart", + .of_match_table = of_match_ptr(digicolor_uart_dt_ids), + }, + .probe = digicolor_uart_probe, + .remove = digicolor_uart_remove, +}; + +static int __init digicolor_uart_init(void) +{ + int ret; + + if (IS_ENABLED(CONFIG_SERIAL_CONEXANT_DIGICOLOR_CONSOLE)) { + digicolor_uart.cons = &digicolor_console; + digicolor_console.data = &digicolor_uart; + } + + ret = uart_register_driver(&digicolor_uart); + if (ret) + return ret; + + return platform_driver_register(&digicolor_uart_platform); +} +module_init(digicolor_uart_init); + +static void __exit digicolor_uart_exit(void) +{ + platform_driver_unregister(&digicolor_uart_platform); + uart_unregister_driver(&digicolor_uart); +} +module_exit(digicolor_uart_exit); + +MODULE_AUTHOR("Baruch Siach "); +MODULE_DESCRIPTION("Conexant Digicolor USART serial driver"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index c17218094f1..9124e837c18 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -248,4 +248,7 @@ /* MESON */ #define PORT_MESON 109 +/* Conexant Digicolor */ +#define PORT_DIGICOLOR 110 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- cgit v1.2.3-70-g09d2 From fddceb8b5399083bcc042266f188116cc779fc80 Mon Sep 17 00:00:00 2001 From: Vijay Rai Date: Mon, 5 Jan 2015 21:14:42 +0530 Subject: tty: 8250: Add 64byte UART support for FSL platforms Some of FSL SoCs like T1040 has new version of UART controller which can support 64byte FiFo. To enable 64 byte support, following needs to be done: -FCR[EN64] needs to be programmed to 1 to enable it. -Also, when FCR[EN64]==1, RTL bits to be used as below to define various Receive Trigger Levels: -FCR[RTL] = 00 1 byte -FCR[RTL] = 01 16 bytes -FCR[RTL] = 10 32 bytes -FCR[RTL] = 11 56 bytes -tx_loadsz is set to 63-bytes instead of 64-bytes to implement workaround of errata A-008006 which states that tx_loadsz should be configured less than Maximum supported fifo bytes Signed-off-by: Vijay Rai Signed-off-by: Priyanka Jain Signed-off-by: Poonam Aggrwal Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 23 ++++++++++++++++++++++- include/uapi/linux/serial_core.h | 3 ++- include/uapi/linux/serial_reg.h | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 3bfcfdb57d0..689169c11f0 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -329,6 +329,17 @@ static const struct serial8250_config uart_config[] = { .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, .flags = UART_CAP_FIFO | UART_CAP_AFE, }, +/* tx_loadsz is set to 63-bytes instead of 64-bytes to implement +workaround of errata A-008006 which states that tx_loadsz should be +configured less than Maximum supported fifo bytes */ + [PORT_16550A_FSL64] = { + .name = "16550A_FSL64", + .fifo_size = 64, + .tx_loadsz = 63, + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10 | + UART_FCR7_64BYTE, + .flags = UART_CAP_FIFO, + }, }; /* Uart divisor latch read */ @@ -956,7 +967,17 @@ static void autoconfig_16550a(struct uart_8250_port *up) up->port.type = PORT_16650; up->capabilities |= UART_CAP_EFR | UART_CAP_SLEEP; } else { - DEBUG_AUTOCONF("Motorola 8xxx DUART "); + serial_out(up, UART_LCR, 0); + serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR7_64BYTE); + status1 = serial_in(up, UART_IIR) >> 5; + serial_out(up, UART_FCR, 0); + serial_out(up, UART_LCR, 0); + + if (status1 == 7) + up->port.type = PORT_16550A_FSL64; + else + DEBUG_AUTOCONF("Motorola 8xxx DUART "); } serial_out(up, UART_EFR, 0); return; diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 9124e837c18..ee628c489e4 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -55,7 +55,8 @@ #define PORT_ALTR_16550_F64 27 /* Altera 16550 UART with 64 FIFOs */ #define PORT_ALTR_16550_F128 28 /* Altera 16550 UART with 128 FIFOs */ #define PORT_RT2880 29 /* Ralink RT2880 internal UART */ -#define PORT_MAX_8250 29 /* max port ID */ +#define PORT_16550A_FSL64 30 /* Freescale 16550 UART with 64 FIFOs */ +#define PORT_MAX_8250 30 /* max port ID */ /* * ARM specific type numbers. These are not currently guaranteed diff --git a/include/uapi/linux/serial_reg.h b/include/uapi/linux/serial_reg.h index 53af3b79012..00adb01fa5f 100644 --- a/include/uapi/linux/serial_reg.h +++ b/include/uapi/linux/serial_reg.h @@ -86,7 +86,8 @@ #define UART_FCR6_T_TRIGGER_8 0x10 /* Mask for transmit trigger set at 8 */ #define UART_FCR6_T_TRIGGER_24 0x20 /* Mask for transmit trigger set at 24 */ #define UART_FCR6_T_TRIGGER_30 0x30 /* Mask for transmit trigger set at 30 */ -#define UART_FCR7_64BYTE 0x20 /* Go into 64 byte mode (TI16C750) */ +#define UART_FCR7_64BYTE 0x20 /* Go into 64 byte mode (TI16C750 and + some Freescale UARTs) */ #define UART_FCR_R_TRIG_SHIFT 6 #define UART_FCR_R_TRIG_BITS(x) \ -- cgit v1.2.3-70-g09d2 From 5567c37dea5e3146c26a691451925218bd484c01 Mon Sep 17 00:00:00 2001 From: Vineet Gupta Date: Tue, 6 Jan 2015 16:59:10 +0530 Subject: serial: 8250_early: optimize early 8250 uart In early 8250, IER is already zero so no point in writing this - twice per line This helped improve the SystemC model based ARC OSCI platform Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: linux-serial@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: Rob Herring Cc: Peter Hurley Signed-off-by: Vineet Gupta Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_early.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_early.c b/drivers/tty/serial/8250/8250_early.c index 4858b8a99d3..ce2a8abd1f5 100644 --- a/drivers/tty/serial/8250/8250_early.c +++ b/drivers/tty/serial/8250/8250_early.c @@ -95,13 +95,16 @@ static void __init early_serial8250_write(struct console *console, /* Save the IER and disable interrupts */ ier = serial8250_early_in(port, UART_IER); - serial8250_early_out(port, UART_IER, 0); + if (ier) + serial8250_early_out(port, UART_IER, 0); uart_console_write(port, s, count, serial_putc); /* Wait for transmitter to become empty and restore the IER */ wait_for_xmitr(port); - serial8250_early_out(port, UART_IER, ier); + + if (ier) + serial8250_early_out(port, UART_IER, ier); } static unsigned int __init probe_baud(struct uart_port *port) -- cgit v1.2.3-70-g09d2 From 1d6a5e4ce883be40c05dc4080d24fab0b432a9ec Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 7 Dec 2014 20:20:48 +0100 Subject: TTY: fix misspelling of current function in string Replace the last argument of serial_paranoia_check by the actual function name. This was done using Coccinelle, including the use of Levenshtein distance, as proposed by Rasmus Villemoes. Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/tty/amiserial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/amiserial.c b/drivers/tty/amiserial.c index d9f85f95eb2..b2d76005595 100644 --- a/drivers/tty/amiserial.c +++ b/drivers/tty/amiserial.c @@ -931,7 +931,7 @@ static void rs_send_xchar(struct tty_struct *tty, char ch) struct serial_state *info = tty->driver_data; unsigned long flags; - if (serial_paranoia_check(info, tty->name, "rs_send_char")) + if (serial_paranoia_check(info, tty->name, "rs_send_xchar")) return; info->x_char = ch; -- cgit v1.2.3-70-g09d2 From e5f50fbef0818f01d9ad387c54a110de0ab0e663 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 7 Dec 2014 20:21:01 +0100 Subject: TTY: fix misspelling of current function in string Replace a misspelled function name by %s and then __func__. This was done using Coccinelle, including the use of Levenshtein distance, as proposed by Rasmus Villemoes. Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/tty/rocket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/rocket.c b/drivers/tty/rocket.c index 383c4c79663..c8dd8dc3108 100644 --- a/drivers/tty/rocket.c +++ b/drivers/tty/rocket.c @@ -1390,7 +1390,7 @@ static void rp_unthrottle(struct tty_struct *tty) tty->ldisc.chars_in_buffer(tty)); #endif - if (rocket_paranoia_check(info, "rp_throttle")) + if (rocket_paranoia_check(info, "rp_unthrottle")) return; if (I_IXOFF(tty)) @@ -1458,7 +1458,7 @@ static void rp_wait_until_sent(struct tty_struct *tty, int timeout) orig_jiffies = jiffies; #ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT - printk(KERN_INFO "In RP_wait_until_sent(%d) (jiff=%lu)...\n", timeout, + printk(KERN_INFO "In %s(%d) (jiff=%lu)...\n", __func__, timeout, jiffies); printk(KERN_INFO "cps=%d...\n", info->cps); #endif -- cgit v1.2.3-70-g09d2 From 2cf30f752c8f73713f58796eb218cafa242d66fe Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 16 Dec 2014 00:15:59 +0200 Subject: vt: fix check for system/busy console drivers when unregistering them The default console driver (conswitchp) and busy drivers bound to a console (as reported by con_is_bound()) shouldn't be unregistered. System console drivers (without the CON_DRIVER_FLAG_MODULE flag) can be unregistered, provided they are neither default nor busy. The current code checks for the CON_DRIVER_FLAG_INIT flag but this doesn't make sense: this flag is set for a driver whenever its associated console's con_startup() function is called, which first happens when the console driver is registered (so before the console gets bound) and gets cleared when the console gets unbound. The purpose of this flag is to show if we need to call con_startup() on a console before we use it. Based on the above, do_unregister_con_driver() in its current form will allow unregistering a console driver only if it was never bound, but will refuse to unregister one that was bound and later unbound. Fix this by dropping the CON_DRIVER_FLAG_INIT check, allowing unregistering of any console driver provided that it's not the default one and it's not busy. v2: - reword the third paragraph to clarify how the fix works (Peter Hurley) v3: - unchanged v4: - Allow unregistering a system console driver too, needed by i915 to unregister vgacon. Update commit description accordingly. (Daniel) Signed-off-by: Imre Deak Reviewed-by: Daniel Vetter Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index f3fbbbca9bd..9c046fbe851 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -3665,8 +3665,7 @@ int do_unregister_con_driver(const struct consw *csw) for (i = 0; i < MAX_NR_CON_DRIVER; i++) { struct con_driver *con_driver = ®istered_con_driver[i]; - if (con_driver->con == csw && - con_driver->flag & CON_DRIVER_FLAG_INIT) { + if (con_driver->con == csw) { vtconsole_deinit_device(con_driver); device_destroy(vtconsole_class, MKDEV(0, con_driver->node)); -- cgit v1.2.3-70-g09d2 From 4c215fe8e288c87b34c3d5bc910c77af586859d2 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 16 Dec 2014 00:16:00 +0200 Subject: vt: fix locking around vt_bind/vt_unbind Currently vt_bind and vt_unbind access at least the con_driver object and registered_con_driver array without holding the console lock. Fix this by locking around the whole function in each case. Signed-off-by: Imre Deak Reviewed-by: Peter Hurley Reviewed-by: Daniel Vetter Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 9c046fbe851..5d36c2358b4 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -3318,11 +3318,8 @@ static int vt_bind(struct con_driver *con) if (first == 0 && last == MAX_NR_CONSOLES -1) deflt = 1; - if (first != -1) { - console_lock(); + if (first != -1) do_bind_con_driver(csw, first, last, deflt); - console_unlock(); - } first = -1; last = -1; @@ -3362,9 +3359,7 @@ static int vt_unbind(struct con_driver *con) deflt = 1; if (first != -1) { - console_lock(); ret = do_unbind_con_driver(csw, first, last, deflt); - console_unlock(); if (ret != 0) return ret; } @@ -3394,11 +3389,15 @@ static ssize_t store_bind(struct device *dev, struct device_attribute *attr, struct con_driver *con = dev_get_drvdata(dev); int bind = simple_strtoul(buf, NULL, 0); + console_lock(); + if (bind) vt_bind(con); else vt_unbind(con); + console_unlock(); + return count; } -- cgit v1.2.3-70-g09d2 From 19e3ae6b4f07a87822c1c9e7ed99d31860e701af Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 23 Jan 2015 17:07:21 -0500 Subject: vt: provide notifications on selection changes The vcs device's poll/fasync support relies on the vt notifier to signal changes to the screen content. Notifier invocations were missing for changes that comes through the selection interface though. Fix that. Tested with BRLTTY 5.2. Signed-off-by: Nicolas Pitre Cc: Dave Mielke Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 5d36c2358b4..6e00572cbeb 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -500,6 +500,7 @@ void invert_screen(struct vc_data *vc, int offset, int count, int viewed) #endif if (DO_UPDATE(vc)) do_update_region(vc, (unsigned long) p, count); + notify_update(vc); } /* used by selection: complement pointer position */ @@ -516,6 +517,7 @@ void complement_pos(struct vc_data *vc, int offset) scr_writew(old, screenpos(vc, old_offset, 1)); if (DO_UPDATE(vc)) vc->vc_sw->con_putc(vc, old, oldy, oldx); + notify_update(vc); } old_offset = offset; @@ -533,8 +535,8 @@ void complement_pos(struct vc_data *vc, int offset) oldy = (offset >> 1) / vc->vc_cols; vc->vc_sw->con_putc(vc, new, oldy, oldx); } + notify_update(vc); } - } static void insert_char(struct vc_data *vc, unsigned int nr) -- cgit v1.2.3-70-g09d2 From 37480a05685ed5b8e1b9bf5e5c53b5810258b149 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Mon, 19 Jan 2015 13:05:03 -0500 Subject: tty: Prevent untrappable signals from malicious program Commit 26df6d13406d1a5 ("tty: Add EXTPROC support for LINEMODE") allows a process which has opened a pty master to send _any_ signal to the process group of the pty slave. Although potentially exploitable by a malicious program running a setuid program on a pty slave, it's unknown if this exploit currently exists. Limit to signals actually used. Cc: Theodore Ts'o Cc: Howard Chu Cc: One Thousand Gnomes Cc: Jiri Slaby Cc: # 2.6.36+ Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index a9d256d6e90..6e1f1505f04 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -210,6 +210,9 @@ static int pty_signal(struct tty_struct *tty, int sig) { struct pid *pgrp; + if (sig != SIGINT && sig != SIGQUIT && sig != SIGTSTP) + return -EINVAL; + if (tty->link) { pgrp = tty_get_pgrp(tty->link); if (pgrp) -- cgit v1.2.3-70-g09d2 From 4a8588a1cf867333187d9ff071e6fbdab587d194 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 10 Jan 2015 01:08:58 +0100 Subject: serial: fsl_lpuart: delete timer on shutdown If the serial port gets closed while a RX transfer is in progress, the timer might fire after the serial port shutdown finished. This leads in a NULL pointer dereference: [ 7.508324] Unable to handle kernel NULL pointer dereference at virtual address 00000000 [ 7.516590] pgd = 86348000 [ 7.519445] [00000000] *pgd=86179831, *pte=00000000, *ppte=00000000 [ 7.526145] Internal error: Oops: 17 [#1] ARM [ 7.530611] Modules linked in: [ 7.533876] CPU: 0 PID: 123 Comm: systemd Not tainted 3.19.0-rc3-00004-g5b11ea7 #1778 [ 7.541827] Hardware name: Freescale Vybrid VF610 (Device Tree) [ 7.547862] task: 861c3400 ti: 86ac8000 task.ti: 86ac8000 [ 7.553392] PC is at lpuart_timer_func+0x24/0xf8 [ 7.558127] LR is at lpuart_timer_func+0x20/0xf8 [ 7.562857] pc : [<802df99c>] lr : [<802df998>] psr: 600b0113 [ 7.562857] sp : 86ac9b90 ip : 86ac9b90 fp : 86ac9bbc [ 7.574467] r10: 80817180 r9 : 80817b98 r8 : 80817998 [ 7.579803] r7 : 807acee0 r6 : 86989000 r5 : 00000100 r4 : 86997210 [ 7.586444] r3 : 86ac8000 r2 : 86ac9bc0 r1 : 86997210 r0 : 00000000 [ 7.593085] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user [ 7.600341] Control: 10c5387d Table: 86348059 DAC: 00000015 [ 7.606203] Process systemd (pid: 123, stack limit = 0x86ac8230) Setup the timer on UART startup which allows to delete the timer unconditionally on shutdown. This also saves the initialization on each transfer. Signed-off-by: Stefan Agner Cc: stable # 3.14 Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index e7cde3a9566..29932b73c00 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -506,9 +506,6 @@ static inline void lpuart_prepare_rx(struct lpuart_port *sport) spin_lock_irqsave(&sport->port.lock, flags); - init_timer(&sport->lpuart_timer); - sport->lpuart_timer.function = lpuart_timer_func; - sport->lpuart_timer.data = (unsigned long)sport; sport->lpuart_timer.expires = jiffies + sport->dma_rx_timeout; add_timer(&sport->lpuart_timer); @@ -1106,6 +1103,8 @@ static int lpuart_startup(struct uart_port *port) sport->lpuart_dma_use = false; } else { sport->lpuart_dma_use = true; + setup_timer(&sport->lpuart_timer, lpuart_timer_func, + (unsigned long)sport); temp = readb(port->membase + UARTCR5); writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5); } @@ -1180,6 +1179,8 @@ static void lpuart_shutdown(struct uart_port *port) devm_free_irq(port->dev, port->irq, sport); if (sport->lpuart_dma_use) { + del_timer_sync(&sport->lpuart_timer); + lpuart_dma_tx_free(port); lpuart_dma_rx_free(port); } -- cgit v1.2.3-70-g09d2 From 5f1437f61a0b351d25b528c159360da3d5e8c77b Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 10 Jan 2015 01:08:59 +0100 Subject: serial: fsl_lpuart: avoid new transfer while DMA is running When the UART is in DMA receive mode (RDMAS set) and one character just arrived while another interrupt is handled (e.g. TX), the RDRF (receiver data register full flag) is set due to the water level of 1. But since the DMA will take care of this character, there is no need to handle it by calling lpuart_prepare_rx. Handling it leads to adding the RX timeout timer twice: [ 74.336698] Kernel BUG at 80053070 [verbose debug info unavailable] [ 74.342999] Internal error: Oops - BUG: 0 [#1] ARM0:00.00 khungtaskd [ 74.347817] Modules linked in: 0 S 0.0 0.0 0:00.00 writeback [ 74.350926] CPU: 0 PID: 0 Comm: swapper Not tainted 3.19.0-rc3-00001-g39d78e2 #1788 [ 74.358617] Hardware name: Freescale Vybrid VF610 (Device Tree)t [ 74.364563] task: 807a7678 ti: 8079c000 task.ti: 8079c000 kblockd [ 74.370002] PC is at add_timer+0x24/0x28.0 0.0 0:00.09 kworker/u2:1 [ 74.373960] LR is at lpuart_int+0x15c/0x3d8 [ 74.378171] pc : [<80053070>] lr : [<802e0d88>] psr: a0010193 [ 74.378171] sp : 8079de10 ip : 8079de20 fp : 8079de1c [ 74.389694] r10: 807d44c0 r9 : 8688c300 r8 : 00000013 [ 74.394943] r7 : 20010193 r6 : 00000000 r5 : 000000a0 r4 : 86997210 [ 74.401498] r3 : ffffa7da r2 : 80817868 r1 : 86997210 r0 : 86997344 [ 74.408052] Flags: NzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel [ 74.415489] Control: 10c5387d Table: 8611c059 DAC: 00000015 [ 74.421265] Process swapper (pid: 0, stack limit = 0x8079c230) ... Solve this by only execute the receiver path (lpuart_prepare_rx) if the DMA receive mode (RDMAS) is not set. Also, make sure the flag is cleared on initialization, in case it has been left set. This can be best reproduced using UART as a serial console, then running top while dd'ing data into the terminal. Signed-off-by: Stefan Agner Cc: stable # 3.14 Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 29932b73c00..e95c4971327 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -755,18 +755,18 @@ out: static irqreturn_t lpuart_int(int irq, void *dev_id) { struct lpuart_port *sport = dev_id; - unsigned char sts; + unsigned char sts, crdma; sts = readb(sport->port.membase + UARTSR1); + crdma = readb(sport->port.membase + UARTCR5); - if (sts & UARTSR1_RDRF) { + if (sts & UARTSR1_RDRF && !(crdma & UARTCR5_RDMAS)) { if (sport->lpuart_dma_use) lpuart_prepare_rx(sport); else lpuart_rxint(irq, dev_id); } - if (sts & UARTSR1_TDRE && - !(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS)) { + if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) { if (sport->lpuart_dma_use) lpuart_pio_tx(sport); else @@ -1106,6 +1106,7 @@ static int lpuart_startup(struct uart_port *port) setup_timer(&sport->lpuart_timer, lpuart_timer_func, (unsigned long)sport); temp = readb(port->membase + UARTCR5); + temp &= ~UARTCR5_RDMAS; writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5); } -- cgit v1.2.3-70-g09d2 From 4a818c4396cb8f0d00b48921654e7918ed54f17f Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 10 Jan 2015 09:33:45 +0100 Subject: serial: fsl_lpuart: move DMA channel request to probe Move the DMA channel request to probe to avoid requesting the DMA channel on each opening of the ttyLPx device. This also fixes a potential issue that TX channel is not freed when only RX channel allocation fails. The DMA channels are now handled independently, so one could use UART with DMA only in TX direction for instance. Signed-off-by: Stefan Agner Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 96 +++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 52 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index e95c4971327..0ea0af76cb9 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -237,7 +237,8 @@ struct lpuart_port { unsigned int rxfifo_size; bool lpuart32; - bool lpuart_dma_use; + bool lpuart_dma_tx_use; + bool lpuart_dma_rx_use; struct dma_chan *dma_tx_chan; struct dma_chan *dma_rx_chan; struct dma_async_tx_descriptor *dma_tx_desc; @@ -568,7 +569,7 @@ static void lpuart_start_tx(struct uart_port *port) temp = readb(port->membase + UARTCR2); writeb(temp | UARTCR2_TIE, port->membase + UARTCR2); - if (sport->lpuart_dma_use) { + if (sport->lpuart_dma_tx_use) { if (!uart_circ_empty(xmit) && !sport->dma_tx_in_progress) lpuart_prepare_tx(sport); } else { @@ -761,13 +762,13 @@ static irqreturn_t lpuart_int(int irq, void *dev_id) crdma = readb(sport->port.membase + UARTCR5); if (sts & UARTSR1_RDRF && !(crdma & UARTCR5_RDMAS)) { - if (sport->lpuart_dma_use) + if (sport->lpuart_dma_rx_use) lpuart_prepare_rx(sport); else lpuart_rxint(irq, dev_id); } if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) { - if (sport->lpuart_dma_use) + if (sport->lpuart_dma_tx_use) lpuart_pio_tx(sport); else lpuart_txint(irq, dev_id); @@ -950,26 +951,17 @@ static int lpuart_dma_tx_request(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *tx_chan; struct dma_slave_config dma_tx_sconfig; dma_addr_t dma_bus; unsigned char *dma_buf; int ret; - tx_chan = dma_request_slave_channel(sport->port.dev, "tx"); - - if (!tx_chan) { - dev_err(sport->port.dev, "Dma tx channel request failed!\n"); - return -ENODEV; - } - - dma_bus = dma_map_single(tx_chan->device->dev, + dma_bus = dma_map_single(sport->dma_tx_chan->device->dev, sport->port.state->xmit.buf, UART_XMIT_SIZE, DMA_TO_DEVICE); - if (dma_mapping_error(tx_chan->device->dev, dma_bus)) { + if (dma_mapping_error(sport->dma_tx_chan->device->dev, dma_bus)) { dev_err(sport->port.dev, "dma_map_single tx failed\n"); - dma_release_channel(tx_chan); return -ENOMEM; } @@ -978,16 +970,14 @@ static int lpuart_dma_tx_request(struct uart_port *port) dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; dma_tx_sconfig.dst_maxburst = sport->txfifo_size; dma_tx_sconfig.direction = DMA_MEM_TO_DEV; - ret = dmaengine_slave_config(tx_chan, &dma_tx_sconfig); + ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig); if (ret < 0) { dev_err(sport->port.dev, "Dma slave config failed, err = %d\n", ret); - dma_release_channel(tx_chan); return ret; } - sport->dma_tx_chan = tx_chan; sport->dma_tx_buf_virt = dma_buf; sport->dma_tx_buf_bus = dma_bus; sport->dma_tx_in_progress = 0; @@ -999,34 +989,24 @@ static int lpuart_dma_rx_request(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *rx_chan; struct dma_slave_config dma_rx_sconfig; dma_addr_t dma_bus; unsigned char *dma_buf; int ret; - rx_chan = dma_request_slave_channel(sport->port.dev, "rx"); - - if (!rx_chan) { - dev_err(sport->port.dev, "Dma rx channel request failed!\n"); - return -ENODEV; - } - dma_buf = devm_kzalloc(sport->port.dev, FSL_UART_RX_DMA_BUFFER_SIZE, GFP_KERNEL); if (!dma_buf) { dev_err(sport->port.dev, "Dma rx alloc failed\n"); - dma_release_channel(rx_chan); return -ENOMEM; } - dma_bus = dma_map_single(rx_chan->device->dev, dma_buf, + dma_bus = dma_map_single(sport->dma_rx_chan->device->dev, dma_buf, FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - if (dma_mapping_error(rx_chan->device->dev, dma_bus)) { + if (dma_mapping_error(sport->dma_rx_chan->device->dev, dma_bus)) { dev_err(sport->port.dev, "dma_map_single rx failed\n"); - dma_release_channel(rx_chan); return -ENOMEM; } @@ -1034,16 +1014,14 @@ static int lpuart_dma_rx_request(struct uart_port *port) dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; dma_rx_sconfig.src_maxburst = 1; dma_rx_sconfig.direction = DMA_DEV_TO_MEM; - ret = dmaengine_slave_config(rx_chan, &dma_rx_sconfig); + ret = dmaengine_slave_config(sport->dma_rx_chan, &dma_rx_sconfig); if (ret < 0) { dev_err(sport->port.dev, "Dma slave config failed, err = %d\n", ret); - dma_release_channel(rx_chan); return ret; } - sport->dma_rx_chan = rx_chan; sport->dma_rx_buf_virt = dma_buf; sport->dma_rx_buf_bus = dma_bus; sport->dma_rx_in_progress = 0; @@ -1055,31 +1033,24 @@ static void lpuart_dma_tx_free(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *dma_chan; dma_unmap_single(sport->port.dev, sport->dma_tx_buf_bus, UART_XMIT_SIZE, DMA_TO_DEVICE); - dma_chan = sport->dma_tx_chan; - sport->dma_tx_chan = NULL; + sport->dma_tx_buf_bus = 0; sport->dma_tx_buf_virt = NULL; - dma_release_channel(dma_chan); } static void lpuart_dma_rx_free(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *dma_chan; dma_unmap_single(sport->port.dev, sport->dma_rx_buf_bus, FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - dma_chan = sport->dma_rx_chan; - sport->dma_rx_chan = NULL; sport->dma_rx_buf_bus = 0; sport->dma_rx_buf_virt = NULL; - dma_release_channel(dma_chan); } static int lpuart_startup(struct uart_port *port) @@ -1098,17 +1069,21 @@ static int lpuart_startup(struct uart_port *port) sport->rxfifo_size = 0x1 << (((temp >> UARTPFIFO_RXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); - /* Whether use dma support by dma request results */ - if (lpuart_dma_tx_request(port) || lpuart_dma_rx_request(port)) { - sport->lpuart_dma_use = false; - } else { - sport->lpuart_dma_use = true; + if (sport->dma_rx_chan && !lpuart_dma_rx_request(port)) { + sport->lpuart_dma_rx_use = true; setup_timer(&sport->lpuart_timer, lpuart_timer_func, (unsigned long)sport); + } else + sport->lpuart_dma_rx_use = false; + + + if (sport->dma_tx_chan && !lpuart_dma_tx_request(port)) { + sport->lpuart_dma_tx_use = true; temp = readb(port->membase + UARTCR5); temp &= ~UARTCR5_RDMAS; writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5); - } + } else + sport->lpuart_dma_tx_use = false; ret = devm_request_irq(port->dev, port->irq, lpuart_int, 0, DRIVER_NAME, sport); @@ -1179,12 +1154,13 @@ static void lpuart_shutdown(struct uart_port *port) devm_free_irq(port->dev, port->irq, sport); - if (sport->lpuart_dma_use) { + if (sport->lpuart_dma_rx_use) { + lpuart_dma_rx_free(&sport->port); del_timer_sync(&sport->lpuart_timer); - - lpuart_dma_tx_free(port); - lpuart_dma_rx_free(port); } + + if (sport->lpuart_dma_tx_use) + lpuart_dma_tx_free(&sport->port); } static void lpuart32_shutdown(struct uart_port *port) @@ -1306,7 +1282,7 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, /* update the per-port timeout */ uart_update_timeout(port, termios->c_cflag, baud); - if (sport->lpuart_dma_use) { + if (sport->lpuart_dma_rx_use) { /* Calculate delay for 1.5 DMA buffers */ sport->dma_rx_timeout = (sport->port.timeout - HZ / 50) * FSL_UART_RX_DMA_BUFFER_SIZE * 3 / @@ -1835,6 +1811,16 @@ static int lpuart_probe(struct platform_device *pdev) return ret; } + sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx"); + if (!sport->dma_tx_chan) + dev_info(sport->port.dev, "DMA tx channel request failed, " + "operating without tx DMA\n"); + + sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx"); + if (!sport->dma_rx_chan) + dev_info(sport->port.dev, "DMA rx channel request failed, " + "operating without rx DMA\n"); + return 0; } @@ -1846,6 +1832,12 @@ static int lpuart_remove(struct platform_device *pdev) clk_disable_unprepare(sport->clk); + if (sport->dma_tx_chan) + dma_release_channel(sport->dma_tx_chan); + + if (sport->dma_rx_chan) + dma_release_channel(sport->dma_rx_chan); + return 0; } -- cgit v1.2.3-70-g09d2 From 011f5bde62890c39440859c8077a0a5a6aca316d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 10 Jan 2015 09:33:46 +0100 Subject: serial: fsl_lpuart: update RX timer on successful DMA transfer To end a DMA transfer which did not consume a whole buffer (e.g. one character only), a RX timer is used. When lots of data are received the DMA transfer will complete and setup another DMA transfer, which in turn might complete again. In this cases, it is not necessary to abort the DMA transfers using the RX timer. This change pushes the RX timer timeout into the future each time a DMA transfer completed. Aborting the DMA was not very harmful, since the next received character lead to setup of another RX DMA. Signed-off-by: Stefan Agner Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 0ea0af76cb9..914f9a2a015 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -462,6 +462,7 @@ static void lpuart_dma_rx_complete(void *arg) unsigned long flags; async_tx_ack(sport->dma_rx_desc); + mod_timer(&sport->lpuart_timer, jiffies + sport->dma_rx_timeout); spin_lock_irqsave(&sport->port.lock, flags); -- cgit v1.2.3-70-g09d2 From 2fe605df325abfa2d36f63469fa960cd40d8cf50 Mon Sep 17 00:00:00 2001 From: Yuan Yao Date: Fri, 23 Jan 2015 17:48:54 +0800 Subject: serial: fsl-lpuart: disable interrupt when suspend For power management support, we should disable TX and TX interrupt so that kernel can prepare for deep sleep. Retain RX and RX interrupt for wakeup the kernel when receive the input character. Signed-off-by: Yuan Yao Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 914f9a2a015..f196a33e619 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -1846,6 +1846,19 @@ static int lpuart_remove(struct platform_device *pdev) static int lpuart_suspend(struct device *dev) { struct lpuart_port *sport = dev_get_drvdata(dev); + unsigned long temp; + + if (sport->lpuart32) { + /* disable Rx/Tx and interrupts */ + temp = lpuart32_read(sport->port.membase + UARTCTRL); + temp &= ~(UARTCTRL_TE | UARTCTRL_TIE | UARTCTRL_TCIE); + lpuart32_write(temp, sport->port.membase + UARTCTRL); + } else { + /* disable Rx/Tx and interrupts */ + temp = readb(sport->port.membase + UARTCR2); + temp &= ~(UARTCR2_TE | UARTCR2_TIE | UARTCR2_TCIE); + writeb(temp, sport->port.membase + UARTCR2); + } uart_suspend_port(&lpuart_reg, &sport->port); -- cgit v1.2.3-70-g09d2 From bfc2e07f910891516c8eeef61859fa17369fea9f Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 26 Jan 2015 01:10:16 +0100 Subject: tty: serial: fsl_lpuart: terminate DMA on buffer flush On uart buffer flush, serial core resets the circular buffer. If a DMA transfer is in progress at that time, the callback lpuart_dma_tx_complete will move buffer's tail unconditionally, hence tail moves beyond head. Use the flush_buffer hook to terminate the DMA imeaditely and avoid lpuart_dma_tx_complete being called in this situation. This bug often showed up while shutdown and lead to duplicate serial console output. Signed-off-by: Stefan Agner Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index f196a33e619..b1893f3f88f 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -455,6 +455,15 @@ static int lpuart_dma_rx(struct lpuart_port *sport) return 0; } +static void lpuart_flush_buffer(struct uart_port *port) +{ + struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + if (sport->lpuart_dma_tx_use) { + dmaengine_terminate_all(sport->dma_tx_chan); + sport->dma_tx_in_progress = 0; + } +} + static void lpuart_dma_rx_complete(void *arg) { struct lpuart_port *sport = arg; @@ -1496,6 +1505,7 @@ static struct uart_ops lpuart_pops = { .release_port = lpuart_release_port, .config_port = lpuart_config_port, .verify_port = lpuart_verify_port, + .flush_buffer = lpuart_flush_buffer, }; static struct uart_ops lpuart32_pops = { @@ -1514,6 +1524,7 @@ static struct uart_ops lpuart32_pops = { .release_port = lpuart_release_port, .config_port = lpuart_config_port, .verify_port = lpuart_verify_port, + .flush_buffer = lpuart_flush_buffer, }; static struct lpuart_port *lpuart_ports[UART_NR]; -- cgit v1.2.3-70-g09d2 From 934084a9d2d95d0ce98ae8d1ec3bfe81c95c678c Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 13 Jan 2015 10:00:26 -0200 Subject: serial: imx: Fix imx_flush_buffer() When running an userspace program that does a 'tcflush(fd, TCIOFLUSH)' call we still see the last received character in the URXD register afterwards. Clear UCR2_SRST bit so that the UART FIFO is flushed properly. Since UCR2_SRST also resets some UART registers, we need to save and restore some of them. Signed-off-by: Fabio Estevam Tested-by: Fugang Duan Acked-by: Jason Liu Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 59d9ef10b08..0eb29b1c47a 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -1320,6 +1320,7 @@ static void imx_flush_buffer(struct uart_port *port) struct imx_port *sport = (struct imx_port *)port; struct scatterlist *sgl = &sport->tx_sgl[0]; unsigned long temp; + int i = 100, ubir, ubmr, ubrc, uts; if (!sport->dma_chan_tx) return; @@ -1334,6 +1335,31 @@ static void imx_flush_buffer(struct uart_port *port) writel(temp, sport->port.membase + UCR1); sport->dma_is_txing = false; } + + /* + * According to the Reference Manual description of the UART SRST bit: + * "Reset the transmit and receive state machines, + * all FIFOs and register USR1, USR2, UBIR, UBMR, UBRC, URXD, UTXD + * and UTS[6-3]". As we don't need to restore the old values from + * USR1, USR2, URXD, UTXD, only save/restore the other four registers + */ + ubir = readl(sport->port.membase + UBIR); + ubmr = readl(sport->port.membase + UBMR); + ubrc = readl(sport->port.membase + UBRC); + uts = readl(sport->port.membase + IMX21_UTS); + + temp = readl(sport->port.membase + UCR2); + temp &= ~UCR2_SRST; + writel(temp, sport->port.membase + UCR2); + + while (!(readl(sport->port.membase + UCR2) & UCR2_SRST) && (--i > 0)) + udelay(1); + + /* Restore the registers */ + writel(ubir, sport->port.membase + UBIR); + writel(ubmr, sport->port.membase + UBMR); + writel(ubrc, sport->port.membase + UBRC); + writel(uts, sport->port.membase + IMX21_UTS); } static void -- cgit v1.2.3-70-g09d2 From 874e52086f9f1b9f9bdfbf98cca8506b110b63ba Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jan 2015 15:17:22 +0200 Subject: x86, mrst: remove Moorestown specific serial drivers Intel Moorestown platform support was removed few years ago. This is a follow up which removes Moorestown specific code for the serial devices. It includes mrst_max3110 and earlyprintk bits. This was used on SFI (Medfield, Clovertrail) based platforms as well, though new ones use normal serial interface for the console service. Signed-off-by: Andy Shevchenko Acked-by: David Cohen Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/intel-mid.h | 3 - arch/x86/kernel/early_printk.c | 5 - arch/x86/platform/intel-mid/device_libs/Makefile | 2 - .../intel-mid/device_libs/platform_max3111.c | 35 - .../platform/intel-mid/early_printk_intel_mid.c | 220 +---- drivers/tty/serial/Kconfig | 10 - drivers/tty/serial/Makefile | 1 - drivers/tty/serial/mrst_max3110.c | 909 --------------------- drivers/tty/serial/mrst_max3110.h | 61 -- 9 files changed, 4 insertions(+), 1242 deletions(-) delete mode 100644 arch/x86/platform/intel-mid/device_libs/platform_max3111.c delete mode 100644 drivers/tty/serial/mrst_max3110.c delete mode 100644 drivers/tty/serial/mrst_max3110.h (limited to 'drivers') diff --git a/arch/x86/include/asm/intel-mid.h b/arch/x86/include/asm/intel-mid.h index e34e097b6f9..705d35708a5 100644 --- a/arch/x86/include/asm/intel-mid.h +++ b/arch/x86/include/asm/intel-mid.h @@ -136,9 +136,6 @@ extern enum intel_mid_timer_options intel_mid_timer_options; #define SFI_MTMR_MAX_NUM 8 #define SFI_MRTC_MAX 8 -extern struct console early_mrst_console; -extern void mrst_early_console_init(void); - extern struct console early_hsu_console; extern void hsu_early_console_init(const char *); diff --git a/arch/x86/kernel/early_printk.c b/arch/x86/kernel/early_printk.c index 01d1c187c9f..de814dfb8ae 100644 --- a/arch/x86/kernel/early_printk.c +++ b/arch/x86/kernel/early_printk.c @@ -226,11 +226,6 @@ static int __init setup_early_printk(char *buf) early_console_register(&xenboot_console, keep); #endif #ifdef CONFIG_EARLY_PRINTK_INTEL_MID - if (!strncmp(buf, "mrst", 4)) { - mrst_early_console_init(); - early_console_register(&early_mrst_console, keep); - } - if (!strncmp(buf, "hsu", 3)) { hsu_early_console_init(buf + 3); early_console_register(&early_hsu_console, keep); diff --git a/arch/x86/platform/intel-mid/device_libs/Makefile b/arch/x86/platform/intel-mid/device_libs/Makefile index af9307f2cc2..91ec9f8704b 100644 --- a/arch/x86/platform/intel-mid/device_libs/Makefile +++ b/arch/x86/platform/intel-mid/device_libs/Makefile @@ -16,8 +16,6 @@ obj-$(subst m,y,$(CONFIG_INPUT_MPU3050)) += platform_mpu3050.o obj-$(subst m,y,$(CONFIG_INPUT_BMA150)) += platform_bma023.o obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_tca6416.o obj-$(subst m,y,$(CONFIG_DRM_MEDFIELD)) += platform_tc35876x.o -# SPI Devices -obj-$(subst m,y,$(CONFIG_SERIAL_MRST_MAX3110)) += platform_max3111.o # MISC Devices obj-$(subst m,y,$(CONFIG_KEYBOARD_GPIO)) += platform_gpio_keys.o obj-$(subst m,y,$(CONFIG_INTEL_MID_WATCHDOG)) += platform_wdt.o diff --git a/arch/x86/platform/intel-mid/device_libs/platform_max3111.c b/arch/x86/platform/intel-mid/device_libs/platform_max3111.c deleted file mode 100644 index afd1df94e0e..00000000000 --- a/arch/x86/platform/intel-mid/device_libs/platform_max3111.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * platform_max3111.c: max3111 platform data initilization file - * - * (C) Copyright 2013 Intel Corporation - * Author: Sathyanarayanan Kuppuswamy - * - * 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; version 2 - * of the License. - */ - -#include -#include -#include - -static void __init *max3111_platform_data(void *info) -{ - struct spi_board_info *spi_info = info; - int intr = get_gpio_by_name("max3111_int"); - - spi_info->mode = SPI_MODE_0; - if (intr == -1) - return NULL; - spi_info->irq = intr + INTEL_MID_IRQ_OFFSET; - return NULL; -} - -static const struct devs_id max3111_dev_id __initconst = { - .name = "spi_max3111", - .type = SFI_DEV_TYPE_SPI, - .get_platform_data = &max3111_platform_data, -}; - -sfi_device(max3111_dev_id); diff --git a/arch/x86/platform/intel-mid/early_printk_intel_mid.c b/arch/x86/platform/intel-mid/early_printk_intel_mid.c index e0bd082a80e..4e720829ab9 100644 --- a/arch/x86/platform/intel-mid/early_printk_intel_mid.c +++ b/arch/x86/platform/intel-mid/early_printk_intel_mid.c @@ -10,15 +10,13 @@ */ /* - * This file implements two early consoles named mrst and hsu. - * mrst is based on Maxim3110 spi-uart device, it exists in both - * Moorestown and Medfield platforms, while hsu is based on a High - * Speed UART device which only exists in the Medfield platform + * This file implements early console named hsu. + * hsu is based on a High Speed UART device which only exists in the Medfield + * platform */ #include #include -#include #include #include #include @@ -28,216 +26,6 @@ #include #include -#define MRST_SPI_TIMEOUT 0x200000 -#define MRST_REGBASE_SPI0 0xff128000 -#define MRST_REGBASE_SPI1 0xff128400 -#define MRST_CLK_SPI0_REG 0xff11d86c - -/* Bit fields in CTRLR0 */ -#define SPI_DFS_OFFSET 0 - -#define SPI_FRF_OFFSET 4 -#define SPI_FRF_SPI 0x0 -#define SPI_FRF_SSP 0x1 -#define SPI_FRF_MICROWIRE 0x2 -#define SPI_FRF_RESV 0x3 - -#define SPI_MODE_OFFSET 6 -#define SPI_SCPH_OFFSET 6 -#define SPI_SCOL_OFFSET 7 -#define SPI_TMOD_OFFSET 8 -#define SPI_TMOD_TR 0x0 /* xmit & recv */ -#define SPI_TMOD_TO 0x1 /* xmit only */ -#define SPI_TMOD_RO 0x2 /* recv only */ -#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */ - -#define SPI_SLVOE_OFFSET 10 -#define SPI_SRL_OFFSET 11 -#define SPI_CFS_OFFSET 12 - -/* Bit fields in SR, 7 bits */ -#define SR_MASK 0x7f /* cover 7 bits */ -#define SR_BUSY (1 << 0) -#define SR_TF_NOT_FULL (1 << 1) -#define SR_TF_EMPT (1 << 2) -#define SR_RF_NOT_EMPT (1 << 3) -#define SR_RF_FULL (1 << 4) -#define SR_TX_ERR (1 << 5) -#define SR_DCOL (1 << 6) - -struct dw_spi_reg { - u32 ctrl0; - u32 ctrl1; - u32 ssienr; - u32 mwcr; - u32 ser; - u32 baudr; - u32 txfltr; - u32 rxfltr; - u32 txflr; - u32 rxflr; - u32 sr; - u32 imr; - u32 isr; - u32 risr; - u32 txoicr; - u32 rxoicr; - u32 rxuicr; - u32 msticr; - u32 icr; - u32 dmacr; - u32 dmatdlr; - u32 dmardlr; - u32 idr; - u32 version; - - /* Currently operates as 32 bits, though only the low 16 bits matter */ - u32 dr; -} __packed; - -#define dw_readl(dw, name) __raw_readl(&(dw)->name) -#define dw_writel(dw, name, val) __raw_writel((val), &(dw)->name) - -/* Default use SPI0 register for mrst, we will detect Penwell and use SPI1 */ -static unsigned long mrst_spi_paddr = MRST_REGBASE_SPI0; - -static u32 *pclk_spi0; -/* Always contains an accessible address, start with 0 */ -static struct dw_spi_reg *pspi; - -static struct kmsg_dumper dw_dumper; -static int dumper_registered; - -static void dw_kmsg_dump(struct kmsg_dumper *dumper, - enum kmsg_dump_reason reason) -{ - static char line[1024]; - size_t len; - - /* When run to this, we'd better re-init the HW */ - mrst_early_console_init(); - - while (kmsg_dump_get_line(dumper, true, line, sizeof(line), &len)) - early_mrst_console.write(&early_mrst_console, line, len); -} - -/* Set the ratio rate to 115200, 8n1, IRQ disabled */ -static void max3110_write_config(void) -{ - u16 config; - - config = 0xc001; - dw_writel(pspi, dr, config); -} - -/* Translate char to a eligible word and send to max3110 */ -static void max3110_write_data(char c) -{ - u16 data; - - data = 0x8000 | c; - dw_writel(pspi, dr, data); -} - -void mrst_early_console_init(void) -{ - u32 ctrlr0 = 0; - u32 spi0_cdiv; - u32 freq; /* Freqency info only need be searched once */ - - /* Base clk is 100 MHz, the actual clk = 100M / (clk_divider + 1) */ - pclk_spi0 = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, - MRST_CLK_SPI0_REG); - spi0_cdiv = ((*pclk_spi0) & 0xe00) >> 9; - freq = 100000000 / (spi0_cdiv + 1); - - if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL) - mrst_spi_paddr = MRST_REGBASE_SPI1; - - pspi = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, - mrst_spi_paddr); - - /* Disable SPI controller */ - dw_writel(pspi, ssienr, 0); - - /* Set control param, 8 bits, transmit only mode */ - ctrlr0 = dw_readl(pspi, ctrl0); - - ctrlr0 &= 0xfcc0; - ctrlr0 |= 0xf | (SPI_FRF_SPI << SPI_FRF_OFFSET) - | (SPI_TMOD_TO << SPI_TMOD_OFFSET); - dw_writel(pspi, ctrl0, ctrlr0); - - /* - * Change the spi0 clk to comply with 115200 bps, use 100000 to - * calculate the clk dividor to make the clock a little slower - * than real baud rate. - */ - dw_writel(pspi, baudr, freq/100000); - - /* Disable all INT for early phase */ - dw_writel(pspi, imr, 0x0); - - /* Set the cs to spi-uart */ - dw_writel(pspi, ser, 0x2); - - /* Enable the HW, the last step for HW init */ - dw_writel(pspi, ssienr, 0x1); - - /* Set the default configuration */ - max3110_write_config(); - - /* Register the kmsg dumper */ - if (!dumper_registered) { - dw_dumper.dump = dw_kmsg_dump; - kmsg_dump_register(&dw_dumper); - dumper_registered = 1; - } -} - -/* Slave select should be called in the read/write function */ -static void early_mrst_spi_putc(char c) -{ - unsigned int timeout; - u32 sr; - - timeout = MRST_SPI_TIMEOUT; - /* Early putc needs to make sure the TX FIFO is not full */ - while (--timeout) { - sr = dw_readl(pspi, sr); - if (!(sr & SR_TF_NOT_FULL)) - cpu_relax(); - else - break; - } - - if (!timeout) - pr_warn("MRST earlycon: timed out\n"); - else - max3110_write_data(c); -} - -/* Early SPI only uses polling mode */ -static void early_mrst_spi_write(struct console *con, const char *str, - unsigned n) -{ - int i; - - for (i = 0; i < n && *str; i++) { - if (*str == '\n') - early_mrst_spi_putc('\r'); - early_mrst_spi_putc(*str); - str++; - } -} - -struct console early_mrst_console = { - .name = "earlymrst", - .write = early_mrst_spi_write, - .flags = CON_PRINTBUFFER, - .index = -1, -}; - /* * Following is the early console based on Medfield HSU (High * Speed UART) device. @@ -259,7 +47,7 @@ void hsu_early_console_init(const char *s) port = clamp_val(port, 0, 2); paddr = HSU_PORT_BASE + port * 0x80; - phsu = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, paddr); + phsu = (void __iomem *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, paddr); /* Disable FIFO */ writeb(0x0, phsu + UART_FCR); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 96ec6cfd74e..35677f75a1d 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -482,16 +482,6 @@ config SERIAL_SA1100_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) -config SERIAL_MRST_MAX3110 - tristate "SPI UART driver for Max3110" - depends on SPI_DW_PCI - select SERIAL_CORE - select SERIAL_CORE_CONSOLE - help - This is the UART protocol driver for the MAX3110 device on - the Intel Moorestown platform. On other systems use the max3100 - driver. - config SERIAL_MFD_HSU tristate "Medfield High Speed UART support" depends on PCI diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 770a19bb7fc..a1c1085ef75 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -77,7 +77,6 @@ obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o obj-$(CONFIG_SERIAL_VT8500) += vt8500_serial.o -obj-$(CONFIG_SERIAL_MRST_MAX3110) += mrst_max3110.o obj-$(CONFIG_SERIAL_MFD_HSU) += mfd.o obj-$(CONFIG_SERIAL_IFX6X60) += ifx6x60.o obj-$(CONFIG_SERIAL_PCH_UART) += pch_uart.o diff --git a/drivers/tty/serial/mrst_max3110.c b/drivers/tty/serial/mrst_max3110.c deleted file mode 100644 index 77239d5e620..00000000000 --- a/drivers/tty/serial/mrst_max3110.c +++ /dev/null @@ -1,909 +0,0 @@ -/* - * mrst_max3110.c - spi uart protocol driver for Maxim 3110 - * - * Copyright (c) 2008-2010, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Note: - * 1. From Max3110 spec, the Rx FIFO has 8 words, while the Tx FIFO only has - * 1 word. If SPI master controller doesn't support sclk frequency change, - * then the char need be sent out one by one with some delay - * - * 2. Currently only RX available interrupt is used, no need for waiting TXE - * interrupt for a low speed UART device - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#ifdef CONFIG_MAGIC_SYSRQ -#define SUPPORT_SYSRQ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "mrst_max3110.h" - -#define UART_TX_NEEDED 1 -#define CON_TX_NEEDED 2 -#define BIT_IRQ_PENDING 3 - -struct uart_max3110 { - struct uart_port port; - struct spi_device *spi; - char name[SPI_NAME_SIZE]; - - wait_queue_head_t wq; - struct task_struct *main_thread; - struct task_struct *read_thread; - struct mutex thread_mutex; - struct mutex io_mutex; - - u32 baud; - u16 cur_conf; - u8 clock; - u8 parity, word_7bits; - u16 irq; - - unsigned long uart_flags; - - /* console related */ - struct circ_buf con_xmit; -}; - -/* global data structure, may need be removed */ -static struct uart_max3110 *pmax; - -static int receive_chars(struct uart_max3110 *max, - unsigned short *str, int len); -static int max3110_read_multi(struct uart_max3110 *max); -static void max3110_con_receive(struct uart_max3110 *max); - -static int max3110_write_then_read(struct uart_max3110 *max, - const void *txbuf, void *rxbuf, unsigned len, int always_fast) -{ - struct spi_device *spi = max->spi; - struct spi_message message; - struct spi_transfer x; - int ret; - - mutex_lock(&max->io_mutex); - spi_message_init(&message); - memset(&x, 0, sizeof x); - x.len = len; - x.tx_buf = txbuf; - x.rx_buf = rxbuf; - spi_message_add_tail(&x, &message); - - if (always_fast) - x.speed_hz = spi->max_speed_hz; - else if (max->baud) - x.speed_hz = max->baud; - - /* Do the i/o */ - ret = spi_sync(spi, &message); - mutex_unlock(&max->io_mutex); - return ret; -} - -/* Write a 16b word to the device */ -static int max3110_out(struct uart_max3110 *max, const u16 out) -{ - void *buf; - u16 *obuf, *ibuf; - int ret; - - buf = kzalloc(8, GFP_KERNEL | GFP_DMA); - if (!buf) - return -ENOMEM; - - obuf = buf; - ibuf = buf + 4; - *obuf = out; - ret = max3110_write_then_read(max, obuf, ibuf, 2, 1); - if (ret) { - pr_warn("%s: get err msg %d when sending 0x%x\n", - __func__, ret, out); - goto exit; - } - - receive_chars(max, ibuf, 1); - -exit: - kfree(buf); - return ret; -} - -/* - * This is usually used to read data from SPIC RX FIFO, which doesn't - * need any delay like flushing character out. - * - * Return how many valide bytes are read back - */ -static int max3110_read_multi(struct uart_max3110 *max) -{ - void *buf; - u16 *obuf, *ibuf; - int ret, blen; - - blen = M3110_RX_FIFO_DEPTH * sizeof(u16); - buf = kzalloc(blen * 2, GFP_KERNEL | GFP_DMA); - if (!buf) - return 0; - - /* tx/rx always have the same length */ - obuf = buf; - ibuf = buf + blen; - - if (max3110_write_then_read(max, obuf, ibuf, blen, 1)) { - kfree(buf); - return 0; - } - - ret = receive_chars(max, ibuf, M3110_RX_FIFO_DEPTH); - - kfree(buf); - return ret; -} - -static void serial_m3110_con_putchar(struct uart_port *port, int ch) -{ - struct uart_max3110 *max = - container_of(port, struct uart_max3110, port); - struct circ_buf *xmit = &max->con_xmit; - - if (uart_circ_chars_free(xmit)) { - xmit->buf[xmit->head] = (char)ch; - xmit->head = (xmit->head + 1) & (PAGE_SIZE - 1); - } -} - -/* - * Print a string to the serial port trying not to disturb - * any possible real use of the port... - * - * The console_lock must be held when we get here. - */ -static void serial_m3110_con_write(struct console *co, - const char *s, unsigned int count) -{ - if (!pmax) - return; - - uart_console_write(&pmax->port, s, count, serial_m3110_con_putchar); - - if (!test_and_set_bit(CON_TX_NEEDED, &pmax->uart_flags)) - wake_up(&pmax->wq); -} - -static int __init -serial_m3110_con_setup(struct console *co, char *options) -{ - struct uart_max3110 *max = pmax; - int baud = 115200; - int bits = 8; - int parity = 'n'; - int flow = 'n'; - - pr_info("setting up console\n"); - - if (co->index == -1) - co->index = 0; - - if (!max) { - pr_err("pmax is NULL, return\n"); - return -ENODEV; - } - - if (options) - uart_parse_options(options, &baud, &parity, &bits, &flow); - - return uart_set_options(&max->port, co, baud, parity, bits, flow); -} - -static struct tty_driver *serial_m3110_con_device(struct console *co, - int *index) -{ - struct uart_driver *p = co->data; - *index = co->index; - return p->tty_driver; -} - -static struct uart_driver serial_m3110_reg; -static struct console serial_m3110_console = { - .name = "ttyS", - .write = serial_m3110_con_write, - .device = serial_m3110_con_device, - .setup = serial_m3110_con_setup, - .flags = CON_PRINTBUFFER, - .index = -1, - .data = &serial_m3110_reg, -}; - -static unsigned int serial_m3110_tx_empty(struct uart_port *port) -{ - return 1; -} - -static void serial_m3110_stop_tx(struct uart_port *port) -{ - return; -} - -/* stop_rx will be called in spin_lock env */ -static void serial_m3110_stop_rx(struct uart_port *port) -{ - return; -} - -#define WORDS_PER_XFER 128 -static void send_circ_buf(struct uart_max3110 *max, - struct circ_buf *xmit) -{ - void *buf; - u16 *obuf, *ibuf; - int i, len, blen, dma_size, left, ret = 0; - - - dma_size = WORDS_PER_XFER * sizeof(u16) * 2; - buf = kzalloc(dma_size, GFP_KERNEL | GFP_DMA); - if (!buf) - return; - obuf = buf; - ibuf = buf + dma_size/2; - - while (!uart_circ_empty(xmit)) { - left = uart_circ_chars_pending(xmit); - while (left) { - len = min(left, WORDS_PER_XFER); - blen = len * sizeof(u16); - memset(ibuf, 0, blen); - - for (i = 0; i < len; i++) { - obuf[i] = (u8)xmit->buf[xmit->tail] | WD_TAG; - xmit->tail = (xmit->tail + 1) & - (UART_XMIT_SIZE - 1); - } - - /* Fail to send msg to console is not very critical */ - - ret = max3110_write_then_read(max, obuf, ibuf, blen, 0); - if (ret) - pr_warn("%s: get err msg %d\n", __func__, ret); - - receive_chars(max, ibuf, len); - - max->port.icount.tx += len; - left -= len; - } - } - - kfree(buf); -} - -static void transmit_char(struct uart_max3110 *max) -{ - struct uart_port *port = &max->port; - struct circ_buf *xmit = &port->state->xmit; - - if (uart_circ_empty(xmit) || uart_tx_stopped(port)) - return; - - send_circ_buf(max, xmit); - - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(port); - - if (uart_circ_empty(xmit)) - serial_m3110_stop_tx(port); -} - -/* - * This will be called by uart_write() and tty_write, can't - * go to sleep - */ -static void serial_m3110_start_tx(struct uart_port *port) -{ - struct uart_max3110 *max = - container_of(port, struct uart_max3110, port); - - if (!test_and_set_bit(UART_TX_NEEDED, &max->uart_flags)) - wake_up(&max->wq); -} - -static int -receive_chars(struct uart_max3110 *max, unsigned short *str, int len) -{ - struct uart_port *port = &max->port; - struct tty_port *tport; - char buf[M3110_RX_FIFO_DEPTH]; - int r, w, usable; - - /* If uart is not opened, just return */ - if (!port->state) - return 0; - - tport = &port->state->port; - - for (r = 0, w = 0; r < len; r++) { - if (str[r] & MAX3110_BREAK && - uart_handle_break(port)) - continue; - - if (str[r] & MAX3110_READ_DATA_AVAILABLE) { - if (uart_handle_sysrq_char(port, str[r] & 0xff)) - continue; - - buf[w++] = str[r] & 0xff; - } - } - - if (!w) - return 0; - - for (r = 0; w; r += usable, w -= usable) { - usable = tty_buffer_request_room(tport, w); - if (usable) { - tty_insert_flip_string(tport, buf + r, usable); - port->icount.rx += usable; - } - } - tty_flip_buffer_push(tport); - - return r; -} - -/* - * This routine will be used in read_thread or RX IRQ handling, - * it will first do one round buffer read(8 words), if there is some - * valid RX data, will try to read 5 more rounds till all data - * is read out. - * - * Use stack space as data buffer to save some system load, and chose - * 504 Btyes as a threadhold to do a bulk push to upper tty layer when - * receiving bulk data, a much bigger buffer may cause stack overflow - */ -static void max3110_con_receive(struct uart_max3110 *max) -{ - int loop = 1, num; - - do { - num = max3110_read_multi(max); - - if (num) { - loop = 5; - } - } while (--loop); -} - -static int max3110_main_thread(void *_max) -{ - struct uart_max3110 *max = _max; - wait_queue_head_t *wq = &max->wq; - int ret = 0; - struct circ_buf *xmit = &max->con_xmit; - - pr_info("start main thread\n"); - - do { - wait_event_interruptible(*wq, - max->uart_flags || kthread_should_stop()); - - mutex_lock(&max->thread_mutex); - - if (test_and_clear_bit(BIT_IRQ_PENDING, &max->uart_flags)) - max3110_con_receive(max); - - /* first handle console output */ - if (test_and_clear_bit(CON_TX_NEEDED, &max->uart_flags)) - send_circ_buf(max, xmit); - - /* handle uart output */ - if (test_and_clear_bit(UART_TX_NEEDED, &max->uart_flags)) - transmit_char(max); - - mutex_unlock(&max->thread_mutex); - - } while (!kthread_should_stop()); - - return ret; -} - -static irqreturn_t serial_m3110_irq(int irq, void *dev_id) -{ - struct uart_max3110 *max = dev_id; - - /* max3110's irq is a falling edge, not level triggered, - * so no need to disable the irq */ - - if (!test_and_set_bit(BIT_IRQ_PENDING, &max->uart_flags)) - wake_up(&max->wq); - - return IRQ_HANDLED; -} - -/* if don't use RX IRQ, then need a thread to polling read */ -static int max3110_read_thread(void *_max) -{ - struct uart_max3110 *max = _max; - - pr_info("start read thread\n"); - do { - /* - * If can't acquire the mutex, it means the main thread - * is running which will also perform the rx job - */ - if (mutex_trylock(&max->thread_mutex)) { - max3110_con_receive(max); - mutex_unlock(&max->thread_mutex); - } - - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(HZ / 20); - } while (!kthread_should_stop()); - - return 0; -} - -static int serial_m3110_startup(struct uart_port *port) -{ - struct uart_max3110 *max = - container_of(port, struct uart_max3110, port); - u16 config = 0; - int ret = 0; - - if (port->line != 0) { - pr_err("uart port startup failed\n"); - return -1; - } - - /* Disable all IRQ and config it to 115200, 8n1 */ - config = WC_TAG | WC_FIFO_ENABLE - | WC_1_STOPBITS - | WC_8BIT_WORD - | WC_BAUD_DR2; - - /* as we use thread to handle tx/rx, need set low latency */ - port->state->port.low_latency = 1; - - if (max->irq) { - /* Enable RX IRQ only */ - config |= WC_RXA_IRQ_ENABLE; - } else { - /* If IRQ is disabled, start a read thread for input data */ - max->read_thread = - kthread_run(max3110_read_thread, max, "max3110_read"); - if (IS_ERR(max->read_thread)) { - ret = PTR_ERR(max->read_thread); - max->read_thread = NULL; - pr_err("Can't create read thread!\n"); - return ret; - } - } - - ret = max3110_out(max, config); - if (ret) { - if (max->read_thread) - kthread_stop(max->read_thread); - max->read_thread = NULL; - return ret; - } - - max->cur_conf = config; - return 0; -} - -static void serial_m3110_shutdown(struct uart_port *port) -{ - struct uart_max3110 *max = - container_of(port, struct uart_max3110, port); - u16 config; - - if (max->read_thread) { - kthread_stop(max->read_thread); - max->read_thread = NULL; - } - - /* Disable interrupts from this port */ - config = WC_TAG | WC_SW_SHDI; - max3110_out(max, config); -} - -static void serial_m3110_release_port(struct uart_port *port) -{ -} - -static int serial_m3110_request_port(struct uart_port *port) -{ - return 0; -} - -static void serial_m3110_config_port(struct uart_port *port, int flags) -{ - port->type = PORT_MAX3100; -} - -static int -serial_m3110_verify_port(struct uart_port *port, struct serial_struct *ser) -{ - /* we don't want the core code to modify any port params */ - return -EINVAL; -} - - -static const char *serial_m3110_type(struct uart_port *port) -{ - struct uart_max3110 *max = - container_of(port, struct uart_max3110, port); - return max->name; -} - -static void -serial_m3110_set_termios(struct uart_port *port, struct ktermios *termios, - struct ktermios *old) -{ - struct uart_max3110 *max = - container_of(port, struct uart_max3110, port); - unsigned char cval; - unsigned int baud, parity = 0; - int clk_div = -1; - u16 new_conf = max->cur_conf; - - switch (termios->c_cflag & CSIZE) { - case CS7: - cval = UART_LCR_WLEN7; - new_conf |= WC_7BIT_WORD; - break; - default: - /* We only support CS7 & CS8 */ - termios->c_cflag &= ~CSIZE; - termios->c_cflag |= CS8; - case CS8: - cval = UART_LCR_WLEN8; - new_conf |= WC_8BIT_WORD; - break; - } - - baud = uart_get_baud_rate(port, termios, old, 0, 230400); - - /* First calc the div for 1.8MHZ clock case */ - switch (baud) { - case 300: - clk_div = WC_BAUD_DR384; - break; - case 600: - clk_div = WC_BAUD_DR192; - break; - case 1200: - clk_div = WC_BAUD_DR96; - break; - case 2400: - clk_div = WC_BAUD_DR48; - break; - case 4800: - clk_div = WC_BAUD_DR24; - break; - case 9600: - clk_div = WC_BAUD_DR12; - break; - case 19200: - clk_div = WC_BAUD_DR6; - break; - case 38400: - clk_div = WC_BAUD_DR3; - break; - case 57600: - clk_div = WC_BAUD_DR2; - break; - case 115200: - clk_div = WC_BAUD_DR1; - break; - case 230400: - if (max->clock & MAX3110_HIGH_CLK) - break; - default: - /* Pick the previous baud rate */ - baud = max->baud; - clk_div = max->cur_conf & WC_BAUD_DIV_MASK; - tty_termios_encode_baud_rate(termios, baud, baud); - } - - if (max->clock & MAX3110_HIGH_CLK) { - clk_div += 1; - /* High clk version max3110 doesn't support B300 */ - if (baud == 300) { - baud = 600; - clk_div = WC_BAUD_DR384; - } - if (baud == 230400) - clk_div = WC_BAUD_DR1; - tty_termios_encode_baud_rate(termios, baud, baud); - } - - new_conf = (new_conf & ~WC_BAUD_DIV_MASK) | clk_div; - - if (unlikely(termios->c_cflag & CMSPAR)) - termios->c_cflag &= ~CMSPAR; - - if (termios->c_cflag & CSTOPB) - new_conf |= WC_2_STOPBITS; - else - new_conf &= ~WC_2_STOPBITS; - - if (termios->c_cflag & PARENB) { - new_conf |= WC_PARITY_ENABLE; - parity |= UART_LCR_PARITY; - } else - new_conf &= ~WC_PARITY_ENABLE; - - if (!(termios->c_cflag & PARODD)) - parity |= UART_LCR_EPAR; - max->parity = parity; - - uart_update_timeout(port, termios->c_cflag, baud); - - new_conf |= WC_TAG; - if (new_conf != max->cur_conf) { - if (!max3110_out(max, new_conf)) { - max->cur_conf = new_conf; - max->baud = baud; - } - } -} - -/* Don't handle hw handshaking */ -static unsigned int serial_m3110_get_mctrl(struct uart_port *port) -{ - return TIOCM_DSR | TIOCM_CAR | TIOCM_DSR; -} - -static void serial_m3110_set_mctrl(struct uart_port *port, unsigned int mctrl) -{ -} - -static void serial_m3110_break_ctl(struct uart_port *port, int break_state) -{ -} - -static void serial_m3110_pm(struct uart_port *port, unsigned int state, - unsigned int oldstate) -{ -} - -static struct uart_ops serial_m3110_ops = { - .tx_empty = serial_m3110_tx_empty, - .set_mctrl = serial_m3110_set_mctrl, - .get_mctrl = serial_m3110_get_mctrl, - .stop_tx = serial_m3110_stop_tx, - .start_tx = serial_m3110_start_tx, - .stop_rx = serial_m3110_stop_rx, - .break_ctl = serial_m3110_break_ctl, - .startup = serial_m3110_startup, - .shutdown = serial_m3110_shutdown, - .set_termios = serial_m3110_set_termios, - .pm = serial_m3110_pm, - .type = serial_m3110_type, - .release_port = serial_m3110_release_port, - .request_port = serial_m3110_request_port, - .config_port = serial_m3110_config_port, - .verify_port = serial_m3110_verify_port, -}; - -static struct uart_driver serial_m3110_reg = { - .owner = THIS_MODULE, - .driver_name = "MRST serial", - .dev_name = "ttyS", - .major = TTY_MAJOR, - .minor = 64, - .nr = 1, - .cons = &serial_m3110_console, -}; - -#ifdef CONFIG_PM_SLEEP -static int serial_m3110_suspend(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct uart_max3110 *max = spi_get_drvdata(spi); - - if (max->irq > 0) - disable_irq(max->irq); - uart_suspend_port(&serial_m3110_reg, &max->port); - max3110_out(max, max->cur_conf | WC_SW_SHDI); - return 0; -} - -static int serial_m3110_resume(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct uart_max3110 *max = spi_get_drvdata(spi); - - max3110_out(max, max->cur_conf); - uart_resume_port(&serial_m3110_reg, &max->port); - if (max->irq > 0) - enable_irq(max->irq); - return 0; -} - -static SIMPLE_DEV_PM_OPS(serial_m3110_pm_ops, serial_m3110_suspend, - serial_m3110_resume); -#define SERIAL_M3110_PM_OPS (&serial_m3110_pm_ops) - -#else -#define SERIAL_M3110_PM_OPS NULL -#endif - -static int serial_m3110_probe(struct spi_device *spi) -{ - struct uart_max3110 *max; - void *buffer; - u16 res; - int ret = 0; - - max = kzalloc(sizeof(*max), GFP_KERNEL); - if (!max) - return -ENOMEM; - - /* Set spi info */ - spi->bits_per_word = 16; - max->clock = MAX3110_HIGH_CLK; - - spi_setup(spi); - - max->port.type = PORT_MAX3100; - max->port.fifosize = 2; /* Only have 16b buffer */ - max->port.ops = &serial_m3110_ops; - max->port.line = 0; - max->port.dev = &spi->dev; - max->port.uartclk = 115200; - - max->spi = spi; - strcpy(max->name, spi->modalias); - max->irq = (u16)spi->irq; - - mutex_init(&max->thread_mutex); - mutex_init(&max->io_mutex); - - max->word_7bits = 0; - max->parity = 0; - max->baud = 0; - - max->cur_conf = 0; - max->uart_flags = 0; - - /* Check if reading configuration register returns something sane */ - - res = RC_TAG; - ret = max3110_write_then_read(max, (u8 *)&res, (u8 *)&res, 2, 0); - if (ret < 0 || res == 0 || res == 0xffff) { - dev_dbg(&spi->dev, "MAX3111 deemed not present (conf reg %04x)", - res); - ret = -ENODEV; - goto err_get_page; - } - - buffer = (void *)__get_free_page(GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto err_get_page; - } - max->con_xmit.buf = buffer; - max->con_xmit.head = 0; - max->con_xmit.tail = 0; - - init_waitqueue_head(&max->wq); - - max->main_thread = kthread_run(max3110_main_thread, - max, "max3110_main"); - if (IS_ERR(max->main_thread)) { - ret = PTR_ERR(max->main_thread); - goto err_kthread; - } - - if (max->irq) { - ret = request_irq(max->irq, serial_m3110_irq, - IRQ_TYPE_EDGE_FALLING, "max3110", max); - if (ret) { - max->irq = 0; - dev_warn(&spi->dev, - "unable to allocate IRQ, will use polling method\n"); - } - } - - spi_set_drvdata(spi, max); - pmax = max; - - /* Give membase a psudo value to pass serial_core's check */ - max->port.membase = (unsigned char __iomem *)0xff110000; - uart_add_one_port(&serial_m3110_reg, &max->port); - - return 0; - -err_kthread: - free_page((unsigned long)buffer); -err_get_page: - kfree(max); - return ret; -} - -static int serial_m3110_remove(struct spi_device *dev) -{ - struct uart_max3110 *max = spi_get_drvdata(dev); - - if (!max) - return 0; - - uart_remove_one_port(&serial_m3110_reg, &max->port); - - free_page((unsigned long)max->con_xmit.buf); - - if (max->irq) - free_irq(max->irq, max); - - if (max->main_thread) - kthread_stop(max->main_thread); - - kfree(max); - return 0; -} - -static struct spi_driver uart_max3110_driver = { - .driver = { - .name = "spi_max3111", - .owner = THIS_MODULE, - .pm = SERIAL_M3110_PM_OPS, - }, - .probe = serial_m3110_probe, - .remove = serial_m3110_remove, -}; - -static int __init serial_m3110_init(void) -{ - int ret = 0; - - ret = uart_register_driver(&serial_m3110_reg); - if (ret) - return ret; - - ret = spi_register_driver(&uart_max3110_driver); - if (ret) - uart_unregister_driver(&serial_m3110_reg); - - return ret; -} - -static void __exit serial_m3110_exit(void) -{ - spi_unregister_driver(&uart_max3110_driver); - uart_unregister_driver(&serial_m3110_reg); -} - -module_init(serial_m3110_init); -module_exit(serial_m3110_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("spi:max3110-uart"); diff --git a/drivers/tty/serial/mrst_max3110.h b/drivers/tty/serial/mrst_max3110.h deleted file mode 100644 index 35af0739513..00000000000 --- a/drivers/tty/serial/mrst_max3110.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef _MRST_MAX3110_H -#define _MRST_MAX3110_H - -#define MAX3110_HIGH_CLK 0x1 /* 3.6864 MHZ */ -#define MAX3110_LOW_CLK 0x0 /* 1.8432 MHZ */ - -/* status bits for all 4 MAX3110 operate modes */ -#define MAX3110_READ_DATA_AVAILABLE (1 << 15) -#define MAX3110_WRITE_BUF_EMPTY (1 << 14) -#define MAX3110_BREAK (1 << 10) - -#define WC_TAG (3 << 14) -#define RC_TAG (1 << 14) -#define WD_TAG (2 << 14) -#define RD_TAG (0 << 14) - -/* bits def for write configuration */ -#define WC_FIFO_ENABLE_MASK (1 << 13) -#define WC_FIFO_ENABLE (0 << 13) - -#define WC_SW_SHDI (1 << 12) - -#define WC_IRQ_MASK (0xF << 8) -#define WC_TXE_IRQ_ENABLE (1 << 11) /* TX empty irq */ -#define WC_RXA_IRQ_ENABLE (1 << 10) /* RX available irq */ -#define WC_PAR_HIGH_IRQ_ENABLE (1 << 9) -#define WC_REC_ACT_IRQ_ENABLE (1 << 8) - -#define WC_IRDA_ENABLE (1 << 7) - -#define WC_STOPBITS_MASK (1 << 6) -#define WC_2_STOPBITS (1 << 6) -#define WC_1_STOPBITS (0 << 6) - -#define WC_PARITY_ENABLE_MASK (1 << 5) -#define WC_PARITY_ENABLE (1 << 5) - -#define WC_WORDLEN_MASK (1 << 4) -#define WC_7BIT_WORD (1 << 4) -#define WC_8BIT_WORD (0 << 4) - -#define WC_BAUD_DIV_MASK (0xF) -#define WC_BAUD_DR1 (0x0) -#define WC_BAUD_DR2 (0x1) -#define WC_BAUD_DR4 (0x2) -#define WC_BAUD_DR8 (0x3) -#define WC_BAUD_DR16 (0x4) -#define WC_BAUD_DR32 (0x5) -#define WC_BAUD_DR64 (0x6) -#define WC_BAUD_DR128 (0x7) -#define WC_BAUD_DR3 (0x8) -#define WC_BAUD_DR6 (0x9) -#define WC_BAUD_DR12 (0xA) -#define WC_BAUD_DR24 (0xB) -#define WC_BAUD_DR48 (0xC) -#define WC_BAUD_DR96 (0xD) -#define WC_BAUD_DR192 (0xE) -#define WC_BAUD_DR384 (0xF) - -#define M3110_RX_FIFO_DEPTH 8 -#endif -- cgit v1.2.3-70-g09d2 From 0a0d412abe473c37ddec8d3f6c0b84c58f1bb061 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 13:47:46 +0200 Subject: serial: 8250_pci: remove one useless explicit type conversion Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index d1f8dc6aabc..daf2c82984e 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -221,13 +221,13 @@ pci_hp_diva_setup(struct serial_private *priv, */ static int pci_inteli960ni_init(struct pci_dev *dev) { - unsigned long oldval; + u32 oldval; if (!(dev->subsystem_device & 0x1000)) return -ENODEV; /* is firmware started? */ - pci_read_config_dword(dev, 0x44, (void *)&oldval); + pci_read_config_dword(dev, 0x44, &oldval); if (oldval == 0x00001000L) { /* RESET value */ dev_dbg(&dev->dev, "Local i960 firmware missing\n"); return -ENODEV; -- cgit v1.2.3-70-g09d2 From 4daba33466a81197e65c1a86cc1e1d1ca4e70939 Mon Sep 17 00:00:00 2001 From: Pramod Gurav Date: Mon, 12 Jan 2015 19:15:32 +0530 Subject: tty: serial: msm_serial: code cleanup in msm_console_setup The change does following: - baud, flow, bits, parity were being overwritten as they were being reinitialized after parsing. Initialize them when they are declared so that user provided setting are not overwritten. - msm_set_baud_rate() is anyway called in uart_set_options when it calls msm_set_termios(). msm_reset() is called when we change the baud rate. Hence doing away with both of these calls. - CR_CMD_PROTECTION_EN and CR_TX_ENABLE settings are done in msm_set_baud_rate. So do away with this here. Signed-off-by: Pramod Gurav Reviewed-by: Stephen Boyd Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/msm_serial.c | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c index c88b522ccd7..a0cc6e08552 100644 --- a/drivers/tty/serial/msm_serial.c +++ b/drivers/tty/serial/msm_serial.c @@ -920,14 +920,15 @@ static void msm_console_write(struct console *co, const char *s, static int __init msm_console_setup(struct console *co, char *options) { struct uart_port *port; - struct msm_port *msm_port; - int baud = 0, flow, bits, parity; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; if (unlikely(co->index >= UART_NR || co->index < 0)) return -ENXIO; port = get_port_from_line(co->index); - msm_port = UART_TO_MSM(port); if (unlikely(!port->membase)) return -ENXIO; @@ -937,23 +938,6 @@ static int __init msm_console_setup(struct console *co, char *options) if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); - bits = 8; - parity = 'n'; - flow = 'n'; - msm_write(port, UART_MR2_BITS_PER_CHAR_8 | UART_MR2_STOP_BIT_LEN_ONE, - UART_MR2); /* 8N1 */ - - if (baud < 300 || baud > 115200) - baud = 115200; - msm_set_baud_rate(port, baud); - - msm_reset(port); - - if (msm_port->is_uartdm) { - msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR); - msm_write(port, UART_CR_TX_ENABLE, UART_CR); - } - pr_info("msm_serial: console setup on port #%d\n", port->line); return uart_set_options(port, co, baud, parity, bits, flow); -- cgit v1.2.3-70-g09d2 From 2f7f558c222bc0ed617aa9888d0043a9aeeba253 Mon Sep 17 00:00:00 2001 From: Pramod Gurav Date: Mon, 12 Jan 2015 19:22:12 +0530 Subject: tty: serial: msm_serial: Remove console unregistration from driver exit. unregister_console() will be called from uart_remove_one_port() while removing the platform driver. So not necessary to call it in driver exit path. Signed-off-by: Pramod Gurav Reviewed-by: Stephen Boyd Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/msm_serial.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c index a0cc6e08552..b73889c8ed4 100644 --- a/drivers/tty/serial/msm_serial.c +++ b/drivers/tty/serial/msm_serial.c @@ -1126,9 +1126,6 @@ static int __init msm_serial_init(void) static void __exit msm_serial_exit(void) { -#ifdef CONFIG_SERIAL_MSM_CONSOLE - unregister_console(&msm_console); -#endif platform_driver_unregister(&msm_platform_driver); uart_unregister_driver(&msm_uart_driver); } -- cgit v1.2.3-70-g09d2 From 01395d798452435f19de3bfe5d04325db4e49677 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 11:50:24 -0500 Subject: PNP: Allow console to override ACPI device sleep If the serial console is an ACPI PNP device, the PNP bus always powers down the device at system suspend, even though the no_console_suspend command line parameter is specified (eg., when debugging suspend/resume). Add PNP_CONSOLE capability, which when set, prevents calling both the ->disable() and ->suspend() PNP protocol methods if console suspend is disabled. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/pnp/driver.c | 2 +- include/linux/pnp.h | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pnp/driver.c b/drivers/pnp/driver.c index f748cc8cbb0..4e57d337036 100644 --- a/drivers/pnp/driver.c +++ b/drivers/pnp/driver.c @@ -182,7 +182,7 @@ static int __pnp_bus_suspend(struct device *dev, pm_message_t state) return error; } - if (pnp_dev->protocol->suspend) + if (pnp_can_suspend(pnp_dev)) pnp_dev->protocol->suspend(pnp_dev, state); return 0; } diff --git a/include/linux/pnp.h b/include/linux/pnp.h index 195aafc6cd0..6512e9cbc6d 100644 --- a/include/linux/pnp.h +++ b/include/linux/pnp.h @@ -12,6 +12,7 @@ #include #include #include +#include #define PNP_NAME_LEN 50 @@ -309,15 +310,22 @@ struct pnp_fixup { #define PNP_DISABLE 0x0004 #define PNP_CONFIGURABLE 0x0008 #define PNP_REMOVABLE 0x0010 +#define PNP_CONSOLE 0x0020 #define pnp_can_read(dev) (((dev)->protocol->get) && \ ((dev)->capabilities & PNP_READ)) #define pnp_can_write(dev) (((dev)->protocol->set) && \ ((dev)->capabilities & PNP_WRITE)) -#define pnp_can_disable(dev) (((dev)->protocol->disable) && \ - ((dev)->capabilities & PNP_DISABLE)) +#define pnp_can_disable(dev) (((dev)->protocol->disable) && \ + ((dev)->capabilities & PNP_DISABLE) && \ + (!((dev)->capabilities & PNP_CONSOLE) || \ + console_suspend_enabled)) #define pnp_can_configure(dev) ((!(dev)->active) && \ ((dev)->capabilities & PNP_CONFIGURABLE)) +#define pnp_can_suspend(dev) (((dev)->protocol->suspend) && \ + (!((dev)->capabilities & PNP_CONSOLE) || \ + console_suspend_enabled)) + #ifdef CONFIG_ISAPNP extern struct pnp_protocol isapnp_protocol; -- cgit v1.2.3-70-g09d2 From ee15947cf69a9ab035089670ee76160678eb5aa2 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 11:50:25 -0500 Subject: serial: 8250_pnp: Enable PNP_CONSOLE for console ports When the kernel command line parameter, no_console_suspend, is used, the console should continue to output console messages during and after system suspend. For a serial console, the serial core ensures that the device is not shutdown when no_console_suspend is specified. However, the default operation of the pnp bus will disable and suspend the device and no further output occurs. When registering the 8250 port, if the serial device is a console set the PNP_CONSOLE capability, which prevents device power-off if consoles are not suspending. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pnp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index 682a2fbe5c0..50a09cd76d5 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -426,7 +426,7 @@ static int serial_pnp_guess_board(struct pnp_dev *dev) static int serial_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) { - struct uart_8250_port uart; + struct uart_8250_port uart, *port; int ret, line, flags = dev_id->driver_data; if (flags & UNKNOWN_DEV) { @@ -471,6 +471,10 @@ serial_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) if (line < 0 || (flags & CIR_PORT)) return -ENODEV; + port = serial8250_get_port(line); + if (uart_console(&port->port)) + dev->capabilities |= PNP_CONSOLE; + pnp_set_drvdata(dev, (void *)((long)line + 1)); return 0; } @@ -478,6 +482,8 @@ serial_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) static void serial_pnp_remove(struct pnp_dev *dev) { long line = (long)pnp_get_drvdata(dev); + + dev->capabilities &= ~PNP_CONSOLE; if (line) serial8250_unregister_port(line - 1); } -- cgit v1.2.3-70-g09d2 From c0b18db0562738eee578b7cd2291bd2851f6b5f8 Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Wed, 14 Jan 2015 08:04:21 +0200 Subject: tty/serial: digicolor: remove sysrq reference The Digicolor USART hardware does not support detecting the BREAK condition. This means that we can't support sysrq on this hardware. Remove all reference to sysrq from the code. This also fixes build when sysrq is disabled: drivers/tty/serial/digicolor-usart.c: In function 'digicolor_uart_console_write': drivers/tty/serial/digicolor-usart.c:407:33: error: 'struct uart_port' has no member named 'sysrq' Reported-by: Arnd Bergmann Signed-off-by: Baruch Siach Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/digicolor-usart.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/digicolor-usart.c b/drivers/tty/serial/digicolor-usart.c index 09ce0b3764e..a80cdad114f 100644 --- a/drivers/tty/serial/digicolor-usart.c +++ b/drivers/tty/serial/digicolor-usart.c @@ -170,9 +170,6 @@ static void digicolor_uart_rx(struct uart_port *port) ch_flag = TTY_OVERRUN; } - if (uart_handle_sysrq_char(port, ch)) - continue; - if (status & port->ignore_status_mask) continue; @@ -404,7 +401,7 @@ static void digicolor_uart_console_write(struct console *co, const char *c, unsigned long flags; int locked = 1; - if (port->sysrq || oops_in_progress) + if (oops_in_progress) locked = spin_trylock_irqsave(&port->lock, flags); else spin_lock_irqsave(&port->lock, flags); -- cgit v1.2.3-70-g09d2 From 6960cd465e553c8c0f9c26663b32bf3f3cd6a298 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 14 Jan 2015 14:39:07 -0200 Subject: serial: mxs-auart: Remove irq from private structure The irq number is only used inside the probe function, so there is no need to keep it in the private mxs_auart_port structure. Use a local 'irq' variable for storing the irq number instead. Also make its type of 'int' as platform_get_irq() may fail and return a negative number. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index 1e9fb370600..767fddaa757 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -152,8 +152,6 @@ struct mxs_auart_port { unsigned int mctrl_prev; enum mxs_auart_type devtype; - unsigned int irq; - struct clk *clk; struct device *dev; @@ -1228,7 +1226,7 @@ static int mxs_auart_probe(struct platform_device *pdev) of_match_device(mxs_auart_dt_ids, &pdev->dev); struct mxs_auart_port *s; u32 version; - int ret = 0; + int ret = 0, irq; struct resource *r; s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); @@ -1266,9 +1264,9 @@ static int mxs_auart_probe(struct platform_device *pdev) s->mctrl_prev = 0; - s->irq = platform_get_irq(pdev, 0); - s->port.irq = s->irq; - ret = devm_request_irq(&pdev->dev, s->irq, mxs_auart_irq_handle, 0, + irq = platform_get_irq(pdev, 0); + s->port.irq = irq; + ret = devm_request_irq(&pdev->dev, irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s); if (ret) return ret; -- cgit v1.2.3-70-g09d2 From 99c932c26d61f2c225f8f8e00dbea404646b9166 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 14 Jan 2015 14:39:08 -0200 Subject: serial: mxs-auart: Check for platform_get_irq() error We should check whether platform_get_irq() failed, and in the case of error this needs to be propagated. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index 767fddaa757..b54b6a2a577 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1265,6 +1265,9 @@ static int mxs_auart_probe(struct platform_device *pdev) s->mctrl_prev = 0; irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + s->port.irq = irq; ret = devm_request_irq(&pdev->dev, irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s); -- cgit v1.2.3-70-g09d2 From 5f9ba5b693ed6d639a3e6b47f4c6cd80e36f2547 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 14 Jan 2015 14:39:09 -0200 Subject: serial: mxs-auart: Remove unneeded zeroing of 'ret' There is no need to explicitly zero the 'ret' variable as it is properly initialized in a few lines below as: ret = serial_mxs_probe_dt(s, pdev); Remove the unneeded zeroing of 'ret'. Signed-off-by: Fabio Estevam Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mxs-auart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c index b54b6a2a577..d1298b6cc68 100644 --- a/drivers/tty/serial/mxs-auart.c +++ b/drivers/tty/serial/mxs-auart.c @@ -1226,7 +1226,7 @@ static int mxs_auart_probe(struct platform_device *pdev) of_match_device(mxs_auart_dt_ids, &pdev->dev); struct mxs_auart_port *s; u32 version; - int ret = 0, irq; + int ret, irq; struct resource *r; s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); -- cgit v1.2.3-70-g09d2 From f3e2d56dce47dbd0bb3f69f84741b439542fef37 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 13 Jan 2015 22:59:47 +0100 Subject: tty/isicom: fix big-endian compile warning Building an arm allmodconfig kernel triggers a lengthy but harmless warning in the isicom driver: drvers/tty/isicom.c: In function 'isicom_send_break': uapi/linux/swab.h:13:15: warning: integer overflow in expression [-Woverflow] (((__u16)(x) & (__u16)0x00ffU) << 8) | \ ^ uapi/linux/swab.h:107:2: note: in expansion of macro '___constant_swab16' ___constant_swab16(x) : \ ^ uapi/linux/byteorder/big_endian.h:34:43: note: in expansion of macro '__swab16' #define __cpu_to_le16(x) ((__force __le16)__swab16((x))) ^ linux/byteorder/generic.h:89:21: note: in expansion of macro '__cpu_to_le16' #define cpu_to_le16 __cpu_to_le16 ^ include/asm/io.h:270:6: note: in expansion of macro 'cpu_to_le16' cpu_to_le16(v),__io(p)); }) ^ drivers/tty/isicom.c:1058:2: note: in expansion of macro 'outw' outw((length & 0xff00), base); ^ Apparently, the problem is related to the fact that the value 0xff00, when used as a 16-bit number, is negative and passed into bitwise operands of the generic byte swapping code. Marking the input argument as unsigned in both technically correct and avoids the warning. Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/tty/isicom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/isicom.c b/drivers/tty/isicom.c index 59ed783c4bc..2054427992e 100644 --- a/drivers/tty/isicom.c +++ b/drivers/tty/isicom.c @@ -1055,7 +1055,7 @@ static int isicom_send_break(struct tty_struct *tty, int length) outw(0x8000 | ((port->channel) << (card->shift_count)) | 0x3, base); outw((length & 0xff) << 8 | 0x00, base); - outw((length & 0xff00), base); + outw((length & 0xff00u), base); InterruptTheCard(base); unlock_card(card); -- cgit v1.2.3-70-g09d2 From 6ecde472b0d943251f8f348155495391720b61b0 Mon Sep 17 00:00:00 2001 From: Nathan Rossi Date: Fri, 16 Jan 2015 13:49:25 +0100 Subject: tty: xuartps: Fix RX hang, and TX corruption in termios call The implementation of flushing the RX FIFO breaks in a number of cases, it is impossible to ensure an complete flush of the RX FIFO due to the hardware not allowing the use of the FIFOs when the receiver is disabled (Reading from the FIFO register does not remove it from the FIFO when the RX_EN=0 or RX_DIS=1). Additionally during an initial set_termios call where RX_DIS=1 causes a hang waiting forever for the RX FIFO to empty. On top of this the FIFO will be cleared by the use of the RXRST bits on the Control Register, making the RX flush pointless (as it does not preserve the data read anyway). Due to the TXRST the TX FIFO and transmitter can be interrupted during frame trasmission, causing corruption and additionally data lost in the FIFO. Most other serial drivers do not flush or clear the FIFOs during a termios configuration change and as such do not have issues with corruption. For this UART controller is it required that the TXRST/RXRST bit be flagged during the change, this means that the data in the FIFO will be dropped when changing configuration. In order to prevent data loss and corruption of the transmitted data, wait until the TX FIFO is empty before changing the configuration. The performance of this may cause the set_termios call to take a longer amount of time especially on lower baud rates, however it is comparable to the same performance hit that a console_write call costs. Signed-off-by: Nathan Rossi Acked-by: Anirudha Sarangi Acked-by: Harini Katakam Signed-off-by: Michal Simek Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/xilinx_uartps.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/xilinx_uartps.c b/drivers/tty/serial/xilinx_uartps.c index 542bab37e50..cff531a51a7 100644 --- a/drivers/tty/serial/xilinx_uartps.c +++ b/drivers/tty/serial/xilinx_uartps.c @@ -637,10 +637,12 @@ static void cdns_uart_set_termios(struct uart_port *port, spin_lock_irqsave(&port->lock, flags); - /* Empty the receive FIFO 1st before making changes */ - while ((cdns_uart_readl(CDNS_UART_SR_OFFSET) & - CDNS_UART_SR_RXEMPTY) != CDNS_UART_SR_RXEMPTY) { - cdns_uart_readl(CDNS_UART_FIFO_OFFSET); + /* Wait for the transmit FIFO to empty before making changes */ + if (!(cdns_uart_readl(CDNS_UART_CR_OFFSET) & CDNS_UART_CR_TX_DIS)) { + while (!(cdns_uart_readl(CDNS_UART_SR_OFFSET) & + CDNS_UART_SR_TXEMPTY)) { + cpu_relax(); + } } /* Disable the TX and RX to set baud rate */ -- cgit v1.2.3-70-g09d2 From c81622ac4282b0183e738e192c91fabe4a782715 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:22:14 -0500 Subject: pty: Fix overlimit memory use The pty_space() computation is broken; the space already consumed in the tty buffer is not accounted for. Use tty_buffer_set_limit(), which enforces the limit automatically. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index 6e1f1505f04..f882ac81b93 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -87,19 +87,6 @@ static void pty_unthrottle(struct tty_struct *tty) set_bit(TTY_THROTTLED, &tty->flags); } -/** - * pty_space - report space left for writing - * @to: tty we are writing into - * - * Limit the buffer space used by ptys to 8k. - */ - -static int pty_space(struct tty_struct *to) -{ - int n = tty_buffer_space_avail(to->port); - return min(n, 8192); -} - /** * pty_write - write to a pty * @tty: the tty we write from @@ -141,7 +128,7 @@ static int pty_write_room(struct tty_struct *tty) { if (tty->stopped) return 0; - return pty_space(tty->link); + return tty_buffer_space_avail(tty->link->port); } /** @@ -432,6 +419,8 @@ static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, o_tty->link = tty; tty_port_init(ports[0]); tty_port_init(ports[1]); + tty_buffer_set_limit(ports[0], 8192); + tty_buffer_set_limit(ports[1], 8192); o_tty->port = ports[0]; tty->port = ports[1]; o_tty->port->itty = o_tty; -- cgit v1.2.3-70-g09d2 From cb8763417224340f9a6092092077c801ce686eba Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Fri, 16 Jan 2015 13:56:02 -0800 Subject: sh-sci: extend PM methods In order to make it possible to restore from hibernation not only in Linux but also in e.g. U-Boot, we have to use sci_{suspend|remove}() for the PM {freeze| thaw|restore}() methods. It's handy to achieve this by using SIMPLE_DEV_PM_OPS() macro, however we have to annotate sci_{suspend|remove}() with '__maybe_unused' in order to avoid compilation warnings when CONFIG_PM_SLEEP is undefined. Based on orignal patch by Mikhail Ulyanov . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index e032963989f..4f784f8bf26 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -2605,7 +2605,7 @@ static int sci_probe(struct platform_device *dev) return 0; } -static int sci_suspend(struct device *dev) +static __maybe_unused int sci_suspend(struct device *dev) { struct sci_port *sport = dev_get_drvdata(dev); @@ -2615,7 +2615,7 @@ static int sci_suspend(struct device *dev) return 0; } -static int sci_resume(struct device *dev) +static __maybe_unused int sci_resume(struct device *dev) { struct sci_port *sport = dev_get_drvdata(dev); @@ -2625,10 +2625,7 @@ static int sci_resume(struct device *dev) return 0; } -static const struct dev_pm_ops sci_dev_pm_ops = { - .suspend = sci_suspend, - .resume = sci_resume, -}; +static SIMPLE_DEV_PM_OPS(sci_dev_pm_ops, sci_suspend, sci_resume); static struct platform_driver sci_driver = { .probe = sci_probe, -- cgit v1.2.3-70-g09d2 From a39d1da11b675b2cc2e0ffaafa8952ac7c3bd104 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 22 Jan 2015 14:40:26 +0200 Subject: serial: 8250_core: remove redundant else keyword Since we return in the first branch the second one doesn't require an additional else keyword. The patch removes it. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 689169c11f0..df8a5aa96bc 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -1376,9 +1376,11 @@ static void serial8250_start_tx(struct uart_port *port) struct uart_8250_port *up = up_to_u8250p(port); serial8250_rpm_get_tx(up); - if (up->dma && !up->dma->tx_dma(up)) { + + if (up->dma && !up->dma->tx_dma(up)) return; - } else if (!(up->ier & UART_IER_THRI)) { + + if (!(up->ier & UART_IER_THRI)) { up->ier |= UART_IER_THRI; serial_port_out(port, UART_IER, up->ier); -- cgit v1.2.3-70-g09d2 From 9119fba0cfeda6d415c9f068df66838a104b87cb Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 22 Jan 2015 14:40:27 +0200 Subject: serial: 8250_dma: don't bother DMA with small transfers If we would like to send amount of data less than FIFO size we better would do this via PIO mode. Otherwise the overhead could be significant. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dma.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c index e508939daea..21d01a49140 100644 --- a/drivers/tty/serial/8250/8250_dma.c +++ b/drivers/tty/serial/8250/8250_dma.c @@ -80,6 +80,10 @@ int serial8250_tx_dma(struct uart_8250_port *p) return 0; dma->tx_size = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE); + if (dma->tx_size < p->port.fifosize) { + ret = -EINVAL; + goto err; + } desc = dmaengine_prep_slave_single(dma->txchan, dma->tx_addr + xmit->tail, -- cgit v1.2.3-70-g09d2 From 51b31f1c6bbb3e741c930a71af8f0bcd4e5d4ded Mon Sep 17 00:00:00 2001 From: Yoshihiro Kaneko Date: Mon, 26 Jan 2015 20:53:29 +0900 Subject: serial: sh-sci: Use dev_dbg() to log an error message Since the driver cannot return from overrun error if characters are output during overrun process, use dev_dbg() instead of dev_notice() to log the error message of overrun in syslog. Based on a patch by Hisashi Nakamura. Signed-off-by: Yoshihiro Kaneko Acked-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 4f784f8bf26..508bdb527e1 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -858,7 +858,7 @@ static int sci_handle_fifo_overrun(struct uart_port *port) tty_insert_flip_char(tport, 0, TTY_OVERRUN); tty_flip_buffer_push(tport); - dev_notice(port->dev, "overrun error\n"); + dev_dbg(port->dev, "overrun error\n"); copied++; } -- cgit v1.2.3-70-g09d2 From 8b6ff84c2d445a476be5f5493b9deac00271eedd Mon Sep 17 00:00:00 2001 From: Hisashi Nakamura Date: Mon, 26 Jan 2015 21:25:48 +0900 Subject: serial: sh-sci: Fix R-Car SCIF and HSCIF overrun handling When fifo overrun happened, the interrupt status refers to SCLSR register in R-Car SCIF and HSCIF. Thus, overrun handling takes SCLSR register into account. Signed-off-by: Hisashi Nakamura Signed-off-by: Yoshihiro Kaneko Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 508bdb527e1..5b50c792ad5 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -997,12 +997,15 @@ static inline unsigned long port_rx_irq_mask(struct uart_port *port) static irqreturn_t sci_mpxed_interrupt(int irq, void *ptr) { unsigned short ssr_status, scr_status, err_enabled; + unsigned short slr_status = 0; struct uart_port *port = ptr; struct sci_port *s = to_sci_port(port); irqreturn_t ret = IRQ_NONE; ssr_status = serial_port_in(port, SCxSR); scr_status = serial_port_in(port, SCSCR); + if (port->type == PORT_SCIF || port->type == PORT_HSCIF) + slr_status = serial_port_in(port, SCLSR); err_enabled = scr_status & port_rx_irq_mask(port); /* Tx Interrupt */ @@ -1015,8 +1018,11 @@ static irqreturn_t sci_mpxed_interrupt(int irq, void *ptr) * DR flags */ if (((ssr_status & SCxSR_RDxF(port)) || s->chan_rx) && - (scr_status & SCSCR_RIE)) + (scr_status & SCSCR_RIE)) { + if (port->type == PORT_SCIF || port->type == PORT_HSCIF) + sci_handle_fifo_overrun(port); ret = sci_rx_interrupt(irq, ptr); + } /* Error Interrupt */ if ((ssr_status & SCxSR_ERRORS(port)) && err_enabled) @@ -1026,6 +1032,12 @@ static irqreturn_t sci_mpxed_interrupt(int irq, void *ptr) if ((ssr_status & SCxSR_BRK(port)) && err_enabled) ret = sci_br_interrupt(irq, ptr); + /* Overrun Interrupt */ + if (port->type == PORT_SCIF || port->type == PORT_HSCIF) { + if (slr_status & 0x01) + sci_handle_fifo_overrun(port); + } + return ret; } -- cgit v1.2.3-70-g09d2 From b94ba0328d3b36ff95f5074c0e9b44f90dc56bb5 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Fri, 23 Jan 2015 14:47:41 +0100 Subject: serial: samsung: Add support for early console This patch adds support for early console initialized from device tree and kernel command line to all variants of Samsung serial driver. Signed-off-by: Tomasz Figa [mszyprow: added support for command line based initialization, fixed comments, added documentation] Signed-off-by: Marek Szyprowski Reviewed-by: Alim Akhtar Tested-by: Alim Akhtar Tested-by: Chanwoo Choi Signed-off-by: Greg Kroah-Hartman --- Documentation/kernel-parameters.txt | 12 +++++ drivers/tty/serial/Kconfig | 1 + drivers/tty/serial/samsung.c | 103 ++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) (limited to 'drivers') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 4df73da11ad..e855697f54c 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -970,6 +970,18 @@ bytes respectively. Such letter suffixes can also be entirely omitted. smh Use ARM semihosting calls for early console. + s3c2410, + s3c2412, + s3c2440, + s3c6400, + s5pv210, + exynos4210, + Use early console provided by serial driver available + on Samsung SoCs, requires selecting proper type and + a correct base address of the selected UART port. The + serial port must already be setup and configured. + Options are not yet supported. + earlyprintk= [X86,SH,BLACKFIN,ARM,M68k] earlyprintk=vga earlyprintk=efi diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 35677f75a1d..c63317e87c3 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -241,6 +241,7 @@ config SERIAL_SAMSUNG tristate "Samsung SoC serial support" depends on PLAT_SAMSUNG || ARCH_EXYNOS select SERIAL_CORE + select SERIAL_EARLYCON help Support for the on-chip UARTs on the Samsung S3C24XX series CPUs, providing /dev/ttySAC0, 1 and 2 (note, some machines may not diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 024e5ecee98..a6bef8dcdc3 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -2403,6 +2403,109 @@ static struct platform_driver samsung_serial_driver = { module_platform_driver(samsung_serial_driver); +/* + * Early console. + */ + +struct samsung_early_console_data { + u32 txfull_mask; +}; + +static void samsung_early_busyuart(struct uart_port *port) +{ + while (!(readl(port->membase + S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXFE)) + ; +} + +static void samsung_early_busyuart_fifo(struct uart_port *port) +{ + struct samsung_early_console_data *data = port->private_data; + + while (readl(port->membase + S3C2410_UFSTAT) & data->txfull_mask) + ; +} + +static void samsung_early_putc(struct uart_port *port, int c) +{ + if (readl(port->membase + S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE) + samsung_early_busyuart_fifo(port); + else + samsung_early_busyuart(port); + + writeb(c, port->membase + S3C2410_UTXH); +} + +static void samsung_early_write(struct console *con, const char *s, unsigned n) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, s, n, samsung_early_putc); +} + +static int __init samsung_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = samsung_early_write; + return 0; +} + +/* S3C2410 */ +static struct samsung_early_console_data s3c2410_early_console_data = { + .txfull_mask = S3C2410_UFSTAT_TXFULL, +}; + +static int __init s3c2410_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + device->port.private_data = &s3c2410_early_console_data; + return samsung_early_console_setup(device, opt); +} +OF_EARLYCON_DECLARE(s3c2410, "samsung,s3c2410-uart", + s3c2410_early_console_setup); +EARLYCON_DECLARE(s3c2410, s3c2410_early_console_setup); + +/* S3C2412, S3C2440, S3C64xx */ +static struct samsung_early_console_data s3c2440_early_console_data = { + .txfull_mask = S3C2440_UFSTAT_TXFULL, +}; + +static int __init s3c2440_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + device->port.private_data = &s3c2440_early_console_data; + return samsung_early_console_setup(device, opt); +} +OF_EARLYCON_DECLARE(s3c2412, "samsung,s3c2412-uart", + s3c2440_early_console_setup); +OF_EARLYCON_DECLARE(s3c2440, "samsung,s3c2440-uart", + s3c2440_early_console_setup); +OF_EARLYCON_DECLARE(s3c6400, "samsung,s3c6400-uart", + s3c2440_early_console_setup); +EARLYCON_DECLARE(s3c2412, s3c2440_early_console_setup); +EARLYCON_DECLARE(s3c2440, s3c2440_early_console_setup); +EARLYCON_DECLARE(s3c6400, s3c2440_early_console_setup); + +/* S5PV210, EXYNOS */ +static struct samsung_early_console_data s5pv210_early_console_data = { + .txfull_mask = S5PV210_UFSTAT_TXFULL, +}; + +static int __init s5pv210_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + device->port.private_data = &s5pv210_early_console_data; + return samsung_early_console_setup(device, opt); +} +OF_EARLYCON_DECLARE(s5pv210, "samsung,s5pv210-uart", + s5pv210_early_console_setup); +OF_EARLYCON_DECLARE(exynos4210, "samsung,exynos4210-uart", + s5pv210_early_console_setup); +EARLYCON_DECLARE(s5pv210, s5pv210_early_console_setup); +EARLYCON_DECLARE(exynos4210, s5pv210_early_console_setup); + MODULE_ALIAS("platform:samsung-uart"); MODULE_DESCRIPTION("Samsung SoC Serial port driver"); MODULE_AUTHOR("Ben Dooks "); -- cgit v1.2.3-70-g09d2 From 2c5dc4641ca0e046b432f94f7058c4aff3440e47 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:05:34 -0500 Subject: n_tty: Eliminate receive_room() from consumer/exclusive paths The input worker never reschedules itself; it only processes input until either there is no more input or the read buffer is full. So the reader is responsible for restarting the input worker only if the read buffer was previously full (no_room == 1) _and_ space is now available to process more input because the reader has consumed data from the read buffer. However, computing the actual space available is not required to determine if the reader has consumed data from the read buffer. This condition is evaluated in 5 situations, each of which the space avail is already known: 1. n_tty_flush_buffer() - the read buffer is empty; kick the worker 2. n_tty_set_termios() - no data has been consumed; do not kick the worker (although it may have kicked the reader so data _will be_ consumed) 3. n_tty_check_unthrottle - avail space > 3968; kick the worker 4. n_tty_read, before leaving - only kick the worker if the reader has moved the tail. This prevents unnecessarily kicking the worker when timeout-style reading is used. 5. n_tty_read, before sleeping - although it is possible for the read buffer to be full and input_available_p() to be false, this can only happen when the input worker is racing the reader, in which case the reader will have been woken and won't sleep. Rename n_tty_set_room() to n_tty_kick_worker() to reflect what the function actually does. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 4ddfa60c922..b60b043f47d 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -187,10 +187,10 @@ static int receive_room(struct tty_struct *tty) } /** - * n_tty_set_room - receive space + * n_tty_kick_worker - start input worker (if required) * @tty: terminal * - * Re-schedules the flip buffer work if space just became available. + * Re-schedules the flip buffer work if it may have stopped * * Caller holds exclusive termios_rwsem * or @@ -198,12 +198,12 @@ static int receive_room(struct tty_struct *tty) * holds non-exclusive termios_rwsem */ -static void n_tty_set_room(struct tty_struct *tty) +static void n_tty_kick_worker(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - /* Did this open up the receive buffer? We may need to flip */ - if (unlikely(ldata->no_room) && receive_room(tty)) { + /* Did the input worker stop? Restart it */ + if (unlikely(ldata->no_room)) { ldata->no_room = 0; WARN_RATELIMIT(tty->port->itty == NULL, @@ -274,7 +274,7 @@ static void n_tty_check_unthrottle(struct tty_struct *tty) return; if (!tty->count) return; - n_tty_set_room(tty); + n_tty_kick_worker(tty); n_tty_write_wakeup(tty->link); if (waitqueue_active(&tty->link->write_wait)) wake_up_interruptible_poll(&tty->link->write_wait, POLLOUT); @@ -296,7 +296,7 @@ static void n_tty_check_unthrottle(struct tty_struct *tty) break; if (!tty->count) break; - n_tty_set_room(tty); + n_tty_kick_worker(tty); unthrottled = tty_unthrottle_safe(tty); if (!unthrottled) break; @@ -379,7 +379,7 @@ static void n_tty_flush_buffer(struct tty_struct *tty) { down_write(&tty->termios_rwsem); reset_buffer_flags(tty->disc_data); - n_tty_set_room(tty); + n_tty_kick_worker(tty); if (tty->link) n_tty_packet_mode_flush(tty); @@ -1817,7 +1817,6 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) else ldata->real_raw = 0; } - n_tty_set_room(tty); /* * Fix tty hang when I_IXON(tty) is cleared, but the tty * been stopped by STOP_CHAR(tty) before it. @@ -2130,6 +2129,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, ssize_t retval = 0; long timeout; int packet; + size_t tail; c = job_control(tty, file); if (c < 0) @@ -2166,6 +2166,7 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, } packet = tty->packet; + tail = ldata->read_tail; add_wait_queue(&tty->read_wait, &wait); while (nr) { @@ -2208,7 +2209,6 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, retval = -ERESTARTSYS; break; } - n_tty_set_room(tty); up_read(&tty->termios_rwsem); timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, @@ -2253,7 +2253,8 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, if (time) timeout = time; } - n_tty_set_room(tty); + if (tail != ldata->read_tail) + n_tty_kick_worker(tty); up_read(&tty->termios_rwsem); remove_wait_queue(&tty->read_wait, &wait); -- cgit v1.2.3-70-g09d2 From a342846f9b0448996f56ebece80454d41b431ce2 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:05:35 -0500 Subject: n_tty: Fix throttle for canon lines > 3967 chars The tty driver will be mistakenly throttled if a line termination has not been received, and the line exceeds 3967 chars. Thus, it is possible for the driver to stop sending when it has not yet sent the newline. This does not apply to the pty driver. Don't throttle until at least one line termination has been received. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index b60b043f47d..d4b14c30794 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -247,6 +247,8 @@ static void n_tty_write_wakeup(struct tty_struct *tty) static void n_tty_check_throttle(struct tty_struct *tty) { + struct n_tty_data *ldata = tty->disc_data; + if (tty->driver->type == TTY_DRIVER_TYPE_PTY) return; /* @@ -254,6 +256,9 @@ static void n_tty_check_throttle(struct tty_struct *tty) * mode. We don't want to throttle the driver if we're in * canonical mode and don't have a newline yet! */ + if (ldata->icanon && ldata->canon_head == ldata->read_tail) + return; + while (1) { int throttled; tty_set_flow_change(tty, TTY_THROTTLE_SAFE); -- cgit v1.2.3-70-g09d2 From 5e28cca1539f3532cb2392710655cd3e562cee8b Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:05:36 -0500 Subject: n_tty: Simplify throttle threshold calculation The adjustments performed by receive_room() are to ensure a line termination can always be written to the read buffer. However, these adjustments are irrelevant to the throttle threshold (because the threshold < buffer limit). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index d4b14c30794..7efabc4673b 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -262,7 +262,7 @@ static void n_tty_check_throttle(struct tty_struct *tty) while (1) { int throttled; tty_set_flow_change(tty, TTY_THROTTLE_SAFE); - if (receive_room(tty) >= TTY_THRESHOLD_THROTTLE) + if (N_TTY_BUF_SIZE - read_cnt(ldata) >= TTY_THRESHOLD_THROTTLE) break; throttled = tty_throttle_safe(tty); if (!throttled) -- cgit v1.2.3-70-g09d2 From 70aca71f92ca2c111978bf676287fab5580d2598 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:05:37 -0500 Subject: n_tty: Fix unordered accesses to lockless read buffer Add commit_head buffer index, which the producer-side publishes after input processing in non-canon mode. This ensures the consumer-side observes correctly-ordered writes in non-canonical mode (ie., the buffer data is written before the buffer index is advanced). Fix consumer-side uses of read_cnt() to use commit_head instead. Add required memory barriers to the tail index to guarantee the consumer-side has completed the loads before the producer-side begins writing new data. Open-code the producer-side receive_room() into the i/o loop. Remove no-longer-referenced receive_room(). Based on work by Christian Riesch Cc: Christian Riesch Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 101 +++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 53 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 7efabc4673b..f63b25bbe89 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -90,6 +90,7 @@ struct n_tty_data { /* producer-published */ size_t read_head; + size_t commit_head; size_t canon_head; size_t echo_head; size_t echo_commit; @@ -161,31 +162,6 @@ static inline int tty_put_user(struct tty_struct *tty, unsigned char x, return put_user(x, ptr); } -static int receive_room(struct tty_struct *tty) -{ - struct n_tty_data *ldata = tty->disc_data; - int left; - - if (I_PARMRK(tty)) { - /* Multiply read_cnt by 3, since each byte might take up to - * three times as many spaces when PARMRK is set (depending on - * its flags, e.g. parity error). */ - left = N_TTY_BUF_SIZE - read_cnt(ldata) * 3 - 1; - } else - left = N_TTY_BUF_SIZE - read_cnt(ldata) - 1; - - /* - * If we are doing input canonicalization, and there are no - * pending newlines, let characters through without limit, so - * that erase characters will be handled. Other excess - * characters will be beeped. - */ - if (left <= 0) - left = ldata->icanon && ldata->canon_head == ldata->read_tail; - - return left; -} - /** * n_tty_kick_worker - start input worker (if required) * @tty: terminal @@ -224,7 +200,7 @@ static ssize_t chars_in_buffer(struct tty_struct *tty) ssize_t n = 0; if (!ldata->icanon) - n = read_cnt(ldata); + n = ldata->commit_head - ldata->read_tail; else n = ldata->canon_head - ldata->read_tail; return n; @@ -318,10 +294,6 @@ static void n_tty_check_unthrottle(struct tty_struct *tty) * * n_tty_receive_buf()/producer path: * caller holds non-exclusive termios_rwsem - * modifies read_head - * - * read_head is only considered 'published' if canonical mode is - * not active. */ static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata) @@ -345,6 +317,7 @@ static void reset_buffer_flags(struct n_tty_data *ldata) { ldata->read_head = ldata->canon_head = ldata->read_tail = 0; ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; + ldata->commit_head = 0; ldata->echo_mark = 0; ldata->line_start = 0; @@ -992,10 +965,6 @@ static inline void finish_erasing(struct n_tty_data *ldata) * * n_tty_receive_buf()/producer path: * caller holds non-exclusive termios_rwsem - * modifies read_head - * - * Modifying the read_head is not considered a publish in this context - * because canonical mode is active -- only canon_head publishes */ static void eraser(unsigned char c, struct tty_struct *tty) @@ -1144,7 +1113,6 @@ static void isig(int sig, struct tty_struct *tty) * * n_tty_receive_buf()/producer path: * caller holds non-exclusive termios_rwsem - * publishes read_head via put_tty_queue() * * Note: may get exclusive termios_rwsem if flushing input buffer */ @@ -1214,7 +1182,6 @@ static void n_tty_receive_overrun(struct tty_struct *tty) * * n_tty_receive_buf()/producer path: * caller holds non-exclusive termios_rwsem - * publishes read_head via put_tty_queue() */ static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) { @@ -1268,7 +1235,6 @@ n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) * n_tty_receive_buf()/producer path: * caller holds non-exclusive termios_rwsem * publishes canon_head if canonical mode is active - * otherwise, publishes read_head via put_tty_queue() * * Returns 1 if LNEXT was received, else returns 0 */ @@ -1381,7 +1347,7 @@ n_tty_receive_char_special(struct tty_struct *tty, unsigned char c) handle_newline: set_bit(ldata->read_head & (N_TTY_BUF_SIZE - 1), ldata->read_flags); put_tty_queue(c, ldata); - ldata->canon_head = ldata->read_head; + smp_store_release(&ldata->canon_head, ldata->read_head); kill_fasync(&tty->fasync, SIGIO, POLL_IN); if (waitqueue_active(&tty->read_wait)) wake_up_interruptible_poll(&tty->read_wait, POLLIN); @@ -1531,7 +1497,7 @@ n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag) * * n_tty_receive_buf()/producer path: * claims non-exclusive termios_rwsem - * publishes read_head and canon_head + * publishes commit_head or canon_head */ static void @@ -1542,16 +1508,14 @@ n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, size_t n, head; head = ldata->read_head & (N_TTY_BUF_SIZE - 1); - n = N_TTY_BUF_SIZE - max(read_cnt(ldata), head); - n = min_t(size_t, count, n); + n = min_t(size_t, count, N_TTY_BUF_SIZE - head); memcpy(read_buf_addr(ldata, head), cp, n); ldata->read_head += n; cp += n; count -= n; head = ldata->read_head & (N_TTY_BUF_SIZE - 1); - n = N_TTY_BUF_SIZE - max(read_cnt(ldata), head); - n = min_t(size_t, count, n); + n = min_t(size_t, count, N_TTY_BUF_SIZE - head); memcpy(read_buf_addr(ldata, head), cp, n); ldata->read_head += n; } @@ -1681,8 +1645,13 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, tty->ops->flush_chars(tty); } - if ((!ldata->icanon && (read_cnt(ldata) >= ldata->minimum_to_wake)) || - L_EXTPROC(tty)) { + if (ldata->icanon && !L_EXTPROC(tty)) + return; + + /* publish read_head to consumer */ + smp_store_release(&ldata->commit_head, ldata->read_head); + + if ((read_cnt(ldata) >= ldata->minimum_to_wake) || L_EXTPROC(tty)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); if (waitqueue_active(&tty->read_wait)) wake_up_interruptible_poll(&tty->read_wait, POLLIN); @@ -1699,7 +1668,31 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, down_read(&tty->termios_rwsem); while (1) { - room = receive_room(tty); + /* + * When PARMRK is set, multiply read_cnt by 3, since each byte + * might take up to three times as many spaces (depending on + * its flags, e.g. parity error). [This calculation is wrong.] + * + * If we are doing input canonicalization, and there are no + * pending newlines, let characters through without limit, so + * that erase characters will be handled. Other excess + * characters will be beeped. + * + * paired with store in *_copy_from_read_buf() -- guarantees + * the consumer has loaded the data in read_buf up to the new + * read_tail (so this producer will not overwrite unread data) + */ + size_t tail = smp_load_acquire(&ldata->read_tail); + size_t head = ldata->read_head; + + if (I_PARMRK(tty)) + room = N_TTY_BUF_SIZE - (head - tail) * 3 - 1; + else + room = N_TTY_BUF_SIZE - (head - tail) - 1; + + if (room <= 0) + room = ldata->icanon && ldata->canon_head == tail; + n = min(count, room); if (!n) { if (flow && !room) @@ -1769,6 +1762,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) ldata->canon_head = ldata->read_head; ldata->push = 1; } + ldata->commit_head = ldata->read_head; ldata->erasing = 0; ldata->lnext = 0; } @@ -1909,7 +1903,7 @@ static inline int input_available_p(struct tty_struct *tty, int poll) if (ldata->icanon && !L_EXTPROC(tty)) return ldata->canon_head != ldata->read_tail; else - return read_cnt(ldata) >= amt; + return ldata->commit_head - ldata->read_tail >= amt; } /** @@ -1941,10 +1935,11 @@ static int copy_from_read_buf(struct tty_struct *tty, int retval; size_t n; bool is_eof; + size_t head = smp_load_acquire(&ldata->commit_head); size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); retval = 0; - n = min(read_cnt(ldata), N_TTY_BUF_SIZE - tail); + n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail); n = min(*nr, n); if (n) { retval = copy_to_user(*b, read_buf_addr(ldata, tail), n); @@ -1952,9 +1947,10 @@ static int copy_from_read_buf(struct tty_struct *tty, is_eof = n == 1 && read_buf(ldata, tail) == EOF_CHAR(tty); tty_audit_add_data(tty, read_buf_addr(ldata, tail), n, ldata->icanon); - ldata->read_tail += n; + smp_store_release(&ldata->read_tail, ldata->read_tail + n); /* Turn single EOF into zero-length read */ - if (L_EXTPROC(tty) && ldata->icanon && is_eof && !read_cnt(ldata)) + if (L_EXTPROC(tty) && ldata->icanon && is_eof && + (head == ldata->read_tail)) n = 0; *b += n; *nr -= n; @@ -1997,7 +1993,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, bool eof_push = 0; /* N.B. avoid overrun if nr == 0 */ - n = min(*nr, read_cnt(ldata)); + n = min(*nr, smp_load_acquire(&ldata->canon_head) - ldata->read_tail); if (!n) return 0; @@ -2047,8 +2043,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, if (found) clear_bit(eol, ldata->read_flags); - smp_mb__after_atomic(); - ldata->read_tail += c; + smp_store_release(&ldata->read_tail, ldata->read_tail + c); if (found) { if (!ldata->push) -- cgit v1.2.3-70-g09d2 From 06c49f9fa31f026e00a15cc6487b4d8d99b0e333 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:05:38 -0500 Subject: n_tty: Fix PARMRK over-throttling If PARMRK is enabled, the available read buffer space computation is overly-pessimistic, which results in severely throttled i/o, even in the absence of parity errors. For example, if the 4k read buffer contains 1k processed data, the input worker will compute available space of 333 bytes, despite 3k being available. At 1365 chars of processed data, 0 space available is computed. *Divide remaining space* by 3, truncating down (if left == 2, left = 0). Reported-by: Christian Riesch Conflicts: drivers/tty/n_tty.c Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index f63b25bbe89..7aeabb71952 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -1669,9 +1669,8 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, while (1) { /* - * When PARMRK is set, multiply read_cnt by 3, since each byte - * might take up to three times as many spaces (depending on - * its flags, e.g. parity error). [This calculation is wrong.] + * When PARMRK is set, each input char may take up to 3 chars + * in the read buf; reduce the buffer space avail by 3x * * If we are doing input canonicalization, and there are no * pending newlines, let characters through without limit, so @@ -1683,13 +1682,10 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, * read_tail (so this producer will not overwrite unread data) */ size_t tail = smp_load_acquire(&ldata->read_tail); - size_t head = ldata->read_head; + room = N_TTY_BUF_SIZE - (ldata->read_head - tail) - 1; if (I_PARMRK(tty)) - room = N_TTY_BUF_SIZE - (head - tail) * 3 - 1; - else - room = N_TTY_BUF_SIZE - (head - tail) - 1; - + room /= 3; if (room <= 0) room = ldata->icanon && ldata->canon_head == tail; -- cgit v1.2.3-70-g09d2 From fb5ef9e7da39968fec6d6f37f20a23d23740c75e Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 16 Jan 2015 15:05:39 -0500 Subject: n_tty: Fix read buffer overwrite when no newline In canon mode, the read buffer head will advance over the buffer tail if the input > 4095 bytes without receiving a line termination char. Discard additional input until a line termination is received. Before evaluating for overflow, the 'room' value is normalized for I_PARMRK and 1 byte is reserved for line termination (even in !icanon mode, in case the mode is switched). The following table shows the transform: actual buffer | 'room' value before overflow calc space avail | !I_PARMRK | I_PARMRK -------------------------------------------------- 0 | -1 | -1 1 | 0 | 0 2 | 1 | 0 3 | 2 | 0 4+ | 3 | 1 When !icanon or when icanon and the read buffer contains newlines, normalized 'room' values of -1 and 0 are clamped to 0, and 'overflow' is 0, so read_head is not adjusted and the input i/o loop exits (setting no_room if called from flush_to_ldisc()). No input is discarded since the reader does have input available to read which ensures forward progress. When icanon and the read buffer does not contain newlines and the normalized 'room' value is 0, then overflow and room are reset to 1, so that the i/o loop will process the next input char normally (except for parity errors which are ignored). Thus, erasures, signalling chars, 7-bit mode, etc. will continue to be handled properly. If the input char processed was not a line termination char, then the canon_head index will not have advanced, so the normalized 'room' value will now be -1 and 'overflow' will be set, which indicates the read_head can safely be reset, effectively erasing the last char processed. If the input char processed was a line termination, then the canon_head index will have advanced, so 'overflow' is cleared to 0, the read_head is not reset, and 'room' is cleared to 0, which exits the i/o loop (because the reader now have input available to read which ensures forward progress). Note that it is possible for a line termination to be received, and for the reader to copy the line to the user buffer before the input i/o loop is ready to process the next input char. This is why the i/o loop recomputes the room/overflow state with every input char while handling overflow. Finally, if the input data was processed without receiving a line termination (so that overflow is still set), the pty driver must receive a write wakeup. A pty writer may be waiting to write more data in n_tty_write() but without unthrottling here that wakeup will not arrive, and forward progress will halt. (Normally, the pty writer is woken when the reader reads data out of the buffer and more space become available). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 92 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 7aeabb71952..5793aa0d3bf 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -225,8 +225,6 @@ static void n_tty_check_throttle(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - if (tty->driver->type == TTY_DRIVER_TYPE_PTY) - return; /* * Check the remaining room for the input canonicalization * mode. We don't want to throttle the driver if we're in @@ -1483,23 +1481,6 @@ n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag) n_tty_receive_char_flagged(tty, c, flag); } -/** - * n_tty_receive_buf - data receive - * @tty: terminal device - * @cp: buffer - * @fp: flag buffer - * @count: characters - * - * Called by the terminal driver when a block of characters has - * been received. This function must be called from soft contexts - * not from interrupt context. The driver is responsible for making - * calls one at a time and in order (or using flush_to_ldisc) - * - * n_tty_receive_buf()/producer path: - * claims non-exclusive termios_rwsem - * publishes commit_head or canon_head - */ - static void n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) @@ -1658,12 +1639,45 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, } } +/** + * n_tty_receive_buf_common - process input + * @tty: device to receive input + * @cp: input chars + * @fp: flags for each char (if NULL, all chars are TTY_NORMAL) + * @count: number of input chars in @cp + * + * Called by the terminal driver when a block of characters has + * been received. This function must be called from soft contexts + * not from interrupt context. The driver is responsible for making + * calls one at a time and in order (or using flush_to_ldisc) + * + * Returns the # of input chars from @cp which were processed. + * + * In canonical mode, the maximum line length is 4096 chars (including + * the line termination char); lines longer than 4096 chars are + * truncated. After 4095 chars, input data is still processed but + * not stored. Overflow processing ensures the tty can always + * receive more input until at least one line can be read. + * + * In non-canonical mode, the read buffer will only accept 4095 chars; + * this provides the necessary space for a newline char if the input + * mode is switched to canonical. + * + * Note it is possible for the read buffer to _contain_ 4096 chars + * in non-canonical mode: the read buffer could already contain the + * maximum canon line of 4096 chars when the mode is switched to + * non-canonical. + * + * n_tty_receive_buf()/producer path: + * claims non-exclusive termios_rwsem + * publishes commit_head or canon_head + */ static int n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, char *fp, int count, int flow) { struct n_tty_data *ldata = tty->disc_data; - int room, n, rcvd = 0; + int room, n, rcvd = 0, overflow; down_read(&tty->termios_rwsem); @@ -1683,19 +1697,27 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, */ size_t tail = smp_load_acquire(&ldata->read_tail); - room = N_TTY_BUF_SIZE - (ldata->read_head - tail) - 1; + room = N_TTY_BUF_SIZE - (ldata->read_head - tail); if (I_PARMRK(tty)) - room /= 3; - if (room <= 0) - room = ldata->icanon && ldata->canon_head == tail; + room = (room + 2) / 3; + room--; + if (room <= 0) { + overflow = ldata->icanon && ldata->canon_head == tail; + if (overflow && room < 0) + ldata->read_head--; + room = overflow; + ldata->no_room = flow && !room; + } else + overflow = 0; n = min(count, room); - if (!n) { - if (flow && !room) - ldata->no_room = 1; + if (!n) break; - } - __receive_buf(tty, cp, fp, n); + + /* ignore parity errors if handling overflow */ + if (!overflow || !fp || *fp != TTY_PARITY) + __receive_buf(tty, cp, fp, n); + cp += n; if (fp) fp += n; @@ -1704,7 +1726,17 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, } tty->receive_room = room; - n_tty_check_throttle(tty); + + /* Unthrottle if handling overflow on pty */ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY) { + if (overflow) { + tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); + tty_unthrottle_safe(tty); + __tty_set_flow_change(tty, 0); + } + } else + n_tty_check_throttle(tty); + up_read(&tty->termios_rwsem); return rcvd; -- cgit v1.2.3-70-g09d2 From 3abf87cd3e70009ed70a7f28c2914888a7e27332 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 17 Jan 2015 15:42:04 -0500 Subject: tty: Make lock subclasses available for other tty locks Besides nested legacy_mutex locking which is required on pty pair teardown, other nested pty operations require lock subclassing. Move lock subclass definition to tty interface header, include/linux/tty.h, and document its use. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_mutex.c | 12 +----------- include/linux/tty.h | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/tty_mutex.c b/drivers/tty/tty_mutex.c index a872389dc0b..0efcf713b75 100644 --- a/drivers/tty/tty_mutex.c +++ b/drivers/tty/tty_mutex.c @@ -4,18 +4,8 @@ #include #include -/* - * Nested tty locks are necessary for releasing pty pairs. - * The stable lock order is master pty first, then slave pty. - */ - /* Legacy tty mutex glue */ -enum { - TTY_MUTEX_NORMAL, - TTY_MUTEX_SLAVE, -}; - /* * Getting the big tty mutex. */ @@ -58,5 +48,5 @@ void __lockfunc tty_unlock_slave(struct tty_struct *tty) void tty_set_lock_subclass(struct tty_struct *tty) { - lockdep_set_subclass(&tty->legacy_mutex, TTY_MUTEX_SLAVE); + lockdep_set_subclass(&tty->legacy_mutex, TTY_LOCK_SLAVE); } diff --git a/include/linux/tty.h b/include/linux/tty.h index 7d66ae508e5..849659908f2 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -14,6 +14,23 @@ #include +/* + * Lock subclasses for tty locks + * + * TTY_LOCK_NORMAL is for normal ttys and master ptys. + * TTY_LOCK_SLAVE is for slave ptys only. + * + * Lock subclasses are necessary for handling nested locking with pty pairs. + * tty locks which use nested locking: + * + * legacy_mutex - Nested tty locks are necessary for releasing pty pairs. + * The stable lock order is master pty first, then slave pty. + */ + +enum { + TTY_LOCK_NORMAL = 0, + TTY_LOCK_SLAVE, +}; /* * (Note: the *_driver.minor_start values 1, 64, 128, 192 are -- cgit v1.2.3-70-g09d2 From 1d1d14da12e79a6c05fbe1a975401f0f56c93316 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 17 Jan 2015 15:42:05 -0500 Subject: pty: Fix buffer flush deadlock The pty driver does not clear its write buffer when commanded. This is to avoid an apparent deadlock between parallel flushes from both pty ends; specifically when handling either BRK or INTR input. However, parallel flushes from this source is not possible since the pty master can never be set to BRKINT or ISIG. Parallel flushes from other sources are possible but these do not threaten deadlocks. Annotate the tty buffer mutex for lockdep to represent the nested tty_buffer locking which occurs when the pty slave is processing input (its buffer mutex held) and receives INTR or BRK and acquires the linked tty buffer mutex via tty_buffer_flush(). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 10 +++++++++- drivers/tty/tty_buffer.c | 6 ++++++ include/linux/tty.h | 4 ++++ 3 files changed, 19 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index f882ac81b93..0e273158eda 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -212,10 +212,16 @@ static int pty_signal(struct tty_struct *tty, int sig) static void pty_flush_buffer(struct tty_struct *tty) { struct tty_struct *to = tty->link; + struct tty_ldisc *ld; if (!to) return; - /* tty_buffer_flush(to); FIXME */ + + ld = tty_ldisc_ref(to); + tty_buffer_flush(to, ld); + if (ld) + tty_ldisc_deref(ld); + if (to->packet) { spin_lock_irq(&tty->ctrl_lock); tty->ctrl_status |= TIOCPKT_FLUSHWRITE; @@ -425,6 +431,8 @@ static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, tty->port = ports[1]; o_tty->port->itty = o_tty; + tty_buffer_set_lock_subclass(o_tty->port); + tty_driver_kref_get(driver); tty->count++; o_tty->count++; diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 3605103fc1a..75661641f5f 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -557,3 +557,9 @@ int tty_buffer_set_limit(struct tty_port *port, int limit) return 0; } EXPORT_SYMBOL_GPL(tty_buffer_set_limit); + +/* slave ptys can claim nested buffer lock when handling BRK and INTR */ +void tty_buffer_set_lock_subclass(struct tty_port *port) +{ + lockdep_set_subclass(&port->buf.lock, TTY_LOCK_SLAVE); +} diff --git a/include/linux/tty.h b/include/linux/tty.h index 849659908f2..55964b9490d 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -25,6 +25,9 @@ * * legacy_mutex - Nested tty locks are necessary for releasing pty pairs. * The stable lock order is master pty first, then slave pty. + * tty_buffer lock - slave ptys can claim nested buffer lock when handling + * signal chars. The stable lock order is slave pty, then + * master. */ enum { @@ -460,6 +463,7 @@ extern void tty_flush_to_ldisc(struct tty_struct *tty); extern void tty_buffer_free_all(struct tty_port *port); extern void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld); extern void tty_buffer_init(struct tty_port *port); +extern void tty_buffer_set_lock_subclass(struct tty_port *port); extern speed_t tty_termios_baud_rate(struct ktermios *termios); extern speed_t tty_termios_input_baud_rate(struct ktermios *termios); extern void tty_termios_encode_baud_rate(struct ktermios *termios, -- cgit v1.2.3-70-g09d2 From d2b6f44779d3be22d32a5697bd30b59367fd2b33 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 17 Jan 2015 15:42:06 -0500 Subject: n_tty: Fix signal handling flushes BRKINT and ISIG requires input and output flush when a signal char is received. However, the order of operations is significant since parallel i/o may be ongoing. Merge the signal handling for BRKINT with ISIG handling. Process the signal first. This ensures any ongoing i/o is aborted; without this, a waiting writer may continue writing after the flush occurs and after the signal char has been echoed. Write lock the termios_rwsem, which excludes parallel writers from pushing new i/o until after the output buffers are flushed; claiming the write lock is necessary anyway to exclude parallel readers while the read buffer is flushed. Subclass the termios_rwsem for ptys since the slave pty performing the flush may appear to reorder the termios_rwsem->tty buffer lock lock order; adding annotation clarifies that slave tty_buffer lock-> slave termios_rwsem -> master tty_buffer lock is a valid lock order. Flush the echo buffer. In this context, the echo buffer is 'output'. Otherwise, the output will appear discontinuous because the output buffer was cleared which contains older output than the echo buffer. Open-code the read buffer flush since the input worker does not need kicking (this is the input worker). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 45 ++++++++++++++++++++++++++++++--------------- drivers/tty/pty.c | 1 + include/linux/tty.h | 3 +++ 3 files changed, 34 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 5793aa0d3bf..cf6e0f2e133 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -1090,16 +1090,45 @@ static void eraser(unsigned char c, struct tty_struct *tty) * Called when a signal is being sent due to terminal input. * Called from the driver receive_buf path so serialized. * + * Performs input and output flush if !NOFLSH. In this context, the echo + * buffer is 'output'. The signal is processed first to alert any current + * readers or writers to discontinue and exit their i/o loops. + * * Locking: ctrl_lock */ static void isig(int sig, struct tty_struct *tty) { + struct n_tty_data *ldata = tty->disc_data; struct pid *tty_pgrp = tty_get_pgrp(tty); if (tty_pgrp) { kill_pgrp(tty_pgrp, sig, 1); put_pid(tty_pgrp); } + + if (!L_NOFLSH(tty)) { + up_read(&tty->termios_rwsem); + down_write(&tty->termios_rwsem); + + /* clear echo buffer */ + mutex_lock(&ldata->output_lock); + ldata->echo_head = ldata->echo_tail = 0; + ldata->echo_mark = ldata->echo_commit = 0; + mutex_unlock(&ldata->output_lock); + + /* clear output buffer */ + tty_driver_flush_buffer(tty); + + /* clear input buffer */ + reset_buffer_flags(tty->disc_data); + + /* notify pty master of flush */ + if (tty->link) + n_tty_packet_mode_flush(tty); + + up_write(&tty->termios_rwsem); + down_read(&tty->termios_rwsem); + } } /** @@ -1123,13 +1152,6 @@ static void n_tty_receive_break(struct tty_struct *tty) return; if (I_BRKINT(tty)) { isig(SIGINT, tty); - if (!L_NOFLSH(tty)) { - /* flushing needs exclusive termios_rwsem */ - up_read(&tty->termios_rwsem); - n_tty_flush_buffer(tty); - tty_driver_flush_buffer(tty); - down_read(&tty->termios_rwsem); - } return; } if (I_PARMRK(tty)) { @@ -1203,13 +1225,7 @@ static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) static void n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) { - if (!L_NOFLSH(tty)) { - /* flushing needs exclusive termios_rwsem */ - up_read(&tty->termios_rwsem); - n_tty_flush_buffer(tty); - tty_driver_flush_buffer(tty); - down_read(&tty->termios_rwsem); - } + isig(signal, tty); if (I_IXON(tty)) start_tty(tty); if (L_ECHO(tty)) { @@ -1217,7 +1233,6 @@ n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) commit_echoes(tty); } else process_echoes(tty); - isig(signal, tty); return; } diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index 0e273158eda..e72ee629cea 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -395,6 +395,7 @@ static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, goto err_put_module; tty_set_lock_subclass(o_tty); + lockdep_set_subclass(&o_tty->termios_rwsem, TTY_LOCK_SLAVE); if (legacy) { /* We always use new tty termios data so we can do this diff --git a/include/linux/tty.h b/include/linux/tty.h index 55964b9490d..fe5623c9af7 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -25,6 +25,9 @@ * * legacy_mutex - Nested tty locks are necessary for releasing pty pairs. * The stable lock order is master pty first, then slave pty. + * termios_rwsem - The stable lock order is tty_buffer lock->termios_rwsem. + * Subclassing this lock enables the slave pty to hold its + * termios_rwsem when claiming the master tty_buffer lock. * tty_buffer lock - slave ptys can claim nested buffer lock when handling * signal chars. The stable lock order is slave pty, then * master. -- cgit v1.2.3-70-g09d2 From b164c9721e3ea4c08a4738cd4d0538bbb0c24419 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:25 -0500 Subject: serial: core: Simplify console suspend logic in uart_suspend_port() When the uart port being suspended is a console and consoles are not suspending (kernel command line contains no_console_suspend), then no action is performed for that port, and the function can return early. If the function has not returned early, then one of the conditions is not true, so the expression (console_suspend_enabled || !uart_console(uport)) must be true and can be eliminated. Similarly, the expression (console_suspend_enabled && uart_console(uport)) simplifies to just uart_console(uport). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 984605bb5bf..2c67a077042 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -2008,23 +2008,24 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport) } put_device(tty_dev); - if (console_suspend_enabled || !uart_console(uport)) - uport->suspended = 1; + /* Nothing to do if the console is not suspending */ + if (!console_suspend_enabled && uart_console(uport)) + goto unlock; + + uport->suspended = 1; if (port->flags & ASYNC_INITIALIZED) { const struct uart_ops *ops = uport->ops; int tries; - if (console_suspend_enabled || !uart_console(uport)) { - set_bit(ASYNCB_SUSPENDED, &port->flags); - clear_bit(ASYNCB_INITIALIZED, &port->flags); - - spin_lock_irq(&uport->lock); - ops->stop_tx(uport); - ops->set_mctrl(uport, 0); - ops->stop_rx(uport); - spin_unlock_irq(&uport->lock); - } + set_bit(ASYNCB_SUSPENDED, &port->flags); + clear_bit(ASYNCB_INITIALIZED, &port->flags); + + spin_lock_irq(&uport->lock); + ops->stop_tx(uport); + ops->set_mctrl(uport, 0); + ops->stop_rx(uport); + spin_unlock_irq(&uport->lock); /* * Wait for the transmitter to empty. @@ -2036,19 +2037,17 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport) drv->dev_name, drv->tty_driver->name_base + uport->line); - if (console_suspend_enabled || !uart_console(uport)) - ops->shutdown(uport); + ops->shutdown(uport); } /* * Disable the console device before suspending. */ - if (console_suspend_enabled && uart_console(uport)) + if (uart_console(uport)) console_stop(uport->cons); - if (console_suspend_enabled || !uart_console(uport)) - uart_change_pm(state, UART_PM_STATE_OFF); - + uart_change_pm(state, UART_PM_STATE_OFF); +unlock: mutex_unlock(&port->mutex); return 0; -- cgit v1.2.3-70-g09d2 From 01ba8d6af4b76aee99a8ae3a76748c8c36db49f3 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 19 Jan 2015 07:44:41 +0100 Subject: tty: serial: men_z135_uart: Fix driver for changes in hardware 16z135 IP Core has changed so the driver needs to be updated to respect these changes. The following changes have been made: * Don't invert the 16z135 modem status register when reading. * Add module parameter to configure the (baud rate dependent) RX timeout. Character timeout in seconds = (timeout_reg * baud_reg * 4)/freq_reg. * Enable the handling of UART core's automatic flow control feature. When AFE is active disable generation of modem status IRQs. * Rework the handling of IRQs to be conform with newer FPGA versions and take precautions not to miss an interrupt because of the destructive read of the IIR register. * Correct men_z135_handle_modem_status(), MSR is stat_reg[15:8] not stat_reg[7:0] * Correct calling of uart_handle_{dcd,cts}_change() * Reset CLOCAL when CRTSCTS is set Signed-off-by: Johannes Thumshirn Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/men_z135_uart.c | 155 ++++++++++++++++++++++++++----------- 1 file changed, 109 insertions(+), 46 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/men_z135_uart.c b/drivers/tty/serial/men_z135_uart.c index 517cd073dc0..35c55505b3e 100644 --- a/drivers/tty/serial/men_z135_uart.c +++ b/drivers/tty/serial/men_z135_uart.c @@ -23,7 +23,6 @@ #define MEN_Z135_MAX_PORTS 12 #define MEN_Z135_BASECLK 29491200 #define MEN_Z135_FIFO_SIZE 1024 -#define MEN_Z135_NUM_MSI_VECTORS 2 #define MEN_Z135_FIFO_WATERMARK 1020 #define MEN_Z135_STAT_REG 0x0 @@ -34,12 +33,11 @@ #define MEN_Z135_CONF_REG 0x808 #define MEN_Z135_UART_FREQ 0x80c #define MEN_Z135_BAUD_REG 0x810 -#define MENZ135_TIMEOUT 0x814 +#define MEN_Z135_TIMEOUT 0x814 #define MEN_Z135_MEM_SIZE 0x818 -#define IS_IRQ(x) ((x) & 1) -#define IRQ_ID(x) (((x) >> 1) & 7) +#define IRQ_ID(x) ((x) & 0x1f) #define MEN_Z135_IER_RXCIEN BIT(0) /* RX Space IRQ */ #define MEN_Z135_IER_TXCIEN BIT(1) /* TX Space IRQ */ @@ -94,11 +92,11 @@ #define MEN_Z135_LSR_TEXP BIT(6) #define MEN_Z135_LSR_RXFIFOERR BIT(7) -#define MEN_Z135_IRQ_ID_MST 0 -#define MEN_Z135_IRQ_ID_TSA 1 -#define MEN_Z135_IRQ_ID_RDA 2 -#define MEN_Z135_IRQ_ID_RLS 3 -#define MEN_Z135_IRQ_ID_CTI 6 +#define MEN_Z135_IRQ_ID_RLS BIT(0) +#define MEN_Z135_IRQ_ID_RDA BIT(1) +#define MEN_Z135_IRQ_ID_CTI BIT(2) +#define MEN_Z135_IRQ_ID_TSA BIT(3) +#define MEN_Z135_IRQ_ID_MST BIT(4) #define LCR(x) (((x) >> MEN_Z135_LCR_SHIFT) & 0xff) @@ -118,12 +116,18 @@ static int align; module_param(align, int, S_IRUGO); MODULE_PARM_DESC(align, "Keep hardware FIFO write pointer aligned, default 0"); +static uint rx_timeout; +module_param(rx_timeout, uint, S_IRUGO); +MODULE_PARM_DESC(rx_timeout, "RX timeout. " + "Timeout in seconds = (timeout_reg * baud_reg * 4) / freq_reg"); + struct men_z135_port { struct uart_port port; struct mcb_device *mdev; unsigned char *rxbuf; u32 stat_reg; spinlock_t lock; + bool automode; }; #define to_men_z135(port) container_of((port), struct men_z135_port, port) @@ -180,12 +184,16 @@ static inline void men_z135_reg_clr(struct men_z135_port *uart, */ static void men_z135_handle_modem_status(struct men_z135_port *uart) { - if (uart->stat_reg & MEN_Z135_MSR_DDCD) + u8 msr; + + msr = (uart->stat_reg >> 8) & 0xff; + + if (msr & MEN_Z135_MSR_DDCD) uart_handle_dcd_change(&uart->port, - uart->stat_reg & ~MEN_Z135_MSR_DCD); - if (uart->stat_reg & MEN_Z135_MSR_DCTS) + msr & MEN_Z135_MSR_DCD); + if (msr & MEN_Z135_MSR_DCTS) uart_handle_cts_change(&uart->port, - uart->stat_reg & ~MEN_Z135_MSR_CTS); + msr & MEN_Z135_MSR_CTS); } static void men_z135_handle_lsr(struct men_z135_port *uart) @@ -322,7 +330,8 @@ static void men_z135_handle_tx(struct men_z135_port *uart) txfree = MEN_Z135_FIFO_WATERMARK - txc; if (txfree <= 0) { - pr_err("Not enough room in TX FIFO have %d, need %d\n", + dev_err(&uart->mdev->dev, + "Not enough room in TX FIFO have %d, need %d\n", txfree, qlen); goto irq_en; } @@ -373,43 +382,54 @@ out: * @irq: The IRQ number * @data: Pointer to UART port * - * Check IIR register to see which tasklet to start. + * Check IIR register to find the cause of the interrupt and handle it. + * It is possible that multiple interrupts reason bits are set and reading + * the IIR is a destructive read, so we always need to check for all possible + * interrupts and handle them. */ static irqreturn_t men_z135_intr(int irq, void *data) { struct men_z135_port *uart = (struct men_z135_port *)data; struct uart_port *port = &uart->port; + bool handled = false; + unsigned long flags; int irq_id; uart->stat_reg = ioread32(port->membase + MEN_Z135_STAT_REG); - /* IRQ pending is low active */ - if (IS_IRQ(uart->stat_reg)) - return IRQ_NONE; - irq_id = IRQ_ID(uart->stat_reg); - switch (irq_id) { - case MEN_Z135_IRQ_ID_MST: - men_z135_handle_modem_status(uart); - break; - case MEN_Z135_IRQ_ID_TSA: - men_z135_handle_tx(uart); - break; - case MEN_Z135_IRQ_ID_CTI: - dev_dbg(&uart->mdev->dev, "Character Timeout Indication\n"); - /* Fallthrough */ - case MEN_Z135_IRQ_ID_RDA: - /* Reading data clears RX IRQ */ - men_z135_handle_rx(uart); - break; - case MEN_Z135_IRQ_ID_RLS: + + if (!irq_id) + goto out; + + spin_lock_irqsave(&port->lock, flags); + /* It's save to write to IIR[7:6] RXC[9:8] */ + iowrite8(irq_id, port->membase + MEN_Z135_STAT_REG); + + if (irq_id & MEN_Z135_IRQ_ID_RLS) { men_z135_handle_lsr(uart); - break; - default: - dev_warn(&uart->mdev->dev, "Unknown IRQ id %d\n", irq_id); - return IRQ_NONE; + handled = true; + } + + if (irq_id & (MEN_Z135_IRQ_ID_RDA | MEN_Z135_IRQ_ID_CTI)) { + if (irq_id & MEN_Z135_IRQ_ID_CTI) + dev_dbg(&uart->mdev->dev, "Character Timeout Indication\n"); + men_z135_handle_rx(uart); + handled = true; + } + + if (irq_id & MEN_Z135_IRQ_ID_TSA) { + men_z135_handle_tx(uart); + handled = true; } - return IRQ_HANDLED; + if (irq_id & MEN_Z135_IRQ_ID_MST) { + men_z135_handle_modem_status(uart); + handled = true; + } + + spin_unlock_irqrestore(&port->lock, flags); +out: + return IRQ_RETVAL(handled); } /** @@ -464,21 +484,37 @@ static unsigned int men_z135_tx_empty(struct uart_port *port) */ static void men_z135_set_mctrl(struct uart_port *port, unsigned int mctrl) { - struct men_z135_port *uart = to_men_z135(port); - u32 conf_reg = 0; + u32 old; + u32 conf_reg; + conf_reg = old = ioread32(port->membase + MEN_Z135_CONF_REG); if (mctrl & TIOCM_RTS) conf_reg |= MEN_Z135_MCR_RTS; + else + conf_reg &= ~MEN_Z135_MCR_RTS; + if (mctrl & TIOCM_DTR) conf_reg |= MEN_Z135_MCR_DTR; + else + conf_reg &= ~MEN_Z135_MCR_DTR; + if (mctrl & TIOCM_OUT1) conf_reg |= MEN_Z135_MCR_OUT1; + else + conf_reg &= ~MEN_Z135_MCR_OUT1; + if (mctrl & TIOCM_OUT2) conf_reg |= MEN_Z135_MCR_OUT2; + else + conf_reg &= ~MEN_Z135_MCR_OUT2; + if (mctrl & TIOCM_LOOP) conf_reg |= MEN_Z135_MCR_LOOP; + else + conf_reg &= ~MEN_Z135_MCR_LOOP; - men_z135_reg_set(uart, MEN_Z135_CONF_REG, conf_reg); + if (conf_reg != old) + iowrite32(conf_reg, port->membase + MEN_Z135_CONF_REG); } /** @@ -490,12 +526,9 @@ static void men_z135_set_mctrl(struct uart_port *port, unsigned int mctrl) static unsigned int men_z135_get_mctrl(struct uart_port *port) { unsigned int mctrl = 0; - u32 stat_reg; u8 msr; - stat_reg = ioread32(port->membase + MEN_Z135_STAT_REG); - - msr = ~((stat_reg >> 8) & 0xff); + msr = ioread8(port->membase + MEN_Z135_STAT_REG + 1); if (msr & MEN_Z135_MSR_CTS) mctrl |= TIOCM_CTS; @@ -524,6 +557,19 @@ static void men_z135_stop_tx(struct uart_port *port) men_z135_reg_clr(uart, MEN_Z135_CONF_REG, MEN_Z135_IER_TXCIEN); } +/* + * men_z135_disable_ms() - Disable Modem Status + * port: The UART port + * + * Enable Modem Status IRQ. + */ +static void men_z135_disable_ms(struct uart_port *port) +{ + struct men_z135_port *uart = to_men_z135(port); + + men_z135_reg_clr(uart, MEN_Z135_CONF_REG, MEN_Z135_IER_MSIEN); +} + /** * men_z135_start_tx() - Start transmitting characters * @port: The UART port @@ -535,6 +581,9 @@ static void men_z135_start_tx(struct uart_port *port) { struct men_z135_port *uart = to_men_z135(port); + if (uart->automode) + men_z135_disable_ms(port); + men_z135_handle_tx(uart); } @@ -584,6 +633,9 @@ static int men_z135_startup(struct uart_port *port) iowrite32(conf_reg, port->membase + MEN_Z135_CONF_REG); + if (rx_timeout) + iowrite32(rx_timeout, port->membase + MEN_Z135_TIMEOUT); + return 0; } @@ -603,6 +655,7 @@ static void men_z135_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { + struct men_z135_port *uart = to_men_z135(port); unsigned int baud; u32 conf_reg; u32 bd_reg; @@ -643,6 +696,16 @@ static void men_z135_set_termios(struct uart_port *port, } else lcr |= MEN_Z135_PAR_DIS << MEN_Z135_PEN_SHIFT; + conf_reg |= MEN_Z135_IER_MSIEN; + if (termios->c_cflag & CRTSCTS) { + conf_reg |= MEN_Z135_MCR_RCFC; + uart->automode = true; + termios->c_cflag &= ~CLOCAL; + } else { + conf_reg &= ~MEN_Z135_MCR_RCFC; + uart->automode = false; + } + termios->c_cflag &= ~CMSPAR; /* Mark/Space parity is not supported */ conf_reg |= lcr << MEN_Z135_LCR_SHIFT; -- cgit v1.2.3-70-g09d2 From d1f2f219319813070c022e841f48bfd0fe17fd34 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:26 -0500 Subject: serial: 8250: Move UART_BUG_QUOT workaround The UART_BUG_QUOT workaround adjusts the divisor computed from the baud rate by serial8250_get_divisor(). Move the workaround into serial8250_get_divisor(), so that divisor-from-baud computation is encapsulated. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index df8a5aa96bc..8e3302c6bbf 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2413,8 +2413,9 @@ static void serial8250_shutdown(struct uart_port *port) serial8250_do_shutdown(port); } -static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud) +static unsigned int serial8250_get_divisor(struct uart_8250_port *up, unsigned int baud) { + struct uart_port *port = &up->port; unsigned int quot; /* @@ -2430,6 +2431,12 @@ static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int else quot = uart_get_divisor(port, baud); + /* + * Oxford Semi 952 rev B workaround + */ + if (up->bugs & UART_BUG_QUOT && (quot & 0xff) == 0) + quot++; + return quot; } @@ -2478,13 +2485,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, baud = uart_get_baud_rate(port, termios, old, port->uartclk / 16 / 0xffff, port->uartclk / 16); - quot = serial8250_get_divisor(port, baud); - - /* - * Oxford Semi 952 rev B workaround - */ - if (up->bugs & UART_BUG_QUOT && (quot & 0xff) == 0) - quot++; + quot = serial8250_get_divisor(up, baud); if (up->capabilities & UART_CAP_FIFO && port->fifosize > 1) { /* NOTE: If fifo_bug is not set, a user can set RX_trigger. */ -- cgit v1.2.3-70-g09d2 From 0ec3f585cfb503a18b82cd181c9bf32b45a24f5e Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:27 -0500 Subject: serial: 8250: Refactor LCR computation Refactor the computation of the LCR register value from termios c_cflag into a new local function, serial8250_compute_lcr(). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 8e3302c6bbf..b63b9c352cc 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2440,16 +2440,12 @@ static unsigned int serial8250_get_divisor(struct uart_8250_port *up, unsigned i return quot; } -void -serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, - struct ktermios *old) +static unsigned char serial8250_compute_lcr(struct uart_8250_port *up, + tcflag_t c_cflag) { - struct uart_8250_port *up = up_to_u8250p(port); unsigned char cval; - unsigned long flags; - unsigned int baud, quot; - switch (termios->c_cflag & CSIZE) { + switch (c_cflag & CSIZE) { case CS5: cval = UART_LCR_WLEN5; break; @@ -2465,20 +2461,34 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, break; } - if (termios->c_cflag & CSTOPB) + if (c_cflag & CSTOPB) cval |= UART_LCR_STOP; - if (termios->c_cflag & PARENB) { + if (c_cflag & PARENB) { cval |= UART_LCR_PARITY; if (up->bugs & UART_BUG_PARITY) up->fifo_bug = true; } - if (!(termios->c_cflag & PARODD)) + if (!(c_cflag & PARODD)) cval |= UART_LCR_EPAR; #ifdef CMSPAR - if (termios->c_cflag & CMSPAR) + if (c_cflag & CMSPAR) cval |= UART_LCR_SPAR; #endif + return cval; +} + +void +serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + struct uart_8250_port *up = up_to_u8250p(port); + unsigned char cval; + unsigned long flags; + unsigned int baud, quot; + + cval = serial8250_compute_lcr(up, termios->c_cflag); + /* * Ask the core to calculate the divisor for us. */ -- cgit v1.2.3-70-g09d2 From e72abd5da0730dfc4c8f566f7896c15c2d1bb88f Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:28 -0500 Subject: serial: 8250: Refactor divisor programming Refactor divisor register programming into a new function, serial8250_set_divisor; this allows serial console to reinitialize early after resume from suspend. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 87 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index b63b9c352cc..fd9e723926c 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2478,6 +2478,50 @@ static unsigned char serial8250_compute_lcr(struct uart_8250_port *up, return cval; } +void serial8250_set_divisor(struct uart_port *port, unsigned int baud, + unsigned int quot) +{ + struct uart_8250_port *up = up_to_u8250p(port); + + /* Workaround to enable 115200 baud on OMAP1510 internal ports */ + if (is_omap1510_8250(up)) { + if (baud == 115200) { + quot = 1; + serial_port_out(port, UART_OMAP_OSC_12M_SEL, 1); + } else + serial_port_out(port, UART_OMAP_OSC_12M_SEL, 0); + } + + /* + * For NatSemi, switch to bank 2 not bank 1, to avoid resetting EXCR2, + * otherwise just set DLAB + */ + if (up->capabilities & UART_NATSEMI) + serial_port_out(port, UART_LCR, 0xe0); + else + serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB); + + serial_dl_write(up, quot); + + /* + * XR17V35x UARTs have an extra fractional divisor register (DLD) + * + * We need to recalculate all of the registers, because DLM and DLL + * are already rounded to a whole integer. + * + * When recalculating we use a 32x clock instead of a 16x clock to + * allow 1-bit for rounding in the fractional part. + */ + if (up->port.type == PORT_XR17V35X) { + unsigned int baud_x32 = (port->uartclk * 2) / baud; + u16 quot = baud_x32 / 32; + u8 quot_frac = DIV_ROUND_CLOSEST(baud_x32 % 32, 2); + + serial_dl_write(up, quot); + serial_port_out(port, 0x2, quot_frac & 0xf); + } +} + void serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) @@ -2526,6 +2570,8 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, serial8250_rpm_get(up); spin_lock_irqsave(&port->lock, flags); + up->lcr = cval; /* Save computed LCR */ + /* * Update the per-port timeout. */ @@ -2590,43 +2636,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, serial_port_out(port, UART_EFR, efr); } - /* Workaround to enable 115200 baud on OMAP1510 internal ports */ - if (is_omap1510_8250(up)) { - if (baud == 115200) { - quot = 1; - serial_port_out(port, UART_OMAP_OSC_12M_SEL, 1); - } else - serial_port_out(port, UART_OMAP_OSC_12M_SEL, 0); - } - - /* - * For NatSemi, switch to bank 2 not bank 1, to avoid resetting EXCR2, - * otherwise just set DLAB - */ - if (up->capabilities & UART_NATSEMI) - serial_port_out(port, UART_LCR, 0xe0); - else - serial_port_out(port, UART_LCR, cval | UART_LCR_DLAB); - - serial_dl_write(up, quot); - - /* - * XR17V35x UARTs have an extra fractional divisor register (DLD) - * - * We need to recalculate all of the registers, because DLM and DLL - * are already rounded to a whole integer. - * - * When recalculating we use a 32x clock instead of a 16x clock to - * allow 1-bit for rounding in the fractional part. - */ - if (up->port.type == PORT_XR17V35X) { - unsigned int baud_x32 = (port->uartclk * 2) / baud; - u16 quot = baud_x32 / 32; - u8 quot_frac = DIV_ROUND_CLOSEST(baud_x32 % 32, 2); - - serial_dl_write(up, quot); - serial_port_out(port, 0x2, quot_frac & 0xf); - } + serial8250_set_divisor(port, baud, quot); /* * LCR DLAB must be set to enable 64-byte FIFO mode. If the FCR @@ -2635,8 +2645,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, if (port->type == PORT_16750) serial_port_out(port, UART_FCR, up->fcr); - serial_port_out(port, UART_LCR, cval); /* reset DLAB */ - up->lcr = cval; /* Save LCR */ + serial_port_out(port, UART_LCR, up->lcr); /* reset DLAB */ if (port->type != PORT_16750) { /* emulated UARTs (Lucent Venus 167x) need two steps */ if (up->fcr & UART_FCR_ENABLE_FIFO) -- cgit v1.2.3-70-g09d2 From 06a4c710673184f5c750bdb2f8579e0ae1cc252c Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:29 -0500 Subject: serial: 8250: Refactor XR17V35X divisor calculation Exar XR17V35X PCIe uarts support a 4-bit fractional divisor register. Refactor the divisor calculation from the divisor programming. Allow a fractional result from serial8250_get_divisor() and pass this result to serial8250_dl_write(). Simplify the calculation for quot and quot_frac. This was verified to be identical to the results of the original calculation with a test jig. NB: The results were also compared with the divisor value chart on pg 33 of the Exar XR17V352 datasheet, rev 1.0.3, here: http://www.exar.com/common/content/document.ashx?id=1585 which differs from the calculated values by 1 in the fractional result. This is because the calculated values are still rounded in the fractional result, whereas the table values are truncated. Note that the data error rate % values in the datasheet are for rounded fractional results, as the truncated fractional results have more error. Cc: Joe Schultz Cc: Aaron Sierra Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 52 +++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index fd9e723926c..c8ecfaf49eb 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2413,7 +2413,26 @@ static void serial8250_shutdown(struct uart_port *port) serial8250_do_shutdown(port); } -static unsigned int serial8250_get_divisor(struct uart_8250_port *up, unsigned int baud) +/* + * XR17V35x UARTs have an extra fractional divisor register (DLD) + * Calculate divisor with extra 4-bit fractional portion + */ +static unsigned int xr17v35x_get_divisor(struct uart_8250_port *up, + unsigned int baud, + unsigned int *frac) +{ + struct uart_port *port = &up->port; + unsigned int quot_16; + + quot_16 = DIV_ROUND_CLOSEST(port->uartclk, baud); + *frac = quot_16 & 0x0f; + + return quot_16 >> 4; +} + +static unsigned int serial8250_get_divisor(struct uart_8250_port *up, + unsigned int baud, + unsigned int *frac) { struct uart_port *port = &up->port; unsigned int quot; @@ -2421,6 +2440,7 @@ static unsigned int serial8250_get_divisor(struct uart_8250_port *up, unsigned i /* * Handle magic divisors for baud rates above baud_base on * SMSC SuperIO chips. + * */ if ((port->flags & UPF_MAGIC_MULTIPLIER) && baud == (port->uartclk/4)) @@ -2428,6 +2448,8 @@ static unsigned int serial8250_get_divisor(struct uart_8250_port *up, unsigned i else if ((port->flags & UPF_MAGIC_MULTIPLIER) && baud == (port->uartclk/8)) quot = 0x8002; + else if (up->port.type == PORT_XR17V35X) + quot = xr17v35x_get_divisor(up, baud, frac); else quot = uart_get_divisor(port, baud); @@ -2479,7 +2501,7 @@ static unsigned char serial8250_compute_lcr(struct uart_8250_port *up, } void serial8250_set_divisor(struct uart_port *port, unsigned int baud, - unsigned int quot) + unsigned int quot, unsigned int quot_frac) { struct uart_8250_port *up = up_to_u8250p(port); @@ -2503,23 +2525,9 @@ void serial8250_set_divisor(struct uart_port *port, unsigned int baud, serial_dl_write(up, quot); - /* - * XR17V35x UARTs have an extra fractional divisor register (DLD) - * - * We need to recalculate all of the registers, because DLM and DLL - * are already rounded to a whole integer. - * - * When recalculating we use a 32x clock instead of a 16x clock to - * allow 1-bit for rounding in the fractional part. - */ - if (up->port.type == PORT_XR17V35X) { - unsigned int baud_x32 = (port->uartclk * 2) / baud; - u16 quot = baud_x32 / 32; - u8 quot_frac = DIV_ROUND_CLOSEST(baud_x32 % 32, 2); - - serial_dl_write(up, quot); - serial_port_out(port, 0x2, quot_frac & 0xf); - } + /* XR17V35x UARTs have an extra fractional divisor register (DLD) */ + if (up->port.type == PORT_XR17V35X) + serial_port_out(port, 0x2, quot_frac); } void @@ -2529,7 +2537,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, struct uart_8250_port *up = up_to_u8250p(port); unsigned char cval; unsigned long flags; - unsigned int baud, quot; + unsigned int baud, quot, frac = 0; cval = serial8250_compute_lcr(up, termios->c_cflag); @@ -2539,7 +2547,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, baud = uart_get_baud_rate(port, termios, old, port->uartclk / 16 / 0xffff, port->uartclk / 16); - quot = serial8250_get_divisor(up, baud); + quot = serial8250_get_divisor(up, baud, &frac); if (up->capabilities & UART_CAP_FIFO && port->fifosize > 1) { /* NOTE: If fifo_bug is not set, a user can set RX_trigger. */ @@ -2636,7 +2644,7 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, serial_port_out(port, UART_EFR, efr); } - serial8250_set_divisor(port, baud, quot); + serial8250_set_divisor(port, baud, quot, frac); /* * LCR DLAB must be set to enable 64-byte FIFO mode. If the FCR -- cgit v1.2.3-70-g09d2 From 4516d50aabedbe5ae334155193e4d35c02390d9a Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:30 -0500 Subject: serial: 8250: Use canary to restart console after suspend When using no_console_suspend, the serial console may be powered off anyway during system sleep. Upon resume, the port may be in its default power-on state, but is expected to continue console i/o before the device has received its pm callback. The resultant garbage i/o can cause all kinds of havoc on the remote end. Use the scratch register as a canary to discover if the console has been powered-off. Write a non-zero value to the scratch register at port suspend and reprogram the port before any console i/o if the scratch register != canary before port resume. This workaround is disabled for omap_8250 (which uses different divisor programming). Credit to Doug Anderson for the idea of using the scratch register canary to discover port power-down. Cc: Doug Anderson Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 35 ++++++++++++++++++++++++++++++++++- include/linux/serial_8250.h | 3 +++ 2 files changed, 37 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index c8ecfaf49eb..1449c56506b 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -3267,6 +3267,27 @@ serial8250_console_write(struct console *co, const char *s, unsigned int count) else serial_port_out(port, UART_IER, 0); + /* check scratch reg to see if port powered off during system sleep */ + if (up->canary && (up->canary != serial_port_in(port, UART_SCR))) { + struct ktermios termios; + unsigned int baud, quot, frac = 0; + + termios.c_cflag = port->cons->cflag; + if (port->state->port.tty && termios.c_cflag == 0) + termios.c_cflag = port->state->port.tty->termios.c_cflag; + + baud = uart_get_baud_rate(port, &termios, NULL, + port->uartclk / 16 / 0xffff, + port->uartclk / 16); + quot = serial8250_get_divisor(up, baud, &frac); + + serial8250_set_divisor(port, baud, quot, frac); + serial_port_out(port, UART_LCR, up->lcr); + serial_port_out(port, UART_MCR, UART_MCR_DTR | UART_MCR_RTS); + + up->canary = 0; + } + uart_console_write(port, s, count, serial8250_console_putchar); /* @@ -3417,7 +3438,17 @@ int __init early_serial_setup(struct uart_port *port) */ void serial8250_suspend_port(int line) { - uart_suspend_port(&serial8250_reg, &serial8250_ports[line].port); + struct uart_8250_port *up = &serial8250_ports[line]; + struct uart_port *port = &up->port; + + if (!console_suspend_enabled && uart_console(port) && + port->type != PORT_8250) { + unsigned char canary = 0xa5; + serial_out(up, UART_SCR, canary); + up->canary = canary; + } + + uart_suspend_port(&serial8250_reg, port); } /** @@ -3431,6 +3462,8 @@ void serial8250_resume_port(int line) struct uart_8250_port *up = &serial8250_ports[line]; struct uart_port *port = &up->port; + up->canary = 0; + if (up->capabilities & UART_NATSEMI) { /* Ensure it's still in high speed mode */ serial_port_out(port, UART_LCR, 0xE0); diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index 245b959f1ff..a8efa235b7c 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -85,6 +85,9 @@ struct uart_8250_port { unsigned char mcr_force; /* mask of forced bits */ unsigned char cur_iotype; /* Running I/O type */ unsigned int rpm_tx_active; + unsigned char canary; /* non-zero during system sleep + * if no_console_suspend + */ /* * Some bits in registers are cleared on a read, so they must -- cgit v1.2.3-70-g09d2 From e9cef8625e997a5ade9cddd2d501257dfee7cb62 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Thu, 22 Jan 2015 12:24:31 -0500 Subject: serial: 8250: Prevent concurrent updates to shadow registers The port shadow registers, ->fcr and ->mcr, must be protected from concurrent updates. Relocate the shadow register updates in serial8250_do_set_termios() to the port lock critical section. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 1449c56506b..57d13b058a4 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2549,6 +2549,15 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, port->uartclk / 16); quot = serial8250_get_divisor(up, baud, &frac); + /* + * Ok, we're now changing the port state. Do it with + * interrupts disabled. + */ + serial8250_rpm_get(up); + spin_lock_irqsave(&port->lock, flags); + + up->lcr = cval; /* Save computed LCR */ + if (up->capabilities & UART_CAP_FIFO && port->fifosize > 1) { /* NOTE: If fifo_bug is not set, a user can set RX_trigger. */ if ((baud < 2400 && !up->dma) || up->fifo_bug) { @@ -2571,15 +2580,6 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, up->mcr |= UART_MCR_AFE; } - /* - * Ok, we're now changing the port state. Do it with - * interrupts disabled. - */ - serial8250_rpm_get(up); - spin_lock_irqsave(&port->lock, flags); - - up->lcr = cval; /* Save computed LCR */ - /* * Update the per-port timeout. */ -- cgit v1.2.3-70-g09d2 From 6d01bb9dc82a60580f749062a48cb47cd5caca07 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 26 Jan 2015 22:50:07 -0600 Subject: tty/serial: of_serial: add DT alias ID handling Add support for alias parsing from the DT. This allows for consistent tty numbering. Signed-off-by: Rob Herring Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: linux-serial@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/of_serial.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/of_serial.c b/drivers/tty/serial/of_serial.c index 64f1bab7e9d..3194b42c97c 100644 --- a/drivers/tty/serial/of_serial.c +++ b/drivers/tty/serial/of_serial.c @@ -128,6 +128,10 @@ static int of_platform_serial_setup(struct platform_device *ofdev, if (of_find_property(np, "no-loopback-test", NULL)) port->flags |= UPF_SKIP_TEST; + ret = of_alias_get_id(np, "serial"); + if (ret >= 0) + port->line = ret; + port->dev = &ofdev->dev; switch (type) { -- cgit v1.2.3-70-g09d2 From 6ad991b699b4e74af980d08086a99badcb175b37 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 26 Jan 2015 22:50:08 -0600 Subject: tty/serial: of_serial: add support for PXA/MMP uarts Add mrvl,pxa-uart and mrvl,mmp-uart compatible strings for the of_serial driver. These are 8250 variants which have a port type of PORT_XSCALE. There is also the serial driver pxa.c with these compatible strings already. However, it can be replaced with the common 8250 driver. It has some issues like it cannot coexist with the 8250 driver due to tty name collision. That also means adding these compatible strings here should not case a problem. Signed-off-by: Rob Herring Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: linux-serial@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/of_serial.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/of_serial.c b/drivers/tty/serial/of_serial.c index 3194b42c97c..fbb719c89a6 100644 --- a/drivers/tty/serial/of_serial.c +++ b/drivers/tty/serial/of_serial.c @@ -335,6 +335,10 @@ static struct of_device_id of_platform_serial_table[] = { .data = (void *)PORT_ALTR_16550_F64, }, { .compatible = "altr,16550-FIFO128", .data = (void *)PORT_ALTR_16550_F128, }, + { .compatible = "mrvl,mmp-uart", + .data = (void *)PORT_XSCALE, }, + { .compatible = "mrvl,pxa-uart", + .data = (void *)PORT_XSCALE, }, #ifdef CONFIG_SERIAL_OF_PLATFORM_NWPSERIAL { .compatible = "ibm,qpace-nwp-serial", .data = (void *)PORT_NWPSERIAL, }, -- cgit v1.2.3-70-g09d2 From a4c639b04f301ddc3f71bc0f2600c3759846db43 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 26 Jan 2015 22:50:09 -0600 Subject: tty/serial: 8250_early: Add support for PXA UARTs The PXA variant of the 8250 UART adds a UART enable bit which must not be cleared. Make the earlycon support maintain this bit if it is set. This implies some initialization of the UART, but we cannot unconditionally set the bit as some other variants require this bit to be clear for other functions. Signed-off-by: Rob Herring Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: linux-serial@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_early.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_early.c b/drivers/tty/serial/8250/8250_early.c index ce2a8abd1f5..c31a22b4f84 100644 --- a/drivers/tty/serial/8250/8250_early.c +++ b/drivers/tty/serial/8250/8250_early.c @@ -93,10 +93,10 @@ static void __init early_serial8250_write(struct console *console, struct uart_port *port = &early_device->port; unsigned int ier; - /* Save the IER and disable interrupts */ + /* Save the IER and disable interrupts preserving the UUE bit */ ier = serial8250_early_in(port, UART_IER); if (ier) - serial8250_early_out(port, UART_IER, 0); + serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); uart_console_write(port, s, count, serial_putc); @@ -127,9 +127,11 @@ static void __init init_port(struct earlycon_device *device) struct uart_port *port = &device->port; unsigned int divisor; unsigned char c; + unsigned int ier; serial8250_early_out(port, UART_LCR, 0x3); /* 8n1 */ - serial8250_early_out(port, UART_IER, 0); /* no interrupt */ + ier = serial8250_early_in(port, UART_IER); + serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); /* no interrupt */ serial8250_early_out(port, UART_FCR, 0); /* no fifo */ serial8250_early_out(port, UART_MCR, 0x3); /* DTR + RTS */ -- cgit v1.2.3-70-g09d2 From 391f93f2ec9f857c83bdd21a14dcf7e699f38579 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sun, 25 Jan 2015 14:44:51 -0500 Subject: serial: core: Rework hw-assisted flow control support hw-assisted flow control support was added to the serial core in v3.8 with commits, dba05832cbe4f ("SERIAL: core: add hardware assisted h/w flow control support") 2cbacafd7af0f ("SERIAL: core: add hardware assisted s/w flow control support") 9aba8d5b01119 ("SERIAL: core: add throttle/unthrottle callbacks for hardware assisted flow control") Since then, additional requirements for serial core support have arisen. Specifically, 1. Separate tx and rx flow control settings for UARTs which only support tx flow control (ie., autoCTS). 2. Disable sw-assisted CTS flow control in autoCTS mode 3. Support for RTS flow control by serial core and userspace in autoRTS mode Distinguish mode from capability; introduce UPSTAT_AUTORTS, UPSTAT_AUTOCTS and UPSTAT_AUTOXOFF which, when set by the uart driver, enable serial core support for hw-assisted rx, hw-assisted tx and hw-assisted in-band/IXOFF rx flow control, respectively. [Note: hw-assisted in-band/IXON tx flow control does not require serial core support/intervention and can be enabled by the uart driver when required.] These modes must be set/reset in the driver's set_termios() method, based on termios settings, and thus can be safely queried in any context in which one of the port lock, port mutex or termios rwsem are held. Set these modes in the 2 in-tree drivers, omap-serial and 8250_omap, which currently use UPF_HARD_FLOW/UPF_SOFT_FLOW support. Retain UPF_HARD_FLOW and UPF_SOFT_FLOW as capabilities; re-define UPF_HARD_FLOW as both UPF_AUTO_RTS and UPF_AUTO_CTS to allow for distinct and separate rx and tx flow control capabilities. Disable sw-assisted CTS flow control when UPSTAT_AUTOCTS is enabled. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 7 +++- drivers/tty/serial/omap-serial.c | 7 +++- drivers/tty/serial/serial_core.c | 76 ++++++++++++++----------------------- include/linux/serial_core.h | 21 ++++++++-- 4 files changed, 59 insertions(+), 52 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 9191d05cfaf..6e4ff7148ea 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -421,8 +421,11 @@ static void omap_8250_set_termios(struct uart_port *port, priv->efr = 0; up->mcr &= ~(UART_MCR_RTS | UART_MCR_XONANY); + up->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS | UPSTAT_AUTOXOFF); + if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) { /* Enable AUTORTS and AUTOCTS */ + up->port.status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; priv->efr |= UART_EFR_CTS | UART_EFR_RTS; } else if (up->port.flags & UPF_SOFT_FLOW) { /* @@ -438,8 +441,10 @@ static void omap_8250_set_termios(struct uart_port *port, * Enable XON/XOFF flow control on output. * Transmit XON1, XOFF1 */ - if (termios->c_iflag & IXOFF) + if (termios->c_iflag & IXOFF) { + up->port.status |= UPSTAT_AUTOXOFF; priv->efr |= OMAP_UART_SW_TX; + } /* * IXANY Flag: diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c index b1cf9a3b673..6129fe51593 100644 --- a/drivers/tty/serial/omap-serial.c +++ b/drivers/tty/serial/omap-serial.c @@ -1053,8 +1053,11 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); + up->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS | UPSTAT_AUTOXOFF); + if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) { /* Enable AUTORTS and AUTOCTS */ + up->port.status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; up->efr |= UART_EFR_CTS | UART_EFR_RTS; /* Ensure MCR RTS is asserted */ @@ -1081,8 +1084,10 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, * Enable XON/XOFF flow control on output. * Transmit XON1, XOFF1 */ - if (termios->c_iflag & IXOFF) + if (termios->c_iflag & IXOFF) { + up->port.status |= UPSTAT_AUTOXOFF; up->efr |= OMAP_UART_SW_TX; + } /* * IXANY Flag: diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 2c67a077042..6a1055ae343 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -179,14 +179,6 @@ static int uart_port_startup(struct tty_struct *tty, struct uart_state *state, if (tty->termios.c_cflag & CBAUD) uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR); } - - spin_lock_irq(&uport->lock); - if (uart_cts_enabled(uport) && - !(uport->ops->get_mctrl(uport) & TIOCM_CTS)) - uport->hw_stopped = 1; - else - uport->hw_stopped = 0; - spin_unlock_irq(&uport->lock); } /* @@ -442,6 +434,7 @@ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, { struct uart_port *uport = state->uart_port; struct ktermios *termios; + int hw_stopped; /* * If we have no tty, termios, or the port does not exist, @@ -466,6 +459,18 @@ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state, uport->status &= ~UPSTAT_DCD_ENABLE; else uport->status |= UPSTAT_DCD_ENABLE; + + /* reset sw-assisted CTS flow control based on (possibly) new mode */ + hw_stopped = uport->hw_stopped; + uport->hw_stopped = uart_softcts_mode(uport) && + !(uport->ops->get_mctrl(uport) & TIOCM_CTS); + if (uport->hw_stopped) { + if (!hw_stopped) + uport->ops->stop_tx(uport); + } else { + if (hw_stopped) + __uart_start(tty); + } spin_unlock_irq(&uport->lock); } @@ -619,22 +624,22 @@ static void uart_throttle(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; struct uart_port *port = state->uart_port; - upf_t mask = 0; + upstat_t mask = 0; if (I_IXOFF(tty)) - mask |= UPF_SOFT_FLOW; + mask |= UPSTAT_AUTOXOFF; if (tty->termios.c_cflag & CRTSCTS) - mask |= UPF_HARD_FLOW; + mask |= UPSTAT_AUTORTS; - if (port->flags & mask) { + if (port->status & mask) { port->ops->throttle(port); - mask &= ~port->flags; + mask &= ~port->status; } - if (mask & UPF_SOFT_FLOW) + if (mask & UPSTAT_AUTOXOFF) uart_send_xchar(tty, STOP_CHAR(tty)); - if (mask & UPF_HARD_FLOW) + if (mask & UPSTAT_AUTORTS) uart_clear_mctrl(port, TIOCM_RTS); } @@ -642,22 +647,22 @@ static void uart_unthrottle(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; struct uart_port *port = state->uart_port; - upf_t mask = 0; + upstat_t mask = 0; if (I_IXOFF(tty)) - mask |= UPF_SOFT_FLOW; + mask |= UPSTAT_AUTOXOFF; if (tty->termios.c_cflag & CRTSCTS) - mask |= UPF_HARD_FLOW; + mask |= UPSTAT_AUTORTS; - if (port->flags & mask) { + if (port->status & mask) { port->ops->unthrottle(port); - mask &= ~port->flags; + mask &= ~port->status; } - if (mask & UPF_SOFT_FLOW) + if (mask & UPSTAT_AUTOXOFF) uart_send_xchar(tty, START_CHAR(tty)); - if (mask & UPF_HARD_FLOW) + if (mask & UPSTAT_AUTORTS) uart_set_mctrl(port, TIOCM_RTS); } @@ -1351,30 +1356,6 @@ static void uart_set_termios(struct tty_struct *tty, mask |= TIOCM_RTS; uart_set_mctrl(uport, mask); } - - /* - * If the port is doing h/w assisted flow control, do nothing. - * We assume that port->hw_stopped has never been set. - */ - if (uport->flags & UPF_HARD_FLOW) - return; - - /* Handle turning off CRTSCTS */ - if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { - spin_lock_irq(&uport->lock); - uport->hw_stopped = 0; - __uart_start(tty); - spin_unlock_irq(&uport->lock); - } - /* Handle turning on CRTSCTS */ - else if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { - spin_lock_irq(&uport->lock); - if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS)) { - uport->hw_stopped = 1; - uport->ops->stop_tx(uport); - } - spin_unlock_irq(&uport->lock); - } } /* @@ -2855,7 +2836,7 @@ void uart_handle_cts_change(struct uart_port *uport, unsigned int status) uport->icount.cts++; - if (uart_cts_enabled(uport)) { + if (uart_softcts_mode(uport)) { if (uport->hw_stopped) { if (status) { uport->hw_stopped = 0; @@ -2868,6 +2849,7 @@ void uart_handle_cts_change(struct uart_port *uport, unsigned int status) uport->ops->stop_tx(uport); } } + } } EXPORT_SYMBOL_GPL(uart_handle_cts_change); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index a0c7033d5f9..baf3e1d0841 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -191,8 +191,10 @@ struct uart_port { #define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15)) #define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ ) -/* Port has hardware-assisted h/w flow control (iow, auto-RTS *not* auto-CTS) */ -#define UPF_HARD_FLOW ((__force upf_t) (1 << 21)) +/* Port has hardware-assisted h/w flow control */ +#define UPF_AUTO_CTS ((__force upf_t) (1 << 20)) +#define UPF_AUTO_RTS ((__force upf_t) (1 << 21)) +#define UPF_HARD_FLOW ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS)) /* Port has hardware-assisted s/w flow control */ #define UPF_SOFT_FLOW ((__force upf_t) (1 << 22)) #define UPF_CONS_FLOW ((__force upf_t) (1 << 23)) @@ -214,11 +216,17 @@ struct uart_port { #error Change mask not equivalent to userspace-visible bit defines #endif - /* status must be updated while holding port lock */ + /* + * Must hold termios_rwsem, port mutex and port lock to change; + * can hold any one lock to read. + */ upstat_t status; #define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) #define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1)) +#define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2)) +#define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3)) +#define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4)) int hw_stopped; /* sw-assisted CTS flow state */ unsigned int mctrl; /* current modem ctrl settings */ @@ -392,6 +400,13 @@ static inline bool uart_cts_enabled(struct uart_port *uport) return !!(uport->status & UPSTAT_CTS_ENABLE); } +static inline bool uart_softcts_mode(struct uart_port *uport) +{ + upstat_t mask = UPSTAT_CTS_ENABLE | UPSTAT_AUTOCTS; + + return ((uport->status & mask) == UPSTAT_CTS_ENABLE); +} + /* * The following are helper functions for the low level drivers. */ -- cgit v1.2.3-70-g09d2 From 9719acce37803d179fc94884c8d50b883fc8186e Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sun, 25 Jan 2015 14:44:52 -0500 Subject: serial: 8250_omap: Use UPSTAT_AUTORTS for RTS handling Commit 88838d3112702 ("serial: omap_8250: Fix RTS handling") fixed RTS pin control when in autoRTS mode. New support added in "serial: core: Rework hw-assisted flow control support" enables a much simpler approach; rather than masking out autoRTS whenever writing the EFR register, use the UPSTAT_* mode to determine if autoRTS should be enabled when raising RTS (in omap8250_set_mctrl()). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 6e4ff7148ea..fe6d2e51da0 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -120,10 +120,11 @@ static void omap8250_set_mctrl(struct uart_port *port, unsigned int mctrl) */ lcr = serial_in(up, UART_LCR); serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); - if (mctrl & TIOCM_RTS) - serial_out(up, UART_EFR, priv->efr); + if ((mctrl & TIOCM_RTS) && (port->status & UPSTAT_AUTORTS)) + priv->efr |= UART_EFR_RTS; else - serial_out(up, UART_EFR, priv->efr & ~UART_EFR_RTS); + priv->efr &= ~UART_EFR_RTS; + serial_out(up, UART_EFR, priv->efr); serial_out(up, UART_LCR, lcr); } @@ -272,10 +273,7 @@ static void omap8250_restore_regs(struct uart_8250_port *up) serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); serial_dl_write(up, priv->quot); - if (up->port.mctrl & TIOCM_RTS) - serial_out(up, UART_EFR, priv->efr); - else - serial_out(up, UART_EFR, priv->efr & ~UART_EFR_RTS); + serial_out(up, UART_EFR, priv->efr); /* Configure flow control */ serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); @@ -424,9 +422,9 @@ static void omap_8250_set_termios(struct uart_port *port, up->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS | UPSTAT_AUTOXOFF); if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) { - /* Enable AUTORTS and AUTOCTS */ + /* Enable AUTOCTS (autoRTS is enabled when RTS is raised) */ up->port.status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; - priv->efr |= UART_EFR_CTS | UART_EFR_RTS; + priv->efr |= UART_EFR_CTS; } else if (up->port.flags & UPF_SOFT_FLOW) { /* * IXON Flag: -- cgit v1.2.3-70-g09d2 From 348f9bb31c56909f9098a598b6ecdc94a560816e Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sun, 25 Jan 2015 14:44:53 -0500 Subject: serial: omap: Fix RTS handling The OMAP UART ignores MCR[1] (ie., RTS) when in autoRTS mode. This makes it impossible for either the serial core or userspace to manually flow control the sender. Disable autoRTS mode when RTS is lowered and restore the previous mode when RTS is raised. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/omap-serial.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c index 6129fe51593..10256fa04b4 100644 --- a/drivers/tty/serial/omap-serial.c +++ b/drivers/tty/serial/omap-serial.c @@ -681,7 +681,7 @@ static unsigned int serial_omap_get_mctrl(struct uart_port *port) static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl) { struct uart_omap_port *up = to_uart_omap_port(port); - unsigned char mcr = 0, old_mcr; + unsigned char mcr = 0, old_mcr, lcr; dev_dbg(up->port.dev, "serial_omap_set_mctrl+%d\n", up->port.line); if (mctrl & TIOCM_RTS) @@ -701,6 +701,17 @@ static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl) UART_MCR_DTR | UART_MCR_RTS); up->mcr = old_mcr | mcr; serial_out(up, UART_MCR, up->mcr); + + /* Turn off autoRTS if RTS is lowered; restore autoRTS if RTS raised */ + lcr = serial_in(up, UART_LCR); + serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); + if ((mctrl & TIOCM_RTS) && (port->status & UPSTAT_AUTORTS)) + up->efr |= UART_EFR_RTS; + else + up->efr &= UART_EFR_RTS; + serial_out(up, UART_EFR, up->efr); + serial_out(up, UART_LCR, lcr); + pm_runtime_mark_last_busy(up->dev); pm_runtime_put_autosuspend(up->dev); } @@ -756,8 +767,6 @@ static int serial_omap_startup(struct uart_port *port) * (they will be reenabled in set_termios()) */ serial_omap_clear_fifos(up); - /* For Hardware flow control */ - serial_out(up, UART_MCR, UART_MCR_RTS); /* * Clear the interrupt registers. @@ -1056,12 +1065,9 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, up->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS | UPSTAT_AUTOXOFF); if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) { - /* Enable AUTORTS and AUTOCTS */ + /* Enable AUTOCTS (autoRTS is enabled when RTS is raised) */ up->port.status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; - up->efr |= UART_EFR_CTS | UART_EFR_RTS; - - /* Ensure MCR RTS is asserted */ - up->mcr |= UART_MCR_RTS; + up->efr |= UART_EFR_CTS; } else { /* Disable AUTORTS and AUTOCTS */ up->efr &= ~(UART_EFR_CTS | UART_EFR_RTS); -- cgit v1.2.3-70-g09d2 From 632f32e2107d37598e3f6816dcf00c7cab4081ca Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sun, 25 Jan 2015 14:44:54 -0500 Subject: tty: Remove external interface for tty_set_termios() tty_set_termios() is an internal helper intended for file scope use. UART drivers which are capable of driving the RTS pin must properly handle the tiocmset() method, regardless of termios settings. A failure to do so is a UART driver bug and should be fixed there. Do not use this interface to workaround UART driver bugs. Cc: Johan Hedberg Cc: Signed-off-by: Peter Hurley Acked-by: Marcel Holtmann Signed-off-by: Greg Kroah-Hartman --- drivers/bluetooth/hci_ath.c | 15 ++------------- drivers/tty/tty_ioctl.c | 3 +-- include/linux/tty.h | 1 - 3 files changed, 3 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/bluetooth/hci_ath.c b/drivers/bluetooth/hci_ath.c index 2ff6dfd2d3f..9c4dcf4c62e 100644 --- a/drivers/bluetooth/hci_ath.c +++ b/drivers/bluetooth/hci_ath.c @@ -51,33 +51,22 @@ struct ath_struct { static int ath_wakeup_ar3k(struct tty_struct *tty) { - struct ktermios ktermios; int status = tty->driver->ops->tiocmget(tty); if (status & TIOCM_CTS) return status; - /* Disable Automatic RTSCTS */ - ktermios = tty->termios; - ktermios.c_cflag &= ~CRTSCTS; - tty_set_termios(tty, &ktermios); - /* Clear RTS first */ - status = tty->driver->ops->tiocmget(tty); + tty->driver->ops->tiocmget(tty); tty->driver->ops->tiocmset(tty, 0x00, TIOCM_RTS); mdelay(20); /* Set RTS, wake up board */ - status = tty->driver->ops->tiocmget(tty); + tty->driver->ops->tiocmget(tty); tty->driver->ops->tiocmset(tty, TIOCM_RTS, 0x00); mdelay(20); status = tty->driver->ops->tiocmget(tty); - - /* Enable Automatic RTSCTS */ - ktermios.c_cflag |= CRTSCTS; - status = tty_set_termios(tty, &ktermios); - return status; } diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index 1787fa4d944..a5cf253b254 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -530,7 +530,7 @@ EXPORT_SYMBOL(tty_termios_hw_change); * Locking: termios_rwsem */ -int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) +static int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) { struct ktermios old_termios; struct tty_ldisc *ld; @@ -563,7 +563,6 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) up_write(&tty->termios_rwsem); return 0; } -EXPORT_SYMBOL_GPL(tty_set_termios); /** * set_termios - set termios values for a tty diff --git a/include/linux/tty.h b/include/linux/tty.h index fe5623c9af7..358a337af59 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -491,7 +491,6 @@ static inline speed_t tty_get_baud_rate(struct tty_struct *tty) extern void tty_termios_copy_hw(struct ktermios *new, struct ktermios *old); extern int tty_termios_hw_change(struct ktermios *a, struct ktermios *b); -extern int tty_set_termios(struct tty_struct *tty, struct ktermios *kt); extern struct tty_ldisc *tty_ldisc_ref(struct tty_struct *); extern void tty_ldisc_deref(struct tty_ldisc *); -- cgit v1.2.3-70-g09d2 From ba019a3e2ad53dcc56f3c0b15e1061b77129b63d Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 28 Jan 2015 14:44:23 +0100 Subject: serial: samsung: remove redundant interrupt enabling Function s3c24xx_serial_start_tx_pio() enables interrupts if needed, so we don't have to (or even we shouldn't) enable them before. Signed-off-by: Robert Baldyga Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index a6bef8dcdc3..236abae9761 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -358,15 +358,8 @@ void s3c24xx_serial_start_tx(struct uart_port *port) s3c24xx_serial_rx_disable(port); tx_enabled(port) = 1; - if (!ourport->dma || !ourport->dma->tx_chan) { - if (s3c24xx_serial_has_interrupt_mask(port)) - __clear_bit(S3C64XX_UINTM_TXD, - portaddrl(port, S3C64XX_UINTM)); - else - enable_irq(ourport->tx_irq); - + if (!ourport->dma || !ourport->dma->tx_chan) s3c24xx_serial_start_tx_pio(ourport); - } } if (ourport->dma && ourport->dma->tx_chan) { -- cgit v1.2.3-70-g09d2 From b7396a38fb28db4ebbbf35da1057eb5206b4ad6c Mon Sep 17 00:00:00 2001 From: Chunyan Zhang Date: Wed, 28 Jan 2015 19:08:44 +0800 Subject: tty/serial: Add Spreadtrum sc9836-uart driver support Add a full sc9836-uart driver for SC9836 SoC which is based on the spreadtrum sharkl64 platform. This driver also support earlycon. Originally-by: Lanqing Liu Signed-off-by: Orson Zhai Signed-off-by: Chunyan Zhang Acked-by: Arnd Bergmann Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 18 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/sprd_serial.c | 793 +++++++++++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 815 insertions(+) create mode 100644 drivers/tty/serial/sprd_serial.c (limited to 'drivers') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index c63317e87c3..ddcc0a4c659 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1583,6 +1583,24 @@ config SERIAL_MEN_Z135 This driver can also be build as a module. If so, the module will be called men_z135_uart.ko +config SERIAL_SPRD + tristate "Support for Spreadtrum serial" + depends on ARCH_SPRD + select SERIAL_CORE + help + This enables the driver for the Spreadtrum's serial. + +config SERIAL_SPRD_CONSOLE + bool "Spreadtrum UART console support" + depends on SERIAL_SPRD=y + select SERIAL_CORE_CONSOLE + select SERIAL_EARLYCON + help + Support for early debug console using Spreadtrum's serial. This enables + the console before standard serial driver is probed. This is enabled + with "earlycon" on the kernel command line. The console is + enabled when early_param is processed. + endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index a1c1085ef75..431879003cc 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_SERIAL_RP2) += rp2.o obj-$(CONFIG_SERIAL_FSL_LPUART) += fsl_lpuart.o obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o +obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o # GPIOLIB helpers for modem control lines obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c new file mode 100644 index 00000000000..594b63331ef --- /dev/null +++ b/drivers/tty/serial/sprd_serial.c @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2012-2015 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#if defined(CONFIG_SERIAL_SPRD_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* device name */ +#define UART_NR_MAX 8 +#define SPRD_TTY_NAME "ttyS" +#define SPRD_FIFO_SIZE 128 +#define SPRD_DEF_RATE 26000000 +#define SPRD_BAUD_IO_LIMIT 3000000 +#define SPRD_TIMEOUT 256 + +/* the offset of serial registers and BITs for them */ +/* data registers */ +#define SPRD_TXD 0x0000 +#define SPRD_RXD 0x0004 + +/* line status register and its BITs */ +#define SPRD_LSR 0x0008 +#define SPRD_LSR_OE BIT(4) +#define SPRD_LSR_FE BIT(3) +#define SPRD_LSR_PE BIT(2) +#define SPRD_LSR_BI BIT(7) +#define SPRD_LSR_TX_OVER BIT(15) + +/* data number in TX and RX fifo */ +#define SPRD_STS1 0x000C + +/* interrupt enable register and its BITs */ +#define SPRD_IEN 0x0010 +#define SPRD_IEN_RX_FULL BIT(0) +#define SPRD_IEN_TX_EMPTY BIT(1) +#define SPRD_IEN_BREAK_DETECT BIT(7) +#define SPRD_IEN_TIMEOUT BIT(13) + +/* interrupt clear register */ +#define SPRD_ICLR 0x0014 + +/* line control register */ +#define SPRD_LCR 0x0018 +#define SPRD_LCR_STOP_1BIT 0x10 +#define SPRD_LCR_STOP_2BIT 0x30 +#define SPRD_LCR_DATA_LEN (BIT(2) | BIT(3)) +#define SPRD_LCR_DATA_LEN5 0x0 +#define SPRD_LCR_DATA_LEN6 0x4 +#define SPRD_LCR_DATA_LEN7 0x8 +#define SPRD_LCR_DATA_LEN8 0xc +#define SPRD_LCR_PARITY (BIT(0) | BIT(1)) +#define SPRD_LCR_PARITY_EN 0x2 +#define SPRD_LCR_EVEN_PAR 0x0 +#define SPRD_LCR_ODD_PAR 0x1 + +/* control register 1 */ +#define SPRD_CTL1 0x001C +#define RX_HW_FLOW_CTL_THLD BIT(6) +#define RX_HW_FLOW_CTL_EN BIT(7) +#define TX_HW_FLOW_CTL_EN BIT(8) +#define RX_TOUT_THLD_DEF 0x3E00 +#define RX_HFC_THLD_DEF 0x40 + +/* fifo threshold register */ +#define SPRD_CTL2 0x0020 +#define THLD_TX_EMPTY 0x40 +#define THLD_RX_FULL 0x40 + +/* config baud rate register */ +#define SPRD_CLKD0 0x0024 +#define SPRD_CLKD1 0x0028 + +/* interrupt mask status register */ +#define SPRD_IMSR 0x002C +#define SPRD_IMSR_RX_FIFO_FULL BIT(0) +#define SPRD_IMSR_TX_FIFO_EMPTY BIT(1) +#define SPRD_IMSR_BREAK_DETECT BIT(7) +#define SPRD_IMSR_TIMEOUT BIT(13) + +struct reg_backup { + u32 ien; + u32 ctrl0; + u32 ctrl1; + u32 ctrl2; + u32 clkd0; + u32 clkd1; + u32 dspwait; +}; + +struct sprd_uart_port { + struct uart_port port; + struct reg_backup reg_bak; + char name[16]; +}; + +static struct sprd_uart_port *sprd_port[UART_NR_MAX]; +static int sprd_ports_num; + +static inline unsigned int serial_in(struct uart_port *port, int offset) +{ + return readl_relaxed(port->membase + offset); +} + +static inline void serial_out(struct uart_port *port, int offset, int value) +{ + writel_relaxed(value, port->membase + offset); +} + +static unsigned int sprd_tx_empty(struct uart_port *port) +{ + if (serial_in(port, SPRD_STS1) & 0xff00) + return 0; + else + return TIOCSER_TEMT; +} + +static unsigned int sprd_get_mctrl(struct uart_port *port) +{ + return TIOCM_DSR | TIOCM_CTS; +} + +static void sprd_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* nothing to do */ +} + +static void sprd_stop_tx(struct uart_port *port) +{ + unsigned int ien, iclr; + + iclr = serial_in(port, SPRD_ICLR); + ien = serial_in(port, SPRD_IEN); + + iclr |= SPRD_IEN_TX_EMPTY; + ien &= ~SPRD_IEN_TX_EMPTY; + + serial_out(port, SPRD_ICLR, iclr); + serial_out(port, SPRD_IEN, ien); +} + +static void sprd_start_tx(struct uart_port *port) +{ + unsigned int ien; + + ien = serial_in(port, SPRD_IEN); + if (!(ien & SPRD_IEN_TX_EMPTY)) { + ien |= SPRD_IEN_TX_EMPTY; + serial_out(port, SPRD_IEN, ien); + } +} + +static void sprd_stop_rx(struct uart_port *port) +{ + unsigned int ien, iclr; + + iclr = serial_in(port, SPRD_ICLR); + ien = serial_in(port, SPRD_IEN); + + ien &= ~(SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT); + iclr |= SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT; + + serial_out(port, SPRD_IEN, ien); + serial_out(port, SPRD_ICLR, iclr); +} + +/* The Sprd serial does not support this function. */ +static void sprd_break_ctl(struct uart_port *port, int break_state) +{ + /* nothing to do */ +} + +static int handle_lsr_errors(struct uart_port *port, + unsigned int *flag, + unsigned int *lsr) +{ + int ret = 0; + + /* statistics */ + if (*lsr & SPRD_LSR_BI) { + *lsr &= ~(SPRD_LSR_FE | SPRD_LSR_PE); + port->icount.brk++; + ret = uart_handle_break(port); + if (ret) + return ret; + } else if (*lsr & SPRD_LSR_PE) + port->icount.parity++; + else if (*lsr & SPRD_LSR_FE) + port->icount.frame++; + if (*lsr & SPRD_LSR_OE) + port->icount.overrun++; + + /* mask off conditions which should be ignored */ + *lsr &= port->read_status_mask; + if (*lsr & SPRD_LSR_BI) + *flag = TTY_BREAK; + else if (*lsr & SPRD_LSR_PE) + *flag = TTY_PARITY; + else if (*lsr & SPRD_LSR_FE) + *flag = TTY_FRAME; + + return ret; +} + +static inline void sprd_rx(struct uart_port *port) +{ + struct tty_port *tty = &port->state->port; + unsigned int ch, flag, lsr, max_count = SPRD_TIMEOUT; + + while ((serial_in(port, SPRD_STS1) & 0x00ff) && max_count--) { + lsr = serial_in(port, SPRD_LSR); + ch = serial_in(port, SPRD_RXD); + flag = TTY_NORMAL; + port->icount.rx++; + + if (lsr & (SPRD_LSR_BI | SPRD_LSR_PE | + SPRD_LSR_FE | SPRD_LSR_OE)) + if (handle_lsr_errors(port, &lsr, &flag)) + continue; + if (uart_handle_sysrq_char(port, ch)) + continue; + + uart_insert_char(port, lsr, SPRD_LSR_OE, ch, flag); + } + + tty_flip_buffer_push(tty); +} + +static inline void sprd_tx(struct uart_port *port) +{ + struct circ_buf *xmit = &port->state->xmit; + int count; + + if (port->x_char) { + serial_out(port, SPRD_TXD, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + sprd_stop_tx(port); + return; + } + + count = THLD_TX_EMPTY; + do { + serial_out(port, SPRD_TXD, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (uart_circ_empty(xmit)) + break; + } while (--count > 0); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + sprd_stop_tx(port); +} + +/* this handles the interrupt from one port */ +static irqreturn_t sprd_handle_irq(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + unsigned int ims; + + spin_lock(&port->lock); + + ims = serial_in(port, SPRD_IMSR); + + if (!ims) + return IRQ_NONE; + + serial_out(port, SPRD_ICLR, ~0); + + if (ims & (SPRD_IMSR_RX_FIFO_FULL | + SPRD_IMSR_BREAK_DETECT | SPRD_IMSR_TIMEOUT)) + sprd_rx(port); + + if (ims & SPRD_IMSR_TX_FIFO_EMPTY) + sprd_tx(port); + + spin_unlock(&port->lock); + + return IRQ_HANDLED; +} + +static int sprd_startup(struct uart_port *port) +{ + int ret = 0; + unsigned int ien, fc; + unsigned int timeout; + struct sprd_uart_port *sp; + unsigned long flags; + + serial_out(port, SPRD_CTL2, ((THLD_TX_EMPTY << 8) | THLD_RX_FULL)); + + /* clear rx fifo */ + timeout = SPRD_TIMEOUT; + while (timeout-- && serial_in(port, SPRD_STS1) & 0x00ff) + serial_in(port, SPRD_RXD); + + /* clear tx fifo */ + timeout = SPRD_TIMEOUT; + while (timeout-- && serial_in(port, SPRD_STS1) & 0xff00) + cpu_relax(); + + /* clear interrupt */ + serial_out(port, SPRD_IEN, 0); + serial_out(port, SPRD_ICLR, ~0); + + /* allocate irq */ + sp = container_of(port, struct sprd_uart_port, port); + snprintf(sp->name, sizeof(sp->name), "sprd_serial%d", port->line); + ret = devm_request_irq(port->dev, port->irq, sprd_handle_irq, + IRQF_SHARED, sp->name, port); + if (ret) { + dev_err(port->dev, "fail to request serial irq %d, ret=%d\n", + port->irq, ret); + return ret; + } + fc = serial_in(port, SPRD_CTL1); + fc |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF; + serial_out(port, SPRD_CTL1, fc); + + /* enable interrupt */ + spin_lock_irqsave(&port->lock, flags); + ien = serial_in(port, SPRD_IEN); + ien |= SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT | SPRD_IEN_TIMEOUT; + serial_out(port, SPRD_IEN, ien); + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void sprd_shutdown(struct uart_port *port) +{ + serial_out(port, SPRD_IEN, 0); + serial_out(port, SPRD_ICLR, ~0); + devm_free_irq(port->dev, port->irq, port); +} + +static void sprd_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned int baud, quot; + unsigned int lcr = 0, fc; + unsigned long flags; + + /* ask the core to calculate the divisor for us */ + baud = uart_get_baud_rate(port, termios, old, 0, SPRD_BAUD_IO_LIMIT); + + quot = (unsigned int)((port->uartclk + baud / 2) / baud); + + /* set data length */ + switch (termios->c_cflag & CSIZE) { + case CS5: + lcr |= SPRD_LCR_DATA_LEN5; + break; + case CS6: + lcr |= SPRD_LCR_DATA_LEN6; + break; + case CS7: + lcr |= SPRD_LCR_DATA_LEN7; + break; + case CS8: + default: + lcr |= SPRD_LCR_DATA_LEN8; + break; + } + + /* calculate stop bits */ + lcr &= ~(SPRD_LCR_STOP_1BIT | SPRD_LCR_STOP_2BIT); + if (termios->c_cflag & CSTOPB) + lcr |= SPRD_LCR_STOP_2BIT; + else + lcr |= SPRD_LCR_STOP_1BIT; + + /* calculate parity */ + lcr &= ~SPRD_LCR_PARITY; + termios->c_cflag &= ~CMSPAR; /* no support mark/space */ + if (termios->c_cflag & PARENB) { + lcr |= SPRD_LCR_PARITY_EN; + if (termios->c_cflag & PARODD) + lcr |= SPRD_LCR_ODD_PAR; + else + lcr |= SPRD_LCR_EVEN_PAR; + } + + spin_lock_irqsave(&port->lock, flags); + + /* update the per-port timeout */ + uart_update_timeout(port, termios->c_cflag, baud); + + port->read_status_mask = SPRD_LSR_OE; + if (termios->c_iflag & INPCK) + port->read_status_mask |= SPRD_LSR_FE | SPRD_LSR_PE; + if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) + port->read_status_mask |= SPRD_LSR_BI; + + /* characters to ignore */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= SPRD_LSR_PE | SPRD_LSR_FE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= SPRD_LSR_BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= SPRD_LSR_OE; + } + + /* flow control */ + fc = serial_in(port, SPRD_CTL1); + fc &= ~(RX_HW_FLOW_CTL_THLD | RX_HW_FLOW_CTL_EN | TX_HW_FLOW_CTL_EN); + if (termios->c_cflag & CRTSCTS) { + fc |= RX_HW_FLOW_CTL_THLD; + fc |= RX_HW_FLOW_CTL_EN; + fc |= TX_HW_FLOW_CTL_EN; + } + + /* clock divider bit0~bit15 */ + serial_out(port, SPRD_CLKD0, quot & 0xffff); + + /* clock divider bit16~bit20 */ + serial_out(port, SPRD_CLKD1, (quot & 0x1f0000) >> 16); + serial_out(port, SPRD_LCR, lcr); + fc |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF; + serial_out(port, SPRD_CTL1, fc); + + spin_unlock_irqrestore(&port->lock, flags); + + /* Don't rewrite B0 */ + if (tty_termios_baud_rate(termios)) + tty_termios_encode_baud_rate(termios, baud, baud); +} + +static const char *sprd_type(struct uart_port *port) +{ + return "SPX"; +} + +static void sprd_release_port(struct uart_port *port) +{ + /* nothing to do */ +} + +static int sprd_request_port(struct uart_port *port) +{ + return 0; +} + +static void sprd_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_SPRD; +} + +static int sprd_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (ser->type != PORT_SPRD) + return -EINVAL; + if (port->irq != ser->irq) + return -EINVAL; + return 0; +} + +static struct uart_ops serial_sprd_ops = { + .tx_empty = sprd_tx_empty, + .get_mctrl = sprd_get_mctrl, + .set_mctrl = sprd_set_mctrl, + .stop_tx = sprd_stop_tx, + .start_tx = sprd_start_tx, + .stop_rx = sprd_stop_rx, + .break_ctl = sprd_break_ctl, + .startup = sprd_startup, + .shutdown = sprd_shutdown, + .set_termios = sprd_set_termios, + .type = sprd_type, + .release_port = sprd_release_port, + .request_port = sprd_request_port, + .config_port = sprd_config_port, + .verify_port = sprd_verify_port, +}; + +#ifdef CONFIG_SERIAL_SPRD_CONSOLE +static inline void wait_for_xmitr(struct uart_port *port) +{ + unsigned int status, tmout = 10000; + + /* wait up to 10ms for the character(s) to be sent */ + do { + status = serial_in(port, SPRD_STS1); + if (--tmout == 0) + break; + udelay(1); + } while (status & 0xff00); +} + +static void sprd_console_putchar(struct uart_port *port, int ch) +{ + wait_for_xmitr(port); + serial_out(port, SPRD_TXD, ch); +} + +static void sprd_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct uart_port *port = &sprd_port[co->index]->port; + int locked = 1; + unsigned long flags; + + if (port->sysrq) + locked = 0; + else if (oops_in_progress) + locked = spin_trylock_irqsave(&port->lock, flags); + else + spin_lock_irqsave(&port->lock, flags); + + uart_console_write(port, s, count, sprd_console_putchar); + + /* wait for transmitter to become empty */ + wait_for_xmitr(port); + + if (locked) + spin_unlock_irqrestore(&port->lock, flags); +} + +static int __init sprd_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + if (co->index >= UART_NR_MAX || co->index < 0) + co->index = 0; + + port = &sprd_port[co->index]->port; + if (port == NULL) { + pr_info("serial port %d not yet initialized\n", co->index); + return -ENODEV; + } + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static struct uart_driver sprd_uart_driver; +static struct console sprd_console = { + .name = SPRD_TTY_NAME, + .write = sprd_console_write, + .device = uart_console_device, + .setup = sprd_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &sprd_uart_driver, +}; + +#define SPRD_CONSOLE (&sprd_console) + +/* Support for earlycon */ +static void sprd_putc(struct uart_port *port, int c) +{ + unsigned int timeout = SPRD_TIMEOUT; + + while (timeout-- && + !(readl(port->membase + SPRD_LSR) & SPRD_LSR_TX_OVER)) + cpu_relax(); + + writeb(c, port->membase + SPRD_TXD); +} + +static void sprd_early_write(struct console *con, const char *s, + unsigned n) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, s, n, sprd_putc); +} + +static int __init sprd_early_console_setup( + struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = sprd_early_write; + return 0; +} + +EARLYCON_DECLARE(sprd_serial, sprd_early_console_setup); +OF_EARLYCON_DECLARE(sprd_serial, "sprd,sc9836-uart", + sprd_early_console_setup); + +#else /* !CONFIG_SERIAL_SPRD_CONSOLE */ +#define SPRD_CONSOLE NULL +#endif + +static struct uart_driver sprd_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "sprd_serial", + .dev_name = SPRD_TTY_NAME, + .major = 0, + .minor = 0, + .nr = UART_NR_MAX, + .cons = SPRD_CONSOLE, +}; + +static int sprd_probe_dt_alias(int index, struct device *dev) +{ + struct device_node *np; + int ret = index; + + if (!IS_ENABLED(CONFIG_OF)) + return ret; + + np = dev->of_node; + if (!np) + return ret; + + ret = of_alias_get_id(np, "serial"); + if (IS_ERR_VALUE(ret)) + ret = index; + else if (ret >= ARRAY_SIZE(sprd_port) || sprd_port[ret] != NULL) { + dev_warn(dev, "requested serial port %d not available.\n", ret); + ret = index; + } + + return ret; +} + +static int sprd_remove(struct platform_device *dev) +{ + struct sprd_uart_port *sup = platform_get_drvdata(dev); + + if (sup) { + uart_remove_one_port(&sprd_uart_driver, &sup->port); + sprd_port[sup->port.line] = NULL; + sprd_ports_num--; + } + + if (!sprd_ports_num) + uart_unregister_driver(&sprd_uart_driver); + + return 0; +} + +static int sprd_probe(struct platform_device *pdev) +{ + struct resource *res; + struct uart_port *up; + struct clk *clk; + int irq; + int index; + int ret; + + for (index = 0; index < ARRAY_SIZE(sprd_port); index++) + if (sprd_port[index] == NULL) + break; + + if (index == ARRAY_SIZE(sprd_port)) + return -EBUSY; + + index = sprd_probe_dt_alias(index, &pdev->dev); + + sprd_port[index] = devm_kzalloc(&pdev->dev, + sizeof(*sprd_port[index]), GFP_KERNEL); + if (!sprd_port[index]) + return -ENOMEM; + + up = &sprd_port[index]->port; + up->dev = &pdev->dev; + up->line = index; + up->type = PORT_SPRD; + up->iotype = SERIAL_IO_PORT; + up->uartclk = SPRD_DEF_RATE; + up->fifosize = SPRD_FIFO_SIZE; + up->ops = &serial_sprd_ops; + up->flags = UPF_BOOT_AUTOCONF; + + clk = devm_clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) + up->uartclk = clk_get_rate(clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "not provide mem resource\n"); + return -ENODEV; + } + up->mapbase = res->start; + up->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(up->membase)) + return PTR_ERR(up->membase); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "not provide irq resource\n"); + return -ENODEV; + } + up->irq = irq; + + if (!sprd_ports_num) { + ret = uart_register_driver(&sprd_uart_driver); + if (ret < 0) { + pr_err("Failed to register SPRD-UART driver\n"); + return ret; + } + } + sprd_ports_num++; + + ret = uart_add_one_port(&sprd_uart_driver, up); + if (ret) { + sprd_port[index] = NULL; + sprd_remove(pdev); + } + + platform_set_drvdata(pdev, up); + + return ret; +} + +static int sprd_suspend(struct device *dev) +{ + struct sprd_uart_port *sup = dev_get_drvdata(dev); + + uart_suspend_port(&sprd_uart_driver, &sup->port); + + return 0; +} + +static int sprd_resume(struct device *dev) +{ + struct sprd_uart_port *sup = dev_get_drvdata(dev); + + uart_resume_port(&sprd_uart_driver, &sup->port); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(sprd_pm_ops, sprd_suspend, sprd_resume); + +static const struct of_device_id serial_ids[] = { + {.compatible = "sprd,sc9836-uart",}, + {} +}; + +static struct platform_driver sprd_platform_driver = { + .probe = sprd_probe, + .remove = sprd_remove, + .driver = { + .name = "sprd_serial", + .of_match_table = of_match_ptr(serial_ids), + .pm = &sprd_pm_ops, + }, +}; + +module_platform_driver(sprd_platform_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Spreadtrum SoC serial driver series"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index ee628c489e4..55da91e763b 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -252,4 +252,7 @@ /* Conexant Digicolor */ #define PORT_DIGICOLOR 110 +/* SPRD SERIAL */ +#define PORT_SPRD 111 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- cgit v1.2.3-70-g09d2 From eb1527a8738bcfe12a42475aeef105914d63615a Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Sat, 31 Jan 2015 08:58:34 +0800 Subject: tty/serial: serial8250_set_divisor() can be static drivers/tty/serial/8250/8250_core.c:2503:6: sparse: symbol 'serial8250_set_divisor' was not declared. Should it be static? Signed-off-by: Fengguang Wu Cc: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 57d13b058a4..46ae9fda68b 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -2500,7 +2500,7 @@ static unsigned char serial8250_compute_lcr(struct uart_8250_port *up, return cval; } -void serial8250_set_divisor(struct uart_port *port, unsigned int baud, +static void serial8250_set_divisor(struct uart_port *port, unsigned int baud, unsigned int quot, unsigned int quot_frac) { struct uart_8250_port *up = up_to_u8250p(port); -- cgit v1.2.3-70-g09d2 From c3bda295eeb002ac708303350d5776c7fe01a127 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 2 Feb 2015 10:47:35 +0100 Subject: serial: samsung: earlycon support depends on CONFIG_SERIAL_SAMSUNG_CONSOLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EarlyCon support depends on serial console infrastructure, so the code implementing it should depend on CONFIG_SERIAL_SAMSUNG_CONSOLE. This patch fixes the following build break: CC [M] drivers/tty/serial/samsung.o drivers/tty/serial/samsung.c:2468:1: error: expected declaration specifiers or ‘...’ before string constant drivers/tty/serial/samsung.c:2468:1: error: expected declaration specifiers or ‘...’ before ‘s3c2410_setup_earlycon’ drivers/tty/serial/samsung.c:2487:1: error: expected declaration specifiers or ‘...’ before string constant drivers/tty/serial/samsung.c:2487:1: error: expected declaration specifiers or ‘...’ before ‘s3c2412_setup_earlycon’ drivers/tty/serial/samsung.c:2488:1: error: expected declaration specifiers or ‘...’ before string constant drivers/tty/serial/samsung.c:2488:1: error: expected declaration specifiers or ‘...’ before ‘s3c2440_setup_earlycon’ drivers/tty/serial/samsung.c:2489:1: error: expected declaration specifiers or ‘...’ before string constant drivers/tty/serial/samsung.c:2489:1: error: expected declaration specifiers or ‘...’ before ‘s3c6400_setup_earlycon’ drivers/tty/serial/samsung.c:2506:1: error: expected declaration specifiers or ‘...’ before string constant drivers/tty/serial/samsung.c:2506:1: error: expected declaration specifiers or ‘...’ before ‘s5pv210_setup_earlycon’ drivers/tty/serial/samsung.c:2507:1: error: expected declaration specifiers or ‘...’ before string constant drivers/tty/serial/samsung.c:2507:1: error: expected declaration specifiers or ‘...’ before ‘exynos4210_setup_earlycon’ drivers/tty/serial/samsung.c:2468:1: warning: ‘s3c2410_setup_earlycon’ defined but not used [-Wunused-function] drivers/tty/serial/samsung.c:2487:1: warning: ‘s3c2412_setup_earlycon’ defined but not used [-Wunused-function] drivers/tty/serial/samsung.c:2488:1: warning: ‘s3c2440_setup_earlycon’ defined but not used [-Wunused-function] drivers/tty/serial/samsung.c:2489:1: warning: ‘s3c6400_setup_earlycon’ defined but not used [-Wunused-function] drivers/tty/serial/samsung.c:2506:1: warning: ‘s5pv210_setup_earlycon’ defined but not used [-Wunused-function] drivers/tty/serial/samsung.c:2507:1: warning: ‘exynos4210_setup_earlycon’ defined but not used [-Wunused-function] make[3]: *** [drivers/tty/serial/samsung.o] Error 1 Reported-by: kbuild test robot Signed-off-by: Marek Szyprowski Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/samsung.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c index 236abae9761..af821a90872 100644 --- a/drivers/tty/serial/samsung.c +++ b/drivers/tty/serial/samsung.c @@ -2396,6 +2396,7 @@ static struct platform_driver samsung_serial_driver = { module_platform_driver(samsung_serial_driver); +#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE /* * Early console. */ @@ -2498,6 +2499,7 @@ OF_EARLYCON_DECLARE(exynos4210, "samsung,exynos4210-uart", s5pv210_early_console_setup); EARLYCON_DECLARE(s5pv210, s5pv210_early_console_setup); EARLYCON_DECLARE(exynos4210, s5pv210_early_console_setup); +#endif MODULE_ALIAS("platform:samsung-uart"); MODULE_DESCRIPTION("Samsung SoC Serial port driver"); -- cgit v1.2.3-70-g09d2 From 3239fd31d4b6b7e0f5629eb458bd67b719050783 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 5 Dec 2014 20:21:57 +0100 Subject: serial: of-serial: fetch line number from DT The general agreed way to specify a fixed line number for a serial console is to provide a "serial" alias in the devicetree. Start parsing this property in of_serial. Signed-off-by: Lucas Stach Acked-by: Olof Johansson Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/of_serial.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/tty/serial/of_serial.c b/drivers/tty/serial/of_serial.c index fbb719c89a6..7ff61e24a19 100644 --- a/drivers/tty/serial/of_serial.c +++ b/drivers/tty/serial/of_serial.c @@ -102,6 +102,11 @@ static int of_platform_serial_setup(struct platform_device *ofdev, if (of_property_read_u32(np, "fifo-size", &prop) == 0) port->fifosize = prop; + /* Check for a fixed line number */ + ret = of_alias_get_id(np, "serial"); + if (ret >= 0) + port->line = ret; + port->irq = irq_of_parse_and_map(np, 0); port->iotype = UPIO_MEM; if (of_property_read_u32(np, "reg-io-width", &prop) == 0) { -- cgit v1.2.3-70-g09d2 From 79ce9d52ae17ff9a29edc2c71638af37f709b9cd Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Sat, 10 Jan 2015 18:08:51 +0100 Subject: tty: remove unused variable sprop sprop is unused in find_console_handle() since commit a752ee56ad84 ("tty: Update hypervisor tty drivers to use core stdout parsing code.") Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman --- drivers/tty/ehv_bytechan.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/ehv_bytechan.c b/drivers/tty/ehv_bytechan.c index 3c60923b095..342b36b9ad3 100644 --- a/drivers/tty/ehv_bytechan.c +++ b/drivers/tty/ehv_bytechan.c @@ -112,7 +112,6 @@ static void disable_tx_interrupt(struct ehv_bc_data *bc) static int find_console_handle(void) { struct device_node *np = of_stdout; - const char *sprop = NULL; const uint32_t *iprop; /* We don't care what the aliased node is actually called. We only -- cgit v1.2.3-70-g09d2 From 692132b5b1c5ce97076915d4aed0c61513e18b03 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 2 Feb 2015 23:19:20 +0100 Subject: serial: driver for ETRAX FS UART This is the last missing piece to get a kernel booting to a prompt in qemu-cris. Signed-off-by: Niklas Cassel Acked-by: Jesper Nilsson Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 10 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/etraxfs-uart.c | 996 ++++++++++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 1010 insertions(+) create mode 100644 drivers/tty/serial/etraxfs-uart.c (limited to 'drivers') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index ddcc0a4c659..5d916c7a216 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1085,6 +1085,16 @@ config SERIAL_VT8500_CONSOLE depends on SERIAL_VT8500=y select SERIAL_CORE_CONSOLE +config SERIAL_ETRAXFS + bool "ETRAX FS serial port support" + depends on ETRAX_ARCH_V32 && OF + select SERIAL_CORE + +config SERIAL_ETRAXFS_CONSOLE + bool "ETRAX FS serial console support" + depends on SERIAL_ETRAXFS + select SERIAL_CORE_CONSOLE + config SERIAL_NETX tristate "NetX serial port support" depends on ARCH_NETX diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 431879003cc..599be4b05a2 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SERIAL_MPSC) += mpsc.o obj-$(CONFIG_SERIAL_MESON) += meson_uart.o obj-$(CONFIG_SERIAL_SB1250_DUART) += sb1250-duart.o obj-$(CONFIG_ETRAX_SERIAL) += crisv10.o +obj-$(CONFIG_SERIAL_ETRAXFS) += etraxfs-uart.o obj-$(CONFIG_SERIAL_SCCNXP) += sccnxp.o obj-$(CONFIG_SERIAL_SC16IS7XX) += sc16is7xx.o obj-$(CONFIG_SERIAL_JSM) += jsm/ diff --git a/drivers/tty/serial/etraxfs-uart.c b/drivers/tty/serial/etraxfs-uart.c new file mode 100644 index 00000000000..a57301a6fe4 --- /dev/null +++ b/drivers/tty/serial/etraxfs-uart.c @@ -0,0 +1,996 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "etraxfs-uart" +#define UART_NR CONFIG_ETRAX_SERIAL_PORTS + +#define MODIFY_REG(instance, reg, var) \ + do { \ + if (REG_RD_INT(ser, instance, reg) != \ + REG_TYPE_CONV(int, reg_ser_##reg, var)) \ + REG_WR(ser, instance, reg, var); \ + } while (0) + +struct uart_cris_port { + struct uart_port port; + + int initialized; + int irq; + + void __iomem *regi_ser; + + struct gpio_desc *dtr_pin; + struct gpio_desc *dsr_pin; + struct gpio_desc *ri_pin; + struct gpio_desc *cd_pin; + + int write_ongoing; +}; + +static struct uart_driver etraxfs_uart_driver; +static struct uart_port *console_port; +static int console_baud = 115200; +static struct uart_cris_port *etraxfs_uart_ports[UART_NR]; + +static void cris_serial_port_init(struct uart_port *port, int line); +static void etraxfs_uart_stop_rx(struct uart_port *port); +static inline void etraxfs_uart_start_tx_bottom(struct uart_port *port); + +#ifdef CONFIG_SERIAL_ETRAXFS_CONSOLE +static void +cris_console_write(struct console *co, const char *s, unsigned int count) +{ + struct uart_cris_port *up; + int i; + reg_ser_r_stat_din stat; + reg_ser_rw_tr_dma_en tr_dma_en, old; + + up = etraxfs_uart_ports[co->index]; + + if (!up) + return; + + /* Switch to manual mode. */ + tr_dma_en = old = REG_RD(ser, up->regi_ser, rw_tr_dma_en); + if (tr_dma_en.en == regk_ser_yes) { + tr_dma_en.en = regk_ser_no; + REG_WR(ser, up->regi_ser, rw_tr_dma_en, tr_dma_en); + } + + /* Send data. */ + for (i = 0; i < count; i++) { + /* LF -> CRLF */ + if (s[i] == '\n') { + do { + stat = REG_RD(ser, up->regi_ser, r_stat_din); + } while (!stat.tr_rdy); + REG_WR_INT(ser, up->regi_ser, rw_dout, '\r'); + } + /* Wait until transmitter is ready and send. */ + do { + stat = REG_RD(ser, up->regi_ser, r_stat_din); + } while (!stat.tr_rdy); + REG_WR_INT(ser, up->regi_ser, rw_dout, s[i]); + } + + /* Restore mode. */ + if (tr_dma_en.en != old.en) + REG_WR(ser, up->regi_ser, rw_tr_dma_en, old); +} + +static int __init +cris_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + if (co->index < 0 || co->index >= UART_NR) + co->index = 0; + port = &etraxfs_uart_ports[co->index]->port; + console_port = port; + + co->flags |= CON_CONSDEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + console_baud = baud; + cris_serial_port_init(port, co->index); + uart_set_options(port, co, baud, parity, bits, flow); + + return 0; +} + +static struct tty_driver *cris_console_device(struct console *co, int *index) +{ + struct uart_driver *p = co->data; + *index = co->index; + return p->tty_driver; +} + +static struct console cris_console = { + .name = "ttyS", + .write = cris_console_write, + .device = cris_console_device, + .setup = cris_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &etraxfs_uart_driver, +}; +#endif /* CONFIG_SERIAL_ETRAXFS_CONSOLE */ + +static struct uart_driver etraxfs_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "serial", + .dev_name = "ttyS", + .major = TTY_MAJOR, + .minor = 64, + .nr = UART_NR, +#ifdef CONFIG_SERIAL_ETRAXFS_CONSOLE + .cons = &cris_console, +#endif /* CONFIG_SERIAL_ETRAXFS_CONSOLE */ +}; + +static inline int crisv32_serial_get_rts(struct uart_cris_port *up) +{ + void __iomem *regi_ser = up->regi_ser; + /* + * Return what the user has controlled rts to or + * what the pin is? (if auto_rts is used it differs during tx) + */ + reg_ser_r_stat_din rstat = REG_RD(ser, regi_ser, r_stat_din); + + return !(rstat.rts_n == regk_ser_active); +} + +/* + * A set = 0 means 3.3V on the pin, bitvalue: 0=active, 1=inactive + * 0=0V , 1=3.3V + */ +static inline void crisv32_serial_set_rts(struct uart_cris_port *up, + int set, int force) +{ + void __iomem *regi_ser = up->regi_ser; + + unsigned long flags; + reg_ser_rw_rec_ctrl rec_ctrl; + + local_irq_save(flags); + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + + if (set) + rec_ctrl.rts_n = regk_ser_active; + else + rec_ctrl.rts_n = regk_ser_inactive; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + local_irq_restore(flags); +} + +static inline int crisv32_serial_get_cts(struct uart_cris_port *up) +{ + void __iomem *regi_ser = up->regi_ser; + reg_ser_r_stat_din rstat = REG_RD(ser, regi_ser, r_stat_din); + + return (rstat.cts_n == regk_ser_active); +} + +/* + * Send a single character for XON/XOFF purposes. We do it in this separate + * function instead of the alternative support port.x_char, in the ...start_tx + * function, so we don't mix up this case with possibly enabling transmission + * of queued-up data (in case that's disabled after *receiving* an XOFF or + * negative CTS). This function is used for both DMA and non-DMA case; see HW + * docs specifically blessing sending characters manually when DMA for + * transmission is enabled and running. We may be asked to transmit despite + * the transmitter being disabled by a ..._stop_tx call so we need to enable + * it temporarily but restore the state afterwards. + */ +static void etraxfs_uart_send_xchar(struct uart_port *port, char ch) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + reg_ser_rw_dout dout = { .data = ch }; + reg_ser_rw_ack_intr ack_intr = { .tr_rdy = regk_ser_yes }; + reg_ser_r_stat_din rstat; + reg_ser_rw_tr_ctrl prev_tr_ctrl, tr_ctrl; + void __iomem *regi_ser = up->regi_ser; + unsigned long flags; + + /* + * Wait for tr_rdy in case a character is already being output. Make + * sure we have integrity between the register reads and the writes + * below, but don't busy-wait with interrupts off and the port lock + * taken. + */ + spin_lock_irqsave(&port->lock, flags); + do { + spin_unlock_irqrestore(&port->lock, flags); + spin_lock_irqsave(&port->lock, flags); + prev_tr_ctrl = tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + rstat = REG_RD(ser, regi_ser, r_stat_din); + } while (!rstat.tr_rdy); + + /* + * Ack an interrupt if one was just issued for the previous character + * that was output. This is required for non-DMA as the interrupt is + * used as the only indicator that the transmitter is ready and it + * isn't while this x_char is being transmitted. + */ + REG_WR(ser, regi_ser, rw_ack_intr, ack_intr); + + /* Enable the transmitter in case it was disabled. */ + tr_ctrl.stop = 0; + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + + /* + * Finally, send the blessed character; nothing should stop it now, + * except for an xoff-detected state, which we'll handle below. + */ + REG_WR(ser, regi_ser, rw_dout, dout); + up->port.icount.tx++; + + /* There might be an xoff state to clear. */ + rstat = REG_RD(ser, up->regi_ser, r_stat_din); + + /* + * Clear any xoff state that *may* have been there to + * inhibit transmission of the character. + */ + if (rstat.xoff_detect) { + reg_ser_rw_xoff_clr xoff_clr = { .clr = 1 }; + reg_ser_rw_tr_dma_en tr_dma_en; + + REG_WR(ser, regi_ser, rw_xoff_clr, xoff_clr); + tr_dma_en = REG_RD(ser, regi_ser, rw_tr_dma_en); + + /* + * If we had an xoff state but cleared it, instead sneak in a + * disabled state for the transmitter, after the character we + * sent. Thus we keep the port disabled, just as if the xoff + * state was still in effect (or actually, as if stop_tx had + * been called, as we stop DMA too). + */ + prev_tr_ctrl.stop = 1; + + tr_dma_en.en = 0; + REG_WR(ser, regi_ser, rw_tr_dma_en, tr_dma_en); + } + + /* Restore "previous" enabled/disabled state of the transmitter. */ + REG_WR(ser, regi_ser, rw_tr_ctrl, prev_tr_ctrl); + + spin_unlock_irqrestore(&port->lock, flags); +} + +/* + * Do not spin_lock_irqsave or disable interrupts by other means here; it's + * already done by the caller. + */ +static void etraxfs_uart_start_tx(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + + /* we have already done below if a write is ongoing */ + if (up->write_ongoing) + return; + + /* Signal that write is ongoing */ + up->write_ongoing = 1; + + etraxfs_uart_start_tx_bottom(port); +} + +static inline void etraxfs_uart_start_tx_bottom(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + void __iomem *regi_ser = up->regi_ser; + reg_ser_rw_tr_ctrl tr_ctrl; + reg_ser_rw_intr_mask intr_mask; + + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + tr_ctrl.stop = regk_ser_no; + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.tr_rdy = regk_ser_yes; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); +} + +/* + * This function handles both the DMA and non-DMA case by ordering the + * transmitter to stop of after the current character. We don't need to wait + * for any such character to be completely transmitted; we do that where it + * matters, like in etraxfs_uart_set_termios. Don't busy-wait here; see + * Documentation/serial/driver: this function is called within + * spin_lock_irq{,save} and thus separate ones would be disastrous (when SMP). + * There's no documented need to set the txd pin to any particular value; + * break setting is controlled solely by etraxfs_uart_break_ctl. + */ +static void etraxfs_uart_stop_tx(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + void __iomem *regi_ser = up->regi_ser; + reg_ser_rw_tr_ctrl tr_ctrl; + reg_ser_rw_intr_mask intr_mask; + reg_ser_rw_tr_dma_en tr_dma_en = {0}; + reg_ser_rw_xoff_clr xoff_clr = {0}; + + /* + * For the non-DMA case, we'd get a tr_rdy interrupt that we're not + * interested in as we're not transmitting any characters. For the + * DMA case, that interrupt is already turned off, but no reason to + * waste code on conditionals here. + */ + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.tr_rdy = regk_ser_no; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + tr_ctrl.stop = 1; + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + + /* + * Always clear possible hardware xoff-detected state here, no need to + * unnecessary consider mctrl settings and when they change. We clear + * it here rather than in start_tx: both functions are called as the + * effect of XOFF processing, but start_tx is also called when upper + * levels tell the driver that there are more characters to send, so + * avoid adding code there. + */ + xoff_clr.clr = 1; + REG_WR(ser, regi_ser, rw_xoff_clr, xoff_clr); + + /* + * Disable transmitter DMA, so that if we're in XON/XOFF, we can send + * those single characters without also giving go-ahead for queued up + * DMA data. + */ + tr_dma_en.en = 0; + REG_WR(ser, regi_ser, rw_tr_dma_en, tr_dma_en); + + /* + * Make sure that write_ongoing is reset when stopping tx. + */ + up->write_ongoing = 0; +} + +static void etraxfs_uart_stop_rx(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + void __iomem *regi_ser = up->regi_ser; + reg_ser_rw_rec_ctrl rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + + rec_ctrl.en = regk_ser_no; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); +} + +static void etraxfs_uart_enable_ms(struct uart_port *port) +{ +} + +static void check_modem_status(struct uart_cris_port *up) +{ +} + +static unsigned int etraxfs_uart_tx_empty(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + unsigned long flags; + unsigned int ret; + reg_ser_r_stat_din rstat = {0}; + + spin_lock_irqsave(&up->port.lock, flags); + + rstat = REG_RD(ser, up->regi_ser, r_stat_din); + ret = rstat.tr_empty ? TIOCSER_TEMT : 0; + + spin_unlock_irqrestore(&up->port.lock, flags); + return ret; +} +static unsigned int etraxfs_uart_get_mctrl(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + unsigned int ret; + + ret = 0; + if (crisv32_serial_get_rts(up)) + ret |= TIOCM_RTS; + /* DTR is active low */ + if (up->dtr_pin && !gpiod_get_raw_value(up->dtr_pin)) + ret |= TIOCM_DTR; + /* CD is active low */ + if (up->cd_pin && !gpiod_get_raw_value(up->cd_pin)) + ret |= TIOCM_CD; + /* RI is active low */ + if (up->ri_pin && !gpiod_get_raw_value(up->ri_pin)) + ret |= TIOCM_RI; + /* DSR is active low */ + if (up->dsr_pin && !gpiod_get_raw_value(up->dsr_pin)) + ret |= TIOCM_DSR; + if (crisv32_serial_get_cts(up)) + ret |= TIOCM_CTS; + return ret; +} + +static void etraxfs_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + + crisv32_serial_set_rts(up, mctrl & TIOCM_RTS ? 1 : 0, 0); + /* DTR is active low */ + if (up->dtr_pin) + gpiod_set_raw_value(up->dtr_pin, mctrl & TIOCM_DTR ? 0 : 1); + /* RI is active low */ + if (up->ri_pin) + gpiod_set_raw_value(up->ri_pin, mctrl & TIOCM_RNG ? 0 : 1); + /* CD is active low */ + if (up->cd_pin) + gpiod_set_raw_value(up->cd_pin, mctrl & TIOCM_CD ? 0 : 1); +} + +static void etraxfs_uart_break_ctl(struct uart_port *port, int break_state) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + unsigned long flags; + reg_ser_rw_tr_ctrl tr_ctrl; + reg_ser_rw_tr_dma_en tr_dma_en; + reg_ser_rw_intr_mask intr_mask; + + spin_lock_irqsave(&up->port.lock, flags); + tr_ctrl = REG_RD(ser, up->regi_ser, rw_tr_ctrl); + tr_dma_en = REG_RD(ser, up->regi_ser, rw_tr_dma_en); + intr_mask = REG_RD(ser, up->regi_ser, rw_intr_mask); + + if (break_state != 0) { /* Send break */ + /* + * We need to disable DMA (if used) or tr_rdy interrupts if no + * DMA. No need to make this conditional on use of DMA; + * disabling will be a no-op for the other mode. + */ + intr_mask.tr_rdy = regk_ser_no; + tr_dma_en.en = 0; + + /* + * Stop transmission and set the txd pin to 0 after the + * current character. The txd setting will take effect after + * any current transmission has completed. + */ + tr_ctrl.stop = 1; + tr_ctrl.txd = 0; + } else { + /* Re-enable the serial interrupt. */ + intr_mask.tr_rdy = regk_ser_yes; + + tr_ctrl.stop = 0; + tr_ctrl.txd = 1; + } + REG_WR(ser, up->regi_ser, rw_tr_ctrl, tr_ctrl); + REG_WR(ser, up->regi_ser, rw_tr_dma_en, tr_dma_en); + REG_WR(ser, up->regi_ser, rw_intr_mask, intr_mask); + + spin_unlock_irqrestore(&up->port.lock, flags); +} + +static void +transmit_chars_no_dma(struct uart_cris_port *up) +{ + int max_count; + struct circ_buf *xmit = &up->port.state->xmit; + + void __iomem *regi_ser = up->regi_ser; + reg_ser_r_stat_din rstat; + reg_ser_rw_ack_intr ack_intr = { .tr_rdy = regk_ser_yes }; + + if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) { + /* No more to send, so disable the interrupt. */ + reg_ser_rw_intr_mask intr_mask; + + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.tr_rdy = 0; + intr_mask.tr_empty = 0; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + up->write_ongoing = 0; + return; + } + + /* If the serport is fast, we send up to max_count bytes before + exiting the loop. */ + max_count = 64; + do { + reg_ser_rw_dout dout = { .data = xmit->buf[xmit->tail] }; + + REG_WR(ser, regi_ser, rw_dout, dout); + REG_WR(ser, regi_ser, rw_ack_intr, ack_intr); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE-1); + up->port.icount.tx++; + if (xmit->head == xmit->tail) + break; + rstat = REG_RD(ser, regi_ser, r_stat_din); + } while ((--max_count > 0) && rstat.tr_rdy); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&up->port); +} + +static void receive_chars_no_dma(struct uart_cris_port *up) +{ + reg_ser_rs_stat_din stat_din; + reg_ser_r_stat_din rstat; + struct tty_port *port; + struct uart_icount *icount; + int max_count = 16; + char flag; + reg_ser_rw_ack_intr ack_intr = { 0 }; + + rstat = REG_RD(ser, up->regi_ser, r_stat_din); + icount = &up->port.icount; + port = &up->port.state->port; + + do { + stat_din = REG_RD(ser, up->regi_ser, rs_stat_din); + + flag = TTY_NORMAL; + ack_intr.dav = 1; + REG_WR(ser, up->regi_ser, rw_ack_intr, ack_intr); + icount->rx++; + + if (stat_din.framing_err | stat_din.par_err | stat_din.orun) { + if (stat_din.data == 0x00 && + stat_din.framing_err) { + /* Most likely a break. */ + flag = TTY_BREAK; + icount->brk++; + } else if (stat_din.par_err) { + flag = TTY_PARITY; + icount->parity++; + } else if (stat_din.orun) { + flag = TTY_OVERRUN; + icount->overrun++; + } else if (stat_din.framing_err) { + flag = TTY_FRAME; + icount->frame++; + } + } + + /* + * If this becomes important, we probably *could* handle this + * gracefully by keeping track of the unhandled character. + */ + if (!tty_insert_flip_char(port, stat_din.data, flag)) + panic("%s: No tty buffer space", __func__); + rstat = REG_RD(ser, up->regi_ser, r_stat_din); + } while (rstat.dav && (max_count-- > 0)); + spin_unlock(&up->port.lock); + tty_flip_buffer_push(port); + spin_lock(&up->port.lock); +} + +static irqreturn_t +ser_interrupt(int irq, void *dev_id) +{ + struct uart_cris_port *up = (struct uart_cris_port *)dev_id; + void __iomem *regi_ser; + int handled = 0; + + spin_lock(&up->port.lock); + + regi_ser = up->regi_ser; + + if (regi_ser) { + reg_ser_r_masked_intr masked_intr; + + masked_intr = REG_RD(ser, regi_ser, r_masked_intr); + /* + * Check what interrupts are active before taking + * actions. If DMA is used the interrupt shouldn't + * be enabled. + */ + if (masked_intr.dav) { + receive_chars_no_dma(up); + handled = 1; + } + check_modem_status(up); + + if (masked_intr.tr_rdy) { + transmit_chars_no_dma(up); + handled = 1; + } + } + spin_unlock(&up->port.lock); + return IRQ_RETVAL(handled); +} + +#ifdef CONFIG_CONSOLE_POLL +static int etraxfs_uart_get_poll_char(struct uart_port *port) +{ + reg_ser_rs_stat_din stat; + reg_ser_rw_ack_intr ack_intr = { 0 }; + struct uart_cris_port *up = (struct uart_cris_port *)port; + + do { + stat = REG_RD(ser, up->regi_ser, rs_stat_din); + } while (!stat.dav); + + /* Ack the data_avail interrupt. */ + ack_intr.dav = 1; + REG_WR(ser, up->regi_ser, rw_ack_intr, ack_intr); + + return stat.data; +} + +static void etraxfs_uart_put_poll_char(struct uart_port *port, + unsigned char c) +{ + reg_ser_r_stat_din stat; + struct uart_cris_port *up = (struct uart_cris_port *)port; + + do { + stat = REG_RD(ser, up->regi_ser, r_stat_din); + } while (!stat.tr_rdy); + REG_WR_INT(ser, up->regi_ser, rw_dout, c); +} +#endif /* CONFIG_CONSOLE_POLL */ + +static int etraxfs_uart_startup(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + unsigned long flags; + reg_ser_rw_intr_mask ser_intr_mask = {0}; + + ser_intr_mask.dav = regk_ser_yes; + + if (request_irq(etraxfs_uart_ports[port->line]->irq, ser_interrupt, + 0, DRV_NAME, etraxfs_uart_ports[port->line])) + panic("irq ser%d", port->line); + + spin_lock_irqsave(&up->port.lock, flags); + + REG_WR(ser, up->regi_ser, rw_intr_mask, ser_intr_mask); + + etraxfs_uart_set_mctrl(&up->port, up->port.mctrl); + + spin_unlock_irqrestore(&up->port.lock, flags); + + return 0; +} + +static void etraxfs_uart_shutdown(struct uart_port *port) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + unsigned long flags; + + spin_lock_irqsave(&up->port.lock, flags); + + etraxfs_uart_stop_tx(port); + etraxfs_uart_stop_rx(port); + + free_irq(etraxfs_uart_ports[port->line]->irq, + etraxfs_uart_ports[port->line]); + + etraxfs_uart_set_mctrl(&up->port, up->port.mctrl); + + spin_unlock_irqrestore(&up->port.lock, flags); + +} + +static void +etraxfs_uart_set_termios(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + unsigned long flags; + reg_ser_rw_xoff xoff; + reg_ser_rw_xoff_clr xoff_clr = {0}; + reg_ser_rw_tr_ctrl tx_ctrl = {0}; + reg_ser_rw_tr_dma_en tx_dma_en = {0}; + reg_ser_rw_rec_ctrl rx_ctrl = {0}; + reg_ser_rw_tr_baud_div tx_baud_div = {0}; + reg_ser_rw_rec_baud_div rx_baud_div = {0}; + int baud; + + if (old && + termios->c_cflag == old->c_cflag && + termios->c_iflag == old->c_iflag) + return; + + /* Tx: 8 bit, no/even parity, 1 stop bit, no cts. */ + tx_ctrl.base_freq = regk_ser_f29_493; + tx_ctrl.en = 0; + tx_ctrl.stop = 0; + tx_ctrl.auto_rts = regk_ser_no; + tx_ctrl.txd = 1; + tx_ctrl.auto_cts = 0; + /* Rx: 8 bit, no/even parity. */ + rx_ctrl.dma_err = regk_ser_stop; + rx_ctrl.sampling = regk_ser_majority; + rx_ctrl.timeout = 1; + + rx_ctrl.rts_n = regk_ser_inactive; + + /* Common for tx and rx: 8N1. */ + tx_ctrl.data_bits = regk_ser_bits8; + rx_ctrl.data_bits = regk_ser_bits8; + tx_ctrl.par = regk_ser_even; + rx_ctrl.par = regk_ser_even; + tx_ctrl.par_en = regk_ser_no; + rx_ctrl.par_en = regk_ser_no; + + tx_ctrl.stop_bits = regk_ser_bits1; + + /* + * Change baud-rate and write it to the hardware. + * + * baud_clock = base_freq / (divisor*8) + * divisor = base_freq / (baud_clock * 8) + * base_freq is either: + * off, ext, 29.493MHz, 32.000 MHz, 32.768 MHz or 100 MHz + * 20.493MHz is used for standard baudrates + */ + + /* + * For the console port we keep the original baudrate here. Not very + * beautiful. + */ + if ((port != console_port) || old) + baud = uart_get_baud_rate(port, termios, old, 0, + port->uartclk / 8); + else + baud = console_baud; + + tx_baud_div.div = 29493000 / (8 * baud); + /* Rx uses same as tx. */ + rx_baud_div.div = tx_baud_div.div; + rx_ctrl.base_freq = tx_ctrl.base_freq; + + if ((termios->c_cflag & CSIZE) == CS7) { + /* Set 7 bit mode. */ + tx_ctrl.data_bits = regk_ser_bits7; + rx_ctrl.data_bits = regk_ser_bits7; + } + + if (termios->c_cflag & CSTOPB) { + /* Set 2 stop bit mode. */ + tx_ctrl.stop_bits = regk_ser_bits2; + } + + if (termios->c_cflag & PARENB) { + /* Enable parity. */ + tx_ctrl.par_en = regk_ser_yes; + rx_ctrl.par_en = regk_ser_yes; + } + + if (termios->c_cflag & CMSPAR) { + if (termios->c_cflag & PARODD) { + /* Set mark parity if PARODD and CMSPAR. */ + tx_ctrl.par = regk_ser_mark; + rx_ctrl.par = regk_ser_mark; + } else { + tx_ctrl.par = regk_ser_space; + rx_ctrl.par = regk_ser_space; + } + } else { + if (termios->c_cflag & PARODD) { + /* Set odd parity. */ + tx_ctrl.par = regk_ser_odd; + rx_ctrl.par = regk_ser_odd; + } + } + + if (termios->c_cflag & CRTSCTS) { + /* Enable automatic CTS handling. */ + tx_ctrl.auto_cts = regk_ser_yes; + } + + /* Make sure the tx and rx are enabled. */ + tx_ctrl.en = regk_ser_yes; + rx_ctrl.en = regk_ser_yes; + + spin_lock_irqsave(&port->lock, flags); + + tx_dma_en.en = 0; + REG_WR(ser, up->regi_ser, rw_tr_dma_en, tx_dma_en); + + /* Actually write the control regs (if modified) to the hardware. */ + uart_update_timeout(port, termios->c_cflag, port->uartclk/8); + MODIFY_REG(up->regi_ser, rw_rec_baud_div, rx_baud_div); + MODIFY_REG(up->regi_ser, rw_rec_ctrl, rx_ctrl); + + MODIFY_REG(up->regi_ser, rw_tr_baud_div, tx_baud_div); + MODIFY_REG(up->regi_ser, rw_tr_ctrl, tx_ctrl); + + tx_dma_en.en = 0; + REG_WR(ser, up->regi_ser, rw_tr_dma_en, tx_dma_en); + + xoff = REG_RD(ser, up->regi_ser, rw_xoff); + + if (up->port.state && up->port.state->port.tty && + (up->port.state->port.tty->termios.c_iflag & IXON)) { + xoff.chr = STOP_CHAR(up->port.state->port.tty); + xoff.automatic = regk_ser_yes; + } else + xoff.automatic = regk_ser_no; + + MODIFY_REG(up->regi_ser, rw_xoff, xoff); + + /* + * Make sure we don't start in an automatically shut-off state due to + * a previous early exit. + */ + xoff_clr.clr = 1; + REG_WR(ser, up->regi_ser, rw_xoff_clr, xoff_clr); + + etraxfs_uart_set_mctrl(&up->port, up->port.mctrl); + spin_unlock_irqrestore(&up->port.lock, flags); +} + +static const char * +etraxfs_uart_type(struct uart_port *port) +{ + return "CRISv32"; +} + +static void etraxfs_uart_release_port(struct uart_port *port) +{ +} + +static int etraxfs_uart_request_port(struct uart_port *port) +{ + return 0; +} + +static void etraxfs_uart_config_port(struct uart_port *port, int flags) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + + up->port.type = PORT_CRIS; +} + +static const struct uart_ops etraxfs_uart_pops = { + .tx_empty = etraxfs_uart_tx_empty, + .set_mctrl = etraxfs_uart_set_mctrl, + .get_mctrl = etraxfs_uart_get_mctrl, + .stop_tx = etraxfs_uart_stop_tx, + .start_tx = etraxfs_uart_start_tx, + .send_xchar = etraxfs_uart_send_xchar, + .stop_rx = etraxfs_uart_stop_rx, + .enable_ms = etraxfs_uart_enable_ms, + .break_ctl = etraxfs_uart_break_ctl, + .startup = etraxfs_uart_startup, + .shutdown = etraxfs_uart_shutdown, + .set_termios = etraxfs_uart_set_termios, + .type = etraxfs_uart_type, + .release_port = etraxfs_uart_release_port, + .request_port = etraxfs_uart_request_port, + .config_port = etraxfs_uart_config_port, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = etraxfs_uart_get_poll_char, + .poll_put_char = etraxfs_uart_put_poll_char, +#endif +}; + +static void cris_serial_port_init(struct uart_port *port, int line) +{ + struct uart_cris_port *up = (struct uart_cris_port *)port; + + if (up->initialized) + return; + up->initialized = 1; + port->line = line; + spin_lock_init(&port->lock); + port->ops = &etraxfs_uart_pops; + port->irq = up->irq; + port->iobase = (unsigned long) up->regi_ser; + port->uartclk = 29493000; + + /* + * We can't fit any more than 255 here (unsigned char), though + * actually UART_XMIT_SIZE characters could be pending output. + * At time of this writing, the definition of "fifosize" is here the + * amount of characters that can be pending output after a start_tx call + * until tx_empty returns 1: see serial_core.c:uart_wait_until_sent. + * This matters for timeout calculations unfortunately, but keeping + * larger amounts at the DMA wouldn't win much so let's just play nice. + */ + port->fifosize = 255; + port->flags = UPF_BOOT_AUTOCONF; +} + +static int etraxfs_uart_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct uart_cris_port *up; + int dev_id; + + if (!np) + return -ENODEV; + + dev_id = of_alias_get_id(np, "serial"); + if (dev_id < 0) + dev_id = 0; + + if (dev_id >= UART_NR) + return -EINVAL; + + if (etraxfs_uart_ports[dev_id]) + return -EBUSY; + + up = devm_kzalloc(&pdev->dev, sizeof(struct uart_cris_port), + GFP_KERNEL); + if (!up) + return -ENOMEM; + + up->irq = irq_of_parse_and_map(np, 0); + up->regi_ser = of_iomap(np, 0); + up->dtr_pin = devm_gpiod_get_optional(&pdev->dev, "dtr"); + up->dsr_pin = devm_gpiod_get_optional(&pdev->dev, "dsr"); + up->ri_pin = devm_gpiod_get_optional(&pdev->dev, "ri"); + up->cd_pin = devm_gpiod_get_optional(&pdev->dev, "cd"); + up->port.dev = &pdev->dev; + cris_serial_port_init(&up->port, dev_id); + + etraxfs_uart_ports[dev_id] = up; + platform_set_drvdata(pdev, &up->port); + uart_add_one_port(&etraxfs_uart_driver, &up->port); + + return 0; +} + +static int etraxfs_uart_remove(struct platform_device *pdev) +{ + struct uart_port *port; + + port = platform_get_drvdata(pdev); + uart_remove_one_port(&etraxfs_uart_driver, port); + etraxfs_uart_ports[pdev->id] = NULL; + + return 0; +} + +static const struct of_device_id etraxfs_uart_dt_ids[] = { + { .compatible = "axis,etraxfs-uart" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, etraxfs_uart_dt_ids); + +static struct platform_driver etraxfs_uart_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(etraxfs_uart_dt_ids), + }, + .probe = etraxfs_uart_probe, + .remove = etraxfs_uart_remove, +}; + +static int __init etraxfs_uart_init(void) +{ + int ret; + + ret = uart_register_driver(&etraxfs_uart_driver); + if (ret) + return ret; + + ret = platform_driver_register(&etraxfs_uart_platform_driver); + if (ret) + uart_unregister_driver(&etraxfs_uart_driver); + + return ret; +} + +static void __exit etraxfs_uart_exit(void) +{ + platform_driver_unregister(&etraxfs_uart_platform_driver); + uart_unregister_driver(&etraxfs_uart_driver); +} + +module_init(etraxfs_uart_init); +module_exit(etraxfs_uart_exit); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 55da91e763b..b2122813f18 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -255,4 +255,7 @@ /* SPRD SERIAL */ #define PORT_SPRD 111 +/* Cris v10 / v32 SoC */ +#define PORT_CRIS 112 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- cgit v1.2.3-70-g09d2 From c09babfab7ae8c7d79a5dce9d866fbb28b82dde4 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Mon, 2 Feb 2015 15:47:07 -0500 Subject: serial: 8250: Fix UART_BUG_TXEN workaround UARTs which do not trigger THRE interrupt if the fifo is already empty when the interrupt is enabled need tx primed manually. These UARTs are identified by the UART_BUG_TXEN flag to enable the required workaround. However, the current workaround is broken; if the fifo is already empty but the shifter is still transmitting, then serial8250_tx_chars() will not be called but no further THRE interrupt will occur, and tx will stall. The appropriate check is for fifo empty (THRE), not transmitter empty (TEMT). Signed-off-by: Dick Hollenbeck [pjh: rewrote commit log] Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 46ae9fda68b..e3b9570a1ef 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -1388,7 +1388,7 @@ static void serial8250_start_tx(struct uart_port *port) unsigned char lsr; lsr = serial_in(up, UART_LSR); up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; - if (lsr & UART_LSR_TEMT) + if (lsr & UART_LSR_THRE) serial8250_tx_chars(up); } } -- cgit v1.2.3-70-g09d2