sc16is7xx: use kworker for RS-485 configuration
[firefly-linux-kernel-4.4.55.git] / drivers / tty / serial / sc16is7xx.c
index 0509be0253991f4f004d6103b8070afbd4cf04eb..9e6576004a427e51cac6f1b59237248a706332f8 100644 (file)
@@ -301,25 +301,38 @@ struct sc16is7xx_devtype {
        int     nr_uart;
 };
 
+#define SC16IS7XX_RECONF_MD            (1 << 0)
+#define SC16IS7XX_RECONF_IER           (1 << 1)
+#define SC16IS7XX_RECONF_RS485         (1 << 2)
+
+struct sc16is7xx_one_config {
+       unsigned int                    flags;
+       u8                              ier_clear;
+};
+
 struct sc16is7xx_one {
        struct uart_port                port;
-       struct work_struct              tx_work;
-       struct work_struct              md_work;
+       struct kthread_work             tx_work;
+       struct kthread_work             reg_work;
+       struct sc16is7xx_one_config     config;
 };
 
 struct sc16is7xx_port {
        struct uart_driver              uart;
        struct sc16is7xx_devtype        *devtype;
        struct regmap                   *regmap;
-       struct mutex                    mutex;
        struct clk                      *clk;
 #ifdef CONFIG_GPIOLIB
        struct gpio_chip                gpio;
 #endif
        unsigned char                   buf[SC16IS7XX_FIFO_SIZE];
+       struct kthread_worker           kworker;
+       struct task_struct              *kworker_task;
+       struct kthread_work             irq_work;
        struct sc16is7xx_one            p[0];
 };
 
+#define to_sc16is7xx_port(p,e) ((container_of((p), struct sc16is7xx_port, e)))
 #define to_sc16is7xx_one(p,e)  ((container_of((p), struct sc16is7xx_one, e)))
 
 static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
@@ -616,9 +629,7 @@ static void sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
                                               !!(msr & SC16IS7XX_MSR_CTS_BIT));
                        break;
                case SC16IS7XX_IIR_THRI_SRC:
-                       mutex_lock(&s->mutex);
                        sc16is7xx_handle_tx(port);
-                       mutex_unlock(&s->mutex);
                        break;
                default:
                        dev_err_ratelimited(port->dev,
@@ -629,66 +640,115 @@ static void sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
        } while (1);
 }
 
-static irqreturn_t sc16is7xx_ist(int irq, void *dev_id)
+static void sc16is7xx_ist(struct kthread_work *ws)
 {
-       struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
+       struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
        int i;
 
        for (i = 0; i < s->uart.nr; ++i)
                sc16is7xx_port_irq(s, i);
+}
+
+static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
+{
+       struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
+
+       queue_kthread_work(&s->kworker, &s->irq_work);
 
        return IRQ_HANDLED;
 }
 
-static void sc16is7xx_wq_proc(struct work_struct *ws)
+static void sc16is7xx_tx_proc(struct kthread_work *ws)
 {
-       struct sc16is7xx_one *one = to_sc16is7xx_one(ws, tx_work);
-       struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev);
+       struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port);
 
-       mutex_lock(&s->mutex);
-       sc16is7xx_handle_tx(&one->port);
-       mutex_unlock(&s->mutex);
+       if ((port->rs485.flags & SER_RS485_ENABLED) &&
+           (port->rs485.delay_rts_before_send > 0))
+               msleep(port->rs485.delay_rts_before_send);
+
+       sc16is7xx_handle_tx(port);
 }
 
-static void sc16is7xx_stop_tx(struct uart_port* port)
+static void sc16is7xx_reconf_rs485(struct uart_port *port)
 {
-       sc16is7xx_port_update(port, SC16IS7XX_IER_REG,
-                             SC16IS7XX_IER_THRI_BIT,
-                             0);
+       const u32 mask = SC16IS7XX_EFCR_AUTO_RS485_BIT |
+                        SC16IS7XX_EFCR_RTS_INVERT_BIT;
+       u32 efcr = 0;
+       struct serial_rs485 *rs485 = &port->rs485;
+       unsigned long irqflags;
+
+       spin_lock_irqsave(&port->lock, irqflags);
+       if (rs485->flags & SER_RS485_ENABLED) {
+               efcr |= SC16IS7XX_EFCR_AUTO_RS485_BIT;
+
+               if (rs485->flags & SER_RS485_RTS_AFTER_SEND)
+                       efcr |= SC16IS7XX_EFCR_RTS_INVERT_BIT;
+       }
+       spin_unlock_irqrestore(&port->lock, irqflags);
+
+       sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, mask, efcr);
 }
 
-static void sc16is7xx_stop_rx(struct uart_port* port)
+static void sc16is7xx_reg_proc(struct kthread_work *ws)
 {
+       struct sc16is7xx_one *one = to_sc16is7xx_one(ws, reg_work);
+       struct sc16is7xx_one_config config;
+       unsigned long irqflags;
+
+       spin_lock_irqsave(&one->port.lock, irqflags);
+       config = one->config;
+       memset(&one->config, 0, sizeof(one->config));
+       spin_unlock_irqrestore(&one->port.lock, irqflags);
+
+       if (config.flags & SC16IS7XX_RECONF_MD)
+               sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG,
+                                     SC16IS7XX_MCR_LOOP_BIT,
+                                     (one->port.mctrl & TIOCM_LOOP) ?
+                                     SC16IS7XX_MCR_LOOP_BIT : 0);
+
+       if (config.flags & SC16IS7XX_RECONF_IER)
+               sc16is7xx_port_update(&one->port, SC16IS7XX_IER_REG,
+                                     config.ier_clear, 0);
+
+       if (config.flags & SC16IS7XX_RECONF_RS485)
+               sc16is7xx_reconf_rs485(&one->port);
+}
+
+static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
        struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
 
-       one->port.read_status_mask &= ~SC16IS7XX_LSR_DR_BIT;
-       sc16is7xx_port_update(port, SC16IS7XX_IER_REG,
-                             SC16IS7XX_LSR_DR_BIT,
-                             0);
+       one->config.flags |= SC16IS7XX_RECONF_IER;
+       one->config.ier_clear |= bit;
+       queue_kthread_work(&s->kworker, &one->reg_work);
+}
+
+static void sc16is7xx_stop_tx(struct uart_port *port)
+{
+       sc16is7xx_ier_clear(port, SC16IS7XX_IER_THRI_BIT);
+}
+
+static void sc16is7xx_stop_rx(struct uart_port *port)
+{
+       sc16is7xx_ier_clear(port, SC16IS7XX_IER_RDI_BIT);
 }
 
 static void sc16is7xx_start_tx(struct uart_port *port)
 {
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
        struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
 
-       /* handle rs485 */
-       if ((port->rs485.flags & SER_RS485_ENABLED) &&
-           (port->rs485.delay_rts_before_send > 0)) {
-               mdelay(port->rs485.delay_rts_before_send);
-       }
-
-       if (!work_pending(&one->tx_work))
-               schedule_work(&one->tx_work);
+       queue_kthread_work(&s->kworker, &one->tx_work);
 }
 
 static unsigned int sc16is7xx_tx_empty(struct uart_port *port)
 {
-       unsigned int lvl, lsr;
+       unsigned int lsr;
 
-       lvl = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG);
        lsr = sc16is7xx_port_read(port, SC16IS7XX_LSR_REG);
 
-       return ((lsr & SC16IS7XX_LSR_THRE_BIT) && !lvl) ? TIOCSER_TEMT : 0;
+       return (lsr & SC16IS7XX_LSR_TEMT_BIT) ? TIOCSER_TEMT : 0;
 }
 
 static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
@@ -699,21 +759,13 @@ static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
        return TIOCM_DSR | TIOCM_CAR;
 }
 
-static void sc16is7xx_md_proc(struct work_struct *ws)
-{
-       struct sc16is7xx_one *one = to_sc16is7xx_one(ws, md_work);
-
-       sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG,
-                             SC16IS7XX_MCR_LOOP_BIT,
-                             (one->port.mctrl & TIOCM_LOOP) ?
-                                     SC16IS7XX_MCR_LOOP_BIT : 0);
-}
-
 static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl)
 {
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
        struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
 
-       schedule_work(&one->md_work);
+       one->config.flags |= SC16IS7XX_RECONF_MD;
+       queue_kthread_work(&s->kworker, &one->reg_work);
 }
 
 static void sc16is7xx_break_ctl(struct uart_port *port, int break_state)
@@ -817,9 +869,8 @@ static void sc16is7xx_set_termios(struct uart_port *port,
 static int sc16is7xx_config_rs485(struct uart_port *port,
                                  struct serial_rs485 *rs485)
 {
-       const u32 mask = SC16IS7XX_EFCR_AUTO_RS485_BIT |
-                        SC16IS7XX_EFCR_RTS_INVERT_BIT;
-       u32 efcr = 0;
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
 
        if (rs485->flags & SER_RS485_ENABLED) {
                bool rts_during_rx, rts_during_tx;
@@ -827,13 +878,7 @@ static int sc16is7xx_config_rs485(struct uart_port *port,
                rts_during_rx = rs485->flags & SER_RS485_RTS_AFTER_SEND;
                rts_during_tx = rs485->flags & SER_RS485_RTS_ON_SEND;
 
-               efcr |= SC16IS7XX_EFCR_AUTO_RS485_BIT;
-
-               if (!rts_during_rx && rts_during_tx)
-                       /* default */;
-               else if (rts_during_rx && !rts_during_tx)
-                       efcr |= SC16IS7XX_EFCR_RTS_INVERT_BIT;
-               else
+               if (rts_during_rx == rts_during_tx)
                        dev_err(port->dev,
                                "unsupported RTS signalling on_send:%d after_send:%d - exactly one of RS485 RTS flags should be set\n",
                                rts_during_tx, rts_during_rx);
@@ -847,9 +892,9 @@ static int sc16is7xx_config_rs485(struct uart_port *port,
                        return -EINVAL;
        }
 
-       sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, mask, efcr);
-
        port->rs485 = *rs485;
+       one->config.flags |= SC16IS7XX_RECONF_RS485;
+       queue_kthread_work(&s->kworker, &one->reg_work);
 
        return 0;
 }
@@ -910,6 +955,8 @@ static int sc16is7xx_startup(struct uart_port *port)
 
 static void sc16is7xx_shutdown(struct uart_port *port)
 {
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+
        /* Disable all interrupts */
        sc16is7xx_port_write(port, SC16IS7XX_IER_REG, 0);
        /* Disable TX/RX */
@@ -920,6 +967,8 @@ static void sc16is7xx_shutdown(struct uart_port *port)
                              SC16IS7XX_EFCR_TXDISABLE_BIT);
 
        sc16is7xx_power(port, 0);
+
+       flush_kthread_worker(&s->kworker);
 }
 
 static const char *sc16is7xx_type(struct uart_port *port)
@@ -1037,6 +1086,7 @@ static int sc16is7xx_probe(struct device *dev,
                           struct sc16is7xx_devtype *devtype,
                           struct regmap *regmap, int irq, unsigned long flags)
 {
+       struct sched_param sched_param = { .sched_priority = MAX_RT_PRIO / 2 };
        unsigned long freq, *pfreq = dev_get_platdata(dev);
        int i, ret;
        struct sc16is7xx_port *s;
@@ -1078,6 +1128,16 @@ static int sc16is7xx_probe(struct device *dev,
                goto out_clk;
        }
 
+       init_kthread_worker(&s->kworker);
+       init_kthread_work(&s->irq_work, sc16is7xx_ist);
+       s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker,
+                                     "sc16is7xx");
+       if (IS_ERR(s->kworker_task)) {
+               ret = PTR_ERR(s->kworker_task);
+               goto out_uart;
+       }
+       sched_setscheduler(s->kworker_task, SCHED_FIFO, &sched_param);
+
 #ifdef CONFIG_GPIOLIB
        if (devtype->nr_gpio) {
                /* Setup GPIO cotroller */
@@ -1093,12 +1153,10 @@ static int sc16is7xx_probe(struct device *dev,
                s->gpio.can_sleep        = 1;
                ret = gpiochip_add(&s->gpio);
                if (ret)
-                       goto out_uart;
+                       goto out_thread;
        }
 #endif
 
-       mutex_init(&s->mutex);
-
        for (i = 0; i < devtype->nr_uart; ++i) {
                /* Initialize port data */
                s->p[i].port.line       = i;
@@ -1117,10 +1175,9 @@ static int sc16is7xx_probe(struct device *dev,
                sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFCR_REG,
                                     SC16IS7XX_EFCR_RXDISABLE_BIT |
                                     SC16IS7XX_EFCR_TXDISABLE_BIT);
-               /* Initialize queue for start TX */
-               INIT_WORK(&s->p[i].tx_work, sc16is7xx_wq_proc);
-               /* Initialize queue for changing mode */
-               INIT_WORK(&s->p[i].md_work, sc16is7xx_md_proc);
+               /* Initialize kthread work structs */
+               init_kthread_work(&s->p[i].tx_work, sc16is7xx_tx_proc);
+               init_kthread_work(&s->p[i].reg_work, sc16is7xx_reg_proc);
                /* Register port */
                uart_add_one_port(&s->uart, &s->p[i].port);
                /* Go to suspend mode */
@@ -1128,22 +1185,23 @@ static int sc16is7xx_probe(struct device *dev,
        }
 
        /* Setup interrupt */
-       ret = devm_request_threaded_irq(dev, irq, NULL, sc16is7xx_ist,
-                                       IRQF_ONESHOT | flags, dev_name(dev), s);
+       ret = devm_request_irq(dev, irq, sc16is7xx_irq,
+                              IRQF_ONESHOT | flags, dev_name(dev), s);
        if (!ret)
                return 0;
 
        for (i = 0; i < s->uart.nr; i++)
                uart_remove_one_port(&s->uart, &s->p[i].port);
 
-       mutex_destroy(&s->mutex);
-
 #ifdef CONFIG_GPIOLIB
        if (devtype->nr_gpio)
                gpiochip_remove(&s->gpio);
 
-out_uart:
+out_thread:
 #endif
+       kthread_stop(s->kworker_task);
+
+out_uart:
        uart_unregister_driver(&s->uart);
 
 out_clk:
@@ -1164,13 +1222,13 @@ static int sc16is7xx_remove(struct device *dev)
 #endif
 
        for (i = 0; i < s->uart.nr; i++) {
-               cancel_work_sync(&s->p[i].tx_work);
-               cancel_work_sync(&s->p[i].md_work);
                uart_remove_one_port(&s->uart, &s->p[i].port);
                sc16is7xx_power(&s->p[i].port, 0);
        }
 
-       mutex_destroy(&s->mutex);
+       flush_kthread_worker(&s->kworker);
+       kthread_stop(s->kworker_task);
+
        uart_unregister_driver(&s->uart);
        if (!IS_ERR(s->clk))
                clk_disable_unprepare(s->clk);