serial: max310x: Fix RS485 handling
authorAlexander Shiyan <shc_work@mail.ru>
Tue, 7 Jun 2016 15:59:21 +0000 (18:59 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 25 Jun 2016 20:55:35 +0000 (13:55 -0700)
RS485 IOCTLs uses spinlocks, so move regmap functions into a separate
work queue.

Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/max310x.c

index 3f6e0ab725fe29e1bfe9355c824528507d38b2aa..bf6e3e08ec675fd3cda9b4b77e9f8a85c3baf5f5 100644 (file)
@@ -262,6 +262,7 @@ struct max310x_one {
        struct uart_port        port;
        struct work_struct      tx_work;
        struct work_struct      md_work;
+       struct work_struct      rs_work;
 };
 
 struct max310x_port {
@@ -877,36 +878,45 @@ static void max310x_set_termios(struct uart_port *port,
        uart_update_timeout(port, termios->c_cflag, baud);
 }
 
-static int max310x_rs485_config(struct uart_port *port,
-                               struct serial_rs485 *rs485)
+static void max310x_rs_proc(struct work_struct *ws)
 {
+       struct max310x_one *one = container_of(ws, struct max310x_one, rs_work);
        unsigned int val;
 
-       if (rs485->delay_rts_before_send > 0x0f ||
-                   rs485->delay_rts_after_send > 0x0f)
-               return -ERANGE;
+       val = (one->port.rs485.delay_rts_before_send << 4) |
+               one->port.rs485.delay_rts_after_send;
+       max310x_port_write(&one->port, MAX310X_HDPIXDELAY_REG, val);
 
-       val = (rs485->delay_rts_before_send << 4) |
-               rs485->delay_rts_after_send;
-       max310x_port_write(port, MAX310X_HDPIXDELAY_REG, val);
-       if (rs485->flags & SER_RS485_ENABLED) {
-               max310x_port_update(port, MAX310X_MODE1_REG,
+       if (one->port.rs485.flags & SER_RS485_ENABLED) {
+               max310x_port_update(&one->port, MAX310X_MODE1_REG,
                                MAX310X_MODE1_TRNSCVCTRL_BIT,
                                MAX310X_MODE1_TRNSCVCTRL_BIT);
-               max310x_port_update(port, MAX310X_MODE2_REG,
+               max310x_port_update(&one->port, MAX310X_MODE2_REG,
                                MAX310X_MODE2_ECHOSUPR_BIT,
                                MAX310X_MODE2_ECHOSUPR_BIT);
        } else {
-               max310x_port_update(port, MAX310X_MODE1_REG,
+               max310x_port_update(&one->port, MAX310X_MODE1_REG,
                                MAX310X_MODE1_TRNSCVCTRL_BIT, 0);
-               max310x_port_update(port, MAX310X_MODE2_REG,
+               max310x_port_update(&one->port, MAX310X_MODE2_REG,
                                MAX310X_MODE2_ECHOSUPR_BIT, 0);
        }
+}
+
+static int max310x_rs485_config(struct uart_port *port,
+                               struct serial_rs485 *rs485)
+{
+       struct max310x_one *one = container_of(port, struct max310x_one, port);
+
+       if ((rs485->delay_rts_before_send > 0x0f) ||
+           (rs485->delay_rts_after_send > 0x0f))
+               return -ERANGE;
 
        rs485->flags &= SER_RS485_RTS_ON_SEND | SER_RS485_ENABLED;
        memset(rs485->padding, 0, sizeof(rs485->padding));
        port->rs485 = *rs485;
 
+       schedule_work(&one->rs_work);
+
        return 0;
 }
 
@@ -1214,8 +1224,10 @@ static int max310x_probe(struct device *dev, struct max310x_devtype *devtype,
                                    MAX310X_MODE1_IRQSEL_BIT);
                /* Initialize queue for start TX */
                INIT_WORK(&s->p[i].tx_work, max310x_wq_proc);
-               /* Initialize queue for changing mode */
+               /* Initialize queue for changing LOOPBACK mode */
                INIT_WORK(&s->p[i].md_work, max310x_md_proc);
+               /* Initialize queue for changing RS485 mode */
+               INIT_WORK(&s->p[i].rs_work, max310x_rs_proc);
                /* Register port */
                uart_add_one_port(&s->uart, &s->p[i].port);
                /* Go to suspend mode */
@@ -1257,6 +1269,7 @@ static int max310x_remove(struct device *dev)
        for (i = 0; i < s->uart.nr; i++) {
                cancel_work_sync(&s->p[i].tx_work);
                cancel_work_sync(&s->p[i].md_work);
+               cancel_work_sync(&s->p[i].rs_work);
                uart_remove_one_port(&s->uart, &s->p[i].port);
                s->devtype->power(&s->p[i].port, 0);
        }