diff options
Diffstat (limited to 'drivers/tty/serial/stm32-usart.c')
-rw-r--r-- | drivers/tty/serial/stm32-usart.c | 125 |
1 files changed, 115 insertions, 10 deletions
diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c index 033856287ca2..03a583264d9e 100644 --- a/drivers/tty/serial/stm32-usart.c +++ b/drivers/tty/serial/stm32-usart.c @@ -1,5 +1,6 @@ /* * Copyright (C) Maxime Coquelin 2015 + * Copyright (C) STMicroelectronics SA 2017 * Authors: Maxime Coquelin <mcoquelin.stm32@gmail.com> * Gerald Baeza <gerald.baeza@st.com> * License terms: GNU General Public License (GPL), version 2 @@ -25,6 +26,7 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/pm_wakeirq.h> #include <linux/serial_core.h> #include <linux/serial.h> #include <linux/spinlock.h> @@ -110,14 +112,13 @@ static void stm32_receive_chars(struct uart_port *port, bool threaded) unsigned long c; u32 sr; char flag; - static int last_res = RX_BUF_L; - if (port->irq_wake) + if (irqd_is_wakeup_set(irq_get_irq_data(port->irq))) pm_wakeup_event(tport->tty->dev, 0); - while (stm32_pending_rx(port, &sr, &last_res, threaded)) { + while (stm32_pending_rx(port, &sr, &stm32_port->last_res, threaded)) { sr |= USART_SR_DUMMY_RX; - c = stm32_get_char(port, &sr, &last_res); + c = stm32_get_char(port, &sr, &stm32_port->last_res); flag = TTY_NORMAL; port->icount.rx++; @@ -202,7 +203,7 @@ static void stm32_transmit_chars_pio(struct uart_port *port) ret = readl_relaxed_poll_timeout_atomic(port->membase + ofs->isr, isr, (isr & USART_SR_TXE), - 10, 100); + 10, 100000); if (ret) dev_err(port->dev, "tx empty not set\n"); @@ -326,6 +327,10 @@ static irqreturn_t stm32_interrupt(int irq, void *ptr) sr = readl_relaxed(port->membase + ofs->isr); + if ((sr & USART_SR_WUF) && (ofs->icr != UNDEF_REG)) + writel_relaxed(USART_ICR_WUCF, + port->membase + ofs->icr); + if ((sr & USART_SR_RXNE) && !(stm32_port->rx_ch)) stm32_receive_chars(port, false); @@ -442,6 +447,7 @@ static int stm32_startup(struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; + struct stm32_usart_config *cfg = &stm32_port->info->cfg; const char *name = to_platform_device(port->dev)->name; u32 val; int ret; @@ -452,7 +458,18 @@ static int stm32_startup(struct uart_port *port) if (ret) return ret; + if (cfg->has_wakeup && stm32_port->wakeirq >= 0) { + ret = dev_pm_set_dedicated_wake_irq(port->dev, + stm32_port->wakeirq); + if (ret) { + free_irq(port->irq, port); + return ret; + } + } + val = USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE; + if (stm32_port->fifoen) + val |= USART_CR1_FIFOEN; stm32_set_bits(port, ofs->cr1, val); return 0; @@ -467,8 +484,11 @@ static void stm32_shutdown(struct uart_port *port) val = USART_CR1_TXEIE | USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE; val |= BIT(cfg->uart_enable_bit); + if (stm32_port->fifoen) + val |= USART_CR1_FIFOEN; stm32_clr_bits(port, ofs->cr1, val); + dev_pm_clear_wake_irq(port->dev); free_irq(port->irq, port); } @@ -496,6 +516,8 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios, cr1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE; cr1 |= BIT(cfg->uart_enable_bit); + if (stm32_port->fifoen) + cr1 |= USART_CR1_FIFOEN; cr2 = 0; cr3 = 0; @@ -518,7 +540,7 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios, port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); if (cflag & CRTSCTS) { port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; - cr3 |= USART_CR3_CTSE; + cr3 |= USART_CR3_CTSE | USART_CR3_RTSE; } usartdiv = DIV_ROUND_CLOSEST(port->uartclk, baud); @@ -659,6 +681,8 @@ static int stm32_init_port(struct stm32_port *stm32port, port->ops = &stm32_uart_ops; port->dev = &pdev->dev; port->irq = platform_get_irq(pdev, 0); + stm32port->wakeirq = platform_get_irq(pdev, 1); + stm32port->fifoen = stm32port->info->cfg.has_fifo; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); port->membase = devm_ioremap_resource(&pdev->dev, res); @@ -678,8 +702,10 @@ static int stm32_init_port(struct stm32_port *stm32port, return ret; stm32port->port.uartclk = clk_get_rate(stm32port->clk); - if (!stm32port->port.uartclk) + if (!stm32port->port.uartclk) { + clk_disable_unprepare(stm32port->clk); ret = -EINVAL; + } return ret; } @@ -693,8 +719,10 @@ static struct stm32_port *stm32_of_get_stm32_port(struct platform_device *pdev) return NULL; id = of_alias_get_id(np, "serial"); - if (id < 0) - id = 0; + if (id < 0) { + dev_err(&pdev->dev, "failed to get alias id, errno %d\n", id); + return NULL; + } if (WARN_ON(id >= STM32_MAX_PORTS)) return NULL; @@ -702,6 +730,7 @@ static struct stm32_port *stm32_of_get_stm32_port(struct platform_device *pdev) stm32_ports[id].hw_flow_control = of_property_read_bool(np, "st,hw-flow-ctrl"); stm32_ports[id].port.line = id; + stm32_ports[id].last_res = RX_BUF_L; return &stm32_ports[id]; } @@ -711,6 +740,8 @@ static const struct of_device_id stm32_match[] = { { .compatible = "st,stm32-uart", .data = &stm32f4_info}, { .compatible = "st,stm32f7-usart", .data = &stm32f7_info}, { .compatible = "st,stm32f7-uart", .data = &stm32f7_info}, + { .compatible = "st,stm32h7-usart", .data = &stm32h7_info}, + { .compatible = "st,stm32h7-uart", .data = &stm32h7_info}, {}, }; @@ -860,9 +891,15 @@ static int stm32_serial_probe(struct platform_device *pdev) if (ret) return ret; + if (stm32port->info->cfg.has_wakeup && stm32port->wakeirq >= 0) { + ret = device_init_wakeup(&pdev->dev, true); + if (ret) + goto err_uninit; + } + ret = uart_add_one_port(&stm32_usart_driver, &stm32port->port); if (ret) - return ret; + goto err_nowup; ret = stm32_of_dma_rx_probe(stm32port, pdev); if (ret) @@ -875,6 +912,15 @@ static int stm32_serial_probe(struct platform_device *pdev) platform_set_drvdata(pdev, &stm32port->port); return 0; + +err_nowup: + if (stm32port->info->cfg.has_wakeup && stm32port->wakeirq >= 0) + device_init_wakeup(&pdev->dev, false); + +err_uninit: + clk_disable_unprepare(stm32port->clk); + + return ret; } static int stm32_serial_remove(struct platform_device *pdev) @@ -882,6 +928,7 @@ static int stm32_serial_remove(struct platform_device *pdev) struct uart_port *port = platform_get_drvdata(pdev); struct stm32_port *stm32_port = to_stm32_port(port); struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; + struct stm32_usart_config *cfg = &stm32_port->info->cfg; stm32_clr_bits(port, ofs->cr3, USART_CR3_DMAR); @@ -903,6 +950,9 @@ static int stm32_serial_remove(struct platform_device *pdev) TX_BUF_L, stm32_port->tx_buf, stm32_port->tx_dma_buf); + if (cfg->has_wakeup && stm32_port->wakeirq >= 0) + device_init_wakeup(&pdev->dev, false); + clk_disable_unprepare(stm32_port->clk); return uart_remove_one_port(&stm32_usart_driver, port); @@ -1008,11 +1058,66 @@ static struct uart_driver stm32_usart_driver = { .cons = STM32_SERIAL_CONSOLE, }; +#ifdef CONFIG_PM_SLEEP +static void stm32_serial_enable_wakeup(struct uart_port *port, bool enable) +{ + struct stm32_port *stm32_port = to_stm32_port(port); + struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; + struct stm32_usart_config *cfg = &stm32_port->info->cfg; + u32 val; + + if (!cfg->has_wakeup || stm32_port->wakeirq < 0) + return; + + if (enable) { + stm32_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); + stm32_set_bits(port, ofs->cr1, USART_CR1_UESM); + val = readl_relaxed(port->membase + ofs->cr3); + val &= ~USART_CR3_WUS_MASK; + /* Enable Wake up interrupt from low power on start bit */ + val |= USART_CR3_WUS_START_BIT | USART_CR3_WUFIE; + writel_relaxed(val, port->membase + ofs->cr3); + stm32_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); + } else { + stm32_clr_bits(port, ofs->cr1, USART_CR1_UESM); + } +} + +static int stm32_serial_suspend(struct device *dev) +{ + struct uart_port *port = dev_get_drvdata(dev); + + uart_suspend_port(&stm32_usart_driver, port); + + if (device_may_wakeup(dev)) + stm32_serial_enable_wakeup(port, true); + else + stm32_serial_enable_wakeup(port, false); + + return 0; +} + +static int stm32_serial_resume(struct device *dev) +{ + struct uart_port *port = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + stm32_serial_enable_wakeup(port, false); + + return uart_resume_port(&stm32_usart_driver, port); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_serial_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_serial_suspend, stm32_serial_resume) +}; + static struct platform_driver stm32_serial_driver = { .probe = stm32_serial_probe, .remove = stm32_serial_remove, .driver = { .name = DRIVER_NAME, + .pm = &stm32_serial_pm_ops, .of_match_table = of_match_ptr(stm32_match), }, }; |