aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/tty/serial/sc16is7xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serial/sc16is7xx.c')
-rw-r--r--drivers/tty/serial/sc16is7xx.c328
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, &regcfg);
+
+ 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>");