mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
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:
@@ -92,6 +92,7 @@ struct serial8250_config {
|
||||
#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_TXRACE BIT(5) /* UART Tx fails to set remote DR */
|
||||
#define UART_BUG_NOMSI BIT(6) /* UART has no modem status interrupt */
|
||||
|
||||
/* Module parameters */
|
||||
#define UART_NR CONFIG_SERIAL_8250_NR_UARTS
|
||||
|
||||
@@ -101,6 +101,7 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
|
||||
up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP;
|
||||
up.port.rs485_config = serial8250_em485_config;
|
||||
up.port.rs485_supported = serial8250_em485_supported;
|
||||
up.bugs |= UART_BUG_NOMSI;
|
||||
up.rs485_start_tx = bcm2835aux_rs485_start_tx;
|
||||
up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;
|
||||
|
||||
|
||||
@@ -210,6 +210,18 @@ static void serial8250_timeout(struct timer_list *t)
|
||||
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 = timer_container_of(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)
|
||||
{
|
||||
struct uart_8250_port *up = timer_container_of(up, t, timer);
|
||||
@@ -273,6 +285,9 @@ static void univ8250_setup_timer(struct uart_8250_port *up)
|
||||
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
|
||||
* hardware interrupt, we use a timer-based system. The original
|
||||
|
||||
@@ -1399,7 +1399,7 @@ static void serial8250_stop_tx(struct uart_port *port)
|
||||
{
|
||||
struct uart_8250_port *up = up_to_u8250p(port);
|
||||
|
||||
guard(serial8250_rpm)(up);
|
||||
serial8250_rpm_get(up);
|
||||
__stop_tx(up);
|
||||
|
||||
/*
|
||||
@@ -1409,6 +1409,10 @@ static void serial8250_stop_tx(struct uart_port *port)
|
||||
up->acr |= UART_ACR_TXDIS;
|
||||
serial_icr_write(up, UART_ACR, up->acr);
|
||||
}
|
||||
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)
|
||||
@@ -1522,6 +1526,9 @@ static void serial8250_start_tx(struct uart_port *port)
|
||||
/* Port locked to synchronize UART_IER access against the console. */
|
||||
lockdep_assert_held_once(&port->lock);
|
||||
|
||||
if (up->bugs & UART_BUG_NOMSI)
|
||||
timer_delete(&up->timer);
|
||||
|
||||
if (!port->x_char && kfifo_is_empty(&port->state->port.xmit_fifo))
|
||||
return;
|
||||
|
||||
@@ -1749,6 +1756,9 @@ unsigned int serial8250_modem_status(struct uart_8250_port *up)
|
||||
uart_handle_cts_change(port, status & UART_MSR_CTS);
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user