Devicetree
 help / color / mirror / Atom feed
* [PATCH v5 0/2] WK2xxx SPI to UART bridge driver
@ 2026-10-09  8:24 zjzhao
  2026-10-09  8:24 ` [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
  2026-10-09  8:24 ` [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
  0 siblings, 2 replies; 6+ messages in thread
From: zjzhao @ 2026-10-09  8:24 UTC (permalink / raw)
  To: gregkh, jirislaby
  Cc: robh, krzk+dt, conor+dt, hugo, hy, linux-serial, linux-kernel,
	devicetree, linux-api, Zi Jie Zhao

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

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

The public v4 series used WK2202 by mistake. The WK2202 name in the public
reference driver is a typo; the correct model is WK2212. The v5 binding and
driver use WK2212 consistently. It is a two-channel model with hardware
flow control and RS-485 support.

Huang Yang <hy@wkmic.com> is listed as the primary maintainer of the driver
and binding from v5 onward. He will lead follow-up code changes and
reviewer replies, with Zi Jie Zhao providing transition support.

Changes in v5:

- Rebase onto 9b87fdc9af2f (Linux 7.3-rc3 based master);
- correct WK2202 to WK2212 in compatible data and documentation;
- list Huang Yang as the binding and driver maintainer;
- align per-compatible flow-control and RS-485 flags with the product data;
- size DMA-safe SPI buffers independently of cacheline size;
- synchronize TX snapshots with flush without doing SPI under the
  port lock;
- make tx_empty() non-sleeping and maintain its state asynchronously;
- report each FIFO-level RX overrun once;
- clear queued IER configuration during shutdown after cancelling work;
- complete shutdown cleanup on SPI errors and synchronize IRQ/work
  teardown;
- release all serial child-node references;
- remove the arbitrary 200-byte TX cap and replace the 200-iteration poll;
- apply Hugo Villeneuve's naming, comment, whitespace and line-width
  feedback;
- explain why direct SPI and explicit IRQ ownership are retained;
- use WK2204 in the binding example because it enables RS-485.

Conor Dooley's v4 Reviewed-by tag is not carried forward: v5 changes the
compatible and binding maintainer. Please review the updated binding.

Validation performed for the candidate:

- ARM (multi_v7_defconfig, module), ARM64 (defconfig, module and built-in),
  and m68k (multi_defconfig, module) driver builds pass;
- dt_binding_check passes and compiles the binding example; optional
  yamllint was skipped because it is not installed;
- checkpatch reports no errors or checks; patch 2 has one generic warning
  that MAINTAINERS may need updating, and patch 1 adds Huang Yang;
- sparse was not completed because the available sparse 0.6.4 does not support
  the __typeof_unqual__ probe required by this kernel version;
- on the SBC2300/WK2204 CM4 profile, all four ports registered and pairwise
  loopback passed byte-for-byte at 9600, 115200 and 230400 baud;
- the shutdown cleanup passed a four-port 512-byte bidirectional smoke test
  in 10/10 runs on a matched overlay and 3/3 runs after reboot with
  the formal v5 overlay;
- on the v5 candidate, three closed-port module unload/reload cycles and
  post-reload loopback passed;
- on the IPC1200/WK2132 CM4 profile, boot and TX smoke tests passed.

The hardware checks above were performed on Raspberry Pi CM4 systems
running 6.18.34 and 6.18.39 downstream kernels. RS-485 electrical behavior
was not tested.

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 +
 MAINTAINERS                                   |    7 +
 drivers/tty/serial/Kconfig                    |   17 +
 drivers/tty/serial/Makefile                   |    1 +
 drivers/tty/serial/wk2xxx.c                   | 1527 +++++++++++++++++
 include/uapi/linux/serial_core.h              |    3 +
 7 files changed, 1676 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
 create mode 100644 drivers/tty/serial/wk2xxx.c

base-commit: 9b87fdc9af2fbfcdb5c24a64139685ef80f6573f

-- 
2.43.0


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

* [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART
  2026-10-09  8:24 [PATCH v5 0/2] WK2xxx SPI to UART bridge driver zjzhao
@ 2026-10-09  8:24 ` zjzhao
  2026-10-09  8:30   ` sashiko-bot
  2026-10-09 14:58   ` Conor Dooley
  2026-10-09  8:24 ` [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
  1 sibling, 2 replies; 6+ messages in thread
From: zjzhao @ 2026-10-09  8:24 UTC (permalink / raw)
  To: gregkh, jirislaby
  Cc: robh, krzk+dt, conor+dt, hugo, hy, linux-serial, linux-kernel,
	devicetree, linux-api, 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, WK2212 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.

Differentiate compatible strings by channel count and register
availability. WK2124 and WK2132 lack hardware flow-control and
RS-485 registers. WK2212 is a separate two-channel model that
supports both features. Reject serial@2 and serial@3 on the
two-channel members.

Correct the WK2202 compatible used in the public v4 series to
WK2212. WK2202 in the public reference driver is a typo.

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

Assisted-by: LLM
Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>
---
 .../bindings/serial/wkmic,wk2124.yaml         | 119 ++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |   7 ++
 3 files changed, 128 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..47a0cbdb5ca1
--- /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:
+  - Huang Yang <hy@wkmic.com>
+
+description:
+  The WK2xxx family (WK2124, WK2132, WK2168, WK2212 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,wk2212
+      - 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,wk2212
+    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,wk2204";
+            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,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index 3b2eb2a7a89a..e4a2443524d4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29432,6 +29432,13 @@ M:	Miloslav Trmac <mitr@volny.cz>
 S:	Maintained
 F:	drivers/input/misc/wistron_btns.c
 
+WK2XXX SPI UART DRIVER
+M:	Huang Yang <hy@wkmic.com>
+L:	linux-serial@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
+F:	drivers/tty/serial/wk2xxx.c
+
 WMI BINARY MOF DRIVER
 M:	Armin Wolf <W_Armin@gmx.de>
 R:	Thomas Weißschuh <linux@weissschuh.net>
-- 
2.43.0


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

* [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver
  2026-10-09  8:24 [PATCH v5 0/2] WK2xxx SPI to UART bridge driver zjzhao
  2026-10-09  8:24 ` [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
@ 2026-10-09  8:24 ` zjzhao
  2026-10-09  8:39   ` sashiko-bot
  1 sibling, 1 reply; 6+ messages in thread
From: zjzhao @ 2026-10-09  8:24 UTC (permalink / raw)
  To: gregkh, jirislaby
  Cc: robh, krzk+dt, conor+dt, hugo, hy, linux-serial, linux-kernel,
	devicetree, linux-api, Zi Jie Zhao

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

Add a driver for the WK2xxx SPI-to-UART bridge ICs (WK2124, WK2132,
WK2168, WK2212 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

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 its channel.

WK2124 and WK2132 do not support hardware flow-control or RS-485.
WK2168, WK2212 and WK2204 support both features.

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

Clear pending per-port register configuration after cancelling shutdown
work. This discards IER updates queued by stop_rx() or stop_tx() during
shutdown before they can affect a later open.

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

Assisted-by: LLM
Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>
---
 drivers/tty/serial/Kconfig       |   17 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/wk2xxx.c      | 1527 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 4 files changed, 1548 insertions(+)
 create mode 100644 drivers/tty/serial/wk2xxx.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index cf7dba473b20..11e6b6b2ba62 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
+	    WK2212
+	    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..7d2827a42847
--- /dev/null
+++ b/drivers/tty/serial/wk2xxx.c
@@ -0,0 +1,1527 @@
+// 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, WK2212 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
+ *
+ * (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/iopoll.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_LINES	8
+#define WK2XXX_MAX_PORTS	4	/* Maximum number of ports per IC. */
+#define WK2XXX_FIFO_SIZE	256
+#define WK2XXX_MAX_SPI_LEN	30	/* Conservative SPI controller payload limit. */
+#define WK2XXX_SPI_BUF_SIZE	(WK2XXX_MAX_SPI_LEN + 1)
+#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_TX_STATUS_PERIOD_MS 1
+#define WK2XXX_IRQ_MAX_PASSES	8	/* Bound the IRQ drain loop. */
+#define WK2XXX_TX_POLL_US	100
+#define WK2XXX_TX_TIMEOUT_US	20000
+
+/* SPI command byte: bit 6 selects read/write; bit 7 selects FIFO/register access. */
+#define WK2XXX_SPI_READ		BIT(6)
+#define WK2XXX_SPI_FIFO_WRITE	BIT(7)
+#define WK2XXX_SPI_FIFO_READ	(BIT(7) | BIT(6))
+
+/* Address marker for page 1 registers. */
+#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 (WK2124 and WK2132 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 kthread_delayed_work tx_empty_work;
+	struct wk2xxx_one_config config;
+	unsigned int		tx_seq;
+	unsigned int		tx_state_seq;
+	bool			active;
+	bool			tx_empty;
+	unsigned char		rx_buf[WK2XXX_FIFO_SIZE];
+	unsigned char		tx_buf[WK2XXX_FIFO_SIZE];
+};
+
+struct wk2xxx_port {
+	const struct wk2xxx_devtype	*devtype;
+	struct spi_device		*spi;
+	struct clk			*clk;
+	struct mutex			reg_lock;	/* Serializes SPI register access. */
+	struct mutex			poll_lock;	/* Serializes polling start and stop. */
+	struct kthread_worker		kworker;
+	struct task_struct		*kworker_task;
+	struct kthread_delayed_work	poll_work;
+	bool				polling;
+	bool				irq_requested;
+	bool				removing;
+	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 the following flexible array is aligned as
+	 * well, so DMA cache maintenance cannot touch an unrelated field.
+	 */
+	u8				spi_tx[WK2XXX_SPI_BUF_SIZE] ____cacheline_aligned;
+	u8				spi_rx[WK2XXX_SPI_BUF_SIZE] ____cacheline_aligned;
+	struct wk2xxx_one		p[] ____cacheline_aligned;
+};
+
+static DEFINE_IDA(wk2xxx_lines);
+
+static struct uart_driver wk2xxx_uart = {
+	.owner		= THIS_MODULE,
+	.driver_name	= WK2XXX_NAME,
+	.dev_name	= "ttyWK",
+	.nr		= WK2XXX_MAX_LINES,
+};
+
+#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,
+};
+
+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 wk2212_devtype = {
+	.name		= "WK2212",
+	.nr_uart	= 2,
+	.has_hw_flow_control	= true,
+	.has_rs485		= true,
+};
+
+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 command 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, seq = 0;
+	unsigned int sent = 0;
+	bool no_data, x_char_sent = false;
+	u8 x_char, fsr, tfcnt;
+
+	guard(mutex)(&one->tx_lock);
+	if (!READ_ONCE(one->active))
+		return;
+
+	/*
+	 * 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;
+		no_data = kfifo_is_empty(&tport->xmit_fifo) ||
+			  uart_tx_stopped(port);
+
+		if (!x_char && no_data)
+			wk2xxx_stop_tx(port);
+	}
+	if (!x_char && no_data)
+		goto poll_tx;
+
+	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().
+		 */
+		if (!wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, x_char)) {
+			scoped_guard(uart_port_lock_irqsave, port)
+				port->icount.tx++;
+			x_char_sent = true;
+		} else {
+			/* Restore a failed high-priority character when possible. */
+			scoped_guard(uart_port_lock_irqsave, port) {
+				if (!port->x_char)
+					port->x_char = x_char;
+			}
+		}
+	}
+
+	scoped_guard(uart_port_lock_irqsave, port) {
+		no_data = kfifo_is_empty(&tport->xmit_fifo) ||
+			  uart_tx_stopped(port);
+		if (no_data)
+			wk2xxx_stop_tx(port);
+	}
+	if (no_data)
+		goto poll_tx;
+
+	if (wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt))
+		goto poll_tx;
+
+	if (tfcnt == 0) {
+		if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr))
+			goto poll_tx;
+		/* TFCNT zero means empty or full; TFULL distinguishes the two. */
+		txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE;
+	} else {
+		txlen = WK2XXX_FIFO_SIZE - tfcnt;
+	}
+
+	/*
+	 * Copy under port->lock, but perform the sleeping SPI transfer after
+	 * dropping it. tx_seq detects a concurrent uart_flush_buffer(); in that
+	 * case the bytes already sent belong to the old generation and must not
+	 * advance the newly reset FIFO.
+	 */
+	scoped_guard(uart_port_lock_irqsave, port) {
+		seq = one->tx_seq;
+		if (uart_tx_stopped(port))
+			to_send = 0;
+		else
+			to_send = kfifo_out_peek(&tport->xmit_fifo, one->tx_buf,
+						 txlen);
+	}
+
+	while (to_send) {
+		unsigned int chunk = min_t(unsigned int, to_send,
+					   WK2XXX_MAX_SPI_LEN);
+
+		if (wk2xxx_fifo_write(s, portno, one->tx_buf + sent, chunk))
+			break;
+		to_send -= chunk;
+		sent += chunk;
+	}
+
+	scoped_guard(uart_port_lock_irqsave, port) {
+		if (one->tx_seq == seq)
+			uart_xmit_advance(port, sent);
+		else
+			port->icount.tx += sent;
+
+		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);
+	}
+
+poll_tx:
+	if (x_char_sent || sent || !READ_ONCE(one->tx_empty))
+		kthread_mod_delayed_work(&s->kworker, &one->tx_empty_work, 0);
+}
+
+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;
+	bool overrun_pending;
+	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;
+
+	if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt))
+		return;
+
+	/* RFCNT zero means empty or full; RDAT above rules out empty. */
+	rxlen = rfcnt ? rfcnt : WK2XXX_FIFO_SIZE;
+
+	len_p = 0;
+	while (rxlen) {
+		chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN);
+		if (wk2xxx_fifo_read(s, portno, one->rx_buf + len_p, chunk))
+			break;
+		len_p += chunk;
+		rxlen -= chunk;
+	}
+	rxlen = len_p;
+
+	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;
+		}
+		if (fsr & WK2XXX_FSR_RFBI_BIT) {
+			port->icount.brk++;
+			lsr |= WK2XXX_LSR_BI_BIT;
+			flag = TTY_BREAK;
+		}
+	}
+
+	port->icount.rx += rxlen;
+
+	/* CREAD is clear, so drain the FIFO but drop the received data. */
+	if (port->ignore_status_mask & WK2XXX_LSR_IGNORE_DATA)
+		return;
+
+	overrun_pending = lsr & WK2XXX_LSR_OE_BIT;
+	for (i = 0; i < rxlen; ++i) {
+		u8 ch = one->rx_buf[i];
+		u8 overrun = overrun_pending ? WK2XXX_LSR_OE_BIT : 0;
+
+		if (uart_handle_sysrq_char(port, ch))
+			continue;
+
+		uart_insert_char(port, lsr, overrun, ch, flag);
+		overrun_pending = false;
+	}
+
+	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 (!READ_ONCE(s->p[portno].active))
+		return 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; treat the interrupt as spurious. */
+
+		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);
+
+	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 && !READ_ONCE(s->removing))
+		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_tx_empty_proc(struct kthread_work *ws)
+{
+	struct wk2xxx_one *one = container_of(ws, struct wk2xxx_one,
+					      tx_empty_work.work);
+	struct wk2xxx_port *s = dev_get_drvdata(one->port.dev);
+	unsigned int seq = READ_ONCE(one->tx_state_seq);
+	unsigned long delay;
+	u8 fsr;
+
+	if (!READ_ONCE(one->active)) {
+		WRITE_ONCE(one->tx_empty, true);
+		return;
+	}
+
+	if (wk2xxx_port_reg_read(s, one->port.iobase, WK2XXX_FSR_REG, &fsr)) {
+		delay = msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS);
+		kthread_mod_delayed_work(&s->kworker, &one->tx_empty_work, delay);
+		return;
+	}
+
+	if (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) {
+		delay = msecs_to_jiffies(WK2XXX_TX_STATUS_PERIOD_MS);
+		kthread_mod_delayed_work(&s->kworker, &one->tx_empty_work,
+					 delay);
+		return;
+	}
+
+	if (seq == READ_ONCE(one->tx_state_seq))
+		WRITE_ONCE(one->tx_empty, true);
+	else
+		kthread_mod_delayed_work(&s->kworker, &one->tx_empty_work, 0);
+}
+
+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);
+
+	one->tx_state_seq++;
+	WRITE_ONCE(one->tx_empty, false);
+	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 (!READ_ONCE(one->active))
+		return;
+
+	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_one *one = to_wk2xxx_one(port, port);
+
+	return READ_ONCE(one->tx_empty) ? TIOCSER_TEMT : 0;
+}
+
+static void wk2xxx_flush_buffer(struct uart_port *port)
+{
+	struct wk2xxx_one *one = to_wk2xxx_one(port, port);
+	struct wk2xxx_port *s = dev_get_drvdata(port->dev);
+
+	lockdep_assert_held_once(&port->lock);
+
+	/* Invalidate a TX snapshot currently being transferred by the worker. */
+	one->tx_seq++;
+	one->tx_state_seq++;
+	kthread_mod_delayed_work(&s->kworker, &one->tx_empty_work, 0);
+}
+
+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;
+	unsigned long timeout_us;
+	u8 sier, scr, fsr;
+	int read_ret, ret;
+
+	guard(mutex)(&s->reg_lock);
+
+	wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier);
+	wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0);
+
+	timeout_us = max_t(unsigned long, jiffies_to_usecs(uart_fifo_timeout(port)),
+			   WK2XXX_TX_TIMEOUT_US);
+	ret = read_poll_timeout(wk2xxx_raw_port_read, read_ret,
+				(read_ret || !(fsr & WK2XXX_FSR_TBUSY_BIT)),
+				WK2XXX_TX_POLL_US, timeout_us, false, s, portno,
+				WK2XXX_FSR_REG, &fsr);
+	if (read_ret)
+		dev_warn(port->dev, "Failed to read TX status: %d\n", read_ret);
+	else if (ret)
+		dev_warn(port->dev, "Timed out waiting for TX idle\n");
+
+	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));
+
+	wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr);
+
+	if (fwcr) {
+		wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr);
+		wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1);
+		/* FWTH and FWTL are the flow-control stop and resume thresholds. */
+		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);
+	}
+
+	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);
+
+	wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG,
+			      scr | WK2XXX_SCR_TXEN_BIT | WK2XXX_SCR_RXEN_BIT);
+
+	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;
+
+	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;
+
+	termios->c_cflag &= ~CSIZE;
+	termios->c_cflag |= CS8;
+
+	if (termios->c_cflag & PARENB) {
+		lcr |= WK2XXX_LCR_PAEN_BIT;
+		switch (termios->c_cflag & (PARODD | CMSPAR)) {
+		case 0:
+			lcr |= WK2XXX_LCR_PAM1_BIT;	/* Even parity. */
+			break;
+		case PARODD:
+			lcr |= WK2XXX_LCR_PAM0_BIT;	/* Odd parity. */
+			break;
+		case CMSPAR:
+			break;				/* Space parity. */
+		case PARODD | CMSPAR:
+			lcr |= WK2XXX_LCR_PAM1_BIT |
+			       WK2XXX_LCR_PAM0_BIT;	/* Mark parity. */
+			break;
+		}
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		lcr |= WK2XXX_LCR_STPL_BIT;
+
+	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;
+
+	if (!s->devtype->has_hw_flow_control)
+		termios->c_cflag &= ~CRTSCTS;
+
+	if (s->devtype->has_hw_flow_control && (termios->c_cflag & CRTSCTS))
+		fwcr = WK2XXX_FWCR_FWM_RTS_CTS;
+
+	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_one *one = to_wk2xxx_one(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);
+
+		wk2xxx_raw_write(s, WK2XXX_GRST_REG, BIT(portno));
+
+		if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, &reg))
+			return -EIO;
+		reg |= BIT(portno);
+		wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg);
+
+		wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG,
+				      WK2XXX_SIER_RFTRIG_IEN_BIT |
+				      WK2XXX_SIER_RXOUT_IEN_BIT);
+
+		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);
+
+		wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xff);
+		wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xfc);
+
+		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);
+	}
+
+	WRITE_ONCE(one->active, true);
+	WRITE_ONCE(one->tx_empty, true);
+	kfifo_reset(&port->state->port.xmit_fifo);
+
+	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_one *one = to_wk2xxx_one(port, port);
+	struct wk2xxx_port *s = dev_get_drvdata(port->dev);
+	unsigned int portno = port->iobase;
+	u8 reg;
+
+	WRITE_ONCE(one->active, false);
+	kthread_cancel_work_sync(&one->tx_work);
+	kthread_cancel_work_sync(&one->reg_work);
+	kthread_cancel_delayed_work_sync(&one->tx_empty_work);
+
+	/* Discard IER updates queued by stop_rx()/stop_tx() during shutdown. */
+	scoped_guard(uart_port_lock_irqsave, port)
+		memset(&one->config, 0, sizeof(one->config));
+
+	scoped_guard(mutex, &s->reg_lock) {
+		if (!wk2xxx_raw_read(s, WK2XXX_GIER_REG, &reg)) {
+			reg &= ~BIT(portno);
+			wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg);
+		}
+
+		wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0);
+
+		if (!wk2xxx_raw_read(s, WK2XXX_GRST_REG, &reg)) {
+			reg |= BIT(portno);
+			wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg);
+		}
+
+		if (!wk2xxx_raw_read(s, WK2XXX_GENA_REG, &reg)) {
+			reg &= ~BIT(portno);
+			wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg);
+		}
+	}
+
+	/*
+	 * Stop the shared polling loop once the last port is closed. The
+	 * check and the cancel are serialized by poll_lock against a
+	 * concurrent open of another port, so the two cannot tear the
+	 * open_ports 0/1 boundary in a way that leaves the loop cancelled
+	 * while a port is still open.
+	 */
+	if (s->polling) {
+		guard(mutex)(&s->poll_lock);
+		if (atomic_dec_return(&s->open_ports) == 0)
+			kthread_cancel_delayed_work_sync(&s->poll_work);
+	}
+
+	WRITE_ONCE(one->tx_empty, true);
+}
+
+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,
+	.flush_buffer	= wk2xxx_flush_buffer,
+	.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; the driver does not support RTS delays. */
+};
+
+static int wk2xxx_request_irq(struct wk2xxx_port *s)
+{
+	struct device *dev = &s->spi->dev;
+	int ret;
+
+	ret = request_threaded_irq(s->spi->irq, NULL, wk2xxx_irq,
+				   IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_ONESHOT,
+				   dev_name(dev), s);
+	if (!ret)
+		return 0;
+
+	return request_threaded_irq(s->spi->irq, NULL, wk2xxx_irq,
+				    IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				    dev_name(dev), s);
+}
+
+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;
+
+	/* Set up the SPI bus using the mode supplied by firmware.
+	 * The SPI core defaults to mode 0 when no mode flags are present.
+	 */
+	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");
+
+	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");
+
+	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");
+
+	for (i = 0; i < devtype->nr_uart; ++i) {
+		s->p[i].port.line = WK2XXX_MAX_LINES;
+		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) {
+		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_LINES - 1,
+				    GFP_KERNEL);
+		if (ret < 0)
+			goto out_ports;
+
+		s->p[i].port.line = ret;
+
+		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;
+			}
+		}
+
+		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);
+		kthread_init_delayed_work(&s->p[i].tx_empty_work,
+					  wk2xxx_tx_empty_proc);
+		WRITE_ONCE(s->p[i].tx_empty, true);
+
+		/*
+		 * Temporarily retarget dev's fwnode to the per-port subnode
+		 * so uart_get_rs485_mode() picks up the per-port properties.
+		 */
+		ret = 0;
+		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;
+
+		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 = wk2xxx_request_irq(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_LINES)
+			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;
+
+	WRITE_ONCE(s->removing, true);
+
+	/*
+	 * Stop every producer before unregistering the ports. The IRQ is kept
+	 * explicit instead of devm-managed so free_irq() synchronizes an in-flight
+	 * handler before uart_remove_one_port() releases port->state.
+	 */
+	for (i = 0; i < s->devtype->nr_uart; i++)
+		WRITE_ONCE(s->p[i].active, false);
+
+	if (s->polling)
+		kthread_cancel_delayed_work_sync(&s->poll_work);
+
+	for (i = 0; i < s->devtype->nr_uart; i++) {
+		kthread_cancel_work_sync(&s->p[i].tx_work);
+		kthread_cancel_work_sync(&s->p[i].reg_work);
+		kthread_cancel_delayed_work_sync(&s->p[i].tx_empty_work);
+	}
+
+	wk2xxx_reg_write(s, WK2XXX_GIER_REG, 0);
+	for (i = 0; i < s->devtype->nr_uart; i++)
+		wk2xxx_port_reg_write(s, i, WK2XXX_SIER_REG, 0);
+
+	if (s->irq_requested) {
+		free_irq(spi->irq, s);
+		s->irq_requested = false;
+	}
+
+	for (i = 0; i < s->devtype->nr_uart; i++) {
+		uart_remove_one_port(&wk2xxx_uart, &s->p[i].port);
+		ida_free(&wk2xxx_lines, s->p[i].port.line);
+	}
+
+	kthread_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,wk2212", .data = &wk2212_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 },
+	{ "wk2212", (kernel_ulong_t)&wk2212_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 related	[flat|nested] 6+ messages in thread

* Re: [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART
  2026-10-09  8:24 ` [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
@ 2026-10-09  8:30   ` sashiko-bot
  2026-10-09 14:58   ` Conor Dooley
  1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-09  8:30 UTC (permalink / raw)
  To: zjzhao; +Cc: linux-serial, robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] dt-bindings: missing constraints for wk2124/wk2132 flow-control and rs485
--

commit f014f512db212c81969cf531fd505916a62774fa
Author: Zi Jie Zhao <zjzhao@edatec.cn>

dt-bindings: serial: Document WK2xxx SPI UART

Add a DT binding for the WK2xxx SPI-to-UART bridge ICs (WK2124, WK2132,
WK2168, WK2212 and WK2204) from Chengdu Weikai Microelectronics (WKmic).

> diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
> new file mode 100644
> index 0000000000000..47a0cbdb5ca14
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
> @@ -0,0 +1,119 @@
> [ ... ]
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - wkmic,wk2132
> +              - wkmic,wk2212
> +    then:
> +      patternProperties:
> +        "^serial@[23]$": false

[Severity: Medium]
The commit message explicitly states: "Differentiate compatible strings by
channel count and register availability. WK2124 and WK2132 lack hardware
flow-control and RS-485 registers."

However, this conditional block only differentiates the models by channel
count. Since serial.yaml and rs485.yaml are referenced unconditionally for
all serial@[0-3] nodes, device trees that configure RS-485 or hardware flow
control on WK2124 and WK2132 will silently pass schema validation despite
the hardware lacking these registers.

Should there be an additional conditional block to reject RS-485 and hardware
flow-control properties for the WK2124 and WK2132 models?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261009082530.248219-1-zjzhao@edatec.cn?part=1

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

* Re: [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver
  2026-10-09  8:24 ` [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
@ 2026-10-09  8:39   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-09  8:39 UTC (permalink / raw)
  To: zjzhao; +Cc: devicetree, robh, conor+dt, linux-serial

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [High] tty: Bus error hides handled interrupts, risking IRQ storm disablement
- [High] tty: UART ports exposed to userspace before IRQ handler registration
- [High] tty: wk2xxx_shutdown lacks synchronization with concurrent IRQ handler
- [High] tty: Shared register lock held while sleeping blocks entire chip
- [High] tty: FIFO burst read applies single error status to all characters
- [High] tty: Shared threaded IRQ requested without primary handler fails
- [High] tty: SPI register read before device clock is enabled
- [Medium] tty: Initial RS485 config silently discarded before port opens
- [Medium] tty: Hardware flow control cannot be disabled once enabled
- [Medium] tty: Missing uart_read_port_properties() call in probe
- [Medium] tty: Data race in one->config.flags update without port->lock
- [Medium] tty: Truncation of intermediate fractional baud rate calculation
- [Medium] tty/serial: data race in wk2xxx_handle_rx() during port->icount updates
--

commit bdebcf784ac6f738394efad39bd16c7783d79d1f
Author: Zi Jie Zhao <zjzhao@edatec.cn>

serial: wk2xxx: Add WK2xxx SPI UART driver

This commit adds a driver for WK2xxx SPI-to-UART bridge ICs, supporting
up to four full-duplex UART channels over a single SPI interface. The
driver registers the ports, handles interrupts via a kthread worker, and
supports device tree properties.

> diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/tty/serial/wk2xxx.c
[ ... ]
> +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;
[ ... ]
> +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios,
> +			       struct serial_rs485 *rs485)
> +{
> +	struct wk2xxx_one *one = to_wk2xxx_one(port, port);
> +	struct wk2xxx_port *s = dev_get_drvdata(port->dev);
[ ... ]
> +	/*
> +	 * RTS is driven by hardware and its timing cannot be influenced
> +	 * from the driver. Non-zero RTS delays are rejected (sanitized to
> +	 * zero) by the serial core.
> +	 */
> +	one->config.flags |= WK2XXX_RECONF_RS485;

[Severity: Medium]
Is there a data race on the config.flags field here?

The serial core can invoke the rs485_config callback without holding
port->lock. In wk2xxx_config_rs485(), the driver modifies one->config.flags
locklessly. Concurrently, functions like wk2xxx_ier_set() modify the exact
same field while holding port->lock.

If these run concurrently, could we silently drop either the RS485
configuration flag or the interrupt mask update?

[ ... ]
> +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 (!READ_ONCE(one->active))
> +		return;

[Severity: Medium]
Will the initial RS485 configuration from the device tree be discarded here?

During probe, uart_add_one_port() invokes wk2xxx_config_rs485() to apply
device tree settings, which queues reg_work. If the worker thread runs
wk2xxx_reg_proc() before the port is opened (meaning wk2xxx_startup() hasn't
been called and active is false), the early return skips the SPI writes and
clears the pending config flags, permanently losing the RS-485 mode
configuration.

[ ... ]
> +static void wk2xxx_handle_rx(struct uart_port *port)
> +{
[ ... ]
> +	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;
> +		}

[Severity: Medium]
Are these lockless updates to the port->icount fields safe?

When wk2xxx_handle_rx() processes received data, it increments icount fields
and calls uart_insert_char() without acquiring port->lock. Concurrently, a
userspace application invoking the TIOCGICOUNT ioctl calls uart_get_icount(),
which acquires port->lock and copies the entire structure. Without locking on
the writer side, could concurrent reads cause torn or inconsistent statistics?

[ ... ]
> +	for (i = 0; i < rxlen; ++i) {
> +		u8 ch = one->rx_buf[i];
> +		u8 overrun = overrun_pending ? WK2XXX_LSR_OE_BIT : 0;
> +
> +		if (uart_handle_sysrq_char(port, ch))
> +			continue;
> +
> +		uart_insert_char(port, lsr, overrun, ch, flag);
> +		overrun_pending = false;
> +	}

[Severity: High]
Does this apply a single FSR error status to every character in the FIFO
burst?

The driver reads the FIFO status register once, then performs a burst read
of up to 256 bytes. If any error bit is set in that single snapshot, the
corresponding flag is applied to every character in the burst when calling
uart_insert_char(). Could a single flawed character cause the entire block
of valid characters to be flagged as erroneous?

[ ... ]
> +static irqreturn_t wk2xxx_irq(int irq, void *dev_id)
> +{
[ ... ]
> +	do {
> +		u8 gifr;
> +		int i;
> +
> +		keep_polling = false;
> +
> +		if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr))
> +			return IRQ_NONE; /* Bus error; treat the interrupt as spurious. */

[Severity: High]
If an interrupt is successfully handled in a previous loop iteration, does
returning IRQ_NONE here discard that handled state?

If handled gets lost because of a subsequent SPI bus error, the core IRQ
subsystem will log the interrupt as unhandled. Repeated occurrences could
cause the kernel to permanently disable the shared interrupt line.

[ ... ]
> +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr,
> +			     u8 baud0, u8 baud1, u8 pres)
> +{
[ ... ]
> +	guard(mutex)(&s->reg_lock);
> +
> +	wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier);
> +	wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0);
> +
> +	timeout_us = max_t(unsigned long, jiffies_to_usecs(uart_fifo_timeout(port)),
> +			   WK2XXX_TX_TIMEOUT_US);
> +	ret = read_poll_timeout(wk2xxx_raw_port_read, read_ret,
> +				(read_ret || !(fsr & WK2XXX_FSR_TBUSY_BIT)),
> +				WK2XXX_TX_POLL_US, timeout_us, false, s, portno,
> +				WK2XXX_FSR_REG, &fsr);

[Severity: High]
Will holding s->reg_lock while sleeping block the entire chip?

The reg_lock serializes all SPI communication, and read_poll_timeout() can
sleep for potentially hundreds of milliseconds waiting for the TX FIFO to
drain. Since the lock is held across this sleep, wouldn't it prevent all
other sub-UARTs from being serviced, causing massive latency spikes and RX
FIFO overruns on other active ports?

[ ... ]
> +	wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr);
> +
> +	if (fwcr) {
> +		wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr);
> +		wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1);
> +		/* FWTH and FWTL are the flow-control stop and resume thresholds. */
> +		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);
> +	}

[Severity: Medium]
If hardware flow control is later disabled by userspace, fwcr will be 0.
Since there is no else branch to write 0 to WK2XXX_FWCR_REG, how does the
driver clear a previously enabled hardware flow control state?

[ ... ]
> +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud,
> +				u8 *baud0, u8 *baud1, u8 *pres)
> +{
[ ... ]
> +	rem = clk % (baud * 16);
> +	*pres = (u32)div_u64((u64)rem * 100, baud);
> +	*pres = (*pres + 50) / 100;
> +}

[Severity: Medium]
Is the intermediate calculation truncated here?

The div_u64() call produces a value that can be larger than 255, but
assigning it directly to the u8 pointer *pres truncates it to 8 bits. The
subsequent division by 100 operates on this truncated value, which seems like
it could produce a wildly incorrect fractional divisor at high baud rates.

[ ... ]
> +static void wk2xxx_shutdown(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;
> +	u8 reg;
> +
> +	WRITE_ONCE(one->active, false);
> +	kthread_cancel_work_sync(&one->tx_work);
> +	kthread_cancel_work_sync(&one->reg_work);
> +	kthread_cancel_delayed_work_sync(&one->tx_empty_work);

[Severity: High]
Does wk2xxx_shutdown() need synchronization with the threaded IRQ handler?

It sets active = false and cancels workers, but without synchronize_irq() or
disable_irq(), the handler can run concurrently. If the handler evaluates the
active check before this clears it, could it re-queue workers or update
buffers after the logical shutdown is complete?

[ ... ]
> +static int wk2xxx_request_irq(struct wk2xxx_port *s)
> +{
> +	struct device *dev = &s->spi->dev;
> +	int ret;
> +
> +	ret = request_threaded_irq(s->spi->irq, NULL, wk2xxx_irq,
> +				   IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_ONESHOT,
> +				   dev_name(dev), s);

[Severity: High]
Will this request_threaded_irq() call always fail with -EINVAL?

The core IRQ subsystem forbids requesting an IRQF_SHARED threaded IRQ with a
NULL primary handler, because it needs to verify if the shared interrupt
belongs to the device before waking the thread. This failure would force the
driver into the fallback edge-triggered path, potentially causing missed
interrupts.

[ ... ]
> +static int wk2xxx_probe(struct spi_device *spi)
> +{
[ ... ]
> +	/*
> +	 * 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");
> +
> +	s->clk = devm_clk_get_enabled(dev, NULL);

[Severity: High]
Is it safe to perform the SPI register read before enabling the device clock?

Internal logic and SPI interfaces often require the core clock to be active.
Without it, this initial read might time out or return garbage, causing probe
to fail erroneously.

[ ... ]
> +		/*
> +		 * Temporarily retarget dev's fwnode to the per-port subnode
> +		 * so uart_get_rs485_mode() picks up the per-port properties.
> +		 */
> +		ret = 0;
> +		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;
> +
> +		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 = wk2xxx_request_irq(s);

[Severity: Medium]
The commit message mentions applying "serial and RS-485 properties from each
serial@N DT subnode". While uart_get_rs485_mode() handles the RS-485
properties, it looks like a call to uart_read_port_properties() is missing
here. Does this mean standard properties like clock-frequency, fifo-size,
and the serial alias will be ignored?

[Severity: High]
Could exposing ports to userspace via uart_add_one_port() before registering
the IRQ handler in wk2xxx_request_irq() be risky?

If a process opens the port immediately, wk2xxx_startup() will enable
hardware interrupts on the device. Since the OS handler is not yet
registered, an asserted interrupt would be unhandled, potentially resulting
in an interrupt storm that disables the shared IRQ line.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261009082530.248219-1-zjzhao@edatec.cn?part=2

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

* Re: [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART
  2026-10-09  8:24 ` [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
  2026-10-09  8:30   ` sashiko-bot
@ 2026-10-09 14:58   ` Conor Dooley
  1 sibling, 0 replies; 6+ messages in thread
From: Conor Dooley @ 2026-10-09 14:58 UTC (permalink / raw)
  To: zjzhao
  Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, hugo, hy,
	linux-serial, linux-kernel, devicetree, linux-api

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

On Fri, Oct 09, 2026 at 04:24:07PM +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, WK2212 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.
> 
> Differentiate compatible strings by channel count and register
> availability. WK2124 and WK2132 lack hardware flow-control and
> RS-485 registers. WK2212 is a separate two-channel model that
> supports both features. Reject serial@2 and serial@3 on the
> two-channel members.
> 
> Correct the WK2202 compatible used in the public v4 series to
> WK2212. WK2202 in the public reference driver is a typo.
> 
> Require the standard clocks property for the single external
> reference clock used by the chips.
> 
> Assisted-by: LLM
> Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn>
> ---
>  .../bindings/serial/wkmic,wk2124.yaml         | 119 ++++++++++++++++++
>  .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
>  MAINTAINERS                                   |   7 ++
>  3 files changed, 128 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..47a0cbdb5ca1
> --- /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:
> +  - Huang Yang <hy@wkmic.com>
> +
> +description:
> +  The WK2xxx family (WK2124, WK2132, WK2168, WK2212 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,wk2212
> +      - 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,wk2212
> +    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>;
> +    };

Drop this node here please, the infra will populate it at runtime to
make your example work.

pw-bot: changes-requested

Otherwise, it looks fine.to me..

Thanks,
Conor.


> +
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        serial@0 {
> +            compatible = "wkmic,wk2204";
> +            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,.*":
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3b2eb2a7a89a..e4a2443524d4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -29432,6 +29432,13 @@ M:	Miloslav Trmac <mitr@volny.cz>
>  S:	Maintained
>  F:	drivers/input/misc/wistron_btns.c
>  
> +WK2XXX SPI UART DRIVER
> +M:	Huang Yang <hy@wkmic.com>
> +L:	linux-serial@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml
> +F:	drivers/tty/serial/wk2xxx.c
> +
>  WMI BINARY MOF DRIVER
>  M:	Armin Wolf <W_Armin@gmx.de>
>  R:	Thomas Weißschuh <linux@weissschuh.net>
> -- 
> 2.43.0
> 

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

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

end of thread, other threads:[~2026-10-09 14:58 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09  8:24 [PATCH v5 0/2] WK2xxx SPI to UART bridge driver zjzhao
2026-10-09  8:24 ` [PATCH v5 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao
2026-10-09  8:30   ` sashiko-bot
2026-10-09 14:58   ` Conor Dooley
2026-10-09  8:24 ` [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
2026-10-09  8:39   ` sashiko-bot

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