serial: 8250: Provide flag for IER toggling for RS485
For RS485 mode, if SER_RS485_RX_DURING_TX is not available, the console ->write() callback needs to enable/disable Tx. It does this by calling the ->rs485_start_tx() and ->rs485_stop_tx() callbacks. However, some of these callbacks also disable/enable interrupts and makes power management calls. This causes 2 problems for console writing: 1. A console write can occur in contexts that are illegal for pm_runtime_*(). It is not even necessary for console writing to use pm_runtime_*() because a console already does this in serial8250_console_setup() and serial8250_console_exit(). 2. The console ->write() callback already handles disabling/enabling the interrupts by properly restoring the previous IER value. Add an argument @toggle_ier to the ->rs485_start_tx() and ->rs485_stop_tx() callbacks to specify if they may disable/enable receive interrupts while using pm_runtime_*(). Console writing will not allow the toggling. For all call sites other than console writing there is no functional change. Signed-off-by: John Ogness <john.ogness@linutronix.de> Reviewed-by: Petr Mladek <pmladek@suse.com> Link: https://lore.kernel.org/r/20250107212702.169493-5-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>pull/1136/head
parent
95a1b409ba
commit
910ef438e9
|
|
@ -231,8 +231,8 @@ void serial8250_rpm_put_tx(struct uart_8250_port *p);
|
|||
|
||||
int serial8250_em485_config(struct uart_port *port, struct ktermios *termios,
|
||||
struct serial_rs485 *rs485);
|
||||
void serial8250_em485_start_tx(struct uart_8250_port *p);
|
||||
void serial8250_em485_stop_tx(struct uart_8250_port *p);
|
||||
void serial8250_em485_start_tx(struct uart_8250_port *p, bool toggle_ier);
|
||||
void serial8250_em485_stop_tx(struct uart_8250_port *p, bool toggle_ier);
|
||||
void serial8250_em485_destroy(struct uart_8250_port *p);
|
||||
extern struct serial_rs485 serial8250_em485_supported;
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ struct bcm2835aux_data {
|
|||
u32 cntl;
|
||||
};
|
||||
|
||||
static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up)
|
||||
static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up, bool toggle_ier)
|
||||
{
|
||||
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
|
||||
struct bcm2835aux_data *data = dev_get_drvdata(up->port.dev);
|
||||
|
|
@ -65,7 +65,7 @@ static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up)
|
|||
serial8250_out_MCR(up, UART_MCR_RTS);
|
||||
}
|
||||
|
||||
static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up)
|
||||
static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up, bool toggle_ier)
|
||||
{
|
||||
if (up->port.rs485.flags & SER_RS485_RTS_AFTER_SEND)
|
||||
serial8250_out_MCR(up, 0);
|
||||
|
|
|
|||
|
|
@ -365,7 +365,7 @@ static void omap8250_restore_regs(struct uart_8250_port *up)
|
|||
|
||||
if (up->port.rs485.flags & SER_RS485_ENABLED &&
|
||||
up->port.rs485_config == serial8250_em485_config)
|
||||
serial8250_em485_stop_tx(up);
|
||||
serial8250_em485_stop_tx(up, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ static int serial8250_em485_init(struct uart_8250_port *p)
|
|||
|
||||
deassert_rts:
|
||||
if (p->em485->tx_stopped)
|
||||
p->rs485_stop_tx(p);
|
||||
p->rs485_stop_tx(p, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1398,10 +1398,11 @@ static void serial8250_stop_rx(struct uart_port *port)
|
|||
/**
|
||||
* serial8250_em485_stop_tx() - generic ->rs485_stop_tx() callback
|
||||
* @p: uart 8250 port
|
||||
* @toggle_ier: true to allow enabling receive interrupts
|
||||
*
|
||||
* Generic callback usable by 8250 uart drivers to stop rs485 transmission.
|
||||
*/
|
||||
void serial8250_em485_stop_tx(struct uart_8250_port *p)
|
||||
void serial8250_em485_stop_tx(struct uart_8250_port *p, bool toggle_ier)
|
||||
{
|
||||
unsigned char mcr = serial8250_in_MCR(p);
|
||||
|
||||
|
|
@ -1422,8 +1423,10 @@ void serial8250_em485_stop_tx(struct uart_8250_port *p)
|
|||
if (!(p->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
|
||||
serial8250_clear_and_reinit_fifos(p);
|
||||
|
||||
p->ier |= UART_IER_RLSI | UART_IER_RDI;
|
||||
serial_port_out(&p->port, UART_IER, p->ier);
|
||||
if (toggle_ier) {
|
||||
p->ier |= UART_IER_RLSI | UART_IER_RDI;
|
||||
serial_port_out(&p->port, UART_IER, p->ier);
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(serial8250_em485_stop_tx);
|
||||
|
|
@ -1438,7 +1441,7 @@ static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t)
|
|||
serial8250_rpm_get(p);
|
||||
uart_port_lock_irqsave(&p->port, &flags);
|
||||
if (em485->active_timer == &em485->stop_tx_timer) {
|
||||
p->rs485_stop_tx(p);
|
||||
p->rs485_stop_tx(p, true);
|
||||
em485->active_timer = NULL;
|
||||
em485->tx_stopped = true;
|
||||
}
|
||||
|
|
@ -1470,7 +1473,7 @@ static void __stop_tx_rs485(struct uart_8250_port *p, u64 stop_delay)
|
|||
em485->active_timer = &em485->stop_tx_timer;
|
||||
hrtimer_start(&em485->stop_tx_timer, ns_to_ktime(stop_delay), HRTIMER_MODE_REL);
|
||||
} else {
|
||||
p->rs485_stop_tx(p);
|
||||
p->rs485_stop_tx(p, true);
|
||||
em485->active_timer = NULL;
|
||||
em485->tx_stopped = true;
|
||||
}
|
||||
|
|
@ -1559,6 +1562,7 @@ static inline void __start_tx(struct uart_port *port)
|
|||
/**
|
||||
* serial8250_em485_start_tx() - generic ->rs485_start_tx() callback
|
||||
* @up: uart 8250 port
|
||||
* @toggle_ier: true to allow disabling receive interrupts
|
||||
*
|
||||
* Generic callback usable by 8250 uart drivers to start rs485 transmission.
|
||||
* Assumes that setting the RTS bit in the MCR register means RTS is high.
|
||||
|
|
@ -1566,11 +1570,11 @@ static inline void __start_tx(struct uart_port *port)
|
|||
* stoppable by disabling the UART_IER_RDI interrupt. (Some chips set the
|
||||
* UART_LSR_DR bit even when UART_IER_RDI is disabled, foiling this approach.)
|
||||
*/
|
||||
void serial8250_em485_start_tx(struct uart_8250_port *up)
|
||||
void serial8250_em485_start_tx(struct uart_8250_port *up, bool toggle_ier)
|
||||
{
|
||||
unsigned char mcr = serial8250_in_MCR(up);
|
||||
|
||||
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX))
|
||||
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX) && toggle_ier)
|
||||
serial8250_stop_rx(&up->port);
|
||||
|
||||
if (up->port.rs485.flags & SER_RS485_RTS_ON_SEND)
|
||||
|
|
@ -1604,7 +1608,7 @@ static bool start_tx_rs485(struct uart_port *port)
|
|||
if (em485->tx_stopped) {
|
||||
em485->tx_stopped = false;
|
||||
|
||||
up->rs485_start_tx(up);
|
||||
up->rs485_start_tx(up, true);
|
||||
|
||||
if (up->port.rs485.delay_rts_before_send > 0) {
|
||||
em485->active_timer = &em485->start_tx_timer;
|
||||
|
|
@ -3424,7 +3428,7 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
|
|||
|
||||
if (em485) {
|
||||
if (em485->tx_stopped)
|
||||
up->rs485_start_tx(up);
|
||||
up->rs485_start_tx(up, false);
|
||||
mdelay(port->rs485.delay_rts_before_send);
|
||||
}
|
||||
|
||||
|
|
@ -3462,7 +3466,7 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
|
|||
if (em485) {
|
||||
mdelay(port->rs485.delay_rts_after_send);
|
||||
if (em485->tx_stopped)
|
||||
up->rs485_stop_tx(up);
|
||||
up->rs485_stop_tx(up, false);
|
||||
}
|
||||
|
||||
serial_port_out(port, UART_IER, ier);
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ struct uart_8250_port {
|
|||
void (*dl_write)(struct uart_8250_port *up, u32 value);
|
||||
|
||||
struct uart_8250_em485 *em485;
|
||||
void (*rs485_start_tx)(struct uart_8250_port *);
|
||||
void (*rs485_stop_tx)(struct uart_8250_port *);
|
||||
void (*rs485_start_tx)(struct uart_8250_port *up, bool toggle_ier);
|
||||
void (*rs485_stop_tx)(struct uart_8250_port *up, bool toggle_ier);
|
||||
|
||||
/* Serial port overrun backoff */
|
||||
struct delayed_work overrun_backoff;
|
||||
|
|
|
|||
Loading…
Reference in New Issue