serial: 8250_bcm2835aux: Support rs485 software emulation
Amend 8250_bcm2835aux.c to support rs485 as introduced for 8250_omap.c
by commit e490c9144c
("tty: Add software emulated RS485 support for
8250").
The bcm2835aux differs from omap chips by inverting the meaning of RTS
in the MCR register: If the bit is clear, RTS is high. With omap, it's
apparently the other way round.
Moreover, omap achieves half-duplex mode by disabling the UART_IER_RDI
interrupt and clearing the RX FIFO when TX stops. This approach doesn't
work on bcm2835aux because the UART_LSR_DR bit is set even when
UART_IER_RDI is disabled. Consequently, serial8250_handle_irq() invokes
serial8250_rx_chars() to empty the FIFO and characters are received even
though the user requested half-duplex. Solve by disabling the receiver
using the non-standard CNTL register.
Cache that register in the driver's private data for performance. Set
the private data pointer before calling serial8250_register_8250_port()
to prevent a null pointer deref in case one of the rs485 callbacks is
invoked immediately after port registration.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Cc: Matwey V. Kornilov <matwey@sai.msu.ru>
Link: https://lore.kernel.org/r/dd86460e20a8f979b7272a0bde73640312b902b1.1582895077.git.lukas@wunner.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
058bc104f7
commit
f93bf75891
@ -6,6 +6,10 @@
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
@ -16,16 +20,64 @@
|
||||
|
||||
#include "8250.h"
|
||||
|
||||
#define BCM2835_AUX_UART_CNTL 8
|
||||
#define BCM2835_AUX_UART_CNTL_RXEN 0x01 /* Receiver enable */
|
||||
#define BCM2835_AUX_UART_CNTL_TXEN 0x02 /* Transmitter enable */
|
||||
#define BCM2835_AUX_UART_CNTL_AUTORTS 0x04 /* RTS set by RX fill level */
|
||||
#define BCM2835_AUX_UART_CNTL_AUTOCTS 0x08 /* CTS stops transmitter */
|
||||
#define BCM2835_AUX_UART_CNTL_RTS3 0x00 /* RTS set until 3 chars left */
|
||||
#define BCM2835_AUX_UART_CNTL_RTS2 0x10 /* RTS set until 2 chars left */
|
||||
#define BCM2835_AUX_UART_CNTL_RTS1 0x20 /* RTS set until 1 chars left */
|
||||
#define BCM2835_AUX_UART_CNTL_RTS4 0x30 /* RTS set until 4 chars left */
|
||||
#define BCM2835_AUX_UART_CNTL_RTSINV 0x40 /* Invert auto RTS polarity */
|
||||
#define BCM2835_AUX_UART_CNTL_CTSINV 0x80 /* Invert auto CTS polarity */
|
||||
|
||||
/**
|
||||
* struct bcm2835aux_data - driver private data of BCM2835 auxiliary UART
|
||||
* @clk: clock producer of the port's uartclk
|
||||
* @line: index of the port's serial8250_ports[] entry
|
||||
* @cntl: cached copy of CNTL register
|
||||
*/
|
||||
struct bcm2835aux_data {
|
||||
struct clk *clk;
|
||||
int line;
|
||||
u32 cntl;
|
||||
};
|
||||
|
||||
static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up)
|
||||
{
|
||||
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
|
||||
struct bcm2835aux_data *data = dev_get_drvdata(up->port.dev);
|
||||
|
||||
data->cntl &= ~BCM2835_AUX_UART_CNTL_RXEN;
|
||||
serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl);
|
||||
}
|
||||
|
||||
/*
|
||||
* On the bcm2835aux, the MCR register contains no other
|
||||
* flags besides RTS. So no need for a read-modify-write.
|
||||
*/
|
||||
if (up->port.rs485.flags & SER_RS485_RTS_ON_SEND)
|
||||
serial8250_out_MCR(up, 0);
|
||||
else
|
||||
serial8250_out_MCR(up, UART_MCR_RTS);
|
||||
}
|
||||
|
||||
static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up)
|
||||
{
|
||||
if (up->port.rs485.flags & SER_RS485_RTS_AFTER_SEND)
|
||||
serial8250_out_MCR(up, 0);
|
||||
else
|
||||
serial8250_out_MCR(up, UART_MCR_RTS);
|
||||
|
||||
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
|
||||
struct bcm2835aux_data *data = dev_get_drvdata(up->port.dev);
|
||||
|
||||
data->cntl |= BCM2835_AUX_UART_CNTL_RXEN;
|
||||
serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl);
|
||||
}
|
||||
}
|
||||
|
||||
static int bcm2835aux_serial_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct uart_8250_port up = { };
|
||||
@ -47,6 +99,14 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
|
||||
up.port.fifosize = 8;
|
||||
up.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE |
|
||||
UPF_SKIP_TEST | UPF_IOREMAP;
|
||||
up.port.rs485_config = serial8250_em485_config;
|
||||
up.rs485_start_tx = bcm2835aux_rs485_start_tx;
|
||||
up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;
|
||||
|
||||
/* initialize cached copy with power-on reset value */
|
||||
data->cntl = BCM2835_AUX_UART_CNTL_RXEN | BCM2835_AUX_UART_CNTL_TXEN;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
/* get the clock - this also enables the HW */
|
||||
data->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
@ -102,8 +162,6 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
|
||||
}
|
||||
data->line = ret;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return 0;
|
||||
|
||||
dis_clk:
|
||||
|
Loading…
Reference in New Issue
Block a user