diff options
Diffstat (limited to 'drivers/tty/serial/sc16is7xx.c')
-rw-r--r-- | drivers/tty/serial/sc16is7xx.c | 328 |
1 files changed, 243 insertions, 85 deletions
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 468354ef7baa..9e6576004a42 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -25,6 +25,7 @@ #include <linux/serial.h> #include <linux/tty.h> #include <linux/tty_flip.h> +#include <linux/spi/spi.h> #include <linux/uaccess.h> #define SC16IS7XX_NAME "sc16is7xx" @@ -300,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) @@ -615,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, @@ -628,81 +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); + + if ((port->rs485.flags & SER_RS485_ENABLED) && + (port->rs485.delay_rts_before_send > 0)) + msleep(port->rs485.delay_rts_before_send); - mutex_lock(&s->mutex); - sc16is7xx_handle_tx(&one->port); - mutex_unlock(&s->mutex); + sc16is7xx_handle_tx(port); } -static void sc16is7xx_stop_tx(struct uart_port* port) +static void sc16is7xx_reconf_rs485(struct uart_port *port) { - struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); - struct circ_buf *xmit = &one->port.state->xmit; - - /* handle rs485 */ - if (port->rs485.flags & SER_RS485_ENABLED) { - /* do nothing if current tx not yet completed */ - int lsr = sc16is7xx_port_read(port, SC16IS7XX_LSR_REG); - if (!(lsr & SC16IS7XX_LSR_TEMT_BIT)) - return; - - if (uart_circ_empty(xmit) && - (port->rs485.delay_rts_after_send > 0)) - mdelay(port->rs485.delay_rts_after_send); + 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_IER_REG, - SC16IS7XX_IER_THRI_BIT, - 0); + 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) @@ -713,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) @@ -831,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; @@ -841,21 +878,23 @@ 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); - } - sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, mask, efcr); + /* + * RTS signal is handled by HW, it's timing can't be influenced. + * However, it's sometimes useful to delay TX even without RTS + * control therefore we try to handle .delay_rts_before_send. + */ + if (rs485->delay_rts_after_send) + return -EINVAL; + } port->rs485 = *rs485; + one->config.flags |= SC16IS7XX_RECONF_RS485; + queue_kthread_work(&s->kworker, &one->reg_work); return 0; } @@ -916,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 */ @@ -926,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) @@ -1043,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; @@ -1084,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 */ @@ -1099,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; @@ -1123,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 */ @@ -1134,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: @@ -1170,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); @@ -1204,6 +1256,75 @@ static struct regmap_config regcfg = { .precious_reg = sc16is7xx_regmap_precious, }; +#ifdef CONFIG_SERIAL_SC16IS7XX_SPI +static int sc16is7xx_spi_probe(struct spi_device *spi) +{ + struct sc16is7xx_devtype *devtype; + unsigned long flags = 0; + struct regmap *regmap; + int ret; + + /* Setup SPI bus */ + spi->bits_per_word = 8; + /* only supports mode 0 on SC16IS762 */ + spi->mode = spi->mode ? : SPI_MODE_0; + spi->max_speed_hz = spi->max_speed_hz ? : 15000000; + ret = spi_setup(spi); + if (ret) + return ret; + + if (spi->dev.of_node) { + const struct of_device_id *of_id = + of_match_device(sc16is7xx_dt_ids, &spi->dev); + + devtype = (struct sc16is7xx_devtype *)of_id->data; + } else { + const struct spi_device_id *id_entry = spi_get_device_id(spi); + + devtype = (struct sc16is7xx_devtype *)id_entry->driver_data; + flags = IRQF_TRIGGER_FALLING; + } + + regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) | + (devtype->nr_uart - 1); + regmap = devm_regmap_init_spi(spi, ®cfg); + + return sc16is7xx_probe(&spi->dev, devtype, regmap, spi->irq, flags); +} + +static int sc16is7xx_spi_remove(struct spi_device *spi) +{ + return sc16is7xx_remove(&spi->dev); +} + +static const struct spi_device_id sc16is7xx_spi_id_table[] = { + { "sc16is74x", (kernel_ulong_t)&sc16is74x_devtype, }, + { "sc16is740", (kernel_ulong_t)&sc16is74x_devtype, }, + { "sc16is741", (kernel_ulong_t)&sc16is74x_devtype, }, + { "sc16is750", (kernel_ulong_t)&sc16is750_devtype, }, + { "sc16is752", (kernel_ulong_t)&sc16is752_devtype, }, + { "sc16is760", (kernel_ulong_t)&sc16is760_devtype, }, + { "sc16is762", (kernel_ulong_t)&sc16is762_devtype, }, + { } +}; + +MODULE_DEVICE_TABLE(spi, sc16is7xx_spi_id_table); + +static struct spi_driver sc16is7xx_spi_uart_driver = { + .driver = { + .name = SC16IS7XX_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sc16is7xx_dt_ids), + }, + .probe = sc16is7xx_spi_probe, + .remove = sc16is7xx_spi_remove, + .id_table = sc16is7xx_spi_id_table, +}; + +MODULE_ALIAS("spi:sc16is7xx"); +#endif + +#ifdef CONFIG_SERIAL_SC16IS7XX_I2C static int sc16is7xx_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -1235,6 +1356,8 @@ static int sc16is7xx_i2c_remove(struct i2c_client *client) static const struct i2c_device_id sc16is7xx_i2c_id_table[] = { { "sc16is74x", (kernel_ulong_t)&sc16is74x_devtype, }, + { "sc16is740", (kernel_ulong_t)&sc16is74x_devtype, }, + { "sc16is741", (kernel_ulong_t)&sc16is74x_devtype, }, { "sc16is750", (kernel_ulong_t)&sc16is750_devtype, }, { "sc16is752", (kernel_ulong_t)&sc16is752_devtype, }, { "sc16is760", (kernel_ulong_t)&sc16is760_devtype, }, @@ -1253,8 +1376,43 @@ static struct i2c_driver sc16is7xx_i2c_uart_driver = { .remove = sc16is7xx_i2c_remove, .id_table = sc16is7xx_i2c_id_table, }; -module_i2c_driver(sc16is7xx_i2c_uart_driver); + MODULE_ALIAS("i2c:sc16is7xx"); +#endif + +static int __init sc16is7xx_init(void) +{ + int ret = 0; +#ifdef CONFIG_SERIAL_SC16IS7XX_I2C + ret = i2c_add_driver(&sc16is7xx_i2c_uart_driver); + if (ret < 0) { + pr_err("failed to init sc16is7xx i2c --> %d\n", ret); + return ret; + } +#endif + +#ifdef CONFIG_SERIAL_SC16IS7XX_SPI + ret = spi_register_driver(&sc16is7xx_spi_uart_driver); + if (ret < 0) { + pr_err("failed to init sc16is7xx spi --> %d\n", ret); + return ret; + } +#endif + return ret; +} +module_init(sc16is7xx_init); + +static void __exit sc16is7xx_exit(void) +{ +#ifdef CONFIG_SERIAL_SC16IS7XX_I2C + i2c_del_driver(&sc16is7xx_i2c_uart_driver); +#endif + +#ifdef CONFIG_SERIAL_SC16IS7XX_SPI + spi_unregister_driver(&sc16is7xx_spi_uart_driver); +#endif +} +module_exit(sc16is7xx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jon Ringle <jringle@gridpoint.com>"); |