mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
serial: max310x: fix breaks not reaching the wire in RS485 mode
The chip's auto-RTS (MODE1.TRNSCVCTRL) asserts the RS485 transceiver
only while FIFO data is shifting out, and a break is not FIFO data: on
an RS485 port a requested break sets the TX break bit but the
transceiver is never enabled, so the break never reaches the wire.
Break-led protocols cannot work at all.
Disable auto-RTS for the break duration and drive RTS manually via the
LCR RTS bit, then restore auto-RTS when the break ends. Track the break
in tx_break and leave MODE1 alone in the rs485-config worker while it
is set - a TIOCSRS485 arriving mid-break would otherwise re-enable
auto-RTS on top of the manual RTS and release the transceiver before
the break ends; break_ctl() restores auto-RTS from the then-current
configuration when the break completes. The worker runs under
port->mutex - break_ctl() and set_termios() already do - so the
tx_break test and the MODE1 write cannot straddle a break starting or
ending, and startup() clears tx_break alongside the latched TXBREAK
bit, since a port can be closed with a break still asserted.
Only the break assertion is gated on RS485 being enabled; the restore
at break end runs unconditionally and derives MODE1 from the current
configuration. Returning early for a port whose RS485 was disabled
mid-break - that reconfigure is deferred like any other - would leak
the manually driven RTS and leave the transceiver holding the bus
indefinitely after the break ends. On a non-RS485 port the restore is
a no-op: nothing else writes the LCR RTS bit.
Fixes: 55367c620a ("serial: max310x: Add support for RS-485 mode")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-2-6ba881f81e1f@vaisala.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
3b2de448ec
commit
4efacb600b
|
|
@ -296,6 +296,7 @@ struct max310x_one {
|
|||
struct work_struct md_work;
|
||||
struct work_struct rs_work;
|
||||
struct regmap *regmap;
|
||||
bool tx_break; /* break_ctl() owns the transceiver */
|
||||
|
||||
u8 rx_buf[MAX310X_FIFO_SIZE];
|
||||
};
|
||||
|
|
@ -680,6 +681,12 @@ static void max310x_batch_read(struct uart_port *port, u8 *rxbuf, unsigned int l
|
|||
regmap_noinc_read(one->regmap, MAX310X_RHR_REG, rxbuf, len);
|
||||
}
|
||||
|
||||
static void max310x_rts_ctl(struct uart_port *port, bool rts_state)
|
||||
{
|
||||
max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_RTS_BIT,
|
||||
rts_state ? MAX310X_LCR_RTS_BIT : 0);
|
||||
}
|
||||
|
||||
static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen)
|
||||
{
|
||||
struct max310x_one *one = to_max310x_port(port);
|
||||
|
|
@ -927,9 +934,32 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|||
|
||||
static void max310x_break_ctl(struct uart_port *port, int break_state)
|
||||
{
|
||||
struct max310x_one *one = to_max310x_port(port);
|
||||
|
||||
one->tx_break = break_state;
|
||||
|
||||
max310x_port_update(port, MAX310X_LCR_REG,
|
||||
MAX310X_LCR_TXBREAK_BIT,
|
||||
break_state ? MAX310X_LCR_TXBREAK_BIT : 0);
|
||||
|
||||
/*
|
||||
* The chip's auto-RTS asserts the transceiver only while FIFO data is
|
||||
* shifting out, and a break is not FIFO data. Disable auto-RTS for the
|
||||
* break duration and drive RTS manually so the break reaches the wire;
|
||||
* restore auto-RTS when the break ends.
|
||||
*/
|
||||
if (break_state) {
|
||||
if (!(port->rs485.flags & SER_RS485_ENABLED))
|
||||
return;
|
||||
max310x_port_update(port, MAX310X_MODE1_REG,
|
||||
MAX310X_MODE1_TRNSCVCTRL_BIT, 0);
|
||||
} else {
|
||||
max310x_port_update(port, MAX310X_MODE1_REG,
|
||||
MAX310X_MODE1_TRNSCVCTRL_BIT,
|
||||
(port->rs485.flags & SER_RS485_ENABLED) ?
|
||||
MAX310X_MODE1_TRNSCVCTRL_BIT : 0);
|
||||
}
|
||||
max310x_rts_ctl(port, break_state);
|
||||
}
|
||||
|
||||
static void max310x_set_termios(struct uart_port *port,
|
||||
|
|
@ -1053,6 +1083,13 @@ static void max310x_rs_proc(struct work_struct *ws)
|
|||
struct max310x_one *one = container_of(ws, struct max310x_one, rs_work);
|
||||
unsigned int delay, mode1 = 0, mode2 = 0;
|
||||
|
||||
/*
|
||||
* Serialize against break_ctl() and set_termios(), which run under
|
||||
* port->mutex: the tx_break test below and the MODE1 write must not
|
||||
* straddle a break starting or ending.
|
||||
*/
|
||||
guard(mutex)(&one->port.state->port.mutex);
|
||||
|
||||
delay = (one->port.rs485.delay_rts_before_send << 4) |
|
||||
one->port.rs485.delay_rts_after_send;
|
||||
max310x_port_write(&one->port, MAX310X_HDPIXDELAY_REG, delay);
|
||||
|
|
@ -1064,8 +1101,14 @@ static void max310x_rs_proc(struct work_struct *ws)
|
|||
mode2 = MAX310X_MODE2_ECHOSUPR_BIT;
|
||||
}
|
||||
|
||||
max310x_port_update(&one->port, MAX310X_MODE1_REG,
|
||||
MAX310X_MODE1_TRNSCVCTRL_BIT, mode1);
|
||||
/*
|
||||
* A break owns the transceiver: break_ctl() disabled auto-RTS and
|
||||
* drives RTS manually, and restores it when the break ends. Leave
|
||||
* MODE1 alone meanwhile or the break goes undriven mid-way.
|
||||
*/
|
||||
if (!one->tx_break)
|
||||
max310x_port_update(&one->port, MAX310X_MODE1_REG,
|
||||
MAX310X_MODE1_TRNSCVCTRL_BIT, mode1);
|
||||
max310x_port_update(&one->port, MAX310X_MODE2_REG,
|
||||
MAX310X_MODE2_ECHOSUPR_BIT, mode2);
|
||||
}
|
||||
|
|
@ -1088,6 +1131,7 @@ static int max310x_rs485_config(struct uart_port *port, struct ktermios *termios
|
|||
|
||||
static int max310x_startup(struct uart_port *port)
|
||||
{
|
||||
struct max310x_one *one = to_max310x_port(port);
|
||||
unsigned int val;
|
||||
|
||||
max310x_power(port, 1);
|
||||
|
|
@ -1097,6 +1141,7 @@ static int max310x_startup(struct uart_port *port)
|
|||
* closed with a break still asserted, and set_termios() no longer
|
||||
* rewrites it.
|
||||
*/
|
||||
one->tx_break = false;
|
||||
max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TXBREAK_BIT, 0);
|
||||
|
||||
/* Configure MODE1 register */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user