* [PATCH v4 0/2] WK2xxx SPI to UART bridge driver @ 2026-09-08 10:31 zjzhao 2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao 2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 0 siblings, 2 replies; 7+ messages in thread From: zjzhao @ 2026-09-08 10:31 UTC (permalink / raw) To: gregkh Cc: jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial, devicetree, Zi Jie Zhao From: Zi Jie Zhao <zjzhao@edatec.cn> Add support for the WK2xxx family of SPI to UART bridge ICs from Chengdu Weikai Microelectronics (WKmic): WK2124, WK2132, WK2168, WK2202 and WK2204. Base the driver on the vendor's open-source implementation: https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c Use the company's website domain, http://www.wkmic.com/, for the vendor prefix. Describe the external reference clock with the standard "clocks" property and model each UART as a "serial@N" child node. Changes in v4: - Rename the binding file to "wkmic,wk2124.yaml" so its filename matches a compatible string; - Replace "clock-frequency" with a required standard "clocks" input; - Document why individual compatible strings are retained: the two-channel WK2132 and WK2202 do not have the hardware flow-control and RS-485 registers present on the four-channel variants; - Identify the maintainer as Zi Jie Zhao and document the vendor website and source attribution; - Remove the unnecessary "linux/mod_devicetable.h" include; - Describe hardware flow control and RS-485 support per chip variant; - Retain the v3 fixes for IRQ setup and teardown, polling lifetime, SPI error handling, cache-safe shared buffers, and bounded IRQ processing. Testing: - Run dt_binding_check with the WK2xxx binding; - Build the driver for an ARM64 target; - IPC12 with WK2132 on SPI0: reboot, load the driver successfully, and register ttyWK0 and ttyWK1 with IRQ 184 and base_baud 691200; - SBC2300 with WK2204: run four-port TX stress and repeated module unload/load with closed ports. Zi Jie Zhao (2): dt-bindings: serial: Document WK2xxx SPI UART serial: wk2xxx: Add WK2xxx SPI UART driver .../bindings/serial/wkmic,wk2124.yaml | 119 ++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/wk2xxx.c | 1433 +++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 6 files changed, 1575 insertions(+) create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml create mode 100644 drivers/tty/serial/wk2xxx.c -- 2.43.0 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART 2026-09-08 10:31 [PATCH v4 0/2] WK2xxx SPI to UART bridge driver zjzhao @ 2026-09-08 10:31 ` zjzhao 2026-09-08 10:38 ` sashiko-bot 2026-09-08 17:49 ` Conor Dooley 2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 1 sibling, 2 replies; 7+ messages in thread From: zjzhao @ 2026-09-08 10:31 UTC (permalink / raw) To: gregkh Cc: jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial, devicetree, Zi Jie Zhao From: Zi Jie Zhao <zjzhao@edatec.cn> Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from Chengdu Weikai Microelectronics (WKmic). Describe each UART channel with a serial@N child node carrying its serial and RS-485 properties. Register the wkmic vendor prefix based on the vendor website http://www.wkmic.com/. Differentiate compatible strings by channel count and register availability. Account for missing hardware flow-control and RS-485 registers on WK2132 and WK2202. Reject serial@2 and serial@3 on the two-channel members. Require the standard clocks property for the single external reference clock used by the chips. Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn> --- .../bindings/serial/wkmic,wk2124.yaml | 119 ++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml new file mode 100644 index 000000000000..0f6ba8755e1c --- /dev/null +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/serial/wkmic,wk2124.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: WKmic WK2xxx SPI to UART bridge + +maintainers: + - Zi Jie Zhao <zjzhao@edatec.cn> + +description: + The WK2xxx family (WK2124, WK2132, WK2168, WK2202 and WK2204) are SPI to + UART bridge ICs from WKmic (Chengdu Weikai Microelectronics). Each IC + exposes two or four full-duplex UART channels with 256-byte RX/TX FIFOs + through a single SPI slave interface and one interrupt line, and is + clocked from a single external reference clock. Each channel is + described by a "serial@N" child node that carries its own serial and + RS-485 properties. + +properties: + compatible: + enum: + - wkmic,wk2124 + - wkmic,wk2132 + - wkmic,wk2168 + - wkmic,wk2202 + - wkmic,wk2204 + + reg: + maxItems: 1 + + interrupts: + description: + When missing, the device driver uses polling instead. + maxItems: 1 + + clocks: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^serial@[0-3]$": + type: object + description: A single UART channel of the chip. + allOf: + - $ref: /schemas/serial/serial.yaml# + - $ref: /schemas/serial/rs485.yaml# + properties: + reg: + description: UART channel number on the chip. + maximum: 3 + required: + - reg + unevaluatedProperties: false + +required: + - compatible + - reg + - clocks + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + properties: + compatible: + contains: + enum: + - wkmic,wk2132 + - wkmic,wk2202 + then: + patternProperties: + "^serial@[23]$": false + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + + xtal: clock-11059200 { + compatible = "fixed-clock"; + clock-frequency = <11059200>; + #clock-cells = <0>; + }; + + spi { + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + compatible = "wkmic,wk2132"; + reg = <0>; + spi-max-frequency = <10000000>; + clocks = <&xtal>; + interrupt-parent = <&gpio>; + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + reg = <0>; + }; + + serial@1 { + reg = <1>; + rs485-rts-active-low; + linux,rs485-enabled-at-boot-time; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..cdc04229d642 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1907,6 +1907,8 @@ patternProperties: description: Wireless Tag (qiming yunduan) "^wits,.*": description: Shenzhen Merrii Technology Co., Ltd. (WITS) + "^wkmic,.*": + description: Chengdu Weikai Microelectronics Co., Ltd. (http://www.wkmic.com/) "^wlf,.*": description: Wolfson Microelectronics "^wm,.*": -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART 2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao @ 2026-09-08 10:38 ` sashiko-bot 2026-09-08 17:49 ` Conor Dooley 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-09-08 10:38 UTC (permalink / raw) To: zjzhao; +Cc: robh, conor+dt, linux-serial, devicetree Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The binding schema and example contradict the commit message regarding RS-485 and flow-control support for the WK2132 and WK2202 variants. -- commit 812f6612c7639102da515af64bd9d5022ac17f75 Author: Zi Jie Zhao <zjzhao@edatec.cn> dt-bindings: serial: Document WK2xxx SPI UART This commit adds a device tree binding schema for the WK2xxx family of SPI to UART bridge ICs. It specifies required properties for each channel and aims to differentiate compatible strings by channel count and register availability, specifically noting missing RS-485 and flow-control registers for WK2132 and WK2202. > diff --git a/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml > new file mode 100644 > --- /dev/null > +++ b/Documentation/devicetree/bindings/serial/wkmic,wk2124.yaml [ ... ] > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > + - if: > + properties: > + compatible: > + contains: > + enum: > + - wkmic,wk2132 > + - wkmic,wk2202 > + then: > + patternProperties: > + "^serial@[23]$": false [Severity: Medium] The commit message states that the binding accounts for missing hardware flow-control and RS-485 registers on the WK2132 and WK2202 variants. However, this conditional block only restricts the channel count by rejecting the higher serial nodes. Should this block also explicitly restrict the RS-485 and hardware flow-control properties to prevent invalid configurations from passing schema validation? [ ... ] > +examples: > + - | [ ... ] > + serial@0 { > + compatible = "wkmic,wk2132"; > + reg = <0>; > + spi-max-frequency = <10000000>; > + clocks = <&xtal>; > + interrupt-parent = <&gpio>; > + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; > + #address-cells = <1>; > + #size-cells = <0>; > + > + serial@0 { > + reg = <0>; > + }; > + > + serial@1 { > + reg = <1>; > + rs485-rts-active-low; > + linux,rs485-enabled-at-boot-time; > + }; > + }; > + }; [Severity: Medium] Does this example incorrectly demonstrate RS-485 capabilities on the WK2132 variant? The commit message indicates that WK2132 is missing the hardware RS-485 registers, but this example actively applies RS-485 properties to its serial child node. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260908103129.58085-1-zjzhao@edatec.cn?part=1 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART 2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao 2026-09-08 10:38 ` sashiko-bot @ 2026-09-08 17:49 ` Conor Dooley 1 sibling, 0 replies; 7+ messages in thread From: Conor Dooley @ 2026-09-08 17:49 UTC (permalink / raw) To: zjzhao Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial, devicetree [-- Attachment #1: Type: text/plain, Size: 928 bytes --] On Tue, Sep 08, 2026 at 06:31:28PM +0800, zjzhao@edatec.cn wrote: > From: Zi Jie Zhao <zjzhao@edatec.cn> > > Add a DT binding for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, > WK2168, WK2202 and WK2204) from Chengdu Weikai Microelectronics (WKmic). > Describe each UART channel with a serial@N child node carrying its serial > and RS-485 properties. Register the wkmic vendor prefix based on the vendor > website http://www.wkmic.com/. > > Differentiate compatible strings by channel count and > register availability. Account for missing hardware flow-control and > RS-485 registers on WK2132 and WK2202. Reject serial@2 and serial@3 on the > two-channel members. > > Require the standard clocks property for the single external reference > clock used by the chips. > > Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn> Reviewed-by: Conor Dooley <conor.dooley@microchip.com> pw-bot: not-applicable [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-08 10:31 [PATCH v4 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao @ 2026-09-08 10:31 ` zjzhao 2026-09-08 10:44 ` sashiko-bot 2026-09-08 17:51 ` Hugo Villeneuve 1 sibling, 2 replies; 7+ messages in thread From: zjzhao @ 2026-09-08 10:31 UTC (permalink / raw) To: gregkh Cc: jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial, devicetree, Zi Jie Zhao From: Zi Jie Zhao <zjzhao@edatec.cn> Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, WK2202 and WK2204) from Chengdu Weikai Microelectronics. Support two or four full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI slave interface and one interrupt line. Rework the WKmic open-source driver: https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c Model the new driver after the NXP sc16is7xx driver. Register ttyWK0..N lines, use a threaded IRQ with a kthread worker for register access, fall back to polling when the interrupt line is not described, and apply serial and RS-485 properties from each serial@N DT subnode to the corresponding channel. Limit hardware flow-control and RS-485 reporting and configuration to the four-channel variants that implement these features. Reset the chip and disable every sub-UART at probe time. Request the IRQ only after registering all ports. Unregister the ports before stopping the IRQ and worker during removal. Allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option. Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn> --- drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/wk2xxx.c | 1433 ++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 4 files changed, 1454 insertions(+) create mode 100644 drivers/tty/serial/wk2xxx.c diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index cf7dba473b20..5f7a71f377ac 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1207,6 +1207,23 @@ config SERIAL_MXS_AUART_CONSOLE help Enable a MXS AUART port to be the system console. +config SERIAL_WK2XXX + tristate "WK2xxx SPI UART support" + depends on SPI_MASTER + select SERIAL_CORE + help + This selects the WK2xxx SPI to UART bridge driver. + Supported ICs are: + + WK2124 + WK2132 + WK2168 + WK2202 + WK2204 + + To compile this driver as a module, choose M here: the module + will be called wk2xxx. + config SERIAL_XILINX_PS_UART tristate "Cadence (Xilinx Zynq) UART support" depends on OF diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index bba7b21a4a1d..fdd13f3dd058 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_VT8500) += vt8500_serial.o +obj-$(CONFIG_SERIAL_WK2XXX) += wk2xxx.o obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o obj-$(CONFIG_SERIAL_ZS) += zs.o diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c new file mode 100644 index 000000000000..15869fbdeab0 --- /dev/null +++ b/drivers/tty/serial/wk2xxx.c @@ -0,0 +1,1433 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * WK2xxx SPI to UART bridge tty serial driver + * + * SPI-to-UART bridge ICs from WKmic (Chengdu Weikai Microelectronics): + * WK2124, WK2132, WK2168, WK2202 and WK2204. Each IC exposes two or four + * full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI + * slave interface and one interrupt line. The slave register set is split + * into two banks (page 0 / page 1) selected by the SPAGE register. + * + * This driver is a rework of the WKmic open-source driver: + * https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c + * It is modeled after the NXP sc16is7xx driver. + * + * (C) Copyright 2022 WKIC Ltd. by Xu XunWei Tech, Xuxunwei + * (C) Copyright 2024 EoF Software Labs, B. Eschrich + * Copyright (C) 2026 Zi Jie Zhao, EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> + */ + +#include <linux/atomic.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cache.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/kthread.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/units.h> +#include <linux/workqueue.h> + +#define WK2XXX_NAME "wk2xxx" +#define WK2XXX_MAX_DEVS 8 /* Total number of lines. */ +#define WK2XXX_MAX_PORTS 4 /* Max number of ports per IC. */ +#define WK2XXX_FIFO_SIZE 256 +#define WK2XXX_MAX_SPI_LEN 30 /* Max bytes per SPI FIFO burst. */ +#define WK2XXX_MAX_TX_CHARS 200 /* Leave headroom in the TX FIFO. */ +#define WK2XXX_RXFIFO_LEVEL 0x40 /* RX FIFO trigger level. */ +#define WK2XXX_TXFIFO_LEVEL 0x01 /* TX FIFO trigger level. */ +#define WK2XXX_POLL_PERIOD_MS 10 +#define WK2XXX_IRQ_MAX_PASSES 8 /* Bound the IRQ drain loop. */ + +/* SPI command byte: bit 6 = read, bit 7 = FIFO access. */ +#define WK2XXX_SPI_READ BIT(6) +#define WK2XXX_SPI_FIFO_WRITE BIT(7) +#define WK2XXX_SPI_FIFO_READ (BIT(7) | BIT(6)) + +/* Marker used to address registers located in page 1. */ +#define WK2XXX_PAGE1 BIT(7) + +/* Global registers. */ +#define WK2XXX_GENA_REG 0x00 /* Global UART enable */ +#define WK2XXX_GRST_REG 0x01 /* Global reset */ +#define WK2XXX_GMUT_REG 0x02 /* Master UART control */ +#define WK2XXX_GIER_REG 0x10 /* Global interrupt enable */ +#define WK2XXX_GIFR_REG 0x11 /* Global interrupt flag */ + +/* Port (sub-UART) registers, page 0. */ +#define WK2XXX_SPAGE_REG 0x03 /* Register page select */ +#define WK2XXX_SCR_REG 0x04 /* Slave control */ +#define WK2XXX_LCR_REG 0x05 /* Line control */ +#define WK2XXX_FCR_REG 0x06 /* FIFO control */ +#define WK2XXX_SIER_REG 0x07 /* Slave interrupt enable */ +#define WK2XXX_SIFR_REG 0x08 /* Slave interrupt flag */ +#define WK2XXX_TFCNT_REG 0x09 /* TX FIFO count */ +#define WK2XXX_RFCNT_REG 0x0a /* RX FIFO count */ +#define WK2XXX_FSR_REG 0x0b /* FIFO status */ +#define WK2XXX_LSR_REG 0x0c /* Line status */ +#define WK2XXX_FDAT_REG 0x0d /* FIFO data */ +#define WK2XXX_FWCR_REG 0x0e /* Flow control */ +#define WK2XXX_RS485_REG 0x0f /* RS485 control */ + +/* Port (sub-UART) registers, page 1. */ +#define WK2XXX_BAUD1_REG (0x04 | WK2XXX_PAGE1) /* Divisor Latch High */ +#define WK2XXX_BAUD0_REG (0x05 | WK2XXX_PAGE1) /* Divisor Latch Low */ +#define WK2XXX_PRES_REG (0x06 | WK2XXX_PAGE1) /* Fractional divisor */ +#define WK2XXX_RFTL_REG (0x07 | WK2XXX_PAGE1) /* RX FIFO trigger level */ +#define WK2XXX_TFTL_REG (0x08 | WK2XXX_PAGE1) /* TX FIFO trigger level */ +#define WK2XXX_FWTH_REG (0x09 | WK2XXX_PAGE1) /* Flow control high level */ +#define WK2XXX_FWTL_REG (0x0a | WK2XXX_PAGE1) /* Flow control low level */ +#define WK2XXX_XON1_REG (0x0b | WK2XXX_PAGE1) /* Xon word */ +#define WK2XXX_XOFF1_REG (0x0c | WK2XXX_PAGE1) /* Xoff word */ +#define WK2XXX_SADR_REG (0x0d | WK2XXX_PAGE1) /* RS485 auto address */ +#define WK2XXX_SAEN_REG (0x0e | WK2XXX_PAGE1) /* RS485 address mask */ +#define WK2XXX_RRSDLY_REG (0x0f | WK2XXX_PAGE1) /* RS485 RTS delay */ + +/* SCR register bits. */ +#define WK2XXX_SCR_RXEN_BIT BIT(0) +#define WK2XXX_SCR_TXEN_BIT BIT(1) + +/* LCR register bits. */ +#define WK2XXX_LCR_STPL_BIT BIT(0) /* Two stop bits */ +#define WK2XXX_LCR_PAM0_BIT BIT(1) /* Parity mode bit 0 */ +#define WK2XXX_LCR_PAM1_BIT BIT(2) /* Parity mode bit 1 */ +#define WK2XXX_LCR_PAEN_BIT BIT(3) /* Parity enable */ +#define WK2XXX_LCR_BREAK_BIT BIT(5) /* TX break */ + +/* SIER register bits. */ +#define WK2XXX_SIER_RFTRIG_IEN_BIT BIT(0) /* RX FIFO trigger */ +#define WK2XXX_SIER_RXOUT_IEN_BIT BIT(1) /* RX time-out */ +#define WK2XXX_SIER_TFTRIG_IEN_BIT BIT(2) /* TX FIFO trigger */ + +/* SIFR register bits. */ +#define WK2XXX_SIFR_RFTRIG_INT_BIT BIT(0) +#define WK2XXX_SIFR_RXOVT_INT_BIT BIT(1) +#define WK2XXX_SIFR_TFTRIG_INT_BIT BIT(2) + +/* FSR register bits. */ +#define WK2XXX_FSR_TBUSY_BIT BIT(0) +#define WK2XXX_FSR_TFULL_BIT BIT(1) +#define WK2XXX_FSR_TDAT_BIT BIT(2) +#define WK2XXX_FSR_RDAT_BIT BIT(3) +#define WK2XXX_FSR_RFPE_BIT BIT(4) /* RX FIFO parity error */ +#define WK2XXX_FSR_RFFE_BIT BIT(5) /* RX FIFO frame error */ +#define WK2XXX_FSR_RFBI_BIT BIT(6) /* RX FIFO break */ +#define WK2XXX_FSR_RFOE_BIT BIT(7) /* RX FIFO overrun */ +#define WK2XXX_FSR_ERR_MASK GENMASK(7, 4) + +/* LSR error bits, for use with uart_insert_char(). */ +#define WK2XXX_LSR_PE_BIT BIT(0) +#define WK2XXX_LSR_FE_BIT BIT(1) +#define WK2XXX_LSR_BI_BIT BIT(2) +#define WK2XXX_LSR_OE_BIT BIT(3) +#define WK2XXX_LSR_BRK_ERROR_MASK (WK2XXX_LSR_OE_BIT | WK2XXX_LSR_PE_BIT | \ + WK2XXX_LSR_FE_BIT | WK2XXX_LSR_BI_BIT) +/* Internal marker: drop all received data (termios CREAD is clear). */ +#define WK2XXX_LSR_IGNORE_DATA BIT(7) + +/* + * FWCR register bits. The flow-control mode is selected by the FWM2-0 + * field in bits 6-4 (the two-channel variants have no FWCR register). + */ +#define WK2XXX_FWCR_FWM_MASK GENMASK(6, 4) +#define WK2XXX_FWCR_FWM_RTS_CTS FIELD_PREP(WK2XXX_FWCR_FWM_MASK, 0x3) + +/* RS485 register bits. */ +#define WK2XXX_RS485_RTSINV_BIT BIT(0) +#define WK2XXX_RS485_RTSEN_BIT BIT(1) +#define WK2XXX_RS485_RSRS485_BIT BIT(6) + +struct wk2xxx_devtype { + const char *name; + int nr_uart; + bool has_hw_flow_control; + bool has_rs485; +}; + +#define WK2XXX_RECONF_IER BIT(0) +#define WK2XXX_RECONF_RS485 BIT(1) + +struct wk2xxx_one_config { + unsigned int flags; + u8 ier_mask; + u8 ier_val; +}; + +struct wk2xxx_one { + struct uart_port port; + struct mutex tx_lock; /* Serializes the TX path. */ + struct kthread_work tx_work; + struct kthread_work reg_work; + struct wk2xxx_one_config config; + unsigned char buf[WK2XXX_FIFO_SIZE]; /* RX buffer. */ +}; + +struct wk2xxx_port { + const struct wk2xxx_devtype *devtype; + struct spi_device *spi; + struct clk *clk; + struct mutex reg_lock; /* SPI register access. */ + struct mutex poll_lock; /* Serializes polling start/stop. */ + struct kthread_worker kworker; + struct task_struct *kworker_task; + struct kthread_delayed_work poll_work; + bool polling; + bool irq_requested; + atomic_t open_ports; + /* + * Shared SPI transfer buffers. All SPI accesses are serialized by + * s->reg_lock, so these are never used concurrently. Each buffer is + * cache-line aligned and sized to a full cache line, so DMA cache + * maintenance on one of them can never invalidate a line shared + * with any other field (notably the p[] array below). + */ + u8 spi_tx[L1_CACHE_BYTES] ____cacheline_aligned; + u8 spi_rx[L1_CACHE_BYTES] ____cacheline_aligned; + struct wk2xxx_one p[]; +}; + +static_assert(WK2XXX_MAX_SPI_LEN + 1 <= L1_CACHE_BYTES); + +static DEFINE_IDA(wk2xxx_lines); + +static struct uart_driver wk2xxx_uart = { + .owner = THIS_MODULE, + .driver_name = WK2XXX_NAME, + .dev_name = "ttyWK", + .nr = WK2XXX_MAX_DEVS, +}; + +#define to_wk2xxx_one(p, e) ((container_of((p), struct wk2xxx_one, e))) + +static const struct wk2xxx_devtype wk2124_devtype = { + .name = "WK2124", + .nr_uart = 4, + .has_hw_flow_control = true, + .has_rs485 = true, +}; + +static const struct wk2xxx_devtype wk2132_devtype = { + .name = "WK2132", + .nr_uart = 2, +}; + +static const struct wk2xxx_devtype wk2168_devtype = { + .name = "WK2168", + .nr_uart = 4, + .has_hw_flow_control = true, + .has_rs485 = true, +}; + +static const struct wk2xxx_devtype wk2202_devtype = { + .name = "WK2202", + .nr_uart = 2, +}; + +static const struct wk2xxx_devtype wk2204_devtype = { + .name = "WK2204", + .nr_uart = 4, + .has_hw_flow_control = true, + .has_rs485 = true, +}; + +/* + * The following functions are the low-level SPI accessors. The caller must + * hold s->reg_lock, so that multi-byte accesses and page switches are + * performed atomically on the SPI bus. + */ +static int wk2xxx_spi_transfer(struct wk2xxx_port *s, const u8 *tx, u8 *rx, + unsigned int len) +{ + struct spi_transfer xfer = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + }; + struct spi_message msg; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + return spi_sync(s->spi, &msg); +} + +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + int ret; + + tx[0] = WK2XXX_SPI_READ | addr; + tx[1] = 0; + ret = wk2xxx_spi_transfer(s, tx, rx, 2); + if (ret) { + *val = 0; + return ret; + } + + *val = rx[1]; + return 0; +} + +static int wk2xxx_raw_write(struct wk2xxx_port *s, u8 addr, u8 val) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + + tx[0] = addr; + tx[1] = val; + + return wk2xxx_spi_transfer(s, tx, rx, 2); +} + +static unsigned int wk2xxx_port_addr(unsigned int portno, u8 reg) +{ + /* The sub-UART number is encoded in the upper nibble of the cmd byte. */ + return (portno << 4) | reg; +} + +static int wk2xxx_raw_port_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +static int wk2xxx_raw_port_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + int ret; + + if (reg & WK2XXX_PAGE1) { + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); + if (ret) + return ret; + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); + return ret; + } + + return wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); +} + +/* + * Locked wrappers used outside the register sequences that already hold + * s->reg_lock. + */ +static int wk2xxx_reg_read(struct wk2xxx_port *s, u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_read(s, reg, val); +} + +static int wk2xxx_reg_write(struct wk2xxx_port *s, u8 addr, u8 val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_write(s, addr, val); +} + +static int wk2xxx_port_reg_read(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 *val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_read(s, portno, reg, val); +} + +static int wk2xxx_port_reg_write(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 val) +{ + guard(mutex)(&s->reg_lock); + return wk2xxx_raw_port_write(s, portno, reg, val); +} + +static void wk2xxx_port_reg_update(struct wk2xxx_port *s, unsigned int portno, + u8 reg, u8 mask, u8 val) +{ + u8 r = 0; + + guard(mutex)(&s->reg_lock); + if (wk2xxx_raw_port_read(s, portno, reg, &r)) + return; + wk2xxx_raw_port_write(s, portno, reg, (r & ~mask) | val); +} + +static int wk2xxx_fifo_read(struct wk2xxx_port *s, unsigned int portno, + u8 *buf, unsigned int len) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + int ret; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + /* + * Take the register lock before touching the shared SPI buffers so a + * concurrent transfer cannot observe (or be corrupted by) a partially + * constructed command. + */ + guard(mutex)(&s->reg_lock); + + memset(tx, 0, WK2XXX_MAX_SPI_LEN + 1); + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_READ); + + ret = wk2xxx_spi_transfer(s, tx, rx, len + 1); + if (ret) + return ret; + + memcpy(buf, rx + 1, len); + return 0; +} + +static int wk2xxx_fifo_write(struct wk2xxx_port *s, unsigned int portno, + const u8 *buf, unsigned int len) +{ + u8 *tx = s->spi_tx; + u8 *rx = s->spi_rx; + + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) + return -EINVAL; + + /* + * Take the register lock before touching the shared SPI buffers so a + * concurrent transfer cannot observe (or be corrupted by) a partially + * constructed command. + */ + guard(mutex)(&s->reg_lock); + + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_WRITE); + memcpy(tx + 1, buf, len); + + return wk2xxx_spi_transfer(s, tx, rx, len + 1); +} + +static void wk2xxx_ier_set(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val |= bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_ier_clear(struct uart_port *port, u8 bit) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + lockdep_assert_held_once(&port->lock); + + one->config.flags |= WK2XXX_RECONF_IER; + one->config.ier_mask |= bit; + one->config.ier_val &= ~bit; + kthread_queue_work(&s->kworker, &one->reg_work); +} + +static void wk2xxx_stop_tx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_TFTRIG_IEN_BIT); +} + +static void wk2xxx_stop_rx(struct uart_port *port) +{ + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_throttle(struct uart_port *port) +{ + /* + * Stop draining the RX FIFO to apply back-pressure. The RX time-out + * interrupt must be disabled too, otherwise remaining FIFO data would + * still be pushed out and defeat the flow control request. + */ + guard(uart_port_lock_irqsave)(port); + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_unthrottle(struct uart_port *port) +{ + guard(uart_port_lock_irqsave)(port); + wk2xxx_ier_set(port, WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); +} + +static void wk2xxx_handle_tx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct tty_port *tport = &port->state->port; + unsigned int portno = port->iobase; + unsigned int txlen, to_send, sent; + const unsigned char *tail; + u8 x_char, fsr, tfcnt; + + guard(mutex)(&one->tx_lock); + + /* + * The serial core sets x_char and then calls start_tx() under the + * port lock, so snapshot it under the same lock. + */ + scoped_guard(uart_port_lock_irqsave, port) { + x_char = port->x_char; + port->x_char = 0; + + /* Nothing queued and no x_char: nothing to do. */ + if (!x_char && + (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port))) { + wk2xxx_stop_tx(port); + return; + } + } + + if (x_char) { + /* + * A high-priority x_char goes out first, but do not return + * here: pending xmit FIFO data still has to be drained (and + * the TX trigger re-armed) below, otherwise it would be + * stranded until the next start_tx(). + */ + wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, x_char); + scoped_guard(uart_port_lock_irqsave, port) + port->icount.tx++; + } + + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { + scoped_guard(uart_port_lock_irqsave, port) { + wk2xxx_stop_tx(port); + } + return; + } + + /* Limit to the free space available in the TX FIFO. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt)) + return; + if (tfcnt == 0) { + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) + return; + txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE; + } else { + txlen = WK2XXX_FIFO_SIZE - tfcnt; + } + if (txlen > WK2XXX_MAX_TX_CHARS) + txlen = WK2XXX_MAX_TX_CHARS; + + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + sent = 0; + while (to_send) { + unsigned int chunk = min_t(unsigned int, to_send, + WK2XXX_MAX_SPI_LEN); + + if (wk2xxx_fifo_write(s, portno, tail, chunk)) + break; + tail += chunk; + to_send -= chunk; + sent += chunk; + } + uart_xmit_advance(port, sent); + + scoped_guard(uart_port_lock_irqsave, port) { + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (kfifo_is_empty(&tport->xmit_fifo)) + wk2xxx_stop_tx(port); + else + wk2xxx_ier_set(port, WK2XXX_SIER_TFTRIG_IEN_BIT); + } +} + +static void wk2xxx_handle_rx(struct uart_port *port) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + unsigned int i, rxlen, len_p, chunk; + u8 fsr = 0, rfcnt = 0, lsr = 0, flag = TTY_NORMAL; + + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) + return; + + if (!(fsr & WK2XXX_FSR_RDAT_BIT)) + return; + + /* Get the number of bytes available in the RX FIFO. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) + return; + if (rfcnt == 0) { + /* The count may race with the FIFO status bit; retry once. */ + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) + return; + rxlen = rfcnt ? rfcnt : WK2XXX_FIFO_SIZE; + } else { + rxlen = rfcnt; + } + + /* Read the FIFO contents in chunks. */ + len_p = 0; + while (rxlen) { + chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN); + if (wk2xxx_fifo_read(s, portno, one->buf + len_p, chunk)) + break; + len_p += chunk; + rxlen -= chunk; + } + rxlen = len_p; + + /* Map the FIFO status register error flags to line status. */ + if (fsr & WK2XXX_FSR_ERR_MASK) { + if (fsr & WK2XXX_FSR_RFPE_BIT) { + port->icount.parity++; + lsr |= WK2XXX_LSR_PE_BIT; + flag = TTY_PARITY; + } + if (fsr & WK2XXX_FSR_RFFE_BIT) { + port->icount.frame++; + lsr |= WK2XXX_LSR_FE_BIT; + flag = TTY_FRAME; + } + if (fsr & WK2XXX_FSR_RFOE_BIT) { + port->icount.overrun++; + lsr |= WK2XXX_LSR_OE_BIT; + flag = TTY_OVERRUN; + } + if (fsr & WK2XXX_FSR_RFBI_BIT) { + port->icount.brk++; + lsr |= WK2XXX_LSR_BI_BIT; + flag = TTY_BREAK; + } + } + + port->icount.rx += rxlen; + + /* CREAD is clear: drain the FIFO and drop all received data. */ + if (port->ignore_status_mask & WK2XXX_LSR_IGNORE_DATA) + return; + + for (i = 0; i < rxlen; ++i) { + u8 ch = one->buf[i]; + + if (uart_handle_sysrq_char(port, ch)) + continue; + + if (lsr & port->ignore_status_mask) + continue; + + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); + } + + tty_flip_buffer_push(&port->state->port); +} + +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno) +{ + struct uart_port *port = &s->p[portno].port; + u8 sifr = 0, sier = 0; + bool rc = false; + + if (wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr) || + wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier)) + return false; + + if (sifr & (WK2XXX_SIFR_RFTRIG_INT_BIT | WK2XXX_SIFR_RXOVT_INT_BIT)) { + wk2xxx_handle_rx(port); + rc = true; + } + + if ((sifr & WK2XXX_SIFR_TFTRIG_INT_BIT) && + (sier & WK2XXX_SIER_TFTRIG_IEN_BIT)) { + wk2xxx_handle_tx(port); + rc = true; + } + + return rc; +} + +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) +{ + struct wk2xxx_port *s = dev_id; + bool handled = false; + bool keep_polling; + int passes = WK2XXX_IRQ_MAX_PASSES; + + do { + u8 gifr; + int i; + + keep_polling = false; + + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) + return IRQ_NONE; /* Bus error; spurious handling applies. */ + + if (!gifr) + break; + + handled = true; + + for (i = 0; i < s->devtype->nr_uart; ++i) + if (gifr & BIT(i)) + keep_polling |= wk2xxx_port_irq(s, i); + } while (keep_polling && !s->polling && --passes); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static void wk2xxx_poll_proc(struct kthread_work *ws) +{ + struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port, + poll_work.work); + + /* Reuse the IRQ handler; the interrupt ID is unused here. */ + wk2xxx_irq(0, s); + + /* + * Only keep polling while at least one port is open. The last + * shutdown cancels the pending instance (see wk2xxx_shutdown); a + * poll that is already running when that happens must not re-queue + * itself, otherwise it would outlive the cancelled one. + */ + if (atomic_read(&s->open_ports) > 0) + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); +} + +static void wk2xxx_tx_proc(struct kthread_work *ws) +{ + struct uart_port *port = &(to_wk2xxx_one(ws, tx_work)->port); + + wk2xxx_handle_tx(port); +} + +static void wk2xxx_start_tx(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + + kthread_queue_work(&s->kworker, &one->tx_work); +} + +static void wk2xxx_reconf_rs485(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned long irqflags; + u8 rs485 = 0; + + /* + * TIOCSRS485 updates port->rs485 under the port lock, so read the + * flags under the same lock before programming the chip. + */ + uart_port_lock_irqsave(port, &irqflags); + if (port->rs485.flags & SER_RS485_ENABLED) { + rs485 = WK2XXX_RS485_RSRS485_BIT | WK2XXX_RS485_RTSEN_BIT; + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) + rs485 |= WK2XXX_RS485_RTSINV_BIT; + } + uart_port_unlock_irqrestore(port, irqflags); + + wk2xxx_port_reg_write(s, port->iobase, WK2XXX_RS485_REG, rs485); +} + +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct wk2xxx_one *one = to_wk2xxx_one(port, port); + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + if (!s->devtype->has_rs485) + return -EOPNOTSUPP; + + /* + * RTS is driven by hardware and its timing cannot be influenced + * from the driver. Non-zero RTS delays are rejected (sanitized to + * zero) by the serial core. + */ + one->config.flags |= WK2XXX_RECONF_RS485; + kthread_queue_work(&s->kworker, &one->reg_work); + + return 0; +} + +static void wk2xxx_reg_proc(struct kthread_work *ws) +{ + struct wk2xxx_one *one = to_wk2xxx_one(ws, reg_work); + struct wk2xxx_port *s = dev_get_drvdata(one->port.dev); + struct wk2xxx_one_config config; + unsigned long irqflags; + + uart_port_lock_irqsave(&one->port, &irqflags); + config = one->config; + memset(&one->config, 0, sizeof(one->config)); + uart_port_unlock_irqrestore(&one->port, irqflags); + + if (config.flags & WK2XXX_RECONF_IER) + wk2xxx_port_reg_update(s, one->port.iobase, WK2XXX_SIER_REG, + config.ier_mask, config.ier_val); + + if (config.flags & WK2XXX_RECONF_RS485) + wk2xxx_reconf_rs485(&one->port); +} + +static unsigned int wk2xxx_tx_empty(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + u8 fsr = 0; + + if (wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr)) + return TIOCSER_TEMT; + + return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 : + TIOCSER_TEMT; +} + +static unsigned int wk2xxx_get_mctrl(struct uart_port *port) +{ + /* The WK2xxx does not expose modem control lines. */ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + +static void wk2xxx_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* The WK2xxx does not support modem control lines. */ +} + +static void wk2xxx_enable_ms(struct uart_port *port) +{ + /* The WK2xxx does not have modem status registers. */ +} + +static void wk2xxx_break_ctl(struct uart_port *port, int break_state) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + + wk2xxx_port_reg_update(s, port->iobase, WK2XXX_LCR_REG, + WK2XXX_LCR_BREAK_BIT, + break_state ? WK2XXX_LCR_BREAK_BIT : 0); +} + +/* + * Configure a sub-UART: disable interrupts and TX/RX, program the line + * control and baud rate registers and restore the previous state. + */ +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr, + u8 baud0, u8 baud1, u8 pres) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 sier, scr, fsr; + int count = 200; + + guard(mutex)(&s->reg_lock); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier); + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Wait for the transmitter to become idle. */ + do { + wk2xxx_raw_port_read(s, portno, WK2XXX_FSR_REG, &fsr); + } while ((fsr & WK2XXX_FSR_TBUSY_BIT) && count--); + + /* Disable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, &scr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr & ~(WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Program the line control register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr); + + /* Configure hardware flow control levels. */ + if (fwcr) { + wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTH_REG, 0xf0); + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTL_REG, 0x80); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + /* Program the baud rate generator (page 1 registers). */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD0_REG, baud0); + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD1_REG, baud1); + wk2xxx_raw_port_write(s, portno, WK2XXX_PRES_REG, pres); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + + /* Re-enable the transmitter and receiver. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, + scr | (WK2XXX_SCR_TXEN_BIT | + WK2XXX_SCR_RXEN_BIT)); + + /* Restore the interrupt enable register. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, sier); +} + +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, + u8 *baud0, u8 *baud1, u8 *pres) +{ + unsigned int div, rem; + + /* Never divide by zero; the serial core normally prevents this. */ + if (baud == 0) + baud = 9600; + + div = clk / (baud * 16); + if (div == 0) + div = 1; + div--; + *baud0 = div & 0xff; + *baud1 = (div >> 8) & 0xff; + + rem = clk % (baud * 16); + *pres = (u32)div_u64((u64)rem * 100, baud); + *pres = (*pres + 50) / 100; +} + +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, + const struct ktermios *old) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int baud, read_mask, ignore_mask; + u8 lcr = 0, fwcr = 0; + u8 baud0, baud1, pres; + + /* The WK2xxx supports 8 data bits only. */ + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + + /* Parity. */ + if (termios->c_cflag & PARENB) { + lcr |= WK2XXX_LCR_PAEN_BIT; + switch (termios->c_cflag & (PARODD | CMSPAR)) { + case 0: + lcr |= WK2XXX_LCR_PAM1_BIT; /* even */ + break; + case PARODD: + lcr |= WK2XXX_LCR_PAM0_BIT; /* odd */ + break; + case CMSPAR: + break; /* space */ + case PARODD | CMSPAR: + lcr |= WK2XXX_LCR_PAM1_BIT | + WK2XXX_LCR_PAM0_BIT; /* mark */ + break; + } + } + + /* Stop bits. */ + if (termios->c_cflag & CSTOPB) + lcr |= WK2XXX_LCR_STPL_BIT; + + /* Determine the status masks to publish. */ + read_mask = WK2XXX_LSR_OE_BIT; + if (termios->c_iflag & INPCK) + read_mask |= WK2XXX_LSR_PE_BIT | WK2XXX_LSR_FE_BIT; + if (termios->c_iflag & (BRKINT | PARMRK)) + read_mask |= WK2XXX_LSR_BI_BIT; + + ignore_mask = 0; + if (termios->c_iflag & IGNBRK) + ignore_mask |= WK2XXX_LSR_BI_BIT; + if (!(termios->c_cflag & CREAD)) + ignore_mask |= WK2XXX_LSR_BRK_ERROR_MASK | + WK2XXX_LSR_IGNORE_DATA; + + /* The two-channel variants do not implement hardware flow control. */ + if (!s->devtype->has_hw_flow_control) + termios->c_cflag &= ~CRTSCTS; + + /* Hardware flow control is configured in the chip below. */ + if (s->devtype->has_hw_flow_control && (termios->c_cflag & CRTSCTS)) + fwcr = WK2XXX_FWCR_FWM_RTS_CTS; + + /* Get the baud rate generator configuration. */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / 16 / 0xffff, + port->uartclk / 16); + + wk2xxx_calc_divisor(port->uartclk, baud, &baud0, &baud1, &pres); + wk2xxx_conf_port(port, lcr, fwcr, baud0, baud1, pres); + + /* + * Publish the masks and flow-control status under the port lock; the + * RX/TX paths read them from their kthread context. + */ + guard(uart_port_lock_irqsave)(port); + port->read_status_mask = read_mask; + port->ignore_status_mask = ignore_mask; + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); + if (s->devtype->has_hw_flow_control && (termios->c_cflag & CRTSCTS)) + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; + uart_update_timeout(port, termios->c_cflag, baud); +} + +static int wk2xxx_startup(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* + * GENA and GIER are shared by all ports on the chip: a failed + * read must not be turned into a write of only this port's + * bit, which would clear the sibling ports' bits. + */ + if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®)) + return -EIO; + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + + /* Reset the sub-UART. */ + wk2xxx_raw_write(s, WK2XXX_GRST_REG, BIT(portno)); + + /* Enable the sub-UART interrupt in the global mask. */ + if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®)) + return -EIO; + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Enable RX FIFO trigger and RX time-out interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, + WK2XXX_SIER_RFTRIG_IEN_BIT | + WK2XXX_SIER_RXOUT_IEN_BIT); + + /* Enable the transmitter and receiver. */ + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, ®); + reg |= WK2XXX_SCR_TXEN_BIT | WK2XXX_SCR_RXEN_BIT; + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, reg); + + /* Reset and configure the FIFOs. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xff); + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xfc); + + /* Set the RX/TX FIFO trigger levels. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); + wk2xxx_raw_port_write(s, portno, WK2XXX_RFTL_REG, + WK2XXX_RXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_TFTL_REG, + WK2XXX_TXFIFO_LEVEL); + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); + } + + kfifo_reset(&port->state->port.xmit_fifo); + + /* Start the shared polling loop when the first port is opened. */ + if (s->polling) { + guard(mutex)(&s->poll_lock); + if (atomic_inc_return(&s->open_ports) == 1) + kthread_queue_delayed_work(&s->kworker, &s->poll_work, + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); + } + + return 0; +} + +static void wk2xxx_shutdown(struct uart_port *port) +{ + struct wk2xxx_port *s = dev_get_drvdata(port->dev); + unsigned int portno = port->iobase; + u8 reg; + + scoped_guard(mutex, &s->reg_lock) { + /* Disable the sub-UART interrupt in the global mask. */ + if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®)) + return; + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); + + /* Disable all sub-UART interrupts. */ + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); + + /* Reset the sub-UART. */ + wk2xxx_raw_read(s, WK2XXX_GRST_REG, ®); + reg |= BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg); + + /* Disable the sub-UART. */ + if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®)) + return; + reg &= ~BIT(portno); + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); + } + + /* + * Stop the shared polling loop once the last port is closed. The + * check and the cancel are serialized by poll_lock against a + * concurrent open of another port, so the two cannot tear the + * open_ports 0/1 boundary in a way that leaves the loop cancelled + * while a port is still open. + */ + if (s->polling) { + guard(mutex)(&s->poll_lock); + if (atomic_dec_return(&s->open_ports) == 0) + kthread_cancel_delayed_work_sync(&s->poll_work); + } + + kthread_flush_worker(&s->kworker); +} + +static const char *wk2xxx_type(struct uart_port *port) +{ + return (port->type == PORT_WK2XXX) ? WK2XXX_NAME : NULL; +} + +static void wk2xxx_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_WK2XXX; +} + +static int wk2xxx_verify_port(struct uart_port *port, struct serial_struct *s) +{ + if (s->type != PORT_UNKNOWN && s->type != PORT_WK2XXX) + return -EINVAL; + if (s->irq != port->irq) + return -EINVAL; + + return 0; +} + +static const struct uart_ops wk2xxx_ops = { + .tx_empty = wk2xxx_tx_empty, + .set_mctrl = wk2xxx_set_mctrl, + .get_mctrl = wk2xxx_get_mctrl, + .stop_tx = wk2xxx_stop_tx, + .start_tx = wk2xxx_start_tx, + .throttle = wk2xxx_throttle, + .unthrottle = wk2xxx_unthrottle, + .stop_rx = wk2xxx_stop_rx, + .enable_ms = wk2xxx_enable_ms, + .break_ctl = wk2xxx_break_ctl, + .startup = wk2xxx_startup, + .shutdown = wk2xxx_shutdown, + .set_termios = wk2xxx_set_termios, + .type = wk2xxx_type, + .config_port = wk2xxx_config_port, + .verify_port = wk2xxx_verify_port, +}; + +static const struct serial_rs485 wk2xxx_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND, + /* RTS timing is hardware-driven; RTS delays are not supported. */ +}; + +static int wk2xxx_probe(struct spi_device *spi) +{ + const struct wk2xxx_devtype *devtype; + struct device *dev = &spi->dev; + struct wk2xxx_port *s; + unsigned long uartclk; + bool port_registered[WK2XXX_MAX_PORTS]; + u8 val; + int i, ret; + + /* Setup SPI bus. The SPI mode follows the device tree (spi-cpha, + * spi-cpol); it defaults to SPI mode 0 when unspecified. + */ + spi->bits_per_word = 8; + spi->max_speed_hz = spi->max_speed_hz ? : 10 * HZ_PER_MHZ; + ret = spi_setup(spi); + if (ret) + return ret; + + devtype = spi_get_device_match_data(spi); + if (!devtype) + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); + + /* Allocate port structure. */ + s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); + if (!s) + return dev_err_probe(dev, -ENOMEM, + "Error allocating port structure\n"); + + s->devtype = devtype; + s->spi = spi; + mutex_init(&s->reg_lock); + mutex_init(&s->poll_lock); + atomic_set(&s->open_ports, 0); + dev_set_drvdata(dev, s); + + /* + * The WK2xxx has no identification register, so the best we can do + * is to check that communication is at all possible. + */ + ret = wk2xxx_reg_read(s, WK2XXX_GENA_REG, &val); + if (ret) + return dev_err_probe(dev, ret, "Failed to read GENA register\n"); + + /* The reference clock (crystal or external clock input) is mandatory. */ + s->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(s->clk)) + return dev_err_probe(dev, PTR_ERR(s->clk), + "Failed to get the reference clock\n"); + + uartclk = clk_get_rate(s->clk); + if (!uartclk) + return dev_err_probe(dev, -EINVAL, + "Clock rate must not be zero\n"); + + /* Mark each port line and status as uninitialized. */ + for (i = 0; i < devtype->nr_uart; ++i) { + s->p[i].port.line = WK2XXX_MAX_DEVS; + port_registered[i] = false; + } + + kthread_init_worker(&s->kworker); + s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker, + "wk2xxx"); + if (IS_ERR(s->kworker_task)) { + ret = PTR_ERR(s->kworker_task); + goto out_ports; + } + sched_set_fifo(s->kworker_task); + + /* + * Reset the chip and disable every sub-UART and its interrupt before + * the ports are registered (and, in interrupt mode, before the IRQ is + * requested). The sub-UARTs stay disabled until a port is opened in + * wk2xxx_startup(), so no stale pending condition can raise the IRQ + * line while the ports are being set up. + */ + wk2xxx_reg_write(s, WK2XXX_GRST_REG, (1 << devtype->nr_uart) - 1); + wk2xxx_reg_write(s, WK2XXX_GENA_REG, 0); + wk2xxx_reg_write(s, WK2XXX_GIER_REG, 0); + + if (spi->irq <= 0) { + /* Poll the device instead of using interrupts. */ + s->polling = true; + kthread_init_delayed_work(&s->poll_work, wk2xxx_poll_proc); + } + + for (i = 0; i < devtype->nr_uart; ++i) { + struct fwnode_handle *saved_fwnode = dev_fwnode(dev); + struct device_node *port_np = NULL; + struct device_node *child; + + ret = ida_alloc_max(&wk2xxx_lines, WK2XXX_MAX_DEVS - 1, + GFP_KERNEL); + if (ret < 0) + goto out_ports; + + s->p[i].port.line = ret; + + /* Locate the matching "serial@i" DT subnode, if any. */ + for_each_available_child_of_node(dev->of_node, child) { + u32 reg; + + if (!of_node_name_eq(child, "serial")) + continue; + if (of_property_read_u32(child, "reg", ®)) + continue; + if (reg == i) { + port_np = child; + break; + } + } + + /* Initialize port data. */ + s->p[i].port.dev = dev; + s->p[i].port.irq = spi->irq; + s->p[i].port.type = PORT_WK2XXX; + s->p[i].port.fifosize = WK2XXX_FIFO_SIZE; + s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; + s->p[i].port.iobase = i; + s->p[i].port.iotype = UPIO_BUS; + s->p[i].port.uartclk = uartclk; + if (devtype->has_rs485) { + s->p[i].port.rs485_config = wk2xxx_config_rs485; + s->p[i].port.rs485_supported = wk2xxx_rs485_supported; + } + s->p[i].port.ops = &wk2xxx_ops; + + mutex_init(&s->p[i].tx_lock); + + kthread_init_work(&s->p[i].tx_work, wk2xxx_tx_proc); + kthread_init_work(&s->p[i].reg_work, wk2xxx_reg_proc); + + /* + * Temporarily retarget dev's fwnode to the per-port subnode + * so uart_get_rs485_mode() picks up the per-port properties. + */ + if (port_np && devtype->has_rs485) { + device_set_node(dev, of_fwnode_handle(port_np)); + ret = uart_get_rs485_mode(&s->p[i].port); + device_set_node(dev, saved_fwnode); + of_node_put(port_np); + if (ret) + goto out_ports; + } + + /* Register port. */ + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); + if (ret) + goto out_ports; + + port_registered[i] = true; + } + + /* + * Request the IRQ only after every port is registered so that an early + * interrupt can never reach a port whose port->state is not ready yet. + * We first try to acquire the IRQ line as a level IRQ; if that + * succeeds, we can allow sharing the interrupt as well. In case the + * interrupt controller doesn't support that, we fall back to a + * non-shared falling-edge trigger. + */ + if (!s->polling) { + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_LOW | IRQF_SHARED | + IRQF_ONESHOT, dev_name(dev), s); + if (ret) + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(dev), s); + if (ret) { + dev_err(dev, "Unable to request IRQ %i\n", spi->irq); + goto out_ports; + } + s->irq_requested = true; + } + + return 0; + +out_ports: + if (s->irq_requested) + free_irq(spi->irq, s); + + for (i = 0; i < devtype->nr_uart; i++) { + if (port_registered[i]) + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + if (s->p[i].port.line < WK2XXX_MAX_DEVS) + ida_free(&wk2xxx_lines, s->p[i].port.line); + } + + if (!IS_ERR(s->kworker_task)) + kthread_stop(s->kworker_task); + + return ret; +} + +static void wk2xxx_remove(struct spi_device *spi) +{ + struct wk2xxx_port *s = dev_get_drvdata(&spi->dev); + int i; + + /* + * Unregister the ports first. Removing a port that is still open + * hangs up its tty and runs wk2xxx_shutdown(), which disables the + * sub-UART in the chip and drains the shared worker, so afterwards no + * enabled interrupt source (and thus no queued TX/register work) can + * target a port that is being torn down. + */ + for (i = 0; i < s->devtype->nr_uart; i++) { + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); + ida_free(&wk2xxx_lines, s->p[i].port.line); + } + + /* + * Every sub-UART is disabled now, so the chip can no longer raise the + * IRQ line. Free the IRQ; a handler already in flight only observes + * disabled ports and returns IRQ_NONE. + */ + if (s->irq_requested) + free_irq(spi->irq, s); + + if (s->polling) + kthread_cancel_delayed_work_sync(&s->poll_work); + + kthread_flush_worker(&s->kworker); + kthread_stop(s->kworker_task); +} + +static const struct of_device_id wk2xxx_dt_ids[] = { + { .compatible = "wkmic,wk2124", .data = &wk2124_devtype }, + { .compatible = "wkmic,wk2132", .data = &wk2132_devtype }, + { .compatible = "wkmic,wk2168", .data = &wk2168_devtype }, + { .compatible = "wkmic,wk2202", .data = &wk2202_devtype }, + { .compatible = "wkmic,wk2204", .data = &wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(of, wk2xxx_dt_ids); + +static const struct spi_device_id wk2xxx_id_table[] = { + { "wk2124", (kernel_ulong_t)&wk2124_devtype }, + { "wk2132", (kernel_ulong_t)&wk2132_devtype }, + { "wk2168", (kernel_ulong_t)&wk2168_devtype }, + { "wk2202", (kernel_ulong_t)&wk2202_devtype }, + { "wk2204", (kernel_ulong_t)&wk2204_devtype }, + { } +}; +MODULE_DEVICE_TABLE(spi, wk2xxx_id_table); + +static struct spi_driver wk2xxx_spi_driver = { + .driver = { + .name = WK2XXX_NAME, + .of_match_table = wk2xxx_dt_ids, + }, + .probe = wk2xxx_probe, + .remove = wk2xxx_remove, + .id_table = wk2xxx_id_table, +}; + +static int __init wk2xxx_init(void) +{ + int ret; + + ret = uart_register_driver(&wk2xxx_uart); + if (ret) + return ret; + + ret = spi_register_driver(&wk2xxx_spi_driver); + if (ret) + uart_unregister_driver(&wk2xxx_uart); + + return ret; +} +module_init(wk2xxx_init); + +static void __exit wk2xxx_exit(void) +{ + spi_unregister_driver(&wk2xxx_spi_driver); + uart_unregister_driver(&wk2xxx_uart); +} +module_exit(wk2xxx_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Xuxunwei"); +MODULE_AUTHOR("B. Eschrich"); +MODULE_AUTHOR("Zi Jie Zhao <zjzhao@edatec.cn>"); +MODULE_DESCRIPTION("WK2xxx SPI UART driver"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 377884e3856a..05c75d3ef479 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -234,6 +234,9 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* WK2xxx SPI to UART bridge */ +#define PORT_WK2XXX 124 + /* Generic type identifier for ports which type is not important to userspace. */ #define PORT_GENERIC (-1) -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao @ 2026-09-08 10:44 ` sashiko-bot 2026-09-08 17:51 ` Hugo Villeneuve 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-09-08 10:44 UTC (permalink / raw) To: zjzhao; +Cc: robh, linux-serial, devicetree, conor+dt Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: - [High] UART transmit FIFO data corruption and stats race due to missing port lock in wk2xxx_handle_tx(). - [High] TTY buffer flood due to persistent overrun flag in batch RX loop. - [High] The tx_empty callback performs a synchronous SPI read which sleeps while the serial core holds the port spinlock. - [High] Early return on SPI error in wk2xxx_shutdown skips essential software state cleanup, causing open_ports to permanently leak. - [Medium] OF node reference leak when parsing DT nodes for non-RS485 variants. - [Medium] Commit message claims to apply serial properties from DT subnodes, but they are not applied. - [Low] The driver intentionally breaks the build on architectures with L1_CACHE_BYTES < 31 (e.g., m68k). -- commit 8ec8660308853d2cff61b90024208e8b3e1bafdb Author: Zi Jie Zhao <zjzhao@edatec.cn> Subject: serial: wk2xxx: Add WK2xxx SPI UART driver This commit introduces a new driver for the WK2xxx SPI to UART bridge ICs. It models the driver after the NXP sc16is7xx driver, utilizing a kthread worker for SPI register accesses and supporting polled or interrupt-driven operation. > diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c > index 0000000000000..15869fbdeab07 > --- /dev/null > +++ b/drivers/tty/serial/wk2xxx.c [ ... ] > + /* > + * Shared SPI transfer buffers. All SPI accesses are serialized by > + * s->reg_lock, so these are never used concurrently. Each buffer is > + * cache-line aligned and sized to a full cache line, so DMA cache > + * maintenance on one of them can never invalidate a line shared > + * with any other field (notably the p[] array below). > + */ > + u8 spi_tx[L1_CACHE_BYTES] ____cacheline_aligned; > + u8 spi_rx[L1_CACHE_BYTES] ____cacheline_aligned; > + struct wk2xxx_one p[]; > +}; > + > +static_assert(WK2XXX_MAX_SPI_LEN + 1 <= L1_CACHE_BYTES); [Severity: Low] Does this intentionally break the build for architectures with a cache line size smaller than 31 bytes (such as m68k where L1_CACHE_BYTES is 16)? Might it be better to size the array to something like max(L1_CACHE_BYTES, 32) instead of failing the compilation? [ ... ] > +static void wk2xxx_handle_tx(struct uart_port *port) > +{ [ ... ] > + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); > + sent = 0; > + while (to_send) { > + unsigned int chunk = min_t(unsigned int, to_send, > + WK2XXX_MAX_SPI_LEN); > + > + if (wk2xxx_fifo_write(s, portno, tail, chunk)) > + break; > + tail += chunk; > + to_send -= chunk; > + sent += chunk; > + } > + uart_xmit_advance(port, sent); [Severity: High] Is it safe to manipulate the xmit_fifo pointers and call uart_xmit_advance() here without holding the port lock? If a concurrent operation like a tcflush() ioctl resets the FIFO via uart_flush_buffer() (which zeroes the in and out indices), could this lockless uart_xmit_advance() call advance the out index beyond the in index, causing a substantial underflow and subsequent data corruption? [ ... ] > +static void wk2xxx_handle_rx(struct uart_port *port) > +{ [ ... ] > + /* Map the FIFO status register error flags to line status. */ > + if (fsr & WK2XXX_FSR_ERR_MASK) { > + if (fsr & WK2XXX_FSR_RFPE_BIT) { > + port->icount.parity++; > + lsr |= WK2XXX_LSR_PE_BIT; > + flag = TTY_PARITY; > + } > + if (fsr & WK2XXX_FSR_RFFE_BIT) { > + port->icount.frame++; > + lsr |= WK2XXX_LSR_FE_BIT; > + flag = TTY_FRAME; > + } > + if (fsr & WK2XXX_FSR_RFOE_BIT) { > + port->icount.overrun++; > + lsr |= WK2XXX_LSR_OE_BIT; > + flag = TTY_OVERRUN; > + } [ ... ] > + for (i = 0; i < rxlen; ++i) { > + u8 ch = one->buf[i]; > + > + if (uart_handle_sysrq_char(port, ch)) > + continue; > + > + if (lsr & port->ignore_status_mask) > + continue; > + > + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); > + } [Severity: High] Could this loop insert an excessive number of dummy overrun characters into the TTY buffer? Since lsr is modified once outside the loop when the WK2XXX_FSR_RFOE_BIT is set, the WK2XXX_LSR_OE_BIT remains active for the entire iteration. When uart_insert_char() evaluates this bit, it will insert a dummy overrun byte for every single character in this chunk (up to 256 times) for a single overrun event. [ ... ] > +static unsigned int wk2xxx_tx_empty(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + u8 fsr = 0; > + > + if (wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr)) > + return TIOCSER_TEMT; [Severity: High] Can this sequence sleep while executing in an atomic context? The serial core routinely invokes the ops->tx_empty() callback while holding the port spinlock with interrupts disabled (for instance, during the TIOCSERGETLSR ioctl). However, wk2xxx_port_reg_read() performs a synchronous spi_sync() transfer which can sleep. [ ... ] > +static void wk2xxx_shutdown(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + u8 reg; > + > + scoped_guard(mutex, &s->reg_lock) { > + /* Disable the sub-UART interrupt in the global mask. */ > + if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®)) > + return; [Severity: High] If this SPI read fails, the function returns early. Does this leave the software state permanently out of sync? By returning here, the function skips the atomic_dec_return(&s->open_ports) and kthread_flush_worker() cleanup steps at the end of wk2xxx_shutdown(). This leaves the open_ports counter permanently elevated and strands the polling kthread so it can never be stopped. [ ... ] > +static int wk2xxx_probe(struct spi_device *spi) > +{ [ ... ] > + /* Locate the matching "serial@i" DT subnode, if any. */ > + for_each_available_child_of_node(dev->of_node, child) { > + u32 reg; > + > + if (!of_node_name_eq(child, "serial")) > + continue; > + if (of_property_read_u32(child, "reg", ®)) > + continue; > + if (reg == i) { > + port_np = child; > + break; > + } > + } [ ... ] > + /* > + * Temporarily retarget dev's fwnode to the per-port subnode > + * so uart_get_rs485_mode() picks up the per-port properties. > + */ > + if (port_np && devtype->has_rs485) { > + device_set_node(dev, of_fwnode_handle(port_np)); > + ret = uart_get_rs485_mode(&s->p[i].port); > + device_set_node(dev, saved_fwnode); > + of_node_put(port_np); > + if (ret) > + goto out_ports; > + } [Severity: Medium] Is there a device tree node reference leak here for non-RS485 variants? The port_np reference is acquired via the for_each_available_child_of_node() loop earlier. However, if devtype->has_rs485 evaluates to false (such as for the WK2132 or WK2202 variants), this block is entirely bypassed and of_node_put(port_np) is never called. [Severity: Medium] The commit message states the driver will "apply serial and RS-485 properties from each serial@N DT subnode to the corresponding channel." While uart_get_rs485_mode() is called in this block, should there also be a call to uart_read_port_properties() to ensure the generic serial properties (like tx-trigger or rx-trigger) are actually applied as described? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260908103129.58085-1-zjzhao@edatec.cn?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver 2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-08 10:44 ` sashiko-bot @ 2026-09-08 17:51 ` Hugo Villeneuve 1 sibling, 0 replies; 7+ messages in thread From: Hugo Villeneuve @ 2026-09-08 17:51 UTC (permalink / raw) To: zjzhao Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, linux-kernel, linux-serial, devicetree On Tue, 8 Sep 2026 18:31:29 +0800 zjzhao@edatec.cn wrote: > From: Zi Jie Zhao <zjzhao@edatec.cn> > > Add a driver for the WK2xxx SPI to UART bridge ICs (WK2124, WK2132, WK2168, > WK2202 and WK2204) from Chengdu Weikai Microelectronics. Support two or > four full-duplex UART channels with 256-byte RX/TX FIFOs through a single > SPI slave interface and one interrupt line. > > Rework the WKmic open-source driver: > https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c > Model the new driver after the NXP sc16is7xx driver. Register ttyWK0..N > lines, use a threaded IRQ with a kthread worker for register access, fall > back to polling when the interrupt line is not described, and apply serial > and RS-485 properties from each serial@N DT subnode to the corresponding > channel. > > Limit hardware flow-control and RS-485 reporting and configuration to the > four-channel variants that implement these features. > > Reset the chip and disable every sub-UART at probe time. Request the IRQ > only after registering all ports. Unregister the ports before stopping the > IRQ and worker during removal. > > Allocate PORT_WK2XXX (124) and add the SERIAL_WK2XXX Kconfig option. > > Signed-off-by: Zi Jie Zhao <zjzhao@edatec.cn> > --- > drivers/tty/serial/Kconfig | 17 + > drivers/tty/serial/Makefile | 1 + > drivers/tty/serial/wk2xxx.c | 1433 ++++++++++++++++++++++++++++++ > include/uapi/linux/serial_core.h | 3 + > 4 files changed, 1454 insertions(+) > create mode 100644 drivers/tty/serial/wk2xxx.c > > diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig > index cf7dba473b20..5f7a71f377ac 100644 > --- a/drivers/tty/serial/Kconfig > +++ b/drivers/tty/serial/Kconfig > @@ -1207,6 +1207,23 @@ config SERIAL_MXS_AUART_CONSOLE > help > Enable a MXS AUART port to be the system console. > > +config SERIAL_WK2XXX > + tristate "WK2xxx SPI UART support" > + depends on SPI_MASTER > + select SERIAL_CORE > + help > + This selects the WK2xxx SPI to UART bridge driver. > + Supported ICs are: > + > + WK2124 > + WK2132 > + WK2168 > + WK2202 > + WK2204 > + > + To compile this driver as a module, choose M here: the module > + will be called wk2xxx. > + > config SERIAL_XILINX_PS_UART > tristate "Cadence (Xilinx Zynq) UART support" > depends on OF > diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile > index bba7b21a4a1d..fdd13f3dd058 100644 > --- a/drivers/tty/serial/Makefile > +++ b/drivers/tty/serial/Makefile > @@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o > obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o > obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o > obj-$(CONFIG_SERIAL_VT8500) += vt8500_serial.o > +obj-$(CONFIG_SERIAL_WK2XXX) += wk2xxx.o > obj-$(CONFIG_SERIAL_XILINX_PS_UART) += xilinx_uartps.o > obj-$(CONFIG_SERIAL_ZS) += zs.o > > diff --git a/drivers/tty/serial/wk2xxx.c b/drivers/tty/serial/wk2xxx.c > new file mode 100644 > index 000000000000..15869fbdeab0 > --- /dev/null > +++ b/drivers/tty/serial/wk2xxx.c > @@ -0,0 +1,1433 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * WK2xxx SPI to UART bridge tty serial driver > + * > + * SPI-to-UART bridge ICs from WKmic (Chengdu Weikai Microelectronics): > + * WK2124, WK2132, WK2168, WK2202 and WK2204. Each IC exposes two or four > + * full-duplex UART channels with 256-byte RX/TX FIFOs through a single SPI > + * slave interface and one interrupt line. The slave register set is split > + * into two banks (page 0 / page 1) selected by the SPAGE register. > + * > + * This driver is a rework of the WKmic open-source driver: > + * https://github.com/britus/wk2xxx/blob/master/spi-wk2xxx.c > + * It is modeled after the NXP sc16is7xx driver. > + * > + * (C) Copyright 2022 WKIC Ltd. by Xu XunWei Tech, Xuxunwei > + * (C) Copyright 2024 EoF Software Labs, B. Eschrich > + * Copyright (C) 2026 Zi Jie Zhao, EDATEC Technology Co., Ltd. <zjzhao@edatec.cn> > + */ > + > +#include <linux/atomic.h> > +#include <linux/bits.h> > +#include <linux/bitfield.h> > +#include <linux/cache.h> > +#include <linux/cleanup.h> > +#include <linux/clk.h> > +#include <linux/device.h> > +#include <linux/idr.h> > +#include <linux/interrupt.h> > +#include <linux/kfifo.h> > +#include <linux/kthread.h> > +#include <linux/math64.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/of.h> > +#include <linux/overflow.h> > +#include <linux/property.h> > +#include <linux/sched.h> > +#include <linux/serial.h> > +#include <linux/serial_core.h> > +#include <linux/slab.h> > +#include <linux/spi/spi.h> > +#include <linux/string.h> > +#include <linux/tty.h> > +#include <linux/tty_flip.h> > +#include <linux/units.h> > +#include <linux/workqueue.h> > + > +#define WK2XXX_NAME "wk2xxx" > +#define WK2XXX_MAX_DEVS 8 /* Total number of lines. */ This is the number of devices/ICs, not lines. You could remove the comment as this is self-explanatory. > +#define WK2XXX_MAX_PORTS 4 /* Max number of ports per IC. */ > +#define WK2XXX_FIFO_SIZE 256 > +#define WK2XXX_MAX_SPI_LEN 30 /* Max bytes per SPI FIFO burst. */ > +#define WK2XXX_MAX_TX_CHARS 200 /* Leave headroom in the TX FIFO. */ I do not understand this comment in relation with the #define name... > +#define WK2XXX_RXFIFO_LEVEL 0x40 /* RX FIFO trigger level. */ > +#define WK2XXX_TXFIFO_LEVEL 0x01 /* TX FIFO trigger level. */ > +#define WK2XXX_POLL_PERIOD_MS 10 > +#define WK2XXX_IRQ_MAX_PASSES 8 /* Bound the IRQ drain loop. */ > + > +/* SPI command byte: bit 6 = read, bit 7 = FIFO access. */ A little bit unclear, so bit 6 is direction R/W, and bit 7 is access type, register or FIFO? > +#define WK2XXX_SPI_READ BIT(6) > +#define WK2XXX_SPI_FIFO_WRITE BIT(7) > +#define WK2XXX_SPI_FIFO_READ (BIT(7) | BIT(6)) > + > +/* Marker used to address registers located in page 1. */ > +#define WK2XXX_PAGE1 BIT(7) > + > +/* Global registers. */ > +#define WK2XXX_GENA_REG 0x00 /* Global UART enable */ Your previous comments have a trailing dot ".", and these have not. Choose one format and use it everywhere... > +#define WK2XXX_GRST_REG 0x01 /* Global reset */ > +#define WK2XXX_GMUT_REG 0x02 /* Master UART control */ > +#define WK2XXX_GIER_REG 0x10 /* Global interrupt enable */ > +#define WK2XXX_GIFR_REG 0x11 /* Global interrupt flag */ > + > +/* Port (sub-UART) registers, page 0. */ > +#define WK2XXX_SPAGE_REG 0x03 /* Register page select */ > +#define WK2XXX_SCR_REG 0x04 /* Slave control */ > +#define WK2XXX_LCR_REG 0x05 /* Line control */ > +#define WK2XXX_FCR_REG 0x06 /* FIFO control */ > +#define WK2XXX_SIER_REG 0x07 /* Slave interrupt enable */ > +#define WK2XXX_SIFR_REG 0x08 /* Slave interrupt flag */ > +#define WK2XXX_TFCNT_REG 0x09 /* TX FIFO count */ > +#define WK2XXX_RFCNT_REG 0x0a /* RX FIFO count */ > +#define WK2XXX_FSR_REG 0x0b /* FIFO status */ > +#define WK2XXX_LSR_REG 0x0c /* Line status */ > +#define WK2XXX_FDAT_REG 0x0d /* FIFO data */ > +#define WK2XXX_FWCR_REG 0x0e /* Flow control */ > +#define WK2XXX_RS485_REG 0x0f /* RS485 control */ > + > +/* Port (sub-UART) registers, page 1. */ > +#define WK2XXX_BAUD1_REG (0x04 | WK2XXX_PAGE1) /* Divisor Latch High */ > +#define WK2XXX_BAUD0_REG (0x05 | WK2XXX_PAGE1) /* Divisor Latch Low */ > +#define WK2XXX_PRES_REG (0x06 | WK2XXX_PAGE1) /* Fractional divisor */ > +#define WK2XXX_RFTL_REG (0x07 | WK2XXX_PAGE1) /* RX FIFO trigger level */ > +#define WK2XXX_TFTL_REG (0x08 | WK2XXX_PAGE1) /* TX FIFO trigger level */ > +#define WK2XXX_FWTH_REG (0x09 | WK2XXX_PAGE1) /* Flow control high level */ > +#define WK2XXX_FWTL_REG (0x0a | WK2XXX_PAGE1) /* Flow control low level */ > +#define WK2XXX_XON1_REG (0x0b | WK2XXX_PAGE1) /* Xon word */ > +#define WK2XXX_XOFF1_REG (0x0c | WK2XXX_PAGE1) /* Xoff word */ > +#define WK2XXX_SADR_REG (0x0d | WK2XXX_PAGE1) /* RS485 auto address */ > +#define WK2XXX_SAEN_REG (0x0e | WK2XXX_PAGE1) /* RS485 address mask */ > +#define WK2XXX_RRSDLY_REG (0x0f | WK2XXX_PAGE1) /* RS485 RTS delay */ > + > +/* SCR register bits. */ > +#define WK2XXX_SCR_RXEN_BIT BIT(0) > +#define WK2XXX_SCR_TXEN_BIT BIT(1) > + > +/* LCR register bits. */ > +#define WK2XXX_LCR_STPL_BIT BIT(0) /* Two stop bits */ > +#define WK2XXX_LCR_PAM0_BIT BIT(1) /* Parity mode bit 0 */ > +#define WK2XXX_LCR_PAM1_BIT BIT(2) /* Parity mode bit 1 */ > +#define WK2XXX_LCR_PAEN_BIT BIT(3) /* Parity enable */ > +#define WK2XXX_LCR_BREAK_BIT BIT(5) /* TX break */ > + > +/* SIER register bits. */ > +#define WK2XXX_SIER_RFTRIG_IEN_BIT BIT(0) /* RX FIFO trigger */ > +#define WK2XXX_SIER_RXOUT_IEN_BIT BIT(1) /* RX time-out */ > +#define WK2XXX_SIER_TFTRIG_IEN_BIT BIT(2) /* TX FIFO trigger */ > + > +/* SIFR register bits. */ > +#define WK2XXX_SIFR_RFTRIG_INT_BIT BIT(0) > +#define WK2XXX_SIFR_RXOVT_INT_BIT BIT(1) > +#define WK2XXX_SIFR_TFTRIG_INT_BIT BIT(2) > + > +/* FSR register bits. */ > +#define WK2XXX_FSR_TBUSY_BIT BIT(0) > +#define WK2XXX_FSR_TFULL_BIT BIT(1) > +#define WK2XXX_FSR_TDAT_BIT BIT(2) > +#define WK2XXX_FSR_RDAT_BIT BIT(3) > +#define WK2XXX_FSR_RFPE_BIT BIT(4) /* RX FIFO parity error */ > +#define WK2XXX_FSR_RFFE_BIT BIT(5) /* RX FIFO frame error */ > +#define WK2XXX_FSR_RFBI_BIT BIT(6) /* RX FIFO break */ > +#define WK2XXX_FSR_RFOE_BIT BIT(7) /* RX FIFO overrun */ > +#define WK2XXX_FSR_ERR_MASK GENMASK(7, 4) > + > +/* LSR error bits, for use with uart_insert_char(). */ > +#define WK2XXX_LSR_PE_BIT BIT(0) > +#define WK2XXX_LSR_FE_BIT BIT(1) > +#define WK2XXX_LSR_BI_BIT BIT(2) > +#define WK2XXX_LSR_OE_BIT BIT(3) > +#define WK2XXX_LSR_BRK_ERROR_MASK (WK2XXX_LSR_OE_BIT | WK2XXX_LSR_PE_BIT | \ > + WK2XXX_LSR_FE_BIT | WK2XXX_LSR_BI_BIT) > +/* Internal marker: drop all received data (termios CREAD is clear). */ > +#define WK2XXX_LSR_IGNORE_DATA BIT(7) > + > +/* > + * FWCR register bits. The flow-control mode is selected by the FWM2-0 > + * field in bits 6-4 (the two-channel variants have no FWCR register). > + */ > +#define WK2XXX_FWCR_FWM_MASK GENMASK(6, 4) > +#define WK2XXX_FWCR_FWM_RTS_CTS FIELD_PREP(WK2XXX_FWCR_FWM_MASK, 0x3) > + > +/* RS485 register bits. */ > +#define WK2XXX_RS485_RTSINV_BIT BIT(0) > +#define WK2XXX_RS485_RTSEN_BIT BIT(1) > +#define WK2XXX_RS485_RSRS485_BIT BIT(6) > + > +struct wk2xxx_devtype { > + const char *name; > + int nr_uart; > + bool has_hw_flow_control; > + bool has_rs485; > +}; > + > +#define WK2XXX_RECONF_IER BIT(0) > +#define WK2XXX_RECONF_RS485 BIT(1) > + > +struct wk2xxx_one_config { > + unsigned int flags; > + u8 ier_mask; > + u8 ier_val; > +}; > + > +struct wk2xxx_one { > + struct uart_port port; > + struct mutex tx_lock; /* Serializes the TX path. */ > + struct kthread_work tx_work; > + struct kthread_work reg_work; > + struct wk2xxx_one_config config; > + unsigned char buf[WK2XXX_FIFO_SIZE]; /* RX buffer. */ > +}; > + > +struct wk2xxx_port { > + const struct wk2xxx_devtype *devtype; > + struct spi_device *spi; > + struct clk *clk; > + struct mutex reg_lock; /* SPI register access. */ > + struct mutex poll_lock; /* Serializes polling start/stop. */ > + struct kthread_worker kworker; > + struct task_struct *kworker_task; > + struct kthread_delayed_work poll_work; > + bool polling; > + bool irq_requested; > + atomic_t open_ports; > + /* > + * Shared SPI transfer buffers. All SPI accesses are serialized by > + * s->reg_lock, so these are never used concurrently. Each buffer is > + * cache-line aligned and sized to a full cache line, so DMA cache > + * maintenance on one of them can never invalidate a line shared > + * with any other field (notably the p[] array below). > + */ > + u8 spi_tx[L1_CACHE_BYTES] ____cacheline_aligned; > + u8 spi_rx[L1_CACHE_BYTES] ____cacheline_aligned; > + struct wk2xxx_one p[]; > +}; > + > +static_assert(WK2XXX_MAX_SPI_LEN + 1 <= L1_CACHE_BYTES); > + > +static DEFINE_IDA(wk2xxx_lines); > + > +static struct uart_driver wk2xxx_uart = { > + .owner = THIS_MODULE, > + .driver_name = WK2XXX_NAME, > + .dev_name = "ttyWK", > + .nr = WK2XXX_MAX_DEVS, > +}; > + > +#define to_wk2xxx_one(p, e) ((container_of((p), struct wk2xxx_one, e))) > + > +static const struct wk2xxx_devtype wk2124_devtype = { > + .name = "WK2124", > + .nr_uart = 4, > + .has_hw_flow_control = true, > + .has_rs485 = true, > +}; > + > +static const struct wk2xxx_devtype wk2132_devtype = { > + .name = "WK2132", > + .nr_uart = 2, > +}; > + > +static const struct wk2xxx_devtype wk2168_devtype = { > + .name = "WK2168", > + .nr_uart = 4, > + .has_hw_flow_control = true, > + .has_rs485 = true, > +}; > + > +static const struct wk2xxx_devtype wk2202_devtype = { > + .name = "WK2202", > + .nr_uart = 2, > +}; > + > +static const struct wk2xxx_devtype wk2204_devtype = { > + .name = "WK2204", > + .nr_uart = 4, > + .has_hw_flow_control = true, > + .has_rs485 = true, > +}; > + > +/* > + * The following functions are the low-level SPI accessors. The caller must > + * hold s->reg_lock, so that multi-byte accesses and page switches are > + * performed atomically on the SPI bus. > + */ > +static int wk2xxx_spi_transfer(struct wk2xxx_port *s, const u8 *tx, u8 *rx, > + unsigned int len) > +{ > + struct spi_transfer xfer = { > + .tx_buf = tx, > + .rx_buf = rx, > + .len = len, > + }; > + struct spi_message msg; > + > + spi_message_init(&msg); > + spi_message_add_tail(&xfer, &msg); > + > + return spi_sync(s->spi, &msg); > +} sc16is7xx and max310x use regmap for I2C/SPI access. You do not, can you explain why? Using regmap, you can take advantage of the cache. And you can (maybe) simplify paged accesses using regmap ranges? > + > +static int wk2xxx_raw_read(struct wk2xxx_port *s, u8 addr, u8 *val) > +{ > + u8 *tx = s->spi_tx; > + u8 *rx = s->spi_rx; > + int ret; > + > + tx[0] = WK2XXX_SPI_READ | addr; > + tx[1] = 0; > + ret = wk2xxx_spi_transfer(s, tx, rx, 2); > + if (ret) { > + *val = 0; > + return ret; > + } > + > + *val = rx[1]; > + return 0; > +} > + > +static int wk2xxx_raw_write(struct wk2xxx_port *s, u8 addr, u8 val) > +{ > + u8 *tx = s->spi_tx; > + u8 *rx = s->spi_rx; > + > + tx[0] = addr; > + tx[1] = val; > + > + return wk2xxx_spi_transfer(s, tx, rx, 2); > +} > + > +static unsigned int wk2xxx_port_addr(unsigned int portno, u8 reg) > +{ > + /* The sub-UART number is encoded in the upper nibble of the cmd byte. */ > + return (portno << 4) | reg; > +} > + > +static int wk2xxx_raw_port_read(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 *val) > +{ > + int ret; > + > + if (reg & WK2XXX_PAGE1) { > + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); > + if (ret) > + return ret; Add empty line for clarity? > + ret = wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); > + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); > + return ret; > + } > + > + return wk2xxx_raw_read(s, wk2xxx_port_addr(portno, reg & 0x0f), val); > +} > + > +static int wk2xxx_raw_port_write(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 val) > +{ > + int ret; > + > + if (reg & WK2XXX_PAGE1) { > + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 1); > + if (ret) > + return ret; Same as above? > + ret = wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); > + wk2xxx_raw_write(s, wk2xxx_port_addr(portno, WK2XXX_SPAGE_REG), 0); > + return ret; > + } > + > + return wk2xxx_raw_write(s, wk2xxx_port_addr(portno, reg & 0x0f), val); > +} > + > +/* > + * Locked wrappers used outside the register sequences that already hold > + * s->reg_lock. > + */ > +static int wk2xxx_reg_read(struct wk2xxx_port *s, u8 reg, u8 *val) > +{ > + guard(mutex)(&s->reg_lock); > + return wk2xxx_raw_read(s, reg, val); > +} > + > +static int wk2xxx_reg_write(struct wk2xxx_port *s, u8 addr, u8 val) > +{ > + guard(mutex)(&s->reg_lock); > + return wk2xxx_raw_write(s, addr, val); > +} > + > +static int wk2xxx_port_reg_read(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 *val) > +{ > + guard(mutex)(&s->reg_lock); > + return wk2xxx_raw_port_read(s, portno, reg, val); > +} > + > +static int wk2xxx_port_reg_write(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 val) > +{ > + guard(mutex)(&s->reg_lock); > + return wk2xxx_raw_port_write(s, portno, reg, val); > +} > + > +static void wk2xxx_port_reg_update(struct wk2xxx_port *s, unsigned int portno, > + u8 reg, u8 mask, u8 val) > +{ > + u8 r = 0; > + > + guard(mutex)(&s->reg_lock); > + if (wk2xxx_raw_port_read(s, portno, reg, &r)) > + return; Empty line > + wk2xxx_raw_port_write(s, portno, reg, (r & ~mask) | val); > +} > + > +static int wk2xxx_fifo_read(struct wk2xxx_port *s, unsigned int portno, > + u8 *buf, unsigned int len) > +{ > + u8 *tx = s->spi_tx; > + u8 *rx = s->spi_rx; > + int ret; > + > + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) > + return -EINVAL; > + > + /* > + * Take the register lock before touching the shared SPI buffers so a > + * concurrent transfer cannot observe (or be corrupted by) a partially > + * constructed command. > + */ > + guard(mutex)(&s->reg_lock); > + > + memset(tx, 0, WK2XXX_MAX_SPI_LEN + 1); > + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_READ); > + > + ret = wk2xxx_spi_transfer(s, tx, rx, len + 1); > + if (ret) > + return ret; > + > + memcpy(buf, rx + 1, len); > + return 0; > +} > + > +static int wk2xxx_fifo_write(struct wk2xxx_port *s, unsigned int portno, > + const u8 *buf, unsigned int len) > +{ > + u8 *tx = s->spi_tx; > + u8 *rx = s->spi_rx; > + > + if (len == 0 || len > WK2XXX_MAX_SPI_LEN) > + return -EINVAL; > + > + /* > + * Take the register lock before touching the shared SPI buffers so a > + * concurrent transfer cannot observe (or be corrupted by) a partially > + * constructed command. > + */ > + guard(mutex)(&s->reg_lock); > + > + tx[0] = wk2xxx_port_addr(portno, WK2XXX_SPI_FIFO_WRITE); > + memcpy(tx + 1, buf, len); > + > + return wk2xxx_spi_transfer(s, tx, rx, len + 1); > +} > + > +static void wk2xxx_ier_set(struct uart_port *port, u8 bit) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + > + lockdep_assert_held_once(&port->lock); > + > + one->config.flags |= WK2XXX_RECONF_IER; > + one->config.ier_mask |= bit; > + one->config.ier_val |= bit; > + kthread_queue_work(&s->kworker, &one->reg_work); > +} > + > +static void wk2xxx_ier_clear(struct uart_port *port, u8 bit) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + > + lockdep_assert_held_once(&port->lock); > + > + one->config.flags |= WK2XXX_RECONF_IER; > + one->config.ier_mask |= bit; > + one->config.ier_val &= ~bit; > + kthread_queue_work(&s->kworker, &one->reg_work); > +} > + > +static void wk2xxx_stop_tx(struct uart_port *port) > +{ > + wk2xxx_ier_clear(port, WK2XXX_SIER_TFTRIG_IEN_BIT); > +} > + > +static void wk2xxx_stop_rx(struct uart_port *port) > +{ > + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | > + WK2XXX_SIER_RXOUT_IEN_BIT); Put on the same line to take advantage of 100 columns limit, like you already did previously... > +} > + > +static void wk2xxx_throttle(struct uart_port *port) > +{ > + /* > + * Stop draining the RX FIFO to apply back-pressure. The RX time-out > + * interrupt must be disabled too, otherwise remaining FIFO data would > + * still be pushed out and defeat the flow control request. > + */ > + guard(uart_port_lock_irqsave)(port); > + wk2xxx_ier_clear(port, WK2XXX_SIER_RFTRIG_IEN_BIT | > + WK2XXX_SIER_RXOUT_IEN_BIT); Put on same line, as above... > +} > + > +static void wk2xxx_unthrottle(struct uart_port *port) > +{ > + guard(uart_port_lock_irqsave)(port); > + wk2xxx_ier_set(port, WK2XXX_SIER_RFTRIG_IEN_BIT | > + WK2XXX_SIER_RXOUT_IEN_BIT); > +} > + > +static void wk2xxx_handle_tx(struct uart_port *port) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + struct tty_port *tport = &port->state->port; > + unsigned int portno = port->iobase; > + unsigned int txlen, to_send, sent; > + const unsigned char *tail; > + u8 x_char, fsr, tfcnt; > + > + guard(mutex)(&one->tx_lock); > + > + /* > + * The serial core sets x_char and then calls start_tx() under the > + * port lock, so snapshot it under the same lock. > + */ > + scoped_guard(uart_port_lock_irqsave, port) { > + x_char = port->x_char; > + port->x_char = 0; > + > + /* Nothing queued and no x_char: nothing to do. */ > + if (!x_char && > + (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port))) { > + wk2xxx_stop_tx(port); > + return; > + } > + } > + > + if (x_char) { > + /* > + * A high-priority x_char goes out first, but do not return > + * here: pending xmit FIFO data still has to be drained (and > + * the TX trigger re-armed) below, otherwise it would be > + * stranded until the next start_tx(). > + */ > + wk2xxx_port_reg_write(s, portno, WK2XXX_FDAT_REG, x_char); > + scoped_guard(uart_port_lock_irqsave, port) > + port->icount.tx++; > + } > + > + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { > + scoped_guard(uart_port_lock_irqsave, port) { > + wk2xxx_stop_tx(port); > + } > + return; > + } > + > + /* Limit to the free space available in the TX FIFO. */ > + if (wk2xxx_port_reg_read(s, portno, WK2XXX_TFCNT_REG, &tfcnt)) > + return; Empty line > + if (tfcnt == 0) { > + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) > + return; > + txlen = (fsr & WK2XXX_FSR_TFULL_BIT) ? 0 : WK2XXX_FIFO_SIZE; > + } else { > + txlen = WK2XXX_FIFO_SIZE - tfcnt; > + } Empty line > + if (txlen > WK2XXX_MAX_TX_CHARS) > + txlen = WK2XXX_MAX_TX_CHARS; > + > + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); > + sent = 0; > + while (to_send) { > + unsigned int chunk = min_t(unsigned int, to_send, > + WK2XXX_MAX_SPI_LEN); > + > + if (wk2xxx_fifo_write(s, portno, tail, chunk)) > + break; > + tail += chunk; > + to_send -= chunk; > + sent += chunk; > + } > + uart_xmit_advance(port, sent); > + > + scoped_guard(uart_port_lock_irqsave, port) { > + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) > + uart_write_wakeup(port); > + > + if (kfifo_is_empty(&tport->xmit_fifo)) > + wk2xxx_stop_tx(port); > + else > + wk2xxx_ier_set(port, WK2XXX_SIER_TFTRIG_IEN_BIT); > + } > +} > + > +static void wk2xxx_handle_rx(struct uart_port *port) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + unsigned int i, rxlen, len_p, chunk; > + u8 fsr = 0, rfcnt = 0, lsr = 0, flag = TTY_NORMAL; > + > + if (wk2xxx_port_reg_read(s, portno, WK2XXX_FSR_REG, &fsr)) > + return; > + > + if (!(fsr & WK2XXX_FSR_RDAT_BIT)) > + return; > + > + /* Get the number of bytes available in the RX FIFO. */ > + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) > + return; Empty line... > + if (rfcnt == 0) { > + /* The count may race with the FIFO status bit; retry once. */ > + if (wk2xxx_port_reg_read(s, portno, WK2XXX_RFCNT_REG, &rfcnt)) > + return; > + rxlen = rfcnt ? rfcnt : WK2XXX_FIFO_SIZE; > + } else { > + rxlen = rfcnt; > + } > + > + /* Read the FIFO contents in chunks. */ > + len_p = 0; > + while (rxlen) { > + chunk = min_t(unsigned int, rxlen, WK2XXX_MAX_SPI_LEN); > + if (wk2xxx_fifo_read(s, portno, one->buf + len_p, chunk)) > + break; > + len_p += chunk; > + rxlen -= chunk; > + } > + rxlen = len_p; > + > + /* Map the FIFO status register error flags to line status. */ > + if (fsr & WK2XXX_FSR_ERR_MASK) { > + if (fsr & WK2XXX_FSR_RFPE_BIT) { > + port->icount.parity++; > + lsr |= WK2XXX_LSR_PE_BIT; > + flag = TTY_PARITY; > + } > + if (fsr & WK2XXX_FSR_RFFE_BIT) { > + port->icount.frame++; > + lsr |= WK2XXX_LSR_FE_BIT; > + flag = TTY_FRAME; > + } > + if (fsr & WK2XXX_FSR_RFOE_BIT) { > + port->icount.overrun++; > + lsr |= WK2XXX_LSR_OE_BIT; > + flag = TTY_OVERRUN; > + } > + if (fsr & WK2XXX_FSR_RFBI_BIT) { > + port->icount.brk++; > + lsr |= WK2XXX_LSR_BI_BIT; > + flag = TTY_BREAK; > + } > + } > + > + port->icount.rx += rxlen; > + > + /* CREAD is clear: drain the FIFO and drop all received data. */ > + if (port->ignore_status_mask & WK2XXX_LSR_IGNORE_DATA) > + return; > + > + for (i = 0; i < rxlen; ++i) { > + u8 ch = one->buf[i]; > + > + if (uart_handle_sysrq_char(port, ch)) > + continue; > + > + if (lsr & port->ignore_status_mask) > + continue; > + > + uart_insert_char(port, lsr, WK2XXX_LSR_OE_BIT, ch, flag); > + } > + > + tty_flip_buffer_push(&port->state->port); > +} > + > +static bool wk2xxx_port_irq(struct wk2xxx_port *s, unsigned int portno) > +{ > + struct uart_port *port = &s->p[portno].port; > + u8 sifr = 0, sier = 0; > + bool rc = false; > + > + if (wk2xxx_port_reg_read(s, portno, WK2XXX_SIFR_REG, &sifr) || > + wk2xxx_port_reg_read(s, portno, WK2XXX_SIER_REG, &sier)) > + return false; > + > + if (sifr & (WK2XXX_SIFR_RFTRIG_INT_BIT | WK2XXX_SIFR_RXOVT_INT_BIT)) { > + wk2xxx_handle_rx(port); > + rc = true; > + } > + > + if ((sifr & WK2XXX_SIFR_TFTRIG_INT_BIT) && > + (sier & WK2XXX_SIER_TFTRIG_IEN_BIT)) { > + wk2xxx_handle_tx(port); > + rc = true; > + } > + > + return rc; > +} > + > +static irqreturn_t wk2xxx_irq(int irq, void *dev_id) > +{ > + struct wk2xxx_port *s = dev_id; > + bool handled = false; > + bool keep_polling; > + int passes = WK2XXX_IRQ_MAX_PASSES; > + > + do { > + u8 gifr; > + int i; > + > + keep_polling = false; > + > + if (wk2xxx_reg_read(s, WK2XXX_GIFR_REG, &gifr)) > + return IRQ_NONE; /* Bus error; spurious handling applies. */ > + > + if (!gifr) > + break; > + > + handled = true; > + > + for (i = 0; i < s->devtype->nr_uart; ++i) > + if (gifr & BIT(i)) > + keep_polling |= wk2xxx_port_irq(s, i); > + } while (keep_polling && !s->polling && --passes); > + > + return handled ? IRQ_HANDLED : IRQ_NONE; > +} > + > +static void wk2xxx_poll_proc(struct kthread_work *ws) > +{ > + struct wk2xxx_port *s = container_of(ws, struct wk2xxx_port, > + poll_work.work); > + > + /* Reuse the IRQ handler; the interrupt ID is unused here. */ > + wk2xxx_irq(0, s); > + > + /* > + * Only keep polling while at least one port is open. The last > + * shutdown cancels the pending instance (see wk2xxx_shutdown); a > + * poll that is already running when that happens must not re-queue > + * itself, otherwise it would outlive the cancelled one. > + */ > + if (atomic_read(&s->open_ports) > 0) > + kthread_queue_delayed_work(&s->kworker, &s->poll_work, > + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); > +} > + > +static void wk2xxx_tx_proc(struct kthread_work *ws) > +{ > + struct uart_port *port = &(to_wk2xxx_one(ws, tx_work)->port); > + > + wk2xxx_handle_tx(port); > +} > + > +static void wk2xxx_start_tx(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + > + kthread_queue_work(&s->kworker, &one->tx_work); > +} > + > +static void wk2xxx_reconf_rs485(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned long irqflags; > + u8 rs485 = 0; > + > + /* > + * TIOCSRS485 updates port->rs485 under the port lock, so read the > + * flags under the same lock before programming the chip. > + */ > + uart_port_lock_irqsave(port, &irqflags); > + if (port->rs485.flags & SER_RS485_ENABLED) { > + rs485 = WK2XXX_RS485_RSRS485_BIT | WK2XXX_RS485_RTSEN_BIT; > + if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND) > + rs485 |= WK2XXX_RS485_RTSINV_BIT; > + } > + uart_port_unlock_irqrestore(port, irqflags); > + > + wk2xxx_port_reg_write(s, port->iobase, WK2XXX_RS485_REG, rs485); > +} > + > +static int wk2xxx_config_rs485(struct uart_port *port, struct ktermios *termios, > + struct serial_rs485 *rs485) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(port, port); > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + > + if (!s->devtype->has_rs485) > + return -EOPNOTSUPP; > + > + /* > + * RTS is driven by hardware and its timing cannot be influenced > + * from the driver. Non-zero RTS delays are rejected (sanitized to > + * zero) by the serial core. > + */ > + one->config.flags |= WK2XXX_RECONF_RS485; > + kthread_queue_work(&s->kworker, &one->reg_work); > + > + return 0; > +} > + > +static void wk2xxx_reg_proc(struct kthread_work *ws) > +{ > + struct wk2xxx_one *one = to_wk2xxx_one(ws, reg_work); > + struct wk2xxx_port *s = dev_get_drvdata(one->port.dev); > + struct wk2xxx_one_config config; > + unsigned long irqflags; > + > + uart_port_lock_irqsave(&one->port, &irqflags); > + config = one->config; > + memset(&one->config, 0, sizeof(one->config)); > + uart_port_unlock_irqrestore(&one->port, irqflags); > + > + if (config.flags & WK2XXX_RECONF_IER) > + wk2xxx_port_reg_update(s, one->port.iobase, WK2XXX_SIER_REG, > + config.ier_mask, config.ier_val); > + > + if (config.flags & WK2XXX_RECONF_RS485) > + wk2xxx_reconf_rs485(&one->port); > +} > + > +static unsigned int wk2xxx_tx_empty(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + u8 fsr = 0; > + > + if (wk2xxx_port_reg_read(s, port->iobase, WK2XXX_FSR_REG, &fsr)) > + return TIOCSER_TEMT; > + > + return (fsr & (WK2XXX_FSR_TDAT_BIT | WK2XXX_FSR_TBUSY_BIT)) ? 0 : > + TIOCSER_TEMT; > +} > + > +static unsigned int wk2xxx_get_mctrl(struct uart_port *port) > +{ > + /* The WK2xxx does not expose modem control lines. */ > + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; > +} > + > +static void wk2xxx_set_mctrl(struct uart_port *port, unsigned int mctrl) > +{ > + /* The WK2xxx does not support modem control lines. */ > +} > + > +static void wk2xxx_enable_ms(struct uart_port *port) > +{ > + /* The WK2xxx does not have modem status registers. */ > +} > + > +static void wk2xxx_break_ctl(struct uart_port *port, int break_state) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + > + wk2xxx_port_reg_update(s, port->iobase, WK2XXX_LCR_REG, > + WK2XXX_LCR_BREAK_BIT, > + break_state ? WK2XXX_LCR_BREAK_BIT : 0); > +} > + > +/* > + * Configure a sub-UART: disable interrupts and TX/RX, program the line > + * control and baud rate registers and restore the previous state. > + */ > +static void wk2xxx_conf_port(struct uart_port *port, u8 lcr, u8 fwcr, > + u8 baud0, u8 baud1, u8 pres) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + u8 sier, scr, fsr; > + int count = 200; Where does this 200 limit come from? > + > + guard(mutex)(&s->reg_lock); > + > + /* Disable all sub-UART interrupts. */ > + wk2xxx_raw_port_read(s, portno, WK2XXX_SIER_REG, &sier); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); > + > + /* Wait for the transmitter to become idle. */ > + do { > + wk2xxx_raw_port_read(s, portno, WK2XXX_FSR_REG, &fsr); > + } while ((fsr & WK2XXX_FSR_TBUSY_BIT) && count--); > + > + /* Disable the transmitter and receiver. */ > + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, &scr); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, > + scr & ~(WK2XXX_SCR_TXEN_BIT | > + WK2XXX_SCR_RXEN_BIT)); Same line (100 columns)... > + > + /* Program the line control register. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_LCR_REG, lcr); > + > + /* Configure hardware flow control levels. */ > + if (fwcr) { > + wk2xxx_raw_port_write(s, portno, WK2XXX_FWCR_REG, fwcr); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTH_REG, 0xf0); > + wk2xxx_raw_port_write(s, portno, WK2XXX_FWTL_REG, 0x80); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); > + } > + > + /* Program the baud rate generator (page 1 registers). */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD0_REG, baud0); > + wk2xxx_raw_port_write(s, portno, WK2XXX_BAUD1_REG, baud1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_PRES_REG, pres); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); > + > + /* Re-enable the transmitter and receiver. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, > + scr | (WK2XXX_SCR_TXEN_BIT | > + WK2XXX_SCR_RXEN_BIT)); Same line (100 columns)... > + > + /* Restore the interrupt enable register. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, sier); > +} > + > +static void wk2xxx_calc_divisor(unsigned long clk, unsigned int baud, > + u8 *baud0, u8 *baud1, u8 *pres) > +{ > + unsigned int div, rem; > + > + /* Never divide by zero; the serial core normally prevents this. */ > + if (baud == 0) > + baud = 9600; > + > + div = clk / (baud * 16); > + if (div == 0) > + div = 1; > + div--; > + *baud0 = div & 0xff; > + *baud1 = (div >> 8) & 0xff; > + > + rem = clk % (baud * 16); > + *pres = (u32)div_u64((u64)rem * 100, baud); > + *pres = (*pres + 50) / 100; > +} > + > +static void wk2xxx_set_termios(struct uart_port *port, struct ktermios *termios, > + const struct ktermios *old) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int baud, read_mask, ignore_mask; > + u8 lcr = 0, fwcr = 0; > + u8 baud0, baud1, pres; > + > + /* The WK2xxx supports 8 data bits only. */ > + termios->c_cflag &= ~CSIZE; > + termios->c_cflag |= CS8; > + > + /* Parity. */ > + if (termios->c_cflag & PARENB) { > + lcr |= WK2XXX_LCR_PAEN_BIT; > + switch (termios->c_cflag & (PARODD | CMSPAR)) { > + case 0: > + lcr |= WK2XXX_LCR_PAM1_BIT; /* even */ > + break; > + case PARODD: > + lcr |= WK2XXX_LCR_PAM0_BIT; /* odd */ > + break; > + case CMSPAR: > + break; /* space */ > + case PARODD | CMSPAR: > + lcr |= WK2XXX_LCR_PAM1_BIT | > + WK2XXX_LCR_PAM0_BIT; /* mark */ > + break; > + } > + } > + > + /* Stop bits. */ > + if (termios->c_cflag & CSTOPB) > + lcr |= WK2XXX_LCR_STPL_BIT; > + > + /* Determine the status masks to publish. */ > + read_mask = WK2XXX_LSR_OE_BIT; > + if (termios->c_iflag & INPCK) > + read_mask |= WK2XXX_LSR_PE_BIT | WK2XXX_LSR_FE_BIT; Empty line > + if (termios->c_iflag & (BRKINT | PARMRK)) > + read_mask |= WK2XXX_LSR_BI_BIT; > + > + ignore_mask = 0; > + if (termios->c_iflag & IGNBRK) > + ignore_mask |= WK2XXX_LSR_BI_BIT; Empty line > + if (!(termios->c_cflag & CREAD)) > + ignore_mask |= WK2XXX_LSR_BRK_ERROR_MASK | > + WK2XXX_LSR_IGNORE_DATA; > + > + /* The two-channel variants do not implement hardware flow control. */ > + if (!s->devtype->has_hw_flow_control) > + termios->c_cflag &= ~CRTSCTS; > + > + /* Hardware flow control is configured in the chip below. */ > + if (s->devtype->has_hw_flow_control && (termios->c_cflag & CRTSCTS)) > + fwcr = WK2XXX_FWCR_FWM_RTS_CTS; > + > + /* Get the baud rate generator configuration. */ > + baud = uart_get_baud_rate(port, termios, old, > + port->uartclk / 16 / 0xffff, > + port->uartclk / 16); > + > + wk2xxx_calc_divisor(port->uartclk, baud, &baud0, &baud1, &pres); > + wk2xxx_conf_port(port, lcr, fwcr, baud0, baud1, pres); > + > + /* > + * Publish the masks and flow-control status under the port lock; the > + * RX/TX paths read them from their kthread context. > + */ > + guard(uart_port_lock_irqsave)(port); > + port->read_status_mask = read_mask; > + port->ignore_status_mask = ignore_mask; > + port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); > + if (s->devtype->has_hw_flow_control && (termios->c_cflag & CRTSCTS)) > + port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; > + uart_update_timeout(port, termios->c_cflag, baud); > +} > + > +static int wk2xxx_startup(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + u8 reg; > + > + scoped_guard(mutex, &s->reg_lock) { > + /* > + * GENA and GIER are shared by all ports on the chip: a failed > + * read must not be turned into a write of only this port's > + * bit, which would clear the sibling ports' bits. > + */ > + if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®)) > + return -EIO; > + reg |= BIT(portno); > + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); > + > + /* Reset the sub-UART. */ > + wk2xxx_raw_write(s, WK2XXX_GRST_REG, BIT(portno)); > + > + /* Enable the sub-UART interrupt in the global mask. */ > + if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®)) > + return -EIO; > + reg |= BIT(portno); > + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); > + > + /* Enable RX FIFO trigger and RX time-out interrupts. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, > + WK2XXX_SIER_RFTRIG_IEN_BIT | > + WK2XXX_SIER_RXOUT_IEN_BIT); > + > + /* Enable the transmitter and receiver. */ > + wk2xxx_raw_port_read(s, portno, WK2XXX_SCR_REG, ®); > + reg |= WK2XXX_SCR_TXEN_BIT | WK2XXX_SCR_RXEN_BIT; > + wk2xxx_raw_port_write(s, portno, WK2XXX_SCR_REG, reg); > + > + /* Reset and configure the FIFOs. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xff); > + wk2xxx_raw_port_write(s, portno, WK2XXX_FCR_REG, 0xfc); > + > + /* Set the RX/TX FIFO trigger levels. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 1); > + wk2xxx_raw_port_write(s, portno, WK2XXX_RFTL_REG, > + WK2XXX_RXFIFO_LEVEL); > + wk2xxx_raw_port_write(s, portno, WK2XXX_TFTL_REG, > + WK2XXX_TXFIFO_LEVEL); > + wk2xxx_raw_port_write(s, portno, WK2XXX_SPAGE_REG, 0); > + } > + > + kfifo_reset(&port->state->port.xmit_fifo); > + > + /* Start the shared polling loop when the first port is opened. */ > + if (s->polling) { > + guard(mutex)(&s->poll_lock); > + if (atomic_inc_return(&s->open_ports) == 1) > + kthread_queue_delayed_work(&s->kworker, &s->poll_work, > + msecs_to_jiffies(WK2XXX_POLL_PERIOD_MS)); > + } > + > + return 0; > +} > + > +static void wk2xxx_shutdown(struct uart_port *port) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(port->dev); > + unsigned int portno = port->iobase; > + u8 reg; > + > + scoped_guard(mutex, &s->reg_lock) { > + /* Disable the sub-UART interrupt in the global mask. */ > + if (wk2xxx_raw_read(s, WK2XXX_GIER_REG, ®)) > + return; > + reg &= ~BIT(portno); > + wk2xxx_raw_write(s, WK2XXX_GIER_REG, reg); > + > + /* Disable all sub-UART interrupts. */ > + wk2xxx_raw_port_write(s, portno, WK2XXX_SIER_REG, 0); > + > + /* Reset the sub-UART. */ > + wk2xxx_raw_read(s, WK2XXX_GRST_REG, ®); > + reg |= BIT(portno); > + wk2xxx_raw_write(s, WK2XXX_GRST_REG, reg); > + > + /* Disable the sub-UART. */ > + if (wk2xxx_raw_read(s, WK2XXX_GENA_REG, ®)) > + return; > + reg &= ~BIT(portno); > + wk2xxx_raw_write(s, WK2XXX_GENA_REG, reg); > + } > + > + /* > + * Stop the shared polling loop once the last port is closed. The > + * check and the cancel are serialized by poll_lock against a > + * concurrent open of another port, so the two cannot tear the > + * open_ports 0/1 boundary in a way that leaves the loop cancelled > + * while a port is still open. > + */ > + if (s->polling) { > + guard(mutex)(&s->poll_lock); > + if (atomic_dec_return(&s->open_ports) == 0) > + kthread_cancel_delayed_work_sync(&s->poll_work); > + } > + > + kthread_flush_worker(&s->kworker); > +} > + > +static const char *wk2xxx_type(struct uart_port *port) > +{ > + return (port->type == PORT_WK2XXX) ? WK2XXX_NAME : NULL; > +} > + > +static void wk2xxx_config_port(struct uart_port *port, int flags) > +{ > + if (flags & UART_CONFIG_TYPE) > + port->type = PORT_WK2XXX; > +} > + > +static int wk2xxx_verify_port(struct uart_port *port, struct serial_struct *s) > +{ > + if (s->type != PORT_UNKNOWN && s->type != PORT_WK2XXX) > + return -EINVAL; > + if (s->irq != port->irq) > + return -EINVAL; > + > + return 0; > +} > + > +static const struct uart_ops wk2xxx_ops = { > + .tx_empty = wk2xxx_tx_empty, > + .set_mctrl = wk2xxx_set_mctrl, > + .get_mctrl = wk2xxx_get_mctrl, > + .stop_tx = wk2xxx_stop_tx, > + .start_tx = wk2xxx_start_tx, > + .throttle = wk2xxx_throttle, > + .unthrottle = wk2xxx_unthrottle, > + .stop_rx = wk2xxx_stop_rx, > + .enable_ms = wk2xxx_enable_ms, > + .break_ctl = wk2xxx_break_ctl, > + .startup = wk2xxx_startup, > + .shutdown = wk2xxx_shutdown, > + .set_termios = wk2xxx_set_termios, > + .type = wk2xxx_type, > + .config_port = wk2xxx_config_port, > + .verify_port = wk2xxx_verify_port, > +}; > + > +static const struct serial_rs485 wk2xxx_rs485_supported = { > + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | > + SER_RS485_RTS_AFTER_SEND, > + /* RTS timing is hardware-driven; RTS delays are not supported. */ > +}; > + > +static int wk2xxx_probe(struct spi_device *spi) > +{ > + const struct wk2xxx_devtype *devtype; > + struct device *dev = &spi->dev; > + struct wk2xxx_port *s; > + unsigned long uartclk; > + bool port_registered[WK2XXX_MAX_PORTS]; > + u8 val; > + int i, ret; Use reverse-xmax ordering... > + > + /* Setup SPI bus. The SPI mode follows the device tree (spi-cpha, > + * spi-cpol); it defaults to SPI mode 0 when unspecified. > + */ > + spi->bits_per_word = 8; > + spi->max_speed_hz = spi->max_speed_hz ? : 10 * HZ_PER_MHZ; > + ret = spi_setup(spi); > + if (ret) > + return ret; > + > + devtype = spi_get_device_match_data(spi); > + if (!devtype) > + return dev_err_probe(dev, -ENODEV, "Failed to match device\n"); > + > + /* Allocate port structure. */ > + s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); > + if (!s) > + return dev_err_probe(dev, -ENOMEM, > + "Error allocating port structure\n"); > + > + s->devtype = devtype; > + s->spi = spi; > + mutex_init(&s->reg_lock); > + mutex_init(&s->poll_lock); > + atomic_set(&s->open_ports, 0); > + dev_set_drvdata(dev, s); > + > + /* > + * The WK2xxx has no identification register, so the best we can do > + * is to check that communication is at all possible. > + */ > + ret = wk2xxx_reg_read(s, WK2XXX_GENA_REG, &val); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to read GENA register\n"); > + > + /* The reference clock (crystal or external clock input) is mandatory. */ > + s->clk = devm_clk_get_enabled(dev, NULL); > + if (IS_ERR(s->clk)) > + return dev_err_probe(dev, PTR_ERR(s->clk), > + "Failed to get the reference clock\n"); > + > + uartclk = clk_get_rate(s->clk); > + if (!uartclk) > + return dev_err_probe(dev, -EINVAL, > + "Clock rate must not be zero\n"); > + > + /* Mark each port line and status as uninitialized. */ > + for (i = 0; i < devtype->nr_uart; ++i) { > + s->p[i].port.line = WK2XXX_MAX_DEVS; > + port_registered[i] = false; > + } > + > + kthread_init_worker(&s->kworker); > + s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker, > + "wk2xxx"); > + if (IS_ERR(s->kworker_task)) { > + ret = PTR_ERR(s->kworker_task); > + goto out_ports; > + } > + sched_set_fifo(s->kworker_task); > + > + /* > + * Reset the chip and disable every sub-UART and its interrupt before > + * the ports are registered (and, in interrupt mode, before the IRQ is > + * requested). The sub-UARTs stay disabled until a port is opened in > + * wk2xxx_startup(), so no stale pending condition can raise the IRQ > + * line while the ports are being set up. > + */ > + wk2xxx_reg_write(s, WK2XXX_GRST_REG, (1 << devtype->nr_uart) - 1); > + wk2xxx_reg_write(s, WK2XXX_GENA_REG, 0); > + wk2xxx_reg_write(s, WK2XXX_GIER_REG, 0); > + > + if (spi->irq <= 0) { > + /* Poll the device instead of using interrupts. */ > + s->polling = true; > + kthread_init_delayed_work(&s->poll_work, wk2xxx_poll_proc); > + } > + > + for (i = 0; i < devtype->nr_uart; ++i) { > + struct fwnode_handle *saved_fwnode = dev_fwnode(dev); > + struct device_node *port_np = NULL; > + struct device_node *child; > + > + ret = ida_alloc_max(&wk2xxx_lines, WK2XXX_MAX_DEVS - 1, > + GFP_KERNEL); > + if (ret < 0) > + goto out_ports; > + > + s->p[i].port.line = ret; > + > + /* Locate the matching "serial@i" DT subnode, if any. */ > + for_each_available_child_of_node(dev->of_node, child) { > + u32 reg; > + > + if (!of_node_name_eq(child, "serial")) > + continue; > + if (of_property_read_u32(child, "reg", ®)) > + continue; > + if (reg == i) { > + port_np = child; > + break; > + } > + } > + > + /* Initialize port data. */ > + s->p[i].port.dev = dev; > + s->p[i].port.irq = spi->irq; > + s->p[i].port.type = PORT_WK2XXX; > + s->p[i].port.fifosize = WK2XXX_FIFO_SIZE; > + s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; > + s->p[i].port.iobase = i; > + s->p[i].port.iotype = UPIO_BUS; > + s->p[i].port.uartclk = uartclk; > + if (devtype->has_rs485) { > + s->p[i].port.rs485_config = wk2xxx_config_rs485; > + s->p[i].port.rs485_supported = wk2xxx_rs485_supported; > + } > + s->p[i].port.ops = &wk2xxx_ops; > + > + mutex_init(&s->p[i].tx_lock); > + > + kthread_init_work(&s->p[i].tx_work, wk2xxx_tx_proc); > + kthread_init_work(&s->p[i].reg_work, wk2xxx_reg_proc); > + > + /* > + * Temporarily retarget dev's fwnode to the per-port subnode > + * so uart_get_rs485_mode() picks up the per-port properties. > + */ > + if (port_np && devtype->has_rs485) { > + device_set_node(dev, of_fwnode_handle(port_np)); > + ret = uart_get_rs485_mode(&s->p[i].port); > + device_set_node(dev, saved_fwnode); > + of_node_put(port_np); > + if (ret) > + goto out_ports; > + } > + > + /* Register port. */ > + ret = uart_add_one_port(&wk2xxx_uart, &s->p[i].port); > + if (ret) > + goto out_ports; > + > + port_registered[i] = true; > + } > + > + /* > + * Request the IRQ only after every port is registered so that an early > + * interrupt can never reach a port whose port->state is not ready yet. > + * We first try to acquire the IRQ line as a level IRQ; if that > + * succeeds, we can allow sharing the interrupt as well. In case the > + * interrupt controller doesn't support that, we fall back to a > + * non-shared falling-edge trigger. > + */ > + if (!s->polling) { > + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, > + IRQF_TRIGGER_LOW | IRQF_SHARED | > + IRQF_ONESHOT, dev_name(dev), s); Why not using devm_ to drop irq_requested variable and irq cleanup in remove()? > + if (ret) Why not issue, like sc16is7xx: if (!ret) return 0; ? > + ret = request_threaded_irq(spi->irq, NULL, wk2xxx_irq, > + IRQF_TRIGGER_FALLING | > + IRQF_ONESHOT, > + dev_name(dev), s); > + if (ret) { > + dev_err(dev, "Unable to request IRQ %i\n", spi->irq); > + goto out_ports; > + } Similar here? > + s->irq_requested = true; > + } > + > + return 0; > + > +out_ports: > + if (s->irq_requested) > + free_irq(spi->irq, s); > + > + for (i = 0; i < devtype->nr_uart; i++) { > + if (port_registered[i]) > + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); > + if (s->p[i].port.line < WK2XXX_MAX_DEVS) > + ida_free(&wk2xxx_lines, s->p[i].port.line); > + } > + > + if (!IS_ERR(s->kworker_task)) > + kthread_stop(s->kworker_task); > + > + return ret; > +} > + > +static void wk2xxx_remove(struct spi_device *spi) > +{ > + struct wk2xxx_port *s = dev_get_drvdata(&spi->dev); > + int i; > + > + /* > + * Unregister the ports first. Removing a port that is still open > + * hangs up its tty and runs wk2xxx_shutdown(), which disables the > + * sub-UART in the chip and drains the shared worker, so afterwards no > + * enabled interrupt source (and thus no queued TX/register work) can > + * target a port that is being torn down. > + */ > + for (i = 0; i < s->devtype->nr_uart; i++) { > + uart_remove_one_port(&wk2xxx_uart, &s->p[i].port); > + ida_free(&wk2xxx_lines, s->p[i].port.line); > + } > + > + /* > + * Every sub-UART is disabled now, so the chip can no longer raise the > + * IRQ line. Free the IRQ; a handler already in flight only observes > + * disabled ports and returns IRQ_NONE. > + */ > + if (s->irq_requested) > + free_irq(spi->irq, s); > + > + if (s->polling) > + kthread_cancel_delayed_work_sync(&s->poll_work); > + > + kthread_flush_worker(&s->kworker); > + kthread_stop(s->kworker_task); > +} > + > +static const struct of_device_id wk2xxx_dt_ids[] = { > + { .compatible = "wkmic,wk2124", .data = &wk2124_devtype }, > + { .compatible = "wkmic,wk2132", .data = &wk2132_devtype }, > + { .compatible = "wkmic,wk2168", .data = &wk2168_devtype }, > + { .compatible = "wkmic,wk2202", .data = &wk2202_devtype }, > + { .compatible = "wkmic,wk2204", .data = &wk2204_devtype }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, wk2xxx_dt_ids); > + > +static const struct spi_device_id wk2xxx_id_table[] = { > + { "wk2124", (kernel_ulong_t)&wk2124_devtype }, > + { "wk2132", (kernel_ulong_t)&wk2132_devtype }, > + { "wk2168", (kernel_ulong_t)&wk2168_devtype }, > + { "wk2202", (kernel_ulong_t)&wk2202_devtype }, > + { "wk2204", (kernel_ulong_t)&wk2204_devtype }, > + { } > +}; > +MODULE_DEVICE_TABLE(spi, wk2xxx_id_table); > + > +static struct spi_driver wk2xxx_spi_driver = { > + .driver = { > + .name = WK2XXX_NAME, > + .of_match_table = wk2xxx_dt_ids, > + }, > + .probe = wk2xxx_probe, > + .remove = wk2xxx_remove, > + .id_table = wk2xxx_id_table, > +}; > + > +static int __init wk2xxx_init(void) > +{ > + int ret; > + > + ret = uart_register_driver(&wk2xxx_uart); > + if (ret) > + return ret; > + > + ret = spi_register_driver(&wk2xxx_spi_driver); > + if (ret) > + uart_unregister_driver(&wk2xxx_uart); > + > + return ret; > +} > +module_init(wk2xxx_init); > + > +static void __exit wk2xxx_exit(void) > +{ > + spi_unregister_driver(&wk2xxx_spi_driver); > + uart_unregister_driver(&wk2xxx_uart); > +} > +module_exit(wk2xxx_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Xuxunwei"); > +MODULE_AUTHOR("B. Eschrich"); > +MODULE_AUTHOR("Zi Jie Zhao <zjzhao@edatec.cn>"); > +MODULE_DESCRIPTION("WK2xxx SPI UART driver"); > diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h > index 377884e3856a..05c75d3ef479 100644 > --- a/include/uapi/linux/serial_core.h > +++ b/include/uapi/linux/serial_core.h > @@ -234,6 +234,9 @@ > /* Sunplus UART */ > #define PORT_SUNPLUS 123 > > +/* WK2xxx SPI to UART bridge */ > +#define PORT_WK2XXX 124 > + > /* Generic type identifier for ports which type is not important to userspace. */ > #define PORT_GENERIC (-1) > > -- > 2.43.0 > > -- Hugo Villeneuve ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-08 18:35 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-08 10:31 [PATCH v4 0/2] WK2xxx SPI to UART bridge driver zjzhao 2026-09-08 10:31 ` [PATCH v4 1/2] dt-bindings: serial: Document WK2xxx SPI UART zjzhao 2026-09-08 10:38 ` sashiko-bot 2026-09-08 17:49 ` Conor Dooley 2026-09-08 10:31 ` [PATCH v4 2/2] serial: wk2xxx: Add WK2xxx SPI UART driver zjzhao 2026-09-08 10:44 ` sashiko-bot 2026-09-08 17:51 ` Hugo Villeneuve
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).