// SPDX-License-Identifier: GPL-2.0+ /* Synopsys DesignWare 8250 library. */ #include #include #include #include #include #include #include #include #include #include "8250_dwlib.h" /* Offsets for the DesignWare specific registers */ #define DW_UART_TCR 0xac /* Transceiver Control Register (RS485) */ #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) #define DW_UART_TCR_DE_POL BIT(2) #define DW_UART_TCR_XFER_MODE GENMASK(4, 3) #define DW_UART_TCR_XFER_MODE_DE_DURING_RE FIELD_PREP(DW_UART_TCR_XFER_MODE, 0) #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 GENMASK(1, 0) #define DW_UART_CPR_AFCE_MODE BIT(4) #define DW_UART_CPR_THRE_MODE BIT(5) #define DW_UART_CPR_SIR_MODE BIT(6) #define DW_UART_CPR_SIR_LP_MODE BIT(7) #define DW_UART_CPR_ADDITIONAL_FEATURES BIT(8) #define DW_UART_CPR_FIFO_ACCESS BIT(9) #define DW_UART_CPR_FIFO_STAT BIT(10) #define DW_UART_CPR_SHADOW BIT(11) #define DW_UART_CPR_ENCODED_PARMS BIT(12) #define DW_UART_CPR_DMA_EXTRA BIT(13) #define DW_UART_CPR_FIFO_MODE GENMASK(23, 16) /* Helper for FIFO size calculation */ #define DW_UART_CPR_FIFO_SIZE(a) (FIELD_GET(DW_UART_CPR_FIFO_MODE, (a)) * 16) /* * divisor = div(I) + div(F) * "I" means integer, "F" means fractional * quot = div(I) = clk / (16 * baud) * frac = div(F) * 2^dlf_size * * let rem = clk % (16 * baud) * we have: div(F) * (16 * baud) = rem * so frac = 2^dlf_size * rem / (16 * baud) = (rem << dlf_size) / (16 * baud) */ static unsigned int dw8250_get_divisor(struct uart_port *p, unsigned int baud, unsigned int *frac) { unsigned int quot, rem, base_baud = baud * 16; struct dw8250_port_data *d = p->private_data; quot = p->uartclk / base_baud; rem = p->uartclk % base_baud; *frac = DIV_ROUND_CLOSEST(rem << d->dlf_size, base_baud); return quot; } static void dw8250_set_divisor(struct uart_port *p, unsigned int baud, unsigned int quot, unsigned int quot_frac) { dw8250_writel_ext(p, DW_UART_DLF, quot_frac); serial8250_do_set_divisor(p, baud, quot); } void dw8250_do_set_termios(struct uart_port *p, struct ktermios *termios, const struct ktermios *old) { p->status &= ~UPSTAT_AUTOCTS; if (termios->c_cflag & CRTSCTS) p->status |= UPSTAT_AUTOCTS; serial8250_do_set_termios(p, termios, old); /* Filter addresses which have 9th bit set */ 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); /* * 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) { ndelay(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_rs485_set_addr(struct uart_port *p, struct serial_rs485 *rs485, struct ktermios *termios) { u32 lcr = dw8250_readl_ext(p, DW_UART_LCR_EXT); if (rs485->flags & SER_RS485_ADDRB) { lcr |= DW_UART_LCR_EXT_DLS_E; if (termios) termios->c_cflag |= ADDRB; if (rs485->flags & SER_RS485_ADDR_RECV) { u32 delta = p->rs485.flags ^ rs485->flags; /* * rs485 (param) is equal to uart_port's rs485 only during init * (during init, delta is not yet applicable). */ if (unlikely(&p->rs485 == rs485)) delta = rs485->flags; if ((delta & SER_RS485_ADDR_RECV) || (p->rs485.addr_recv != rs485->addr_recv)) dw8250_update_rar(p, rs485->addr_recv); lcr |= DW_UART_LCR_EXT_ADDR_MATCH; } else { lcr &= ~DW_UART_LCR_EXT_ADDR_MATCH; } if (rs485->flags & SER_RS485_ADDR_DEST) { /* * Don't skip writes here as another endpoint could * have changed communication line's destination * address in between. */ dw8250_writel_ext(p, DW_UART_TAR, rs485->addr_dest); lcr |= DW_UART_LCR_EXT_SEND_ADDR; } } else { lcr = 0; } dw8250_writel_ext(p, DW_UART_LCR_EXT, lcr); } static int dw8250_rs485_config(struct uart_port *p, struct ktermios *termios, struct serial_rs485 *rs485) { u32 tcr; tcr = dw8250_readl_ext(p, DW_UART_TCR); tcr &= ~DW_UART_TCR_XFER_MODE; if (rs485->flags & SER_RS485_ENABLED) { tcr |= DW_UART_TCR_RS485_EN; if (rs485->flags & SER_RS485_RX_DURING_TX) tcr |= DW_UART_TCR_XFER_MODE_DE_DURING_RE; else tcr |= DW_UART_TCR_XFER_MODE_DE_OR_RE; dw8250_writel_ext(p, DW_UART_DE_EN, 1); dw8250_writel_ext(p, DW_UART_RE_EN, 1); } else { if (termios) termios->c_cflag &= ~ADDRB; tcr &= ~DW_UART_TCR_RS485_EN; } /* Reset to default polarity */ tcr |= DW_UART_TCR_DE_POL; tcr &= ~DW_UART_TCR_RE_POL; if (!(rs485->flags & SER_RS485_RTS_ON_SEND)) tcr &= ~DW_UART_TCR_DE_POL; if (device_property_read_bool(p->dev, "rs485-rx-active-high")) tcr |= DW_UART_TCR_RE_POL; dw8250_writel_ext(p, DW_UART_TCR, tcr); /* Addressing mode can only be set up after TCR */ if (rs485->flags & SER_RS485_ENABLED) dw8250_rs485_set_addr(p, rs485, termios); return 0; } /* * Tests if RE_EN register can have non-zero value to see if RS-485 HW support * is present. */ static bool dw8250_detect_rs485_hw(struct uart_port *p) { u32 reg; dw8250_writel_ext(p, DW_UART_RE_EN, 1); reg = dw8250_readl_ext(p, DW_UART_RE_EN); dw8250_writel_ext(p, DW_UART_RE_EN, 0); return reg; } static const struct serial_rs485 dw8250_rs485_supported = { .flags = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND | SER_RS485_ADDRB | SER_RS485_ADDR_RECV | SER_RS485_ADDR_DEST, }; void dw8250_setup_port(struct uart_port *p) { struct dw8250_port_data *pd = p->private_data; struct uart_8250_port *up = up_to_u8250p(p); u32 reg, old_dlf; pd->hw_rs485_support = dw8250_detect_rs485_hw(p); if (pd->hw_rs485_support) { p->rs485_config = dw8250_rs485_config; up->lsr_save_mask = LSR_SAVE_FLAGS | DW_UART_LSR_ADDR_RCVD; p->rs485_supported = dw8250_rs485_supported; } else { p->rs485_config = serial8250_em485_config; p->rs485_supported = serial8250_em485_supported; up->rs485_start_tx = serial8250_em485_start_tx; up->rs485_stop_tx = serial8250_em485_stop_tx; } up->capabilities |= UART_CAP_NOTEMT; /* Preserve value written by firmware or bootloader */ old_dlf = dw8250_readl_ext(p, DW_UART_DLF); dw8250_writel_ext(p, DW_UART_DLF, ~0U); reg = dw8250_readl_ext(p, DW_UART_DLF); dw8250_writel_ext(p, DW_UART_DLF, old_dlf); if (reg) { pd->dlf_size = fls(reg); p->get_divisor = dw8250_get_divisor; p->set_divisor = dw8250_set_divisor; } reg = dw8250_readl_ext(p, DW_UART_UCV); if (reg) dev_dbg(p->dev, "Designware UART version %c.%c%c\n", (reg >> 24) & 0xff, (reg >> 16) & 0xff, (reg >> 8) & 0xff); reg = dw8250_readl_ext(p, DW_UART_CPR); if (!reg) { reg = pd->cpr_value; dev_dbg(p->dev, "CPR is not available, using 0x%08x instead\n", reg); } if (!reg) return; /* Select the type based on FIFO */ if (reg & DW_UART_CPR_FIFO_MODE) { p->type = PORT_16550A; p->flags |= UPF_FIXED_TYPE; p->fifosize = DW_UART_CPR_FIFO_SIZE(reg); up->capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT; } if (reg & DW_UART_CPR_AFCE_MODE) up->capabilities |= UART_CAP_AFE; if (reg & DW_UART_CPR_SIR_MODE) up->capabilities |= UART_CAP_IRDA; } EXPORT_SYMBOL_GPL(dw8250_setup_port);