mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control
The bcm2835aux mini UART can automatically de-assert RTS based on the RX FIFO fill level and gate the transmitter on CTS, but the driver never made use of it. Wire it up: - Enable AUTORTS/AUTOCTS (with the RTS4 threshold and inverted, i.e. conventional active-low, polarity) whenever CRTSCTS is set on ports declaring the "uart-has-rtscts" property, advertised via UPF_HARD_FLOW. - Gate the AUTORTS function on TIOCM_RTS in set_mctrl() so that clearing RTS (port close, TIOCMBIC, B0) actually de-asserts the pin while auto flow control is active. - Implement throttle()/unthrottle() by disabling the RX interrupts and letting the FIFO fill up, making the hardware de-assert RTS - the same approach as in 8250_omap. Re-sync this state on termios and rs485 changes, as those paths may re-enable the RX interrupts (or leave them disabled) behind the throttle's back. - Never enable auto flow control together with rs485, which uses RTS as the transceiver direction control. As the software CTS fallback of the serial core cannot work without a modem status interrupt, strip CRTSCTS whenever it cannot be handled in hardware. Keep it when a CTS GPIO is present, as the serial core's mctrl_gpio handling then delivers the interrupt and the software fallback keeps working. - Restore RXEN when rs485 is disabled, as the emulation may be torn down without the rs485_stop_tx() callback ever being called, which would leave the receiver disabled. Assisted-by: Claude:claude-fable-5 Signed-off-by: Jan Čermák <sairon@sairon.cz> Link: https://patch.msgid.link/20261001090432.1045270-1-sairon@sairon.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
e95442c31a
commit
ca576923c3
|
|
@ -7,9 +7,9 @@
|
|||
* Based on 8250_lpc18xx.c:
|
||||
* Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
|
||||
*
|
||||
* The bcm2835aux is capable of RTS auto flow-control, but this driver doesn't
|
||||
* take advantage of it yet. When adding support, be sure not to enable it
|
||||
* simultaneously to rs485.
|
||||
* The bcm2835aux's RTS/CTS auto flow-control is enabled for ports declaring
|
||||
* the "uart-has-rtscts" property, but never simultaneously to rs485, which
|
||||
* repurposes RTS as the transceiver direction control.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
#include "8250.h"
|
||||
|
||||
|
|
@ -34,6 +35,18 @@
|
|||
#define BCM2835_AUX_UART_CNTL_RTSINV 0x40 /* Invert auto RTS polarity */
|
||||
#define BCM2835_AUX_UART_CNTL_CTSINV 0x80 /* Invert auto CTS polarity */
|
||||
|
||||
/*
|
||||
* Set auto RTS to de-assert with 4 FIFO slots left (RTS4).
|
||||
*
|
||||
* The RTSINV/CTSINV bits select the auto flow assert level, which out of
|
||||
* reset is active-high. Set them to get the conventional active-low RTS/CTS.
|
||||
*/
|
||||
#define BCM2835_AUX_UART_CNTL_AUTORTS_MASK \
|
||||
(BCM2835_AUX_UART_CNTL_AUTORTS | BCM2835_AUX_UART_CNTL_RTS4 | \
|
||||
BCM2835_AUX_UART_CNTL_RTSINV)
|
||||
#define BCM2835_AUX_UART_CNTL_AUTOCTS_MASK \
|
||||
(BCM2835_AUX_UART_CNTL_AUTOCTS | BCM2835_AUX_UART_CNTL_CTSINV)
|
||||
|
||||
/**
|
||||
* struct bcm2835aux_data - driver private data of BCM2835 auxiliary UART
|
||||
* @clk: clock producer of the port's uartclk
|
||||
|
|
@ -46,6 +59,146 @@ struct bcm2835aux_data {
|
|||
u32 cntl;
|
||||
};
|
||||
|
||||
static bool bcm2835aux_tty_throttled(struct uart_port *port)
|
||||
{
|
||||
if (!port->state)
|
||||
return false;
|
||||
|
||||
scoped_guard(tty_port_tty, &port->state->port)
|
||||
return tty_throttled(scoped_tty());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Program the CNTL auto flow bits from port->status and mctrl */
|
||||
static void bcm2835aux_update_flow(struct uart_port *port, unsigned int mctrl)
|
||||
{
|
||||
struct bcm2835aux_data *data = dev_get_drvdata(port->dev);
|
||||
struct uart_8250_port *up = up_to_u8250p(port);
|
||||
|
||||
data->cntl &= ~(BCM2835_AUX_UART_CNTL_AUTORTS_MASK |
|
||||
BCM2835_AUX_UART_CNTL_AUTOCTS_MASK);
|
||||
if ((port->status & UPSTAT_AUTORTS) && (mctrl & TIOCM_RTS))
|
||||
data->cntl |= BCM2835_AUX_UART_CNTL_AUTORTS_MASK;
|
||||
if (port->status & UPSTAT_AUTOCTS)
|
||||
data->cntl |= BCM2835_AUX_UART_CNTL_AUTOCTS_MASK;
|
||||
|
||||
serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl);
|
||||
}
|
||||
|
||||
static void bcm2835aux_enable_rx_irq(struct uart_port *port)
|
||||
{
|
||||
struct uart_8250_port *up = up_to_u8250p(port);
|
||||
|
||||
up->ier |= UART_IER_RLSI | UART_IER_RDI;
|
||||
serial_out(up, UART_IER, up->ier);
|
||||
}
|
||||
|
||||
static void bcm2835aux_set_autoflow(struct uart_port *port, tcflag_t cflag,
|
||||
bool rs485_enabled)
|
||||
{
|
||||
bool was_enabled = port->status & UPSTAT_AUTORTS;
|
||||
bool enable;
|
||||
|
||||
/*
|
||||
* rs485 uses RTS as the transceiver direction control, which is
|
||||
* mutually exclusive with the auto flow-control of the pin.
|
||||
*/
|
||||
enable = (cflag & CRTSCTS) && (port->flags & UPF_HARD_FLOW) &&
|
||||
!rs485_enabled;
|
||||
|
||||
if (enable) {
|
||||
port->status |= UPSTAT_AUTORTS | UPSTAT_AUTOCTS;
|
||||
/* Restore the throttle state lost in startup/resume/rs485 paths */
|
||||
if (bcm2835aux_tty_throttled(port)) {
|
||||
/* let the FIFO fill up and de-assert RTS in hardware */
|
||||
port->ops->stop_rx(port);
|
||||
/* reclaim an RTS lowered by software flow control */
|
||||
if ((cflag & CBAUD) != B0)
|
||||
port->mctrl |= TIOCM_RTS;
|
||||
}
|
||||
} else {
|
||||
port->status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS);
|
||||
/* unthrottle() will no longer be called after leaving auto-RTS mode */
|
||||
if (was_enabled)
|
||||
bcm2835aux_enable_rx_irq(port);
|
||||
}
|
||||
|
||||
bcm2835aux_update_flow(port, port->mctrl);
|
||||
}
|
||||
|
||||
static void bcm2835aux_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
||||
{
|
||||
serial8250_do_set_mctrl(port, mctrl);
|
||||
bcm2835aux_update_flow(port, mctrl);
|
||||
}
|
||||
|
||||
static void bcm2835aux_set_termios(struct uart_port *port,
|
||||
struct ktermios *termios,
|
||||
const struct ktermios *old)
|
||||
{
|
||||
struct uart_8250_port *up = up_to_u8250p(port);
|
||||
bool hw_flow = (port->flags & UPF_HARD_FLOW) &&
|
||||
!(port->rs485.flags & SER_RS485_ENABLED);
|
||||
|
||||
/*
|
||||
* Strip CRTSCTS when the hardware auto flow-control cannot be
|
||||
* used, as the serial core's software CTS fallback cannot work
|
||||
* without a modem status interrupt. A CTS GPIO provides one.
|
||||
*/
|
||||
if (!hw_flow && !mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_CTS))
|
||||
termios->c_cflag &= ~CRTSCTS;
|
||||
|
||||
serial8250_do_set_termios(port, termios, old);
|
||||
|
||||
guard(uart_port_lock_irqsave)(port);
|
||||
|
||||
bcm2835aux_set_autoflow(port, termios->c_cflag,
|
||||
port->rs485.flags & SER_RS485_ENABLED);
|
||||
}
|
||||
|
||||
static void bcm2835aux_throttle(struct uart_port *port)
|
||||
{
|
||||
guard(uart_port_lock_irqsave)(port);
|
||||
|
||||
port->ops->stop_rx(port);
|
||||
}
|
||||
|
||||
static void bcm2835aux_unthrottle(struct uart_port *port)
|
||||
{
|
||||
guard(uart_port_lock_irqsave)(port);
|
||||
|
||||
bcm2835aux_enable_rx_irq(port);
|
||||
}
|
||||
|
||||
static int bcm2835aux_rs485_config(struct uart_port *port,
|
||||
struct ktermios *termios,
|
||||
struct serial_rs485 *rs485)
|
||||
{
|
||||
tcflag_t cflag = termios ? termios->c_cflag : 0;
|
||||
bool rs485_enabled;
|
||||
int ret;
|
||||
|
||||
ret = serial8250_em485_config(port, termios, rs485);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rs485_enabled = rs485->flags & SER_RS485_ENABLED;
|
||||
if (!rs485_enabled) {
|
||||
struct bcm2835aux_data *data = dev_get_drvdata(port->dev);
|
||||
|
||||
data->cntl |= BCM2835_AUX_UART_CNTL_RXEN;
|
||||
}
|
||||
|
||||
/*
|
||||
* termios is NULL when rs485 is set up from firmware properties at
|
||||
* port registration, before the port has been opened.
|
||||
*/
|
||||
bcm2835aux_set_autoflow(port, cflag, rs485_enabled);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up, bool toggle_ier)
|
||||
{
|
||||
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
|
||||
|
|
@ -99,7 +252,11 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
|
|||
up.port.dev = &pdev->dev;
|
||||
up.port.type = PORT_16550;
|
||||
up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP;
|
||||
up.port.rs485_config = serial8250_em485_config;
|
||||
up.port.set_termios = bcm2835aux_set_termios;
|
||||
up.port.set_mctrl = bcm2835aux_set_mctrl;
|
||||
up.port.throttle = bcm2835aux_throttle;
|
||||
up.port.unthrottle = bcm2835aux_unthrottle;
|
||||
up.port.rs485_config = bcm2835aux_rs485_config;
|
||||
up.port.rs485_supported = serial8250_em485_supported;
|
||||
up.rs485_start_tx = bcm2835aux_rs485_start_tx;
|
||||
up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;
|
||||
|
|
@ -134,6 +291,8 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
|
|||
ret = uart_read_port_properties(&up.port);
|
||||
if (ret)
|
||||
goto rm_swnode;
|
||||
if (device_property_read_bool(&pdev->dev, "uart-has-rtscts"))
|
||||
up.port.flags |= UPF_HARD_FLOW;
|
||||
|
||||
up.port.regshift = 2;
|
||||
up.port.fifosize = 8;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user