[PATCH 7/8] serial: 8250_bcm2835aux: Support rs485 software emulation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Amend 8250_bcm2835aux.c to support rs485 as introduced for 8250_omap.c
by commit e490c9144cfa ("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@xxxxxxxxx>
Cc: Matwey V. Kornilov <matwey@xxxxxxxxxx>
---
 drivers/tty/serial/8250/8250_bcm2835aux.c | 62 ++++++++++++++++++++++-
 1 file changed, 60 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c
index 5cc03bf24f85..12d03e678295 100644
--- a/drivers/tty/serial/8250/8250_bcm2835aux.c
+++ b/drivers/tty/serial/8250/8250_bcm2835aux.c
@@ -6,6 +6,10 @@
  *
  * Based on 8250_lpc18xx.c:
  * Copyright (C) 2015 Joachim Eastwood <manabian@xxxxxxxxx>
+ *
+ * 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:
-- 
2.24.0




[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux