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),  	},  };  | 
