diff mbox

[RFC] tty: serial: omap: use mctrl_gpio helpers

Message ID 1398331471-16745-2-git-send-email-yegorslists@googlemail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Yegor Yefremov April 24, 2014, 9:24 a.m. UTC
From: Yegor Yefremov <yegorslists@googlemail.com>

This patch permits to use GPIOs to control the CTS/RTS/DTR/DSR/DCD/RI
signals.

Signed-off-by: Yegor Yefremov <yegorslists@googlemail.com>
---
 drivers/tty/serial/Kconfig       |    1 +
 drivers/tty/serial/omap-serial.c |  168 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 162 insertions(+), 7 deletions(-)
diff mbox

Patch

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 6e748dc..3eeaa09 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1088,6 +1088,7 @@  config SERIAL_OMAP
 	tristate "OMAP serial port support"
 	depends on ARCH_OMAP2PLUS
 	select SERIAL_CORE
+	select SERIAL_MCTRL_GPIO
 	help
 	  If you have a machine based on an Texas Instruments OMAP CPU you
 	  can enable its onboard serial ports by enabling this option.
diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c
index 08b6b94..87dcad7 100644
--- a/drivers/tty/serial/omap-serial.c
+++ b/drivers/tty/serial/omap-serial.c
@@ -43,9 +43,13 @@ 
 #include <linux/gpio.h>
 #include <linux/of_gpio.h>
 #include <linux/platform_data/serial-omap.h>
+#include <linux/gpio/consumer.h>
+#include <linux/err.h>
 
 #include <dt-bindings/gpio/gpio.h>
 
+#include "serial_mctrl_gpio.h"
+
 #define OMAP_MAX_HSUART_PORTS	6
 
 #define UART_BUILD_REVISION(x, y)	(((x) << 8) | (y))
@@ -169,6 +173,9 @@  struct uart_omap_port {
 
 	struct serial_rs485	rs485;
 	int			rts_gpio;
+	struct mctrl_gpios	*gpios;
+	int			gpio_irq[UART_GPIO_MAX];
+	bool			ms_irq_enabled;
 
 	struct pm_qos_request	pm_qos_request;
 	u32			latency;
@@ -294,6 +301,27 @@  static void serial_omap_enable_ms(struct uart_port *port)
 	dev_dbg(up->port.dev, "serial_omap_enable_ms+%d\n", up->port.line);
 
 	pm_runtime_get_sync(up->dev);
+
+	/*
+	* Interrupt should not be enabled twice
+	*/
+	if (up->ms_irq_enabled)
+		return;
+
+	up->ms_irq_enabled = true;
+
+	if (up->gpio_irq[UART_GPIO_CTS] >= 0)
+		enable_irq(up->gpio_irq[UART_GPIO_CTS]);
+
+	if (up->gpio_irq[UART_GPIO_DSR] >= 0)
+		enable_irq(up->gpio_irq[UART_GPIO_DSR]);
+
+	if (up->gpio_irq[UART_GPIO_RI] >= 0)
+		enable_irq(up->gpio_irq[UART_GPIO_RI]);
+
+	if (up->gpio_irq[UART_GPIO_DCD] >= 0)
+		enable_irq(up->gpio_irq[UART_GPIO_DCD]);
+
 	up->ier |= UART_IER_MSI;
 	serial_out(up, UART_IER, up->ier);
 	pm_runtime_mark_last_busy(up->dev);
@@ -310,6 +338,10 @@  static void serial_omap_stop_tx(struct uart_port *port)
 	/* Handle RS-485 */
 	if (up->rs485.flags & SER_RS485_ENABLED) {
 		if (up->scr & OMAP_UART_SCR_TX_EMPTY) {
+			struct gpio_desc *rts_gpiod;
+
+			rts_gpiod = mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_RTS);
+
 			/* THR interrupt is fired when both TX FIFO and TX
 			 * shift register are empty. This means there's nothing
 			 * left to transmit now, so make sure the THR interrupt
@@ -320,10 +352,10 @@  static void serial_omap_stop_tx(struct uart_port *port)
 			up->scr &= ~OMAP_UART_SCR_TX_EMPTY;
 			serial_out(up, UART_OMAP_SCR, up->scr);
 			res = (up->rs485.flags & SER_RS485_RTS_AFTER_SEND) ? 1 : 0;
-			if (gpio_get_value(up->rts_gpio) != res) {
+			if (gpiod_get_value(rts_gpiod) != res) {
 				if (up->rs485.delay_rts_after_send > 0)
 					mdelay(up->rs485.delay_rts_after_send);
-				gpio_set_value(up->rts_gpio, res);
+				gpiod_set_value(rts_gpiod, res);
 			}
 		} else {
 			/* We're asked to stop, but there's still stuff in the
@@ -425,14 +457,18 @@  static void serial_omap_start_tx(struct uart_port *port)
 
 	/* Handle RS-485 */
 	if (up->rs485.flags & SER_RS485_ENABLED) {
+		struct gpio_desc *rts_gpiod;
+
+		rts_gpiod = mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_RTS);
+
 		/* Fire THR interrupts when FIFO is below trigger level */
 		up->scr &= ~OMAP_UART_SCR_TX_EMPTY;
 		serial_out(up, UART_OMAP_SCR, up->scr);
 
 		/* if rts not already enabled */
 		res = (up->rs485.flags & SER_RS485_RTS_ON_SEND) ? 1 : 0;
-		if (gpio_get_value(up->rts_gpio) != res) {
-			gpio_set_value(up->rts_gpio, res);
+		if (gpiod_get_value(rts_gpiod) != res) {
+			gpiod_set_value(rts_gpiod, res);
 			if (up->rs485.delay_rts_before_send > 0)
 				mdelay(up->rs485.delay_rts_before_send);
 		}
@@ -581,10 +617,45 @@  static irqreturn_t serial_omap_irq(int irq, void *dev_id)
 	unsigned int type;
 	irqreturn_t ret = IRQ_NONE;
 	int max_count = 256;
+	bool gpio_handled = false;
+	bool gpio_any_delta = false;
 
 	spin_lock(&up->port.lock);
 	pm_runtime_get_sync(up->dev);
 
+	if (!gpio_handled) {
+		/*
+		* Dealing with GPIO interrupt
+		*/
+		if (irq == up->gpio_irq[UART_GPIO_RI]) {
+			up->port.icount.rng++;
+			gpio_any_delta = true;
+		}
+
+		if (irq == up->gpio_irq[UART_GPIO_DSR]) {
+			up->port.icount.dsr++;
+			gpio_any_delta = true;
+		}
+
+		if (irq == up->gpio_irq[UART_GPIO_DCD]) {
+			uart_handle_dcd_change
+				(&up->port, UART_MSR_DCD);
+			gpio_any_delta = true;
+		}
+
+		if (irq == up->gpio_irq[UART_GPIO_CTS]) {
+			uart_handle_cts_change
+				(&up->port, UART_MSR_CTS);
+			gpio_any_delta = true;
+		}
+
+		if (gpio_any_delta) {
+			wake_up_interruptible(&up->port.state->port.delta_msr_wait);
+		}
+
+		gpio_handled = true;
+	}
+
 	do {
 		iir = serial_in(up, UART_IIR);
 		if (iir & UART_IIR_NO_INT)
@@ -632,6 +703,45 @@  static irqreturn_t serial_omap_irq(int irq, void *dev_id)
 	return ret;
 }
 
+static void serial_omap_free_gpio_irq(struct uart_port *port)
+{
+	struct uart_omap_port *up = to_uart_omap_port(port);
+	enum mctrl_gpio_idx i;
+
+	for (i = 0; i < UART_GPIO_MAX; i++)
+		if (up->gpio_irq[i] >= 0)
+			free_irq(up->gpio_irq[i], port);
+}
+
+static int serial_omap_request_gpio_irq(struct uart_port *port)
+{
+	struct uart_omap_port *up = to_uart_omap_port(port);
+	int *irq = up->gpio_irq;
+	enum mctrl_gpio_idx i;
+	int err = 0;
+
+	for (i = 0; (i < UART_GPIO_MAX) && !err; i++) {
+		if (irq[i] < 0)
+			continue;
+
+		irq_set_status_flags(irq[i], IRQ_NOAUTOEN);
+		err = request_irq(irq[i], serial_omap_irq, IRQ_TYPE_EDGE_BOTH,
+				"omap_serial", port);
+		if (err)
+			dev_err(port->dev, "omap_startup - Can't get %d irq\n",
+				irq[i]);
+	}
+
+	/*
+	 * If something went wrong, rollback.
+	 */
+	while (err && (--i >= 0))
+		if (irq[i] >= 0)
+			free_irq(irq[i], port);
+
+	return err;
+}
+
 static unsigned int serial_omap_tx_empty(struct uart_port *port)
 {
 	struct uart_omap_port *up = to_uart_omap_port(port);
@@ -669,7 +779,8 @@  static unsigned int serial_omap_get_mctrl(struct uart_port *port)
 		ret |= TIOCM_DSR;
 	if (status & UART_MSR_CTS)
 		ret |= TIOCM_CTS;
-	return ret;
+
+	return mctrl_gpio_get(up->gpios, &ret);
 }
 
 static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl)
@@ -698,6 +809,8 @@  static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl)
 	pm_runtime_mark_last_busy(up->dev);
 	pm_runtime_put_autosuspend(up->dev);
 
+	mctrl_gpio_set(up->gpios, mctrl);
+
 	if (gpio_is_valid(up->DTR_gpio) &&
 	    !!(mctrl & TIOCM_DTR) != up->DTR_active) {
 		up->DTR_active = !up->DTR_active;
@@ -733,6 +846,8 @@  static int serial_omap_startup(struct uart_port *port)
 	unsigned long flags = 0;
 	int retval;
 
+	up->ms_irq_enabled = false;
+
 	/*
 	 * Allocate the IRQ
 	 */
@@ -752,6 +867,15 @@  static int serial_omap_startup(struct uart_port *port)
 		disable_irq(up->wakeirq);
 	}
 
+	retval = serial_omap_request_gpio_irq(port);
+	if (retval) {
+		free_irq(up->port.irq, up);
+		if (up->wakeirq) {
+			free_irq(up->wakeirq, up);
+		}
+		return retval;
+	}
+
 	dev_dbg(up->port.dev, "serial_omap_startup+%d\n", up->port.line);
 
 	pm_runtime_get_sync(up->dev);
@@ -842,6 +966,8 @@  static void serial_omap_shutdown(struct uart_port *port)
 	free_irq(up->port.irq, up);
 	if (up->wakeirq)
 		free_irq(up->wakeirq, up);
+	serial_omap_free_gpio_irq(port);
+	up->ms_irq_enabled = false;
 }
 
 static void serial_omap_uart_qos_work(struct work_struct *work)
@@ -1370,6 +1496,9 @@  serial_omap_config_rs485(struct uart_port *port, struct serial_rs485 *rs485conf)
 	unsigned long flags;
 	unsigned int mode;
 	int val;
+	struct gpio_desc *rts_gpiod;
+
+	rts_gpiod = mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_RTS);
 
 	pm_runtime_get_sync(up->dev);
 	spin_lock_irqsave(&up->port.lock, flags);
@@ -1386,12 +1515,12 @@  serial_omap_config_rs485(struct uart_port *port, struct serial_rs485 *rs485conf)
 	 * Just as a precaution, only allow rs485
 	 * to be enabled if the gpio pin is valid
 	 */
-	if (gpio_is_valid(up->rts_gpio)) {
+	if (!IS_ERR_OR_NULL(rts_gpiod)) {
 		/* enable / disable rts */
 		val = (up->rs485.flags & SER_RS485_ENABLED) ?
 			SER_RS485_RTS_AFTER_SEND : SER_RS485_RTS_ON_SEND;
 		val = (up->rs485.flags & val) ? 1 : 0;
-		gpio_set_value(up->rts_gpio, val);
+		gpiod_set_value(rts_gpiod, val);
 	} else
 		up->rs485.flags &= ~SER_RS485_ENABLED;
 
@@ -1642,6 +1771,26 @@  static int serial_omap_probe_rs485(struct uart_omap_port *up,
 	return 0;
 }
 
+static int serial_omap_init_gpios(struct uart_omap_port *up, struct device *dev)
+{
+	enum mctrl_gpio_idx i;
+	struct gpio_desc *gpiod;
+
+	up->gpios = mctrl_gpio_init(dev, 0);
+	if (IS_ERR_OR_NULL(up->gpios))
+		return -1;
+
+	for (i = 0; i < UART_GPIO_MAX; i++) {
+		gpiod = mctrl_gpio_to_gpiod(up->gpios, i);
+		if (gpiod && (gpiod_get_direction(gpiod) == GPIOF_DIR_IN))
+			up->gpio_irq[i] = gpiod_to_irq(gpiod);
+		else
+			up->gpio_irq[i] = -EINVAL;
+	}
+
+	return 0;
+}
+
 static int serial_omap_probe(struct platform_device *pdev)
 {
 	struct uart_omap_port	*up;
@@ -1727,6 +1876,11 @@  static int serial_omap_probe(struct platform_device *pdev)
 		goto err_port_line;
 	}
 
+	ret = serial_omap_init_gpios(up, &pdev->dev);
+	if (ret < 0)
+		dev_err(&pdev->dev, "%s",
+			"Failed to initialize GPIOs. The serial port may not work as expected");
+
 	ret = serial_omap_probe_rs485(up, pdev->dev.of_node);
 	if (ret < 0)
 		goto err_rs485;