]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
serial: sc16is7xx: Add polling mode if no IRQ pin is available
authorAndre Werner <andre.werner@systec-electronic.com>
Fri, 10 Jan 2025 07:31:04 +0000 (08:31 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 10 Jan 2025 15:07:45 +0000 (16:07 +0100)
Fall back to polling mode if no interrupt is configured because there
is no possibility to connect the interrupt pin.
If "interrupts" property is missing in devicetree the driver
uses a delayed worker to pull the state of interrupt status registers.

Signed-off-by: Andre Werner <andre.werner@systec-electronic.com>
Link: https://lore.kernel.org/r/20250110073104.1029633-2-andre.werner@systec-electronic.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/sc16is7xx.c

index a3093e09309ffe6999eeee8be97e5adbe74c18b5..7b51cdc274fd8cc311d6de4ca37d03d20a4f1fab 100644 (file)
 #define SC16IS7XX_FIFO_SIZE            (64)
 #define SC16IS7XX_GPIOS_PER_BANK       4
 
+#define SC16IS7XX_POLL_PERIOD_MS       10
 #define SC16IS7XX_RECONF_MD            BIT(0)
 #define SC16IS7XX_RECONF_IER           BIT(1)
 #define SC16IS7XX_RECONF_RS485         BIT(2)
@@ -348,6 +349,8 @@ struct sc16is7xx_port {
        u8                              mctrl_mask;
        struct kthread_worker           kworker;
        struct task_struct              *kworker_task;
+       struct kthread_delayed_work     poll_work;
+       bool                            polling;
        struct sc16is7xx_one            p[];
 };
 
@@ -861,6 +864,18 @@ static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
+static void sc16is7xx_poll_proc(struct kthread_work *ws)
+{
+       struct sc16is7xx_port *s = container_of(ws, struct sc16is7xx_port, poll_work.work);
+
+       /* Reuse standard IRQ handler. Interrupt ID is unused in this context. */
+       sc16is7xx_irq(0, s);
+
+       /* Setup delay based on SC16IS7XX_POLL_PERIOD_MS */
+       kthread_queue_delayed_work(&s->kworker, &s->poll_work,
+                                  msecs_to_jiffies(SC16IS7XX_POLL_PERIOD_MS));
+}
+
 static void sc16is7xx_tx_proc(struct kthread_work *ws)
 {
        struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port);
@@ -1149,6 +1164,7 @@ static int sc16is7xx_config_rs485(struct uart_port *port, struct ktermios *termi
 static int sc16is7xx_startup(struct uart_port *port)
 {
        struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
        unsigned int val;
        unsigned long flags;
 
@@ -1211,6 +1227,10 @@ static int sc16is7xx_startup(struct uart_port *port)
        sc16is7xx_enable_ms(port);
        uart_port_unlock_irqrestore(port, flags);
 
+       if (s->polling)
+               kthread_queue_delayed_work(&s->kworker, &s->poll_work,
+                                          msecs_to_jiffies(SC16IS7XX_POLL_PERIOD_MS));
+
        return 0;
 }
 
@@ -1232,6 +1252,9 @@ static void sc16is7xx_shutdown(struct uart_port *port)
 
        sc16is7xx_power(port, 0);
 
+       if (s->polling)
+               kthread_cancel_delayed_work_sync(&s->poll_work);
+
        kthread_flush_worker(&s->kworker);
 }
 
@@ -1538,6 +1561,11 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype,
        /* Always ask for fixed clock rate from a property. */
        device_property_read_u32(dev, "clock-frequency", &uartclk);
 
+       s->polling = !!irq;
+       if (s->polling)
+               dev_dbg(dev,
+                       "No interrupt pin definition, falling back to polling mode\n");
+
        s->clk = devm_clk_get_optional(dev, NULL);
        if (IS_ERR(s->clk))
                return PTR_ERR(s->clk);
@@ -1665,6 +1693,12 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype,
                goto out_ports;
 #endif
 
+       if (s->polling) {
+               /* Initialize kernel thread for polling */
+               kthread_init_delayed_work(&s->poll_work, sc16is7xx_poll_proc);
+               return 0;
+       }
+
        /*
         * Setup interrupt. We first try to acquire the IRQ line as level IRQ.
         * If that succeeds, we can allow sharing the interrupt as well.
@@ -1724,6 +1758,9 @@ void sc16is7xx_remove(struct device *dev)
                sc16is7xx_power(&s->p[i].port, 0);
        }
 
+       if (s->polling)
+               kthread_cancel_delayed_work_sync(&s->poll_work);
+
        kthread_flush_worker(&s->kworker);
        kthread_stop(s->kworker_task);