mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/2] WK2xxx SPI to UART bridge driver
@ 2026-09-08 10:31 zjzhao
  2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
  2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
  0 siblings, 2 replies; 5+ messages in thread
From: zjzhao @ 2026-09-08 10:31 UTC (permalink / raw)
  To: gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial,
	devicetree, Zi Jie Zhao

From: Zi Jie Zhao <zjzhao@edatec.cn>

Add support for the WK2xxx family of SPI to UART bridge ICs from Chengdu
Weikai Microelectronics (WKmic): WK2124, WK2132, WK2168, WK2202 and WK2204.

Base the driver on the vendor's open-source implementation:
https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c

Use the company's website domain, http://www.wkmic.com/, for the vendor
prefix.

Describe the external reference clock with the standard "clocks" property
and model each UART as a "serial@N" child node.

Changes in v4:

- Rename the binding file to "wkmic,wk2124.yaml" so its filename matches a
  compatible string;
- Replace "clock-frequency" with a required standard "clocks" input;
- Document why individual compatible strings are retained: the two-channel
  WK2132 and WK2202 do not have the hardware flow-control and RS-485
  registers present on the four-channel variants;
- Identify the maintainer as Zi Jie Zhao and document the vendor website and
  source attribution;
- Remove the unnecessary "linux/mod_devicetable.h" include;
- Describe hardware flow control and RS-485 support per chip variant;
- Retain the v3 fixes for IRQ setup and teardown, polling lifetime, SPI error
  handling, cache-safe shared buffers, and bounded IRQ processing.

Testing:

- Run dt_binding_check with the WK2xxx binding;
- Build the driver for an ARM64 target;
- IPC12 with WK2132 on SPI0: reboot, load the driver successfully, and
  register ttyWK0 and ttyWK1 with IRQ 184 and base_baud 691200;
- SBC2300 with WK2204: run four-port TX stress and repeated module
  unload/load with closed ports.

Zi Jie Zhao (2):
  dt-bindings: serial: Document WK2xxx SPI UART
  serial: wk2xxx: Add WK2xxx SPI UART driver

 .../bindings/serial/wkmic,wk2124.yaml         |  119 ++
 .../devicetree/bindings/vendor-prefixes.yaml  |    2 +
 drivers/tty/serial/Kconfig                    |   17 +
 drivers/tty/serial/Makefile                   |    1 +
 drivers/tty/serial/wk2xxx.c                   | 1433 +++++++++++++++++
 include/uapi/linux/serial_core.h              |    3 +
 6 files changed, 1575 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
 create mode 100644 drivers/tty/serial/wk2xxx.c

-- 
2.43.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART
  2026-09-08 10:31 [PATCH v4 0/2] WK2xxx SPI to UART bridge driver zjzhao
@ 2026-09-08 10:31 ` zjzhao
  2026-09-08 17:49   ` Conor Dooley
  2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
  1 sibling, 1 reply; 5+ messages in thread
From: zjzhao @ 2026-09-08 10:31 UTC (permalink / raw)
  To: gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial,
	devicetree, Zi Jie Zhao

From: Zi Jie Zhao <zjzhao@edatec.cn>

Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132,
WK2168, WK2202 and WK2204) from Chengdu Weikai Microelectronics (WKmic).
Describe each UART channel with a serial@N child node carrying its serial
and RS-485 properties. Register the wkmic vendor prefix based on the vendor
website http://www.wkmic.com/.

Differentiate compatible strings by channel count and
register availability. Account for missing hardware flow-control and
RS-485 registers on WK2132 and WK2202. Reject serial@2 and serial@3 on the
two-channel members.

Require the standard clocks property for the single external reference
clock used by the chips.

Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>
---
 .../bindings/serial/wkmic,wk2124.yaml         | 119 ++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 2 files changed, 121 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml

diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
new file mode 100644
index 000000000000..0f6ba8755e1c
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/wkmic,wk2124.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WKmic WK2xxx SPI to UART bridge
+
+maintainers:
+  - Zi Jie Zhao <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, and is
+  clocked from a single external reference clock. 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
+
+  clocks:
+    maxItems: 1
+
+  "#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
+  - clocks
+  - "#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>
+
+    xtal: clock-11059200 {
+        compatible = "fixed-clock";
+        clock-frequency = <11059200>;
+        #clock-cells = <0>;
+    };
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        serial@0 {
+            compatible = "wkmic,wk2132";
+            reg = <0>;
+            spi-max-frequency = <10000000>;
+            clocks = <&xtal>;
+            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..cdc04229d642 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. (http://www.wkmic.com/)
   "^wlf,.*":
     description: Wolfson Microelectronics
   "^wm,.*":
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver
  2026-09-08 10:31 [PATCH v4 0/2] WK2xxx SPI to UART bridge driver zjzhao
  2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
@ 2026-09-08 10:31 ` zjzhao
  2026-09-08 17:51   ` Hugo Villeneuve
  1 sibling, 1 reply; 5+ messages in thread
From: zjzhao @ 2026-09-08 10:31 UTC (permalink / raw)
  To: gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial,
	devicetree, Zi Jie Zhao

From: Zi Jie Zhao <zjzhao@edatec.cn>

Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168,
WK2202 and WK2204) from Chengdu Weikai Microelectronics. Support two or
four full-duplex UART channels with 256-byte RX/TX FIFOs through a single
SPI slave interface and one interrupt line.

Rework the WKmic open-source driver:
https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c
Model the new driver after the NXP sc16is7xx driver. Register ttyWK0..N
lines, use a threaded IRQ with a kthread worker for register access, fall
back to polling when the interrupt line is not described, and apply serial
and RS-485 properties from each serial@N DT subnode to the corresponding
channel.

Limit hardware flow-control and RS-485 reporting and configuration to the
four-channel variants that implement these features.

Reset the chip and disable every sub-UART at probe time. Request the IRQ
only after registering all ports. Unregister the ports before stopping the
IRQ and worker during removal.

Allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option.

Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>
---
 drivers/tty/serial/Kconfig       |   17 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/wk2xxx.c      | 1433 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 4 files changed, 1454 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..15869fbdeab0
--- /dev/null
+++ b/drivers/tty/serial/wk2xxx.c
@@ -0,0 +1,1433 @@
+// 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 WKmic open-source driver:
+ * https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c
+ * It 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 Zi Jie Zhao, 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/clk.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/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 (the two-channel variants have no FWCR register).
+ */
+#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;
+	bool		has_hw_flow_control;
+	bool		has_rs485;
+};
+
+#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 clk			*clk;
+	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 and sized to a full cache line, so DMA cache
+	 * maintenance on one of them can never invalidate a line shared
+	 * with any other field (notably the p[] array below).
+	 */
+	u8				spi_tx[L1_CACHE_BYTES] ____cacheline_aligned;
+	u8				spi_rx[L1_CACHE_BYTES] ____cacheline_aligned;
+	struct wk2xxx_one		p[];
+};
+
+static_assert(WK2XXX_MAX_SPI_LEN + 1 <= L1_CACHE_BYTES);
+
+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,
+	.has_hw_flow_control	= true,
+	.has_rs485		= true,
+};
+
+static const struct wk2xxx_devtype wk2132_devtype = {
+	.name		= "WK2132",
+	.nr_uart	= 2,
+};
+
+static const struct wk2xxx_devtype wk2168_devtype = {
+	.name			= "WK2168",
+	.nr_uart		= 4,
+	.has_hw_flow_control	= true,
+	.has_rs485		= true,
+};
+
+static const struct wk2xxx_devtype wk2202_devtype = {
+	.name		= "WK2202",
+	.nr_uart	= 2,
+};
+
+static const struct wk2xxx_devtype wk2204_devtype = {
+	.name			= "WK2204",
+	.nr_uart		= 4,
+	.has_hw_flow_control	= true,
+	.has_rs485		= true,
+};
+
+/*
+ * 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))
+			break;
+		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_NONE; /* Bus error; spurious handling applies. */
+
+		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);
+	unsigned long irqflags;
+	u8 rs485 = 0;
+
+	/*
+	 * TIOCSRS485 updates port->rs485 under the port lock, so read the
+	 * flags under the same lock before programming the chip.
+	 */
+	uart_port_lock_irqsave(port, &irqflags);
+	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;
+	}
+	uart_port_unlock_irqrestore(port, irqflags);
+
+	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 (!s->devtype->has_rs485)
+		return -EOPNOTSUPP;
+
+	/*
+	 * 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)
+{
+	struct wk2xxx_port *s = dev_get_drvdata(port->dev);
+	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;
+
+	/* The two-channel variants do not implement hardware flow control. */
+	if (!s->devtype->has_hw_flow_control)
+		termios->c_cflag &= ~CRTSCTS;
+
+	/* Hardware flow control is configured in the chip below. */
+	if (s->devtype->has_hw_flow_control && (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 (s->devtype->has_hw_flow_control && (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) {
+		/*
+		 * GENA and GIER are shared by all ports on the chip: a failed
+		 * read must not be turned into a write of only this port's
+		 * bit, which would clear the sibling ports' bits.
+		 */
+		if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, &reg))
+			return -EIO;
+		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. */
+		if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, &reg))
+			return -EIO;
+		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);
+		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. */
+		if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, &reg))
+			return;
+		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);
+		reg |= BIT(portno);
+		wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg);
+
+		/* Disable the sub-UART. */
+		if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, &reg))
+			return;
+		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;
+	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");
+
+	/* The reference clock (crystal or external clock input) is mandatory. */
+	s->clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(s->clk))
+		return dev_err_probe(dev, PTR_ERR(s->clk),
+				     "Failed to get the reference clock\n");
+
+	uartclk = clk_get_rate(s->clk);
+	if (!uartclk)
+		return dev_err_probe(dev, -EINVAL,
+				     "Clock rate must not be zero\n");
+
+	/* 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", &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;
+		if (devtype->has_rs485) {
+			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 && devtype->has_rs485) {
+			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("Zi Jie Zhao <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	[flat|nested] 5+ messages in thread

* Re: [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART
  2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
@ 2026-09-08 17:49   ` Conor Dooley
  0 siblings, 0 replies; 5+ messages in thread
From: Conor Dooley @ 2026-09-08 17:49 UTC (permalink / raw)
  To: zjzhao
  Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, linux-kernel,
	linux-serial, devicetree

[-- Attachment #1: Type: text/plain, Size: 928 bytes --]

On Tue, Sep 08, 2026 at 06:31:28PM +0800, zjzhao@edatec.cn wrote:
> From: Zi Jie Zhao <zjzhao@edatec.cn>
> 
> Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132,
> WK2168, WK2202 and WK2204) from Chengdu Weikai Microelectronics (WKmic).
> Describe each UART channel with a serial@N child node carrying its serial
> and RS-485 properties. Register the wkmic vendor prefix based on the vendor
> website http://www.wkmic.com/.
> 
> Differentiate compatible strings by channel count and
> register availability. Account for missing hardware flow-control and
> RS-485 registers on WK2132 and WK2202. Reject serial@2 and serial@3 on the
> two-channel members.
> 
> Require the standard clocks property for the single external reference
> clock used by the chips.
> 
> Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>

Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver
  2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
@ 2026-09-08 17:51   ` Hugo Villeneuve
  0 siblings, 0 replies; 5+ messages in thread
From: Hugo Villeneuve @ 2026-09-08 17:51 UTC (permalink / raw)
  To: zjzhao
  Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, linux-kernel,
	linux-serial, devicetree

On Tue,  8 Sep 2026 18:31:29 +0800
zjzhao@edatec.cn wrote:

> From: Zi Jie Zhao <zjzhao@edatec.cn>
> 
> Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168,
> WK2202 and WK2204) from Chengdu Weikai Microelectronics. Support two or
> four full-duplex UART channels with 256-byte RX/TX FIFOs through a single
> SPI slave interface and one interrupt line.
> 
> Rework the WKmic open-source driver:
> https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c
> Model the new driver after the NXP sc16is7xx driver. Register ttyWK0..N
> lines, use a threaded IRQ with a kthread worker for register access, fall
> back to polling when the interrupt line is not described, and apply serial
> and RS-485 properties from each serial@N DT subnode to the corresponding
> channel.
> 
> Limit hardware flow-control and RS-485 reporting and configuration to the
> four-channel variants that implement these features.
> 
> Reset the chip and disable every sub-UART at probe time. Request the IRQ
> only after registering all ports. Unregister the ports before stopping the
> IRQ and worker during removal.
> 
> Allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option.
> 
> Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>
> ---
>  drivers/tty/serial/Kconfig       |   17 +
>  drivers/tty/serial/Makefile      |    1 +
>  drivers/tty/serial/wk2xxx.c      | 1433 ++++++++++++++++++++++++++++++
>  include/uapi/linux/serial_core.h |    3 +
>  4 files changed, 1454 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..15869fbdeab0
> --- /dev/null
> +++ b/drivers/tty/serial/wk2xxx.c
> @@ -0,0 +1,1433 @@
> +// 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 WKmic open-source driver:
> + * https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c
> + * It 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 Zi Jie Zhao, 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/clk.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/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. */

This is the number of devices/ICs, not lines. You could remove the
comment as this is self-explanatory.

> +#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. */

I do not understand this comment in relation with the #define name...


> +#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. */

A little bit unclear, so bit 6 is direction R/W, and bit 7 is access
type, register or FIFO?


> +#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 */

Your previous comments have a trailing dot ".", and these
have not. Choose one format and use it everywhere...

> +#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 (the two-channel variants have no FWCR register).
> + */
> +#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;
> +	bool		has_hw_flow_control;
> +	bool		has_rs485;
> +};
> +
> +#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 clk			*clk;
> +	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 and sized to a full cache line, so DMA cache
> +	 * maintenance on one of them can never invalidate a line shared
> +	 * with any other field (notably the p[] array below).
> +	 */
> +	u8				spi_tx[L1_CACHE_BYTES] ____cacheline_aligned;
> +	u8				spi_rx[L1_CACHE_BYTES] ____cacheline_aligned;
> +	struct wk2xxx_one		p[];
> +};
> +
> +static_assert(WK2XXX_MAX_SPI_LEN + 1 <= L1_CACHE_BYTES);
> +
> +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,
> +	.has_hw_flow_control	= true,
> +	.has_rs485		= true,
> +};
> +
> +static const struct wk2xxx_devtype wk2132_devtype = {
> +	.name		= "WK2132",
> +	.nr_uart	= 2,
> +};
> +
> +static const struct wk2xxx_devtype wk2168_devtype = {
> +	.name			= "WK2168",
> +	.nr_uart		= 4,
> +	.has_hw_flow_control	= true,
> +	.has_rs485		= true,
> +};
> +
> +static const struct wk2xxx_devtype wk2202_devtype = {
> +	.name		= "WK2202",
> +	.nr_uart	= 2,
> +};
> +
> +static const struct wk2xxx_devtype wk2204_devtype = {
> +	.name			= "WK2204",
> +	.nr_uart		= 4,
> +	.has_hw_flow_control	= true,
> +	.has_rs485		= true,
> +};
> +
> +/*
> + * 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);
> +}

sc16is7xx and max310x use regmap for I2C/SPI access. You do not, can
you explain why?

Using regmap, you can take advantage of the cache. And you can (maybe)
simplify paged accesses using regmap ranges?


> +
> +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;

Add empty line for clarity?

> +		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;

Same as above?

> +		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;

Empty line

> +	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);

Put on the same line to take advantage of 100 columns limit, like you
already did previously...


> +}
> +
> +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);

Put on same line, as above...

> +}
> +
> +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;

Empty line

> +	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;
> +	}

Empty line

> +	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;

Empty line...

> +	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))
> +			break;
> +		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_NONE; /* Bus error; spurious handling applies. */
> +
> +		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);
> +	unsigned long irqflags;
> +	u8 rs485 = 0;
> +
> +	/*
> +	 * TIOCSRS485 updates port->rs485 under the port lock, so read the
> +	 * flags under the same lock before programming the chip.
> +	 */
> +	uart_port_lock_irqsave(port, &irqflags);
> +	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;
> +	}
> +	uart_port_unlock_irqrestore(port, irqflags);
> +
> +	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 (!s->devtype->has_rs485)
> +		return -EOPNOTSUPP;
> +
> +	/*
> +	 * 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;

Where does this 200 limit come from?


> +
> +	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));

Same line (100 columns)...

> +
> +	/* 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));

Same line (100 columns)...

> +
> +	/* 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)
> +{
> +	struct wk2xxx_port *s = dev_get_drvdata(port->dev);
> +	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;

Empty line

> +	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;

Empty line

> +	if (!(termios->c_cflag & CREAD))
> +		ignore_mask |= WK2XXX_LSR_BRK_ERROR_MASK |
> +			       WK2XXX_LSR_IGNORE_DATA;
> +
> +	/* The two-channel variants do not implement hardware flow control. */
> +	if (!s->devtype->has_hw_flow_control)
> +		termios->c_cflag &= ~CRTSCTS;
> +
> +	/* Hardware flow control is configured in the chip below. */
> +	if (s->devtype->has_hw_flow_control && (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 (s->devtype->has_hw_flow_control && (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) {
> +		/*
> +		 * GENA and GIER are shared by all ports on the chip: a failed
> +		 * read must not be turned into a write of only this port's
> +		 * bit, which would clear the sibling ports' bits.
> +		 */
> +		if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, &reg))
> +			return -EIO;
> +		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. */
> +		if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, &reg))
> +			return -EIO;
> +		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);
> +		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. */
> +		if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, &reg))
> +			return;
> +		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);
> +		reg |= BIT(portno);
> +		wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg);
> +
> +		/* Disable the sub-UART. */
> +		if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, &reg))
> +			return;
> +		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;
> +	bool port_registered[WK2XXX_MAX_PORTS];
> +	u8 val;
> +	int i, ret;

Use reverse-xmax ordering...

> +
> +	/* 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");
> +
> +	/* The reference clock (crystal or external clock input) is mandatory. */
> +	s->clk = devm_clk_get_enabled(dev, NULL);
> +	if (IS_ERR(s->clk))
> +		return dev_err_probe(dev, PTR_ERR(s->clk),
> +				     "Failed to get the reference clock\n");
> +
> +	uartclk = clk_get_rate(s->clk);
> +	if (!uartclk)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Clock rate must not be zero\n");
> +
> +	/* 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", &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;
> +		if (devtype->has_rs485) {
> +			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 && devtype->has_rs485) {
> +			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);

Why not using devm_ to drop irq_requested variable and irq cleanup in
remove()?

> +		if (ret)

Why not issue, like sc16is7xx:

    if (!ret)
        return 0;
?

> +			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;
> +		}

Similar here?

> +		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("Zi Jie Zhao <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
> 
> 


-- 
Hugo Villeneuve

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-08 18:35 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-08 10:31 [PATCH v4 0/2] WK2xxx SPI to UART bridge driver zjzhao
2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
2026-09-08 17:49   ` Conor Dooley
2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
2026-09-08 17:51   ` Hugo Villeneuve

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®