serial: 8250: Add NOMSI bug for bcm2835aux

The BCM2835 mini-UART has no modem status interrupt, causing all
transmission to stop, never to use, if a speed change ever happens
while the CTS signal is high.

Add a simple polling mechanism in order to allow recovery in that
situation.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>
This commit is contained in:
Phil Elwell
2023-04-24 11:48:31 +01:00
committed by Dom Cobley
parent 3cfee383ce
commit a13c474435
4 changed files with 26 additions and 0 deletions

View File

@@ -92,6 +92,7 @@ struct serial8250_config {
#define UART_BUG_NOMSR BIT(2) /* UART has buggy MSR status bits (Au1x00) */ #define UART_BUG_NOMSR BIT(2) /* UART has buggy MSR status bits (Au1x00) */
#define UART_BUG_THRE BIT(3) /* UART has buggy THRE reassertion */ #define UART_BUG_THRE BIT(3) /* UART has buggy THRE reassertion */
#define UART_BUG_TXRACE BIT(5) /* UART Tx fails to set remote DR */ #define UART_BUG_TXRACE BIT(5) /* UART Tx fails to set remote DR */
#define UART_BUG_NOMSI BIT(6) /* UART has no modem status interrupt */
#ifdef CONFIG_SERIAL_8250_SHARE_IRQ #ifdef CONFIG_SERIAL_8250_SHARE_IRQ

View File

@@ -109,6 +109,7 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
UPF_SKIP_TEST | UPF_IOREMAP; UPF_SKIP_TEST | UPF_IOREMAP;
up.port.rs485_config = serial8250_em485_config; up.port.rs485_config = serial8250_em485_config;
up.port.rs485_supported = serial8250_em485_supported; up.port.rs485_supported = serial8250_em485_supported;
up.bugs |= UART_BUG_NOMSI;
up.rs485_start_tx = bcm2835aux_rs485_start_tx; up.rs485_start_tx = bcm2835aux_rs485_start_tx;
up.rs485_stop_tx = bcm2835aux_rs485_stop_tx; up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;

View File

@@ -253,6 +253,18 @@ static void serial8250_timeout(struct timer_list *t)
mod_timer(&up->timer, jiffies + uart_poll_timeout(&up->port)); mod_timer(&up->timer, jiffies + uart_poll_timeout(&up->port));
} }
static void serial8250_cts_poll_timeout(struct timer_list *t)
{
struct uart_8250_port *up = from_timer(up, t, timer);
unsigned long flags;
spin_lock_irqsave(&up->port.lock, flags);
serial8250_modem_status(up);
spin_unlock_irqrestore(&up->port.lock, flags);
if (up->port.hw_stopped)
mod_timer(&up->timer, jiffies + 1);
}
static void serial8250_backup_timeout(struct timer_list *t) static void serial8250_backup_timeout(struct timer_list *t)
{ {
struct uart_8250_port *up = from_timer(up, t, timer); struct uart_8250_port *up = from_timer(up, t, timer);
@@ -315,6 +327,9 @@ static void univ8250_setup_timer(struct uart_8250_port *up)
uart_poll_timeout(port) + HZ / 5); uart_poll_timeout(port) + HZ / 5);
} }
if (up->bugs & UART_BUG_NOMSI)
up->timer.function = serial8250_cts_poll_timeout;
/* /*
* If the "interrupt" for this port doesn't correspond with any * If the "interrupt" for this port doesn't correspond with any
* hardware interrupt, we use a timer-based system. The original * hardware interrupt, we use a timer-based system. The original

View File

@@ -1521,6 +1521,9 @@ static void serial8250_stop_tx(struct uart_port *port)
serial_icr_write(up, UART_ACR, up->acr); serial_icr_write(up, UART_ACR, up->acr);
} }
serial8250_rpm_put(up); serial8250_rpm_put(up);
if (port->hw_stopped && (up->bugs & UART_BUG_NOMSI))
mod_timer(&up->timer, jiffies + 1);
} }
static inline void __start_tx(struct uart_port *port) static inline void __start_tx(struct uart_port *port)
@@ -1634,6 +1637,9 @@ static void serial8250_start_tx(struct uart_port *port)
/* Port locked to synchronize UART_IER access against the console. */ /* Port locked to synchronize UART_IER access against the console. */
lockdep_assert_held_once(&port->lock); lockdep_assert_held_once(&port->lock);
if (up->bugs & UART_BUG_NOMSI)
del_timer(&up->timer);
if (!port->x_char && uart_circ_empty(&port->state->xmit)) if (!port->x_char && uart_circ_empty(&port->state->xmit))
return; return;
@@ -1858,6 +1864,9 @@ unsigned int serial8250_modem_status(struct uart_8250_port *up)
uart_handle_cts_change(port, status & UART_MSR_CTS); uart_handle_cts_change(port, status & UART_MSR_CTS);
wake_up_interruptible(&port->state->port.delta_msr_wait); wake_up_interruptible(&port->state->port.delta_msr_wait);
} else if (up->bugs & UART_BUG_NOMSI && port->hw_stopped &&
status & UART_MSR_CTS) {
uart_handle_cts_change(port, status & UART_MSR_CTS);
} }
return status; return status;