mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
serial: amba-pl011: drive the bus during break in RS485 mode
In RS485 mode the transceiver is enabled only while data is sent. A break set with TIOCSBRK therefore never reaches the bus, and protocols that start each frame with a break, like DMX512, do not work. Handle an active break like a transmission. Enable the transceiver before setting the break, after delay_rts_before_send if one is configured. When the break ends, stop transmitting unless the TX path is still sending data. Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de> Link: https://patch.msgid.link/20261013-devel-pl011-rs485-break-v2-1-31c9f80d3e3d@tipi-net.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
1b60b18a01
commit
1b6d660e03
|
|
@ -327,6 +327,7 @@ struct uart_amba_port {
|
|||
char type[12];
|
||||
ktime_t rs485_tx_drain_interval; /* nano */
|
||||
enum pl011_rs485_tx_state rs485_tx_state;
|
||||
bool rs485_break;
|
||||
struct hrtimer trigger_start_tx;
|
||||
struct hrtimer trigger_stop_tx;
|
||||
bool console_line_ended;
|
||||
|
|
@ -1279,6 +1280,11 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
|
|||
return uap->using_rx_dma && uap->dmarx.running;
|
||||
}
|
||||
|
||||
static inline bool pl011_dma_tx_queued(struct uart_amba_port *uap)
|
||||
{
|
||||
return uap->dmatx.queued;
|
||||
}
|
||||
|
||||
#else
|
||||
/* Blank functions if the DMA engine is not available */
|
||||
static inline void pl011_dma_remove(struct uart_amba_port *uap)
|
||||
|
|
@ -1330,6 +1336,11 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
|
|||
return false;
|
||||
}
|
||||
|
||||
static inline bool pl011_dma_tx_queued(struct uart_amba_port *uap)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#define pl011_dma_flush_buffer NULL
|
||||
#endif
|
||||
|
||||
|
|
@ -1357,6 +1368,10 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap)
|
|||
{
|
||||
struct uart_port *port = &uap->port;
|
||||
|
||||
/* pl011_break_ctl() stops the transmission when the break ends */
|
||||
if (uap->rs485_break)
|
||||
return;
|
||||
|
||||
if (uap->rs485_tx_state == SEND)
|
||||
uap->rs485_tx_state = WAIT_AFTER_SEND;
|
||||
|
||||
|
|
@ -1479,7 +1494,9 @@ static enum hrtimer_restart pl011_trigger_start_tx(struct hrtimer *t)
|
|||
uart_port_lock_irqsave(&uap->port, &flags);
|
||||
if (uap->rs485_tx_state == WAIT_AFTER_RTS) {
|
||||
uap->rs485_tx_state = SEND;
|
||||
pl011_start_tx(&uap->port);
|
||||
/* A break can arm the timer while TX is stopped */
|
||||
if (!uart_tx_stopped(&uap->port))
|
||||
pl011_start_tx(&uap->port);
|
||||
}
|
||||
uart_port_unlock_irqrestore(&uap->port, flags);
|
||||
|
||||
|
|
@ -1772,16 +1789,60 @@ static void pl011_break_ctl(struct uart_port *port, int break_state)
|
|||
{
|
||||
struct uart_amba_port *uap =
|
||||
container_of(port, struct uart_amba_port, port);
|
||||
struct tty_port *tport = &port->state->port;
|
||||
bool rs485 = port->rs485.flags & SER_RS485_ENABLED;
|
||||
unsigned long flags;
|
||||
unsigned int lcr_h;
|
||||
|
||||
uart_port_lock_irqsave(&uap->port, &flags);
|
||||
|
||||
/*
|
||||
* In RS485 mode the transceiver only drives the bus while
|
||||
* transmitting. Treat a break as a transmission.
|
||||
*/
|
||||
if (rs485 && break_state == -1 && !uap->rs485_break) {
|
||||
uap->rs485_break = true;
|
||||
|
||||
/* Pending data already enabled the transceiver */
|
||||
if (uap->rs485_tx_state != SEND &&
|
||||
uap->rs485_tx_state != WAIT_AFTER_RTS)
|
||||
pl011_rs485_tx_start(uap);
|
||||
|
||||
/*
|
||||
* Wait out delay_rts_before_send before starting the break.
|
||||
* The state stays WAIT_AFTER_RTS, so the TX path behaves as
|
||||
* without a break. Start it here if trigger_start_tx is late.
|
||||
*/
|
||||
if (uap->rs485_tx_state == WAIT_AFTER_RTS) {
|
||||
uart_port_unlock_irqrestore(&uap->port, flags);
|
||||
msleep(port->rs485.delay_rts_before_send);
|
||||
uart_port_lock_irqsave(&uap->port, &flags);
|
||||
|
||||
if (uap->rs485_tx_state == WAIT_AFTER_RTS) {
|
||||
hrtimer_try_to_cancel(&uap->trigger_start_tx);
|
||||
uap->rs485_tx_state = SEND;
|
||||
if (!uart_tx_stopped(port))
|
||||
pl011_start_tx(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lcr_h = pl011_read(uap, REG_LCRH_TX);
|
||||
if (break_state == -1)
|
||||
lcr_h |= UART01x_LCRH_BRK;
|
||||
else
|
||||
lcr_h &= ~UART01x_LCRH_BRK;
|
||||
pl011_write(lcr_h, uap, REG_LCRH_TX);
|
||||
|
||||
if (break_state == 0 && uap->rs485_break) {
|
||||
uap->rs485_break = false;
|
||||
/* An active TX path stops the transmission when it is done */
|
||||
if (rs485 && (uart_tx_stopped(port) ||
|
||||
(!pl011_dma_tx_queued(uap) &&
|
||||
kfifo_is_empty(&tport->xmit_fifo))))
|
||||
pl011_rs485_tx_stop(uap);
|
||||
}
|
||||
|
||||
uart_port_unlock_irqrestore(&uap->port, flags);
|
||||
}
|
||||
|
||||
|
|
@ -2102,6 +2163,7 @@ static void pl011_shutdown(struct uart_port *port)
|
|||
hrtimer_cancel(&uap->trigger_stop_tx);
|
||||
|
||||
uart_port_lock_irq(port);
|
||||
uap->rs485_break = false;
|
||||
if (uap->rs485_tx_state != OFF)
|
||||
pl011_rs485_tx_stop_now(uap);
|
||||
uart_port_unlock_irq(port);
|
||||
|
|
@ -2398,6 +2460,7 @@ static int pl011_rs485_config(struct uart_port *port, struct ktermios *termios,
|
|||
struct uart_amba_port *uap =
|
||||
container_of(port, struct uart_amba_port, port);
|
||||
|
||||
uap->rs485_break = false;
|
||||
if ((port->rs485.flags & SER_RS485_ENABLED) &&
|
||||
!(rs485->flags & SER_RS485_ENABLED)) {
|
||||
bool tx_pending = uap->rs485_tx_state == WAIT_AFTER_RTS;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user