* [PATCH 0/2] WK2xxx SPI to UART bridge driver
@ 2026-09-04 7:20 zjzhao
2026-09-04 7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: zjzhao @ 2026-09-04 7:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
linux-serial, devicetree
This series adds a driver for the WK2xxx SPI to UART bridge ICs
(WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai
Microelectronics). Each IC exposes two or four full-duplex UART channels
with 256-byte RX/TX FIFOs through a single SPI slave interface and one
interrupt line.
The driver is a rework of the vendor driver (https://github.com/britus/
wk2xxx) modeled after the NXP sc16is7xx driver. It registers ttyWK0..N
lines (IDA-allocated), uses devm_request_threaded_irq with a kthread
worker for register access, falls back to polling when the interrupt
line is not described, and supports hardware flow control and RS485
where the IC provides them.
Patch 1 adds the driver, the PORT_WK2XXX (124) type and the
SERIAL_WK2XXX Kconfig option. Patch 2 documents the DT binding and
registers the wkmic vendor prefix.
Tested on Raspberry Pi 5 boards (EDATEC IPC1200 with WK2132 on SPI0 and
SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass.
zjzhao (2):
serial: wk2xxx: Add WK2xxx SPI UART driver
dt-bindings: serial: Document WK2xxx SPI UART bindings
.../bindings/serial/wkmic,wk2xxx.yaml | 69 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
drivers/tty/serial/Kconfig | 17 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/wk2xxx.c | 1245 +++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
6 files changed, 1337 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml
create mode 100644 drivers/tty/serial/wk2xxx.c
base-commit: bc35965f6940a9bf834d54187b6088b8eb09206d
--
2.43.0
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 7:20 [PATCH 0/2] WK2xxx SPI to UART bridge driver zjzhao @ 2026-09-04 7:20 ` zjzhao 2026-09-04 7:35 ` sashiko-bot 2026-09-04 8:17 ` Jiri Slaby 2026-09-04 7:20 ` [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao 2 siblings, 2 replies; 20+ messages in thread From: zjzhao @ 2026-09-04 7:20 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). Each IC exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI slave interface and one interrupt line. The driver is a rework of the vendor driver (https://github.com/britus/ wk2xxx) modeled after the NXP sc16is7xx driver. It registers ttyWK0..N lines (IDA-allocated), uses devm_request_threaded_irq with a kthread worker for register access, falls back to polling when the interrupt line is not described, and supports hardware flow control and RS485 where the IC provides them. Also allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option. Tested on Raspberry Pi 5 boards (EDATEC IPC1200 with WK2132 on SPI0 and SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass. Signed-off-by: zjzhao <zjzhao@edatec.cn> --- drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/wk2xxx.c | 1245 ++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 1266 insertions(+) create mode 100644 drivers/tty/serial/wk2xxx.c diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index cf7dba473b20..5f7a71f377ac 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1207,6 +1207,23 @@ config SERIAL_MXS_AUART_CONSOLE help Enable a MXS AUART port to be the system console. +config SERIAL_WK2XXX + tristate "WK2xxx SPI UART support" + depends on SPI_MASTER + select SERIAL_CORE + help + This selects the WK2xxx SPI to UART bridge driver. + Supported ICs are: + + WK2124 + WK2132 + WK2168 + WK2202 + WK2204 + + To compile this driver as a module, choose M here: the module + will be called wk2xxx. + config SERIAL_XILINX_PS_UART tristate "Cadence (Xilinx Zynq) UART support" depends on OF diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index bba7b21a4a1d..fdd13f3dd058 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_VT8500) += vt8500_serial.o +obj-$(CONFIG_SERIAL_WK2XXX) += wk2xxx.o obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o obj-$(CONFIG_SERIAL_ZS) += zs.o diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c new file mode 100644 index 000000000000..184bef9b0fa8 --- /dev/null +++ b/drivers/tty/serial/wk2xxx.c @@ -0,0 +1,1245 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * WK2xxx SPI to UART bridge tty serial driver + * + * SPI-to-UART bridge ICs from WKmic (Chengdu Weikai Microelectronics): + * WK2124, WK2132, WK2168, WK2202 and WK2204. Each IC exposes two or four + * full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI + * slave interface and one interrupt line. The slave register set is split + * into two banks (page 0 / page 1) selected by the SPAGE register. + * + * This driver is a rework of the vendor "wk2xxx" driver (originally at + * https://github.com/britus/wk2xxx) and is modeled after the NXP sc16is7xx + * driver. + * + * (C) Copyright 2022 WKIC Ltd. by Xu XunWei Tech, Xuxunwei + * (C) Copyright 2024 EoF Software Labs, B. Eschrich + * Copyright (C) 2026 EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/kthread.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/overflow.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/units.h> +#include <linux/workqueue.h> + +#define WK2XXX_NAME "wk2xxx" +#define WK2XXX_MAX_DEVS 8 /* Total number of lines. */ +#define WK2XXX_MAX_PORTS 4 /* Max number of ports per IC. */ +#define WK2XXX_FIFO_SIZE 256 +#define WK2XXX_MAX_SPI_LEN 30 /* Max bytes per SPI FIFO burst. */ +#define WK2XXX_MAX_TX_CHARS 200 /* Leave headroom in the TX FIFO. */ +#define WK2XXX_RXFIFO_LEVEL 0x40 /* RX FIFO trigger level. */ +#define WK2XXX_TXFIFO_LEVEL 0x01 /* TX FIFO trigger level. */ +#define WK2XXX_POLL_PERIOD_MS 10 + +/* SPI command byte: bit 6 = read, bit 7 = FIFO access. */ +#define WK2XXX_SPI_READ BIT(6) +#define WK2XXX_SPI_FIFO_WRITE BIT(7) +#define WK2XXX_SPI_FIFO_READ (BIT(7) | BIT(6)) + +/* Marker used to address registers located in page 1. */ +#define WK2XXX_PAGE1 BIT(7) + +/* Global registers. */ +#define WK2XXX_GENA_REG 0x00 /* Global UART enable */ +#define WK2XXX_GRST_REG 0x01 /* Global reset */ +#define WK2XXX_GMUT_REG 0x02 /* Master UART control */ +#define WK2XXX_GIER_REG 0x10 /* Global interrupt enable */ +#define WK2XXX_GIFR_REG 0x11 /* Global interrupt flag */ + +/* Port (sub-UART) registers, page 0. */ +#define WK2XXX_SPAGE_REG 0x03 /* Register page select */ +#define WK2XXX_SCR_REG 0x04 /* Slave control */ +#define WK2XXX_LCR_REG 0x05 /* Line control */ +#define WK2XXX_FCR_REG 0x06 /* FIFO control */ +#define WK2XXX_SIER_REG 0x07 /* Slave interrupt enable */ +#define WK2XXX_SIFR_REG 0x08 /* Slave interrupt flag */ +#define WK2XXX_TFCNT_REG 0x09 /* TX FIFO count */ +#define WK2XXX_RFCNT_REG 0x0a /* RX FIFO count */ +#define WK2XXX_FSR_REG 0x0b /* FIFO status */ +#define WK2XXX_LSR_REG 0x0c /* Line status */ +#define WK2XXX_FDAT_REG 0x0d /* FIFO data */ +#define WK2XXX_FWCR_REG 0x0e /* Flow control */ +#define WK2XXX_RS485_REG 0x0f /* RS485 control */ + +/* Port (sub-UART) registers, page 1. */ +#define WK2XXX_BAUD1_REG (0x04 | WK2XXX_PAGE1) /* Divisor Latch High */ +#define WK2XXX_BAUD0_REG (0x05 | WK2XXX_PAGE1) /* Divisor Latch Low */ +#define WK2XXX_PRES_REG (0x06 | WK2XXX_PAGE1) /* Fractional divisor */ +#define WK2XXX_RFTL_REG (0x07 | WK2XXX_PAGE1) /* RX FIFO trigger level */ +#define WK2XXX_TFTL_REG (0x08 | WK2XXX_PAGE1) /* TX FIFO trigger level */ +#define WK2XXX_FWTH_REG (0x09 | WK2XXX_PAGE1) /* Flow control high level */ +#define WK2XXX_FWTL_REG (0x0a | WK2XXX_PAGE1) /* Flow control low level */ +#define WK2XXX_XON1_REG (0x0b | WK2XXX_PAGE1) /* Xon word */ +#define WK2XXX_XOFF1_REG (0x0c | WK2XXX_PAGE1) /* Xoff word */ +#define WK2XXX_SADR_REG (0x0d | WK2XXX_PAGE1) /* RS485 auto address */ +#define WK2XXX_SAEN_REG (0x0e | WK2XXX_PAGE1) /* RS485 address mask */ +#define WK2XXX_RRSDLY_REG (0x0f | WK2XXX_PAGE1) /* RS485 RTS delay */ + +/* SCR register bits. */ +#define WK2XXX_SCR_RXEN_BIT BIT(0) +#define WK2XXX_SCR_TXEN_BIT BIT(1) + +/* LCR register bits. */ +#define WK2XXX_LCR_STPL_BIT BIT(0) /* Two stop bits */ +#define WK2XXX_LCR_PAM0_BIT BIT(1) /* Parity mode bit 0 */ +#define WK2XXX_LCR_PAM1_BIT BIT(2) /* Parity mode bit 1 */ +#define WK2XXX_LCR_PAEN_BIT BIT(3) /* Parity enable */ +#define WK2XXX_LCR_BREAK_BIT BIT(5) /* TX break */ + +/* SIER register bits. */ +#define WK2XXX_SIER_RFTRIG_IEN_BIT BIT(0) /* RX FIFO trigger */ +#define WK2XXX_SIER_RXOUT_IEN_BIT BIT(1) /* RX time-out */ +#define WK2XXX_SIER_TFTRIG_IEN_BIT BIT(2) /* TX FIFO trigger */ + +/* SIFR register bits. */ +#define WK2XXX_SIFR_RFTRIG_INT_BIT BIT(0) +#define WK2XXX_SIFR_RXOVT_INT_BIT BIT(1) +#define WK2XXX_SIFR_TFTRIG_INT_BIT BIT(2) + +/* FSR register bits. */ +#define WK2XXX_FSR_TBUSY_BIT BIT(0) +#define WK2XXX_FSR_TFULL_BIT BIT(1) +#define WK2XXX_FSR_TDAT_BIT BIT(2) +#define WK2XXX_FSR_RDAT_BIT BIT(3) +#define WK2XXX_FSR_RFPE_BIT BIT(4) /* RX FIFO parity error */ +#define WK2XXX_FSR_RFFE_BIT BIT(5) /* RX FIFO frame error */ +#define WK2XXX_FSR_RFBI_BIT BIT(6) /* RX FIFO break */ +#define WK2XXX_FSR_RFOE_BIT BIT(7) /* RX FIFO overrun */ +#define WK2XXX_FSR_ERR_MASK GENMASK(7, 4) + +/* LSR error bits, for use with uart_insert_char(). */ +#define WK2XXX_LSR_PE_BIT BIT(0) +#define WK2XXX_LSR_FE_BIT BIT(1) +#define WK2XXX_LSR_BI_BIT BIT(2) +#define WK2XXX_LSR_OE_BIT BIT(3) +#define WK2XXX_LSR_BRK_ERROR_MASK (WK2XXX_LSR_OE_BIT | WK2XXX_LSR_PE_BIT | \ + WK2XXX_LSR_FE_BIT | WK2XXX_LSR_BI_BIT) + +/* + * FWCR register bits. The flow-control mode is selected by the FWM2-0 + * field in bits 6-4 (WK2132 has no FWCR register; writing it is ignored). + */ +#define WK2XXX_FWCR_FWM_MASK GENMASK(6, 4) +#define WK2XXX_FWCR_FWM_RTS_CTS FIELD_PREP(WK2XXX_FWCR_FWM_MASK, 0x3) + +/* RS485 register bits. */ +#define WK2XXX_RS485_RTSINV_BIT BIT(0) +#define WK2XXX_RS485_RTSEN_BIT BIT(1) +#define WK2XXX_RS485_RSRS485_BIT BIT(6) + +struct wk2xxx_devtype { + const char *name; + int nr_uart; + unsigned long crystal_freq; +}; + +#define WK2XXX_RECONF_IER BIT(0) +#define WK2XXX_RECONF_RS485 BIT(1) + +struct wk2xxx_one_config { + unsigned int flags; + u8 ier_mask; + u8 ier_val; +}; + +struct wk2xxx_one { + struct uart_port port; + struct mutex tx_lock; /* Serializes the TX path. */ + struct kthread_work tx_work; + struct kthread_work reg_work; + struct wk2xxx_one_config config; + unsigned char buf[WK2XXX_FIFO_SIZE]; /* RX buffer. */ +}; + +struct wk2xxx_port { + const struct wk2xxx_devtype *devtype; + struct spi_device *spi; + struct mutex reg_lock; /* SPI register access. */ + struct kthread_worker kworker; + struct task_struct *kworker_task; + struct kthread_delayed_work poll_work; + bool polling; + struct wk2xxx_one p[]; +}; + +static DEFINE_IDA(wk2xxx_lines); + +static struct uart_driver wk2xxx_uart = { + .owner = THIS_MODULE, + .driver_name = WK2XXX_NAME, + .dev_name = "ttyWK", + .nr = WK2XXX_MAX_DEVS, +}; + +#define to_wk2xxx_one(p, e) ((container_of((p), struct wk2xxx_one, e))) + +static const struct wk2xxx_devtype wk2124_devtype = { + .name = "WK2124", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2132_devtype = { + .name = "WK2132", + .nr_uart = 2, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2168_devtype = { + .name = "WK2168", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2202_devtype = { + .name = "WK2202", + .nr_uart = 2, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2204_devtype = { + .name = "WK2204", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +/* + * The following functions are the low-level SPI accessors. The caller must + * hold s->reg_lock, so that multi-byte accesses and page switches are + * performed atomically on the SPI bus. + */ +static int wk2xxx_spi_transfer(struct wk2xxx_port *s, const u8 *tx, u8 *rx, + unsigned int len) +{ + struct spi_transfer xfer = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + }; + struct spi_message msg; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + return spi_sync(s->spi, &msg); +} + +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) +{ + u8 tx[2] = { WK2XXX_SPI_READ | addr, 0 }; + u8 rx[2] = { 0, 0 }; + int ret; + + ret = wk2xxx_spi_transfer(s, tx, rx, sizeof(tx)); + if (ret) + return ret; + + *val = rx[1]; + return 0; +} + +static int wk2xxx_raw_write(struct wk2xxx_port *s, u8 addr, u8 val) +{ + u8 tx[2] = { addr, val }; + u8 rx[2] = { 0, 0 }; + + return wk2xxx_spi_transfer(s, tx, rx, sizeof(tx)); +} + +static unsigned int wk2xxx_port_addr(unsigned int portno, u8 reg) +{ + /* The sub-UART number is encoded in the upper nibble of the cmd byte. */ + return (portno << 4) | reg; +} + +static int wk2xxx_raw_port_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +static int wk2xxx_raw_port_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +/* + * Locked wrappers used outside the register sequences that already hold + * s->reg_lock. + */ +static int wk2xxx_reg_read(struct wk2xxx_port *s, u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_read(s, reg, val); +} + +static int wk2xxx_port_reg_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_read(s, portno, reg, val); +} + +static int wk2xxx_port_reg_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_write(s, portno, reg, val); +} + +static void wk2xxx_port_reg_update(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 mask, u8 val) +{ + u8 r = 0; + + scoped_guard(mutex, &s->reg_lock) { + if (wk2xxx_raw_port_read(s, portno, reg, &r)) + return; + wk2xxx_raw_port_write(s, portno, reg, (r & ~mask) | val); + } +} + +static int wk2xxx_fifo_read(struct wk2xxx_port *s, unsigned int portno, + u8 *buf, unsigned int len) +{ + u8 tx[WK2XXX_MAX_SPI_LEN + 1]; + u8 rx[WK2XXX_MAX_SPI_LEN + 1]; + int ret; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + memset(tx, 0, sizeof(tx)); + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_READ); + + guard(mutex)(&s->reg_lock); + ret = wk2xxx_spi_transfer(s, tx, rx, len + 1); + if (ret) + return ret; + + memcpy(buf, rx + 1, len); + return 0; +} + +static int wk2xxx_fifo_write(struct wk2xxx_port *s, unsigned int portno, + const u8 *buf, unsigned int len) +{ + u8 tx[WK2XXX_MAX_SPI_LEN + 1]; + u8 rx[WK2XXX_MAX_SPI_LEN + 1]; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_WRITE); + memcpy(tx + 1, buf, len); + + guard(mutex)(&s->reg_lock); + return wk2xxx_spi_transfer(s, tx, rx, len + 1); +} + +static void wk2xxx_ier_set(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val |= bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_ier_clear(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val &= ~bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_stop_tx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_TFTRIG_IEN_BIT); +} + +static void wk2xxx_stop_rx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_throttle(struct uart_port *port) +{ + unsigned long flags; + + /* Stop draining the RX FIFO to apply back-pressure. */ + uart_port_lock_irqsave(port, &flags); + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT); + uart_port_unlock_irqrestore(port, flags); +} + +static void wk2xxx_unthrottle(struct uart_port *port) +{ + unsigned long flags; + + uart_port_lock_irqsave(port, &flags); + wk2xxx_ier_set(port, WK2XXX_SIER_RFTRIG_IEN_BIT); + uart_port_unlock_irqrestore(port, flags); +} + +static void wk2xxx_handle_tx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct tty_port *tport = &port->state->port; + unsigned long flags; + unsigned int portno = port->iobase; + unsigned int txlen, to_send, sent; + const unsigned char *tail; + u8 fsr, tfcnt; + + mutex_lock(&one->tx_lock); + + if (unlikely(port->x_char)) { + wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, port->x_char); + port->icount.tx++; + port->x_char = 0; + goto out; + } + + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { + uart_port_lock_irqsave(port, &flags); + wk2xxx_stop_tx(port); + uart_port_unlock_irqrestore(port, flags); + goto out; + } + + /* Limit to the free space available in the TX FIFO. */ + wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt); + if (tfcnt == 0) { + wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr); + txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE; + } else { + txlen = WK2XXX_FIFO_SIZE - tfcnt; + } + if (txlen > WK2XXX_MAX_TX_CHARS) + txlen = WK2XXX_MAX_TX_CHARS; + + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + sent = to_send; + while (to_send) { + unsigned int chunk = min_t(unsigned int, to_send, + WK2XXX_MAX_SPI_LEN); + + wk2xxx_fifo_write(s, portno, tail, chunk); + tail += chunk; + to_send -= chunk; + } + uart_xmit_advance(port, sent); + + uart_port_lock_irqsave(port, &flags); + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (kfifo_is_empty(&tport->xmit_fifo)) + wk2xxx_stop_tx(port); + else + wk2xxx_ier_set(port, WK2XXX_SIER_TFTRIG_IEN_BIT); + uart_port_unlock_irqrestore(port, flags); + +out: + mutex_unlock(&one->tx_lock); +} + +static void wk2xxx_handle_rx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + unsigned int i, rxlen, len_p, chunk; + u8 fsr, rfcnt, lsr = 0, flag = TTY_NORMAL; + + wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr); + + if (!(fsr & WK2XXX_FSR_RDAT_BIT)) + return; + + /* Get the number of bytes available in the RX FIFO. */ + wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt); + if (rfcnt == 0) { + /* The count may race with the FIFO status bit; retry once. */ + wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt); + rxlen = rfcnt ? rfcnt : WK2XXX_FIFO_SIZE; + } else { + rxlen = rfcnt; + } + + /* Read the FIFO contents in chunks. */ + len_p = 0; + while (rxlen) { + chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN); + wk2xxx_fifo_read(s, portno, one->buf + len_p, chunk); + len_p += chunk; + rxlen -= chunk; + } + rxlen = len_p; + + /* Map the FIFO status register error flags to line status. */ + if (fsr & WK2XXX_FSR_ERR_MASK) { + if (fsr & WK2XXX_FSR_RFPE_BIT) { + port->icount.parity++; + lsr |= WK2XXX_LSR_PE_BIT; + flag = TTY_PARITY; + } + if (fsr & WK2XXX_FSR_RFFE_BIT) { + port->icount.frame++; + lsr |= WK2XXX_LSR_FE_BIT; + flag = TTY_FRAME; + } + if (fsr & WK2XXX_FSR_RFOE_BIT) { + port->icount.overrun++; + lsr |= WK2XXX_LSR_OE_BIT; + flag = TTY_OVERRUN; + } + if (fsr & WK2XXX_FSR_RFBI_BIT) { + port->icount.brk++; + lsr |= WK2XXX_LSR_BI_BIT; + flag = TTY_BREAK; + } + } + + port->icount.rx += rxlen; + + for (i = 0; i < rxlen; ++i) { + u8 ch = one->buf[i]; + + if (uart_handle_sysrq_char(port, ch)) + continue; + + if (lsr & port->ignore_status_mask) + continue; + + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); + } + + tty_flip_buffer_push(&port->state->port); +} + +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno) +{ + struct uart_port *port = &s->p[portno].port; + u8 sifr, sier; + bool rc = false; + + wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr); + wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier); + + if (sifr & (WK2XXX_SIFR_RFTRIG_INT_BIT | WK2XXX_SIFR_RXOVT_INT_BIT)) { + wk2xxx_handle_rx(port); + rc = true; + } + + if ((sifr & WK2XXX_SIFR_TFTRIG_INT_BIT) && + (sier & WK2XXX_SIER_TFTRIG_IEN_BIT)) { + wk2xxx_handle_tx(port); + rc = true; + } + + return rc; +} + +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) +{ + struct wk2xxx_port *s = dev_id; + bool keep_polling; + + do { + u8 gifr; + int i; + + keep_polling = false; + + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) + return IRQ_HANDLED; /* Bus error; give up this pass. */ + + for (i = 0; i < s->devtype->nr_uart; ++i) + if (gifr & BIT(i)) + keep_polling |= wk2xxx_port_irq(s, i); + } while (keep_polling); + + return IRQ_HANDLED; +} + +static void wk2xxx_poll_proc(struct kthread_work *ws) +{ + struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port, + poll_work.work); + + /* Reuse the IRQ handler; the interrupt ID is unused here. */ + wk2xxx_irq(0, s); + + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); +} + +static void wk2xxx_tx_proc(struct kthread_work *ws) +{ + struct uart_port *port = &(to_wk2xxx_one(ws, tx_work)->port); + + wk2xxx_handle_tx(port); +} + +static void wk2xxx_start_tx(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + + kthread_queue_work(&s->kworker, &one->tx_work); +} + +static void wk2xxx_reconf_rs485(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 rs485 = 0; + + if (port->rs485.flags & SER_RS485_ENABLED) { + rs485 = WK2XXX_RS485_RSRS485_BIT | WK2XXX_RS485_RTSEN_BIT; + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + rs485 |= WK2XXX_RS485_RTSINV_BIT; + } + + wk2xxx_port_reg_write(s, port->iobase, WK2XXX_RS485_REG, rs485); +} + +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + if (rs485->flags & SER_RS485_ENABLED) { + /* + * RTS is driven by hardware and its timing cannot be + * influenced from the driver. + */ + if (rs485->delay_rts_after_send) + return -EINVAL; + } + + one->config.flags |= WK2XXX_RECONF_RS485; + kthread_queue_work(&s->kworker, &one->reg_work); + + return 0; +} + +static void wk2xxx_reg_proc(struct kthread_work *ws) +{ + struct wk2xxx_one *one = to_wk2xxx_one(ws, reg_work); + struct wk2xxx_port *s = dev_get_drvdata(one->port.dev); + struct wk2xxx_one_config config; + unsigned long irqflags; + + uart_port_lock_irqsave(&one->port, &irqflags); + config = one->config; + memset(&one->config, 0, sizeof(one->config)); + uart_port_unlock_irqrestore(&one->port, irqflags); + + if (config.flags & WK2XXX_RECONF_IER) + wk2xxx_port_reg_update(s, one->port.iobase, WK2XXX_SIER_REG, + config.ier_mask, config.ier_val); + + if (config.flags & WK2XXX_RECONF_RS485) + wk2xxx_reconf_rs485(&one->port); +} + +static unsigned int wk2xxx_tx_empty(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 fsr; + + wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr); + + return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 : + TIOCSER_TEMT; +} + +static unsigned int wk2xxx_get_mctrl(struct uart_port *port) +{ + /* The WK2xxx does not expose modem control lines. */ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + +static void wk2xxx_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* The WK2xxx does not support modem control lines. */ +} + +static void wk2xxx_enable_ms(struct uart_port *port) +{ + /* The WK2xxx does not have modem status registers. */ +} + +static void wk2xxx_break_ctl(struct uart_port *port, int break_state) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + wk2xxx_port_reg_update(s, port->iobase, WK2XXX_LCR_REG, + WK2XXX_LCR_BREAK_BIT, + break_state ? WK2XXX_LCR_BREAK_BIT : 0); +} + +/* + * Configure a sub-UART: disable interrupts and TX/RX, program the line + * control and baud rate registers and restore the previous state. + */ +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr, + u8 baud0, u8 baud1, u8 pres) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 sier, scr, fsr; + int count = 200; + + scoped_guard(mutex, &s->reg_lock) { + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier); + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Wait for the transmitter to become idle. */ + do { + wk2xxx_raw_port_read(s, portno, WK2XXX_FSR_REG, &fsr); + } while ((fsr & WK2XXX_FSR_TBUSY_BIT) && count--); + + /* Disable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, &scr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr & ~(WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Program the line control register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr); + + /* Configure hardware flow control levels. */ + if (fwcr) { + wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTH_REG, 0xf0); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTL_REG, 0x80); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + /* Program the baud rate generator (page 1 registers). */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD0_REG, baud0); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD1_REG, baud1); + wk2xxx_raw_port_write(s, portno, WK2XXX_PRES_REG, pres); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + + /* Re-enable the transmitter and receiver. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr | (WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Restore the interrupt enable register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, sier); + } +} + +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, + u8 *baud0, u8 *baud1, u8 *pres) +{ + unsigned int div; + + div = clk / (baud * 16); + if (div == 0) + div = 1; + div--; + *baud0 = div & 0xff; + *baud1 = (div >> 8) & 0xff; + *pres = ((unsigned long long)(clk % (baud * 16)) * 100 / baud + 50) / 100; +} + +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, + const struct ktermios *old) +{ + unsigned int baud; + unsigned long flags; + u8 lcr = 0, fwcr = 0; + u8 baud0, baud1, pres; + + /* The WK2xxx supports 8 data bits only. */ + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + + /* Parity. */ + if (termios->c_cflag & PARENB) { + lcr |= WK2XXX_LCR_PAEN_BIT; + switch (termios->c_cflag & (PARODD | CMSPAR)) { + case 0: + lcr |= WK2XXX_LCR_PAM1_BIT; /* even */ + break; + case PARODD: + lcr |= WK2XXX_LCR_PAM0_BIT; /* odd */ + break; + case CMSPAR: + break; /* space */ + case PARODD | CMSPAR: + lcr |= WK2XXX_LCR_PAM1_BIT | + WK2XXX_LCR_PAM0_BIT; /* mark */ + break; + } + } + + /* Stop bits. */ + if (termios->c_cflag & CSTOPB) + lcr |= WK2XXX_LCR_STPL_BIT; + + /* Set read status mask. */ + port->read_status_mask = WK2XXX_LSR_OE_BIT; + if (termios->c_iflag & INPCK) + port->read_status_mask |= WK2XXX_LSR_PE_BIT | + WK2XXX_LSR_FE_BIT; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= WK2XXX_LSR_BI_BIT; + + /* Set status ignore mask. */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNBRK) + port->ignore_status_mask |= WK2XXX_LSR_BI_BIT; + if (!(termios->c_cflag & CREAD)) + port->ignore_status_mask |= WK2XXX_LSR_BRK_ERROR_MASK; + + /* Configure flow control. */ + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + if (termios->c_cflag & CRTSCTS) { + fwcr = WK2XXX_FWCR_FWM_RTS_CTS; + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; + } + + /* Get the baud rate generator configuration. */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / 16 / 0xffff, + port->uartclk / 16); + + wk2xxx_calc_divisor(port->uartclk, baud, &baud0, &baud1, &pres); + wk2xxx_conf_port(port, lcr, fwcr, baud0, baud1, pres); + + uart_port_lock_irqsave(port, &flags); + uart_update_timeout(port, termios->c_cflag, baud); + uart_port_unlock_irqrestore(port, flags); +} + +static int wk2xxx_startup(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Enable the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + + /* Reset the sub-UART. */ + wk2xxx_raw_write(s, WK2XXX_GRST_REG, BIT(portno)); + + /* Enable the sub-UART interrupt in the global mask. */ + wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Enable RX FIFO trigger and RX time-out interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, + WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); + + /* Enable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, ®); + reg |= WK2XXX_SCR_TXEN_BIT | WK2XXX_SCR_RXEN_BIT; + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, reg); + + /* Reset and configure the FIFOs. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xff); + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xfc); + + /* Set the RX/TX FIFO trigger levels. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_RFTL_REG, + WK2XXX_RXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_TFTL_REG, + WK2XXX_TXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + kfifo_reset(&port->state->port.xmit_fifo); + + if (s->polling) + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); + + return 0; +} + +static void wk2xxx_shutdown(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Disable the sub-UART interrupt in the global mask. */ + wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®); + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Reset the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GRST_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg); + + /* Disable the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + } + + if (s->polling) + kthread_cancel_delayed_work_sync(&s->poll_work); + + kthread_flush_worker(&s->kworker); +} + +static const char *wk2xxx_type(struct uart_port *port) +{ + return (port->type == PORT_WK2XXX) ? WK2XXX_NAME : NULL; +} + +static int wk2xxx_request_port(struct uart_port *port) +{ + /* Do nothing. */ + return 0; +} + +static void wk2xxx_null_void(struct uart_port *port) +{ + /* Do nothing. */ +} + +static void wk2xxx_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_WK2XXX; +} + +static int wk2xxx_verify_port(struct uart_port *port, struct serial_struct *s) +{ + if ((s->type != PORT_UNKNOWN) && (s->type != PORT_WK2XXX)) + return -EINVAL; + if (s->irq != port->irq) + return -EINVAL; + + return 0; +} + +static const struct uart_ops wk2xxx_ops = { + .tx_empty = wk2xxx_tx_empty, + .set_mctrl = wk2xxx_set_mctrl, + .get_mctrl = wk2xxx_get_mctrl, + .stop_tx = wk2xxx_stop_tx, + .start_tx = wk2xxx_start_tx, + .throttle = wk2xxx_throttle, + .unthrottle = wk2xxx_unthrottle, + .stop_rx = wk2xxx_stop_rx, + .enable_ms = wk2xxx_enable_ms, + .break_ctl = wk2xxx_break_ctl, + .startup = wk2xxx_startup, + .shutdown = wk2xxx_shutdown, + .set_termios = wk2xxx_set_termios, + .type = wk2xxx_type, + .request_port = wk2xxx_request_port, + .release_port = wk2xxx_null_void, + .config_port = wk2xxx_config_port, + .verify_port = wk2xxx_verify_port, +}; + +static const struct serial_rs485 wk2xxx_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND, + .delay_rts_before_send = 1, + .delay_rts_after_send = 1, /* Not supported but keep returning -EINVAL */ +}; + +static int wk2xxx_probe(struct spi_device *spi) +{ + const struct wk2xxx_devtype *devtype; + struct device *dev = &spi->dev; + struct wk2xxx_port *s; + unsigned long uartclk; + u32 clock_freq = 0; + bool port_registered[WK2XXX_MAX_PORTS]; + u8 val; + int i, ret; + + /* Setup SPI bus. The SPI mode follows the device tree (spi-cpha, + * spi-cpol); it defaults to SPI mode 0 when unspecified. + */ + spi->bits_per_word = 8; + spi->max_speed_hz = spi->max_speed_hz ? : 10 * HZ_PER_MHZ; + ret = spi_setup(spi); + if (ret) + return ret; + + devtype = spi_get_device_match_data(spi); + if (!devtype) + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); + + /* Allocate port structure. */ + s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); + if (!s) + return dev_err_probe(dev, -ENOMEM, + "Error allocating port structure\n"); + + s->devtype = devtype; + s->spi = spi; + mutex_init(&s->reg_lock); + dev_set_drvdata(dev, s); + + /* + * The WK2xxx has no identification register, so the best we can do + * is to check that communication is at all possible. + */ + ret = wk2xxx_reg_read(s, WK2XXX_GENA_REG, &val); + if (ret) + return dev_err_probe(dev, ret, "Failed to read GENA register\n"); + + /* Crystal clock; allow an optional DT override. */ + uartclk = devtype->crystal_freq; + if (device_property_read_u32(dev, "clock-frequency", &clock_freq) == 0) + uartclk = clock_freq; + + /* Mark each port line and status as uninitialized. */ + for (i = 0; i < devtype->nr_uart; ++i) { + s->p[i].port.line = WK2XXX_MAX_DEVS; + port_registered[i] = false; + } + + kthread_init_worker(&s->kworker); + s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker, + "wk2xxx"); + if (IS_ERR(s->kworker_task)) { + ret = PTR_ERR(s->kworker_task); + goto out_ports; + } + sched_set_fifo(s->kworker_task); + + for (i = 0; i < devtype->nr_uart; ++i) { + ret = ida_alloc_max(&wk2xxx_lines, WK2XXX_MAX_DEVS - 1, + GFP_KERNEL); + if (ret < 0) + goto out_ports; + + s->p[i].port.line = ret; + + /* Initialize port data. */ + s->p[i].port.dev = dev; + s->p[i].port.irq = spi->irq; + s->p[i].port.type = PORT_WK2XXX; + s->p[i].port.fifosize = WK2XXX_FIFO_SIZE; + s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; + s->p[i].port.iobase = i; + /* + * Use all ones as membase so that uart_configure_port() in + * serial_core.c does not abort for SPI devices. + */ + s->p[i].port.membase = (void __iomem *)~0; + s->p[i].port.iotype = UPIO_PORT; + s->p[i].port.uartclk = uartclk; + s->p[i].port.rs485_config = wk2xxx_config_rs485; + s->p[i].port.rs485_supported = wk2xxx_rs485_supported; + s->p[i].port.ops = &wk2xxx_ops; + + mutex_init(&s->p[i].tx_lock); + + kthread_init_work(&s->p[i].tx_work, wk2xxx_tx_proc); + kthread_init_work(&s->p[i].reg_work, wk2xxx_reg_proc); + + ret = uart_get_rs485_mode(&s->p[i].port); + if (ret) + goto out_ports; + + /* Register port. */ + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); + if (ret) + goto out_ports; + + port_registered[i] = true; + } + + if (spi->irq <= 0) { + /* Poll the device instead of using interrupts. */ + s->polling = true; + kthread_init_delayed_work(&s->poll_work, wk2xxx_poll_proc); + return 0; + } + + /* + * Setup interrupt. We first try to acquire the IRQ line as level IRQ. + * If that succeeds, we can allow sharing the interrupt as well. + * In case the interrupt controller doesn't support that, we fall + * back to a non-shared falling-edge trigger. + */ + ret = devm_request_threaded_irq(dev, spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_LOW | IRQF_SHARED | + IRQF_ONESHOT, + dev_name(dev), s); + if (!ret) + return 0; + + ret = devm_request_threaded_irq(dev, spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), s); + if (!ret) + return 0; + +out_ports: + for (i = 0; i < devtype->nr_uart; i++) { + if (s->p[i].port.line < WK2XXX_MAX_DEVS) + ida_free(&wk2xxx_lines, s->p[i].port.line); + if (port_registered[i]) + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + } + + if (!IS_ERR(s->kworker_task)) + kthread_stop(s->kworker_task); + + return ret; +} + +static void wk2xxx_remove(struct spi_device *spi) +{ + struct wk2xxx_port *s = dev_get_drvdata(&spi->dev); + int i; + + for (i = 0; i < s->devtype->nr_uart; i++) { + ida_free(&wk2xxx_lines, s->p[i].port.line); + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + } + + if (s->polling) + kthread_cancel_delayed_work_sync(&s->poll_work); + + kthread_flush_worker(&s->kworker); + kthread_stop(s->kworker_task); +} + +static const struct of_device_id wk2xxx_dt_ids[] = { + { .compatible = "wkmic,wk2124", .data = &wk2124_devtype }, + { .compatible = "wkmic,wk2132", .data = &wk2132_devtype }, + { .compatible = "wkmic,wk2168", .data = &wk2168_devtype }, + { .compatible = "wkmic,wk2202", .data = &wk2202_devtype }, + { .compatible = "wkmic,wk2204", .data = &wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(of, wk2xxx_dt_ids); + +static const struct spi_device_id wk2xxx_id_table[] = { + { "wk2124", (kernel_ulong_t)&wk2124_devtype }, + { "wk2132", (kernel_ulong_t)&wk2132_devtype }, + { "wk2168", (kernel_ulong_t)&wk2168_devtype }, + { "wk2202", (kernel_ulong_t)&wk2202_devtype }, + { "wk2204", (kernel_ulong_t)&wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(spi, wk2xxx_id_table); + +static struct spi_driver wk2xxx_spi_driver = { + .driver = { + .name = WK2XXX_NAME, + .of_match_table = wk2xxx_dt_ids, + }, + .probe = wk2xxx_probe, + .remove = wk2xxx_remove, + .id_table = wk2xxx_id_table, +}; + +static int __init wk2xxx_init(void) +{ + int ret; + + ret = uart_register_driver(&wk2xxx_uart); + if (ret) + return ret; + + ret = spi_register_driver(&wk2xxx_spi_driver); + if (ret) + uart_unregister_driver(&wk2xxx_uart); + + return ret; +} +module_init(wk2xxx_init); + +static void __exit wk2xxx_exit(void) +{ + spi_unregister_driver(&wk2xxx_spi_driver); + uart_unregister_driver(&wk2xxx_uart); +} +module_exit(wk2xxx_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xuxunwei"); +MODULE_AUTHOR("B. Eschrich"); +MODULE_AUTHOR("EDATEC Technology Co., Ltd. <zjzhao@edatec.cn>"); +MODULE_DESCRIPTION("WK2xxx SPI UART driver"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 377884e3856a..05c75d3ef479 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -234,6 +234,9 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* WK2xxx SPI to UART bridge */ +#define PORT_WK2XXX 124 + /* Generic type identifier for ports which type is not important to userspace. */ #define PORT_GENERIC (-1) -- 2.43.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-04 7:35 ` sashiko-bot 2026-09-04 8:17 ` Jiri Slaby 1 sibling, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-09-04 7:35 UTC (permalink / raw) To: zjzhao; +Cc: linux-serial, robh, devicetree, conor+dt Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider: - [High] The IDA line number is freed before the UART port is unregistered, allowing an ID reuse race condition. - [High] Closing a single UART port unconditionally stops the shared polling loop, breaking all other active ports on the same SPI chip. - [High] Ignored SPI read errors leave stack variables uninitialized, leading to undefined control flow and leakage of stale buffer data to user-space. - [High] Ports are registered and exposed to user-space before the IRQ handler and polling infrastructure are initialized. - [High] Unprotected access to serial core data structures (xmit_fifo, x_char, icount) without holding port->lock causes data races and severe kfifo corruption. - [High] SPI transfer buffers are allocated on the stack, violating DMA-safety requirements and potentially causing memory corruption or panics. - [High] Late devm IRQ teardown causes Use-After-Free of port->state and kworker task_struct if an interrupt fires during manual port removal. - [High] Polling mode deadlocks the kworker thread permanently upon the first TX FIFO drain. - [High] 64-bit division using the `/` operator will break kernel builds on 32-bit architectures. - [Medium] The shared IRQ handler unconditionally returns IRQ_HANDLED, breaking shared interrupt semantics. - [Medium] Standard termios CREAD (Receiver Enable) flag is incorrectly evaluated, failing to drop normal characters. - [Medium] RX error flags from FSR are incorrectly applied to every character in the burst read. - [Medium] wk2xxx_throttle() fails to disable the RX timeout interrupt, defeating flow control. - [Medium] Incorrect implementation of unsupported RS485 delays. -- commit fa4922eb42a208a55c46770ba645376285db8163 Author: zjzhao <zjzhao@edatec.cn> Subject: serial: wk2xxx: Add WK2xxx SPI UART driver This commit adds a new serial driver for the WK2xxx SPI to UART bridge ICs. It supports up to four full-duplex UART channels over a single SPI slave interface, with features like hardware flow control and RS485, using a kthread worker for register accesses. > diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c > new file mode 100644 > index 0000000000000..184bef9b0fa81 > --- /dev/null > +++ b/drivers/tty/serial/wk2xxx.c [ ... ] > +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) > +{ > + u8 tx[2] = { WK2XXX_SPI_READ | addr, 0 }; > + u8 rx[2] = { 0, 0 }; > + int ret; > + > + ret = wk2xxx_spi_transfer(s, tx, rx, sizeof(tx)); [Severity: High] Is it safe to pass stack-allocated buffers to spi_sync() here and in the fifo access functions? Stack memory is typically not DMA-safe and can lack cacheline alignment, potentially causing memory corruption or panics on cache-incoherent architectures. [ ... ] > +static void wk2xxx_throttle(struct uart_port *port) > +{ > + unsigned long flags; > + > + /* Stop draining the RX FIFO to apply back-pressure. */ > + uart_port_lock_irqsave(port, &flags); > + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT); > + uart_port_unlock_irqrestore(port, flags); > +} [Severity: Medium] Does this sequence fully apply back-pressure? The RX timeout interrupt (WK2XXX_SIER_RXOUT_IEN_BIT) remains enabled. If any bytes remain in the hardware FIFO, the timeout interrupt will eventually fire and push more characters, defeating the flow control request. [ ... ] > +static void wk2xxx_handle_tx(struct uart_port *port) > +{ [ ... ] > + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); > + sent = to_send; > + while (to_send) { > + unsigned int chunk = min_t(unsigned int, to_send, > + WK2XXX_MAX_SPI_LEN); > + > + wk2xxx_fifo_write(s, portno, tail, chunk); > + tail += chunk; > + to_send -= chunk; > + } > + uart_xmit_advance(port, sent); [Severity: High] Does this lockless access to tport->xmit_fifo expose the buffer to races? Because wk2xxx_fifo_write() can sleep, a concurrent flush operation could acquire port->lock and reset the kfifo. If that happens, the subsequent lockless uart_xmit_advance() could increment the fifo out index beyond the in index, leading to underflows and heap corruption. [ ... ] > +static void wk2xxx_handle_rx(struct uart_port *port) > +{ [ ... ] > + u8 fsr, rfcnt, lsr = 0, flag = TTY_NORMAL; > + > + wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr); [ ... ] > + wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt); [Severity: High] If the SPI transaction fails here, the return value is ignored. Could this leave rfcnt uninitialized, using its stale value to dictate how much memory is read? Since wk2xxx_fifo_read() can also fail silently, this might push unmodified, stale heap data directly to user-space. > + /* Map the FIFO status register error flags to line status. */ > + if (fsr & WK2XXX_FSR_ERR_MASK) { [ ... ] > + } > + > + port->icount.rx += rxlen; > + > + for (i = 0; i < rxlen; ++i) { > + u8 ch = one->buf[i]; [ ... ] > + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); > + } [Severity: Medium] Does reading fsr once and applying the exact same lsr error flags to every character in the loop incorrectly flag valid characters as corrupted? A single corrupted byte in the hardware FIFO could cause the entire batch to be marked with framing, parity, or overrun errors. [ ... ] > +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno) > +{ > + struct uart_port *port = &s->p[portno].port; > + u8 sifr, sier; > + bool rc = false; > + > + wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr); > + wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier); [Severity: High] If the SPI transaction fails here, the return value is ignored. Could this leave sifr and sier uninitialized, causing undefined behavior and spurious interrupt handling based on stack garbage? [ ... ] > +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) > +{ [ ... ] > + for (i = 0; i < s->devtype->nr_uart; ++i) > + if (gifr & BIT(i)) > + keep_polling |= wk2xxx_port_irq(s, i); > + } while (keep_polling); > + > + return IRQ_HANDLED; > +} [Severity: Medium] If keep_polling evaluates to false on the first iteration, this function unconditionally returns IRQ_HANDLED even though no device interrupt was pending. Will this mask unhandled interrupts and break semantics for other devices sharing the IRQ line? [ ... ] > +static void wk2xxx_poll_proc(struct kthread_work *ws) > +{ > + struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port, > + poll_work.work); > + > + /* Reuse the IRQ handler; the interrupt ID is unused here. */ > + wk2xxx_irq(0, s); > + > + kthread_queue_delayed_work(&s->kworker, &s->poll_work, > + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); > +} [Severity: High] Can this lead to a permanent deadlock when the TX FIFO drains? The wk2xxx_poll_proc runs on s->kworker and processes the loop until the device is idle. When TX empties, wk2xxx_handle_tx attempts to disable the interrupt by queuing reg_work via wk2xxx_ier_clear to the very same s->kworker. Since the worker is already blocked waiting for the interrupt to clear in the keep_polling loop, reg_work never runs, creating an infinite loop. [ ... ] > +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios, > + struct serial_rs485 *rs485) > +{ [ ... ] > + if (rs485->delay_rts_after_send) > + return -EINVAL; [ ... ] > +static const struct serial_rs485 wk2xxx_rs485_supported = { > + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | > + SER_RS485_RTS_AFTER_SEND, > + .delay_rts_before_send = 1, > + .delay_rts_after_send = 1, /* Not supported but keep returning -EINVAL */ > +}; [Severity: Medium] Is it intentional to advertise up to 1ms of delay_rts_after_send support to the serial core, but then explicitly return -EINVAL in wk2xxx_config_rs485 when that delay is requested? This mismatch causes valid user-space ioctls to fail unexpectedly. [ ... ] > +static unsigned int wk2xxx_tx_empty(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + u8 fsr; > + > + wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr); > + > + return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 : > + TIOCSER_TEMT; > +} [Severity: High] Like the previous read functions, SPI failures here are ignored. Will this leave fsr uninitialized and evaluate arbitrary stack garbage in the ternary logic? [ ... ] > +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, > + u8 *baud0, u8 *baud1, u8 *pres) > +{ [ ... ] > + *pres = ((unsigned long long)(clk % (baud * 16)) * 100 / baud + 50) / 100; > +} [Severity: High] Can this division cause link failures on 32-bit architectures? The code casts clk to a 64-bit unsigned long long and then divides by the 32-bit baud using the native / operator, which breaks 32-bit builds lacking libgcc. [ ... ] > +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, > + const struct ktermios *old) > +{ [ ... ] > + /* Set status ignore mask. */ > + port->ignore_status_mask = 0; > + if (termios->c_iflag & IGNBRK) > + port->ignore_status_mask |= WK2XXX_LSR_BI_BIT; > + if (!(termios->c_cflag & CREAD)) > + port->ignore_status_mask |= WK2XXX_LSR_BRK_ERROR_MASK; [Severity: Medium] Does this correctly filter out characters when the receiver is disabled via CREAD? By setting only error bits in the ignore mask, standard characters with a successful line status (lsr == 0) will bypass the filter in wk2xxx_handle_rx and continue being pushed to the TTY layer. [ ... ] > +static void wk2xxx_shutdown(struct uart_port *port) > +{ [ ... ] > + if (s->polling) > + kthread_cancel_delayed_work_sync(&s->poll_work); [Severity: High] Since s->poll_work is shared across all UART ports on the SPI chip, won't shutting down a single port permanently cancel the shared polling loop? This appears to starve all other concurrent open ports on the same device of TX and RX events. [ ... ] > +static int wk2xxx_probe(struct spi_device *spi) > +{ [ ... ] > + /* Register port. */ > + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); > + if (ret) > + goto out_ports; > + > + port_registered[i] = true; > + } > + > + if (spi->irq <= 0) { [ ... ] > + ret = devm_request_threaded_irq(dev, spi->irq, NULL, wk2xxx_irq, [Severity: High] Does exposing the port to user-space before its interrupt handler is requested create a race condition? If the port is opened immediately after uart_add_one_port, wk2xxx_startup will unmask IC hardware interrupts before the handler is actually registered, potentially causing unhandled IRQ storms that disable the shared line. [ ... ] > +out_ports: > + for (i = 0; i < devtype->nr_uart; i++) { > + if (s->p[i].port.line < WK2XXX_MAX_DEVS) > + ida_free(&wk2xxx_lines, s->p[i].port.line); > + if (port_registered[i]) > + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); > + } [Severity: High] Does freeing the IDA line before unregistering the port expose a race condition? Releasing the ID early could allow a concurrent probe to claim the same line ID before uart_remove_one_port is fully processed, leading to subsystem corruption. [ ... ] > +static void wk2xxx_remove(struct spi_device *spi) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(&spi->dev); > + int i; > + > + for (i = 0; i < s->devtype->nr_uart; i++) { > + ida_free(&wk2xxx_lines, s->p[i].port.line); > + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); > + } [Severity: High] Along with the IDA freeing issue mentioned above, is there a use-after-free risk here if a hardware interrupt fires? The IRQ was requested via the managed devm_request_threaded_irq API, so it stays active until after this function returns and devres cleanup runs. However, uart_remove_one_port frees the port state manually, meaning a late interrupt could deference freed memory. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260904072027.67473-1-zjzhao@edatec.cn?part=1 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 7:35 ` sashiko-bot @ 2026-09-04 8:17 ` Jiri Slaby 2026-09-04 9:32 ` zjzhao 1 sibling, 1 reply; 20+ messages in thread From: Jiri Slaby @ 2026-09-04 8:17 UTC (permalink / raw) To: zjzhao, Greg Kroah-Hartman Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree Hi, On 04. 09. 26, 9:20, zjzhao wrote: > Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). > Each IC exposes two or four full-duplex UART channels with 256-byte > RX/TX FIFOs through a single SPI slave interface and one interrupt line. > > The driver is a rework of the vendor driver (https://github.com/britus/ > wk2xxx) modeled after the NXP sc16is7xx driver. It registers ttyWK0..N > lines (IDA-allocated), uses devm_request_threaded_irq with a kthread > worker for register access, falls back to polling when the interrupt > line is not described, and supports hardware flow control and RS485 > where the IC provides them. Have you checked that there is no similar driver which could be only extended? > Also allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig > option. > > Tested on Raspberry Pi 5 boards (EDATEC IPC1200 with WK2132 on SPI0 and > SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass. > > Signed-off-by: zjzhao <zjzhao@edatec.cn> ... > --- /dev/null > +++ b/drivers/tty/serial/wk2xxx.c > @@ -0,0 +1,1245 @@ ... > +static int wk2xxx_port_reg_write(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 val) > +{ > + guard(mutex)(&s->reg_lock); > + return wk2xxx_raw_port_write(s, portno, reg, val); > +} > + > +static void wk2xxx_port_reg_update(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 mask, u8 val) > +{ > + u8 r = 0; > + > + scoped_guard(mutex, &s->reg_lock) { why is this one scoped? > + if (wk2xxx_raw_port_read(s, portno, reg, &r)) > + return; > + wk2xxx_raw_port_write(s, portno, reg, (r & ~mask) | val); > + } > +} ... > +static void wk2xxx_throttle(struct uart_port *port) > +{ > + unsigned long flags; > + > + /* Stop draining the RX FIFO to apply back-pressure. */ > + uart_port_lock_irqsave(port, &flags); This can be a guard too, right? > + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT); > + uart_port_unlock_irqrestore(port, flags); > +} > + > +static void wk2xxx_unthrottle(struct uart_port *port) > +{ > + unsigned long flags; > + > + uart_port_lock_irqsave(port, &flags); Same here. > + wk2xxx_ier_set(port, WK2XXX_SIER_RFTRIG_IEN_BIT); > + uart_port_unlock_irqrestore(port, flags); > +} > + > +static void wk2xxx_handle_tx(struct uart_port *port) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + struct tty_port *tport = &port->state->port; > + unsigned long flags; > + unsigned int portno = port->iobase; > + unsigned int txlen, to_send, sent; > + const unsigned char *tail; > + u8 fsr, tfcnt; What's the reason not to use any of the uart_port_tx* helpers? > + mutex_lock(&one->tx_lock); Why not guard? > + > + if (unlikely(port->x_char)) { > + wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, port->x_char); > + port->icount.tx++; > + port->x_char = 0; > + goto out; And kill the goto then. > + } > + > + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { > + uart_port_lock_irqsave(port, &flags); > + wk2xxx_stop_tx(port); > + uart_port_unlock_irqrestore(port, flags); > + goto out; > + } > + > + /* Limit to the free space available in the TX FIFO. */ > + wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt); > + if (tfcnt == 0) { > + wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr); > + txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE; > + } else { > + txlen = WK2XXX_FIFO_SIZE - tfcnt; > + } > + if (txlen > WK2XXX_MAX_TX_CHARS) > + txlen = WK2XXX_MAX_TX_CHARS; > + > + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); > + sent = to_send; > + while (to_send) { > + unsigned int chunk = min_t(unsigned int, to_send, > + WK2XXX_MAX_SPI_LEN); > + > + wk2xxx_fifo_write(s, portno, tail, chunk); > + tail += chunk; > + to_send -= chunk; > + } > + uart_xmit_advance(port, sent); > + > + uart_port_lock_irqsave(port, &flags); > + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) > + uart_write_wakeup(port); > + > + if (kfifo_is_empty(&tport->xmit_fifo)) > + wk2xxx_stop_tx(port); > + else > + wk2xxx_ier_set(port, WK2XXX_SIER_TFTRIG_IEN_BIT); > + uart_port_unlock_irqrestore(port, flags); > + > +out: > + mutex_unlock(&one->tx_lock); > +} ... > +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) > +{ > + struct wk2xxx_port *s = dev_id; > + bool keep_polling; > + > + do { > + u8 gifr; > + int i; > + > + keep_polling = false; > + > + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) > + return IRQ_HANDLED; /* Bus error; give up this pass. */ > + > + for (i = 0; i < s->devtype->nr_uart; ++i) > + if (gifr & BIT(i)) > + keep_polling |= wk2xxx_port_irq(s, i); > + } while (keep_polling); Should you perhaps cap the loop count as well? > + > + return IRQ_HANDLED; Pointed out by sashiko, this is indeed bad for shared irqs. > +} ... > +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr, > + u8 baud0, u8 baud1, u8 pres) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + u8 sier, scr, fsr; > + int count = 200; > + > + scoped_guard(mutex, &s->reg_lock) { Why is this scoped again? > + /* Disable all sub-UART interrupts. */ > + wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); > + > + /* Wait for the transmitter to become idle. */ > + do { > + wk2xxx_raw_port_read(s, portno, WK2XXX_FSR_REG, &fsr); > + } while ((fsr & WK2XXX_FSR_TBUSY_BIT) && count--); > + > + /* Disable the transmitter and receiver. */ > + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, &scr); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, > + scr & ~(WK2XXX_SCR_TXEN_BIT | > + WK2XXX_SCR_RXEN_BIT)); > + > + /* Program the line control register. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr); > + > + /* Configure hardware flow control levels. */ > + if (fwcr) { > + wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTH_REG, 0xf0); > + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTL_REG, 0x80); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); > + } > + > + /* Program the baud rate generator (page 1 registers). */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD0_REG, baud0); > + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD1_REG, baud1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_PRES_REG, pres); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); > + > + /* Re-enable the transmitter and receiver. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, > + scr | (WK2XXX_SCR_TXEN_BIT | > + WK2XXX_SCR_RXEN_BIT)); > + > + /* Restore the interrupt enable register. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, sier); > + } > +} > + > +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, > + u8 *baud0, u8 *baud1, u8 *pres) > +{ > + unsigned int div; > + > + div = clk / (baud * 16); > + if (div == 0) > + div = 1; > + div--; > + *baud0 = div & 0xff; > + *baud1 = (div >> 8) & 0xff; > + *pres = ((unsigned long long)(clk % (baud * 16)) * 100 / baud + 50) / 100; Did you mean to use explicit u64? > +} > + > +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, > + const struct ktermios *old) > +{ > + unsigned int baud; > + unsigned long flags; > + u8 lcr = 0, fwcr = 0; > + u8 baud0, baud1, pres; > + > + /* The WK2xxx supports 8 data bits only. */ > + termios->c_cflag &= ~CSIZE; > + termios->c_cflag |= CS8; > + > + /* Parity. */ > + if (termios->c_cflag & PARENB) { > + lcr |= WK2XXX_LCR_PAEN_BIT; > + switch (termios->c_cflag & (PARODD | CMSPAR)) { > + case 0: > + lcr |= WK2XXX_LCR_PAM1_BIT; /* even */ > + break; > + case PARODD: > + lcr |= WK2XXX_LCR_PAM0_BIT; /* odd */ > + break; > + case CMSPAR: > + break; /* space */ > + case PARODD | CMSPAR: > + lcr |= WK2XXX_LCR_PAM1_BIT | > + WK2XXX_LCR_PAM0_BIT; /* mark */ > + break; > + } > + } > + > + /* Stop bits. */ > + if (termios->c_cflag & CSTOPB) > + lcr |= WK2XXX_LCR_STPL_BIT; > + > + /* Set read status mask. */ > + port->read_status_mask = WK2XXX_LSR_OE_BIT; > + if (termios->c_iflag & INPCK) > + port->read_status_mask |= WK2XXX_LSR_PE_BIT | > + WK2XXX_LSR_FE_BIT; > + if (termios->c_iflag & (BRKINT | PARMRK)) > + port->read_status_mask |= WK2XXX_LSR_BI_BIT; > + > + /* Set status ignore mask. */ > + port->ignore_status_mask = 0; > + if (termios->c_iflag & IGNBRK) > + port->ignore_status_mask |= WK2XXX_LSR_BI_BIT; > + if (!(termios->c_cflag & CREAD)) > + port->ignore_status_mask |= WK2XXX_LSR_BRK_ERROR_MASK; > + > + /* Configure flow control. */ > + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); > + if (termios->c_cflag & CRTSCTS) { > + fwcr = WK2XXX_FWCR_FWM_RTS_CTS; > + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; > + } > + > + /* Get the baud rate generator configuration. */ > + baud = uart_get_baud_rate(port, termios, old, > + port->uartclk / 16 / 0xffff, > + port->uartclk / 16); > + > + wk2xxx_calc_divisor(port->uartclk, baud, &baud0, &baud1, &pres); > + wk2xxx_conf_port(port, lcr, fwcr, baud0, baud1, pres); > + > + uart_port_lock_irqsave(port, &flags); guard > + uart_update_timeout(port, termios->c_cflag, baud); > + uart_port_unlock_irqrestore(port, flags); > +} ... > +static const struct uart_ops wk2xxx_ops = { > + .tx_empty = wk2xxx_tx_empty, > + .set_mctrl = wk2xxx_set_mctrl, > + .get_mctrl = wk2xxx_get_mctrl, > + .stop_tx = wk2xxx_stop_tx, > + .start_tx = wk2xxx_start_tx, > + .throttle = wk2xxx_throttle, > + .unthrottle = wk2xxx_unthrottle, > + .stop_rx = wk2xxx_stop_rx, > + .enable_ms = wk2xxx_enable_ms, > + .break_ctl = wk2xxx_break_ctl, > + .startup = wk2xxx_startup, > + .shutdown = wk2xxx_shutdown, > + .set_termios = wk2xxx_set_termios, > + .type = wk2xxx_type, > + .request_port = wk2xxx_request_port, > + .release_port = wk2xxx_null_void, req + rel are optional. Drop them. > + .config_port = wk2xxx_config_port, > + .verify_port = wk2xxx_verify_port, > +}; ... > +static int wk2xxx_probe(struct spi_device *spi) > +{ > + const struct wk2xxx_devtype *devtype; > + struct device *dev = &spi->dev; > + struct wk2xxx_port *s; > + unsigned long uartclk; > + u32 clock_freq = 0; > + bool port_registered[WK2XXX_MAX_PORTS]; > + u8 val; > + int i, ret; > + > + /* Setup SPI bus. The SPI mode follows the device tree (spi-cpha, > + * spi-cpol); it defaults to SPI mode 0 when unspecified. > + */ > + spi->bits_per_word = 8; > + spi->max_speed_hz = spi->max_speed_hz ? : 10 * HZ_PER_MHZ; > + ret = spi_setup(spi); > + if (ret) > + return ret; > + > + devtype = spi_get_device_match_data(spi); > + if (!devtype) > + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); > + > + /* Allocate port structure. */ > + s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); > + if (!s) > + return dev_err_probe(dev, -ENOMEM, > + "Error allocating port structure\n"); > + > + s->devtype = devtype; > + s->spi = spi; > + mutex_init(&s->reg_lock); > + dev_set_drvdata(dev, s); > + > + /* > + * The WK2xxx has no identification register, so the best we can do > + * is to check that communication is at all possible. > + */ > + ret = wk2xxx_reg_read(s, WK2XXX_GENA_REG, &val); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to read GENA register\n"); > + > + /* Crystal clock; allow an optional DT override. */ > + uartclk = devtype->crystal_freq; > + if (device_property_read_u32(dev, "clock-frequency", &clock_freq) == 0) > + uartclk = clock_freq; > + > + /* Mark each port line and status as uninitialized. */ > + for (i = 0; i < devtype->nr_uart; ++i) { > + s->p[i].port.line = WK2XXX_MAX_DEVS; > + port_registered[i] = false; > + } > + > + kthread_init_worker(&s->kworker); > + s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker, > + "wk2xxx"); > + if (IS_ERR(s->kworker_task)) { > + ret = PTR_ERR(s->kworker_task); > + goto out_ports; > + } > + sched_set_fifo(s->kworker_task); > + > + for (i = 0; i < devtype->nr_uart; ++i) { > + ret = ida_alloc_max(&wk2xxx_lines, WK2XXX_MAX_DEVS - 1, > + GFP_KERNEL); > + if (ret < 0) > + goto out_ports; > + > + s->p[i].port.line = ret; > + > + /* Initialize port data. */ > + s->p[i].port.dev = dev; > + s->p[i].port.irq = spi->irq; > + s->p[i].port.type = PORT_WK2XXX; > + s->p[i].port.fifosize = WK2XXX_FIFO_SIZE; > + s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; > + s->p[i].port.iobase = i; > + /* > + * Use all ones as membase so that uart_configure_port() in > + * serial_core.c does not abort for SPI devices. > + */ > + s->p[i].port.membase = (void __iomem *)~0; 0 is int, does the compiler not complain about cast to pointer? Or sparse? Maybe not, I am not sure anymore... > + s->p[i].port.iotype = UPIO_PORT; > + s->p[i].port.uartclk = uartclk; > + s->p[i].port.rs485_config = wk2xxx_config_rs485; > + s->p[i].port.rs485_supported = wk2xxx_rs485_supported; > + s->p[i].port.ops = &wk2xxx_ops; thanks, -- js suse labs ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 8:17 ` Jiri Slaby @ 2026-09-04 9:32 ` zjzhao 0 siblings, 0 replies; 20+ messages in thread From: zjzhao @ 2026-09-04 9:32 UTC (permalink / raw) To: Jiri Slaby Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao From: zjzhao <zjzhao@edatec.cn> Hi Jiri, thanks for the review. Answers and a [PATCH v2] follow; v2 also carries the Sashiko round-2 findings. > Have you checked that there is no similar driver which could be only > extended? Yes. The only SPI/I2C UART bridge drivers in the tree are max3100.c, max310x.c and sc16is7xx.c. The latter two implement the 16550-style register set (THR/RHR, IER, IIR/FCR, LCR, MCR/MSR, LSR) over regmap, which is exactly what the WK2xxx does *not* provide: its register map and SPI protocol are proprietary (the sub-UART number and a page-select bit live in the SPI command byte, plus dedicated FIFO burst commands), it has no modem-control registers and only supports 8 data bits. Folding it into sc16is7xx/max310x would force a more capable shared layer to degrade, and it would mix two vendors into one binding. I take the kernel's own precedent (sc16is7xx and max310x are separate drivers even though sc16is7xx is derived from max310x) as confirmation that a dedicated driver is the right model. > > + scoped_guard(mutex, &s->reg_lock) { ... } > why is this one scoped? The early return inside made it scoped; that was overkill since the lock covers the whole function anyway. v2 uses a plain guard(mutex) for wk2xxx_port_reg_update() and wk2xxx_conf_port(). > throttle/unthrottle, set_termios: this can be a guard too, right? Yes - v2 uses guard(uart_port_lock_irqsave). > What's the reason not to use any of the uart_port_tx* helpers? TX needs sleeping SPI transfers, which cannot run while the port spinlock is held. The helpers acquire port->lock around the drain, so they are not usable here; like sc16is7xx/max310x, TX is run from the kthread worker under a per-port mutex. > Why not guard? (handle_tx) / And kill the goto then. Done in v2: guard(mutex) + scoped_guard(uart_port_lock_irqsave), the goto and the tail unlock are gone. > Should you perhaps cap the loop count as well? Done - the drain loop is now bounded by WK2XXX_IRQ_MAX_PASSES. > return IRQ_HANDLED ... bad for shared irqs Fixed in v2 (IRQ_NONE when nothing was pending). > Did you mean to use explicit u64? Fixed - the divisor computation now uses div_u64()/u64 and builds on 32-bit. > req + rel are optional. Drop them. Done - request_port/release_port and their no-op helpers are dropped. > membase=(void __iomem *)~0 ... compiler/sparse not complain? Rather than fixing the cast I switched the ports to UPIO_BUS (as sc16is7xx/max310x do) and removed the membase workaround altogether. The v2 cover letter lists the full change set. Please review; happy to iterate. thanks, -- zjzhao EDATEC Technology Co., Ltd. ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 7:20 [PATCH 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-04 7:20 ` zjzhao 2026-09-04 7:28 ` sashiko-bot 2026-09-04 9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao 2 siblings, 1 reply; 20+ messages in thread From: zjzhao @ 2026-09-04 7:20 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics), covering the compatible strings, SPI properties, the optional interrupt line and the crystal clock frequency. Also register the wkmic vendor prefix. Signed-off-by: zjzhao <zjzhao@edatec.cn> --- .../bindings/serial/wkmic,wk2xxx.yaml | 69 +++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + 2 files changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml new file mode 100644 index 000000000000..0ef76957d73f --- /dev/null +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/serial/wkmic,wk2xxx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: WKmic WK2xxx SPI to UART bridge + +maintainers: + - EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + +description: + The WK2xxx family (WK2124, WK2132, WK2168, WK2202 and WK2204) are SPI to + UART bridge ICs from WKmic (Chengdu Weikai Microelectronics). Each IC + exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs + through a single SPI slave interface and one interrupt line. + +properties: + compatible: + enum: + - wkmic,wk2124 + - wkmic,wk2132 + - wkmic,wk2168 + - wkmic,wk2202 + - wkmic,wk2204 + + reg: + maxItems: 1 + + interrupts: + description: + When missing, the device driver uses polling instead. + maxItems: 1 + + clock-frequency: + description: + Crystal or external clock frequency for the IC in Hz. The WK2xxx has + no internal clock, so this must match the connected crystal. Defaults + to 11059200 Hz when the property is absent. + minimum: 1 + maximum: 30000000 + +required: + - compatible + - reg + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + - $ref: /schemas/serial/serial.yaml# + - $ref: /schemas/serial/rs485.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + spi { + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + compatible = "wkmic,wk2132"; + reg = <0>; + spi-max-frequency = <10000000>; + clock-frequency = <11059200>; + interrupt-parent = <&gpio>; + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; + }; + }; \ No newline at end of file diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..654fc8525559 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1907,6 +1907,8 @@ patternProperties: description: Wireless Tag (qiming yunduan) "^wits,.*": description: Shenzhen Merrii Technology Co., Ltd. (WITS) + "^wkmic,.*": + description: Chengdu Weikai Microelectronics Co., Ltd. "^wlf,.*": description: Wolfson Microelectronics "^wm,.*": -- 2.43.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 7:20 ` [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao @ 2026-09-04 7:28 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-09-04 7:28 UTC (permalink / raw) To: zjzhao; +Cc: conor+dt, linux-serial, robh, devicetree Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Multi-port UART device incorrectly modeled as a single flat node without subnodes, preventing per-port independent configuration. - [Low] Missing trailing newline at the end of the YAML file. - [Low] Missing `default` property for `clock-frequency` despite the description claiming a default exists. -- commit beaa6c014e093e4f6483f239f3024922536014b5 Author: zjzhao <zjzhao@edatec.cn> dt-bindings: serial: Document WK2xxx SPI UART bindings Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204). It covers the compatible strings, SPI properties, interrupt line, and the crystal clock frequency. It also registers the wkmic vendor prefix. > diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml > new file mode 100644 > index 0000000000000..0ef76957d73f1 > --- /dev/null > +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml > @@ -0,0 +1,69 @@ [ ... ] > + clock-frequency: > + description: > + Crystal or external clock frequency for the IC in Hz. The WK2xxx has > + no internal clock, so this must match the connected crystal. Defaults > + to 11059200 Hz when the property is absent. > + minimum: 1 > + maximum: 30000000 [Severity: Low] Is it possible to include the default property value of 11059200 alongside the minimum and maximum constraints? Schema validation tools and external parsers cannot automatically infer the default fallback value because it is documented in text but not defined as a JSON schema constraint. > + > +required: > + - compatible > + - reg > + > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + - $ref: /schemas/serial/serial.yaml# > + - $ref: /schemas/serial/rs485.yaml# [Severity: High] Does this single-node model prevent independent configuration of the individual UART ports? Because this binding applies the serial.yaml and rs485.yaml definitions directly to the parent SPI device node, rather than defining child nodes like serial@0 and serial@1, all configuration properties apply globally to every channel. If a user needs to configure RS-485 on only one of the 4 UART channels using device tree properties, the single flat node approach would force the driver to apply the configuration identically to every port. Should this binding be structured to support child nodes for individual port configuration to prevent permanently restricting these hardware capabilities in the ABI? > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/interrupt-controller/irq.h> > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + serial@0 { > + compatible = "wkmic,wk2132"; > + reg = <0>; > + spi-max-frequency = <10000000>; > + clock-frequency = <11059200>; > + interrupt-parent = <&gpio>; > + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; > + }; > + }; > \ No newline at end of file [Severity: Low] Could a trailing newline be added to the end of this file? Text files without a trailing newline violate standard POSIX conventions and kernel coding styles, which can cause issues with git diffs, patching, and text processing tools. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260904072027.67473-1-zjzhao@edatec.cn?part=2 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 0/2] WK2xxx SPI to UART bridge driver 2026-09-04 7:20 [PATCH 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 7:20 ` [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao @ 2026-09-04 9:33 ` zjzhao 2026-09-04 9:33 ` [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao ` (2 more replies) 2 siblings, 3 replies; 20+ messages in thread From: zjzhao @ 2026-09-04 9:33 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao From: zjzhao <zjzhao@edatec.cn> Hi, This series adds a driver for the WK2xxx SPI-to-UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) by WKmic (Chengdu Weikai Microelectronics), together with a DT binding and the "wkmic" vendor prefix. There is no existing driver that could have been only extended instead: unlike the NXP SC16IS7xx and Maxim MAX310x parts (which implement the 16550-style register set over regmap/SPI/I2C), the WK2xxx uses a proprietary register map and SPI protocol - the sub-UART number and a page-select bit are encoded in the SPI command byte and dedicated FIFO burst commands are used - and it has no modem-control registers and supports 8 data bits only. Just as sc16is7xx and max310x live in separate drivers although the former is derived from the latter, a dedicated driver is the appropriate model here. Each channel is described by a serial@N child node of the SPI device (see the binding); serial and RS-485 properties of a child are applied to that channel only, following the max310x pattern. Changes in v2: - Binding: model each UART channel as a serial@N subnode, add a default for clock-frequency and a trailing newline (Sashiko review, round 1). - Driver - Sashiko review (round 2) and Jiri Slaby's review: * request the IRQ before registering the ports and free it again before tearing the ports down (no devm), fixing both a late-interrupt use-after-free and an open-before-request race; * keep the shared polling loop running while any port is open and stop it only when the last port is closed; * propagate SPI read errors instead of relying on uninitialized data; * use DMA-safe shared transfer buffers (no stack buffers); * no self-deadlock of the kthread worker in polling mode; * no 64-bit division, so the driver builds on 32-bit; * return IRQ_NONE/IRQ_HANDLED correctly for shared IRQs and bound the drain loop; * honour CREAD by dropping received data while the receiver is off; * disable the RX time-out interrupt in throttle() so that flow control actually holds; * advertise no RS485 RTS delays (RTS timing is hardware-driven; the serial core sanitizes non-zero requests to zero); * drop the no-op request_port/release_port ops and use UPIO_BUS instead of the membase workaround; * use the kernel cleanup guards and drop the hand-rolled lock/unlock/goto patterns. - The TX path intentionally does not use the uart_port_tx() helpers: pushing bytes requires sleeping SPI transfers, which cannot run while holding the port spinlock, so TX is driven from the kthread worker under a dedicated mutex, as in sc16is7xx/max310x. - Known hardware limitation: the FIFO-status error flags are FIFO-wide and sticky, so a batch read must apply the same line status to all bytes of the batch. Tested on Raspberry Pi 5 boards (EDATEC IPC1200 with WK2132 on SPI0 and SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass. zjzhao (2): serial: wk2xxx: Add WK2xxx SPI UART driver dt-bindings: serial: Document WK2xxx SPI UART bindings -- 2.43.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao @ 2026-09-04 9:33 ` zjzhao 2026-09-04 9:53 ` sashiko-bot 2026-09-04 9:33 ` [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 10:52 ` [PATCH v3 0/2] WK2xxx SPI to UART bridge driver zjzhao 2 siblings, 1 reply; 20+ messages in thread From: zjzhao @ 2026-09-04 9:33 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao From: zjzhao <zjzhao@edatec.cn> Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). Each IC exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI slave interface and one interrupt line. The driver is a rework of the vendor driver (https://github.com/britus/ wk2xxx) modeled after the NXP sc16is7xx driver. It registers ttyWK0..N lines (IDA-allocated), uses a threaded IRQ with a kthread worker for register access, falls back to polling when the interrupt line is not described, and supports hardware flow control and RS485 where the IC provides them. Each UART channel is matched against a serial@N DT subnode, whose serial and RS-485 properties are applied to that channel only. The IRQ is requested before the ports are registered and freed again before the ports are torn down on removal. Also allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option. Tested on Raspberry Pi 5 boards (EDATEC IPC1200 with WK2132 on SPI0 and SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass. Signed-off-by: zjzhao <zjzhao@edatec.cn> --- drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/wk2xxx.c | 1305 ++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 1326 insertions(+) create mode 100644 drivers/tty/serial/wk2xxx.c diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index cf7dba473b20..5f7a71f377ac 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1207,6 +1207,23 @@ config SERIAL_MXS_AUART_CONSOLE help Enable a MXS AUART port to be the system console. +config SERIAL_WK2XXX + tristate "WK2xxx SPI UART support" + depends on SPI_MASTER + select SERIAL_CORE + help + This selects the WK2xxx SPI to UART bridge driver. + Supported ICs are: + + WK2124 + WK2132 + WK2168 + WK2202 + WK2204 + + To compile this driver as a module, choose M here: the module + will be called wk2xxx. + config SERIAL_XILINX_PS_UART tristate "Cadence (Xilinx Zynq) UART support" depends on OF diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index bba7b21a4a1d..fdd13f3dd058 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_VT8500) += vt8500_serial.o +obj-$(CONFIG_SERIAL_WK2XXX) += wk2xxx.o obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o obj-$(CONFIG_SERIAL_ZS) += zs.o diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c new file mode 100644 index 000000000000..77f288e45234 --- /dev/null +++ b/drivers/tty/serial/wk2xxx.c @@ -0,0 +1,1305 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * WK2xxx SPI to UART bridge tty serial driver + * + * SPI-to-UART bridge ICs from WKmic (Chengdu Weikai Microelectronics): + * WK2124, WK2132, WK2168, WK2202 and WK2204. Each IC exposes two or four + * full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI + * slave interface and one interrupt line. The slave register set is split + * into two banks (page 0 / page 1) selected by the SPAGE register. + * + * This driver is a rework of the vendor "wk2xxx" driver (originally at + * https://github.com/britus/wk2xxx) and is modeled after the NXP sc16is7xx + * driver. + * + * (C) Copyright 2022 WKIC Ltd. by Xu XunWei Tech, Xuxunwei + * (C) Copyright 2024 EoF Software Labs, B. Eschrich + * Copyright (C) 2026 EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + */ + +#include <linux/atomic.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/kthread.h> +#include <linux/math64.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/units.h> +#include <linux/workqueue.h> + +#define WK2XXX_NAME "wk2xxx" +#define WK2XXX_MAX_DEVS 8 /* Total number of lines. */ +#define WK2XXX_MAX_PORTS 4 /* Max number of ports per IC. */ +#define WK2XXX_FIFO_SIZE 256 +#define WK2XXX_MAX_SPI_LEN 30 /* Max bytes per SPI FIFO burst. */ +#define WK2XXX_MAX_TX_CHARS 200 /* Leave headroom in the TX FIFO. */ +#define WK2XXX_RXFIFO_LEVEL 0x40 /* RX FIFO trigger level. */ +#define WK2XXX_TXFIFO_LEVEL 0x01 /* TX FIFO trigger level. */ +#define WK2XXX_POLL_PERIOD_MS 10 +#define WK2XXX_IRQ_MAX_PASSES 8 /* Bound the IRQ drain loop. */ + +/* SPI command byte: bit 6 = read, bit 7 = FIFO access. */ +#define WK2XXX_SPI_READ BIT(6) +#define WK2XXX_SPI_FIFO_WRITE BIT(7) +#define WK2XXX_SPI_FIFO_READ (BIT(7) | BIT(6)) + +/* Marker used to address registers located in page 1. */ +#define WK2XXX_PAGE1 BIT(7) + +/* Global registers. */ +#define WK2XXX_GENA_REG 0x00 /* Global UART enable */ +#define WK2XXX_GRST_REG 0x01 /* Global reset */ +#define WK2XXX_GMUT_REG 0x02 /* Master UART control */ +#define WK2XXX_GIER_REG 0x10 /* Global interrupt enable */ +#define WK2XXX_GIFR_REG 0x11 /* Global interrupt flag */ + +/* Port (sub-UART) registers, page 0. */ +#define WK2XXX_SPAGE_REG 0x03 /* Register page select */ +#define WK2XXX_SCR_REG 0x04 /* Slave control */ +#define WK2XXX_LCR_REG 0x05 /* Line control */ +#define WK2XXX_FCR_REG 0x06 /* FIFO control */ +#define WK2XXX_SIER_REG 0x07 /* Slave interrupt enable */ +#define WK2XXX_SIFR_REG 0x08 /* Slave interrupt flag */ +#define WK2XXX_TFCNT_REG 0x09 /* TX FIFO count */ +#define WK2XXX_RFCNT_REG 0x0a /* RX FIFO count */ +#define WK2XXX_FSR_REG 0x0b /* FIFO status */ +#define WK2XXX_LSR_REG 0x0c /* Line status */ +#define WK2XXX_FDAT_REG 0x0d /* FIFO data */ +#define WK2XXX_FWCR_REG 0x0e /* Flow control */ +#define WK2XXX_RS485_REG 0x0f /* RS485 control */ + +/* Port (sub-UART) registers, page 1. */ +#define WK2XXX_BAUD1_REG (0x04 | WK2XXX_PAGE1) /* Divisor Latch High */ +#define WK2XXX_BAUD0_REG (0x05 | WK2XXX_PAGE1) /* Divisor Latch Low */ +#define WK2XXX_PRES_REG (0x06 | WK2XXX_PAGE1) /* Fractional divisor */ +#define WK2XXX_RFTL_REG (0x07 | WK2XXX_PAGE1) /* RX FIFO trigger level */ +#define WK2XXX_TFTL_REG (0x08 | WK2XXX_PAGE1) /* TX FIFO trigger level */ +#define WK2XXX_FWTH_REG (0x09 | WK2XXX_PAGE1) /* Flow control high level */ +#define WK2XXX_FWTL_REG (0x0a | WK2XXX_PAGE1) /* Flow control low level */ +#define WK2XXX_XON1_REG (0x0b | WK2XXX_PAGE1) /* Xon word */ +#define WK2XXX_XOFF1_REG (0x0c | WK2XXX_PAGE1) /* Xoff word */ +#define WK2XXX_SADR_REG (0x0d | WK2XXX_PAGE1) /* RS485 auto address */ +#define WK2XXX_SAEN_REG (0x0e | WK2XXX_PAGE1) /* RS485 address mask */ +#define WK2XXX_RRSDLY_REG (0x0f | WK2XXX_PAGE1) /* RS485 RTS delay */ + +/* SCR register bits. */ +#define WK2XXX_SCR_RXEN_BIT BIT(0) +#define WK2XXX_SCR_TXEN_BIT BIT(1) + +/* LCR register bits. */ +#define WK2XXX_LCR_STPL_BIT BIT(0) /* Two stop bits */ +#define WK2XXX_LCR_PAM0_BIT BIT(1) /* Parity mode bit 0 */ +#define WK2XXX_LCR_PAM1_BIT BIT(2) /* Parity mode bit 1 */ +#define WK2XXX_LCR_PAEN_BIT BIT(3) /* Parity enable */ +#define WK2XXX_LCR_BREAK_BIT BIT(5) /* TX break */ + +/* SIER register bits. */ +#define WK2XXX_SIER_RFTRIG_IEN_BIT BIT(0) /* RX FIFO trigger */ +#define WK2XXX_SIER_RXOUT_IEN_BIT BIT(1) /* RX time-out */ +#define WK2XXX_SIER_TFTRIG_IEN_BIT BIT(2) /* TX FIFO trigger */ + +/* SIFR register bits. */ +#define WK2XXX_SIFR_RFTRIG_INT_BIT BIT(0) +#define WK2XXX_SIFR_RXOVT_INT_BIT BIT(1) +#define WK2XXX_SIFR_TFTRIG_INT_BIT BIT(2) + +/* FSR register bits. */ +#define WK2XXX_FSR_TBUSY_BIT BIT(0) +#define WK2XXX_FSR_TFULL_BIT BIT(1) +#define WK2XXX_FSR_TDAT_BIT BIT(2) +#define WK2XXX_FSR_RDAT_BIT BIT(3) +#define WK2XXX_FSR_RFPE_BIT BIT(4) /* RX FIFO parity error */ +#define WK2XXX_FSR_RFFE_BIT BIT(5) /* RX FIFO frame error */ +#define WK2XXX_FSR_RFBI_BIT BIT(6) /* RX FIFO break */ +#define WK2XXX_FSR_RFOE_BIT BIT(7) /* RX FIFO overrun */ +#define WK2XXX_FSR_ERR_MASK GENMASK(7, 4) + +/* LSR error bits, for use with uart_insert_char(). */ +#define WK2XXX_LSR_PE_BIT BIT(0) +#define WK2XXX_LSR_FE_BIT BIT(1) +#define WK2XXX_LSR_BI_BIT BIT(2) +#define WK2XXX_LSR_OE_BIT BIT(3) +#define WK2XXX_LSR_BRK_ERROR_MASK (WK2XXX_LSR_OE_BIT | WK2XXX_LSR_PE_BIT | \ + WK2XXX_LSR_FE_BIT | WK2XXX_LSR_BI_BIT) +/* Internal marker: drop all received data (termios CREAD is clear). */ +#define WK2XXX_LSR_IGNORE_DATA BIT(7) + +/* + * FWCR register bits. The flow-control mode is selected by the FWM2-0 + * field in bits 6-4 (WK2132 has no FWCR register; writing it is ignored). + */ +#define WK2XXX_FWCR_FWM_MASK GENMASK(6, 4) +#define WK2XXX_FWCR_FWM_RTS_CTS FIELD_PREP(WK2XXX_FWCR_FWM_MASK, 0x3) + +/* RS485 register bits. */ +#define WK2XXX_RS485_RTSINV_BIT BIT(0) +#define WK2XXX_RS485_RTSEN_BIT BIT(1) +#define WK2XXX_RS485_RSRS485_BIT BIT(6) + +struct wk2xxx_devtype { + const char *name; + int nr_uart; + unsigned long crystal_freq; +}; + +#define WK2XXX_RECONF_IER BIT(0) +#define WK2XXX_RECONF_RS485 BIT(1) + +struct wk2xxx_one_config { + unsigned int flags; + u8 ier_mask; + u8 ier_val; +}; + +struct wk2xxx_one { + struct uart_port port; + struct mutex tx_lock; /* Serializes the TX path. */ + struct kthread_work tx_work; + struct kthread_work reg_work; + struct wk2xxx_one_config config; + unsigned char buf[WK2XXX_FIFO_SIZE]; /* RX buffer. */ +}; + +struct wk2xxx_port { + const struct wk2xxx_devtype *devtype; + struct spi_device *spi; + struct mutex reg_lock; /* SPI register access. */ + struct kthread_worker kworker; + struct task_struct *kworker_task; + struct kthread_delayed_work poll_work; + bool polling; + bool irq_requested; + atomic_t open_ports; + /* + * Shared SPI transfer buffers. All SPI accesses are serialized by + * s->reg_lock, so these are never used concurrently. + */ + u8 spi_tx[WK2XXX_MAX_SPI_LEN + 1]; + u8 spi_rx[WK2XXX_MAX_SPI_LEN + 1]; + struct wk2xxx_one p[]; +}; + +static DEFINE_IDA(wk2xxx_lines); + +static struct uart_driver wk2xxx_uart = { + .owner = THIS_MODULE, + .driver_name = WK2XXX_NAME, + .dev_name = "ttyWK", + .nr = WK2XXX_MAX_DEVS, +}; + +#define to_wk2xxx_one(p, e) ((container_of((p), struct wk2xxx_one, e))) + +static const struct wk2xxx_devtype wk2124_devtype = { + .name = "WK2124", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2132_devtype = { + .name = "WK2132", + .nr_uart = 2, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2168_devtype = { + .name = "WK2168", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2202_devtype = { + .name = "WK2202", + .nr_uart = 2, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2204_devtype = { + .name = "WK2204", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +/* + * The following functions are the low-level SPI accessors. The caller must + * hold s->reg_lock, so that multi-byte accesses and page switches are + * performed atomically on the SPI bus. + */ +static int wk2xxx_spi_transfer(struct wk2xxx_port *s, const u8 *tx, u8 *rx, + unsigned int len) +{ + struct spi_transfer xfer = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + }; + struct spi_message msg; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + return spi_sync(s->spi, &msg); +} + +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + int ret; + + tx[0] = WK2XXX_SPI_READ | addr; + tx[1] = 0; + ret = wk2xxx_spi_transfer(s, tx, rx, 2); + if (ret) { + *val = 0; + return ret; + } + + *val = rx[1]; + return 0; +} + +static int wk2xxx_raw_write(struct wk2xxx_port *s, u8 addr, u8 val) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + + tx[0] = addr; + tx[1] = val; + + return wk2xxx_spi_transfer(s, tx, rx, 2); +} + +static unsigned int wk2xxx_port_addr(unsigned int portno, u8 reg) +{ + /* The sub-UART number is encoded in the upper nibble of the cmd byte. */ + return (portno << 4) | reg; +} + +static int wk2xxx_raw_port_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +static int wk2xxx_raw_port_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +/* + * Locked wrappers used outside the register sequences that already hold + * s->reg_lock. + */ +static int wk2xxx_reg_read(struct wk2xxx_port *s, u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_read(s, reg, val); +} + +static int wk2xxx_port_reg_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_read(s, portno, reg, val); +} + +static int wk2xxx_port_reg_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_write(s, portno, reg, val); +} + +static void wk2xxx_port_reg_update(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 mask, u8 val) +{ + u8 r = 0; + + guard(mutex)(&s->reg_lock); + if (wk2xxx_raw_port_read(s, portno, reg, &r)) + return; + wk2xxx_raw_port_write(s, portno, reg, (r & ~mask) | val); +} + +static int wk2xxx_fifo_read(struct wk2xxx_port *s, unsigned int portno, + u8 *buf, unsigned int len) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + int ret; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + memset(tx, 0, WK2XXX_MAX_SPI_LEN + 1); + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_READ); + + guard(mutex)(&s->reg_lock); + ret = wk2xxx_spi_transfer(s, tx, rx, len + 1); + if (ret) + return ret; + + memcpy(buf, rx + 1, len); + return 0; +} + +static int wk2xxx_fifo_write(struct wk2xxx_port *s, unsigned int portno, + const u8 *buf, unsigned int len) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_WRITE); + memcpy(tx + 1, buf, len); + + guard(mutex)(&s->reg_lock); + return wk2xxx_spi_transfer(s, tx, rx, len + 1); +} + +static void wk2xxx_ier_set(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val |= bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_ier_clear(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val &= ~bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_stop_tx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_TFTRIG_IEN_BIT); +} + +static void wk2xxx_stop_rx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_throttle(struct uart_port *port) +{ + /* + * Stop draining the RX FIFO to apply back-pressure. The RX time-out + * interrupt must be disabled too, otherwise remaining FIFO data would + * still be pushed out and defeat the flow control request. + */ + guard(uart_port_lock_irqsave)(port); + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_unthrottle(struct uart_port *port) +{ + guard(uart_port_lock_irqsave)(port); + wk2xxx_ier_set(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_handle_tx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct tty_port *tport = &port->state->port; + unsigned int portno = port->iobase; + unsigned int txlen, to_send, sent; + const unsigned char *tail; + u8 fsr, tfcnt; + + guard(mutex)(&one->tx_lock); + + if (unlikely(port->x_char)) { + wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { + scoped_guard(uart_port_lock_irqsave, port) { + wk2xxx_stop_tx(port); + } + return; + } + + /* Limit to the free space available in the TX FIFO. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt)) + return; + if (tfcnt == 0) { + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) + return; + txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE; + } else { + txlen = WK2XXX_FIFO_SIZE - tfcnt; + } + if (txlen > WK2XXX_MAX_TX_CHARS) + txlen = WK2XXX_MAX_TX_CHARS; + + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + sent = 0; + while (to_send) { + unsigned int chunk = min_t(unsigned int, to_send, + WK2XXX_MAX_SPI_LEN); + + if (wk2xxx_fifo_write(s, portno, tail, chunk)) + break; + tail += chunk; + to_send -= chunk; + sent += chunk; + } + uart_xmit_advance(port, sent); + + scoped_guard(uart_port_lock_irqsave, port) { + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (kfifo_is_empty(&tport->xmit_fifo)) + wk2xxx_stop_tx(port); + else + wk2xxx_ier_set(port, WK2XXX_SIER_TFTRIG_IEN_BIT); + } +} + +static void wk2xxx_handle_rx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + unsigned int i, rxlen, len_p, chunk; + u8 fsr = 0, rfcnt = 0, lsr = 0, flag = TTY_NORMAL; + + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) + return; + + if (!(fsr & WK2XXX_FSR_RDAT_BIT)) + return; + + /* Get the number of bytes available in the RX FIFO. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) + return; + if (rfcnt == 0) { + /* The count may race with the FIFO status bit; retry once. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) + return; + rxlen = rfcnt ? rfcnt : WK2XXX_FIFO_SIZE; + } else { + rxlen = rfcnt; + } + + /* Read the FIFO contents in chunks. */ + len_p = 0; + while (rxlen) { + chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN); + if (wk2xxx_fifo_read(s, portno, one->buf + len_p, chunk)) + return; + len_p += chunk; + rxlen -= chunk; + } + rxlen = len_p; + + /* Map the FIFO status register error flags to line status. */ + if (fsr & WK2XXX_FSR_ERR_MASK) { + if (fsr & WK2XXX_FSR_RFPE_BIT) { + port->icount.parity++; + lsr |= WK2XXX_LSR_PE_BIT; + flag = TTY_PARITY; + } + if (fsr & WK2XXX_FSR_RFFE_BIT) { + port->icount.frame++; + lsr |= WK2XXX_LSR_FE_BIT; + flag = TTY_FRAME; + } + if (fsr & WK2XXX_FSR_RFOE_BIT) { + port->icount.overrun++; + lsr |= WK2XXX_LSR_OE_BIT; + flag = TTY_OVERRUN; + } + if (fsr & WK2XXX_FSR_RFBI_BIT) { + port->icount.brk++; + lsr |= WK2XXX_LSR_BI_BIT; + flag = TTY_BREAK; + } + } + + port->icount.rx += rxlen; + + /* CREAD is clear: drain the FIFO and drop all received data. */ + if (port->ignore_status_mask & WK2XXX_LSR_IGNORE_DATA) + return; + + for (i = 0; i < rxlen; ++i) { + u8 ch = one->buf[i]; + + if (uart_handle_sysrq_char(port, ch)) + continue; + + if (lsr & port->ignore_status_mask) + continue; + + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); + } + + tty_flip_buffer_push(&port->state->port); +} + +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno) +{ + struct uart_port *port = &s->p[portno].port; + u8 sifr = 0, sier = 0; + bool rc = false; + + if (wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr) || + wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier)) + return false; + + if (sifr & (WK2XXX_SIFR_RFTRIG_INT_BIT | WK2XXX_SIFR_RXOVT_INT_BIT)) { + wk2xxx_handle_rx(port); + rc = true; + } + + if ((sifr & WK2XXX_SIFR_TFTRIG_INT_BIT) && + (sier & WK2XXX_SIER_TFTRIG_IEN_BIT)) { + wk2xxx_handle_tx(port); + rc = true; + } + + return rc; +} + +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) +{ + struct wk2xxx_port *s = dev_id; + bool handled = false; + bool keep_polling; + int passes = WK2XXX_IRQ_MAX_PASSES; + + do { + u8 gifr; + int i; + + keep_polling = false; + + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) + return IRQ_HANDLED; /* Bus error; give up this pass. */ + + if (!gifr) + break; + + handled = true; + + for (i = 0; i < s->devtype->nr_uart; ++i) + if (gifr & BIT(i)) + keep_polling |= wk2xxx_port_irq(s, i); + } while (keep_polling && !s->polling && --passes); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static void wk2xxx_poll_proc(struct kthread_work *ws) +{ + struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port, + poll_work.work); + + /* Reuse the IRQ handler; the interrupt ID is unused here. */ + wk2xxx_irq(0, s); + + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); +} + +static void wk2xxx_tx_proc(struct kthread_work *ws) +{ + struct uart_port *port = &(to_wk2xxx_one(ws, tx_work)->port); + + wk2xxx_handle_tx(port); +} + +static void wk2xxx_start_tx(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + + kthread_queue_work(&s->kworker, &one->tx_work); +} + +static void wk2xxx_reconf_rs485(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 rs485 = 0; + + if (port->rs485.flags & SER_RS485_ENABLED) { + rs485 = WK2XXX_RS485_RSRS485_BIT | WK2XXX_RS485_RTSEN_BIT; + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + rs485 |= WK2XXX_RS485_RTSINV_BIT; + } + + wk2xxx_port_reg_write(s, port->iobase, WK2XXX_RS485_REG, rs485); +} + +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + /* + * RTS is driven by hardware and its timing cannot be influenced + * from the driver. Non-zero RTS delays are rejected (sanitized to + * zero) by the serial core. + */ + one->config.flags |= WK2XXX_RECONF_RS485; + kthread_queue_work(&s->kworker, &one->reg_work); + + return 0; +} + +static void wk2xxx_reg_proc(struct kthread_work *ws) +{ + struct wk2xxx_one *one = to_wk2xxx_one(ws, reg_work); + struct wk2xxx_port *s = dev_get_drvdata(one->port.dev); + struct wk2xxx_one_config config; + unsigned long irqflags; + + uart_port_lock_irqsave(&one->port, &irqflags); + config = one->config; + memset(&one->config, 0, sizeof(one->config)); + uart_port_unlock_irqrestore(&one->port, irqflags); + + if (config.flags & WK2XXX_RECONF_IER) + wk2xxx_port_reg_update(s, one->port.iobase, WK2XXX_SIER_REG, + config.ier_mask, config.ier_val); + + if (config.flags & WK2XXX_RECONF_RS485) + wk2xxx_reconf_rs485(&one->port); +} + +static unsigned int wk2xxx_tx_empty(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 fsr = 0; + + if (wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr)) + return TIOCSER_TEMT; + + return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 : + TIOCSER_TEMT; +} + +static unsigned int wk2xxx_get_mctrl(struct uart_port *port) +{ + /* The WK2xxx does not expose modem control lines. */ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + +static void wk2xxx_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* The WK2xxx does not support modem control lines. */ +} + +static void wk2xxx_enable_ms(struct uart_port *port) +{ + /* The WK2xxx does not have modem status registers. */ +} + +static void wk2xxx_break_ctl(struct uart_port *port, int break_state) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + wk2xxx_port_reg_update(s, port->iobase, WK2XXX_LCR_REG, + WK2XXX_LCR_BREAK_BIT, + break_state ? WK2XXX_LCR_BREAK_BIT : 0); +} + +/* + * Configure a sub-UART: disable interrupts and TX/RX, program the line + * control and baud rate registers and restore the previous state. + */ +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr, + u8 baud0, u8 baud1, u8 pres) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 sier, scr, fsr; + int count = 200; + + guard(mutex)(&s->reg_lock); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier); + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Wait for the transmitter to become idle. */ + do { + wk2xxx_raw_port_read(s, portno, WK2XXX_FSR_REG, &fsr); + } while ((fsr & WK2XXX_FSR_TBUSY_BIT) && count--); + + /* Disable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, &scr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr & ~(WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Program the line control register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr); + + /* Configure hardware flow control levels. */ + if (fwcr) { + wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTH_REG, 0xf0); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTL_REG, 0x80); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + /* Program the baud rate generator (page 1 registers). */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD0_REG, baud0); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD1_REG, baud1); + wk2xxx_raw_port_write(s, portno, WK2XXX_PRES_REG, pres); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + + /* Re-enable the transmitter and receiver. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr | (WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Restore the interrupt enable register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, sier); +} + +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, + u8 *baud0, u8 *baud1, u8 *pres) +{ + unsigned int div, rem; + + div = clk / (baud * 16); + if (div == 0) + div = 1; + div--; + *baud0 = div & 0xff; + *baud1 = (div >> 8) & 0xff; + + rem = clk % (baud * 16); + *pres = (u32)div_u64((u64)rem * 100, baud); + *pres = (*pres + 50) / 100; +} + +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, + const struct ktermios *old) +{ + unsigned int baud; + u8 lcr = 0, fwcr = 0; + u8 baud0, baud1, pres; + + /* The WK2xxx supports 8 data bits only. */ + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + + /* Parity. */ + if (termios->c_cflag & PARENB) { + lcr |= WK2XXX_LCR_PAEN_BIT; + switch (termios->c_cflag & (PARODD | CMSPAR)) { + case 0: + lcr |= WK2XXX_LCR_PAM1_BIT; /* even */ + break; + case PARODD: + lcr |= WK2XXX_LCR_PAM0_BIT; /* odd */ + break; + case CMSPAR: + break; /* space */ + case PARODD | CMSPAR: + lcr |= WK2XXX_LCR_PAM1_BIT | + WK2XXX_LCR_PAM0_BIT; /* mark */ + break; + } + } + + /* Stop bits. */ + if (termios->c_cflag & CSTOPB) + lcr |= WK2XXX_LCR_STPL_BIT; + + /* Set read status mask. */ + port->read_status_mask = WK2XXX_LSR_OE_BIT; + if (termios->c_iflag & INPCK) + port->read_status_mask |= WK2XXX_LSR_PE_BIT | + WK2XXX_LSR_FE_BIT; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= WK2XXX_LSR_BI_BIT; + + /* Set status ignore mask. */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNBRK) + port->ignore_status_mask |= WK2XXX_LSR_BI_BIT; + if (!(termios->c_cflag & CREAD)) + port->ignore_status_mask |= WK2XXX_LSR_BRK_ERROR_MASK | + WK2XXX_LSR_IGNORE_DATA; + + /* Configure flow control. */ + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + if (termios->c_cflag & CRTSCTS) { + fwcr = WK2XXX_FWCR_FWM_RTS_CTS; + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; + } + + /* Get the baud rate generator configuration. */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / 16 / 0xffff, + port->uartclk / 16); + + wk2xxx_calc_divisor(port->uartclk, baud, &baud0, &baud1, &pres); + wk2xxx_conf_port(port, lcr, fwcr, baud0, baud1, pres); + + guard(uart_port_lock_irqsave)(port); + uart_update_timeout(port, termios->c_cflag, baud); +} + +static int wk2xxx_startup(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Enable the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + + /* Reset the sub-UART. */ + wk2xxx_raw_write(s, WK2XXX_GRST_REG, BIT(portno)); + + /* Enable the sub-UART interrupt in the global mask. */ + wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Enable RX FIFO trigger and RX time-out interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, + WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); + + /* Enable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, ®); + reg |= WK2XXX_SCR_TXEN_BIT | WK2XXX_SCR_RXEN_BIT; + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, reg); + + /* Reset and configure the FIFOs. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xff); + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xfc); + + /* Set the RX/TX FIFO trigger levels. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_RFTL_REG, + WK2XXX_RXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_TFTL_REG, + WK2XXX_TXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + kfifo_reset(&port->state->port.xmit_fifo); + + /* Start the shared polling loop when the first port is opened. */ + if (s->polling && atomic_inc_return(&s->open_ports) == 1) + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); + + return 0; +} + +static void wk2xxx_shutdown(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Disable the sub-UART interrupt in the global mask. */ + wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®); + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Reset the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GRST_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg); + + /* Disable the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + } + + /* Stop the shared polling loop once the last port is closed. */ + if (s->polling && atomic_dec_return(&s->open_ports) == 0) + kthread_cancel_delayed_work_sync(&s->poll_work); + + kthread_flush_worker(&s->kworker); +} + +static const char *wk2xxx_type(struct uart_port *port) +{ + return (port->type == PORT_WK2XXX) ? WK2XXX_NAME : NULL; +} + +static void wk2xxx_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_WK2XXX; +} + +static int wk2xxx_verify_port(struct uart_port *port, struct serial_struct *s) +{ + if ((s->type != PORT_UNKNOWN) && (s->type != PORT_WK2XXX)) + return -EINVAL; + if (s->irq != port->irq) + return -EINVAL; + + return 0; +} + +static const struct uart_ops wk2xxx_ops = { + .tx_empty = wk2xxx_tx_empty, + .set_mctrl = wk2xxx_set_mctrl, + .get_mctrl = wk2xxx_get_mctrl, + .stop_tx = wk2xxx_stop_tx, + .start_tx = wk2xxx_start_tx, + .throttle = wk2xxx_throttle, + .unthrottle = wk2xxx_unthrottle, + .stop_rx = wk2xxx_stop_rx, + .enable_ms = wk2xxx_enable_ms, + .break_ctl = wk2xxx_break_ctl, + .startup = wk2xxx_startup, + .shutdown = wk2xxx_shutdown, + .set_termios = wk2xxx_set_termios, + .type = wk2xxx_type, + .config_port = wk2xxx_config_port, + .verify_port = wk2xxx_verify_port, +}; + +static const struct serial_rs485 wk2xxx_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND, + /* RTS timing is hardware-driven; RTS delays are not supported. */ +}; + +static int wk2xxx_probe(struct spi_device *spi) +{ + const struct wk2xxx_devtype *devtype; + struct device *dev = &spi->dev; + struct wk2xxx_port *s; + unsigned long uartclk; + u32 clock_freq = 0; + bool port_registered[WK2XXX_MAX_PORTS]; + u8 val; + int i, ret; + + /* Setup SPI bus. The SPI mode follows the device tree (spi-cpha, + * spi-cpol); it defaults to SPI mode 0 when unspecified. + */ + spi->bits_per_word = 8; + spi->max_speed_hz = spi->max_speed_hz ? : 10 * HZ_PER_MHZ; + ret = spi_setup(spi); + if (ret) + return ret; + + devtype = spi_get_device_match_data(spi); + if (!devtype) + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); + + /* Allocate port structure. */ + s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); + if (!s) + return dev_err_probe(dev, -ENOMEM, + "Error allocating port structure\n"); + + s->devtype = devtype; + s->spi = spi; + mutex_init(&s->reg_lock); + atomic_set(&s->open_ports, 0); + dev_set_drvdata(dev, s); + + /* + * The WK2xxx has no identification register, so the best we can do + * is to check that communication is at all possible. + */ + ret = wk2xxx_reg_read(s, WK2XXX_GENA_REG, &val); + if (ret) + return dev_err_probe(dev, ret, "Failed to read GENA register\n"); + + /* Crystal clock; allow an optional DT override. */ + uartclk = devtype->crystal_freq; + if (device_property_read_u32(dev, "clock-frequency", &clock_freq) == 0) + uartclk = clock_freq; + + /* Mark each port line and status as uninitialized. */ + for (i = 0; i < devtype->nr_uart; ++i) { + s->p[i].port.line = WK2XXX_MAX_DEVS; + port_registered[i] = false; + } + + kthread_init_worker(&s->kworker); + s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker, + "wk2xxx"); + if (IS_ERR(s->kworker_task)) { + ret = PTR_ERR(s->kworker_task); + goto out_ports; + } + sched_set_fifo(s->kworker_task); + + if (spi->irq <= 0) { + /* Poll the device instead of using interrupts. */ + s->polling = true; + kthread_init_delayed_work(&s->poll_work, wk2xxx_poll_proc); + } else { + /* + * Setup interrupt. We first try to acquire the IRQ line as + * level IRQ. If that succeeds, we can allow sharing the + * interrupt as well. In case the interrupt controller + * doesn't support that, we fall back to a non-shared + * falling-edge trigger. + */ + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_LOW | IRQF_SHARED | + IRQF_ONESHOT, dev_name(dev), s); + if (ret) + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(dev), s); + if (ret) { + dev_err(dev, "Unable to request IRQ %i\n", spi->irq); + goto out_ports; + } + s->irq_requested = true; + } + + for (i = 0; i < devtype->nr_uart; ++i) { + struct fwnode_handle *saved_fwnode = dev_fwnode(dev); + struct device_node *port_np = NULL; + struct device_node *child; + + ret = ida_alloc_max(&wk2xxx_lines, WK2XXX_MAX_DEVS - 1, + GFP_KERNEL); + if (ret < 0) + goto out_ports; + + s->p[i].port.line = ret; + + /* Locate the matching "serial@i" DT subnode, if any. */ + for_each_available_child_of_node(dev->of_node, child) { + u32 reg; + + if (!of_node_name_eq(child, "serial")) + continue; + if (of_property_read_u32(child, "reg", ®)) + continue; + if (reg == i) { + port_np = child; + break; + } + } + + /* Initialize port data. */ + s->p[i].port.dev = dev; + s->p[i].port.irq = spi->irq; + s->p[i].port.type = PORT_WK2XXX; + s->p[i].port.fifosize = WK2XXX_FIFO_SIZE; + s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; + s->p[i].port.iobase = i; + s->p[i].port.iotype = UPIO_BUS; + s->p[i].port.uartclk = uartclk; + s->p[i].port.rs485_config = wk2xxx_config_rs485; + s->p[i].port.rs485_supported = wk2xxx_rs485_supported; + s->p[i].port.ops = &wk2xxx_ops; + + mutex_init(&s->p[i].tx_lock); + + kthread_init_work(&s->p[i].tx_work, wk2xxx_tx_proc); + kthread_init_work(&s->p[i].reg_work, wk2xxx_reg_proc); + + /* + * Temporarily retarget dev's fwnode to the per-port subnode + * so uart_get_rs485_mode() picks up the per-port properties. + */ + if (port_np) { + device_set_node(dev, of_fwnode_handle(port_np)); + ret = uart_get_rs485_mode(&s->p[i].port); + device_set_node(dev, saved_fwnode); + of_node_put(port_np); + if (ret) + goto out_ports; + } + + /* Register port. */ + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); + if (ret) + goto out_ports; + + port_registered[i] = true; + } + + return 0; + +out_ports: + if (s->irq_requested) + free_irq(spi->irq, s); + + for (i = 0; i < devtype->nr_uart; i++) { + if (port_registered[i]) + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + if (s->p[i].port.line < WK2XXX_MAX_DEVS) + ida_free(&wk2xxx_lines, s->p[i].port.line); + } + + if (!IS_ERR(s->kworker_task)) + kthread_stop(s->kworker_task); + + return ret; +} + +static void wk2xxx_remove(struct spi_device *spi) +{ + struct wk2xxx_port *s = dev_get_drvdata(&spi->dev); + int i; + + /* + * Free the IRQ before removing the ports so that a late interrupt + * cannot dereference port state that has already been released. + */ + if (s->irq_requested) + free_irq(spi->irq, s); + + if (s->polling) + kthread_cancel_delayed_work_sync(&s->poll_work); + + kthread_flush_worker(&s->kworker); + + for (i = 0; i < s->devtype->nr_uart; i++) { + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + ida_free(&wk2xxx_lines, s->p[i].port.line); + } + + kthread_stop(s->kworker_task); +} + +static const struct of_device_id wk2xxx_dt_ids[] = { + { .compatible = "wkmic,wk2124", .data = &wk2124_devtype }, + { .compatible = "wkmic,wk2132", .data = &wk2132_devtype }, + { .compatible = "wkmic,wk2168", .data = &wk2168_devtype }, + { .compatible = "wkmic,wk2202", .data = &wk2202_devtype }, + { .compatible = "wkmic,wk2204", .data = &wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(of, wk2xxx_dt_ids); + +static const struct spi_device_id wk2xxx_id_table[] = { + { "wk2124", (kernel_ulong_t)&wk2124_devtype }, + { "wk2132", (kernel_ulong_t)&wk2132_devtype }, + { "wk2168", (kernel_ulong_t)&wk2168_devtype }, + { "wk2202", (kernel_ulong_t)&wk2202_devtype }, + { "wk2204", (kernel_ulong_t)&wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(spi, wk2xxx_id_table); + +static struct spi_driver wk2xxx_spi_driver = { + .driver = { + .name = WK2XXX_NAME, + .of_match_table = wk2xxx_dt_ids, + }, + .probe = wk2xxx_probe, + .remove = wk2xxx_remove, + .id_table = wk2xxx_id_table, +}; + +static int __init wk2xxx_init(void) +{ + int ret; + + ret = uart_register_driver(&wk2xxx_uart); + if (ret) + return ret; + + ret = spi_register_driver(&wk2xxx_spi_driver); + if (ret) + uart_unregister_driver(&wk2xxx_uart); + + return ret; +} +module_init(wk2xxx_init); + +static void __exit wk2xxx_exit(void) +{ + spi_unregister_driver(&wk2xxx_spi_driver); + uart_unregister_driver(&wk2xxx_uart); +} +module_exit(wk2xxx_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xuxunwei"); +MODULE_AUTHOR("B. Eschrich"); +MODULE_AUTHOR("EDATEC Technology Co., Ltd. <zjzhao@edatec.cn>"); +MODULE_DESCRIPTION("WK2xxx SPI UART driver"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 377884e3856a..05c75d3ef479 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -234,6 +234,9 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* WK2xxx SPI to UART bridge */ +#define PORT_WK2XXX 124 + /* Generic type identifier for ports which type is not important to userspace. */ #define PORT_GENERIC (-1) -- 2.43.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 9:33 ` [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-04 9:53 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-09-04 9:53 UTC (permalink / raw) To: zjzhao; +Cc: devicetree, conor+dt, linux-serial, robh Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [High] Requesting the threaded IRQ before UART ports are fully initialized causes a NULL pointer dereference on early interrupts. - [High] Unprotected lockless access to the UART transmit kfifo and `x_char` in `wk2xxx_handle_tx()`. - [High] Race condition in polling worker initialization and cancellation can permanently stall polling. - [High] SPI transfer buffers embedded in `struct wk2xxx_port` violate DMA cache coherency rules. - [High] Early return in `wk2xxx_handle_tx` after sending `x_char` stalls pending transmission. - [High] Improper teardown order in `wk2xxx_remove()` flushes the kworker before unregistering the port, leading to Use-After-Free. - [High] Data race on the shared SPI transmit buffer `s->spi_tx` without lock protection. - [High] Kernel panic via division by zero if `clock-frequency` device tree property is 0. - [Medium] Data race on port state and statistics due to missing `port->lock` acquisition. - [Medium] Improper teardown order in `wk2xxx_remove()` stalls the TX flushing process during unbind. -- commit 6a58cb664a5c2176dca76b8cff9410c1dd264818 Author: zjzhao <zjzhao@edatec.cn> serial: wk2xxx: Add WK2xxx SPI UART driver Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). Each IC exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI slave interface and one interrupt line. > diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c > index 0000000000000..77f288e452340 > --- /dev/null > +++ b/drivers/tty/serial/wk2xxx.c > @@ -176,11 +176,11 @@ struct wk2xxx_port { > atomic_t open_ports; > /* > * Shared SPI transfer buffers. All SPI accesses are serialized by > * s->reg_lock, so these are never used concurrently. > */ > u8 spi_tx[WK2XXX_MAX_SPI_LEN + 1]; > u8 spi_rx[WK2XXX_MAX_SPI_LEN + 1]; > struct wk2xxx_one p[]; > }; [Severity: High] Does embedding these buffers directly without ____cacheline_aligned risk cacheline tearing during DMA operations? When the SPI subsystem maps these buffers for DMA transfers on non-cache-coherent architectures, cache invalidation during unmap could destroy adjacent fields in the same cache line. If there are concurrent CPU writes to neighboring fields like open_ports or polling, could they be silently overwritten by stale cache data? [ ... ] > @@ -382,9 +382,9 @@ static int wk2xxx_fifo_read(struct wk2xxx_port *s, unsigned int portno, > > if (len == 0 || len > WK2XXX_MAX_SPI_LEN) > return -EINVAL; > > memset(tx, 0, WK2XXX_MAX_SPI_LEN + 1); > tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_READ); > > guard(mutex)(&s->reg_lock); [Severity: High] Is it safe to modify the shared s->spi_tx buffer before acquiring the s->reg_lock mutex? If multiple threads execute wk2xxx_fifo_read or wk2xxx_fifo_write concurrently, it seems they would mutate this unprotected buffer simultaneously, corrupting the SPI commands. [ ... ] > @@ -487,9 +487,9 @@ static void wk2xxx_handle_tx(struct uart_port *port) > > guard(mutex)(&one->tx_lock); > > if (unlikely(port->x_char)) { > wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, port->x_char); > port->icount.tx++; > port->x_char = 0; > return; > } [Severity: High] Does this code modify port->x_char and port->icount.tx without holding the required port->lock? [Severity: High] Also, does this early return stall pending transmission if an x_char is sent? By returning immediately, it bypasses the logic at the end of the function that checks the xmit_fifo and re-enables the hardware transmit interrupt (WK2XXX_SIER_TFTRIG_IEN_BIT), which might strand the remaining data in the kfifo. > @@ -511,8 +511,8 @@ static void wk2xxx_handle_tx(struct uart_port *port) > txlen = WK2XXX_MAX_TX_CHARS; > > to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); > sent = 0; > while (to_send) { > unsigned int chunk = min_t(unsigned int, to_send, > WK2XXX_MAX_SPI_LEN); [Severity: High] Is it safe to access and advance the serial core kfifo without holding port->lock? If the serial core concurrently executes an operation like uart_flush_buffer (which calls kfifo_reset), the kfifo internal state could be corrupted. [ ... ] > @@ -582,3 +582,3 @@ static void wk2xxx_handle_rx(struct uart_port *port) > } > > port->icount.rx += rxlen; [Severity: Medium] Is it safe to increment the receive byte counter without locking? This could lead to lost updates if there are concurrent readers or writers. [ ... ] > @@ -819,4 +819,4 @@ static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, > unsigned int div, rem; > > div = clk / (baud * 16); > if (div == 0) [Severity: High] Could baud be zero here and cause a division by zero panic? If clock-frequency is set to 0 in the device tree, uart_get_baud_rate will fall back to returning 0, resulting in baud being 0 here. [ ... ] > @@ -849,6 +849,6 @@ static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, > > /* Configure flow control. */ > port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); > if (termios->c_cflag & CRTSCTS) { > fwcr = WK2XXX_FWCR_FWM_RTS_CTS; > port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; > } [Severity: Medium] Is it safe to modify port->status, port->read_status_mask, and port->ignore_status_mask before the spinlock is acquired via guard(uart_port_lock_irqsave) later in the function? [ ... ] > @@ -883,4 +883,4 @@ static int wk2xxx_startup(struct uart_port *port) > > /* Start the shared polling loop when the first port is opened. */ > if (s->polling && atomic_inc_return(&s->open_ports) == 1) > kthread_queue_delayed_work(&s->kworker, &s->poll_work, > msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); [Severity: High] Could this sequence race with port shutdown and permanently stall polling? If port A shuts down concurrently with port B starting up, port B can queue the delayed work just before port A executes kthread_cancel_delayed_work_sync in wk2xxx_shutdown. This would leave the polling loop cancelled even though open_ports is 1. [ ... ] > @@ -1054,5 +1054,5 @@ static int wk2xxx_probe(struct spi_device *spi) > */ > ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, > IRQF_TRIGGER_LOW | IRQF_SHARED | > IRQF_ONESHOT, dev_name(dev), s); > if (ret) [Severity: High] Can requesting the IRQ here before the UART ports are fully initialized cause a NULL pointer dereference on early interrupts? If the UART device has a pending interrupt when the driver is probed, the handler wk2xxx_irq will execute and traverse down to tty_flip_buffer_push(&port->state->port) in wk2xxx_handle_rx. Because port->state is not initialized until uart_add_one_port is called later, this results in a NULL pointer dereference. [ ... ] > @@ -1115,4 +1115,4 @@ static int wk2xxx_probe(struct spi_device *spi) > /* Crystal clock; allow an optional DT override. */ > uartclk = devtype->crystal_freq; > if (device_property_read_u32(dev, "clock-frequency", &clock_freq) == 0) > uartclk = clock_freq; [Severity: High] Does this code apply the clock-frequency from the device tree without validating it? If the property is maliciously or accidentally set to 0, or a value less than 16, it can lead to a division by zero panic in wk2xxx_calc_divisor. [ ... ] > @@ -1243,10 +1243,10 @@ static void wk2xxx_remove(struct spi_device *spi) > */ > if (s->irq_requested) > free_irq(spi->irq, s); > > if (s->polling) > kthread_cancel_delayed_work_sync(&s->poll_work); > > kthread_flush_worker(&s->kworker); > > for (i = 0; i < s->devtype->nr_uart; i++) { > uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); [Severity: Medium] Does freeing the IRQ before unregistering the port stall the TX flushing process during unbind? When uart_remove_one_port is executed, it attempts to wait for the transmit FIFO to drain. Since the driver relies on hardware transmit interrupts to wake the worker and send chunks of data from the software kfifo, prematurely freeing the IRQ disables this mechanism and the teardown sequence will hang until internal timeouts trigger. [Severity: High] Also, does flushing the worker before unregistering the port lead to a use-after-free? While the worker is flushed, userspace or the TTY layer can still queue new transmit works because the port remains fully registered. Once uart_remove_one_port finishes, the port xmit_fifo buffer is freed. A late tx_work that was queued during or immediately after the flush will then execute and attempt to access the destroyed kfifo. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260904093341.100826-1-zjzhao@edatec.cn?part=1 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 9:33 ` [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-04 9:33 ` zjzhao 2026-09-04 9:40 ` sashiko-bot 2026-09-05 7:31 ` Krzysztof Kozlowski 2026-09-04 10:52 ` [PATCH v3 0/2] WK2xxx SPI to UART bridge driver zjzhao 2 siblings, 2 replies; 20+ messages in thread From: zjzhao @ 2026-09-04 9:33 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao From: zjzhao <zjzhao@edatec.cn> Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). Each UART channel is described by a serial@N child node that carries its own serial and RS-485 properties. Also register the wkmic vendor prefix. Signed-off-by: zjzhao <zjzhao@edatec.cn> --- .../bindings/serial/wkmic,wk2xxx.yaml | 115 ++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + 2 files changed, 117 insertions(+) create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml new file mode 100644 index 000000000000..c781482c0f2d --- /dev/null +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/serial/wkmic,wk2xxx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: WKmic WK2xxx SPI to UART bridge + +maintainers: + - EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + +description: + The WK2xxx family (WK2124, WK2132, WK2168, WK2202 and WK2204) are SPI to + UART bridge ICs from WKmic (Chengdu Weikai Microelectronics). Each IC + exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs + through a single SPI slave interface and one interrupt line. Each channel + is described by a "serial@N" child node that carries its own serial and + RS-485 properties. + +properties: + compatible: + enum: + - wkmic,wk2124 + - wkmic,wk2132 + - wkmic,wk2168 + - wkmic,wk2202 + - wkmic,wk2204 + + reg: + maxItems: 1 + + interrupts: + description: + When missing, the device driver uses polling instead. + maxItems: 1 + + clock-frequency: + description: + Crystal or external clock frequency for the IC in Hz. The WK2xxx has + no internal clock, so this must match the connected crystal. + default: 11059200 + minimum: 1 + maximum: 30000000 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^serial@[0-3]$": + type: object + description: A single UART channel of the chip. + allOf: + - $ref: /schemas/serial/serial.yaml# + - $ref: /schemas/serial/rs485.yaml# + properties: + reg: + description: UART channel number on the chip. + maximum: 3 + required: + - reg + unevaluatedProperties: false + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + properties: + compatible: + contains: + enum: + - wkmic,wk2132 + - wkmic,wk2202 + then: + patternProperties: + "^serial@[23]$": false + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + spi { + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + compatible = "wkmic,wk2132"; + reg = <0>; + spi-max-frequency = <10000000>; + clock-frequency = <11059200>; + interrupt-parent = <&gpio>; + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + reg = <0>; + }; + + serial@1 { + reg = <1>; + rs485-rts-active-low; + linux,rs485-enabled-at-boot-time; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..654fc8525559 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1907,6 +1907,8 @@ patternProperties: description: Wireless Tag (qiming yunduan) "^wits,.*": description: Shenzhen Merrii Technology Co., Ltd. (WITS) + "^wkmic,.*": + description: Chengdu Weikai Microelectronics Co., Ltd. "^wlf,.*": description: Wolfson Microelectronics "^wm,.*": -- 2.43.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 9:33 ` [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao @ 2026-09-04 9:40 ` sashiko-bot 2026-09-05 7:31 ` Krzysztof Kozlowski 1 sibling, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-09-04 9:40 UTC (permalink / raw) To: zjzhao; +Cc: robh, linux-serial, devicetree, conor+dt > From: zjzhao <zjzhao@edatec.cn> > > Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). > Each UART channel is described by a serial@N child node that carries its > own serial and RS-485 properties. Also register the wkmic vendor prefix. > > Signed-off-by: zjzhao <zjzhao@edatec.cn> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260904093341.100826-1-zjzhao@edatec.cn?part=2 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 9:33 ` [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 9:40 ` sashiko-bot @ 2026-09-05 7:31 ` Krzysztof Kozlowski 1 sibling, 0 replies; 20+ messages in thread From: Krzysztof Kozlowski @ 2026-09-05 7:31 UTC (permalink / raw) To: zjzhao, Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree On 04/09/2026 11:33, zjzhao@edatec.cn wrote: > From: zjzhao <zjzhao@edatec.cn> > > Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). > Each UART channel is described by a serial@N child node that carries its > own serial and RS-485 properties. Also register the wkmic vendor prefix. > > Signed-off-by: zjzhao <zjzhao@edatec.cn> Same problems as v1... 1. Name is a copied login. 2. drop second/last, redundant "bindings". The "dt-bindings" prefix is already stating that these are bindings. See also: https://elixir.bootlin.com/linux/v7.1-rc7/source/Documentation/devicetree/bindings/submitting-patches.rst#L23 3. Please organize the patch documenting the compatible (DT bindings) before the patch using that compatible. See also: https://elixir.bootlin.com/linux/v6.14-rc6/source/Documentation/devicetree/bindings/submitting-patches.rst#L46 > --- > .../bindings/serial/wkmic,wk2xxx.yaml | 115 ++++++++++++++++++ > .../devicetree/bindings/vendor-prefixes.yaml | 2 + > 2 files changed, 117 insertions(+) > create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml Filename: wkmic,wk2124.yaml as explained in writing bindings. > > diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml > new file mode 100644 > index 000000000000..c781482c0f2d > --- /dev/null > +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml > @@ -0,0 +1,115 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/serial/wkmic,wk2xxx.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: WKmic WK2xxx SPI to UART bridge > + > +maintainers: > + - EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> > + > +description: > + The WK2xxx family (WK2124, WK2132, WK2168, WK2202 and WK2204) are SPI to > + UART bridge ICs from WKmic (Chengdu Weikai Microelectronics). Each IC > + exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs > + through a single SPI slave interface and one interrupt line. Each channel > + is described by a "serial@N" child node that carries its own serial and > + RS-485 properties. > + > +properties: > + compatible: > + enum: > + - wkmic,wk2124 > + - wkmic,wk2132 > + - wkmic,wk2168 > + - wkmic,wk2202 > + - wkmic,wk2204 > + > + reg: > + maxItems: 1 > + > + interrupts: > + description: > + When missing, the device driver uses polling instead. > + maxItems: 1 > + > + clock-frequency: > + description: > + Crystal or external clock frequency for the IC in Hz. The WK2xxx has > + no internal clock, so this must match the connected crystal. Then this is a clock input, no? Use proper clocks in such case. Property is discouraged. > + default: 11059200 > + minimum: 1 > + maximum: 30000000 > + > + "#address-cells": > + const: 1 > + > + "#size-cells": > + const: 0 > + > +patternProperties: > + "^serial@[0-3]$": > + type: object > + description: A single UART channel of the chip. > + allOf: > + - $ref: /schemas/serial/serial.yaml# > + - $ref: /schemas/serial/rs485.yaml# > + properties: > + reg: > + description: UART channel number on the chip. > + maximum: 3 > + required: > + - reg > + unevaluatedProperties: false > + > +required: > + - compatible > + - reg > + - "#address-cells" > + - "#size-cells" > + > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > + - if: > + properties: > + compatible: > + contains: > + enum: > + - wkmic,wk2132 > + - wkmic,wk2202 > + then: > + patternProperties: > + "^serial@[23]$": false > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/interrupt-controller/irq.h> > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + serial@0 { > + compatible = "wkmic,wk2132"; > + reg = <0>; > + spi-max-frequency = <10000000>; > + clock-frequency = <11059200>; > + interrupt-parent = <&gpio>; > + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; > + #address-cells = <1>; > + #size-cells = <0>; > + > + serial@0 { > + reg = <0>; > + }; > + > + serial@1 { > + reg = <1>; > + rs485-rts-active-low; > + linux,rs485-enabled-at-boot-time; > + }; > + }; > + }; > diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml > index ba2002969373..654fc8525559 100644 > --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml > +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml > @@ -1907,6 +1907,8 @@ patternProperties: > description: Wireless Tag (qiming yunduan) > "^wits,.*": > description: Shenzhen Merrii Technology Co., Ltd. (WITS) > + "^wkmic,.*": > + description: Chengdu Weikai Microelectronics Co., Ltd. What is the website URL? > "^wlf,.*": > description: Wolfson Microelectronics > "^wm,.*": Best regards, Krzysztof ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 0/2] WK2xxx SPI to UART bridge driver 2026-09-04 9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 9:33 ` [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 9:33 ` [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao @ 2026-09-04 10:52 ` zjzhao 2026-09-04 10:52 ` [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 10:52 ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2 siblings, 2 replies; 20+ messages in thread From: zjzhao @ 2026-09-04 10:52 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao Hi, This series adds a driver for the WK2xxx SPI-to-UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) by WKmic (Chengdu Weikai Microelectronics), together with a DT binding and the "wkmic" vendor prefix. Why a new driver instead of extending an existing one is briefly explained below the changelog. Changes in v3 (addresses the Sashiko AI review on [PATCH v2]): - probe: register all ports before requesting the threaded IRQ, and reset the chip / disable every sub-UART first, so an early interrupt can never reach a port whose ->state is not set up yet - remove: unregister the ports (each open port is shut down, disabling its sub-UART) before freeing the IRQ, so no late IRQ or queued worker can target a port that is being torn down - handle_tx: do not return right after sending x_char; pending xmit FIFO data is drained and the TX trigger is re-armed below, so it cannot be stranded until the next start_tx(). x_char is snapshot under the port lock - polling: the shared poll loop is started/stopped under a per-device poll_lock and poll_proc stops re-queuing itself once the last port is closed, closing a race that could permanently stop polling - SPI buffers: spi_tx/spi_rx are cache-line aligned and fifo_read()/ fifo_write() take reg_lock before touching them - clock-frequency: a zero value is rejected in probe and calc_divisor() guards against a zero baud rate - set_termios: read_status_mask/ignore_status_mask/status are published under the port lock Items that were reported but intentionally kept as-is, because they mirror the merged sc16is7xx/max310x drivers: - handle_tx() reads the transmit kfifo without port->lock: the TX path is serialized by tx_lock and the kfifo is single-producer/ single-consumer between the serial core and this driver, exactly like sc16is7xx_handle_tx() - icount updates in the RX/TX paths are done without port->lock, the same as sc16is7xx/max310x and the 8250 receive path The WK2xxx uses a proprietary register map and a custom SPI protocol (channel number and a page-select bit are encoded in the SPI command byte, plus dedicated FIFO burst commands). It has no modem-control registers and supports 8 data bits only, so it cannot be folded into the 16550-style sc16is7xx/max310x drivers without degrading a more capable shared layer. Following the kernel's own precedent (sc16is7xx and max310x are separate drivers although sc16is7xx is derived from max310x), a dedicated driver is the appropriate model. Tested on Raspberry Pi boards (EDATEC IPC1200 with WK2132 on SPI0 and SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass. v3 was re-verified on SBC2300 (SPI1/WK2204, kernel 6.18.39): all four ports register and open, multi-port termios churn and repeated module load/unload cycles are clean. The RX data path is unchanged since the loopback runs. zjzhao (2): serial: wk2xxx: Add WK2xxx SPI UART driver dt-bindings: serial: Document WK2xxx SPI UART bindings .../bindings/serial/wkmic,wk2xxx.yaml | 115 ++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/wk2xxx.c | 1401 +++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 6 files changed, 1539 insertions(+) create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml create mode 100644 drivers/tty/serial/wk2xxx.c -- 2.43.0 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 10:52 ` [PATCH v3 0/2] WK2xxx SPI to UART bridge driver zjzhao @ 2026-09-04 10:52 ` zjzhao 2026-09-04 11:10 ` sashiko-bot 2026-09-04 10:52 ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 1 sibling, 1 reply; 20+ messages in thread From: zjzhao @ 2026-09-04 10:52 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). Each IC exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI slave interface and one interrupt line. The driver is a rework of the vendor driver (https://github.com/britus/ wk2xxx) modeled after the NXP sc16is7xx driver. It registers ttyWK0..N lines (IDA-allocated), uses a threaded IRQ with a kthread worker for register access, falls back to polling when the interrupt line is not described, and supports hardware flow control and RS485 where the IC provides them. Each UART channel is matched against a serial@N DT subnode, whose serial and RS-485 properties are applied to that channel only. The chip is reset and every sub-UART disabled at probe time, and the IRQ is requested only after all ports have been registered, so an early interrupt can never reach a port whose state is not ready yet. On removal the ports are unregistered (each open port is shut down, disabling its sub-UART) before the IRQ is freed. Also allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option. Tested on Raspberry Pi 5 boards (EDATEC IPC1200 with WK2132 on SPI0 and SBC2300 with WK2204 on SPI1); loopback TX/RX tests pass. Signed-off-by: zjzhao <zjzhao@edatec.cn> --- drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/wk2xxx.c | 1401 ++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 1422 insertions(+) create mode 100644 drivers/tty/serial/wk2xxx.c diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index cf7dba473b20..5f7a71f377ac 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1207,6 +1207,23 @@ config SERIAL_MXS_AUART_CONSOLE help Enable a MXS AUART port to be the system console. +config SERIAL_WK2XXX + tristate "WK2xxx SPI UART support" + depends on SPI_MASTER + select SERIAL_CORE + help + This selects the WK2xxx SPI to UART bridge driver. + Supported ICs are: + + WK2124 + WK2132 + WK2168 + WK2202 + WK2204 + + To compile this driver as a module, choose M here: the module + will be called wk2xxx. + config SERIAL_XILINX_PS_UART tristate "Cadence (Xilinx Zynq) UART support" depends on OF diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index bba7b21a4a1d..fdd13f3dd058 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_VT8500) += vt8500_serial.o +obj-$(CONFIG_SERIAL_WK2XXX) += wk2xxx.o obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o obj-$(CONFIG_SERIAL_ZS) += zs.o diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c new file mode 100644 index 000000000000..f8005bbec937 --- /dev/null +++ b/drivers/tty/serial/wk2xxx.c @@ -0,0 +1,1401 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * WK2xxx SPI to UART bridge tty serial driver + * + * SPI-to-UART bridge ICs from WKmic (Chengdu Weikai Microelectronics): + * WK2124, WK2132, WK2168, WK2202 and WK2204. Each IC exposes two or four + * full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI + * slave interface and one interrupt line. The slave register set is split + * into two banks (page 0 / page 1) selected by the SPAGE register. + * + * This driver is a rework of the vendor "wk2xxx" driver (originally at + * https://github.com/britus/wk2xxx) and is modeled after the NXP sc16is7xx + * driver. + * + * (C) Copyright 2022 WKIC Ltd. by Xu XunWei Tech, Xuxunwei + * (C) Copyright 2024 EoF Software Labs, B. Eschrich + * Copyright (C) 2026 EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + */ + +#include <linux/atomic.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cache.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/kthread.h> +#include <linux/math64.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/units.h> +#include <linux/workqueue.h> + +#define WK2XXX_NAME "wk2xxx" +#define WK2XXX_MAX_DEVS 8 /* Total number of lines. */ +#define WK2XXX_MAX_PORTS 4 /* Max number of ports per IC. */ +#define WK2XXX_FIFO_SIZE 256 +#define WK2XXX_MAX_SPI_LEN 30 /* Max bytes per SPI FIFO burst. */ +#define WK2XXX_MAX_TX_CHARS 200 /* Leave headroom in the TX FIFO. */ +#define WK2XXX_RXFIFO_LEVEL 0x40 /* RX FIFO trigger level. */ +#define WK2XXX_TXFIFO_LEVEL 0x01 /* TX FIFO trigger level. */ +#define WK2XXX_POLL_PERIOD_MS 10 +#define WK2XXX_IRQ_MAX_PASSES 8 /* Bound the IRQ drain loop. */ + +/* SPI command byte: bit 6 = read, bit 7 = FIFO access. */ +#define WK2XXX_SPI_READ BIT(6) +#define WK2XXX_SPI_FIFO_WRITE BIT(7) +#define WK2XXX_SPI_FIFO_READ (BIT(7) | BIT(6)) + +/* Marker used to address registers located in page 1. */ +#define WK2XXX_PAGE1 BIT(7) + +/* Global registers. */ +#define WK2XXX_GENA_REG 0x00 /* Global UART enable */ +#define WK2XXX_GRST_REG 0x01 /* Global reset */ +#define WK2XXX_GMUT_REG 0x02 /* Master UART control */ +#define WK2XXX_GIER_REG 0x10 /* Global interrupt enable */ +#define WK2XXX_GIFR_REG 0x11 /* Global interrupt flag */ + +/* Port (sub-UART) registers, page 0. */ +#define WK2XXX_SPAGE_REG 0x03 /* Register page select */ +#define WK2XXX_SCR_REG 0x04 /* Slave control */ +#define WK2XXX_LCR_REG 0x05 /* Line control */ +#define WK2XXX_FCR_REG 0x06 /* FIFO control */ +#define WK2XXX_SIER_REG 0x07 /* Slave interrupt enable */ +#define WK2XXX_SIFR_REG 0x08 /* Slave interrupt flag */ +#define WK2XXX_TFCNT_REG 0x09 /* TX FIFO count */ +#define WK2XXX_RFCNT_REG 0x0a /* RX FIFO count */ +#define WK2XXX_FSR_REG 0x0b /* FIFO status */ +#define WK2XXX_LSR_REG 0x0c /* Line status */ +#define WK2XXX_FDAT_REG 0x0d /* FIFO data */ +#define WK2XXX_FWCR_REG 0x0e /* Flow control */ +#define WK2XXX_RS485_REG 0x0f /* RS485 control */ + +/* Port (sub-UART) registers, page 1. */ +#define WK2XXX_BAUD1_REG (0x04 | WK2XXX_PAGE1) /* Divisor Latch High */ +#define WK2XXX_BAUD0_REG (0x05 | WK2XXX_PAGE1) /* Divisor Latch Low */ +#define WK2XXX_PRES_REG (0x06 | WK2XXX_PAGE1) /* Fractional divisor */ +#define WK2XXX_RFTL_REG (0x07 | WK2XXX_PAGE1) /* RX FIFO trigger level */ +#define WK2XXX_TFTL_REG (0x08 | WK2XXX_PAGE1) /* TX FIFO trigger level */ +#define WK2XXX_FWTH_REG (0x09 | WK2XXX_PAGE1) /* Flow control high level */ +#define WK2XXX_FWTL_REG (0x0a | WK2XXX_PAGE1) /* Flow control low level */ +#define WK2XXX_XON1_REG (0x0b | WK2XXX_PAGE1) /* Xon word */ +#define WK2XXX_XOFF1_REG (0x0c | WK2XXX_PAGE1) /* Xoff word */ +#define WK2XXX_SADR_REG (0x0d | WK2XXX_PAGE1) /* RS485 auto address */ +#define WK2XXX_SAEN_REG (0x0e | WK2XXX_PAGE1) /* RS485 address mask */ +#define WK2XXX_RRSDLY_REG (0x0f | WK2XXX_PAGE1) /* RS485 RTS delay */ + +/* SCR register bits. */ +#define WK2XXX_SCR_RXEN_BIT BIT(0) +#define WK2XXX_SCR_TXEN_BIT BIT(1) + +/* LCR register bits. */ +#define WK2XXX_LCR_STPL_BIT BIT(0) /* Two stop bits */ +#define WK2XXX_LCR_PAM0_BIT BIT(1) /* Parity mode bit 0 */ +#define WK2XXX_LCR_PAM1_BIT BIT(2) /* Parity mode bit 1 */ +#define WK2XXX_LCR_PAEN_BIT BIT(3) /* Parity enable */ +#define WK2XXX_LCR_BREAK_BIT BIT(5) /* TX break */ + +/* SIER register bits. */ +#define WK2XXX_SIER_RFTRIG_IEN_BIT BIT(0) /* RX FIFO trigger */ +#define WK2XXX_SIER_RXOUT_IEN_BIT BIT(1) /* RX time-out */ +#define WK2XXX_SIER_TFTRIG_IEN_BIT BIT(2) /* TX FIFO trigger */ + +/* SIFR register bits. */ +#define WK2XXX_SIFR_RFTRIG_INT_BIT BIT(0) +#define WK2XXX_SIFR_RXOVT_INT_BIT BIT(1) +#define WK2XXX_SIFR_TFTRIG_INT_BIT BIT(2) + +/* FSR register bits. */ +#define WK2XXX_FSR_TBUSY_BIT BIT(0) +#define WK2XXX_FSR_TFULL_BIT BIT(1) +#define WK2XXX_FSR_TDAT_BIT BIT(2) +#define WK2XXX_FSR_RDAT_BIT BIT(3) +#define WK2XXX_FSR_RFPE_BIT BIT(4) /* RX FIFO parity error */ +#define WK2XXX_FSR_RFFE_BIT BIT(5) /* RX FIFO frame error */ +#define WK2XXX_FSR_RFBI_BIT BIT(6) /* RX FIFO break */ +#define WK2XXX_FSR_RFOE_BIT BIT(7) /* RX FIFO overrun */ +#define WK2XXX_FSR_ERR_MASK GENMASK(7, 4) + +/* LSR error bits, for use with uart_insert_char(). */ +#define WK2XXX_LSR_PE_BIT BIT(0) +#define WK2XXX_LSR_FE_BIT BIT(1) +#define WK2XXX_LSR_BI_BIT BIT(2) +#define WK2XXX_LSR_OE_BIT BIT(3) +#define WK2XXX_LSR_BRK_ERROR_MASK (WK2XXX_LSR_OE_BIT | WK2XXX_LSR_PE_BIT | \ + WK2XXX_LSR_FE_BIT | WK2XXX_LSR_BI_BIT) +/* Internal marker: drop all received data (termios CREAD is clear). */ +#define WK2XXX_LSR_IGNORE_DATA BIT(7) + +/* + * FWCR register bits. The flow-control mode is selected by the FWM2-0 + * field in bits 6-4 (WK2132 has no FWCR register; writing it is ignored). + */ +#define WK2XXX_FWCR_FWM_MASK GENMASK(6, 4) +#define WK2XXX_FWCR_FWM_RTS_CTS FIELD_PREP(WK2XXX_FWCR_FWM_MASK, 0x3) + +/* RS485 register bits. */ +#define WK2XXX_RS485_RTSINV_BIT BIT(0) +#define WK2XXX_RS485_RTSEN_BIT BIT(1) +#define WK2XXX_RS485_RSRS485_BIT BIT(6) + +struct wk2xxx_devtype { + const char *name; + int nr_uart; + unsigned long crystal_freq; +}; + +#define WK2XXX_RECONF_IER BIT(0) +#define WK2XXX_RECONF_RS485 BIT(1) + +struct wk2xxx_one_config { + unsigned int flags; + u8 ier_mask; + u8 ier_val; +}; + +struct wk2xxx_one { + struct uart_port port; + struct mutex tx_lock; /* Serializes the TX path. */ + struct kthread_work tx_work; + struct kthread_work reg_work; + struct wk2xxx_one_config config; + unsigned char buf[WK2XXX_FIFO_SIZE]; /* RX buffer. */ +}; + +struct wk2xxx_port { + const struct wk2xxx_devtype *devtype; + struct spi_device *spi; + struct mutex reg_lock; /* SPI register access. */ + struct mutex poll_lock; /* Serializes polling start/stop. */ + struct kthread_worker kworker; + struct task_struct *kworker_task; + struct kthread_delayed_work poll_work; + bool polling; + bool irq_requested; + atomic_t open_ports; + /* + * Shared SPI transfer buffers. All SPI accesses are serialized by + * s->reg_lock, so these are never used concurrently. Each buffer is + * cache-line aligned so that DMA cache maintenance performed on a + * buffer never invalidates a line shared with the fields above. + */ + u8 spi_tx[WK2XXX_MAX_SPI_LEN + 1] ____cacheline_aligned; + u8 spi_rx[WK2XXX_MAX_SPI_LEN + 1] ____cacheline_aligned; + struct wk2xxx_one p[]; +}; + +static DEFINE_IDA(wk2xxx_lines); + +static struct uart_driver wk2xxx_uart = { + .owner = THIS_MODULE, + .driver_name = WK2XXX_NAME, + .dev_name = "ttyWK", + .nr = WK2XXX_MAX_DEVS, +}; + +#define to_wk2xxx_one(p, e) ((container_of((p), struct wk2xxx_one, e))) + +static const struct wk2xxx_devtype wk2124_devtype = { + .name = "WK2124", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2132_devtype = { + .name = "WK2132", + .nr_uart = 2, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2168_devtype = { + .name = "WK2168", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2202_devtype = { + .name = "WK2202", + .nr_uart = 2, + .crystal_freq = 11059200, +}; + +static const struct wk2xxx_devtype wk2204_devtype = { + .name = "WK2204", + .nr_uart = 4, + .crystal_freq = 11059200, +}; + +/* + * The following functions are the low-level SPI accessors. The caller must + * hold s->reg_lock, so that multi-byte accesses and page switches are + * performed atomically on the SPI bus. + */ +static int wk2xxx_spi_transfer(struct wk2xxx_port *s, const u8 *tx, u8 *rx, + unsigned int len) +{ + struct spi_transfer xfer = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + }; + struct spi_message msg; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + return spi_sync(s->spi, &msg); +} + +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + int ret; + + tx[0] = WK2XXX_SPI_READ | addr; + tx[1] = 0; + ret = wk2xxx_spi_transfer(s, tx, rx, 2); + if (ret) { + *val = 0; + return ret; + } + + *val = rx[1]; + return 0; +} + +static int wk2xxx_raw_write(struct wk2xxx_port *s, u8 addr, u8 val) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + + tx[0] = addr; + tx[1] = val; + + return wk2xxx_spi_transfer(s, tx, rx, 2); +} + +static unsigned int wk2xxx_port_addr(unsigned int portno, u8 reg) +{ + /* The sub-UART number is encoded in the upper nibble of the cmd byte. */ + return (portno << 4) | reg; +} + +static int wk2xxx_raw_port_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +static int wk2xxx_raw_port_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +/* + * Locked wrappers used outside the register sequences that already hold + * s->reg_lock. + */ +static int wk2xxx_reg_read(struct wk2xxx_port *s, u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_read(s, reg, val); +} + +static int wk2xxx_reg_write(struct wk2xxx_port *s, u8 addr, u8 val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_write(s, addr, val); +} + +static int wk2xxx_port_reg_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_read(s, portno, reg, val); +} + +static int wk2xxx_port_reg_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_write(s, portno, reg, val); +} + +static void wk2xxx_port_reg_update(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 mask, u8 val) +{ + u8 r = 0; + + guard(mutex)(&s->reg_lock); + if (wk2xxx_raw_port_read(s, portno, reg, &r)) + return; + wk2xxx_raw_port_write(s, portno, reg, (r & ~mask) | val); +} + +static int wk2xxx_fifo_read(struct wk2xxx_port *s, unsigned int portno, + u8 *buf, unsigned int len) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + int ret; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + /* + * Take the register lock before touching the shared SPI buffers so a + * concurrent transfer cannot observe (or be corrupted by) a partially + * constructed command. + */ + guard(mutex)(&s->reg_lock); + + memset(tx, 0, WK2XXX_MAX_SPI_LEN + 1); + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_READ); + + ret = wk2xxx_spi_transfer(s, tx, rx, len + 1); + if (ret) + return ret; + + memcpy(buf, rx + 1, len); + return 0; +} + +static int wk2xxx_fifo_write(struct wk2xxx_port *s, unsigned int portno, + const u8 *buf, unsigned int len) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + /* + * Take the register lock before touching the shared SPI buffers so a + * concurrent transfer cannot observe (or be corrupted by) a partially + * constructed command. + */ + guard(mutex)(&s->reg_lock); + + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_WRITE); + memcpy(tx + 1, buf, len); + + return wk2xxx_spi_transfer(s, tx, rx, len + 1); +} + +static void wk2xxx_ier_set(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val |= bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_ier_clear(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val &= ~bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_stop_tx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_TFTRIG_IEN_BIT); +} + +static void wk2xxx_stop_rx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_throttle(struct uart_port *port) +{ + /* + * Stop draining the RX FIFO to apply back-pressure. The RX time-out + * interrupt must be disabled too, otherwise remaining FIFO data would + * still be pushed out and defeat the flow control request. + */ + guard(uart_port_lock_irqsave)(port); + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_unthrottle(struct uart_port *port) +{ + guard(uart_port_lock_irqsave)(port); + wk2xxx_ier_set(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_handle_tx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct tty_port *tport = &port->state->port; + unsigned int portno = port->iobase; + unsigned int txlen, to_send, sent; + const unsigned char *tail; + u8 x_char, fsr, tfcnt; + + guard(mutex)(&one->tx_lock); + + /* + * The serial core sets x_char and then calls start_tx() under the + * port lock, so snapshot it under the same lock. + */ + scoped_guard(uart_port_lock_irqsave, port) { + x_char = port->x_char; + port->x_char = 0; + + /* Nothing queued and no x_char: nothing to do. */ + if (!x_char && + (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port))) { + wk2xxx_stop_tx(port); + return; + } + } + + if (x_char) { + /* + * A high-priority x_char goes out first, but do not return + * here: pending xmit FIFO data still has to be drained (and + * the TX trigger re-armed) below, otherwise it would be + * stranded until the next start_tx(). + */ + wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, x_char); + scoped_guard(uart_port_lock_irqsave, port) + port->icount.tx++; + } + + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { + scoped_guard(uart_port_lock_irqsave, port) { + wk2xxx_stop_tx(port); + } + return; + } + + /* Limit to the free space available in the TX FIFO. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt)) + return; + if (tfcnt == 0) { + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) + return; + txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE; + } else { + txlen = WK2XXX_FIFO_SIZE - tfcnt; + } + if (txlen > WK2XXX_MAX_TX_CHARS) + txlen = WK2XXX_MAX_TX_CHARS; + + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + sent = 0; + while (to_send) { + unsigned int chunk = min_t(unsigned int, to_send, + WK2XXX_MAX_SPI_LEN); + + if (wk2xxx_fifo_write(s, portno, tail, chunk)) + break; + tail += chunk; + to_send -= chunk; + sent += chunk; + } + uart_xmit_advance(port, sent); + + scoped_guard(uart_port_lock_irqsave, port) { + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (kfifo_is_empty(&tport->xmit_fifo)) + wk2xxx_stop_tx(port); + else + wk2xxx_ier_set(port, WK2XXX_SIER_TFTRIG_IEN_BIT); + } +} + +static void wk2xxx_handle_rx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + unsigned int i, rxlen, len_p, chunk; + u8 fsr = 0, rfcnt = 0, lsr = 0, flag = TTY_NORMAL; + + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) + return; + + if (!(fsr & WK2XXX_FSR_RDAT_BIT)) + return; + + /* Get the number of bytes available in the RX FIFO. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) + return; + if (rfcnt == 0) { + /* The count may race with the FIFO status bit; retry once. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) + return; + rxlen = rfcnt ? rfcnt : WK2XXX_FIFO_SIZE; + } else { + rxlen = rfcnt; + } + + /* Read the FIFO contents in chunks. */ + len_p = 0; + while (rxlen) { + chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN); + if (wk2xxx_fifo_read(s, portno, one->buf + len_p, chunk)) + return; + len_p += chunk; + rxlen -= chunk; + } + rxlen = len_p; + + /* Map the FIFO status register error flags to line status. */ + if (fsr & WK2XXX_FSR_ERR_MASK) { + if (fsr & WK2XXX_FSR_RFPE_BIT) { + port->icount.parity++; + lsr |= WK2XXX_LSR_PE_BIT; + flag = TTY_PARITY; + } + if (fsr & WK2XXX_FSR_RFFE_BIT) { + port->icount.frame++; + lsr |= WK2XXX_LSR_FE_BIT; + flag = TTY_FRAME; + } + if (fsr & WK2XXX_FSR_RFOE_BIT) { + port->icount.overrun++; + lsr |= WK2XXX_LSR_OE_BIT; + flag = TTY_OVERRUN; + } + if (fsr & WK2XXX_FSR_RFBI_BIT) { + port->icount.brk++; + lsr |= WK2XXX_LSR_BI_BIT; + flag = TTY_BREAK; + } + } + + port->icount.rx += rxlen; + + /* CREAD is clear: drain the FIFO and drop all received data. */ + if (port->ignore_status_mask & WK2XXX_LSR_IGNORE_DATA) + return; + + for (i = 0; i < rxlen; ++i) { + u8 ch = one->buf[i]; + + if (uart_handle_sysrq_char(port, ch)) + continue; + + if (lsr & port->ignore_status_mask) + continue; + + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); + } + + tty_flip_buffer_push(&port->state->port); +} + +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno) +{ + struct uart_port *port = &s->p[portno].port; + u8 sifr = 0, sier = 0; + bool rc = false; + + if (wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr) || + wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier)) + return false; + + if (sifr & (WK2XXX_SIFR_RFTRIG_INT_BIT | WK2XXX_SIFR_RXOVT_INT_BIT)) { + wk2xxx_handle_rx(port); + rc = true; + } + + if ((sifr & WK2XXX_SIFR_TFTRIG_INT_BIT) && + (sier & WK2XXX_SIER_TFTRIG_IEN_BIT)) { + wk2xxx_handle_tx(port); + rc = true; + } + + return rc; +} + +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) +{ + struct wk2xxx_port *s = dev_id; + bool handled = false; + bool keep_polling; + int passes = WK2XXX_IRQ_MAX_PASSES; + + do { + u8 gifr; + int i; + + keep_polling = false; + + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) + return IRQ_HANDLED; /* Bus error; give up this pass. */ + + if (!gifr) + break; + + handled = true; + + for (i = 0; i < s->devtype->nr_uart; ++i) + if (gifr & BIT(i)) + keep_polling |= wk2xxx_port_irq(s, i); + } while (keep_polling && !s->polling && --passes); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static void wk2xxx_poll_proc(struct kthread_work *ws) +{ + struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port, + poll_work.work); + + /* Reuse the IRQ handler; the interrupt ID is unused here. */ + wk2xxx_irq(0, s); + + /* + * Only keep polling while at least one port is open. The last + * shutdown cancels the pending instance (see wk2xxx_shutdown); a + * poll that is already running when that happens must not re-queue + * itself, otherwise it would outlive the cancelled one. + */ + if (atomic_read(&s->open_ports) > 0) + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); +} + +static void wk2xxx_tx_proc(struct kthread_work *ws) +{ + struct uart_port *port = &(to_wk2xxx_one(ws, tx_work)->port); + + wk2xxx_handle_tx(port); +} + +static void wk2xxx_start_tx(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + + kthread_queue_work(&s->kworker, &one->tx_work); +} + +static void wk2xxx_reconf_rs485(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 rs485 = 0; + + if (port->rs485.flags & SER_RS485_ENABLED) { + rs485 = WK2XXX_RS485_RSRS485_BIT | WK2XXX_RS485_RTSEN_BIT; + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + rs485 |= WK2XXX_RS485_RTSINV_BIT; + } + + wk2xxx_port_reg_write(s, port->iobase, WK2XXX_RS485_REG, rs485); +} + +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + /* + * RTS is driven by hardware and its timing cannot be influenced + * from the driver. Non-zero RTS delays are rejected (sanitized to + * zero) by the serial core. + */ + one->config.flags |= WK2XXX_RECONF_RS485; + kthread_queue_work(&s->kworker, &one->reg_work); + + return 0; +} + +static void wk2xxx_reg_proc(struct kthread_work *ws) +{ + struct wk2xxx_one *one = to_wk2xxx_one(ws, reg_work); + struct wk2xxx_port *s = dev_get_drvdata(one->port.dev); + struct wk2xxx_one_config config; + unsigned long irqflags; + + uart_port_lock_irqsave(&one->port, &irqflags); + config = one->config; + memset(&one->config, 0, sizeof(one->config)); + uart_port_unlock_irqrestore(&one->port, irqflags); + + if (config.flags & WK2XXX_RECONF_IER) + wk2xxx_port_reg_update(s, one->port.iobase, WK2XXX_SIER_REG, + config.ier_mask, config.ier_val); + + if (config.flags & WK2XXX_RECONF_RS485) + wk2xxx_reconf_rs485(&one->port); +} + +static unsigned int wk2xxx_tx_empty(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 fsr = 0; + + if (wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr)) + return TIOCSER_TEMT; + + return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 : + TIOCSER_TEMT; +} + +static unsigned int wk2xxx_get_mctrl(struct uart_port *port) +{ + /* The WK2xxx does not expose modem control lines. */ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + +static void wk2xxx_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* The WK2xxx does not support modem control lines. */ +} + +static void wk2xxx_enable_ms(struct uart_port *port) +{ + /* The WK2xxx does not have modem status registers. */ +} + +static void wk2xxx_break_ctl(struct uart_port *port, int break_state) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + wk2xxx_port_reg_update(s, port->iobase, WK2XXX_LCR_REG, + WK2XXX_LCR_BREAK_BIT, + break_state ? WK2XXX_LCR_BREAK_BIT : 0); +} + +/* + * Configure a sub-UART: disable interrupts and TX/RX, program the line + * control and baud rate registers and restore the previous state. + */ +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr, + u8 baud0, u8 baud1, u8 pres) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 sier, scr, fsr; + int count = 200; + + guard(mutex)(&s->reg_lock); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier); + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Wait for the transmitter to become idle. */ + do { + wk2xxx_raw_port_read(s, portno, WK2XXX_FSR_REG, &fsr); + } while ((fsr & WK2XXX_FSR_TBUSY_BIT) && count--); + + /* Disable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, &scr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr & ~(WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Program the line control register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr); + + /* Configure hardware flow control levels. */ + if (fwcr) { + wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTH_REG, 0xf0); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTL_REG, 0x80); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + /* Program the baud rate generator (page 1 registers). */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD0_REG, baud0); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD1_REG, baud1); + wk2xxx_raw_port_write(s, portno, WK2XXX_PRES_REG, pres); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + + /* Re-enable the transmitter and receiver. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr | (WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Restore the interrupt enable register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, sier); +} + +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, + u8 *baud0, u8 *baud1, u8 *pres) +{ + unsigned int div, rem; + + /* Never divide by zero; the serial core normally prevents this. */ + if (baud == 0) + baud = 9600; + + div = clk / (baud * 16); + if (div == 0) + div = 1; + div--; + *baud0 = div & 0xff; + *baud1 = (div >> 8) & 0xff; + + rem = clk % (baud * 16); + *pres = (u32)div_u64((u64)rem * 100, baud); + *pres = (*pres + 50) / 100; +} + +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, + const struct ktermios *old) +{ + unsigned int baud, read_mask, ignore_mask; + u8 lcr = 0, fwcr = 0; + u8 baud0, baud1, pres; + + /* The WK2xxx supports 8 data bits only. */ + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + + /* Parity. */ + if (termios->c_cflag & PARENB) { + lcr |= WK2XXX_LCR_PAEN_BIT; + switch (termios->c_cflag & (PARODD | CMSPAR)) { + case 0: + lcr |= WK2XXX_LCR_PAM1_BIT; /* even */ + break; + case PARODD: + lcr |= WK2XXX_LCR_PAM0_BIT; /* odd */ + break; + case CMSPAR: + break; /* space */ + case PARODD | CMSPAR: + lcr |= WK2XXX_LCR_PAM1_BIT | + WK2XXX_LCR_PAM0_BIT; /* mark */ + break; + } + } + + /* Stop bits. */ + if (termios->c_cflag & CSTOPB) + lcr |= WK2XXX_LCR_STPL_BIT; + + /* Determine the status masks to publish. */ + read_mask = WK2XXX_LSR_OE_BIT; + if (termios->c_iflag & INPCK) + read_mask |= WK2XXX_LSR_PE_BIT | WK2XXX_LSR_FE_BIT; + if (termios->c_iflag & (BRKINT | PARMRK)) + read_mask |= WK2XXX_LSR_BI_BIT; + + ignore_mask = 0; + if (termios->c_iflag & IGNBRK) + ignore_mask |= WK2XXX_LSR_BI_BIT; + if (!(termios->c_cflag & CREAD)) + ignore_mask |= WK2XXX_LSR_BRK_ERROR_MASK | + WK2XXX_LSR_IGNORE_DATA; + + /* Hardware flow control is configured in the chip below. */ + if (termios->c_cflag & CRTSCTS) + fwcr = WK2XXX_FWCR_FWM_RTS_CTS; + + /* Get the baud rate generator configuration. */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / 16 / 0xffff, + port->uartclk / 16); + + wk2xxx_calc_divisor(port->uartclk, baud, &baud0, &baud1, &pres); + wk2xxx_conf_port(port, lcr, fwcr, baud0, baud1, pres); + + /* + * Publish the masks and flow-control status under the port lock; the + * RX/TX paths read them from their kthread context. + */ + guard(uart_port_lock_irqsave)(port); + port->read_status_mask = read_mask; + port->ignore_status_mask = ignore_mask; + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + if (termios->c_cflag & CRTSCTS) + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; + uart_update_timeout(port, termios->c_cflag, baud); +} + +static int wk2xxx_startup(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Enable the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + + /* Reset the sub-UART. */ + wk2xxx_raw_write(s, WK2XXX_GRST_REG, BIT(portno)); + + /* Enable the sub-UART interrupt in the global mask. */ + wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Enable RX FIFO trigger and RX time-out interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, + WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); + + /* Enable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, ®); + reg |= WK2XXX_SCR_TXEN_BIT | WK2XXX_SCR_RXEN_BIT; + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, reg); + + /* Reset and configure the FIFOs. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xff); + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xfc); + + /* Set the RX/TX FIFO trigger levels. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_RFTL_REG, + WK2XXX_RXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_TFTL_REG, + WK2XXX_TXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + kfifo_reset(&port->state->port.xmit_fifo); + + /* Start the shared polling loop when the first port is opened. */ + if (s->polling) { + guard(mutex)(&s->poll_lock); + if (atomic_inc_return(&s->open_ports) == 1) + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); + } + + return 0; +} + +static void wk2xxx_shutdown(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Disable the sub-UART interrupt in the global mask. */ + wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®); + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Reset the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GRST_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg); + + /* Disable the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + } + + /* + * Stop the shared polling loop once the last port is closed. The + * check and the cancel are serialized by poll_lock against a + * concurrent open of another port, so the two cannot tear the + * open_ports 0/1 boundary in a way that leaves the loop cancelled + * while a port is still open. + */ + if (s->polling) { + guard(mutex)(&s->poll_lock); + if (atomic_dec_return(&s->open_ports) == 0) + kthread_cancel_delayed_work_sync(&s->poll_work); + } + + kthread_flush_worker(&s->kworker); +} + +static const char *wk2xxx_type(struct uart_port *port) +{ + return (port->type == PORT_WK2XXX) ? WK2XXX_NAME : NULL; +} + +static void wk2xxx_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_WK2XXX; +} + +static int wk2xxx_verify_port(struct uart_port *port, struct serial_struct *s) +{ + if ((s->type != PORT_UNKNOWN) && (s->type != PORT_WK2XXX)) + return -EINVAL; + if (s->irq != port->irq) + return -EINVAL; + + return 0; +} + +static const struct uart_ops wk2xxx_ops = { + .tx_empty = wk2xxx_tx_empty, + .set_mctrl = wk2xxx_set_mctrl, + .get_mctrl = wk2xxx_get_mctrl, + .stop_tx = wk2xxx_stop_tx, + .start_tx = wk2xxx_start_tx, + .throttle = wk2xxx_throttle, + .unthrottle = wk2xxx_unthrottle, + .stop_rx = wk2xxx_stop_rx, + .enable_ms = wk2xxx_enable_ms, + .break_ctl = wk2xxx_break_ctl, + .startup = wk2xxx_startup, + .shutdown = wk2xxx_shutdown, + .set_termios = wk2xxx_set_termios, + .type = wk2xxx_type, + .config_port = wk2xxx_config_port, + .verify_port = wk2xxx_verify_port, +}; + +static const struct serial_rs485 wk2xxx_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND, + /* RTS timing is hardware-driven; RTS delays are not supported. */ +}; + +static int wk2xxx_probe(struct spi_device *spi) +{ + const struct wk2xxx_devtype *devtype; + struct device *dev = &spi->dev; + struct wk2xxx_port *s; + unsigned long uartclk; + u32 clock_freq = 0; + bool port_registered[WK2XXX_MAX_PORTS]; + u8 val; + int i, ret; + + /* Setup SPI bus. The SPI mode follows the device tree (spi-cpha, + * spi-cpol); it defaults to SPI mode 0 when unspecified. + */ + spi->bits_per_word = 8; + spi->max_speed_hz = spi->max_speed_hz ? : 10 * HZ_PER_MHZ; + ret = spi_setup(spi); + if (ret) + return ret; + + devtype = spi_get_device_match_data(spi); + if (!devtype) + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); + + /* Allocate port structure. */ + s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); + if (!s) + return dev_err_probe(dev, -ENOMEM, + "Error allocating port structure\n"); + + s->devtype = devtype; + s->spi = spi; + mutex_init(&s->reg_lock); + mutex_init(&s->poll_lock); + atomic_set(&s->open_ports, 0); + dev_set_drvdata(dev, s); + + /* + * The WK2xxx has no identification register, so the best we can do + * is to check that communication is at all possible. + */ + ret = wk2xxx_reg_read(s, WK2XXX_GENA_REG, &val); + if (ret) + return dev_err_probe(dev, ret, "Failed to read GENA register\n"); + + /* Crystal clock; allow an optional DT override. */ + uartclk = devtype->crystal_freq; + if (device_property_read_u32(dev, "clock-frequency", &clock_freq) == 0) { + if (clock_freq == 0) + return dev_err_probe(dev, -EINVAL, + "clock-frequency must not be zero\n"); + uartclk = clock_freq; + } + + /* Mark each port line and status as uninitialized. */ + for (i = 0; i < devtype->nr_uart; ++i) { + s->p[i].port.line = WK2XXX_MAX_DEVS; + port_registered[i] = false; + } + + kthread_init_worker(&s->kworker); + s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker, + "wk2xxx"); + if (IS_ERR(s->kworker_task)) { + ret = PTR_ERR(s->kworker_task); + goto out_ports; + } + sched_set_fifo(s->kworker_task); + + /* + * Reset the chip and disable every sub-UART and its interrupt before + * the ports are registered (and, in interrupt mode, before the IRQ is + * requested). The sub-UARTs stay disabled until a port is opened in + * wk2xxx_startup(), so no stale pending condition can raise the IRQ + * line while the ports are being set up. + */ + wk2xxx_reg_write(s, WK2XXX_GRST_REG, (1 << devtype->nr_uart) - 1); + wk2xxx_reg_write(s, WK2XXX_GENA_REG, 0); + wk2xxx_reg_write(s, WK2XXX_GIER_REG, 0); + + if (spi->irq <= 0) { + /* Poll the device instead of using interrupts. */ + s->polling = true; + kthread_init_delayed_work(&s->poll_work, wk2xxx_poll_proc); + } + + for (i = 0; i < devtype->nr_uart; ++i) { + struct fwnode_handle *saved_fwnode = dev_fwnode(dev); + struct device_node *port_np = NULL; + struct device_node *child; + + ret = ida_alloc_max(&wk2xxx_lines, WK2XXX_MAX_DEVS - 1, + GFP_KERNEL); + if (ret < 0) + goto out_ports; + + s->p[i].port.line = ret; + + /* Locate the matching "serial@i" DT subnode, if any. */ + for_each_available_child_of_node(dev->of_node, child) { + u32 reg; + + if (!of_node_name_eq(child, "serial")) + continue; + if (of_property_read_u32(child, "reg", ®)) + continue; + if (reg == i) { + port_np = child; + break; + } + } + + /* Initialize port data. */ + s->p[i].port.dev = dev; + s->p[i].port.irq = spi->irq; + s->p[i].port.type = PORT_WK2XXX; + s->p[i].port.fifosize = WK2XXX_FIFO_SIZE; + s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; + s->p[i].port.iobase = i; + s->p[i].port.iotype = UPIO_BUS; + s->p[i].port.uartclk = uartclk; + s->p[i].port.rs485_config = wk2xxx_config_rs485; + s->p[i].port.rs485_supported = wk2xxx_rs485_supported; + s->p[i].port.ops = &wk2xxx_ops; + + mutex_init(&s->p[i].tx_lock); + + kthread_init_work(&s->p[i].tx_work, wk2xxx_tx_proc); + kthread_init_work(&s->p[i].reg_work, wk2xxx_reg_proc); + + /* + * Temporarily retarget dev's fwnode to the per-port subnode + * so uart_get_rs485_mode() picks up the per-port properties. + */ + if (port_np) { + device_set_node(dev, of_fwnode_handle(port_np)); + ret = uart_get_rs485_mode(&s->p[i].port); + device_set_node(dev, saved_fwnode); + of_node_put(port_np); + if (ret) + goto out_ports; + } + + /* Register port. */ + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); + if (ret) + goto out_ports; + + port_registered[i] = true; + } + + /* + * Request the IRQ only after every port is registered so that an early + * interrupt can never reach a port whose port->state is not ready yet. + * We first try to acquire the IRQ line as a level IRQ; if that + * succeeds, we can allow sharing the interrupt as well. In case the + * interrupt controller doesn't support that, we fall back to a + * non-shared falling-edge trigger. + */ + if (!s->polling) { + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_LOW | IRQF_SHARED | + IRQF_ONESHOT, dev_name(dev), s); + if (ret) + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(dev), s); + if (ret) { + dev_err(dev, "Unable to request IRQ %i\n", spi->irq); + goto out_ports; + } + s->irq_requested = true; + } + + return 0; + +out_ports: + if (s->irq_requested) + free_irq(spi->irq, s); + + for (i = 0; i < devtype->nr_uart; i++) { + if (port_registered[i]) + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + if (s->p[i].port.line < WK2XXX_MAX_DEVS) + ida_free(&wk2xxx_lines, s->p[i].port.line); + } + + if (!IS_ERR(s->kworker_task)) + kthread_stop(s->kworker_task); + + return ret; +} + +static void wk2xxx_remove(struct spi_device *spi) +{ + struct wk2xxx_port *s = dev_get_drvdata(&spi->dev); + int i; + + /* + * Unregister the ports first. Removing a port that is still open + * hangs up its tty and runs wk2xxx_shutdown(), which disables the + * sub-UART in the chip and drains the shared worker, so afterwards no + * enabled interrupt source (and thus no queued TX/register work) can + * target a port that is being torn down. + */ + for (i = 0; i < s->devtype->nr_uart; i++) { + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + ida_free(&wk2xxx_lines, s->p[i].port.line); + } + + /* + * Every sub-UART is disabled now, so the chip can no longer raise the + * IRQ line. Free the IRQ; a handler already in flight only observes + * disabled ports and returns IRQ_NONE. + */ + if (s->irq_requested) + free_irq(spi->irq, s); + + if (s->polling) + kthread_cancel_delayed_work_sync(&s->poll_work); + + kthread_flush_worker(&s->kworker); + kthread_stop(s->kworker_task); +} + +static const struct of_device_id wk2xxx_dt_ids[] = { + { .compatible = "wkmic,wk2124", .data = &wk2124_devtype }, + { .compatible = "wkmic,wk2132", .data = &wk2132_devtype }, + { .compatible = "wkmic,wk2168", .data = &wk2168_devtype }, + { .compatible = "wkmic,wk2202", .data = &wk2202_devtype }, + { .compatible = "wkmic,wk2204", .data = &wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(of, wk2xxx_dt_ids); + +static const struct spi_device_id wk2xxx_id_table[] = { + { "wk2124", (kernel_ulong_t)&wk2124_devtype }, + { "wk2132", (kernel_ulong_t)&wk2132_devtype }, + { "wk2168", (kernel_ulong_t)&wk2168_devtype }, + { "wk2202", (kernel_ulong_t)&wk2202_devtype }, + { "wk2204", (kernel_ulong_t)&wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(spi, wk2xxx_id_table); + +static struct spi_driver wk2xxx_spi_driver = { + .driver = { + .name = WK2XXX_NAME, + .of_match_table = wk2xxx_dt_ids, + }, + .probe = wk2xxx_probe, + .remove = wk2xxx_remove, + .id_table = wk2xxx_id_table, +}; + +static int __init wk2xxx_init(void) +{ + int ret; + + ret = uart_register_driver(&wk2xxx_uart); + if (ret) + return ret; + + ret = spi_register_driver(&wk2xxx_spi_driver); + if (ret) + uart_unregister_driver(&wk2xxx_uart); + + return ret; +} +module_init(wk2xxx_init); + +static void __exit wk2xxx_exit(void) +{ + spi_unregister_driver(&wk2xxx_spi_driver); + uart_unregister_driver(&wk2xxx_uart); +} +module_exit(wk2xxx_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xuxunwei"); +MODULE_AUTHOR("B. Eschrich"); +MODULE_AUTHOR("EDATEC Technology Co., Ltd. <zjzhao@edatec.cn>"); +MODULE_DESCRIPTION("WK2xxx SPI UART driver"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 377884e3856a..05c75d3ef479 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -234,6 +234,9 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* WK2xxx SPI to UART bridge */ +#define PORT_WK2XXX 124 + /* Generic type identifier for ports which type is not important to userspace. */ #define PORT_GENERIC (-1) -- 2.43.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-04 10:52 ` [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-04 11:10 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-09-04 11:10 UTC (permalink / raw) To: zjzhao; +Cc: linux-serial, robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: - [Critical] Missing port lock synchronization for statistics and kfifo access causes data races, ring buffer corruption, and heap buffer overflows. - [Critical] The `spi_rx` DMA buffer shares a cacheline with the `p[]` flexible array, which can lead to memory corruption on non-cache-coherent architectures. - [High] Ignored SPI read errors corrupt global chip state. - [High] Returning IRQ_HANDLED on SPI bus error leads to IRQ storm. - [High] UART ports are registered and exposed to userspace before the SPI IRQ is requested, leading to potentially missed edge-triggered interrupts and permanent port hangs. - [High] Lockless access to `port->rs485.flags` in `wk2xxx_reconf_rs485()` can race with concurrent modifications from the `TIOCSRS485` ioctl. - [Medium] Overrun and other error markers are incorrectly amplified and duplicated for every byte in a bulk RX chunk, degrading reliability. - [Medium] Lost RX data when wk2xxx_fifo_read fails mid-transfer. -- commit c961eb76f21270c39040554c82f8108729896107 Author: zjzhao <zjzhao@edatec.cn> serial: wk2xxx: Add WK2xxx SPI UART driver This commit adds a new driver for the WK2xxx SPI to UART bridge ICs. The driver registers up to four full-duplex UART channels and supports hardware flow control and RS485. diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c index 0000000000000..f8005bbec9374 --- /dev/null +++ b/drivers/tty/serial/wk2xxx.c @@ -0,0 +1,1401 @@ [ ... ] > +struct wk2xxx_port { > + const struct wk2xxx_devtype *devtype; [ ... ] > + /* > + * Shared SPI transfer buffers. All SPI accesses are serialized by > + * s->reg_lock, so these are never used concurrently. Each buffer is > + * cache-line aligned so that DMA cache maintenance performed on a > + * buffer never invalidates a line shared with the fields above. > + */ > + u8 spi_tx[WK2XXX_MAX_SPI_LEN + 1] ____cacheline_aligned; > + u8 spi_rx[WK2XXX_MAX_SPI_LEN + 1] ____cacheline_aligned; > + struct wk2xxx_one p[]; > +}; [Severity: Critical] Could this struct layout allow the spi_rx DMA buffer to share a cacheline with the flexible array p[]? Because WK2XXX_MAX_SPI_LEN + 1 is 31 bytes, which is not a multiple of typical cacheline sizes, the compiler may place p[0] at the next 8-byte boundary. This would cause spi_rx and p[0] to share a cacheline. On non-cache-coherent architectures, DMA invalidation of spi_rx might discard concurrent CPU writes to p[0] (such as acquiring its tx_lock), or CPU writebacks could clobber the incoming DMA data. [ ... ] > +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) > +{ > + u8 *tx = s->spi_tx; > + u8 *rx = s->spi_rx; > + int ret; > + > + tx[0] = WK2XXX_SPI_READ | addr; > + tx[1] = 0; > + ret = wk2xxx_spi_transfer(s, tx, rx, 2); > + if (ret) { > + *val = 0; > + return ret; > + } [ ... ] > +static void wk2xxx_handle_tx(struct uart_port *port) > +{ [ ... ] > + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); > + sent = 0; > + while (to_send) { > + unsigned int chunk = min_t(unsigned int, to_send, > + WK2XXX_MAX_SPI_LEN); > + > + if (wk2xxx_fifo_write(s, portno, tail, chunk)) > + break; > + tail += chunk; > + to_send -= chunk; > + sent += chunk; > + } > + uart_xmit_advance(port, sent); > + > + scoped_guard(uart_port_lock_irqsave, port) { [Severity: Critical] Is it safe to call uart_xmit_advance() locklessly before entering the scoped_guard? This modifies port->icount.tx and the kfifo out index outside the port lock. If a concurrent userspace thread invokes the TCFLSH ioctl, uart_flush_buffer() could set the kfifo's in and out pointers to 0. If this lockless uart_xmit_advance() subsequently adds to the out pointer, it could integer-underflow the kfifo length calculation. This would cause kfifo_avail() to return a massive buffer size and lead to an out-of-bounds memcpy() on the next write(). [ ... ] > +static void wk2xxx_handle_rx(struct uart_port *port) > +{ [ ... ] > + /* Read the FIFO contents in chunks. */ > + len_p = 0; > + while (rxlen) { > + chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN); > + if (wk2xxx_fifo_read(s, portno, one->buf + len_p, chunk)) > + return; [Severity: Medium] If wk2xxx_fifo_read() fails mid-transfer, does returning directly here cause silent data loss? By issuing an early return, any bytes successfully read into one->buf during previous iterations of the loop are completely discarded. A break statement might be needed instead so the already-read characters can be processed and pushed to the TTY buffer. > + len_p += chunk; > + rxlen -= chunk; > + } > + rxlen = len_p; > + > + /* Map the FIFO status register error flags to line status. */ > + if (fsr & WK2XXX_FSR_ERR_MASK) { > + if (fsr & WK2XXX_FSR_RFPE_BIT) { > + port->icount.parity++; > + lsr |= WK2XXX_LSR_PE_BIT; > + flag = TTY_PARITY; > + } [Severity: Critical] Are there missing locks around the port->icount statistics updates here? Updating the port statistics without holding the port lock could cause data races with concurrent readers. [ ... ] > + if (fsr & WK2XXX_FSR_RFOE_BIT) { > + port->icount.overrun++; > + lsr |= WK2XXX_LSR_OE_BIT; > + flag = TTY_OVERRUN; > + } [ ... ] > + for (i = 0; i < rxlen; ++i) { > + u8 ch = one->buf[i]; > + > + if (uart_handle_sysrq_char(port, ch)) > + continue; > + > + if (lsr & port->ignore_status_mask) > + continue; > + > + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); > + } [Severity: Medium] Does passing the global overrun marker to uart_insert_char() for every byte in the burst incorrectly amplify a single error? Since the hardware FSR register provides a global status rather than a per-byte status, mapping it to lsr and passing it to uart_insert_char() for every processed byte will insert a TTY_OVERRUN dummy character for every single byte in the FIFO burst. This means one actual FIFO overrun will insert up to 256 dummy overrun characters into the TTY buffer and mark all successfully read bytes as corrupted. [ ... ] > +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) > +{ [ ... ] > + do { > + u8 gifr; > + int i; > + > + keep_polling = false; > + > + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) > + return IRQ_HANDLED; /* Bus error; give up this pass. */ [Severity: High] Can returning IRQ_HANDLED on a bus error prevent the IRQ subsystem from masking a stuck interrupt? If reading the global interrupt flag register fails and the interrupt is level-triggered (or stays asserted by hardware), returning IRQ_HANDLED defeats the spurious interrupt detector. The kernel will continuously re-invoke the handler in a tight loop, leading to an IRQ storm. Returning IRQ_NONE would allow the kernel to mask the rogue interrupt when no hardware state is successfully cleared. [ ... ] > +static void wk2xxx_reconf_rs485(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + u8 rs485 = 0; > + > + if (port->rs485.flags & SER_RS485_ENABLED) { [ ... ] > +static void wk2xxx_reg_proc(struct kthread_work *ws) > +{ [ ... ] > + uart_port_unlock_irqrestore(&one->port, irqflags); > + > + if (config.flags & WK2XXX_RECONF_IER) > + wk2xxx_port_reg_update(s, one->port.iobase, WK2XXX_SIER_REG, > + config.ier_mask, config.ier_val); > + > + if (config.flags & WK2XXX_RECONF_RS485) > + wk2xxx_reconf_rs485(&one->port); > +} [Severity: High] Might wk2xxx_reconf_rs485() race with concurrent modifications from the TIOCSRS485 ioctl? The kworker deliberately drops port->lock before invoking wk2xxx_reconf_rs485(), which then reads port->rs485.flags locklessly. If userspace sets the RS485 configuration via the ioctl, wk2xxx_config_rs485() defers execution to the kworker. The kworker might execute and read the old configuration before the serial core has a chance to update port->rs485, causing the hardware configuration to become permanently out of sync. [ ... ] > +static int wk2xxx_startup(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + u8 reg; > + > + scoped_guard(mutex, &s->reg_lock) { > + /* Enable the sub-UART. */ > + wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®); > + reg |= BIT(portno); > + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); [Severity: High] If wk2xxx_raw_read() fails due to a transient SPI error, will this silently disable all other active sub-UARTs? When the initial SPI read fails, it forces the output variable reg to 0, but the code ignores the error. Setting the bit for this port against a 0 value and writing it back inadvertently zeroes out the enable and interrupt bits for all other active ports on the chip, breaking them. [ ... ] > +static int wk2xxx_probe(struct spi_device *spi) > +{ [ ... ] > + for (i = 0; i < devtype->nr_uart; ++i) { [ ... ] > + /* Register port. */ > + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); > + if (ret) > + goto out_ports; > + > + port_registered[i] = true; > + } > + > + /* > + * Request the IRQ only after every port is registered so that an early > + * interrupt can never reach a port whose port->state is not ready yet. > + * We first try to acquire the IRQ line as a level IRQ; if that > + * succeeds, we can allow sharing the interrupt as well. In case the > + * interrupt controller doesn't support that, we fall back to a > + * non-shared falling-edge trigger. > + */ > + if (!s->polling) { > + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, > + IRQF_TRIGGER_LOW | IRQF_SHARED | > + IRQF_ONESHOT, dev_name(dev), s); [Severity: High] Is it safe to register the UART ports and expose them to userspace before requesting the SPI IRQ? Because uart_add_one_port() exposes the TTY device, userspace can immediately open() it. This executes wk2xxx_startup() which enables hardware interrupts on the WK2xxx chip. If the chip pulls the interrupt line low immediately (such as for a TX empty condition) and the system falls back to IRQF_TRIGGER_FALLING, the initial falling edge will be missed because the OS has not requested the IRQ yet. The line would remain low, but because it is edge-triggered, the interrupt will never fire again, permanently hanging the port. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260904105243.120769-1-zjzhao@edatec.cn?part=1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 10:52 ` [PATCH v3 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 10:52 ` [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-04 10:52 ` zjzhao 2026-09-04 11:12 ` sashiko-bot ` (2 more replies) 1 sibling, 3 replies; 20+ messages in thread From: zjzhao @ 2026-09-04 10:52 UTC (permalink / raw) To: Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree, zjzhao Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). Each UART channel is described by a serial@N child node that carries its own serial and RS-485 properties. Also register the wkmic vendor prefix. Signed-off-by: zjzhao <zjzhao@edatec.cn> --- .../bindings/serial/wkmic,wk2xxx.yaml | 115 ++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + 2 files changed, 117 insertions(+) create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml new file mode 100644 index 000000000000..c781482c0f2d --- /dev/null +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/serial/wkmic,wk2xxx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: WKmic WK2xxx SPI to UART bridge + +maintainers: + - EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + +description: + The WK2xxx family (WK2124, WK2132, WK2168, WK2202 and WK2204) are SPI to + UART bridge ICs from WKmic (Chengdu Weikai Microelectronics). Each IC + exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs + through a single SPI slave interface and one interrupt line. Each channel + is described by a "serial@N" child node that carries its own serial and + RS-485 properties. + +properties: + compatible: + enum: + - wkmic,wk2124 + - wkmic,wk2132 + - wkmic,wk2168 + - wkmic,wk2202 + - wkmic,wk2204 + + reg: + maxItems: 1 + + interrupts: + description: + When missing, the device driver uses polling instead. + maxItems: 1 + + clock-frequency: + description: + Crystal or external clock frequency for the IC in Hz. The WK2xxx has + no internal clock, so this must match the connected crystal. + default: 11059200 + minimum: 1 + maximum: 30000000 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^serial@[0-3]$": + type: object + description: A single UART channel of the chip. + allOf: + - $ref: /schemas/serial/serial.yaml# + - $ref: /schemas/serial/rs485.yaml# + properties: + reg: + description: UART channel number on the chip. + maximum: 3 + required: + - reg + unevaluatedProperties: false + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + properties: + compatible: + contains: + enum: + - wkmic,wk2132 + - wkmic,wk2202 + then: + patternProperties: + "^serial@[23]$": false + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + spi { + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + compatible = "wkmic,wk2132"; + reg = <0>; + spi-max-frequency = <10000000>; + clock-frequency = <11059200>; + interrupt-parent = <&gpio>; + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + reg = <0>; + }; + + serial@1 { + reg = <1>; + rs485-rts-active-low; + linux,rs485-enabled-at-boot-time; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..654fc8525559 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1907,6 +1907,8 @@ patternProperties: description: Wireless Tag (qiming yunduan) "^wits,.*": description: Shenzhen Merrii Technology Co., Ltd. (WITS) + "^wkmic,.*": + description: Chengdu Weikai Microelectronics Co., Ltd. "^wlf,.*": description: Wolfson Microelectronics "^wm,.*": -- 2.43.0 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 10:52 ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao @ 2026-09-04 11:12 ` sashiko-bot 2026-09-04 15:28 ` Conor Dooley 2026-09-05 7:32 ` Krzysztof Kozlowski 2 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-09-04 11:12 UTC (permalink / raw) To: zjzhao; +Cc: devicetree, linux-serial, robh, conor+dt > Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). > Each UART channel is described by a serial@N child node that carries its > own serial and RS-485 properties. Also register the wkmic vendor prefix. > > Signed-off-by: zjzhao <zjzhao@edatec.cn> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260904105243.120769-1-zjzhao@edatec.cn?part=2 ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 10:52 ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 11:12 ` sashiko-bot @ 2026-09-04 15:28 ` Conor Dooley 2026-09-05 7:32 ` Krzysztof Kozlowski 2 siblings, 0 replies; 20+ messages in thread From: Conor Dooley @ 2026-09-04 15:28 UTC (permalink / raw) To: zjzhao Cc: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree [-- Attachment #1: Type: text/plain, Size: 5443 bytes --] On Fri, Sep 04, 2026 at 06:52:43PM +0800, zjzhao wrote: > Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). > Each UART channel is described by a serial@N child node that carries its > own serial and RS-485 properties. Also register the wkmic vendor prefix. > > Signed-off-by: zjzhao <zjzhao@edatec.cn> > --- > .../bindings/serial/wkmic,wk2xxx.yaml | 115 ++++++++++++++++++ > .../devicetree/bindings/vendor-prefixes.yaml | 2 + > 2 files changed, 117 insertions(+) > create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml > > diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml > new file mode 100644 > index 000000000000..c781482c0f2d > --- /dev/null > +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2xxx.yaml Filename patching a compatible please. > @@ -0,0 +1,115 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/serial/wkmic,wk2xxx.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: WKmic WK2xxx SPI to UART bridge > + > +maintainers: > + - EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> This should be your name. We want people here. > + > +description: > + The WK2xxx family (WK2124, WK2132, WK2168, WK2202 and WK2204) are SPI to > + UART bridge ICs from WKmic (Chengdu Weikai Microelectronics). Each IC > + exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs > + through a single SPI slave interface and one interrupt line. Each channel > + is described by a "serial@N" child node that carries its own serial and > + RS-485 properties. > + > +properties: > + compatible: > + enum: > + - wkmic,wk2124 > + - wkmic,wk2132 > + - wkmic,wk2168 > + - wkmic,wk2202 > + - wkmic,wk2204 Why are these devices not compatible with one another? If they aren't, state why in your commit message. pw-bot: changes-requested > + > + reg: > + maxItems: 1 > + > + interrupts: > + description: > + When missing, the device driver uses polling instead. > + maxItems: 1 > + > + clock-frequency: > + description: > + Crystal or external clock frequency for the IC in Hz. The WK2xxx has > + no internal clock, so this must match the connected crystal. > + default: 11059200 I think I would drop this default and make the property required. > + minimum: 1 > + maximum: 30000000 I find the 1 Hz minimum hard to believe! > + > + "#address-cells": > + const: 1 > + > + "#size-cells": > + const: 0 > + > +patternProperties: > + "^serial@[0-3]$": > + type: object > + description: A single UART channel of the chip. > + allOf: > + - $ref: /schemas/serial/serial.yaml# > + - $ref: /schemas/serial/rs485.yaml# > + properties: > + reg: > + description: UART channel number on the chip. > + maximum: 3 > + required: > + - reg > + unevaluatedProperties: false > + > +required: > + - compatible > + - reg > + - "#address-cells" > + - "#size-cells" > + > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > + - if: > + properties: > + compatible: > + contains: > + enum: > + - wkmic,wk2132 > + - wkmic,wk2202 > + then: > + patternProperties: > + "^serial@[23]$": false > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/interrupt-controller/irq.h> > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + serial@0 { > + compatible = "wkmic,wk2132"; > + reg = <0>; > + spi-max-frequency = <10000000>; > + clock-frequency = <11059200>; > + interrupt-parent = <&gpio>; > + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; > + #address-cells = <1>; > + #size-cells = <0>; > + > + serial@0 { > + reg = <0>; > + }; > + > + serial@1 { > + reg = <1>; > + rs485-rts-active-low; > + linux,rs485-enabled-at-boot-time; > + }; > + }; > + }; > diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml > index ba2002969373..654fc8525559 100644 > --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml > +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml > @@ -1907,6 +1907,8 @@ patternProperties: > description: Wireless Tag (qiming yunduan) > "^wits,.*": > description: Shenzhen Merrii Technology Co., Ltd. (WITS) > + "^wkmic,.*": > + description: Chengdu Weikai Microelectronics Co., Ltd. "wkmic" doesn't come naturally to me as a non-chinese speaker from "Chengdu Weikai Microelectronics". Usually we use stock ticker symbols, or website URLs to form the vendor prefix. I tried lookig for one here but could not find it. Why not use "weikai," here? Cheers, Conor. > "^wlf,.*": > description: Wolfson Microelectronics > "^wm,.*": > -- > 2.43.0 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings 2026-09-04 10:52 ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 11:12 ` sashiko-bot 2026-09-04 15:28 ` Conor Dooley @ 2026-09-05 7:32 ` Krzysztof Kozlowski 2 siblings, 0 replies; 20+ messages in thread From: Krzysztof Kozlowski @ 2026-09-05 7:32 UTC (permalink / raw) To: zjzhao, Greg Kroah-Hartman, Jiri Slaby Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-serial, devicetree On 04/09/2026 12:52, zjzhao wrote: > Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from WKmic (Chengdu Weikai Microelectronics). > Each UART channel is described by a serial@N child node that carries its > own serial and RS-485 properties. Also register the wkmic vendor prefix. > > Signed-off-by: zjzhao <zjzhao@edatec.cn> How many patches did you thread here!?!? I just reviewed v2 and then found continuation of this. Do not attach (thread) your patchsets to some other threads (unrelated or older versions). This buries them deep in the mailbox and might interfere with applying entire sets. See also: https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830 Review from v2 applies. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-09-05 7:32 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-04 7:20 [PATCH 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 7:20 ` [PATCH 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 7:35 ` sashiko-bot 2026-09-04 8:17 ` Jiri Slaby 2026-09-04 9:32 ` zjzhao 2026-09-04 7:20 ` [PATCH 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 7:28 ` sashiko-bot 2026-09-04 9:33 ` [PATCH v2 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 9:33 ` [PATCH v2 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 9:53 ` sashiko-bot 2026-09-04 9:33 ` [PATCH v2 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 9:40 ` sashiko-bot 2026-09-05 7:31 ` Krzysztof Kozlowski 2026-09-04 10:52 ` [PATCH v3 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-04 10:52 ` [PATCH v3 1/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-04 11:10 ` sashiko-bot 2026-09-04 10:52 ` [PATCH v3 2/2] dt-bindings: serial: Document WK2xxx SPI UART bindings zjzhao 2026-09-04 11:12 ` sashiko-bot 2026-09-04 15:28 ` Conor Dooley 2026-09-05 7:32 ` Krzysztof Kozlowski
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox