From c112653b89e0cea4aa2ea26fcfbbb30a2193339e Mon Sep 17 00:00:00 2001 From: Lech Perczak Date: Mon, 21 Feb 2022 11:56:13 +0100 Subject: sc16is7xx: Preserve EFR bits on update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preserve unaffected bits state when accessing EFR register. This prevents hardware flow control bits from being cleared on enhanced functions access. Signed-off-by: Lech Perczak Signed-off-by: Tomasz Moń Link: https://lore.kernel.org/r/20220221105618.3503470-2-tomasz.mon@camlingroup.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 64e7e6c8145f..3a808b2cae4f 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -289,6 +289,14 @@ * XON1, XON2, XOFF1 and * XOFF2 */ +#define SC16IS7XX_EFR_FLOWCTRL_BITS (SC16IS7XX_EFR_AUTORTS_BIT | \ + SC16IS7XX_EFR_AUTOCTS_BIT | \ + SC16IS7XX_EFR_XOFF2_DETECT_BIT | \ + SC16IS7XX_EFR_SWFLOW3_BIT | \ + SC16IS7XX_EFR_SWFLOW2_BIT | \ + SC16IS7XX_EFR_SWFLOW1_BIT | \ + SC16IS7XX_EFR_SWFLOW0_BIT) + /* Misc definitions */ #define SC16IS7XX_FIFO_SIZE (64) @@ -523,8 +531,10 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud) /* Enable enhanced features */ regcache_cache_bypass(s->regmap, true); - sc16is7xx_port_write(port, SC16IS7XX_EFR_REG, - SC16IS7XX_EFR_ENABLE_BIT); + sc16is7xx_port_update(port, SC16IS7XX_EFR_REG, + SC16IS7XX_EFR_ENABLE_BIT, + SC16IS7XX_EFR_ENABLE_BIT); + regcache_cache_bypass(s->regmap, false); /* Put LCR back to the normal mode */ @@ -932,7 +942,10 @@ static void sc16is7xx_set_termios(struct uart_port *port, if (termios->c_iflag & IXOFF) flow |= SC16IS7XX_EFR_SWFLOW1_BIT; - sc16is7xx_port_write(port, SC16IS7XX_EFR_REG, flow); + sc16is7xx_port_update(port, + SC16IS7XX_EFR_REG, + SC16IS7XX_EFR_FLOWCTRL_BITS, + flow); regcache_cache_bypass(s->regmap, false); /* Update LCR register */ @@ -1007,8 +1020,9 @@ static int sc16is7xx_startup(struct uart_port *port) regcache_cache_bypass(s->regmap, true); /* Enable write access to enhanced features and internal clock div */ - sc16is7xx_port_write(port, SC16IS7XX_EFR_REG, - SC16IS7XX_EFR_ENABLE_BIT); + sc16is7xx_port_update(port, SC16IS7XX_EFR_REG, + SC16IS7XX_EFR_ENABLE_BIT, + SC16IS7XX_EFR_ENABLE_BIT); /* Enable TCR/TLR */ sc16is7xx_port_update(port, SC16IS7XX_MCR_REG, -- cgit v1.2.3-59-g8ed1b From 6cca8f154a296ead948d1ff0f6c63febf84a8856 Mon Sep 17 00:00:00 2001 From: Lech Perczak Date: Mon, 21 Feb 2022 11:56:14 +0100 Subject: sc16is7xx: Update status lines in single call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RTS, DTR and LOOP bits can be updated in a single MCR register update. This reduces the number of (slow) SPI/I2C bus transactions. Signed-off-by: Lech Perczak Signed-off-by: Tomasz Moń Link: https://lore.kernel.org/r/20220221105618.3503470-3-tomasz.mon@camlingroup.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 3a808b2cae4f..a2873acbabd2 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -784,19 +784,24 @@ static void sc16is7xx_reg_proc(struct kthread_work *ws) spin_unlock_irqrestore(&one->port.lock, irqflags); if (config.flags & SC16IS7XX_RECONF_MD) { + u8 mcr = 0; + + /* Device ignores RTS setting when hardware flow is enabled */ + if (one->port.mctrl & TIOCM_RTS) + mcr |= SC16IS7XX_MCR_RTS_BIT; + + if (one->port.mctrl & TIOCM_DTR) + mcr |= SC16IS7XX_MCR_DTR_BIT; + + if (one->port.mctrl & TIOCM_LOOP) + mcr |= SC16IS7XX_MCR_LOOP_BIT; sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG, + SC16IS7XX_MCR_RTS_BIT | + SC16IS7XX_MCR_DTR_BIT | SC16IS7XX_MCR_LOOP_BIT, - (one->port.mctrl & TIOCM_LOOP) ? - SC16IS7XX_MCR_LOOP_BIT : 0); - sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG, - SC16IS7XX_MCR_RTS_BIT, - (one->port.mctrl & TIOCM_RTS) ? - SC16IS7XX_MCR_RTS_BIT : 0); - sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG, - SC16IS7XX_MCR_DTR_BIT, - (one->port.mctrl & TIOCM_DTR) ? - SC16IS7XX_MCR_DTR_BIT : 0); + mcr); } + if (config.flags & SC16IS7XX_RECONF_IER) sc16is7xx_port_update(&one->port, SC16IS7XX_IER_REG, config.ier_clear, 0); -- cgit v1.2.3-59-g8ed1b From 679875d1d8802669590ef4d69b0e7d13207ebd61 Mon Sep 17 00:00:00 2001 From: Lech Perczak Date: Mon, 21 Feb 2022 11:56:15 +0100 Subject: sc16is7xx: Separate GPIOs from modem control lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Export only the GPIOs that are not shared with hardware modem control lines. Introduce new device parameter indicating whether modem control lines are available. Signed-off-by: Lech Perczak Signed-off-by: Tomasz Moń Link: https://lore.kernel.org/r/20220221105618.3503470-4-tomasz.mon@camlingroup.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index a2873acbabd2..b7a2db9c03e2 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -306,6 +306,7 @@ struct sc16is7xx_devtype { char name[10]; int nr_gpio; int nr_uart; + int has_mctrl; }; #define SC16IS7XX_RECONF_MD (1 << 0) @@ -440,30 +441,35 @@ static const struct sc16is7xx_devtype sc16is74x_devtype = { .name = "SC16IS74X", .nr_gpio = 0, .nr_uart = 1, + .has_mctrl = 0, }; static const struct sc16is7xx_devtype sc16is750_devtype = { .name = "SC16IS750", - .nr_gpio = 8, + .nr_gpio = 4, .nr_uart = 1, + .has_mctrl = 1, }; static const struct sc16is7xx_devtype sc16is752_devtype = { .name = "SC16IS752", - .nr_gpio = 8, + .nr_gpio = 0, .nr_uart = 2, + .has_mctrl = 1, }; static const struct sc16is7xx_devtype sc16is760_devtype = { .name = "SC16IS760", - .nr_gpio = 8, + .nr_gpio = 4, .nr_uart = 1, + .has_mctrl = 1, }; static const struct sc16is7xx_devtype sc16is762_devtype = { .name = "SC16IS762", - .nr_gpio = 8, + .nr_gpio = 0, .nr_uart = 2, + .has_mctrl = 1, }; static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg) -- cgit v1.2.3-59-g8ed1b From cc4c1d05eb10c3ad4c6315f1897bc56b1e7429aa Mon Sep 17 00:00:00 2001 From: Tomasz Moń Date: Tue, 1 Mar 2022 07:03:30 +0100 Subject: sc16is7xx: Properly resume TX after stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sc16is7xx_stop_tx() clears THRI bit and thus disables THRI interrupt. This makes it possible for transmission to cease indefinitely when more than 64 characters are being sent. The sc16is7xx_handle_tx() call executed by sc16is7xx_tx_proc() can send up to FIFO length (64) characters. If more characters are written to the output buffer, then the THRI interrupt is needed. Solve the issue by enabling THRI interrupt in sc16is7xx_tx_proc(). Signed-off-by: Tomasz Moń Link: https://lore.kernel.org/r/20220301060332.2561851-2-tomasz.mon@camlingroup.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 47 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 21ae2c0b7bbe..17e79af36604 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -315,7 +315,8 @@ struct sc16is7xx_devtype { struct sc16is7xx_one_config { unsigned int flags; - u8 ier_clear; + u8 ier_mask; + u8 ier_val; }; struct sc16is7xx_one { @@ -349,6 +350,9 @@ static struct uart_driver sc16is7xx_uart = { .nr = SC16IS7XX_MAX_DEVS, }; +static void sc16is7xx_ier_set(struct uart_port *port, u8 bit); +static void sc16is7xx_stop_tx(struct uart_port *port); + #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))) @@ -651,6 +655,7 @@ static void sc16is7xx_handle_tx(struct uart_port *port) struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct circ_buf *xmit = &port->state->xmit; unsigned int txlen, to_send, i; + unsigned long flags; if (unlikely(port->x_char)) { sc16is7xx_port_write(port, SC16IS7XX_THR_REG, port->x_char); @@ -659,8 +664,12 @@ static void sc16is7xx_handle_tx(struct uart_port *port) return; } - if (uart_circ_empty(xmit) || uart_tx_stopped(port)) + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + spin_lock_irqsave(&port->lock, flags); + sc16is7xx_stop_tx(port); + spin_unlock_irqrestore(&port->lock, flags); return; + } /* Get length of data pending in circular buffer */ to_send = uart_circ_chars_pending(xmit); @@ -687,8 +696,13 @@ static void sc16is7xx_handle_tx(struct uart_port *port) sc16is7xx_fifo_write(port, to_send); } + spin_lock_irqsave(&port->lock, flags); if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + sc16is7xx_stop_tx(port); + spin_unlock_irqrestore(&port->lock, flags); } static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) @@ -751,6 +765,7 @@ static void sc16is7xx_tx_proc(struct kthread_work *ws) { struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port); struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + unsigned long flags; if ((port->rs485.flags & SER_RS485_ENABLED) && (port->rs485.delay_rts_before_send > 0)) @@ -759,6 +774,10 @@ static void sc16is7xx_tx_proc(struct kthread_work *ws) mutex_lock(&s->efr_lock); sc16is7xx_handle_tx(port); mutex_unlock(&s->efr_lock); + + spin_lock_irqsave(&port->lock, flags); + sc16is7xx_ier_set(port, SC16IS7XX_IER_THRI_BIT); + spin_unlock_irqrestore(&port->lock, flags); } static void sc16is7xx_reconf_rs485(struct uart_port *port) @@ -813,7 +832,7 @@ static void sc16is7xx_reg_proc(struct kthread_work *ws) if (config.flags & SC16IS7XX_RECONF_IER) sc16is7xx_port_update(&one->port, SC16IS7XX_IER_REG, - config.ier_clear, 0); + config.ier_mask, config.ier_val); if (config.flags & SC16IS7XX_RECONF_RS485) sc16is7xx_reconf_rs485(&one->port); @@ -824,8 +843,24 @@ 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); + lockdep_assert_held_once(&port->lock); + + one->config.flags |= SC16IS7XX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val &= ~bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void sc16is7xx_ier_set(struct uart_port *port, u8 bit) +{ + struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + + lockdep_assert_held_once(&port->lock); + one->config.flags |= SC16IS7XX_RECONF_IER; - one->config.ier_clear |= bit; + one->config.ier_mask |= bit; + one->config.ier_val |= bit; kthread_queue_work(&s->kworker, &one->reg_work); } @@ -1067,8 +1102,8 @@ static int sc16is7xx_startup(struct uart_port *port) SC16IS7XX_EFCR_TXDISABLE_BIT, 0); - /* Enable RX, TX interrupts */ - val = SC16IS7XX_IER_RDI_BIT | SC16IS7XX_IER_THRI_BIT; + /* Enable RX interrupt */ + val = SC16IS7XX_IER_RDI_BIT; sc16is7xx_port_write(port, SC16IS7XX_IER_REG, val); return 0; -- cgit v1.2.3-59-g8ed1b From 21144bab4f1191e01e1cf785720e6af99d86a347 Mon Sep 17 00:00:00 2001 From: Tomasz Moń Date: Tue, 1 Mar 2022 07:03:31 +0100 Subject: sc16is7xx: Handle modem status lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The uart_handle_cts_change() and uart_handle_dcd_change() must be called with port lock being held. Acquire the lock after reading MSR register. Do not acquire spin lock when reading MSR register because I2C/SPI port functions cannot be called with spinlocks held. Update rng and dsr counters. Wake up delta_msr_wait to allow tty notice modem status change. Co-developed-by: Lech Perczak Co-developed-by: Tomasz Moń Signed-off-by: Lech Perczak Signed-off-by: Tomasz Moń Link: https://lore.kernel.org/r/20220301060332.2561851-3-tomasz.mon@camlingroup.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 120 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 6 deletions(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 17e79af36604..5c247b4a01a9 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -324,8 +324,10 @@ struct sc16is7xx_one { u8 line; struct kthread_work tx_work; struct kthread_work reg_work; + struct kthread_delayed_work ms_work; struct sc16is7xx_one_config config; bool irda_mode; + unsigned int old_mctrl; }; struct sc16is7xx_port { @@ -705,12 +707,56 @@ static void sc16is7xx_handle_tx(struct uart_port *port) spin_unlock_irqrestore(&port->lock, flags); } +static unsigned int sc16is7xx_get_hwmctrl(struct uart_port *port) +{ + u8 msr = sc16is7xx_port_read(port, SC16IS7XX_MSR_REG); + unsigned int mctrl = 0; + + mctrl |= (msr & SC16IS7XX_MSR_CTS_BIT) ? TIOCM_CTS : 0; + mctrl |= (msr & SC16IS7XX_MSR_DSR_BIT) ? TIOCM_DSR : 0; + mctrl |= (msr & SC16IS7XX_MSR_CD_BIT) ? TIOCM_CAR : 0; + mctrl |= (msr & SC16IS7XX_MSR_RI_BIT) ? TIOCM_RNG : 0; + return mctrl; +} + +static void sc16is7xx_update_mlines(struct sc16is7xx_one *one) +{ + struct uart_port *port = &one->port; + struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + unsigned long flags; + unsigned int status, changed; + + lockdep_assert_held_once(&s->efr_lock); + + status = sc16is7xx_get_hwmctrl(port); + changed = status ^ one->old_mctrl; + + if (changed == 0) + return; + + one->old_mctrl = status; + + spin_lock_irqsave(&port->lock, flags); + if ((changed & TIOCM_RNG) && (status & TIOCM_RNG)) + port->icount.rng++; + if (changed & TIOCM_DSR) + port->icount.dsr++; + if (changed & TIOCM_CAR) + uart_handle_dcd_change(port, status & TIOCM_CAR); + if (changed & TIOCM_CTS) + uart_handle_cts_change(port, status & TIOCM_CTS); + + wake_up_interruptible(&port->state->port.delta_msr_wait); + spin_unlock_irqrestore(&port->lock, flags); +} + static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) { struct uart_port *port = &s->p[portno].port; do { unsigned int iir, rxlen; + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG); if (iir & SC16IS7XX_IIR_NO_INT_BIT) @@ -727,6 +773,11 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) if (rxlen) sc16is7xx_handle_rx(port, rxlen, iir); break; + /* CTSRTS interrupt comes only when CTS goes inactive */ + case SC16IS7XX_IIR_CTSRTS_SRC: + case SC16IS7XX_IIR_MSI_SRC: + sc16is7xx_update_mlines(one); + break; case SC16IS7XX_IIR_THRI_SRC: sc16is7xx_handle_tx(port); break; @@ -874,6 +925,30 @@ static void sc16is7xx_stop_rx(struct uart_port *port) sc16is7xx_ier_clear(port, SC16IS7XX_IER_RDI_BIT); } +static void sc16is7xx_ms_proc(struct kthread_work *ws) +{ + struct sc16is7xx_one *one = to_sc16is7xx_one(ws, ms_work.work); + struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev); + + if (one->port.state) { + mutex_lock(&s->efr_lock); + sc16is7xx_update_mlines(one); + mutex_unlock(&s->efr_lock); + + kthread_queue_delayed_work(&s->kworker, &one->ms_work, HZ); + } +} + +static void sc16is7xx_enable_ms(struct uart_port *port) +{ + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + kthread_queue_delayed_work(&s->kworker, &one->ms_work, 0); +} + static void sc16is7xx_start_tx(struct uart_port *port) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); @@ -893,10 +968,10 @@ static unsigned int sc16is7xx_tx_empty(struct uart_port *port) static unsigned int sc16is7xx_get_mctrl(struct uart_port *port) { - /* DCD and DSR are not wired and CTS/RTS is handled automatically - * so just indicate DSR and CAR asserted - */ - return TIOCM_DSR | TIOCM_CAR; + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + + /* Called with port lock taken so we can only return cached value */ + return one->old_mctrl; } static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl) @@ -920,8 +995,12 @@ static void sc16is7xx_set_termios(struct uart_port *port, struct ktermios *old) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); unsigned int lcr, flow = 0; int baud; + unsigned long flags; + + kthread_cancel_delayed_work_sync(&one->ms_work); /* Mask termios capabilities we don't support */ termios->c_cflag &= ~CMSPAR; @@ -1010,8 +1089,15 @@ static void sc16is7xx_set_termios(struct uart_port *port, /* Setup baudrate generator */ baud = sc16is7xx_set_baud(port, baud); + spin_lock_irqsave(&port->lock, flags); + /* Update timeout according to new baud rate */ uart_update_timeout(port, termios->c_cflag, baud); + + if (UART_ENABLE_MS(port, termios->c_cflag)) + sc16is7xx_enable_ms(port); + + spin_unlock_irqrestore(&port->lock, flags); } static int sc16is7xx_config_rs485(struct uart_port *port, @@ -1052,6 +1138,7 @@ 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; sc16is7xx_power(port, 1); @@ -1102,16 +1189,25 @@ static int sc16is7xx_startup(struct uart_port *port) SC16IS7XX_EFCR_TXDISABLE_BIT, 0); - /* Enable RX interrupt */ - val = SC16IS7XX_IER_RDI_BIT; + /* Enable RX, CTS change and modem lines interrupts */ + val = SC16IS7XX_IER_RDI_BIT | SC16IS7XX_IER_CTSI_BIT | + SC16IS7XX_IER_MSI_BIT; sc16is7xx_port_write(port, SC16IS7XX_IER_REG, val); + /* Enable modem status polling */ + spin_lock_irqsave(&port->lock, flags); + sc16is7xx_enable_ms(port); + spin_unlock_irqrestore(&port->lock, flags); + return 0; } static void sc16is7xx_shutdown(struct uart_port *port) { struct sc16is7xx_port *s = dev_get_drvdata(port->dev); + struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); + + kthread_cancel_delayed_work_sync(&one->ms_work); /* Disable all interrupts */ sc16is7xx_port_write(port, SC16IS7XX_IER_REG, 0); @@ -1175,6 +1271,7 @@ static const struct uart_ops sc16is7xx_ops = { .stop_tx = sc16is7xx_stop_tx, .start_tx = sc16is7xx_start_tx, .stop_rx = sc16is7xx_stop_rx, + .enable_ms = sc16is7xx_enable_ms, .break_ctl = sc16is7xx_break_ctl, .startup = sc16is7xx_startup, .shutdown = sc16is7xx_shutdown, @@ -1341,7 +1438,9 @@ static int sc16is7xx_probe(struct device *dev, s->p[i].port.uartclk = freq; s->p[i].port.rs485_config = sc16is7xx_config_rs485; s->p[i].port.ops = &sc16is7xx_ops; + s->p[i].old_mctrl = 0; s->p[i].port.line = sc16is7xx_alloc_line(); + if (s->p[i].port.line >= SC16IS7XX_MAX_DEVS) { ret = -ENOMEM; goto out_ports; @@ -1353,9 +1452,17 @@ 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); + + /* Use GPIO lines as modem status registers */ + if (devtype->has_mctrl) + sc16is7xx_port_write(&s->p[i].port, + SC16IS7XX_IOCONTROL_REG, + SC16IS7XX_IOCONTROL_MODEM_BIT); + /* Initialize kthread work structs */ kthread_init_work(&s->p[i].tx_work, sc16is7xx_tx_proc); kthread_init_work(&s->p[i].reg_work, sc16is7xx_reg_proc); + kthread_init_delayed_work(&s->p[i].ms_work, sc16is7xx_ms_proc); /* Register port */ uart_add_one_port(&sc16is7xx_uart, &s->p[i].port); @@ -1439,6 +1546,7 @@ static void sc16is7xx_remove(struct device *dev) #endif for (i = 0; i < s->devtype->nr_uart; i++) { + kthread_cancel_delayed_work_sync(&s->p[i].ms_work); uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port); clear_bit(s->p[i].port.line, &sc16is7xx_lines); sc16is7xx_power(&s->p[i].port, 0); -- cgit v1.2.3-59-g8ed1b From 6e124e58ae2e0e3f6400dce21e942a94a67a7949 Mon Sep 17 00:00:00 2001 From: Tomasz Moń Date: Tue, 1 Mar 2022 07:03:32 +0100 Subject: sc16is7xx: Set AUTOCTS and AUTORTS bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let serial core know that the chip automatically handles RTS/CTS signal. This elimines completely unnecessary I2C/SPI bus traffic. Cease reading from RX FIFO (by disabling RDI interrupt) when throttled. Eventually the FIFO will fill up and the device will drive RTS output inactive. Unthrottle by enabling back RDI interrupt. Indirectly controlling RTS via RX FIFO state seems to be the only option because RTS bit is ignored when hardware flow control is enabled. Signed-off-by: Tomasz Moń Link: https://lore.kernel.org/r/20220301060332.2561851-4-tomasz.mon@camlingroup.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 5c247b4a01a9..683dd3be010d 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -957,6 +957,29 @@ static void sc16is7xx_start_tx(struct uart_port *port) kthread_queue_work(&s->kworker, &one->tx_work); } +static void sc16is7xx_throttle(struct uart_port *port) +{ + unsigned long flags; + + /* + * Hardware flow control is enabled and thus the device ignores RTS + * value set in MCR register. Stop reading data from RX FIFO so the + * AutoRTS feature will de-activate RTS output. + */ + spin_lock_irqsave(&port->lock, flags); + sc16is7xx_ier_clear(port, SC16IS7XX_IER_RDI_BIT); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void sc16is7xx_unthrottle(struct uart_port *port) +{ + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + sc16is7xx_ier_set(port, SC16IS7XX_IER_RDI_BIT); + spin_unlock_irqrestore(&port->lock, flags); +} + static unsigned int sc16is7xx_tx_empty(struct uart_port *port) { unsigned int lsr; @@ -1062,9 +1085,13 @@ static void sc16is7xx_set_termios(struct uart_port *port, regcache_cache_bypass(s->regmap, true); sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]); sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]); - if (termios->c_cflag & CRTSCTS) + + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + if (termios->c_cflag & CRTSCTS) { flow |= SC16IS7XX_EFR_AUTOCTS_BIT | SC16IS7XX_EFR_AUTORTS_BIT; + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; + } if (termios->c_iflag & IXON) flow |= SC16IS7XX_EFR_SWFLOW3_BIT; if (termios->c_iflag & IXOFF) @@ -1270,6 +1297,8 @@ static const struct uart_ops sc16is7xx_ops = { .get_mctrl = sc16is7xx_get_mctrl, .stop_tx = sc16is7xx_stop_tx, .start_tx = sc16is7xx_start_tx, + .throttle = sc16is7xx_throttle, + .unthrottle = sc16is7xx_unthrottle, .stop_rx = sc16is7xx_stop_rx, .enable_ms = sc16is7xx_enable_ms, .break_ctl = sc16is7xx_break_ctl, -- cgit v1.2.3-59-g8ed1b From 927728a34f11b5a27f4610bdb7068317d6fdc72a Mon Sep 17 00:00:00 2001 From: Hui Wang Date: Tue, 8 Mar 2022 19:00:42 +0800 Subject: serial: sc16is7xx: Clear RS485 bits in the shutdown We tested RS485 function on an EVB which has SC16IS752, after finishing the test, we started the RS232 function test, but found the RTS is still working in the RS485 mode. That is because both startup and shutdown call port_update() to set the EFCR_REG, this will not clear the RS485 bits once the bits are set in the reconf_rs485(). To fix it, clear the RS485 bits in shutdown. Cc: Signed-off-by: Hui Wang Link: https://lore.kernel.org/r/20220308110042.108451-1-hui.wang@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/tty/serial/sc16is7xx.c') diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 683dd3be010d..91434876fcde 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1238,10 +1238,12 @@ static void sc16is7xx_shutdown(struct uart_port *port) /* Disable all interrupts */ sc16is7xx_port_write(port, SC16IS7XX_IER_REG, 0); - /* Disable TX/RX */ + /* Disable TX/RX, clear auto RS485 and RTS invert */ sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, SC16IS7XX_EFCR_RXDISABLE_BIT | - SC16IS7XX_EFCR_TXDISABLE_BIT, + SC16IS7XX_EFCR_TXDISABLE_BIT | + SC16IS7XX_EFCR_AUTO_RS485_BIT | + SC16IS7XX_EFCR_RTS_INVERT_BIT, SC16IS7XX_EFCR_RXDISABLE_BIT | SC16IS7XX_EFCR_TXDISABLE_BIT); -- cgit v1.2.3-59-g8ed1b