serial: sccnxp: Implement polling mode

This patch adds support for polling work mode, i.e. system is perform
periodical check chip status when IRQ-line is not connected to CPU.

Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c
index 2ced871..3a4c57e 100644
--- a/drivers/tty/serial/sccnxp.c
+++ b/drivers/tty/serial/sccnxp.c
@@ -23,6 +23,7 @@
 #include <linux/io.h>
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
+#include <linux/spinlock.h>
 #include <linux/platform_device.h>
 #include <linux/platform_data/sccnxp.h>
 
@@ -106,6 +107,7 @@
 struct sccnxp_port {
 	struct uart_driver	uart;
 	struct uart_port	port[SCCNXP_MAX_UARTS];
+	bool			opened[SCCNXP_MAX_UARTS];
 
 	const char		*name;
 	int			irq;
@@ -122,7 +124,10 @@
 	struct console		console;
 #endif
 
-	struct mutex		sccnxp_mutex;
+	spinlock_t		lock;
+
+	bool			poll;
+	struct timer_list	timer;
 
 	struct sccnxp_pdata	pdata;
 };
@@ -371,31 +376,48 @@
 		uart_write_wakeup(port);
 }
 
-static irqreturn_t sccnxp_ist(int irq, void *dev_id)
+static void sccnxp_handle_events(struct sccnxp_port *s)
 {
 	int i;
 	u8 isr;
-	struct sccnxp_port *s = (struct sccnxp_port *)dev_id;
 
-	mutex_lock(&s->sccnxp_mutex);
-
-	for (;;) {
+	do {
 		isr = sccnxp_read(&s->port[0], SCCNXP_ISR_REG);
 		isr &= s->imr;
 		if (!isr)
 			break;
 
-		dev_dbg(s->port[0].dev, "IRQ status: 0x%02x\n", isr);
-
 		for (i = 0; i < s->uart.nr; i++) {
-			if (isr & ISR_RXRDY(i))
+			if (s->opened[i] && (isr & ISR_RXRDY(i)))
 				sccnxp_handle_rx(&s->port[i]);
-			if (isr & ISR_TXRDY(i))
+			if (s->opened[i] && (isr & ISR_TXRDY(i)))
 				sccnxp_handle_tx(&s->port[i]);
 		}
-	}
+	} while (1);
+}
 
-	mutex_unlock(&s->sccnxp_mutex);
+static void sccnxp_timer(unsigned long data)
+{
+	struct sccnxp_port *s = (struct sccnxp_port *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	sccnxp_handle_events(s);
+	spin_unlock_irqrestore(&s->lock, flags);
+
+	if (!timer_pending(&s->timer))
+		mod_timer(&s->timer, jiffies +
+			  usecs_to_jiffies(s->pdata.poll_time_us));
+}
+
+static irqreturn_t sccnxp_ist(int irq, void *dev_id)
+{
+	struct sccnxp_port *s = (struct sccnxp_port *)dev_id;
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	sccnxp_handle_events(s);
+	spin_unlock_irqrestore(&s->lock, flags);
 
 	return IRQ_HANDLED;
 }
@@ -403,8 +425,9 @@
 static void sccnxp_start_tx(struct uart_port *port)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 
 	/* Set direction to output */
 	if (s->flags & SCCNXP_HAVE_IO)
@@ -412,7 +435,7 @@
 
 	sccnxp_enable_irq(port, IMR_TXRDY);
 
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static void sccnxp_stop_tx(struct uart_port *port)
@@ -423,20 +446,22 @@
 static void sccnxp_stop_rx(struct uart_port *port)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_DISABLE);
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static unsigned int sccnxp_tx_empty(struct uart_port *port)
 {
 	u8 val;
+	unsigned long flags;
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 	val = sccnxp_port_read(port, SCCNXP_SR_REG);
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 
 	return (val & SR_TXEMT) ? TIOCSER_TEMT : 0;
 }
@@ -449,28 +474,30 @@
 static void sccnxp_set_mctrl(struct uart_port *port, unsigned int mctrl)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 
 	if (!(s->flags & SCCNXP_HAVE_IO))
 		return;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 
 	sccnxp_set_bit(port, DTR_OP, mctrl & TIOCM_DTR);
 	sccnxp_set_bit(port, RTS_OP, mctrl & TIOCM_RTS);
 
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static unsigned int sccnxp_get_mctrl(struct uart_port *port)
 {
 	u8 bitmask, ipr;
+	unsigned long flags;
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
 	unsigned int mctrl = TIOCM_DSR | TIOCM_CTS | TIOCM_CAR;
 
 	if (!(s->flags & SCCNXP_HAVE_IO))
 		return mctrl;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 
 	ipr = ~sccnxp_read(port, SCCNXP_IPCR_REG);
 
@@ -499,7 +526,7 @@
 		mctrl |= (ipr & bitmask) ? TIOCM_RNG : 0;
 	}
 
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 
 	return mctrl;
 }
@@ -507,21 +534,23 @@
 static void sccnxp_break_ctl(struct uart_port *port, int break_state)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 	sccnxp_port_write(port, SCCNXP_CR_REG, break_state ?
 			  CR_CMD_START_BREAK : CR_CMD_STOP_BREAK);
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static void sccnxp_set_termios(struct uart_port *port,
 			       struct ktermios *termios, struct ktermios *old)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 	u8 mr1, mr2;
 	int baud;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 
 	/* Mask termios capabilities we don't support */
 	termios->c_cflag &= ~CMSPAR;
@@ -588,20 +617,22 @@
 	/* Update timeout according to new baud rate */
 	uart_update_timeout(port, termios->c_cflag, baud);
 
+	/* Report actual baudrate back to core */
 	if (tty_termios_baud_rate(termios))
 		tty_termios_encode_baud_rate(termios, baud, baud);
 
 	/* Enable RX & TX */
 	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_ENABLE | CR_TX_ENABLE);
 
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static int sccnxp_startup(struct uart_port *port)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 
 	if (s->flags & SCCNXP_HAVE_IO) {
 		/* Outputs are controlled manually */
@@ -620,7 +651,9 @@
 	/* Enable RX interrupt */
 	sccnxp_enable_irq(port, IMR_RXRDY);
 
-	mutex_unlock(&s->sccnxp_mutex);
+	s->opened[port->line] = 1;
+
+	spin_unlock_irqrestore(&s->lock, flags);
 
 	return 0;
 }
@@ -628,8 +661,11 @@
 static void sccnxp_shutdown(struct uart_port *port)
 {
 	struct sccnxp_port *s = dev_get_drvdata(port->dev);
+	unsigned long flags;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
+
+	s->opened[port->line] = 0;
 
 	/* Disable interrupts */
 	sccnxp_disable_irq(port, IMR_TXRDY | IMR_RXRDY);
@@ -641,7 +677,7 @@
 	if (s->flags & SCCNXP_HAVE_IO)
 		sccnxp_set_bit(port, DIR_OP, 0);
 
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static const char *sccnxp_type(struct uart_port *port)
@@ -715,10 +751,11 @@
 {
 	struct sccnxp_port *s = (struct sccnxp_port *)co->data;
 	struct uart_port *port = &s->port[co->index];
+	unsigned long flags;
 
-	mutex_lock(&s->sccnxp_mutex);
+	spin_lock_irqsave(&s->lock, flags);
 	uart_console_write(port, c, n, sccnxp_console_putchar);
-	mutex_unlock(&s->sccnxp_mutex);
+	spin_unlock_irqrestore(&s->lock, flags);
 }
 
 static int sccnxp_console_setup(struct console *co, char *options)
@@ -757,7 +794,7 @@
 	}
 	platform_set_drvdata(pdev, s);
 
-	mutex_init(&s->sccnxp_mutex);
+	spin_lock_init(&s->lock);
 
 	/* Individual chip settings */
 	switch (chiptype) {
@@ -854,11 +891,19 @@
 	} else
 		memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata));
 
-	s->irq = platform_get_irq(pdev, 0);
-	if (s->irq <= 0) {
-		dev_err(&pdev->dev, "Missing irq resource data\n");
-		ret = -ENXIO;
-		goto err_out;
+	if (pdata->poll_time_us) {
+		dev_info(&pdev->dev, "Using poll mode, resolution %u usecs\n",
+			 pdata->poll_time_us);
+		s->poll = 1;
+	}
+
+	if (!s->poll) {
+		s->irq = platform_get_irq(pdev, 0);
+		if (s->irq < 0) {
+			dev_err(&pdev->dev, "Missing irq resource data\n");
+			ret = -ENXIO;
+			goto err_out;
+		}
 	}
 
 	/* Check input frequency */
@@ -923,13 +968,23 @@
 	if (s->pdata.init)
 		s->pdata.init();
 
-	ret = devm_request_threaded_irq(&pdev->dev, s->irq, NULL, sccnxp_ist,
-					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-					dev_name(&pdev->dev), s);
-	if (!ret)
-		return 0;
+	if (!s->poll) {
+		ret = devm_request_threaded_irq(&pdev->dev, s->irq, NULL,
+						sccnxp_ist,
+						IRQF_TRIGGER_FALLING |
+						IRQF_ONESHOT,
+						dev_name(&pdev->dev), s);
+		if (!ret)
+			return 0;
 
-	dev_err(&pdev->dev, "Unable to reguest IRQ %i\n", s->irq);
+		dev_err(&pdev->dev, "Unable to reguest IRQ %i\n", s->irq);
+	} else {
+		init_timer(&s->timer);
+		setup_timer(&s->timer, sccnxp_timer, (unsigned long)s);
+		mod_timer(&s->timer, jiffies +
+			  usecs_to_jiffies(s->pdata.poll_time_us));
+		return 0;
+	}
 
 err_out:
 	platform_set_drvdata(pdev, NULL);
@@ -942,7 +997,10 @@
 	int i;
 	struct sccnxp_port *s = platform_get_drvdata(pdev);
 
-	devm_free_irq(&pdev->dev, s->irq, s);
+	if (!s->poll)
+		devm_free_irq(&pdev->dev, s->irq, s);
+	else
+		del_timer_sync(&s->timer);
 
 	for (i = 0; i < s->uart.nr; i++)
 		uart_remove_one_port(&s->uart, &s->port[i]);
diff --git a/include/linux/platform_data/sccnxp.h b/include/linux/platform_data/sccnxp.h
index 7311ccd..096de90 100644
--- a/include/linux/platform_data/sccnxp.h
+++ b/include/linux/platform_data/sccnxp.h
@@ -84,6 +84,8 @@
 	const u8		reg_shift;
 	/* Modem control lines configuration */
 	const u32		mctrl_cfg[SCCNXP_MAX_UARTS];
+	/* Timer value for polling mode (usecs) */
+	const unsigned int	poll_time_us;
 	/* Called during startup */
 	void (*init)(void);
 	/* Called before finish */