@@ -3,9 +3,11 @@
#include <linux/bitops.h>
#include <linux/bitfield.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/math.h>
#include <linux/property.h>
#include <linux/serial_8250.h>
#include <linux/serial_core.h>
@@ -17,9 +19,18 @@
#define DW_UART_DE_EN 0xb0 /* Driver Output Enable Register */
#define DW_UART_RE_EN 0xb4 /* Receiver Output Enable Register */
#define DW_UART_DLF 0xc0 /* Divisor Latch Fraction Register */
+#define DW_UART_RAR 0xc4 /* Receive Address Register */
+#define DW_UART_TAR 0xc8 /* Transmit Address Register */
+#define DW_UART_LCR_EXT 0xcc /* Line Extended Control Register */
#define DW_UART_CPR 0xf4 /* Component Parameter Register */
#define DW_UART_UCV 0xf8 /* UART Component Version */
+/* Receive / Transmit Address Register bits */
+#define DW_UART_ADDR_MASK GENMASK(7, 0)
+
+/* Line Status Register bits */
+#define DW_UART_LSR_ADDR_RCVD BIT(8)
+
/* Transceiver Control Register bits */
#define DW_UART_TCR_RS485_EN BIT(0)
#define DW_UART_TCR_RE_POL BIT(1)
@@ -29,6 +40,12 @@
#define DW_UART_TCR_XFER_MODE_SW_DE_OR_RE FIELD_PREP(DW_UART_TCR_XFER_MODE, 1)
#define DW_UART_TCR_XFER_MODE_DE_OR_RE FIELD_PREP(DW_UART_TCR_XFER_MODE, 2)
+/* Line Extended Control Register bits */
+#define DW_UART_LCR_EXT_DLS_E BIT(0)
+#define DW_UART_LCR_EXT_ADDR_MATCH BIT(1)
+#define DW_UART_LCR_EXT_SEND_ADDR BIT(2)
+#define DW_UART_LCR_EXT_TRANSMIT_MODE BIT(3)
+
/* Component Parameter Register bits */
#define DW_UART_CPR_ABP_DATA_WIDTH (3 << 0)
#define DW_UART_CPR_AFCE_MODE (1 << 4)
@@ -91,18 +108,132 @@ static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
serial8250_do_set_divisor(p, baud, quot, quot_frac);
}
+/*
+ * Wait until re is de-asserted for sure. An ongoing receive will keep
+ * re asserted until end of frame. Without BUSY indication available,
+ * only available course of action is to wait for the time it takes to
+ * receive one frame (there might nothing to receive but w/o BUSY the
+ * driver cannot know).
+ */
+static void dw8250_wait_re_deassert(struct uart_port *p)
+{
+ udelay(p->frame_time);
+}
+
+static void dw8250_update_rar(struct uart_port *p, u32 addr)
+{
+ u32 re_en = dw8250_readl_ext(p, DW_UART_RE_EN);
+
+ /*
+ * RAR shouldn't be changed while receiving. Thus, de-assert RE_EN
+ * if asserted and wait.
+ */
+ if (re_en)
+ dw8250_writel_ext(p, DW_UART_RE_EN, 0);
+ dw8250_wait_re_deassert(p);
+ dw8250_writel_ext(p, DW_UART_RAR, addr);
+ if (re_en)
+ dw8250_writel_ext(p, DW_UART_RE_EN, re_en);
+}
+
+static void dw8250_addrmode_setup(struct uart_port *p, bool enable_addrmode)
+{
+ struct dw8250_port_data *d = p->private_data;
+
+ if (enable_addrmode) {
+ /* Clear RAR & TAR of any previous values */
+ dw8250_writel_ext(p, DW_UART_RAR, 0);
+ dw8250_writel_ext(p, DW_UART_TAR, 0);
+ dw8250_writel_ext(p, DW_UART_LCR_EXT, DW_UART_LCR_EXT_DLS_E);
+ } else {
+ dw8250_writel_ext(p, DW_UART_LCR_EXT, 0);
+ }
+
+ d->addrmode = enable_addrmode;
+}
+
void dw8250_do_set_termios(struct uart_port *p, struct ktermios *termios, struct ktermios *old)
{
+ struct dw8250_port_data *d = p->private_data;
+
p->status &= ~UPSTAT_AUTOCTS;
if (termios->c_cflag & CRTSCTS)
p->status |= UPSTAT_AUTOCTS;
+ if (!(p->rs485.flags & SER_RS485_ENABLED) || !d->hw_rs485_support)
+ termios->c_cflag &= ~ADDRB;
+
+ if (!old || (termios->c_cflag ^ old->c_cflag) & ADDRB)
+ dw8250_addrmode_setup(p, termios->c_cflag & ADDRB);
+
serial8250_do_set_termios(p, termios, old);
+
+ if (d->addrmode) {
+ p->ignore_status_mask |= DW_UART_LSR_ADDR_RCVD;
+ p->read_status_mask |= DW_UART_LSR_ADDR_RCVD;
+ }
}
EXPORT_SYMBOL_GPL(dw8250_do_set_termios);
+static int dw8250_rs485_set_addr(struct uart_port *p, struct serial_addr *addr)
+{
+ struct dw8250_port_data *d = p->private_data;
+ u32 lcr;
+
+ if (!(p->rs485.flags & SER_RS485_ENABLED) || !d->addrmode)
+ return -EINVAL;
+
+ addr->flags &= SER_ADDR_RECV | SER_ADDR_RECV_CLEAR | SER_ADDR_DEST;
+ if (!addr->flags)
+ return -EINVAL;
+
+ lcr = dw8250_readl_ext(p, DW_UART_LCR_EXT);
+ if (addr->flags & SER_ADDR_RECV) {
+ dw8250_update_rar(p, addr->addr & DW_UART_ADDR_MASK);
+ lcr |= DW_UART_LCR_EXT_ADDR_MATCH;
+ addr->flags &= ~SER_ADDR_RECV_CLEAR;
+ } else if (addr->flags & SER_ADDR_RECV_CLEAR) {
+ lcr &= DW_UART_LCR_EXT_ADDR_MATCH;
+ }
+ if (addr->flags & SER_ADDR_DEST) {
+ dw8250_writel_ext(p, DW_UART_TAR, addr->addr & DW_UART_ADDR_MASK);
+ lcr |= DW_UART_LCR_EXT_SEND_ADDR;
+ }
+ dw8250_writel_ext(p, DW_UART_LCR_EXT, lcr);
+
+ return 0;
+}
+
+static int dw8250_rs485_get_addr(struct uart_port *p, struct serial_addr *addr)
+{
+ struct dw8250_port_data *d = p->private_data;
+
+ if (!(p->rs485.flags & SER_RS485_ENABLED) || !d->addrmode)
+ return -EINVAL;
+
+ if (addr->flags == SER_ADDR_DEST) {
+ addr->addr = dw8250_readl_ext(p, DW_UART_TAR) & DW_UART_ADDR_MASK;
+ return 0;
+ }
+ if (addr->flags == SER_ADDR_RECV) {
+ u32 lcr = dw8250_readl_ext(p, DW_UART_LCR_EXT);
+
+ if (!(lcr & DW_UART_LCR_EXT_ADDR_MATCH)) {
+ addr->flags = SER_ADDR_RECV_CLEAR;
+ addr->addr = 0;
+ } else {
+ addr->addr = dw8250_readl_ext(p, DW_UART_RAR) & DW_UART_ADDR_MASK;
+ }
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
static int dw8250_rs485_config(struct uart_port *p, struct serial_rs485 *rs485)
{
+ struct dw8250_port_data *d = p->private_data;
u32 tcr;
tcr = dw8250_readl_ext(p, DW_UART_TCR);
@@ -127,6 +258,14 @@ static int dw8250_rs485_config(struct uart_port *p, struct serial_rs485 *rs485)
dw8250_writel_ext(p, DW_UART_DE_EN, 1);
dw8250_writel_ext(p, DW_UART_RE_EN, 1);
} else {
+ /*
+ * Don't allow disabling RS485 when address mode is enabled.
+ * Clearing address mode would require clearing ADDRB in
+ * termios.
+ */
+ if (d->addrmode)
+ return -EINVAL;
+
rs485->flags = 0;
tcr &= ~DW_UART_TCR_RS485_EN;
@@ -174,6 +313,9 @@ void dw8250_setup_port(struct uart_port *p)
d->hw_rs485_support = dw8250_detect_rs485_hw(p);
if (d->hw_rs485_support) {
p->rs485_config = dw8250_rs485_config;
+ p->set_addr = dw8250_rs485_set_addr;
+ p->get_addr = dw8250_rs485_get_addr;
+ up->lsr_save_mask = LSR_SAVE_FLAGS | DW_UART_LSR_ADDR_RCVD;
} else {
p->rs485_config = serial8250_em485_config;
up->rs485_start_tx = serial8250_em485_start_tx;
@@ -17,6 +17,8 @@ struct dw8250_port_data {
/* RS485 variables */
bool hw_rs485_support;
+ /* 9-bit framing (9th bit is address indicator) */
+ bool addrmode;
};
void dw8250_do_set_termios(struct uart_port *p, struct ktermios *termios, struct ktermios *old);