[v2,10/16] serial: mvebu-uart: dissociate RX and TX interrupts

Message ID 20171013090200.31034-11-miquel.raynal@free-electrons.com
State New
Headers show
Series
  • Support armada-37xx second UART port
Related show

Commit Message

Miquel RAYNAL Oct. 13, 2017, 9:01 a.m.
While the standard UART port can use a single IRQ that 'sums' both RX
and TX interrupts, the extended port cannot and has to use two different
ISR, one for each direction. The standard port also has the hability
to use two separate interrupts (one for each direction).

The logic is then: either there is only one unnamed interrupt on the
standard port and this interrupt must be used for both directions
(this is legacy bindings); or all the interrupts must be described and
named 'uart-sum' (if available), 'uart-rx', 'uart-tx' and two separate
handlers for each direction will be used.

Suggested-by: Allen Yan <yanwei@marvell.com>
Signed-off-by: Miquel Raynal <miquel.raynal@free-electrons.com>
---

Changes since v1: free IRQ with devm_*().

 drivers/tty/serial/mvebu-uart.c | 131 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 119 insertions(+), 12 deletions(-)

Patch

diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c
index e52248ec2689..d0e749f6f052 100644
--- a/drivers/tty/serial/mvebu-uart.c
+++ b/drivers/tty/serial/mvebu-uart.c
@@ -79,7 +79,16 @@ 
 #define MVEBU_UART_TYPE		"mvebu-uart"
 #define DRIVER_NAME		"mvebu_serial"
 
-/* Register offsets, different depending on the UART */
+enum {
+	/* Either there is only one summed IRQ... */
+	UART_IRQ_SUM = 0,
+	/* ...or there are two separate IRQ for RX and TX */
+	UART_RX_IRQ = 0,
+	UART_TX_IRQ,
+	UART_IRQ_COUNT
+};
+
+/* Diverging register offsets */
 struct uart_regs_layout {
 	unsigned int rbr;
 	unsigned int tsh;
@@ -106,6 +115,8 @@  struct mvebu_uart_driver_data {
 struct mvebu_uart {
 	struct uart_port *port;
 	struct clk *clk;
+	int irq[UART_IRQ_COUNT];
+	unsigned char __iomem *nb;
 	struct mvebu_uart_driver_data *data;
 };
 
@@ -313,9 +324,32 @@  static irqreturn_t mvebu_uart_isr(int irq, void *dev_id)
 	unsigned int st = readl(port->membase + UART_STAT);
 
 	if (st & (STAT_RX_RDY(port) | STAT_OVR_ERR | STAT_FRM_ERR |
+		  STAT_BRK_DET))
+		mvebu_uart_rx_chars(port, st);
+
+	if (st & STAT_TX_RDY(port))
+		mvebu_uart_tx_chars(port, st);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t mvebu_uart_rx_isr(int irq, void *dev_id)
+{
+	struct uart_port *port = (struct uart_port *)dev_id;
+	unsigned int st = readl(port->membase + UART_STAT);
+
+	if (st & (STAT_RX_RDY(port) | STAT_OVR_ERR | STAT_FRM_ERR |
 			STAT_BRK_DET))
 		mvebu_uart_rx_chars(port, st);
 
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t mvebu_uart_tx_isr(int irq, void *dev_id)
+{
+	struct uart_port *port = (struct uart_port *)dev_id;
+	unsigned int st = readl(port->membase + UART_STAT);
+
 	if (st & STAT_TX_RDY(port))
 		mvebu_uart_tx_chars(port, st);
 
@@ -324,6 +358,7 @@  static irqreturn_t mvebu_uart_isr(int irq, void *dev_id)
 
 static int mvebu_uart_startup(struct uart_port *port)
 {
+	struct mvebu_uart *mvuart = to_mvuart(port);
 	unsigned int ctl;
 	int ret;
 
@@ -342,11 +377,38 @@  static int mvebu_uart_startup(struct uart_port *port)
 	ctl |= CTRL_RX_RDY_INT(port);
 	writel(ctl, port->membase + UART_INTR(port));
 
-	ret = request_irq(port->irq, mvebu_uart_isr, port->irqflags,
-			  DRIVER_NAME, port);
-	if (ret) {
-		dev_err(port->dev, "failed to request irq\n");
-		return ret;
+	if (!mvuart->irq[UART_TX_IRQ]) {
+		/* Old bindings with just one interrupt (UART0 only) */
+		ret = devm_request_irq(port->dev, mvuart->irq[UART_IRQ_SUM],
+				       mvebu_uart_isr, port->irqflags,
+				       dev_name(port->dev), port);
+		if (ret) {
+			dev_err(port->dev, "unable to request IRQ %d\n",
+				mvuart->irq[UART_IRQ_SUM]);
+			return ret;
+		}
+	} else {
+		/* New bindings with an IRQ for RX and TX (both UART) */
+		ret = devm_request_irq(port->dev, mvuart->irq[UART_RX_IRQ],
+				       mvebu_uart_rx_isr, port->irqflags,
+				       dev_name(port->dev), port);
+		if (ret) {
+			dev_err(port->dev, "unable to request IRQ %d\n",
+				mvuart->irq[UART_RX_IRQ]);
+			return ret;
+		}
+
+		ret = devm_request_irq(port->dev, mvuart->irq[UART_TX_IRQ],
+				       mvebu_uart_tx_isr, port->irqflags,
+				       dev_name(port->dev),
+				       port);
+		if (ret) {
+			dev_err(port->dev, "unable to request IRQ %d\n",
+				mvuart->irq[UART_TX_IRQ]);
+			devm_free_irq(port->dev, mvuart->irq[UART_RX_IRQ],
+				      port);
+			return ret;
+		}
 	}
 
 	return 0;
@@ -354,9 +416,16 @@  static int mvebu_uart_startup(struct uart_port *port)
 
 static void mvebu_uart_shutdown(struct uart_port *port)
 {
+	struct mvebu_uart *mvuart = to_mvuart(port);
+
 	writel(0, port->membase + UART_INTR(port));
 
-	free_irq(port->irq, port);
+	if (!mvuart->irq[UART_TX_IRQ]) {
+		devm_free_irq(port->dev, mvuart->irq[UART_IRQ_SUM], port);
+	} else {
+		devm_free_irq(port->dev, mvuart->irq[UART_RX_IRQ], port);
+		devm_free_irq(port->dev, mvuart->irq[UART_TX_IRQ], port);
+	}
 }
 
 static int mvebu_uart_baud_rate_set(struct uart_port *port, unsigned int baud)
@@ -661,15 +730,14 @@  static int uart_num_counter;
 static int mvebu_uart_probe(struct platform_device *pdev)
 {
 	struct resource *reg = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 	const struct of_device_id *match = of_match_device(mvebu_uart_of_match,
 							   &pdev->dev);
 	struct uart_port *port;
 	struct mvebu_uart *mvuart;
-	int ret, id;
+	int ret, id, irq;
 
-	if (!reg || !irq) {
-		dev_err(&pdev->dev, "no registers/irq defined\n");
+	if (!reg) {
+		dev_err(&pdev->dev, "no registers defined\n");
 		return -EINVAL;
 	}
 
@@ -700,7 +768,12 @@  static int mvebu_uart_probe(struct platform_device *pdev)
 	port->flags      = UPF_FIXED_PORT;
 	port->line       = pdev->id;
 
-	port->irq        = irq->start;
+	/*
+	 * IRQ number is not stored in this structure because we may have two of
+	 * them per port (RX and TX). Instead, use the driver UART structure
+	 * array so called ->irq[].
+	 */
+	port->irq        = 0;
 	port->irqflags   = 0;
 	port->mapbase    = reg->start;
 
@@ -735,6 +808,40 @@  static int mvebu_uart_probe(struct platform_device *pdev)
 			port->uartclk = clk_get_rate(mvuart->clk);
 	}
 
+	/* Manage interrupts */
+	memset(mvuart->irq, 0, UART_IRQ_COUNT);
+	if (platform_irq_count(pdev) == 1) {
+		/* Old bindings: no name on the single unamed UART0 IRQ */
+		irq = platform_get_irq(pdev, 0);
+		if (irq < 0) {
+			dev_err(&pdev->dev, "unable to get UART IRQ\n");
+			return irq;
+		}
+
+		mvuart->irq[UART_IRQ_SUM] = irq;
+	} else {
+		/*
+		 * New bindings: named interrupts (RX, TX) for both UARTS,
+		 * only make use of uart-rx and uart-tx interrupts, do not use
+		 * uart-sum of UART0 port.
+		 */
+		irq = platform_get_irq_byname(pdev, "uart-rx");
+		if (irq < 0) {
+			dev_err(&pdev->dev, "unable to get 'uart-rx' IRQ\n");
+			return irq;
+		}
+
+		mvuart->irq[UART_RX_IRQ] = irq;
+
+		irq = platform_get_irq_byname(pdev, "uart-tx");
+		if (irq < 0) {
+			dev_err(&pdev->dev, "unable to get 'uart-tx' IRQ\n");
+			return irq;
+		}
+
+		mvuart->irq[UART_TX_IRQ] = irq;
+	}
+
 	/* UART Soft Reset*/
 	writel(CTRL_SOFT_RST, port->membase + UART_CTRL(port));
 	udelay(1);