mirror of https://lore.kernel.org/lkml/
 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; 4+ 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] 4+ 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 14:58   ` Conor Dooley
  2026-10-09  8:24 ` [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao
  1 sibling, 1 reply; 4+ 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	[flat|nested] 4+ 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
  1 sibling, 0 replies; 4+ 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	[flat|nested] 4+ 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 14:58   ` Conor Dooley
  0 siblings, 0 replies; 4+ 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] 4+ messages in thread

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

Thread overview: 4+ 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 14:58   ` Conor Dooley
2026-10-09  8:24 ` [PATCH v5 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao

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®