Skip to content

Commit

Permalink
serial: 8250: Change rs485 delays to nsec
Browse files Browse the repository at this point in the history
WIP

UARTs with hardware support for delay after send:
* 8250_fintek.c:  fixed, 4 characters
* 8250_lpc18xx.c: configurable, 0-255 baud clock ticks (baud rate * 16)
* 8250_omap.c:    fixed, 3 bits (not yet implemented in driver)

Signed-off-by: Lukas Wunner <lukas@wunner.de>
  • Loading branch information
l1k committed Jan 23, 2022
1 parent 6379841 commit 2c08878
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 104 deletions.
18 changes: 11 additions & 7 deletions Documentation/devicetree/bindings/serial/rs485.yaml
Expand Up @@ -19,15 +19,19 @@ properties:
$ref: /schemas/types.yaml#/definitions/uint32-array
items:
items:
- description: Delay between rts signal and beginning of data sent in
milliseconds. It corresponds to the delay before sending data.
- description: "Driver Enable to Output Delay" in nanoseconds as given
in the transceiver datasheet. Specifies the delay between enabling
the driver (assertion of RTS) and transmission of the start bit.
For most transceivers it's so short that it can safely be omitted.
default: 0
maximum: 1000
- description: Delay between end of data sent and rts signal in milliseconds.
It corresponds to the delay after sending data and actual release
of the line.
maximum: 20000
- description: "Driver Propagation Delay" in nanoseconds as given in
the transceiver datasheet. Specifies the delay between transmission
of the stop bit and disabling the driver (deassertion of RTS).
The delay allows the stop bit to transition through the transceiver
before its driver is disabled.
default: 0
maximum: 1000
maximum: 20000

rs485-rts-active-low:
description: drive RTS low when sending (default is high).
Expand Down
31 changes: 29 additions & 2 deletions drivers/tty/serial/8250/8250_fintek.c
Expand Up @@ -191,6 +191,33 @@ static int fintek_8250_get_ldn_range(struct fintek_8250 *pdata, int *min,
return -ENODEV;
}

/**
* uart_get_char_time() - calculate time to transmit one character (in nsec)
* @port: uart port
*/
static unsigned int uart_get_char_time(struct uart_port *port)
{
struct tty_port *tport = &port->state->port;
struct tty_struct *tty;
unsigned int char_time;

tty = tty_port_tty_get(tport);
if (!tty)
return 0;

down_read(&tty->termios_rwsem);

if (port->uartclk)
char_time = NSEC_PER_SEC / port->uartclk *
tty_get_frame_size(tty->termios.c_cflag);
else
char_time = 0;

up_read(&tty->termios_rwsem);
tty_kref_put(tty);
return char_time;
}

static int fintek_8250_rs485_config(struct uart_port *port,
struct serial_rs485 *rs485)
{
Expand Down Expand Up @@ -222,12 +249,12 @@ static int fintek_8250_rs485_config(struct uart_port *port,
}

if (rs485->delay_rts_before_send) {
rs485->delay_rts_before_send = 1;
rs485->delay_rts_before_send = 4 * uart_get_char_time(port);
config |= TXW4C_IRA;
}

if (rs485->delay_rts_after_send) {
rs485->delay_rts_after_send = 1;
rs485->delay_rts_after_send = 4 * uart_get_char_time(port);
config |= RXW4C_IRA;
}

Expand Down
98 changes: 14 additions & 84 deletions drivers/tty/serial/8250/8250_port.c
Expand Up @@ -563,9 +563,6 @@ static void serial8250_clear_fifos(struct uart_8250_port *p)
}
}

static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t);
static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t);

void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p)
{
serial8250_clear_fifos(p);
Expand Down Expand Up @@ -619,14 +616,6 @@ static int serial8250_em485_init(struct uart_8250_port *p)
if (!p->em485)
return -ENOMEM;

hrtimer_init(&p->em485->stop_tx_timer, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
hrtimer_init(&p->em485->start_tx_timer, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
p->em485->stop_tx_timer.function = &serial8250_em485_handle_stop_tx;
p->em485->start_tx_timer.function = &serial8250_em485_handle_start_tx;
p->em485->port = p;
p->em485->active_timer = NULL;
p->em485->tx_stopped = true;

p->rs485_stop_tx(p);
Expand All @@ -652,9 +641,6 @@ void serial8250_em485_destroy(struct uart_8250_port *p)
if (!p->em485)
return;

hrtimer_cancel(&p->em485->start_tx_timer);
hrtimer_cancel(&p->em485->stop_tx_timer);

kfree(p->em485);
p->em485 = NULL;
}
Expand Down Expand Up @@ -1461,48 +1447,19 @@ void serial8250_em485_stop_tx(struct uart_8250_port *p)
}
EXPORT_SYMBOL_GPL(serial8250_em485_stop_tx);

static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t)
{
struct uart_8250_em485 *em485 = container_of(t, struct uart_8250_em485,
stop_tx_timer);
struct uart_8250_port *p = em485->port;
unsigned long flags;

serial8250_rpm_get(p);
spin_lock_irqsave(&p->port.lock, flags);
if (em485->active_timer == &em485->stop_tx_timer) {
p->rs485_stop_tx(p);
em485->active_timer = NULL;
em485->tx_stopped = true;
}
spin_unlock_irqrestore(&p->port.lock, flags);
serial8250_rpm_put(p);

return HRTIMER_NORESTART;
}

static void start_hrtimer_ms(struct hrtimer *hrt, unsigned long msec)
{
hrtimer_start(hrt, ms_to_ktime(msec), HRTIMER_MODE_REL);
}

static void __stop_tx_rs485(struct uart_8250_port *p)
{
struct uart_8250_em485 *em485 = p->em485;

if (p->port.rs485.delay_rts_after_send)
ndelay(p->port.rs485.delay_rts_after_send);

/*
* rs485_stop_tx() is going to set RTS according to config
* AND flush RX FIFO if required.
*/
if (p->port.rs485.delay_rts_after_send > 0) {
em485->active_timer = &em485->stop_tx_timer;
start_hrtimer_ms(&em485->stop_tx_timer,
p->port.rs485.delay_rts_after_send);
} else {
p->rs485_stop_tx(p);
em485->active_timer = NULL;
em485->tx_stopped = true;
}
p->rs485_stop_tx(p);
em485->tx_stopped = true;
}

static inline void __do_stop_tx(struct uart_8250_port *p)
Expand Down Expand Up @@ -1605,39 +1562,13 @@ static inline void start_tx_rs485(struct uart_port *port)
struct uart_8250_port *up = up_to_u8250p(port);
struct uart_8250_em485 *em485 = up->em485;

em485->active_timer = NULL;

if (em485->tx_stopped) {
em485->tx_stopped = false;

up->rs485_start_tx(up);

if (up->port.rs485.delay_rts_before_send > 0) {
em485->active_timer = &em485->start_tx_timer;
start_hrtimer_ms(&em485->start_tx_timer,
up->port.rs485.delay_rts_before_send);
return;
}
}

__start_tx(port);
}

static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t)
{
struct uart_8250_em485 *em485 = container_of(t, struct uart_8250_em485,
start_tx_timer);
struct uart_8250_port *p = em485->port;
unsigned long flags;

spin_lock_irqsave(&p->port.lock, flags);
if (em485->active_timer == &em485->start_tx_timer) {
__start_tx(&p->port);
em485->active_timer = NULL;
if (up->port.rs485.delay_rts_before_send)
ndelay(up->port.rs485.delay_rts_before_send);
}
spin_unlock_irqrestore(&p->port.lock, flags);

return HRTIMER_NORESTART;
}

static void serial8250_start_tx(struct uart_port *port)
Expand All @@ -1647,14 +1578,10 @@ static void serial8250_start_tx(struct uart_port *port)

serial8250_rpm_get_tx(up);

if (em485 &&
em485->active_timer == &em485->start_tx_timer)
return;

if (em485)
start_tx_rs485(port);
else
__start_tx(port);

__start_tx(port);
}

static void serial8250_throttle(struct uart_port *port)
Expand Down Expand Up @@ -3359,7 +3286,8 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
if (em485) {
if (em485->tx_stopped)
up->rs485_start_tx(up);
mdelay(port->rs485.delay_rts_before_send);
if (port->rs485.delay_rts_before_send)
ndelay(port->rs485.delay_rts_before_send);
}

uart_console_write(port, s, count, serial8250_console_putchar);
Expand All @@ -3371,7 +3299,9 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s,
wait_for_xmitr(up, BOTH_EMPTY);

if (em485) {
mdelay(port->rs485.delay_rts_after_send);
if (port->rs485.delay_rts_after_send)
ndelay(port->rs485.delay_rts_after_send);

if (em485->tx_stopped)
up->rs485_stop_tx(up);
}
Expand Down
4 changes: 2 additions & 2 deletions drivers/tty/serial/amba-pl011.c
Expand Up @@ -1272,7 +1272,7 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap)
}

if (port->rs485.delay_rts_after_send)
mdelay(port->rs485.delay_rts_after_send);
ndelay(port->rs485.delay_rts_after_send);

cr = pl011_read(uap, REG_CR);

Expand Down Expand Up @@ -1411,7 +1411,7 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap)
pl011_write(cr, uap, REG_CR);

if (port->rs485.delay_rts_before_send)
mdelay(port->rs485.delay_rts_before_send);
ndelay(port->rs485.delay_rts_before_send);

uap->rs485_tx_started = true;
}
Expand Down
23 changes: 20 additions & 3 deletions drivers/tty/serial/serial_core.c
Expand Up @@ -3290,9 +3290,26 @@ void uart_sanitize_rs485_mode(struct uart_port *port)
rs485->flags &= ~SER_RS485_RTS_AFTER_SEND;
}

/* clamp the delays to [0, 100ms] */
rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U);
rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U);
/* clamp the delays to [0, 20us] */
rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 20000U);
rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 20000U);

/*
* We originally used millisecond delays, even though transceivers
* just need a few nano- or microseconds. That left 0 and 1 as the
* only sensible values. We've since migrated to nanosecond delays.
* Interpret a delay of 1 as a legacy millisecond value and convert
* it to 10 usec. Inform users so that they update their apps and
* device trees.
*/
if (rs485->delay_rts_before_send == 1) {
dev_info(dev, "assuming 10 usec for rs485 delay before send\n");
rs485->delay_rts_before_send = 10000;
}
if (rs485->delay_rts_after_send == 1) {
dev_info(dev, "assuming 10 usec for rs485 delay after send\n");
rs485->delay_rts_after_send = 10000;
}

memset(rs485->padding, 0, sizeof(rs485->padding));
}
Expand Down
4 changes: 0 additions & 4 deletions include/linux/serial_8250.h
Expand Up @@ -77,10 +77,6 @@ struct uart_8250_ops {
};

struct uart_8250_em485 {
struct hrtimer start_tx_timer; /* "rs485 start tx" timer */
struct hrtimer stop_tx_timer; /* "rs485 stop tx" timer */
struct hrtimer *active_timer; /* pointer to active timer */
struct uart_8250_port *port; /* for hrtimer callbacks */
unsigned int tx_stopped:1; /* tx is currently stopped */
};

Expand Down
7 changes: 5 additions & 2 deletions include/uapi/linux/serial.h
Expand Up @@ -112,6 +112,9 @@ struct serial_icounter_struct {
* support. Set with TIOCSRS485 and get with TIOCGRS485 if supported by your
* platform. The set function returns the new state, with any unsupported bits
* reverted appropriately.
*
* Driver enable delay between RTS assert and send
* Driver propagation delay between send and RTS deassert
*/

struct serial_rs485 {
Expand All @@ -126,8 +129,8 @@ struct serial_rs485 {
#define SER_RS485_TERMINATE_BUS (1 << 5) /* Enable bus
termination
(if supported) */
__u32 delay_rts_before_send; /* Delay before send (milliseconds) */
__u32 delay_rts_after_send; /* Delay after send (milliseconds) */
__u32 delay_rts_before_send; /* Delay before send (nanoseconds) */
__u32 delay_rts_after_send; /* Delay after send (nanoseconds) */
__u32 padding[5]; /* Memory is cheap, new structs
are a royal PITA .. */
};
Expand Down

0 comments on commit 2c08878

Please sign in to comment.