From: Jiri Slaby <jirislaby@kernel.org>
To: Tapio Reijonen <tapio.reijonen@vaisala.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org
Subject: Re: [PATCH] serial: max310x: drive RTS in software when hardware delays are too short
Date: Thu, 9 Jul 2026 12:41:03 +0200 [thread overview]
Message-ID: <158e6123-46fe-4a8c-9de4-093009a22a02@kernel.org> (raw)
In-Reply-To: <20260709-max310x-rs485-sw-delay-v1-1-454ac10b937a@vaisala.com>
On 09. 07. 26, 10:46, Tapio Reijonen wrote:
> max310x_rs485_config() rejected delay_rts_before_send and
> delay_rts_after_send values larger than 0x0f with -ERANGE, which made
> the UART core wipe port->rs485 in uart_rs485_config() and silently
> disable RS485. The HDPIXDELAY register holds the setup and hold
> delays in 4-bit-per-direction bit-times, so even values inside that
> range only encode a fraction of a millisecond at typical baud rates
> and the chip's hardware auto-RTS path cannot cover the millisecond
> range the kernel UART layer expresses.
...
> --- a/drivers/tty/serial/max310x.c
> +++ b/drivers/tty/serial/max310x.c
...
> @@ -324,6 +345,21 @@ static struct uart_driver max310x_uart = {
>
> static DECLARE_BITMAP(max310x_lines, MAX310X_UART_NRMAX);
>
> +static ktime_t max310x_get_character_duration(const struct ktermios *termios,
> + unsigned int baud)
> +{
> + const unsigned int startstop = 2;
> + const unsigned int char_bits = startstop +
> + (((termios->c_cflag & CSIZE) == CS5) ? 5 : 0) +
> + (((termios->c_cflag & CSIZE) == CS6) ? 6 : 0) +
> + (((termios->c_cflag & CSIZE) == CS7) ? 7 : 0) +
> + (((termios->c_cflag & CSIZE) == CS8) ? 8 : 0) +
> + ((termios->c_cflag & PARENB) ? 1 : 0) +
> + ((termios->c_cflag & CSTOPB) ? 1 : 0);
Is this an open-coded tty_get_frame_size()?
> +
> + return us_to_ktime(DIV_ROUND_UP(USEC_PER_SEC * char_bits, baud));
> +}
> +
> static u8 max310x_port_read(struct uart_port *port, u8 reg)
> {
> struct max310x_one *one = to_max310x_port(port);
> @@ -680,6 +716,39 @@ 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);
> +}
> +
> +/*
> + * Drive the RS485 RTS line to match the current tx_state. This is the only
> + * place that touches RTS, and it reads tx_state rather than a fixed
> + * assert/deassert intent, so a newer assert is never clobbered by a stale
> + * release. It also arms the before-send timer once the RTS edge is on the wire,
> + * so data is never shifted before RTS is asserted.
> + */
> +static void max310x_rts_work_proc(struct work_struct *ws)
> +{
> + struct max310x_one *one = container_of(ws, struct max310x_one, rts_work);
> + struct uart_port *port = &one->port;
> + unsigned long flags;
> + bool rts_on = READ_ONCE(one->tx_state) != MAX310X_TX_OFF;
> +
> + max310x_rts_ctl(port, rts_on ?
> + (port->rs485.flags & SER_RS485_RTS_ON_SEND) :
> + (port->rs485.flags & SER_RS485_RTS_AFTER_SEND));
> +
> + spin_lock_irqsave(&port->lock, flags);
We use guard()s these days.
> + if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_BEFORE_SEND &&
> + !one->cancel_tx_delay_tmr && !hrtimer_active(&one->tx_delay_tmr))
> + hrtimer_start(&one->tx_delay_tmr,
> + ms_to_ktime(port->rs485.delay_rts_before_send),
> + HRTIMER_MODE_REL);
> + spin_unlock_irqrestore(&port->lock, flags);
> +}
> +
> static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen)
> {
> struct max310x_one *one = to_max310x_port(port);
> @@ -776,6 +845,75 @@ static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen)
> tty_flip_buffer_push(&port->state->port);
> }
>
> +static enum hrtimer_restart max310x_tmr_tx(struct hrtimer *timer)
> +{
> + struct max310x_one *one = container_of(timer, struct max310x_one,
> + tx_delay_tmr);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&one->port.lock, flags);
guard() on more places.
> + if (!one->cancel_tx_delay_tmr) {
> + if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_AFTER_SEND) {
> + /* After-send hold elapsed: drop RTS via the rts worker. */
> + WRITE_ONCE(one->tx_state, MAX310X_TX_OFF);
> + schedule_work(&one->rts_work);
> + } else {
> + WRITE_ONCE(one->tx_state, MAX310X_TX_SEND);
> + schedule_work(&one->tx_work);
> + }
> + }
> + spin_unlock_irqrestore(&one->port.lock, flags);
> +
> + return HRTIMER_NORESTART;
> +}
thanks,
--
js
suse labs
prev parent reply other threads:[~2026-07-09 10:41 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-09 8:46 [PATCH] serial: max310x: drive RTS in software when hardware delays are too short Tapio Reijonen
2026-07-09 10:41 ` Jiri Slaby [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=158e6123-46fe-4a8c-9de4-093009a22a02@kernel.org \
--to=jirislaby@kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=tapio.reijonen@vaisala.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox