* [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, ®))
+ 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, ®))
+ 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 |= 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 &= ~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 |= BIT(portno);
+ wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg);
+ }
+
+ if (!wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®)) {
+ reg &= ~BIT(portno);
+ wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg);
+ }
+ }
+
+ /*
+ * Stop the shared polling loop once the last port is closed. The
+ * check and the cancel are serialized by poll_lock against a
+ * concurrent open of another port, so the two cannot tear the
+ * open_ports 0/1 boundary in a way that leaves the loop cancelled
+ * while a port is still open.
+ */
+ if (s->polling) {
+ guard(mutex)(&s->poll_lock);
+ if (atomic_dec_return(&s->open_ports) == 0)
+ kthread_cancel_delayed_work_sync(&s->poll_work);
+ }
+
+ 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", ®))
+ 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®