* Re: [PATCH v3 06/15] drm/tidss: Remove extra pm_runtime_mark_last_busy
From: Swamil Jain @ 2026-06-24 10:39 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-6-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> pm_runtime_put_autosuspend() calls pm_runtime_mark_last_busy(), so no
> need to call pm_runtime_mark_last_busy() explicitly in the driver.
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_drv.c | 2 --
> 1 file changed, 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
> index 1c8cc18bc53c..5cb3e746aeb3 100644
> --- a/drivers/gpu/drm/tidss/tidss_drv.c
> +++ b/drivers/gpu/drm/tidss/tidss_drv.c
> @@ -42,8 +42,6 @@ void tidss_runtime_put(struct tidss_device *tidss)
> {
> int r;
>
> - pm_runtime_mark_last_busy(tidss->dev);
> -
> r = pm_runtime_put_autosuspend(tidss->dev);
> WARN_ON(r < 0);
> }
>
^ permalink raw reply
* [PATCH 2/2] arm64: dts: axiado: Add initial support for AX3005 SoC and eval board
From: Swark Yang @ 2026-06-24 10:21 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: devicetree, linux-arm-kernel, linux-kernel, Swark Yang
In-Reply-To: <20260624-upstream-axiado-ax3005-upstream-v1-0-c05bd0bc9124@axiado.com>
Add initial device tree support for the AX3005 SoC and its evaluation
board. The AX3005 is a multi-core SoC featuring 4 Cortex-A53 cores, and
this adds the CPUs, timer, GPIO, UART, I2C, I3C, SPI and USB
controllers.
The AX3005 groups its low-speed controllers into four Slow Peripheral
(SP) blocks (SP0-SP3), each exposing its own I2C/I3C, SPI and UART
instances. The controllers are numbered by their per-SP hardware
instance ID, so the i2cN/spiN/uartN labels and their aliases are
intentionally non-contiguous and do not increase monotonically with
the register address.
Signed-off-by: Swark Yang <syang@axiado.com>
---
arch/arm64/boot/dts/axiado/Makefile | 1 +
arch/arm64/boot/dts/axiado/ax3005-evk.dts | 327 ++++++++++++
arch/arm64/boot/dts/axiado/ax3005.dtsi | 843 ++++++++++++++++++++++++++++++
3 files changed, 1171 insertions(+)
diff --git a/arch/arm64/boot/dts/axiado/Makefile b/arch/arm64/boot/dts/axiado/Makefile
index 6676ad07db61..e71a0850a451 100644
--- a/arch/arm64/boot/dts/axiado/Makefile
+++ b/arch/arm64/boot/dts/axiado/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_ARCH_AXIADO) += ax3000-evk.dtb
+dtb-$(CONFIG_ARCH_AXIADO) += ax3005-evk.dtb
diff --git a/arch/arm64/boot/dts/axiado/ax3005-evk.dts b/arch/arm64/boot/dts/axiado/ax3005-evk.dts
new file mode 100644
index 000000000000..9187057eb7a5
--- /dev/null
+++ b/arch/arm64/boot/dts/axiado/ax3005-evk.dts
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DTS file for Axiado AX3005 SoC based EVK
+ * Copyright (c) 2026 Axiado Corporation.
+ */
+
+/dts-v1/;
+
+#include "ax3005.dtsi"
+
+/ {
+ model = "Axiado AX3005 EVK";
+ compatible = "axiado,ax3005-evk", "axiado,ax3005";
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ aliases {
+ serial0 = &uart0;
+ serial1 = &uart1;
+ serial2 = &uart2;
+ serial3 = &uart3;
+ serial4 = &uart4;
+ serial5 = &uart5;
+ serial6 = &uart6;
+ serial7 = &uart7;
+ serial8 = &uart8;
+ i2c0 = &i2c0;
+ i2c1 = &i2c1;
+ i2c2 = &i2c2;
+ i2c3 = &i2c3;
+ i2c4 = &i2c4;
+ i2c5 = &i2c5;
+ i2c8 = &i2c8;
+ i2c9 = &i2c9;
+ i2c10 = &i2c10;
+ i2c11 = &i2c11;
+ i2c12 = &i3c12;
+ i2c13 = &i3c13;
+ i2c14 = &i2c14;
+ i2c15 = &i2c15;
+ i2c16 = &i2c16;
+ i2c17 = &i2c17;
+ i2c18 = &i2c18;
+ i2c19 = &i2c19;
+ i2c20 = &i2c20;
+ i2c21 = &i2c21;
+ i2c22 = &i2c22;
+ i2c23 = &i2c23;
+ i2c24 = &i2c24;
+ i2c25 = &i2c25;
+ i2c26 = &i2c26;
+ i2c27 = &i2c27;
+ i2c28 = &i2c28;
+ i2c29 = &i2c29;
+ i2c30 = &i2c30;
+ i2c31 = &i2c31;
+ i2c32 = &i2c32;
+ i2c33 = &i2c33;
+ i2c34 = &i2c34;
+ i2c35 = &i2c35;
+ i2c36 = &i2c36;
+ spi0 = &spi0;
+ spi1 = &spi1;
+ spi2 = &spi2;
+ spi3 = &spi3;
+ spi5 = &spi5;
+ spi6 = &spi6;
+ };
+
+ chosen {
+ stdout-path = "serial3:115200";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ /* Cortex-A53 will use following memory map */
+ reg = <0x0 0x81000000 0x0 0x7f000000>;
+ };
+};
+
+&gpio0 {
+ status = "okay";
+};
+
+&gpio1 {
+ status = "okay";
+};
+
+&gpio2 {
+ status = "okay";
+};
+
+&gpio3 {
+ status = "okay";
+};
+
+&gpio4 {
+ status = "okay";
+};
+
+&gpio5 {
+ status = "okay";
+};
+
+&gpio6 {
+ status = "okay";
+};
+
+&gpio7 {
+ status = "okay";
+};
+
+&i2c0 {
+ status = "okay";
+};
+
+&i2c1 {
+ status = "okay";
+};
+
+&i2c2 {
+ status = "okay";
+};
+
+&i2c3 {
+ status = "okay";
+};
+
+&i2c4 {
+ status = "okay";
+};
+
+&i2c5 {
+ status = "okay";
+};
+
+&i2c8 {
+ status = "okay";
+};
+
+&i2c9 {
+ status = "okay";
+};
+
+&i2c10 {
+ status = "okay";
+};
+
+&i2c11 {
+ status = "okay";
+};
+
+&i3c12 {
+ status = "okay";
+};
+
+&i3c13 {
+ status = "okay";
+};
+
+&i2c14 {
+ status = "okay";
+};
+
+&i2c15 {
+ status = "okay";
+};
+
+&i2c16 {
+ status = "okay";
+};
+
+&i2c17 {
+ status = "okay";
+};
+
+&i2c18 {
+ status = "okay";
+};
+
+&i2c19 {
+ status = "okay";
+};
+
+&i2c20 {
+ status = "okay";
+};
+
+&i2c21 {
+ status = "okay";
+};
+
+&i2c22 {
+ status = "okay";
+};
+
+&i2c23 {
+ status = "okay";
+};
+
+&i2c24 {
+ status = "okay";
+};
+
+&i2c25 {
+ status = "okay";
+};
+
+&i2c26 {
+ status = "okay";
+};
+
+&i2c27 {
+ status = "okay";
+};
+
+&i2c28 {
+ status = "okay";
+};
+
+&i2c29 {
+ status = "okay";
+};
+
+&i2c30 {
+ status = "okay";
+};
+
+&i2c31 {
+ status = "okay";
+};
+
+&i2c32 {
+ status = "okay";
+};
+
+&i2c33 {
+ status = "okay";
+};
+
+&i2c34 {
+ status = "okay";
+};
+
+&i2c35 {
+ status = "okay";
+};
+
+&i2c36 {
+ status = "okay";
+};
+
+&spi0 {
+ status = "okay";
+};
+
+&spi1 {
+ status = "okay";
+};
+
+&spi2 {
+ status = "okay";
+};
+
+&spi3 {
+ status = "okay";
+};
+
+&spi5 {
+ status = "okay";
+};
+
+&spi6 {
+ status = "okay";
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&uart1 {
+ status = "okay";
+};
+
+&uart2 {
+ status = "okay";
+};
+
+&uart3 {
+ status = "okay";
+};
+
+&uart4 {
+ status = "okay";
+};
+
+&uart5 {
+ status = "okay";
+};
+
+&uart6 {
+ status = "okay";
+};
+
+&uart7 {
+ status = "okay";
+};
+
+&uart8 {
+ status = "okay";
+};
+
+&usb2_0 {
+ status = "okay";
+};
+
+&usb2_1 {
+ status = "okay";
+};
+
+&usb3_0 {
+ status = "okay";
+};
+
+&usb3_1 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/axiado/ax3005.dtsi b/arch/arm64/boot/dts/axiado/ax3005.dtsi
new file mode 100644
index 000000000000..3c2ce18082a9
--- /dev/null
+++ b/arch/arm64/boot/dts/axiado/ax3005.dtsi
@@ -0,0 +1,843 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2026 Axiado Corporation.
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+/memreserve/ 0x80002fa0 0x00000008;
+/ {
+ model = "Axiado AX3005";
+ interrupt-parent = <&gic500>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ cpus {
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ cpu0: cpu@0 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x0>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ cpu1: cpu@1 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x1>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ cpu2: cpu@2 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x2>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ cpu3: cpu@3 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x3>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ l2: l2-cache0 {
+ compatible = "cache";
+ cache-unified;
+ cache-size = <0x100000>;
+ cache-line-size = <64>;
+ cache-sets = <1024>;
+ cache-level = <2>;
+ };
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_PPI 14 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_PPI 11 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_PPI 10 IRQ_TYPE_LEVEL_HIGH>;
+ arm,cpu-registers-not-fw-configured;
+ };
+
+ clocks {
+ refclk: clock-125000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <125000000>;
+ };
+
+ pclk: clock-100000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ };
+
+ sysclk: clock-200000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>;
+ };
+
+ spiclk: clock-400000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <400000000>;
+ };
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-parent = <&gic500>;
+ ranges;
+
+ gic500: interrupt-controller@40400000 {
+ compatible = "arm,gic-v3";
+ reg = <0x0 0x40400000 0x0 0x10000>,
+ <0x0 0x40500000 0x0 0xc0000>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ #interrupt-cells = <3>;
+ interrupt-controller;
+ #redistributor-regions = <1>;
+ interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ /* GPIO Controller banks 0 - 7 */
+ gpio0: gpio-controller@33000000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33000000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio1: gpio-controller@33080000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33080000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio2: gpio-controller@33100000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33100000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio3: gpio-controller@33180000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33180000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio4: gpio-controller@33200000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33200000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio5: gpio-controller@33280000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33280000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio6: gpio-controller@33300000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33300000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio7: gpio-controller@33380000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33380000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ i2c0: i2c@33000400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33000400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c1: i2c@33000800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33000800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c2: i2c@33080400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33080400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c3: i2c@33080800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33080800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 79 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c4: i2c@33100400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33100400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c5: i2c@33100800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33100800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c8: i2c@33200400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33200400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c9: i2c@33200800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33200800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c10: i2c@33280400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33280400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c11: i2c@33280800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33280800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i3c12: i3c@33300400 {
+ compatible = "axiado,ax3000-i3c", "cdns,i3c-master";
+ reg = <0x0 0x33300400 0x0 0x400>;
+ clock-names = "pclk", "sysclk";
+ clocks = <&pclk &sysclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-scl-hz = <100000>;
+ i3c-scl-hz = <400000>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i3c13: i3c@33300800 {
+ compatible = "axiado,ax3000-i3c", "cdns,i3c-master";
+ reg = <0x0 0x33300800 0x0 0x400>;
+ clock-names = "pclk", "sysclk";
+ clocks = <&pclk &sysclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-scl-hz = <100000>;
+ i3c-scl-hz = <400000>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c14: i2c@33380400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33380400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c15: i2c@33380800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33380800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c16: i2c@33120400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33120400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c17: i2c@33021400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33021400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 213 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c18: i2c@33021800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33021800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 214 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c19: i2c@330c0400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x330c0400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 215 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c20: i2c@330c0800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x330c0800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 216 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c21: i2c@330c0c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x330c0c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 217 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c22: i2c@331a0800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x331a0800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 218 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c23: i2c@33302800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33302800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 219 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c24: i2c@33302c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33302c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c25: i2c@33303000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33303000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c26: i2c@33382000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c27: i2c@33382400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c28: i2c@33382800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c29: i2c@33382c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 225 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c30: i2c@33383000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33383000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c31: i2c@33383400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33383400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 227 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c32: i2c@33383800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33383800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 228 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c33: i2c@33282400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33282400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 238 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c34: i2c@33282800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33282800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 239 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c35: i2c@33282c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33282c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 240 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c36: i2c@33283000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33283000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 241 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ spi0: spi@33010000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x33010000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <2>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi1: spi@33090000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x33090000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi2: spi@333c0000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x333c0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi3: spi@330e0000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x330e0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi5: spi@33390000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x33390000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <2>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi6: spi@333a0000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x333a0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ uart0: serial@33020000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33020000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart1: serial@330a0000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x330a0000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart2: serial@33120000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33120000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart3: serial@33020800 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33020800 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart4: serial@331a0400 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x331a0400 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 208 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart5: serial@33381d00 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33381d00 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 209 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart6: serial@33381e00 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33381e00 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart7: serial@33381f00 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33381f00 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 211 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart8: serial@330c0000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x330c0000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 212 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ usb2_0: usb@41000000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41000000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ usb2_1: usb@41100000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41100000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ usb3_0: usb@41400000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41400000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ usb3_1: usb@41500000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41500000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ };
+};
--
2.34.1
^ permalink raw reply related
* [PATCH 1/2] dt-bindings: arm: axiado: add AX3005 EVK
From: Swark Yang @ 2026-06-24 10:21 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: devicetree, linux-arm-kernel, linux-kernel, Swark Yang
In-Reply-To: <20260624-upstream-axiado-ax3005-upstream-v1-0-c05bd0bc9124@axiado.com>
Add device tree binding schema for the Axiado AX3005 SoC and its
associated evaluation board. This binding will be used for the
board-level DTS files that support the AX3005 platforms.
Signed-off-by: Swark Yang <syang@axiado.com>
---
Documentation/devicetree/bindings/arm/axiado.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/axiado.yaml b/Documentation/devicetree/bindings/arm/axiado.yaml
index bfabe7b32e65..008d2b1d4e62 100644
--- a/Documentation/devicetree/bindings/arm/axiado.yaml
+++ b/Documentation/devicetree/bindings/arm/axiado.yaml
@@ -20,4 +20,10 @@ properties:
- axiado,ax3000-evk # Axiado AX3000 Evaluation Board
- const: axiado,ax3000 # Axiado AX3000 SoC
+ - description: AX3005 based boards
+ items:
+ - enum:
+ - axiado,ax3005-evk # Axiado AX3005 Evaluation Board
+ - const: axiado,ax3005 # Axiado AX3005 SoC
+
additionalProperties: true
--
2.34.1
^ permalink raw reply related
* [PATCH 0/2] arm64: Add Axiado AX3005 SoC and EVK support
From: Swark Yang @ 2026-06-24 10:21 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: devicetree, linux-arm-kernel, linux-kernel, Swark Yang
This series adds initial device tree support for the Axiado AX3005 SoC
and its evaluation board (EVK).
The AX3005 uses Cadence-derived UART/I2C/I3C/GPIO and Synopsys
DesignWare SPI IP blocks. These are already described by
existing bindings, so the device tree reuses the "axiado,ax3000-*",
"cdns,*" and "snps,*" compatible strings; only a new SoC/board
level compatible is added.
Patch 1 adds the AX3005 board/SoC compatible strings to the Axiado
platform binding.
Patch 2 adds the AX3005 SoC dtsi, the EVK board dts, and the Makefile
entry. The EVK enables the CPUs, timer, GPIO, UART, I2C, I3C, SPI and
USB controllers.
Validated with:
- make CHECK_DTBS=y axiado/ax3005-evk.dtb
- make dt_binding_check DT_SCHEMA_FILES=axiado.yaml
- boot-tested on the AX3005 EVK (to init CLI via ramfs)
Signed-off-by: Swark Yang <syang@axiado.com>
---
Swark Yang (2):
dt-bindings: arm: axiado: add AX3005 EVK
arm64: dts: axiado: Add initial support for AX3005 SoC and eval board
Documentation/devicetree/bindings/arm/axiado.yaml | 6 +
arch/arm64/boot/dts/axiado/Makefile | 1 +
arch/arm64/boot/dts/axiado/ax3005-evk.dts | 327 +++++++++
arch/arm64/boot/dts/axiado/ax3005.dtsi | 843 ++++++++++++++++++++++
4 files changed, 1177 insertions(+)
---
base-commit: 2b414a95b8f7307d42173ba9e580d6d3e2bcbfce
change-id: 20260617-upstream-axiado-ax3005-upstream-bb09780a2fdf
Best regards,
--
Swark Yang <syang@axiado.com>
^ permalink raw reply
* Re: [RFC PATCH 0/2] kasan: hw_tags: Add option to tag only at allocation time
From: Catalin Marinas @ 2026-06-24 10:13 UTC (permalink / raw)
To: Dev Jain
Cc: Harry Yoo, ryabinin.a.a, akpm, corbet, glider, andreyknvl,
dvyukov, vincenzo.frascino, kasan-dev, linux-mm, linux-kernel,
skhan, workflows, linux-doc, linux-arm-kernel, ryan.roberts,
anshuman.khandual, kaleshsingh, 21cnbao, david, will
In-Reply-To: <f5927785-d5d3-4e64-bbac-220d40718a1f@arm.com>
On Wed, Jun 24, 2026 at 09:44:49AM +0530, Dev Jain wrote:
> Anyhow someone needs to first test the current patchset to get some
> numbers, we would be wasting time on this if no one gets an improvement.
I agree. Something like iperf3 would be interesting, lots of skb
allocations.
--
Catalin
^ permalink raw reply
* Re: chipidea: usbmisc_imx: i.MX93 support
From: Stefan Wahren @ 2026-06-24 10:05 UTC (permalink / raw)
To: Xu Yang
Cc: Xu Yang, Frank Li, Jun Li, Alexander Stein, Greg Kroah-Hartman,
Linux ARM, linux-usb@vger.kernel.org
In-Reply-To: <lwo7rsdsfuibceyz5qoi46bbrhmofjpb2e3ukzdawieou3uo7m@5o2ral3q4oaf>
Hi Xu,
Am 24.06.26 um 11:11 schrieb Xu Yang:
> On Wed, Jun 24, 2026 at 08:30:00AM +0200, Stefan Wahren wrote:
> What's your issue on i.MX93?
we have an i.MX93 board using both USB interfaces:
USB1:
- external available via USB-C connector
- OTG interface should be able to switch between host and peripheral
- as USB-C interface control we use the TI TUSB321AI [1] (while its DIR,
OUT1, OUT2, CURRENT_MODE, PORT is unconnected or tied to GND)
USB2:
- used as internal host interface
We have the problem that 5 seconds after disconnecting a USB device
from USB-C (USB1) interface, the USB driver complains that the VBUS is
still to high:
[ 144.078347] usb 2-1: USB disconnect, device number 2
[ 144.083425] ci_hdrc ci_hdrc.0: remove, state 1
[ 144.083521] usb 2-1.1: USB disconnect, device number 3
[ 144.090310] usb usb2: USB disconnect, device number 1
[ 144.139325] usb 2-1.5: USB disconnect, device number 4
[ 144.158700] ci_hdrc ci_hdrc.0: USB bus 2 deregistered
[ 149.190274] ci_hdrc ci_hdrc.0: timeout waiting for 00000800 in OTGSC
[ 149.196741] usbmisc_imx 4c100200.usbmisc: vbus is error
[ 149.202078] usbmisc_imx 4c100200.usbmisc: Error occurs during
detection: -22
But except of this, both roles works as expected.
Here are the relevant device tree parts:
reg_usb_otg1_vbus: regulator-usb-otg1-vbus {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usbotg1grp>;
compatible = "regulator-fixed";
regulator-name = "usb_otg1_vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpio = <&gpio4 13 GPIO_ACTIVE_HIGH>;
enable-active-high;
regulator-boot-on;
};
&usbotg1 {
vbus-supply = <®_usb_otg1_vbus>;
over-current-active-low;
status = "okay";
};
Thanks
Stefan
[1] - https://www.ti.com/lit/ds/symlink/tusb321ai.pdf
>
> Thanks,
> Xu Yang
^ permalink raw reply
* [PATCH v3 0/7] net: wwan: t9xx: Add MediaTek T9XX WWAN driver
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
T9XX is the PCIe host device driver for MediaTek's
t900 modem. The driver uses the WWAN framework
infrastructure to create the following control ports
and network interfaces for data transactions.
* /dev/wwan0at0 - Interface that supports AT commands.
* /dev/wwan0mbim0 - Interface conforming to the MBIM
protocol.
* wwan0-X - Primary network interface for IP traffic.
The main blocks in the T9XX driver are:
* HW layer - Abstracts the hardware bus operations for
the device, and provides generic interfaces for the
transaction layer to get the device's information and
control the device's behavior. It includes:
* PCIe - Implements probe, removal and interrupt
handling.
* MHCCIF (Modem Host Cross-Core Interface) - Provides
interrupt channels for bidirectional event
notification such as handshake and port enumeration.
* Transaction layer - Implements data transactions for
the control plane and the data plane. It includes:
* DPMAIF (Data Plane Modem AP Interface) - Controls
the hardware that provides uplink and downlink
queues for the data path. The data exchange takes
place using circular buffers to share data buffer
addresses and metadata to describe the packets.
* CLDMA (Cross Layer DMA) - Manages the hardware
used by the port layer to send control messages to
the device using MediaTek's CCCI (Cross-Core
Communication Interface) protocol.
* TX Services - Dispatch packets from the port layer
to the device.
* RX Services - Dispatch packets to the port layer
when receiving packets from the device.
* Port layer - Provides control plane and data plane
interfaces to userspace. It includes:
* Control Plane - Provides device node interfaces
for controlling data transactions.
* Data Plane - Provides network link interfaces
wwanX (0, 1, 2...) for IP data transactions.
* Core logic - Contains the core logic to keep the
device working. It includes:
* FSM (Finite State Machine) - Monitors the state
of the device, and notifies each module when the
state changes.
The compilation of the T9XX driver is enabled by the
CONFIG_MTK_T9XX and CONFIG_MTK_T9XX_PCI config option
which depends on CONFIG_WWAN.
This v2 submission covers the control plane only
(patches 1-6). The data plane will follow in a
separate series once the control plane is accepted.
---
Changes in v3:
- Address sashiko AI code review comments and fix sparse warnings
- Patch 1 (Add PCIe core):
- Move extern declaration of mtk_dev_cfg_0900 from mtk_pci.c to mtk_pci.h to fix sparse warning
- Remove mtk_pci_bar_exit(): pcim_iounmap_region() was called with bitmask instead of BAR index, and pcim_iomap_regions() is devres-managed so manual unmap is redundant [sashiko]
- Remove pci_disable_device() from probe error path and remove path: pcim_enable_device() registers devres cleanup, manual disable causes enable_cnt underflow [sashiko]
- Patch 3 (Add control DMA interface):
- Add #include "mtk_cldma.h" in mtk_cldma_drv_m9xx.c to fix sparse undeclared symbol warnings for mtk_cldma_regs_m9xx and cldma_drv_ops_m9xx
- Move extern declaration of mtk_ctrl_info_m9xx from mtk_trans_ctrl.c to mtk_trans_ctrl.h to fix sparse undeclared symbol warning
- Replace kcalloc + radix_tree_gang_lookup with radix_tree_for_each_slot() in mtk_ctrl_remove_radix_tree() to eliminate allocation in teardown path [sashiko]
- Add missing mtk_pci_dev_exit() call in mtk_pci_remove() to properly clean up FSM and trans_ctrl resources before device removal [sashiko]
- Patch 4 (Add control port):
- Replace kcalloc + radix_tree_gang_lookup with radix_tree_for_each_slot() and single-entry gang_lookup in mtk_port_tbl_destroy() to eliminate allocation failure in teardown path [sashiko]
- Add port = NULL after mtk_port_put_locked() in mtk_port_internal_open() error path to prevent returning un-refcounted pointer [sashiko]
- Restore skb_unlink + trb_complete for non-EAGAIN errors in mtk_ctrl_trb_handler() TX path to prevent infinite retry loop [sashiko]
- Patch 5 (Add FSM thread):
- Check mtk_pci_register_irq() return value in mtk_cldma_dev_init() and add err_destroy_wq error label to fix unreachable dead code [sashiko]
- Propagate specific error codes (ENOMEM/EIO/EINVAL) from mtk_cldma_dev_init() error paths instead of generic -EIO [sashiko]
- Free head SKB after detaching frag_list in mtk_cldma_rxq_free() scatter-gather mode to fix memory leak [sashiko]
- Patch 6 (Add AT & MBIM WWAN ports):
- Remove unused write_lock mutex from struct mtk_port and its mutex_init call [sashiko]
- Link to v2: https://patch.msgid.link/20260610-t9xx_driver_v1-v2-0-c65addf23b3f@compal.com
Changes in v2:
- Split series into control plane (this v2) and data plane (follow-up)
- Patch 1 (Add PCIe core):
- Rename BAR_NUM to MTK_PCI_BAR_NUM for driver prefix consistency
- Replace magic numbers in mtk_pci_setup_atr() with named defines
- Remove redundant ATR register comments, use blank line separators
- Add kernel-doc comments to all non-static functions
- Convert 4 MMIO wrapper functions to static inline in header [sashiko]
- Remove unnecessary unlikely() from IRQ validation paths
- Add irq_cnt == 0 and irq_id < 0 guards in mtk_pci_get_virq_id() [sashiko]
- Initialize hw_bits at declaration for consistency
- Merge same-type variable declarations into single lines
- Add #else/#endif comments for CONFIG_ACPI blocks
- Add newlines in mtk_pci_pldr() for readability
- Move return into default case in mtk_pci_dev_reset()
- Simplify mtk_mhccif_init() error path to use direct returns
- Change -EFAULT to -ENOLINK for PCIe link check failure
- Rename goto label "out" to "log_err" in mtk_pci_probe()
- Wrap long lines to stay within 80 columns
- Fix IRQ vector leak: add pci_free_irq_vectors() on error path [sashiko]
- Fix mtk_pci_remove() ordering: free IRQ before cancel_work_sync [sashiko]
- Fix mtk_pci_pldr() ACPI buffer leak: free first result before second call [sashiko]
- Replace msleep(500) with MTK_PLDR_POWER_OFF_DELAY_MS define
- Remove unused EXT_EVT_H2D_DRM_DISABLE_AP and related register define [sashiko]
- Increase MTK_IRQ_NAME_LEN from 20 to 32 to fix W=1 format-truncation warning [sashiko]
- Patch 2 (Add control plane transaction layer):
- Add kernel-doc comments to mtk_ctrl_init() and mtk_ctrl_exit()
- Change mtk_ctrl_exit() return type from int to void
- Set mdev->ctrl_blk to NULL after freeing in mtk_ctrl_exit() [sashiko]
- Change ctrl_blk from void* to typed struct mtk_ctrl_blk* [sashiko]
- Remove redundant "depends on MTK_T9XX" from MTK_T9XX_PCI Kconfig [sashiko]
- Use mtk_dev_free() instead of devm_kfree() in mtk_pci_probe() error path [sashiko]
- Patch 3 (Add control DMA interface):
- Add @ops kernel-doc parameter for mtk_ctrl_init()
- Rename 'err' to 'ret' consistently throughout the patch
- Reorder variable declarations to follow reverse Christmas tree style
- Change mtk_cldma_txq_free() return type from int to void
- Change mtk_cldma_rxq_free() return type from int to void
- Change mtk_cldma_exit() return type from int to void
- Remove unnecessary zero-initialization of ret in mtk_cldma_start_xfer()
- Remove unnecessary zero-initialization of ret in mtk_cldma_tx()
- Use direct return instead of goto out in mtk_cldma_submit_tx() error paths
- Move software state before HWO flag in mtk_cldma_submit_tx()
- Squash variable declarations in mtk_cldma_check_intr_status()
- Remove unlikely() from validation paths in mtk_cldma_check_ch_cfg()
- Clamp data_recv_len with min_t to prevent skb_over_panic in mtk_cldma_rx_skb_adjust() [sashiko]
- Use READ_ONCE() for HWO flag polling in mtk_cldma_check_rx_req() [sashiko]
- Fix mtk_cldma_rx_done_work() to always unmask interrupt on error path [sashiko]
- Add DMA address guard in mtk_cldma_txq_free() teardown loop [sashiko]
- Add IS_ERR() check for kthread_run() in mtk_ctrl_trb_srv_init() [sashiko]
- Fix queue_info memory leak on validation failure in mtk_pcie_hif_init() [sashiko]
- Handle non-EAGAIN errors in mtk_ctrl_trb_handler() TX path [sashiko]
- Fix 'err' typo to 'ret' in mtk_cldma_txbuf_set() error message
- Remove unused variable mdev in mtk_cldma_rx_check_again() [sashiko]
- Remove unused variables trans and ctrl_blk in mtk_cldma_txq_free() and mtk_cldma_rxq_free() [sashiko]
- Patch 4 (Add control port):
- Add @cfg kernel-doc parameter for mtk_ctrl_init()
- Update mtk_ctrl_init() return description to cover additional error codes
- Fix double list_del in mtk_port_stale_list_grp_cleanup() [sashiko]
- Fix direct mtk_port_trb_free() call to use kref_put() in mtk_port_ch_enable() error path [sashiko]
- Fix direct mtk_port_trb_free() call to use kref_put() in mtk_port_ch_disable() error path [sashiko]
- Add mtk_port_tbl_destroy() in mtk_port_mngr_init() error path to prevent port memory leak [sashiko]
- Change port_ops exit/reset/enable/disable callbacks from int to void
- Move -EIO dispatch comment to where the code was introduced
- Patch 5 (Add FSM thread):
- Add bounds check for rtft_entry in mtk_fsm_parse_hs2_msg() [sashiko]
- Add skb length validation before accessing ctrl_msg_header in mtk_fsm_sap_ctrl_msg_handler() [sashiko]
- Fix skb leak on CTRL_MSG_HS2 mismatch return in mtk_fsm_sap_ctrl_msg_handler() [sashiko]
- Add skb length validation before accessing ctrl_msg_header in mtk_fsm_md_ctrl_msg_handler() [sashiko]
- Replace devm_kzalloc/devm_kfree with kzalloc/kfree for FSM events [sashiko]
- Fix mtk_fsm_evt_submit() to return -ETIMEDOUT on blocking event timeout [sashiko]
- Change FSM kthread from TASK_INTERRUPTIBLE to TASK_UNINTERRUPTIBLE [sashiko]
- Remove unused variable hw_id in mtk_cldma_dev_exit() [sashiko]
- Patch 6 (Add AT & MBIM WWAN ports):
- Use imperative mode in commit message
- Remove unnecessary zero-initialization of ret in mtk_port_copy_data_from()
- Change copy_from_user() error code from -EFAULT to -EINVAL in mtk_port_copy_data_from()
- Return -EINVAL for zero-length write in mtk_port_common_write()
- Change mtk_port_wwan_exit/enable/disable() return type from int to void
- Fix packet_size to account for CCCI header reservation in mtk_port_common_write() [sashiko]
- Fix WWAN tx callbacks to consume skb and return 0 per wwan_port_ops contract [sashiko]
- Fix wwan_create_port() error path: clear ERR_PTR to NULL and call mtk_port_ch_disable() [sashiko]
- Patch 7 (Add maintainers entry): new patch
- Link to v1: https://patch.msgid.link/20260529-t9xx_driver_v1-v1-0-bdbfe2c01e57@compal.com
---
Jack Wu (7):
net: wwan: t9xx: Add PCIe core
net: wwan: t9xx: Add control plane transaction layer
net: wwan: t9xx: Add control DMA interface
net: wwan: t9xx: Add control port
net: wwan: t9xx: Add FSM thread
net: wwan: t9xx: Add AT & MBIM WWAN ports
net: wwan: t9xx: Add maintainers entry
MAINTAINERS | 9 +
drivers/net/wwan/Kconfig | 17 +
drivers/net/wwan/Makefile | 1 +
drivers/net/wwan/t9xx/Makefile | 14 +
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 111 ++
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 88 ++
drivers/net/wwan/t9xx/mtk_dev.c | 55 +
drivers/net/wwan/t9xx/mtk_dev.h | 114 ++
drivers/net/wwan/t9xx/mtk_fsm.c | 948 +++++++++++++++
drivers/net/wwan/t9xx/mtk_fsm.h | 140 +++
drivers/net/wwan/t9xx/mtk_port.c | 968 +++++++++++++++
drivers/net/wwan/t9xx/mtk_port.h | 174 +++
drivers/net/wwan/t9xx/mtk_port_io.c | 573 +++++++++
drivers/net/wwan/t9xx/mtk_port_io.h | 41 +
drivers/net/wwan/t9xx/mtk_utility.h | 33 +
drivers/net/wwan/t9xx/pcie/Makefile | 15 +
drivers/net/wwan/t9xx/pcie/mtk_cldma.c | 1420 +++++++++++++++++++++++
drivers/net/wwan/t9xx/pcie/mtk_cldma.h | 173 +++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c | 371 ++++++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h | 174 +++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c | 178 +++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h | 101 ++
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 55 +
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 1102 ++++++++++++++++++
drivers/net/wwan/t9xx/pcie/mtk_pci.h | 234 ++++
drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c | 69 ++
drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h | 71 ++
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 603 ++++++++++
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 107 ++
29 files changed, 7959 insertions(+)
---
base-commit: eb3f4b7426cfd2b79d65b7d37155480b32259a11
change-id: 20260529-t9xx_driver_v1-1744f8af7739
Best regards,
--
Jack Wu <jackbb_wu@compal.com>
^ permalink raw reply
* [PATCH v3 4/7] net: wwan: t9xx: Add control port
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
The control port consists of port I/O and port manager.
Port I/O provides a common operation as defined by "struct port_ops",
and the operation is managed by the "port manager". It provides
interfaces to internal users, the implemented internal interfaces are
open, close, write and recv_register.
The port manager defines and implements port management interfaces and
structures. It is responsible for port creation, destroying, and managing
port states. It sends data from port I/O to CLDMA via TRB ( Transaction
Request Block ), and dispatches received data from CLDMA to port I/O.
The using port will be held in the "stale list" when the driver destroys
it, and after creating it again, the user can continue to use it.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/t9xx/Makefile | 4 +-
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 19 +-
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 20 +-
drivers/net/wwan/t9xx/mtk_dev.c | 13 +-
drivers/net/wwan/t9xx/mtk_port.c | 877 +++++++++++++++++++++++++
drivers/net/wwan/t9xx/mtk_port.h | 159 +++++
drivers/net/wwan/t9xx/mtk_port_io.c | 239 +++++++
drivers/net/wwan/t9xx/mtk_port_io.h | 36 +
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 25 +-
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 2 +
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 24 +-
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 1 +
12 files changed, 1407 insertions(+), 12 deletions(-)
diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
index ae9d6f2344ab..db3b1aa1928b 100644
--- a/drivers/net/wwan/t9xx/Makefile
+++ b/drivers/net/wwan/t9xx/Makefile
@@ -8,4 +8,6 @@ obj-$(CONFIG_MTK_T9XX_PCI) += pcie/
mtk_t9xx-y := \
mtk_dev.o \
- mtk_ctrl_plane.o
+ mtk_ctrl_plane.o \
+ mtk_port.o \
+ mtk_port_io.o
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
index 70348696ac44..b9a0443ce8ec 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
@@ -7,20 +7,23 @@
#include <linux/device.h>
#include "mtk_ctrl_plane.h"
+#include "mtk_port.h"
/**
* mtk_ctrl_init() - Initialize the control plane block.
* @mdev: Pointer to the MTK modem device.
* @ops: HIF operations for the control plane.
+ * @cfg: Control plane configuration.
*
* Allocates and initializes the control plane block
* associated with @mdev.
*
- * Return: 0 on success, -ENOMEM on allocation failure.
+ * Return: 0 on success, negative error code on failure.
*/
-int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops)
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops, struct mtk_ctrl_cfg *cfg)
{
struct mtk_ctrl_blk *ctrl_blk;
+ int err;
ctrl_blk = devm_kzalloc(mdev->dev, sizeof(*ctrl_blk), GFP_KERNEL);
if (!ctrl_blk)
@@ -29,8 +32,19 @@ int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops)
ctrl_blk->mdev = mdev;
mdev->ctrl_blk = ctrl_blk;
ctrl_blk->ops = ops;
+ ctrl_blk->cfg = cfg;
+
+ err = mtk_port_mngr_init(ctrl_blk, cfg->port_layer_cfg->port_cfg,
+ cfg->port_layer_cfg->port_cnt);
+ if (err)
+ goto err_free_mem;
return 0;
+
+err_free_mem:
+ devm_kfree(mdev->dev, ctrl_blk);
+
+ return err;
}
EXPORT_SYMBOL(mtk_ctrl_init);
@@ -44,6 +58,7 @@ void mtk_ctrl_exit(struct mtk_md_dev *mdev)
{
struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ mtk_port_mngr_exit(ctrl_blk);
devm_kfree(mdev->dev, ctrl_blk);
mdev->ctrl_blk = NULL;
}
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
index 88d71ac92084..d7fcccde8a1b 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -11,6 +11,17 @@
#include "mtk_dev.h"
+#define Q_MTU_2K (0x800)
+#define Q_MTU_3_5K (0xE00)
+#define Q_MTU_7K (0x1C00)
+#define Q_MTU_32K (0x8000)
+#define Q_MTU_63K (0xFC00)
+#define Q_FRAG_2K (0x800)
+#define Q_FRAG_3_5K (0xE00)
+#define Q_FRAG_7K (0x1C00)
+#define Q_FRAG_32K (0x8000)
+#define Q_FRAG_63K (0xFC00)
+
enum mtk_trb_cmd_type {
TRB_CMD_MIN,
TRB_CMD_ENABLE,
@@ -54,17 +65,22 @@ struct mtk_ctrl_hif_ops {
int (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);
};
-struct mtk_ctrl_cfg;
+struct mtk_ctrl_cfg {
+ struct mtk_port_layer_cfg *port_layer_cfg;
+};
+
struct mtk_ctrl_trans;
struct mtk_ctrl_blk {
struct mtk_md_dev *mdev;
+ struct mtk_port_mngr *port_mngr;
struct mtk_ctrl_hif_ops *ops;
void *ctrl_hw_priv;
struct mtk_ctrl_cfg *cfg;
};
-int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops);
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops,
+ struct mtk_ctrl_cfg *cfg);
void mtk_ctrl_exit(struct mtk_md_dev *mdev);
#endif /* __MTK_CTRL_PLANE_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_dev.c b/drivers/net/wwan/t9xx/mtk_dev.c
index f254ca7ed877..8ba70d432e6f 100644
--- a/drivers/net/wwan/t9xx/mtk_dev.c
+++ b/drivers/net/wwan/t9xx/mtk_dev.c
@@ -6,6 +6,8 @@
#include <linux/module.h>
#include "mtk_dev.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
struct mtk_md_dev *mtk_dev_alloc(struct device *pdev, const struct mtk_dev_ops *dev_ops)
{
@@ -31,12 +33,21 @@ EXPORT_SYMBOL(mtk_dev_free);
static int __init mtk_common_drv_init(void)
{
- return 0;
+ int ret;
+
+ ret = mtk_port_io_init();
+ if (ret)
+ goto err_init_devid;
+
+err_init_devid:
+ return ret;
}
module_init(mtk_common_drv_init);
static void __exit mtk_common_drv_exit(void)
{
+ mtk_port_io_exit();
+ mtk_port_stale_list_grp_cleanup();
}
module_exit(mtk_common_drv_exit);
diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
new file mode 100644
index 000000000000..034c9ad0f892
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -0,0 +1,877 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+
+#define MTK_DFLT_TRB_TIMEOUT (5 * HZ)
+#define MTK_DFLT_TRB_STATUS (0x1)
+#define MTK_TRB_HEADER_ADDED (0xADDED)
+#define MTK_CHECK_RX_SEQ_MASK (0x7fff)
+
+#define MTK_PORT_ENUM_VER (0)
+#define MTK_PORT_ENUM_HEAD_PATTERN (0x5a5a5a5a)
+#define MTK_PORT_ENUM_TAIL_PATTERN (0xa5a5a5a5)
+
+#define MTK_PORT_SEARCH_FROM_RADIX_TREE(p, s) ({\
+ struct mtk_port *_p; \
+ _p = rcu_dereference_raw(*(s)); \
+ if (!_p) \
+ continue; \
+ p = _p; \
+})
+
+#define MTK_PORT_INTERNAL_NODE_CHECK(p, s, i) ({\
+ if (radix_tree_is_internal_node(p)) { \
+ s = radix_tree_iter_retry(&(i));\
+ continue; \
+ } \
+})
+
+struct mtk_port_info {
+ __le16 channel;
+ __le16 reserved;
+} __packed;
+
+struct mtk_port_enum_msg {
+ __le32 head_pattern;
+ __le16 port_cnt;
+ __le16 version;
+ __le32 tail_pattern;
+ u8 data[];
+} __packed;
+
+/* global group for stale ports */
+static LIST_HEAD(stale_list_grp);
+/* mutex lock for stale_list_group */
+DEFINE_MUTEX(port_mngr_grp_mtx);
+
+static DEFINE_IDA(ccci_dev_ids);
+
+/* This function working always under mutex lock port_mngr_grp_mtx */
+void mtk_port_release(struct kref *port_kref)
+{
+ struct mtk_stale_list *s_list;
+ struct mtk_port *port;
+
+ port = container_of(port_kref, struct mtk_port, kref);
+ if (!test_bit(PORT_S_ON_STALE_LIST, &port->status))
+ goto port_exit;
+
+ list_del(&port->stale_entry);
+ list_for_each_entry(s_list, &stale_list_grp, entry) {
+ if (!strncmp(s_list->dev_str, port->dev_str, MTK_DEV_STR_LEN) &&
+ list_empty(&s_list->ports) && s_list->dev_id >= 0) {
+ ida_free(&ccci_dev_ids, s_list->dev_id);
+ s_list->dev_id = -1;
+ break;
+ }
+ }
+port_exit:
+ ports_ops[port->info.type]->exit(port);
+ kfree(port);
+}
+
+static int mtk_port_tbl_add(struct mtk_port_mngr *port_mngr, struct mtk_port *port)
+{
+ int ret;
+
+ ret = radix_tree_insert(&port_mngr->port_tbl[MTK_PORT_TBL_TYPE(port->info.rx_ch)],
+ port->info.rx_ch & 0xFFF, port);
+ if (ret)
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "port(%s) add to port_tbl failed, return %d\n",
+ port->info.name, ret);
+ else
+ port_mngr->port_cnt++;
+
+ return ret;
+}
+
+static void mtk_port_tbl_del(struct mtk_port_mngr *port_mngr, struct mtk_port *port)
+{
+ radix_tree_delete(&port_mngr->port_tbl[MTK_PORT_TBL_TYPE(port->info.rx_ch)],
+ port->info.rx_ch & 0xFFF);
+ port_mngr->port_cnt--;
+}
+
+static struct mtk_port *mtk_port_restore_from_stale_list(struct mtk_port_mngr *port_mngr,
+ struct mtk_stale_list *s_list)
+{
+ struct mtk_port *port, *next_port;
+ int ret;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_for_each_entry_safe(port, next_port, &s_list->ports, stale_entry) {
+ kref_get(&port->kref);
+ list_del(&port->stale_entry);
+ ret = mtk_port_tbl_add(port_mngr, port);
+ if (ret) {
+ list_add_tail(&port->stale_entry, &s_list->ports);
+ kref_put(&port->kref, mtk_port_release);
+ mutex_unlock(&port_mngr_grp_mtx);
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Failed when adding (%s) to port mngr\n",
+ port->info.name);
+ return ERR_PTR(ret);
+ }
+
+ port->port_mngr = port_mngr;
+ clear_bit(PORT_S_ON_STALE_LIST, &port->status);
+ ports_ops[port->info.type]->reset(port);
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return NULL;
+}
+
+static struct mtk_port *mtk_port_alloc_and_add(struct mtk_port_mngr *port_mngr,
+ struct mtk_port_cfg *dflt_info)
+{
+ struct mtk_port *port;
+ int ret;
+
+ port = kzalloc_obj(*port, GFP_KERNEL);
+ if (!port) {
+ ret = -ENOMEM;
+ goto err_alloc_port;
+ }
+ memcpy(&port->info, dflt_info, sizeof(*dflt_info));
+
+ ret = mtk_port_tbl_add(port_mngr, port);
+ if (ret < 0) {
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to add port(%s) to port tbl\n", dflt_info->name);
+ goto err_free_port;
+ }
+
+ port->port_mngr = port_mngr;
+ ret = ports_ops[port->info.type]->init(port);
+ if (ret < 0) {
+ mtk_port_tbl_del(port_mngr, port);
+ goto err_free_port;
+ }
+
+ memcpy(port->dev_str, port_mngr->ctrl_blk->mdev->dev_str, MTK_DEV_STR_LEN);
+ return port;
+
+err_free_port:
+ kfree(port);
+err_alloc_port:
+ return ERR_PTR(ret);
+}
+
+static void mtk_port_free_or_backup(struct mtk_port_mngr *port_mngr,
+ struct mtk_port *port, struct mtk_stale_list *s_list)
+{
+ mutex_lock(&port_mngr_grp_mtx);
+ mtk_port_tbl_del(port_mngr, port);
+ if (port->info.type != PORT_TYPE_INTERNAL) {
+ if (test_bit(PORT_S_OPEN, &port->status)) {
+ list_add_tail(&port->stale_entry, &s_list->ports);
+ set_bit(PORT_S_ON_STALE_LIST, &port->status);
+ memcpy(port->dev_str, port_mngr->ctrl_blk->mdev->dev_str,
+ MTK_DEV_STR_LEN);
+ port->port_mngr = NULL;
+ }
+ kref_put(&port->kref, mtk_port_release);
+ } else {
+ mtk_port_release(&port->kref);
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static struct mtk_port *mtk_port_search_by_id(struct mtk_port_mngr *port_mngr, int rx_ch)
+{
+ int tbl_type = MTK_PORT_TBL_TYPE(rx_ch);
+
+ if (tbl_type < PORT_TBL_SAP || tbl_type >= PORT_TBL_MAX)
+ return NULL;
+
+ return radix_tree_lookup(&port_mngr->port_tbl[tbl_type], MTK_CH_ID(rx_ch));
+}
+
+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name)
+{
+ int tbl_type = PORT_TBL_SAP;
+ struct radix_tree_iter iter;
+ struct mtk_port *port;
+ void __rcu **slot;
+
+ do {
+ radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type], &iter, 0) {
+ MTK_PORT_SEARCH_FROM_RADIX_TREE(port, slot);
+ MTK_PORT_INTERNAL_NODE_CHECK(port, slot, iter);
+ if (!strncmp(port->info.name, name, MTK_DFLT_PORT_NAME_LEN))
+ return port;
+ }
+ tbl_type++;
+ } while (tbl_type < PORT_TBL_MAX);
+
+ return NULL;
+}
+
+static int mtk_port_tbl_create(struct mtk_port_mngr *port_mngr, struct mtk_port_cfg *cfg,
+ const int port_cnt, struct mtk_stale_list *s_list)
+{
+ struct mtk_port_cfg *dflt_port;
+ struct mtk_port *port;
+ int i;
+
+ INIT_RADIX_TREE(&port_mngr->port_tbl[PORT_TBL_SAP], GFP_KERNEL);
+ INIT_RADIX_TREE(&port_mngr->port_tbl[PORT_TBL_MD], GFP_KERNEL);
+
+ mtk_port_restore_from_stale_list(port_mngr, s_list);
+
+ /* copy ports from static port cfg table */
+ for (i = 0; i < port_cnt; i++) {
+ dflt_port = cfg + i;
+ if (!mtk_port_search_by_id(port_mngr, dflt_port->rx_ch)) {
+ port = mtk_port_alloc_and_add(port_mngr, dflt_port);
+ if (IS_ERR(port))
+ return PTR_ERR(port);
+ }
+ }
+
+ return 0;
+}
+
+static void mtk_port_tbl_destroy(struct mtk_port_mngr *port_mngr, struct mtk_stale_list *s_list)
+{
+ struct radix_tree_iter iter;
+ struct mtk_port *port;
+ void __rcu **slot;
+ int tbl_type;
+
+ tbl_type = PORT_TBL_SAP;
+ do {
+ radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type], &iter, 0) {
+ port = radix_tree_deref_slot(slot);
+ if (!port)
+ continue;
+ ports_ops[port->info.type]->disable(port);
+ }
+
+ while (radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+ (void **)&port, 0, 1))
+ mtk_port_free_or_backup(port_mngr, port, s_list);
+ } while (++tbl_type < PORT_TBL_MAX);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_create(struct mtk_ctrl_blk *ctrl_blk)
+{
+ struct mtk_stale_list *s_list;
+
+ s_list = kzalloc_obj(*s_list, GFP_KERNEL);
+ if (!s_list)
+ return NULL;
+
+ memcpy(s_list->dev_str, ctrl_blk->mdev->dev_str, MTK_DEV_STR_LEN);
+ s_list->dev_id = -1;
+ INIT_LIST_HEAD(&s_list->ports);
+ rwlock_init(&s_list->port_mngr_lock);
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_add_tail(&s_list->entry, &stale_list_grp);
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return s_list;
+}
+
+static void mtk_port_stale_list_destroy(struct mtk_stale_list *s_list)
+{
+ mutex_lock(&port_mngr_grp_mtx);
+ list_del(&s_list->entry);
+ mutex_unlock(&port_mngr_grp_mtx);
+ kfree(s_list);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_search(const char *dev_str)
+{
+ struct mtk_stale_list *tmp, *s_list = NULL;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_for_each_entry(tmp, &stale_list_grp, entry) {
+ if (!strncmp(tmp->dev_str, dev_str, MTK_DEV_STR_LEN)) {
+ s_list = tmp;
+ break;
+ }
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return s_list;
+}
+
+void mtk_port_stale_list_grp_cleanup(void)
+{
+ struct mtk_stale_list *s_list, *next_s_list;
+ struct mtk_port *port, *next_port;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_for_each_entry_safe(s_list, next_s_list, &stale_list_grp, entry) {
+ list_del(&s_list->entry);
+
+ list_for_each_entry_safe(port, next_port, &s_list->ports, stale_entry) {
+ clear_bit(PORT_S_ON_STALE_LIST, &port->status);
+ mtk_port_release(&port->kref);
+ }
+
+ kfree(s_list);
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_init(struct mtk_ctrl_blk *ctrl_blk, int *dev_id)
+{
+ struct mtk_stale_list *s_list;
+
+ s_list = mtk_port_stale_list_search(ctrl_blk->mdev->dev_str);
+ if (!s_list) {
+ s_list = mtk_port_stale_list_create(ctrl_blk);
+ if (unlikely(!s_list))
+ return NULL;
+ }
+
+ mutex_lock(&port_mngr_grp_mtx);
+ if (s_list->dev_id < 0) {
+ *dev_id = ida_alloc_range(&ccci_dev_ids, 0, MTK_DFLT_MAX_DEV_CNT - 1, GFP_KERNEL);
+ } else {
+ *dev_id = s_list->dev_id;
+ s_list->dev_id = -1;
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return s_list;
+}
+
+static void mtk_port_stale_list_exit(struct mtk_ctrl_blk *ctrl_blk,
+ struct mtk_stale_list *s_list, int dev_id)
+{
+ if (!s_list)
+ return;
+ mutex_lock(&port_mngr_grp_mtx);
+ if (list_empty(&s_list->ports)) {
+ ida_free(&ccci_dev_ids, dev_id);
+ mutex_unlock(&port_mngr_grp_mtx);
+ mtk_port_stale_list_destroy(s_list);
+ } else {
+ s_list->dev_id = dev_id;
+ mutex_unlock(&port_mngr_grp_mtx);
+ }
+}
+
+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
+ int (*trb_complete)(struct sk_buff *skb))
+{
+ kref_init(&trb->kref);
+ trb->channel_id = port->info.rx_ch;
+ trb->status = MTK_DFLT_TRB_STATUS;
+ trb->priv = port;
+ trb->cmd = cmd;
+ trb->trb_complete = trb_complete;
+}
+
+void mtk_port_trb_free(struct kref *trb_kref)
+{
+ struct trb *trb = container_of(trb_kref, struct trb, kref);
+ struct sk_buff *skb, *frag_skb, *next_skb;
+
+ skb = container_of((char *)trb, struct sk_buff, cb[0]);
+ /* Free frag_list for scatter gather TX */
+ if (trb->cmd == TRB_CMD_TX && skb_has_frag_list(skb)) {
+ frag_skb = skb_shinfo(skb)->frag_list;
+ while (frag_skb) {
+ next_skb = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = next_skb;
+ }
+ skb_shinfo(skb)->frag_list = NULL;
+ skb->data_len = 0;
+ }
+ dev_kfree_skb_any(skb);
+}
+EXPORT_SYMBOL(mtk_port_trb_free);
+
+static int mtk_port_open_trb_complete(struct sk_buff *skb)
+{
+ struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+ struct trb *trb = (struct trb *)skb->cb;
+ struct mtk_port *port = trb->priv;
+
+ if (!trb->status) {
+ port->tx_mtu = trb_open_priv->tx_mtu;
+ port->rx_mtu = trb_open_priv->rx_mtu;
+ port->tx_frag_size = trb_open_priv->tx_frag_size;
+ port->rx_frag_size = trb_open_priv->rx_frag_size;
+ port->tx_mtu -= MTK_CCCI_H_ELEN;
+ port->rx_mtu -= MTK_CCCI_H_ELEN;
+ }
+
+ wake_up_interruptible_all(&port->trb_wq);
+
+ kref_put(&trb->kref, mtk_port_trb_free);
+ return 0;
+}
+
+static int mtk_port_close_trb_complete(struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct mtk_port *port = trb->priv;
+
+ wake_up_interruptible_all(&port->trb_wq);
+ wake_up_interruptible_all(&port->rx_wq);
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return 0;
+}
+
+static int mtk_port_tx_complete(struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct mtk_port *port = trb->priv;
+
+ if (trb->status < 0)
+ dev_warn(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to send data: status:%d, port:%s\n",
+ trb->status, port->info.name);
+
+ wake_up_interruptible_all(&port->trb_wq);
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return 0;
+}
+
+int mtk_port_status_check(struct mtk_port *port)
+{
+ if (!test_bit(PORT_S_ENABLE, &port->status))
+ return -ENODEV;
+
+ if (!test_bit(PORT_S_OPEN, &port->status) || test_bit(PORT_S_FLUSH, &port->status) ||
+ !test_bit(PORT_S_WR, &port->status))
+ return -EBADF;
+
+ return 0;
+}
+
+int mtk_port_send_data(struct mtk_port *port, void *data)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct sk_buff *skb = data;
+ bool force_send;
+ struct trb *trb;
+ int ret, len;
+
+ port_mngr = port->port_mngr;
+
+ force_send = !!(port->info.flags & (PORT_F_BLOCKING | PORT_F_FORCE_SEND));
+ trb = (struct trb *)skb->cb;
+ mtk_port_trb_init(port, trb, TRB_CMD_TX, mtk_port_tx_complete);
+ len = skb->len;
+ kref_get(&trb->kref); /* kref count 1->2 */
+
+ /* add ccci header */
+ mtk_port_add_header(skb);
+ ret = mtk_port_status_check(port);
+ if (!ret)
+ ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev,
+ skb, force_send);
+
+ if (ret < 0) {
+ kref_put(&trb->kref, mtk_port_trb_free); /* kref count 2->1 */
+ kref_put(&trb->kref, mtk_port_trb_free); /* kref count 1->0 */
+ port->tx_seq--;
+ goto out;
+ }
+
+ if (!(port->info.flags & PORT_F_BLOCKING)) {
+ kref_put(&trb->kref, mtk_port_trb_free);
+ ret = len;
+ goto out;
+ }
+start_wait:
+
+ /* wait trb done, and no timeout in tx blocking mode */
+ ret = wait_event_interruptible_timeout(port->trb_wq,
+ trb->status <= 0 ||
+ test_bit(PORT_S_FLUSH, &port->status) ||
+ !test_bit(PORT_S_WR, &port->status),
+ MTK_DFLT_TRB_TIMEOUT);
+ if (!ret) {
+ goto start_wait;
+ } else if (ret == -ERESTARTSYS) {
+ ret = -EINTR;
+ } else if (ret > 0) {
+ if (test_bit(PORT_S_FLUSH, &port->status))
+ ret = len;
+ else
+ ret = (!trb->status) ? len : trb->status;
+ }
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+out:
+ return ret;
+}
+
+static int mtk_port_check_rx_seq(struct mtk_port *port, struct mtk_ccci_header *ccci_h)
+{
+ u16 seq_num, assert_bit, channel;
+ struct mtk_md_dev *mdev;
+
+ seq_num = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h->status));
+ assert_bit = FIELD_GET(MTK_HDR_FLD_AST, le32_to_cpu(ccci_h->status));
+ if (assert_bit && port->rx_seq &&
+ ((seq_num - port->rx_seq) & MTK_CHECK_RX_SEQ_MASK) != 1) {
+ mdev = port->port_mngr->ctrl_blk->mdev;
+ channel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h->status));
+ dev_warn((mdev)->dev,
+ "<ch: %04x> seq num out-of-order %d->%d, len(%u)\n",
+ channel, seq_num, port->rx_seq,
+ le32_to_cpu(ccci_h->packet_len));
+
+ port->rx_seq = seq_num;
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int mtk_port_rx_dispatch_frag_skb(struct mtk_port *port, struct sk_buff *skb)
+{
+ struct sk_buff *frag_skb, *frag_next;
+ int ret;
+
+ frag_skb = skb_shinfo(skb)->frag_list;
+ skb->len -= skb->data_len;
+ skb->data_len = 0;
+ skb_shinfo(skb)->frag_list = NULL;
+
+ ret = ports_ops[port->info.type]->recv(port, skb);
+ if (ret < 0) {
+ skb_shinfo(skb)->frag_list = frag_skb;
+ return ret;
+ }
+
+ while (frag_skb) {
+ frag_next = frag_skb->next;
+ if (!frag_skb->len) {
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = frag_next;
+ continue;
+ }
+ frag_skb->next = NULL;
+ ret = ports_ops[port->info.type]->recv(port, frag_skb);
+ if (ret < 0) {
+ frag_skb->next = frag_next;
+ while (frag_skb) {
+ frag_next = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = frag_next;
+ }
+ return -EIO;
+ }
+ frag_skb = frag_next;
+ }
+
+ return 0;
+}
+
+static int mtk_port_rx_dispatch(struct sk_buff *skb, void *priv, bool force_recv)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_ccci_header *ccci_h;
+ struct mtk_port *port = priv;
+ int ret = -EPROTO;
+ u16 channel;
+
+ if (!skb || !priv) {
+ pr_err("Invalid input value in rx dispatch\n");
+ return -EINVAL;
+ }
+
+ port_mngr = port->port_mngr;
+
+ ccci_h = mtk_port_strip_header(skb);
+ if (unlikely(!ccci_h)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Unsupported: skb length(%d) is less than ccci header\n",
+ skb->len);
+ goto drop_data;
+ }
+
+ channel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h->status));
+ port = mtk_port_search_by_id(port_mngr, channel);
+ if (unlikely(!port)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to find port by channel:%d\n", channel);
+ goto drop_data;
+ }
+
+ ret = mtk_port_check_rx_seq(port, ccci_h);
+ if (unlikely(ret))
+ goto drop_data;
+
+ port->rx_seq = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h->status));
+ skb_pull(skb, sizeof(*ccci_h));
+
+ /* Support scatter gather transmission */
+ if (port->rx_mtu > port->rx_frag_size) {
+ ret = mtk_port_rx_dispatch_frag_skb(port, skb);
+ /* -EIO means partial data dispatch complete, does not goto drop flow */
+ if (ret < 0 && ret != -EIO)
+ goto drop_frag_skb;
+ } else {
+ ret = ports_ops[port->info.type]->recv(port, skb);
+ if (ret < 0)
+ goto drop_data;
+ }
+
+ return ret;
+
+drop_frag_skb:
+ {
+ struct sk_buff *frag_skb, *tmp;
+
+ frag_skb = skb_shinfo(skb)->frag_list;
+ while (frag_skb) {
+ tmp = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = tmp;
+ }
+ skb_shinfo(skb)->frag_list = NULL;
+ }
+drop_data:
+ dev_kfree_skb_any(skb);
+ return ret;
+}
+
+int mtk_port_add_header(struct sk_buff *skb)
+{
+ struct mtk_ccci_header *ccci_h;
+ struct mtk_port *port;
+ struct trb *trb;
+
+ trb = (struct trb *)skb->cb;
+ if (trb->status == MTK_TRB_HEADER_ADDED)
+ return 0;
+
+ port = trb->priv;
+ if (!port)
+ return -EINVAL;
+
+ ccci_h = skb_push(skb, sizeof(*ccci_h));
+
+ ccci_h->packet_header = cpu_to_le32(0);
+ ccci_h->packet_len = cpu_to_le32(skb->len);
+ ccci_h->ex_msg = cpu_to_le32(0);
+ ccci_h->status = cpu_to_le32(FIELD_PREP(MTK_HDR_FLD_CHN, port->info.tx_ch) |
+ FIELD_PREP(MTK_HDR_FLD_SEQ, port->tx_seq++) |
+ FIELD_PREP(MTK_HDR_FLD_AST, 1));
+
+ trb->status = MTK_TRB_HEADER_ADDED;
+
+ return 0;
+}
+
+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb)
+{
+ struct mtk_ccci_header *ccci_h;
+
+ if (skb->len < sizeof(*ccci_h)) {
+ pr_err("Invalid input value\n");
+ return NULL;
+ }
+
+ ccci_h = (struct mtk_ccci_header *)skb->data;
+
+ return ccci_h;
+}
+
+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data)
+{
+ struct mtk_port_enum_msg *msg = data;
+ struct mtk_port_info *port_info;
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_port *port;
+ int port_id;
+ u16 ch_id;
+
+ if (unlikely(!mdev || !msg))
+ return -EINVAL;
+
+ ctrl_blk = mdev->ctrl_blk;
+ port_mngr = ctrl_blk->port_mngr;
+ if (le16_to_cpu(msg->version) != MTK_PORT_ENUM_VER ||
+ le32_to_cpu(msg->head_pattern) != MTK_PORT_ENUM_HEAD_PATTERN ||
+ le32_to_cpu(msg->tail_pattern) != MTK_PORT_ENUM_TAIL_PATTERN)
+ return -EPROTO;
+
+ for (port_id = 0; port_id < le16_to_cpu(msg->port_cnt); port_id++) {
+ port_info = (struct mtk_port_info *)(msg->data +
+ (sizeof(*port_info) * port_id));
+ ch_id = FIELD_GET(MTK_INFO_FLD_CHID, le16_to_cpu(port_info->channel));
+ port = mtk_port_search_by_id(port_mngr, ch_id);
+ if (!port)
+ continue;
+ port->enable = FIELD_GET(MTK_INFO_FLD_EN, le16_to_cpu(port_info->channel));
+ }
+
+ return 0;
+}
+
+int mtk_port_ch_enable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr = port->port_mngr;
+ struct trb_open_priv *trb_open_priv;
+ struct sk_buff *skb;
+ struct trb *trb;
+ int ret;
+
+ skb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ trb_open_priv = (struct trb_open_priv *)skb->data;
+ trb_open_priv->rx_done = mtk_port_rx_dispatch;
+
+ skb_put(skb, sizeof(struct trb_open_priv));
+ trb = (struct trb *)skb->cb;
+ mtk_port_trb_init(port, trb, TRB_CMD_ENABLE, mtk_port_open_trb_complete);
+ kref_get(&trb->kref);
+
+ ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev, skb, true);
+ if (ret) {
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to submit trb for port(%s), ret=%d\n",
+ port->info.name, ret);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ return ret;
+ }
+
+start_wait:
+ ret = wait_event_interruptible_timeout(port->trb_wq, trb->status <= 0,
+ MTK_DFLT_TRB_TIMEOUT);
+ if (ret == -ERESTARTSYS)
+ goto start_wait;
+ else if (!ret)
+ ret = -ETIMEDOUT;
+ else
+ ret = trb->status;
+
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return ret;
+}
+
+int mtk_port_ch_disable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr = port->port_mngr;
+ struct sk_buff *skb;
+ struct trb *trb;
+ int ret;
+
+ skb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ trb = (struct trb *)skb->cb;
+ mtk_port_trb_init(port, trb, TRB_CMD_DISABLE, mtk_port_close_trb_complete);
+ kref_get(&trb->kref);
+
+ ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev, skb, true);
+ if (ret) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to submit trb for port(%s), ret=%d\n",
+ port->info.name, ret);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ return ret;
+ }
+
+start_wait:
+ ret = wait_event_interruptible_timeout(port->trb_wq, trb->status <= 0,
+ MTK_DFLT_TRB_TIMEOUT);
+ if (ret == -ERESTARTSYS)
+ goto start_wait;
+ else if (!ret)
+ ret = -ETIMEDOUT;
+ else
+ ret = trb->status;
+
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return ret;
+}
+
+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_stale_list *s_list;
+ int ret = -ENOMEM;
+ int dev_id;
+
+ s_list = mtk_port_stale_list_init(ctrl_blk, &dev_id);
+ if (!s_list) {
+ dev_err((ctrl_blk->mdev)->dev, "Failed to init mtk_stale_list\n");
+ goto err_out;
+ }
+
+ port_mngr = devm_kzalloc(ctrl_blk->mdev->dev, sizeof(*port_mngr), GFP_KERNEL);
+ if (unlikely(!port_mngr)) {
+ dev_err((ctrl_blk->mdev)->dev, "Failed to alloc memory for port_mngr\n");
+ goto err_exit_stale_list;
+ }
+
+ port_mngr->ctrl_blk = ctrl_blk;
+ port_mngr->dev_id = dev_id;
+
+ ret = mtk_port_tbl_create(port_mngr, port_cfg, port_cnt, s_list);
+ if (unlikely(ret)) {
+ dev_err((ctrl_blk->mdev)->dev, "Failed to create port_tbl\n");
+ goto err_free_port_mngr;
+ }
+
+ ctrl_blk->port_mngr = port_mngr;
+
+ return ret;
+
+err_free_port_mngr:
+ mtk_port_tbl_destroy(port_mngr, s_list);
+ devm_kfree(ctrl_blk->mdev->dev, port_mngr);
+err_exit_stale_list:
+ mtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);
+err_out:
+ return ret;
+}
+
+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk)
+{
+ struct mtk_port_mngr *port_mngr = ctrl_blk->port_mngr;
+ struct mtk_stale_list *s_list;
+ int dev_id;
+
+ s_list = mtk_port_stale_list_search(port_mngr->ctrl_blk->mdev->dev_str);
+ dev_id = port_mngr->dev_id;
+
+ mtk_port_tbl_destroy(port_mngr, s_list);
+
+ devm_kfree(ctrl_blk->mdev->dev, port_mngr);
+ ctrl_blk->port_mngr = NULL;
+ mtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);
+}
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
new file mode 100644
index 000000000000..bd4291408bc2
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PORT_H__
+#define __MTK_PORT_H__
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/radix-tree.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "mtk_ctrl_plane.h"
+#include "mtk_dev.h"
+
+#define MTK_PEER_ID_MASK (0xF000)
+#define MTK_PEER_ID_SHIFT (12)
+#define MTK_PEER_ID(ch) (((ch) & MTK_PEER_ID_MASK) >> MTK_PEER_ID_SHIFT)
+#define MTK_PEER_ID_SAP (0x1)
+#define MTK_PEER_ID_MD (0x2)
+#define MTK_CH_ID_MASK (0x0FFF)
+#define MTK_CH_ID(ch) ((ch) & MTK_CH_ID_MASK)
+#define MTK_DFLT_MAX_DEV_CNT (10)
+#define MTK_DFLT_PORT_NAME_LEN (20)
+
+/* Mapping MTK_PEER_ID and mtk_port_tbl index */
+#define MTK_PORT_TBL_TYPE(ch) (MTK_PEER_ID(ch) - 1)
+
+/* ccci header length + reserved space that is used in exception flow */
+#define MTK_CCCI_H_ELEN (128)
+
+#define MTK_HDR_FLD_AST ((u32)BIT(31))
+#define MTK_HDR_FLD_SEQ GENMASK(30, 16)
+#define MTK_HDR_FLD_CHN GENMASK(15, 0)
+
+#define MTK_INFO_FLD_EN ((u16)BIT(15))
+#define MTK_INFO_FLD_CHID GENMASK(14, 0)
+
+enum mtk_port_status {
+ PORT_S_DFLT = 0,
+ PORT_S_ENABLE,
+ PORT_S_OPEN,
+ PORT_S_RD,
+ PORT_S_WR,
+ PORT_S_FLUSH,
+ PORT_S_ON_STALE_LIST,
+ PORT_S_STOP,
+};
+
+enum mtk_ccci_ch {
+ /* to sAP */
+ CCCI_SAP_CONTROL_RX = 0x1000,
+ CCCI_SAP_CONTROL_TX = 0x1001,
+ /* to MD */
+ CCCI_CONTROL_RX = 0x2000,
+ CCCI_CONTROL_TX = 0x2001,
+};
+
+enum mtk_port_flag {
+ PORT_F_DFLT = 0,
+ PORT_F_BLOCKING = BIT(1),
+ PORT_F_ALLOW_DROP = BIT(2),
+ PORT_F_FORCE_SEND = BIT(6),
+};
+
+enum mtk_port_tbl {
+ PORT_TBL_SAP,
+ PORT_TBL_MD,
+ PORT_TBL_MAX
+};
+
+enum mtk_port_type {
+ PORT_TYPE_INTERNAL,
+ PORT_TYPE_MAX
+};
+
+struct mtk_internal_port {
+ void *arg;
+ int (*recv_cb)(void *arg, struct sk_buff *skb);
+};
+
+struct mtk_port_cfg {
+ enum mtk_ccci_ch tx_ch;
+ enum mtk_ccci_ch rx_ch;
+ enum mtk_port_type type;
+ char name[MTK_DFLT_PORT_NAME_LEN];
+ unsigned char flags;
+};
+
+struct mtk_port {
+ struct mtk_port_cfg info;
+ struct kref kref;
+ bool enable;
+ unsigned long status;
+ unsigned int minor;
+ unsigned short tx_seq;
+ unsigned short rx_seq;
+ unsigned int tx_mtu;
+ unsigned int rx_mtu;
+ u32 tx_frag_size;
+ u32 rx_frag_size;
+ struct sk_buff_head rx_skb_list;
+ unsigned int rx_data_len;
+ unsigned int rx_buf_size;
+ wait_queue_head_t trb_wq;
+ wait_queue_head_t rx_wq;
+ struct list_head stale_entry;
+ char dev_str[MTK_DEV_STR_LEN];
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_internal_port i_priv;
+};
+
+struct mtk_port_mngr {
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct radix_tree_root port_tbl[PORT_TBL_MAX];
+ unsigned int port_cnt;
+ int dev_id;
+};
+
+struct mtk_stale_list {
+ struct list_head entry;
+ struct list_head ports;
+ char dev_str[MTK_DEV_STR_LEN];
+ int dev_id;
+ rwlock_t port_mngr_lock;
+};
+
+struct mtk_ccci_header {
+ __le32 packet_header;
+ __le32 packet_len;
+ __le32 status;
+ __le32 ex_msg;
+};
+
+struct mtk_port_layer_cfg {
+ struct mtk_port_cfg *port_cfg;
+ int port_cnt;
+};
+
+extern const struct port_ops *ports_ops[PORT_TYPE_MAX];
+
+void mtk_port_release(struct kref *port_kref);
+void mtk_port_trb_free(struct kref *trb_kref);
+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name);
+void mtk_port_stale_list_grp_cleanup(void);
+int mtk_port_add_header(struct sk_buff *skb);
+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb);
+int mtk_port_status_check(struct mtk_port *port);
+int mtk_port_send_data(struct mtk_port *port, void *data);
+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data);
+int mtk_port_ch_enable(struct mtk_port *port);
+int mtk_port_ch_disable(struct mtk_port *port);
+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt);
+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk);
+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
+ int (*trb_complete)(struct sk_buff *skb));
+#endif /* __MTK_PORT_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
new file mode 100644
index 000000000000..e3a2de6d2f29
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+#include <linux/netdevice.h>
+
+#include "mtk_port_io.h"
+
+static int mtk_port_get_locked(struct mtk_port *port)
+{
+ int ret = 0;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ if (!port) {
+ mutex_unlock(&port_mngr_grp_mtx);
+ pr_err("Port does not exist\n");
+ return -ENODEV;
+ }
+ kref_get(&port->kref);
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return ret;
+}
+
+static void mtk_port_put_locked(struct mtk_port *port)
+{
+ mutex_lock(&port_mngr_grp_mtx);
+ kref_put(&port->kref, mtk_port_release);
+ mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static void mtk_port_struct_init(struct mtk_port *port)
+{
+ port->tx_seq = 0;
+ port->rx_seq = -1;
+ clear_bit(PORT_S_ENABLE, &port->status);
+ kref_init(&port->kref);
+ skb_queue_head_init(&port->rx_skb_list);
+ port->rx_buf_size = MTK_RX_BUF_SIZE;
+ init_waitqueue_head(&port->trb_wq);
+ init_waitqueue_head(&port->rx_wq);
+}
+
+static int mtk_port_internal_init(struct mtk_port *port)
+{
+ mtk_port_struct_init(port);
+ port->enable = false;
+
+ return 0;
+}
+
+static void mtk_port_internal_exit(struct mtk_port *port)
+{
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ ports_ops[port->info.type]->disable(port);
+}
+
+static void mtk_port_reset(struct mtk_port *port)
+{
+ port->tx_seq = 0;
+ port->rx_seq = -1;
+}
+
+static void mtk_port_internal_enable(struct mtk_port *port)
+{
+ int ret;
+
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ ret = mtk_port_ch_enable(port);
+ if (ret && ret != -EBUSY)
+ return;
+
+ set_bit(PORT_S_WR, &port->status);
+ set_bit(PORT_S_ENABLE, &port->status);
+}
+
+static void mtk_port_internal_disable(struct mtk_port *port)
+{
+ if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ clear_bit(PORT_S_WR, &port->status);
+ mtk_port_ch_disable(port);
+}
+
+static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+ struct mtk_internal_port *priv;
+ int ret = -ENXIO;
+
+ if (!test_bit(PORT_S_OPEN, &port->status))
+ goto drop_data;
+
+ priv = &port->i_priv;
+ if (!priv->recv_cb || !priv->arg)
+ goto drop_data;
+
+ ret = priv->recv_cb(priv->arg, skb);
+ return ret;
+
+drop_data:
+ dev_kfree_skb_any(skb);
+ return ret;
+}
+
+static int mtk_port_common_open(struct mtk_port *port)
+{
+ int ret = 0;
+
+ if (!test_bit(PORT_S_ENABLE, &port->status))
+ return -ENODEV;
+
+ if (test_bit(PORT_S_OPEN, &port->status))
+ return -EBUSY;
+
+ skb_queue_purge(&port->rx_skb_list);
+ set_bit(PORT_S_OPEN, &port->status);
+ clear_bit(PORT_S_FLUSH, &port->status);
+
+ return ret;
+}
+
+static void mtk_port_common_close(struct mtk_port *port)
+{
+ clear_bit(PORT_S_OPEN, &port->status);
+
+ skb_queue_purge(&port->rx_skb_list);
+ port->rx_data_len = 0;
+
+ set_bit(PORT_S_FLUSH, &port->status);
+ wake_up_interruptible_all(&port->trb_wq);
+ wake_up_interruptible_all(&port->rx_wq);
+}
+
+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_port *port;
+ int ret;
+
+ ctrl_blk = mdev->ctrl_blk;
+ port_mngr = ctrl_blk->port_mngr;
+
+ port = mtk_port_search_by_name(port_mngr, name);
+ if (port && port->info.type != PORT_TYPE_INTERNAL) {
+ port = NULL;
+ goto out;
+ }
+
+ ret = mtk_port_get_locked(port);
+ if (ret)
+ goto out;
+
+ ret = mtk_port_common_open(port);
+ if (ret) {
+ mtk_port_put_locked(port);
+ port = NULL;
+ goto out;
+ }
+
+ if (flag & O_NONBLOCK)
+ port->info.flags &= ~PORT_F_BLOCKING;
+ else
+ port->info.flags |= PORT_F_BLOCKING;
+out:
+ return port;
+}
+
+int mtk_port_internal_close(void *i_port)
+{
+ struct mtk_port *port = i_port;
+ int ret = 0;
+
+ if (!port) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ if (!test_bit(PORT_S_OPEN, &port->status)) {
+ pr_err("Port(%s) has been closed\n", port->info.name);
+ ret = -EBADF;
+ goto end;
+ }
+
+ mtk_port_common_close(port);
+ mtk_port_put_locked(port);
+end:
+ return ret;
+}
+
+int mtk_port_internal_write(void *i_port, struct sk_buff *skb)
+{
+ struct mtk_port *port = i_port;
+
+ if (!port || !skb) {
+ if (skb)
+ dev_kfree_skb_any(skb);
+ pr_err_ratelimited("Internal write: invalid input\n");
+ return -EINVAL;
+ }
+ return mtk_port_send_data(port, skb);
+}
+
+void mtk_port_internal_recv_register(void *i_port,
+ int (*cb)(void *priv, struct sk_buff *skb),
+ void *arg)
+{
+ struct mtk_port *port = i_port;
+ struct mtk_internal_port *priv;
+
+ priv = &port->i_priv;
+ priv->arg = arg;
+ priv->recv_cb = cb;
+}
+
+int mtk_port_io_init(void)
+{
+ return 0;
+}
+
+void mtk_port_io_exit(void)
+{
+}
+
+static const struct port_ops port_internal_ops = {
+ .init = mtk_port_internal_init,
+ .exit = mtk_port_internal_exit,
+ .reset = mtk_port_reset,
+ .enable = mtk_port_internal_enable,
+ .disable = mtk_port_internal_disable,
+ .recv = mtk_port_internal_recv,
+};
+
+const struct port_ops *ports_ops[PORT_TYPE_MAX] = {
+ &port_internal_ops,
+};
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
new file mode 100644
index 000000000000..0c10e893b7e0
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PORT_IO_H__
+#define __MTK_PORT_IO_H__
+
+#include <linux/skbuff.h>
+
+#include "mtk_port.h"
+
+#define MTK_RX_BUF_SIZE (1024 * 1024)
+
+extern struct mutex port_mngr_grp_mtx;
+
+struct port_ops {
+ int (*init)(struct mtk_port *port);
+ void (*exit)(struct mtk_port *port);
+ void (*reset)(struct mtk_port *port);
+ void (*enable)(struct mtk_port *port);
+ void (*disable)(struct mtk_port *port);
+ int (*recv)(struct mtk_port *port, struct sk_buff *skb);
+};
+
+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);
+int mtk_port_internal_close(void *i_port);
+int mtk_port_internal_write(void *i_port, struct sk_buff *skb);
+void mtk_port_internal_recv_register(void *i_port,
+ int (*cb)(void *priv, struct sk_buff *skb),
+ void *arg);
+
+int mtk_port_io_init(void);
+void mtk_port_io_exit(void);
+
+#endif /* __MTK_PORT_IO_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
index c1bb787ee981..8611561dd67c 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
@@ -4,6 +4,7 @@
*/
#include "mtk_cldma.h"
+#include "mtk_port.h"
#include "mtk_trans_ctrl.h"
#define TRB_SRV_NUM (1)
@@ -13,12 +14,34 @@ static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
{0},
};
+/* the number of RX GPDs should be at last two */
static const struct queue_info mtk_queue_info_m9xx[] = {
+ {CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+ {CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+};
+
+static const struct mtk_port_cfg port_cfg_m9xx[] = {
+ {CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
+ PORT_F_ALLOW_DROP},
+ {CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
+ PORT_F_ALLOW_DROP},
+};
+
+static struct mtk_port_layer_cfg port_layer_cfg_m9xx = {
+ .port_cfg = (struct mtk_port_cfg *)port_cfg_m9xx,
+ .port_cnt = ARRAY_SIZE(port_cfg_m9xx),
+};
+
+static struct mtk_ctrl_cfg mtk_ctrl_cfg_m9xx = {
+ .port_layer_cfg = &port_layer_cfg_m9xx,
};
struct mtk_ctrl_info mtk_ctrl_info_m9xx = {
+ .ctrl_cfg = &mtk_ctrl_cfg_m9xx,
+ .srv_cfg = (int **)mtk_srv_cfg_m9xx,
.queue_info = (struct queue_info *)mtk_queue_info_m9xx,
.queue_info_num = ARRAY_SIZE(mtk_queue_info_m9xx),
- .srv_cfg = (int **)mtk_srv_cfg_m9xx,
.trb_srv_num = TRB_SRV_NUM,
};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
index 6efbcd0cba73..d3f862098a1d 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -17,6 +17,8 @@
#include "mtk_trans_ctrl.h"
#include "mtk_pci.h"
#include "mtk_pci_reg.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
#define MTK_PCI_BAR_NUM 6
#define MTK_PCI_TRANSPARENT_ATR_SIZE (0x3F)
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
index 32f00c15f383..6eeed6935550 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
@@ -16,12 +16,13 @@
#include "mtk_ctrl_plane.h"
#include "mtk_dev.h"
#include "mtk_pci.h"
+#include "mtk_port.h"
#include "mtk_trans_ctrl.h"
#define MTK_DFLT_PORT_NAME_LEN (20)
static struct mtk_ctrl_info_desc mtk_ctrl_info_tbl[] = {
- {2304, &ctrl_info_name(m9xx)},
+ {0x01CA, &ctrl_info_name(m9xx)},
{0, NULL},
};
@@ -133,6 +134,7 @@ static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_l
if (!skb)
break;
trb = (struct trb *)skb->cb;
+ kref_get(&trb->kref);
switch (trb->cmd) {
case TRB_CMD_ENABLE:
@@ -152,8 +154,10 @@ static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_l
kick = true;
break;
}
- if (err == -EAGAIN)
+ if (err == -EAGAIN) {
+ kref_put(&trb->kref, mtk_port_trb_free);
return;
+ }
skb_unlink(skb, skb_list);
trb->status = err;
@@ -184,6 +188,8 @@ static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_l
kick = false;
}
+ kref_put(&trb->kref, mtk_port_trb_free);
+
loop++;
} while (loop < TRB_NUM_PER_ROUND);
}
@@ -518,6 +524,7 @@ static void mtk_trans_get_ctrl_info(struct mtk_ctrl_cfg *cfg,
continue;
ctrl_info = ctrl_info_desc->ctrl_info;
+ cfg->port_layer_cfg = ctrl_info->ctrl_cfg->port_layer_cfg;
memcpy(trans->srv_cfg, ctrl_info->srv_cfg,
sizeof(int) * NR_CLDMA * HW_QUE_NUM);
trans->queue_info = ctrl_info->queue_info;
@@ -530,6 +537,7 @@ int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
{
struct mtk_ctrl_trans *trans;
struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_ctrl_cfg *cfg;
int err;
trans = devm_kzalloc(mdev->dev, sizeof(*trans), GFP_KERNEL);
@@ -538,15 +546,19 @@ int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
trans->mdev = mdev;
trans->queues_cnt = 0;
- mtk_trans_get_ctrl_info(NULL, trans, mdev->hw_ver);
- if (!trans->queue_info ||
+ cfg = devm_kzalloc(mdev->dev, sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ goto err_free_trans;
+
+ mtk_trans_get_ctrl_info(cfg, trans, mdev->hw_ver);
+ if (!cfg->port_layer_cfg || !trans->queue_info ||
trans->trb_srv_num <= 0 || trans->trb_srv_num > TRB_SRV_MAX_NUM ||
trans->queue_info_num <= 0) {
dev_err((mdev)->dev, "Failed to get ctrl info!\n");
goto err_free_cfg;
}
- err = mtk_ctrl_init(mdev, &pcie_ctrl_ops);
+ err = mtk_ctrl_init(mdev, &pcie_ctrl_ops, cfg);
if (err)
goto err_free_cfg;
@@ -556,6 +568,8 @@ int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
return 0;
err_free_cfg:
+ devm_kfree(mdev->dev, cfg);
+err_free_trans:
devm_kfree(mdev->dev, trans);
return -ENOMEM;
}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
index 0d25d1f51671..a3ff56ddf86f 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
@@ -12,6 +12,7 @@
#include <linux/types.h>
#include "mtk_dev.h"
+#include "mtk_port.h"
#define TRB_SRV_MAX_NUM (1)
#define HW_QUE_NUM (8)
--
2.34.1
^ permalink raw reply related
* [PATCH v3 6/7] net: wwan: t9xx: Add AT & MBIM WWAN ports
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
Add AT & MBIM ports to the port infrastructure.
The WWAN initialization method is responsible for creating the
corresponding ports using the WWAN framework infrastructure. The
implemented WWAN port operations are start, stop, tx, tx_blocking
and tx_poll.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/t9xx/mtk_port.c | 26 ++
drivers/net/wwan/t9xx/mtk_port.h | 13 +
drivers/net/wwan/t9xx/mtk_port_io.c | 336 ++++++++++++++++++++++++-
drivers/net/wwan/t9xx/mtk_port_io.h | 5 +
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 8 +
5 files changed, 387 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
index fbc2fb7bdc2d..9015be090023 100644
--- a/drivers/net/wwan/t9xx/mtk_port.c
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -819,6 +819,29 @@ int mtk_port_ch_disable(struct mtk_port *port)
return ret;
}
+static int mtk_port_enable_by_type(struct mtk_port_mngr *port_mngr, int tbl_type)
+{
+ struct mtk_port **ports;
+ int ret, idx;
+
+ if (tbl_type < 0 || tbl_type >= PORT_TBL_MAX)
+ return -EINVAL;
+
+ ports = kcalloc(port_mngr->port_cnt, sizeof(struct mtk_port *), GFP_KERNEL);
+ if (!ports)
+ return -ENOMEM;
+
+ ret = radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+ (void **)ports, 0, port_mngr->port_cnt);
+ for (idx = 0; idx < ret; idx++) {
+ if (ports[idx]->enable)
+ ports_ops[ports[idx]->info.type]->enable(ports[idx]);
+ }
+
+ kfree(ports);
+ return 0;
+}
+
static void mtk_port_disable(struct mtk_port_mngr *port_mngr)
{
struct mtk_port **ports;
@@ -852,6 +875,9 @@ void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg)
case FSM_STATE_OFF:
mtk_port_disable(port_mngr);
break;
+ case FSM_STATE_READY:
+ mtk_port_enable_by_type(port_mngr, PORT_TBL_MD);
+ break;
default:
break;
}
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
index a201c0007878..4846354981d7 100644
--- a/drivers/net/wwan/t9xx/mtk_port.h
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -56,6 +56,10 @@ enum mtk_ccci_ch {
/* to MD */
CCCI_CONTROL_RX = 0x2000,
CCCI_CONTROL_TX = 0x2001,
+ CCCI_UART2_RX = 0x200A,
+ CCCI_UART2_TX = 0x200C,
+ CCCI_MBIM_RX = 0x20D0,
+ CCCI_MBIM_TX = 0x20D1,
};
enum mtk_port_flag {
@@ -73,6 +77,7 @@ enum mtk_port_tbl {
enum mtk_port_type {
PORT_TYPE_INTERNAL,
+ PORT_TYPE_WWAN,
PORT_TYPE_MAX
};
@@ -81,6 +86,13 @@ struct mtk_internal_port {
int (*recv_cb)(void *arg, struct sk_buff *skb);
};
+struct mtk_wwan_port {
+ /* w_lock protects wwan_port when recv data and disable port at the same time */
+ struct mutex w_lock;
+ int w_type;
+ void *w_port;
+};
+
struct mtk_port_cfg {
enum mtk_ccci_ch tx_ch;
enum mtk_ccci_ch rx_ch;
@@ -110,6 +122,7 @@ struct mtk_port {
char dev_str[MTK_DEV_STR_LEN];
struct mtk_port_mngr *port_mngr;
struct mtk_internal_port i_priv;
+ struct mtk_wwan_port w_priv;
};
struct mtk_port_mngr {
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
index e3a2de6d2f29..882254b74026 100644
--- a/drivers/net/wwan/t9xx/mtk_port_io.c
+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
@@ -3,6 +3,10 @@
* Copyright (c) 2022, MediaTek Inc.
*/
#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/wwan.h>
#include "mtk_port_io.h"
@@ -41,6 +45,145 @@ static void mtk_port_struct_init(struct mtk_port *port)
init_waitqueue_head(&port->rx_wq);
}
+static int mtk_port_copy_data_from(void *to, union user_buf from, unsigned int len,
+ unsigned int offset, bool from_user_space)
+{
+ if (from_user_space) {
+ if (copy_from_user(to, from.ubuf + offset, len))
+ return -EINVAL;
+ } else {
+ memcpy(to, from.kbuf + offset, len);
+ }
+
+ return 0;
+}
+
+static int mtk_port_common_write_frag_skb(struct mtk_port *port, struct sk_buff *skb,
+ union user_buf buf, u32 packet_size,
+ u32 cur_pos, bool from_user_space)
+{
+ struct sk_buff *frag_skb, *tmp = NULL;
+ u32 frag_size;
+ int ret;
+
+ frag_size = min(packet_size, port->tx_frag_size);
+ ret = mtk_port_copy_data_from(skb_put(skb, frag_size),
+ buf, frag_size,
+ cur_pos, from_user_space);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy skb for port(%s)\n", port->info.name);
+ goto err_reset_skb;
+ }
+ cur_pos += frag_size;
+ packet_size -= frag_size;
+ if (!packet_size)
+ return cur_pos;
+
+ while (packet_size > 0) {
+ frag_skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+ if (!frag_skb) {
+ ret = -ENOMEM;
+ goto err_free_frag_list;
+ }
+
+ frag_size = min(packet_size, port->tx_frag_size);
+ ret = mtk_port_copy_data_from(skb_put(frag_skb, frag_size),
+ buf, frag_size,
+ cur_pos, from_user_space);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy frag_skb for port(%s)\n", port->info.name);
+ dev_kfree_skb_any(frag_skb);
+ goto err_free_frag_list;
+ }
+ skb->data_len += frag_size;
+ skb->len += frag_size;
+ cur_pos += frag_size;
+ packet_size -= frag_size;
+ if (!tmp)
+ skb_shinfo(skb)->frag_list = frag_skb;
+ else
+ tmp->next = frag_skb;
+ tmp = frag_skb;
+ }
+ return cur_pos;
+
+err_free_frag_list:
+ frag_skb = skb_shinfo(skb)->frag_list;
+ while (frag_skb) {
+ tmp = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = tmp;
+ }
+ skb_shinfo(skb)->frag_list = NULL;
+err_reset_skb:
+ skb->data_len = 0;
+ return ret;
+}
+
+static int mtk_port_common_write(struct mtk_port *port, union user_buf buf, unsigned int len,
+ bool from_user_space)
+{
+ u32 packet_size, left_cnt = len, cur_pos;
+ struct sk_buff *skb;
+ int ret;
+
+ if (len == 0)
+ return -EINVAL;
+
+start_write:
+ ret = mtk_port_status_check(port);
+ if (ret)
+ goto end_write;
+
+ skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto end_write;
+ }
+
+ skb_reserve(skb, sizeof(struct mtk_ccci_header));
+
+ packet_size = min_t(u32, left_cnt, port->tx_mtu - sizeof(struct mtk_ccci_header));
+ cur_pos = len - left_cnt;
+ /* Support scatter gather transmission */
+ if (port->tx_mtu > port->tx_frag_size) {
+ ret = mtk_port_common_write_frag_skb(port, skb, buf, packet_size,
+ cur_pos, from_user_space);
+ if (ret < 0)
+ goto err_free_skb;
+ } else {
+ ret = mtk_port_copy_data_from(skb_put(skb, packet_size),
+ buf, packet_size,
+ cur_pos, from_user_space);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy data for port(%s)\n", port->info.name);
+ goto err_free_skb;
+ }
+ }
+
+ ret = mtk_port_send_data(port, skb);
+ if (ret < 0) {
+ if (ret == -EINTR)
+ left_cnt -= packet_size;
+ goto end_write;
+ }
+
+ left_cnt -= ret;
+ if (left_cnt)
+ goto start_write;
+ else
+ goto end_write;
+
+err_free_skb:
+ dev_kfree_skb_any(skb);
+end_write:
+ return (len > left_cnt) ? (len - left_cnt) : ret;
+}
+
static int mtk_port_internal_init(struct mtk_port *port)
{
mtk_port_struct_init(port);
@@ -101,7 +244,6 @@ static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)
return ret;
drop_data:
- dev_kfree_skb_any(skb);
return ret;
}
@@ -234,6 +376,198 @@ static const struct port_ops port_internal_ops = {
.recv = mtk_port_internal_recv,
};
+static int mtk_port_wwan_open(struct wwan_port *w_port)
+{
+ struct mtk_port *port;
+ int ret;
+
+ port = wwan_port_get_drvdata(w_port);
+ ret = mtk_port_get_locked(port);
+ if (ret)
+ return ret;
+
+ ret = mtk_port_common_open(port);
+ if (ret)
+ mtk_port_put_locked(port);
+
+ return ret;
+}
+
+static void mtk_port_wwan_close(struct wwan_port *w_port)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+
+ mtk_port_common_close(port);
+ mtk_port_put_locked(port);
+}
+
+static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ union user_buf user_buf;
+ int ret;
+
+ if (unlikely(!skb->len)) {
+ consume_skb(skb);
+ return 0;
+ }
+
+ port->info.flags &= ~PORT_F_BLOCKING;
+ user_buf.kbuf = (void *)skb->data;
+ ret = mtk_port_common_write(port, user_buf, skb->len, false);
+ if (ret < 0)
+ return ret;
+
+ consume_skb(skb);
+ return 0;
+}
+
+static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ union user_buf user_buf;
+ int ret;
+
+ if (unlikely(!skb->len)) {
+ consume_skb(skb);
+ return 0;
+ }
+
+ port->info.flags |= PORT_F_BLOCKING;
+ user_buf.kbuf = (void *)skb->data;
+ ret = mtk_port_common_write(port, user_buf, skb->len, false);
+ if (ret < 0)
+ return ret;
+
+ consume_skb(skb);
+ return 0;
+}
+
+static __poll_t mtk_port_wwan_poll(struct wwan_port *w_port, struct file *file,
+ struct poll_table_struct *poll)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ union ctrl_hif_cmd_data hif_cmd;
+ struct mtk_ctrl_blk *ctrl_blk;
+ __poll_t mask = 0;
+
+ poll_wait(file, &port->trb_wq, poll);
+ if (mtk_port_status_check(port))
+ return EPOLLERR | EPOLLHUP;
+
+ ctrl_blk = port->port_mngr->ctrl_blk;
+ hif_cmd.rx_ch = port->info.rx_ch;
+ if (!ctrl_blk->ops->send_cmd(ctrl_blk->mdev, HIF_CTRL_CMD_CHECK_TX_FULL, &hif_cmd))
+ mask |= EPOLLOUT | EPOLLWRNORM;
+
+ return mask;
+}
+
+static const struct wwan_port_ops wwan_ops = {
+ .start = mtk_port_wwan_open,
+ .stop = mtk_port_wwan_close,
+ .tx = mtk_port_wwan_write,
+ .tx_blocking = mtk_port_wwan_write_blocking,
+ .tx_poll = mtk_port_wwan_poll,
+};
+
+static int mtk_port_wwan_init(struct mtk_port *port)
+{
+ mtk_port_struct_init(port);
+ port->enable = false;
+
+ mutex_init(&port->w_priv.w_lock);
+
+ switch (port->info.rx_ch) {
+ case CCCI_MBIM_RX:
+ port->w_priv.w_type = WWAN_PORT_MBIM;
+ break;
+ case CCCI_UART2_RX:
+ port->w_priv.w_type = WWAN_PORT_AT;
+ break;
+ default:
+ port->w_priv.w_type = WWAN_PORT_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static void mtk_port_wwan_exit(struct mtk_port *port)
+{
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ ports_ops[port->info.type]->disable(port);
+}
+
+static void mtk_port_wwan_enable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr;
+ int ret;
+
+ port_mngr = port->port_mngr;
+
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ ret = mtk_port_ch_enable(port);
+ if (ret && ret != -EBUSY)
+ return;
+
+ port->w_priv.w_port = wwan_create_port(port_mngr->ctrl_blk->mdev->dev,
+ port->w_priv.w_type,
+ &wwan_ops, NULL, port);
+ if (IS_ERR(port->w_priv.w_port)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to create wwan port for (%s)\n", port->info.name);
+ port->w_priv.w_port = NULL;
+ mtk_port_ch_disable(port);
+ return;
+ }
+
+ set_bit(PORT_S_WR, &port->status);
+ set_bit(PORT_S_ENABLE, &port->status);
+}
+
+static void mtk_port_wwan_disable(struct mtk_port *port)
+{
+ struct wwan_port *w_port;
+
+ if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ clear_bit(PORT_S_WR, &port->status);
+ w_port = port->w_priv.w_port;
+ mutex_lock(&port->w_priv.w_lock);
+ port->w_priv.w_port = NULL;
+ mutex_unlock(&port->w_priv.w_lock);
+
+ mtk_port_ch_disable(port);
+ wwan_remove_port(w_port);
+}
+
+static int mtk_port_wwan_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+ mutex_lock(&port->w_priv.w_lock);
+ if (!port->w_priv.w_port) {
+ mutex_unlock(&port->w_priv.w_lock);
+ return -ENXIO;
+ }
+
+ wwan_port_rx(port->w_priv.w_port, skb);
+ mutex_unlock(&port->w_priv.w_lock);
+ return 0;
+}
+
+static const struct port_ops port_wwan_ops = {
+ .init = mtk_port_wwan_init,
+ .exit = mtk_port_wwan_exit,
+ .reset = mtk_port_reset,
+ .enable = mtk_port_wwan_enable,
+ .disable = mtk_port_wwan_disable,
+ .recv = mtk_port_wwan_recv,
+};
+
const struct port_ops *ports_ops[PORT_TYPE_MAX] = {
&port_internal_ops,
+ &port_wwan_ops,
};
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
index 0c10e893b7e0..ea92cd22dba0 100644
--- a/drivers/net/wwan/t9xx/mtk_port_io.h
+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
@@ -23,6 +23,11 @@ struct port_ops {
int (*recv)(struct mtk_port *port, struct sk_buff *skb);
};
+union user_buf {
+ void __user *ubuf;
+ void *kbuf;
+};
+
void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);
int mtk_port_internal_close(void *i_port);
int mtk_port_internal_write(void *i_port, struct sk_buff *skb);
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
index 8611561dd67c..aab09cab360c 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
@@ -16,6 +16,10 @@ static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
/* the number of RX GPDs should be at last two */
static const struct queue_info mtk_queue_info_m9xx[] = {
+ {CCCI_UART2_TX, CCCI_UART2_RX, CLDMA1, TXQ(5), RXQ(5),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+ {CCCI_MBIM_TX, CCCI_MBIM_RX, CLDMA1, TXQ(2), RXQ(2),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
{CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),
Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),
@@ -23,6 +27,10 @@ static const struct queue_info mtk_queue_info_m9xx[] = {
};
static const struct mtk_port_cfg port_cfg_m9xx[] = {
+ {CCCI_UART2_TX, CCCI_UART2_RX, PORT_TYPE_WWAN, "AT",
+ PORT_F_ALLOW_DROP},
+ {CCCI_MBIM_TX, CCCI_MBIM_RX, PORT_TYPE_WWAN, "MBIM",
+ PORT_F_ALLOW_DROP},
{CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
PORT_F_ALLOW_DROP},
{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
--
2.34.1
^ permalink raw reply related
* [PATCH v3 7/7] net: wwan: t9xx: Add maintainers entry
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
Add MAINTAINERS entry for the MediaTek T9XX 5G WWAN modem device
driver.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 461a3eed6129..8155d26bff03 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16494,6 +16494,15 @@ L: netdev@vger.kernel.org
S: Supported
F: drivers/net/wwan/t7xx/
+MEDIATEK T9XX 5G WWAN MODEM DRIVER
+M: Jack Wu <jackbb_wu@compal.com>
+R: Wen-Zhi Huang <wen-zhi.huang@mediatek.com>
+R: Shi-Wei Yeh <shi-wei.yeh@mediatek.com>
+R: Minano Tseng <Minano.tseng@mediatek.com>
+L: netdev@vger.kernel.org
+S: Supported
+F: drivers/net/wwan/t9xx/
+
MEDIATEK USB3 DRD IP DRIVER
M: Chunfeng Yun <chunfeng.yun@mediatek.com>
L: linux-usb@vger.kernel.org
--
2.34.1
^ permalink raw reply related
* [PATCH v3 3/7] net: wwan: t9xx: Add control DMA interface
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
Cross Layer Direct Memory Access(CLDMA) is the hardware
interface used by the control plane and designated to
translate data between the host and the device. It supports
8 hardware queues for the device AP and modem respectively.
CLDMA driver uses General Purpose Descriptor (GPD) to
describe transaction information that can be recognized by
CLDMA hardware. Once CLDMA hardware transaction is started,
it would fetch and parse GPD to transfer data correctly.
To facilitate the CLDMA transaction, a GPD ring for each
queue is used. Once the transaction is started, CLDMA
hardware will traverse the GPD ring to transfer data between
the host and the device until no GPD is available.
CLDMA TX flow:
Once a TX service receives the TX data from the port layer,
it uses APIs exported by the CLDMA driver to configure GPD
with the DMA address of TX data. After that, the service
triggers CLDMA to fetch the first available GPD to transfer
data.
CLDMA RX flow:
When there is RX data from the MD, CLDMA hardware asserts an
interrupt to notify the host to fetch data and dispatch it
to FSM (for handshake messages) or the port layer.
After CLDMA opening is finished, All RX GPDs are fulfilled
and ready to receive data from the device.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 4 +-
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 52 +-
drivers/net/wwan/t9xx/pcie/Makefile | 7 +-
drivers/net/wwan/t9xx/pcie/mtk_cldma.c | 1200 +++++++++++++++++++++++
drivers/net/wwan/t9xx/pcie/mtk_cldma.h | 170 ++++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c | 371 +++++++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h | 177 ++++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c | 183 ++++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h | 103 ++
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 24 +
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 39 +
drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h | 1 +
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 579 +++++++++++
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 86 ++
14 files changed, 2992 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
index 07938f3e6fe2..70348696ac44 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
@@ -11,13 +11,14 @@
/**
* mtk_ctrl_init() - Initialize the control plane block.
* @mdev: Pointer to the MTK modem device.
+ * @ops: HIF operations for the control plane.
*
* Allocates and initializes the control plane block
* associated with @mdev.
*
* Return: 0 on success, -ENOMEM on allocation failure.
*/
-int mtk_ctrl_init(struct mtk_md_dev *mdev)
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops)
{
struct mtk_ctrl_blk *ctrl_blk;
@@ -27,6 +28,7 @@ int mtk_ctrl_init(struct mtk_md_dev *mdev)
ctrl_blk->mdev = mdev;
mdev->ctrl_blk = ctrl_blk;
+ ctrl_blk->ops = ops;
return 0;
}
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
index c141876ef95d..88d71ac92084 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -11,12 +11,60 @@
#include "mtk_dev.h"
+enum mtk_trb_cmd_type {
+ TRB_CMD_MIN,
+ TRB_CMD_ENABLE,
+ TRB_CMD_TX,
+ TRB_CMD_DISABLE,
+ TRB_CMD_STOP,
+ TRB_CMD_RECOVER,
+ TRB_CMD_MAX,
+};
+
+enum mtk_hif_dev_ctrl_cmd {
+ HIF_CTRL_CMD_CHECK_TX_FULL,
+};
+
+struct trb_open_priv {
+ u8 log_rg_offset;
+ u32 tx_mtu;
+ u32 rx_mtu;
+ u32 tx_frag_size;
+ u32 rx_frag_size;
+ int (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);
+};
+
+struct trb {
+ u32 channel_id;
+ enum mtk_trb_cmd_type cmd;
+ int status;
+ struct kref kref;
+ void *priv;
+ int (*trb_complete)(struct sk_buff *skb);
+};
+
+union ctrl_hif_cmd_data {
+ u32 rx_ch;
+};
+
+struct mtk_ctrl_hif_ops {
+ int (*init)(struct mtk_md_dev *mdev);
+ int (*exit)(struct mtk_md_dev *mdev);
+ int (*submit_skb)(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send);
+ int (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);
+};
+
+struct mtk_ctrl_cfg;
+struct mtk_ctrl_trans;
+
struct mtk_ctrl_blk {
struct mtk_md_dev *mdev;
- struct mtk_ctrl_trans *trans;
+ struct mtk_ctrl_hif_ops *ops;
+ void *ctrl_hw_priv;
+ struct mtk_ctrl_cfg *cfg;
};
-int mtk_ctrl_init(struct mtk_md_dev *mdev);
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops);
void mtk_ctrl_exit(struct mtk_md_dev *mdev);
#endif /* __MTK_CTRL_PLANE_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/Makefile b/drivers/net/wwan/t9xx/pcie/Makefile
index 7410d1796d27..5252f158b058 100644
--- a/drivers/net/wwan/t9xx/pcie/Makefile
+++ b/drivers/net/wwan/t9xx/pcie/Makefile
@@ -7,4 +7,9 @@ obj-$(CONFIG_MTK_T9XX_PCI) += mtk_t9xx_pcie.o
mtk_t9xx_pcie-y := \
mtk_pci_drv_m9xx.o \
- mtk_pci.o
+ mtk_cldma_drv_m9xx.o \
+ mtk_ctrl_cfg_m9xx.o \
+ mtk_pci.o \
+ mtk_trans_ctrl.o \
+ mtk_cldma.o \
+ mtk_cldma_drv.o
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
new file mode 100644
index 000000000000..7a0815aa2fc8
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
@@ -0,0 +1,1200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include "mtk_pci.h"
+#include "mtk_cldma.h"
+#include "mtk_cldma_drv.h"
+#include "mtk_dev.h"
+
+#define cldma_drv_ops_null NULL
+#define DMA_POOL_NAME_LEN (64)
+#define WAIT_HWO_ROUND (10)
+#define WAIT_HWO_TIME (5)
+#define CLDMA_RETRY_DELAY_MS (100)
+#define NO_BUDGET (0)
+
+static const int mtk_cldma_hw_id_tbl[NR_CLDMA] = {
+ [CLDMA0] = CLDMA0_HW_ID,
+ [CLDMA1] = CLDMA1_HW_ID,
+ [CLDMA4] = CLDMA4_HW_ID,
+};
+
+static inline void mtk_cldma_clr_bd_dsc(struct cldma_drv_info *drv_info,
+ struct bd_dsc *bd_dsc_pool, int nr_bds)
+{
+ struct bd_dsc *bd_dsc;
+ int i;
+
+ for (i = 0; i < nr_bds; i++) {
+ bd_dsc = bd_dsc_pool + i;
+ dma_unmap_single(drv_info->mdev->dev, bd_dsc->data_dma_addr,
+ bd_dsc->data_len, DMA_TO_DEVICE);
+ bd_dsc->data_dma_addr = 0;
+ bd_dsc->data_len = 0;
+ if (bd_dsc->bd->tx_bd.bd_flags & CLDMA_BD_FLAG_EOL) {
+ bd_dsc->bd->tx_bd.bd_flags &= ~CLDMA_BD_FLAG_EOL;
+ break;
+ }
+ }
+}
+
+static void mtk_cldma_tx_done_work(struct work_struct *work)
+{
+ struct txq *txq = container_of(work, struct txq, tx_done_work);
+ struct cldma_drv_info *drv_info;
+ struct cldma_drv_ops *drv_ops;
+ struct mtk_ctrl_trans *trans;
+ struct mtk_md_dev *mdev;
+ struct tx_req *req;
+ unsigned int state;
+ struct trb *trb;
+ int i, hif_id;
+ u32 txqno;
+
+ drv_info = txq->drv_info;
+ hif_id = drv_info->hif_id;
+ txqno = txq->txqno;
+ mdev = drv_info->mdev;
+ drv_ops = drv_info->drv_ops;
+ trans = drv_info->cd->trans;
+
+again:
+ for (i = 0; i < txq->nr_gpds; i++) {
+ req = txq->req_pool + txq->free_idx;
+
+ rmb(); /* ensure HWO setup done before HWO read */
+
+ if (!req->data_vm_addr || (req->gpd->tx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO))
+ break;
+
+ if (txq->nr_bds)
+ mtk_cldma_clr_bd_dsc(drv_info, req->bd_dsc_pool, txq->nr_bds);
+ else
+ dma_unmap_single(mdev->dev, req->data_dma_addr,
+ req->data_len, DMA_TO_DEVICE);
+
+ trb = (struct trb *)req->skb->cb;
+ trb->status = 0;
+ trb->trb_complete(req->skb);
+
+ req->data_vm_addr = NULL;
+ req->data_dma_addr = 0;
+ req->data_len = 0;
+ req->skb = NULL;
+
+ txq->free_idx = (txq->free_idx + 1) % txq->nr_gpds;
+ if (atomic_fetch_inc(&txq->req_budget) == NO_BUDGET)
+ wake_up(&trans->trb_srv[trans->srv_cfg[hif_id][txqno]]->trb_waitq);
+ }
+
+ state = drv_ops->cldma_check_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+ if (state) {
+ if (unlikely(state == LINK_ERROR_VAL))
+ goto out;
+
+ drv_ops->cldma_clr_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+
+ cond_resched();
+
+ goto again;
+ }
+
+out:
+ drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+}
+
+static void mtk_cldma_rx_skb_adjust(struct mtk_md_dev *mdev, struct rxq *rxq,
+ struct rx_req *req)
+{
+ struct bd_dsc *bd_dsc;
+ int i;
+
+ for (i = 0; i < rxq->nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ if (bd_dsc->data_dma_addr) {
+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+ req->frag_size, DMA_FROM_DEVICE);
+ bd_dsc->data_dma_addr = 0;
+ }
+ bd_dsc->skb->len = 0;
+ skb_reset_tail_pointer(bd_dsc->skb);
+ skb_put(bd_dsc->skb,
+ min_t(u16, le16_to_cpu(bd_dsc->bd->rx_bd.data_recv_len),
+ req->frag_size));
+ if (req->skb != bd_dsc->skb) {
+ req->skb->len += bd_dsc->skb->len;
+ req->skb->data_len += bd_dsc->skb->len;
+ }
+ bd_dsc->bd->rx_bd.data_recv_len = 0;
+ bd_dsc->skb = NULL;
+ }
+ if (!rxq->nr_bds) {
+ if (req->data_dma_addr) {
+ dma_unmap_single(mdev->dev, req->data_dma_addr,
+ req->mtu, DMA_FROM_DEVICE);
+ req->data_dma_addr = 0;
+ }
+ req->skb->len = 0;
+ skb_reset_tail_pointer(req->skb);
+ skb_put(req->skb,
+ min_t(u16, le16_to_cpu(req->gpd->rx_gpd.data_recv_len),
+ req->mtu));
+ }
+
+ req->gpd->rx_gpd.data_recv_len = 0;
+}
+
+static int mtk_cldma_reload_rx_skb(struct mtk_md_dev *mdev, struct rxq *rxq,
+ struct rx_req *req)
+{
+ struct sk_buff *tail = NULL;
+ struct bd_dsc *bd_dsc;
+ int nr_bds;
+ int i, ret;
+
+ nr_bds = rxq->nr_bds;
+
+ for (i = 0; i < nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
+ if (!bd_dsc->skb) {
+ dev_warn((mdev)->dev, "Failed to alloc SKB\n");
+ ret = -ENOMEM;
+ goto err_free_skb;
+ }
+ bd_dsc->skb->next = NULL;
+ bd_dsc->data_dma_addr = dma_map_single(mdev->dev, bd_dsc->skb->data,
+ req->frag_size, DMA_FROM_DEVICE);
+ ret = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
+ if (unlikely(ret)) {
+ dev_warn((mdev)->dev, "Failed to map SKB data\n");
+ ret = -EFAULT;
+ goto err_free_skb;
+ }
+ bd_dsc->bd->rx_bd.data_buff_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
+ bd_dsc->bd->rx_bd.data_buff_ptr_l =
+ cpu_to_le32(bd_dsc->data_dma_addr);
+ if (tail) {
+ tail->next = bd_dsc->skb;
+ tail = bd_dsc->skb;
+ continue;
+ }
+ if (!req->skb) {
+ req->skb = bd_dsc->skb;
+ } else {
+ skb_shinfo(req->skb)->frag_list = bd_dsc->skb;
+ tail = bd_dsc->skb;
+ }
+ }
+ if (!nr_bds) {
+ req->skb = __dev_alloc_skb(req->mtu, GFP_KERNEL);
+ if (!req->skb) {
+ ret = -ENOMEM;
+ goto err_free_skb;
+ }
+
+ req->data_dma_addr = dma_map_single(mdev->dev, req->skb->data,
+ req->mtu, DMA_FROM_DEVICE);
+ ret = dma_mapping_error(mdev->dev, req->data_dma_addr);
+ if (unlikely(ret)) {
+ dev_warn((mdev)->dev, "Failed to map SKB data\n");
+ ret = -EFAULT;
+ goto err_free_skb;
+ }
+ req->gpd->rx_gpd.data_buff_ptr_h = cpu_to_le32((u64)req->data_dma_addr >> 32);
+ req->gpd->rx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
+ }
+ return 0;
+
+err_free_skb:
+ if (nr_bds) {
+ if (req->skb)
+ skb_shinfo(req->skb)->frag_list = NULL;
+ for (i = 0; i < nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ if (!bd_dsc->skb)
+ break;
+ if (!dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr))
+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+ req->frag_size, DMA_FROM_DEVICE);
+ bd_dsc->data_dma_addr = 0;
+ bd_dsc->skb->next = NULL;
+ dev_kfree_skb_any(bd_dsc->skb);
+ }
+ } else {
+ req->data_dma_addr = 0;
+ if (req->skb)
+ dev_kfree_skb_any(req->skb);
+ }
+ req->skb = NULL;
+
+ return ret;
+}
+
+static int mtk_cldma_check_rx_req(struct cldma_drv_info *drv_info, struct rxq *rxq)
+{
+ struct rx_req *req = rxq->req_pool + rxq->free_idx;
+ u64 curr_addr;
+ int i;
+
+ curr_addr = drv_info->drv_ops->cldma_get_rx_curr_addr(drv_info, rxq->rxqno);
+ if (unlikely(!curr_addr))
+ return -ENXIO;
+
+ if (req->gpd_dma_addr == curr_addr)
+ return -EAGAIN;
+ for (i = 0; i < WAIT_HWO_ROUND; i++) {
+ udelay(WAIT_HWO_TIME);
+ if (!(READ_ONCE(req->gpd->rx_gpd.gpd_flags) & CLDMA_GPD_FLAG_HWO))
+ break;
+ }
+ if (i == WAIT_HWO_ROUND) {
+ dev_err((drv_info->mdev)->dev, "Failed to check HWO=0\n");
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static bool mtk_cldma_rx_check_again(struct rxq *rxq)
+{
+ struct cldma_drv_info *drv_info;
+ struct cldma_drv_ops *drv_ops;
+ bool need_check_again = false;
+ u32 state;
+ int rxqno;
+
+ drv_info = rxq->drv_info;
+ drv_ops = drv_info->drv_ops;
+ rxqno = rxq->rxqno;
+
+ do {
+ state = drv_ops->cldma_check_intr_status(drv_info, DIR_RX,
+ rxqno, QUEUE_XFER_DONE);
+ if (state) {
+ if (unlikely(state == LINK_ERROR_VAL))
+ break;
+
+ drv_ops->cldma_clr_intr_status(drv_info, DIR_RX,
+ rxqno, QUEUE_XFER_DONE);
+ cond_resched();
+ return true;
+ }
+ } while (need_check_again);
+
+ return false;
+}
+
+static void mtk_cldma_rx_done_work(struct work_struct *work)
+{
+ struct rx_req *req = NULL, *pre_req = NULL;
+ struct rxq *rxq = container_of(work, struct rxq, rx_done_work);
+ struct cldma_drv_info *drv_info;
+ struct cldma_drv_ops *drv_ops;
+ struct mtk_md_dev *mdev;
+ int i, ret, idx;
+
+ drv_info = rxq->drv_info;
+ mdev = drv_info->mdev;
+ drv_ops = drv_info->drv_ops;
+
+again:
+ for (i = 0; i < rxq->nr_gpds; i++) {
+ req = rxq->req_pool + rxq->free_idx;
+ if (!req->skb) {
+ dev_err((mdev)->dev,
+ "Failed to get valid req cldma%d rxq%d req%d\n",
+ drv_info->hw_id, rxq->rxqno, rxq->free_idx);
+ goto out;
+ }
+
+ if (req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO)
+ break;
+
+ mtk_cldma_rx_skb_adjust(mdev, rxq, req);
+ do {
+ ret = rxq->rx_done(req->skb, rxq->arg,
+ atomic_read(&rxq->need_exit) ? true : false);
+ if (ret == -EAGAIN)
+ usleep_range(1000, 2000);
+ else
+ req->skb = NULL;
+ } while (ret == -EAGAIN);
+
+ ret = mtk_cldma_reload_rx_skb(mdev, rxq, req);
+ if (ret)
+ goto out;
+
+ wmb(); /* ensure addr set done before HWO setup done */
+
+ idx = rxq->free_idx == 0 ? rxq->nr_gpds - 1 : rxq->free_idx - 1;
+ pre_req = rxq->req_pool + idx;
+ pre_req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
+ rxq->free_idx = (rxq->free_idx + 1) % rxq->nr_gpds;
+ }
+
+ ret = mtk_cldma_check_rx_req(drv_info, rxq);
+ if (!ret)
+ goto again;
+ else if (ret == -ENXIO)
+ goto out;
+
+ if (!atomic_read(&rxq->need_exit))
+ drv_ops->cldma_resume_queue(drv_info, DIR_RX, rxq->rxqno);
+
+ if (mtk_cldma_rx_check_again(rxq))
+ goto again;
+
+out:
+ drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_XFER_DONE);
+ drv_ops->cldma_clear_ip_busy(drv_info);
+}
+
+static int mtk_cldma_alloc_tx_bd(struct cldma_drv_info *drv_info, struct txq *txq,
+ struct tx_req *req)
+{
+ struct bd_dsc *bd_dsc, *last_bd_dsc = NULL;
+ int i;
+
+ req->bd_dsc_pool = devm_kcalloc(drv_info->mdev->dev, txq->nr_bds,
+ sizeof(*bd_dsc), GFP_KERNEL);
+ if (!req->bd_dsc_pool)
+ return -ENOMEM;
+
+ for (i = 0; i < txq->nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ bd_dsc->bd = dma_pool_zalloc(drv_info->bd_dma_pool, GFP_KERNEL,
+ &bd_dsc->bd_dma_addr);
+ if (!bd_dsc->bd)
+ return -ENOMEM;
+ if (!last_bd_dsc) {
+ req->gpd->tx_gpd.data_buff_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+ req->gpd->tx_gpd.data_buff_ptr_l =
+ cpu_to_le32(bd_dsc->bd_dma_addr);
+ } else {
+ last_bd_dsc->bd->tx_bd.next_bd_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+ last_bd_dsc->bd->tx_bd.next_bd_ptr_l =
+ cpu_to_le32(bd_dsc->bd_dma_addr);
+ }
+ last_bd_dsc = bd_dsc;
+ }
+ return 0;
+}
+
+static struct txq *mtk_cldma_txq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct cldma_drv_ops *drv_ops;
+ struct mtk_ctrl_trans *trans;
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_md_dev *mdev;
+ struct bd_dsc *bd_dsc;
+ struct tx_req *next;
+ struct tx_req *req;
+ u16 tx_frag_size;
+ struct txq *txq;
+ int i, j, ret;
+
+ mdev = drv_info->mdev;
+ ctrl_blk = mdev->ctrl_blk;
+ trans = ctrl_blk->ctrl_hw_priv;
+ drv_ops = drv_info->drv_ops;
+
+ txq = devm_kzalloc(mdev->dev, sizeof(*txq), GFP_KERNEL);
+ if (!txq)
+ return NULL;
+
+ txq->que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & 0xFFFF);
+ txq->drv_info = drv_info;
+ txq->txqno = txq->que->txqno;
+ txq->nr_gpds = txq->que->tx_nr_gpds;
+ atomic_set(&txq->req_budget, txq->que->tx_nr_gpds);
+ txq->is_stopping = false;
+ tx_frag_size = txq->que->tx_frag_size;
+ if (txq->que->tx_mtu > tx_frag_size && tx_frag_size)
+ txq->nr_bds = (txq->que->tx_mtu + tx_frag_size - 1) / tx_frag_size;
+
+ txq->req_pool = devm_kcalloc(mdev->dev, txq->nr_gpds, sizeof(*req), GFP_KERNEL);
+ if (!txq->req_pool)
+ goto err_free_txq;
+
+ for (i = 0; i < txq->nr_gpds; i++) {
+ req = txq->req_pool + i;
+ req->mtu = txq->que->tx_mtu;
+ req->frag_size = tx_frag_size;
+ req->gpd = dma_pool_zalloc(drv_info->gpd_dma_pool, GFP_KERNEL, &req->gpd_dma_addr);
+ if (!req->gpd)
+ goto err_free_req;
+ if (txq->nr_bds) {
+ ret = mtk_cldma_alloc_tx_bd(drv_info, txq, req);
+ if (ret)
+ goto err_free_req;
+ req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;
+ }
+ }
+
+ for (i = 0; i < txq->nr_gpds; i++) {
+ req = txq->req_pool + i;
+ next = txq->req_pool + ((i + 1) % txq->nr_gpds);
+ req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;
+ req->gpd->tx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next->gpd_dma_addr) >> 32);
+ req->gpd->tx_gpd.next_gpd_ptr_l = cpu_to_le32(next->gpd_dma_addr);
+ }
+
+ INIT_WORK(&txq->tx_done_work, mtk_cldma_tx_done_work);
+
+ drv_ops->cldma_stop_queue(drv_info, DIR_TX, txq->txqno);
+ txq->tx_started = false;
+ drv_ops->cldma_setup_start_addr(drv_info, DIR_TX, txq->txqno,
+ txq->req_pool[0].gpd_dma_addr);
+ drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txq->txqno, QUEUE_ERROR);
+ drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txq->txqno, QUEUE_XFER_DONE);
+
+ drv_info->txq[txq->txqno] = txq;
+ return txq;
+
+err_free_req:
+ for (i = 0; i < txq->nr_gpds; i++) {
+ req = txq->req_pool + i;
+ if (!req->gpd)
+ break;
+ if (req->bd_dsc_pool) {
+ for (j = 0; j < txq->nr_bds; j++) {
+ bd_dsc = req->bd_dsc_pool + j;
+ if (!bd_dsc->bd)
+ break;
+ dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
+ bd_dsc->bd_dma_addr);
+ }
+ devm_kfree(mdev->dev, req->bd_dsc_pool);
+ }
+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+ }
+ devm_kfree(mdev->dev, txq->req_pool);
+err_free_txq:
+ devm_kfree(mdev->dev, txq);
+ return NULL;
+}
+
+static void mtk_cldma_txq_free(struct cldma_drv_info *drv_info, u32 txqno)
+{
+ struct cldma_drv_ops *drv_ops;
+ struct mtk_md_dev *mdev;
+ struct bd_dsc *bd_dsc;
+ struct tx_req *req;
+ struct txq *txq;
+ struct trb *trb;
+ int irq_id;
+ int i, j;
+
+ mdev = drv_info->mdev;
+ drv_ops = drv_info->drv_ops;
+
+ txq = drv_info->txq[txqno];
+ drv_info->txq[txqno] = NULL;
+ /* stop HW tx transaction */
+ drv_ops->cldma_stop_queue(drv_info, DIR_TX, txqno);
+ txq->tx_started = false;
+
+ irq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
+ synchronize_irq(irq_id);
+ /* flush on-going work */
+ flush_work(&txq->tx_done_work);
+ drv_ops->cldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
+ drv_ops->cldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_ERROR);
+
+ /* free tx req resource */
+ for (i = 0; i < txq->nr_gpds; i++) {
+ req = txq->req_pool + txq->free_idx;
+ if (req->skb && req->data_len) {
+ if (!txq->nr_bds)
+ dma_unmap_single(mdev->dev, req->data_dma_addr,
+ req->data_len, DMA_TO_DEVICE);
+ for (j = 0; j < txq->nr_bds; j++) {
+ bd_dsc = req->bd_dsc_pool + j;
+ if (!bd_dsc->data_dma_addr)
+ continue;
+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+ bd_dsc->data_len, DMA_TO_DEVICE);
+ }
+ trb = (struct trb *)req->skb->cb;
+ trb->status = -EPIPE;
+ trb->trb_complete(req->skb);
+ }
+ for (j = 0; j < txq->nr_bds; j++) {
+ bd_dsc = req->bd_dsc_pool + j;
+ dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
+ bd_dsc->bd_dma_addr);
+ }
+ if (req->bd_dsc_pool)
+ devm_kfree(mdev->dev, req->bd_dsc_pool);
+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+ txq->free_idx = (txq->free_idx + 1) % txq->nr_gpds;
+ }
+
+ devm_kfree(mdev->dev, txq->req_pool);
+ devm_kfree(mdev->dev, txq);
+}
+
+static int mtk_cldma_alloc_rx_bd(struct cldma_drv_info *drv_info, struct rx_req *req,
+ int nr_bds)
+{
+ struct bd_dsc *bd_dsc, *last_bd_dsc = NULL;
+ struct sk_buff *tail = NULL;
+ struct mtk_md_dev *mdev;
+ u32 left_size;
+ int ret;
+ int i;
+
+ mdev = drv_info->mdev;
+ left_size = req->mtu;
+
+ req->bd_dsc_pool = devm_kcalloc(mdev->dev, nr_bds,
+ sizeof(*bd_dsc), GFP_KERNEL);
+ if (!req->bd_dsc_pool)
+ return -ENOMEM;
+ for (i = 0; i < nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ bd_dsc->bd = dma_pool_zalloc(drv_info->bd_dma_pool, GFP_KERNEL,
+ &bd_dsc->bd_dma_addr);
+ if (!bd_dsc->bd)
+ return -ENOMEM;
+
+ bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
+ if (!bd_dsc->skb)
+ return -ENOMEM;
+ bd_dsc->skb->next = NULL;
+ bd_dsc->data_dma_addr =
+ dma_map_single(mdev->dev, bd_dsc->skb->data,
+ req->frag_size, DMA_FROM_DEVICE);
+ ret = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
+ if (unlikely(ret))
+ return -ENOMEM;
+
+ bd_dsc->bd->rx_bd.data_buff_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
+ bd_dsc->bd->rx_bd.data_buff_ptr_l =
+ cpu_to_le32(bd_dsc->data_dma_addr);
+ bd_dsc->bd->rx_bd.data_allow_len =
+ cpu_to_le16(min(req->frag_size, left_size));
+ left_size -= min(req->frag_size, left_size);
+ if (!last_bd_dsc) {
+ req->gpd->rx_gpd.data_buff_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+ req->gpd->rx_gpd.data_buff_ptr_l =
+ cpu_to_le32(bd_dsc->bd_dma_addr);
+ } else {
+ last_bd_dsc->bd->rx_bd.next_bd_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
+ last_bd_dsc->bd->rx_bd.next_bd_ptr_l =
+ cpu_to_le32(bd_dsc->bd_dma_addr);
+ }
+ last_bd_dsc = bd_dsc;
+ if (tail) {
+ tail->next = bd_dsc->skb;
+ tail = bd_dsc->skb;
+ continue;
+ }
+ if (!req->skb) {
+ req->skb = bd_dsc->skb;
+ } else {
+ skb_shinfo(req->skb)->frag_list = bd_dsc->skb;
+ tail = bd_dsc->skb;
+ }
+ }
+ last_bd_dsc->bd->rx_bd.bd_flags |= CLDMA_BD_FLAG_EOL;
+ return 0;
+}
+
+static void mtk_cldma_rxq_alloc_cancel(struct cldma_drv_info *drv_info, struct rx_req *req,
+ int nr_bds)
+{
+ struct mtk_md_dev *mdev;
+ struct bd_dsc *bd_dsc;
+ int i;
+
+ mdev = drv_info->mdev;
+
+ if (nr_bds) {
+ if (req->skb)
+ skb_shinfo(req->skb)->frag_list = NULL;
+ if (req->bd_dsc_pool) {
+ for (i = 0; i < nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ if (!bd_dsc->bd)
+ break;
+ if (bd_dsc->skb) {
+ if (!dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr))
+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+ req->frag_size, DMA_FROM_DEVICE);
+ bd_dsc->data_dma_addr = 0;
+ bd_dsc->skb->next = NULL;
+ dev_kfree_skb_any(bd_dsc->skb);
+ }
+ dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
+ bd_dsc->bd_dma_addr);
+ }
+ devm_kfree(mdev->dev, req->bd_dsc_pool);
+ }
+ } else {
+ if (req->skb) {
+ if (!dma_mapping_error(mdev->dev, req->data_dma_addr))
+ dma_unmap_single(mdev->dev, req->data_dma_addr,
+ req->mtu, DMA_FROM_DEVICE);
+ req->data_dma_addr = 0;
+ dev_kfree_skb_any(req->skb);
+ }
+ }
+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+}
+
+static struct rxq *mtk_cldma_rxq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)
+{
+ struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+ struct trb *trb = (struct trb *)skb->cb;
+ struct cldma_drv_ops *drv_ops;
+ struct mtk_ctrl_trans *trans;
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_md_dev *mdev;
+ struct rx_req *next;
+ struct rx_req *req;
+ u16 rx_frag_size;
+ struct rxq *rxq;
+ int ret;
+ int i;
+
+ mdev = drv_info->mdev;
+ ctrl_blk = mdev->ctrl_blk;
+ trans = ctrl_blk->ctrl_hw_priv;
+ drv_ops = drv_info->drv_ops;
+
+ rxq = devm_kzalloc(mdev->dev, sizeof(*rxq), GFP_KERNEL);
+ if (!rxq)
+ return NULL;
+
+ rxq->que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & 0xFFFF);
+ if (rxq->que->rx_nr_gpds < MIN_GPD_NUM) {
+ dev_err((mdev)->dev,
+ "Failed to alloc cldma%d rxq%d due to gpd number < 2\n",
+ drv_info->hw_id, rxq->rxqno);
+ goto err_free_rxq;
+ }
+ rxq->drv_info = drv_info;
+ rxq->rxqno = rxq->que->rxqno;
+ rxq->nr_gpds = rxq->que->rx_nr_gpds;
+ rxq->arg = trb->priv;
+ rxq->rx_done = trb_open_priv->rx_done;
+ atomic_set(&rxq->need_exit, 0);
+ rx_frag_size = rxq->que->rx_frag_size;
+ if (rxq->que->rx_mtu > rx_frag_size && rx_frag_size)
+ rxq->nr_bds = (rxq->que->rx_mtu + rx_frag_size - 1) / rx_frag_size;
+
+ rxq->req_pool = devm_kcalloc(mdev->dev, rxq->nr_gpds, sizeof(*req), GFP_KERNEL);
+ if (!rxq->req_pool)
+ goto err_free_rxq;
+
+ /* setup rx request */
+ for (i = 0; i < rxq->nr_gpds; i++) {
+ req = rxq->req_pool + i;
+ req->mtu = rxq->que->rx_mtu;
+ req->frag_size = rx_frag_size;
+ req->gpd = dma_pool_zalloc(drv_info->gpd_dma_pool, GFP_KERNEL, &req->gpd_dma_addr);
+ if (!req->gpd)
+ goto err_free_req;
+ if (rxq->nr_bds) {
+ ret = mtk_cldma_alloc_rx_bd(drv_info, req, rxq->nr_bds);
+ if (ret)
+ goto err_free_req;
+ req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;
+ } else {
+ req->skb = __dev_alloc_skb(req->mtu, GFP_KERNEL);
+ if (!req->skb)
+ goto err_free_req;
+ req->data_dma_addr = dma_map_single(mdev->dev, req->skb->data,
+ req->mtu, DMA_FROM_DEVICE);
+ ret = dma_mapping_error(mdev->dev, req->data_dma_addr);
+ if (unlikely(ret))
+ goto err_free_req;
+ }
+ }
+
+ for (i = 0; i < rxq->nr_gpds; i++) {
+ req = rxq->req_pool + i;
+ next = rxq->req_pool + ((i + 1) % rxq->nr_gpds);
+ req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;
+ req->gpd->rx_gpd.data_allow_len = cpu_to_le16(req->mtu);
+ req->gpd->rx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next->gpd_dma_addr) >> 32);
+ req->gpd->rx_gpd.next_gpd_ptr_l = cpu_to_le32(next->gpd_dma_addr);
+ if (!rxq->nr_bds) {
+ req->gpd->rx_gpd.data_buff_ptr_h =
+ cpu_to_le32((u64)(req->data_dma_addr) >> 32);
+ req->gpd->rx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
+ }
+ if (i != rxq->nr_gpds - 1)
+ req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
+ }
+
+ INIT_WORK(&rxq->rx_done_work, mtk_cldma_rx_done_work);
+
+ drv_info->rxq[rxq->rxqno] = rxq;
+ drv_ops->cldma_stop_queue(drv_info, DIR_RX, rxq->rxqno);
+ drv_ops->cldma_setup_start_addr(drv_info, DIR_RX,
+ rxq->rxqno, rxq->req_pool[0].gpd_dma_addr);
+ drv_ops->cldma_start_queue(drv_info, DIR_RX, rxq->rxqno);
+ drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_ERROR);
+ drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_XFER_DONE);
+
+ return rxq;
+
+err_free_req:
+ for (i = 0; i < rxq->nr_gpds; i++) {
+ req = rxq->req_pool + i;
+ if (!req->gpd)
+ break;
+ mtk_cldma_rxq_alloc_cancel(drv_info, req, rxq->nr_bds);
+ }
+
+ devm_kfree(mdev->dev, rxq->req_pool);
+err_free_rxq:
+ devm_kfree(mdev->dev, rxq);
+ return NULL;
+}
+
+static void mtk_cldma_rxq_free(struct cldma_drv_info *drv_info, u32 rxqno)
+{
+ struct cldma_drv_ops *drv_ops;
+ struct mtk_md_dev *mdev;
+ struct bd_dsc *bd_dsc;
+ struct rx_req *req;
+ struct rxq *rxq;
+ int irq_id;
+ int i, j;
+
+ mdev = drv_info->mdev;
+ drv_ops = drv_info->drv_ops;
+
+ rxq = drv_info->rxq[rxqno];
+ drv_info->rxq[rxqno] = NULL;
+
+ /* stop HW rx transaction */
+ atomic_set(&rxq->need_exit, 1);
+ drv_ops->cldma_stop_queue(drv_info, DIR_RX, rxqno);
+
+ irq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
+ synchronize_irq(irq_id);
+ /* flush on-going work */
+ flush_work(&rxq->rx_done_work);
+ /* mask L2 RX interrupt again to avoid race condition causing use-after-free issue */
+ drv_ops->cldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_XFER_DONE);
+ drv_ops->cldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_ERROR);
+
+ /* free rx req resource */
+ for (i = 0; i < rxq->nr_gpds; i++) {
+ req = rxq->req_pool + rxq->free_idx;
+ if (!(req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO) &&
+ le16_to_cpu(req->gpd->rx_gpd.data_recv_len)) {
+ mtk_cldma_rx_skb_adjust(mdev, rxq, req);
+ rxq->rx_done(req->skb, rxq->arg, true);
+ req->skb = NULL;
+ }
+ if (req->skb) {
+ if (rxq->nr_bds) {
+ skb_shinfo(req->skb)->frag_list = NULL;
+ } else {
+ if (req->data_dma_addr)
+ dma_unmap_single(mdev->dev, req->data_dma_addr,
+ req->mtu, DMA_FROM_DEVICE);
+ dev_kfree_skb_any(req->skb);
+ }
+ }
+ for (j = 0; j < rxq->nr_bds; j++) {
+ bd_dsc = req->bd_dsc_pool + j;
+ if (bd_dsc->skb) {
+ if (bd_dsc->data_dma_addr)
+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+ req->frag_size, DMA_FROM_DEVICE);
+ bd_dsc->skb->next = NULL;
+ dev_kfree_skb_any(bd_dsc->skb);
+ }
+ dma_pool_free(drv_info->bd_dma_pool,
+ bd_dsc->bd, bd_dsc->bd_dma_addr);
+ }
+ if (req->bd_dsc_pool)
+ devm_kfree(mdev->dev, req->bd_dsc_pool);
+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
+ rxq->free_idx = (rxq->free_idx + 1) % rxq->nr_gpds;
+ }
+
+ devm_kfree(mdev->dev, rxq->req_pool);
+ devm_kfree(mdev->dev, rxq);
+}
+
+static int mtk_cldma_start_xfer(struct cldma_drv_info *drv_info, u32 qno)
+{
+ struct cldma_drv_ops *drv_ops;
+ struct txq *txq;
+ u32 val;
+
+ txq = drv_info->txq[qno];
+ drv_ops = drv_info->drv_ops;
+
+ val = drv_ops->cldma_get_tx_start_addr(drv_info, qno);
+ if (unlikely(val == LINK_ERROR_VAL))
+ return -EIO;
+
+ if (unlikely(!val)) {
+ drv_ops->cldma_drv_init(drv_info);
+ txq = drv_info->txq[qno];
+ drv_ops->cldma_setup_start_addr(drv_info, DIR_TX, qno,
+ txq->req_pool[txq->free_idx].gpd_dma_addr);
+ drv_ops->cldma_start_queue(drv_info, DIR_TX, qno);
+ txq->tx_started = true;
+ } else if (unlikely(!txq->tx_started)) {
+ drv_ops->cldma_start_queue(drv_info, DIR_TX, qno);
+ txq->tx_started = true;
+ } else {
+ drv_ops->cldma_resume_queue(drv_info, DIR_TX, qno);
+ }
+
+ return 0;
+}
+
+int mtk_cldma_init(struct mtk_ctrl_trans *trans)
+{
+ struct cldma_dev *cd;
+
+ cd = devm_kzalloc(trans->mdev->dev, sizeof(*cd), GFP_KERNEL);
+ if (!cd)
+ return -ENOMEM;
+
+ cd->trans = trans;
+ trans->dev = cd;
+
+ return 0;
+}
+
+void mtk_cldma_exit(struct mtk_ctrl_trans *trans)
+{
+ if (!trans->dev)
+ return;
+
+ devm_kfree(trans->mdev->dev, trans->dev);
+ trans->dev = NULL;
+}
+
+static int mtk_cldma_open(struct cldma_dev *cd, struct sk_buff *skb)
+{
+ struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+ struct trb *trb = (struct trb *)skb->cb;
+ struct cldma_drv_info *drv_info;
+ struct queue_info *que;
+ struct txq *txq;
+ struct rxq *rxq;
+ int ret = 0;
+
+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+ drv_info = cd->cldma_drv_info[que->hif_id];
+ if (!drv_info) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (que->tx_mtu == 0 || que->rx_mtu == 0) {
+ dev_err((cd->trans->mdev)->dev,
+ "Failed to enable cldma%d txq%d rxq%d due to wrong mtu\n",
+ drv_info->hw_id, que->txqno, que->rxqno);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ trb_open_priv->tx_mtu = que->tx_mtu;
+ trb_open_priv->rx_mtu = que->rx_mtu;
+ trb_open_priv->tx_frag_size = que->tx_frag_size;
+ trb_open_priv->rx_frag_size = que->rx_frag_size;
+
+ if (drv_info->txq[que->txqno] || drv_info->rxq[que->rxqno]) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ txq = mtk_cldma_txq_alloc(drv_info, skb);
+ if (!txq) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ rxq = mtk_cldma_rxq_alloc(drv_info, skb);
+ if (!rxq) {
+ ret = -ENOMEM;
+ mtk_cldma_txq_free(drv_info, txq->txqno);
+ goto out;
+ }
+
+out:
+ trb->status = ret;
+ trb->trb_complete(skb);
+
+ return ret;
+}
+
+static int mtk_cldma_tx(struct cldma_dev *cd, struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct cldma_drv_info *drv_info;
+ struct mtk_md_dev *mdev;
+ struct queue_info *que;
+ struct txq *txq;
+ int ret;
+
+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+ drv_info = cd->cldma_drv_info[que->hif_id];
+ if (unlikely(!drv_info))
+ return -EPIPE;
+ txq = drv_info->txq[que->txqno];
+ if (unlikely(!txq) || txq->is_stopping)
+ return -EPIPE;
+
+ mdev = drv_info->mdev;
+
+ ret = mtk_cldma_start_xfer(drv_info, que->txqno);
+ if (unlikely(ret))
+ dev_err((mdev)->dev, "Failed to trigger cldma tx\n");
+
+ return ret;
+}
+
+static int mtk_cldma_close(struct cldma_dev *cd, struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct cldma_drv_info *drv_info;
+ struct queue_info *que;
+
+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+ drv_info = cd->cldma_drv_info[que->hif_id];
+ if (unlikely(!drv_info))
+ return -EPIPE;
+
+ if (drv_info->txq[que->txqno])
+ mtk_cldma_txq_free(drv_info, que->txqno);
+ if (drv_info->rxq[que->rxqno])
+ mtk_cldma_rxq_free(drv_info, que->rxqno);
+
+ trb->status = 0;
+ trb->trb_complete(skb);
+
+ return 0;
+}
+
+static int mtk_cldma_txbuf_set(struct cldma_drv_info *drv_info, struct sk_buff *skb,
+ struct tx_req *req, int nr_bds)
+{
+ struct sk_buff *curr_skb, *next_skb;
+ struct mtk_md_dev *mdev;
+ struct bd_dsc *bd_dsc;
+ int ret;
+ int i;
+
+ mdev = drv_info->mdev;
+
+ if (nr_bds) {
+ bd_dsc = req->bd_dsc_pool;
+ curr_skb = skb;
+ for (i = 0; i < nr_bds && curr_skb; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ if (req->bd_dsc_pool == bd_dsc) {
+ bd_dsc->data_len = skb->len - skb->data_len;
+ next_skb = skb_shinfo(skb)->frag_list;
+ } else {
+ bd_dsc->data_len = curr_skb->len;
+ next_skb = curr_skb->next;
+ }
+ bd_dsc->data_dma_addr = dma_map_single(mdev->dev, curr_skb->data,
+ bd_dsc->data_len, DMA_TO_DEVICE);
+ ret = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
+ if (unlikely(ret))
+ goto err_unmap_buffer;
+
+ bd_dsc->bd->tx_bd.data_buff_ptr_h =
+ cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
+ bd_dsc->bd->tx_bd.data_buff_ptr_l = cpu_to_le32(bd_dsc->data_dma_addr);
+ bd_dsc->bd->tx_bd.data_buffer_len = cpu_to_le16(bd_dsc->data_len);
+ curr_skb = next_skb;
+ }
+ bd_dsc->bd->tx_bd.bd_flags = CLDMA_BD_FLAG_EOL;
+ } else {
+ req->data_dma_addr = dma_map_single(mdev->dev, skb->data,
+ skb->len, DMA_TO_DEVICE);
+ ret = dma_mapping_error(mdev->dev, req->data_dma_addr);
+ if (unlikely(ret)) {
+ req->data_dma_addr = 0;
+ goto err_exit;
+ }
+
+ req->gpd->tx_gpd.data_buff_ptr_h = cpu_to_le32((u64)(req->data_dma_addr) >> 32);
+ req->gpd->tx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
+ }
+
+ return 0;
+
+err_unmap_buffer:
+ for (i = 0; i < nr_bds; i++) {
+ bd_dsc = req->bd_dsc_pool + i;
+ if (dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr)) {
+ bd_dsc->data_dma_addr = 0;
+ break;
+ }
+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
+ bd_dsc->data_len, DMA_TO_DEVICE);
+ bd_dsc->data_dma_addr = 0;
+ }
+err_exit:
+ dev_err((mdev)->dev, "Failed to map dma! error:%d\n", ret);
+ return -EAGAIN;
+}
+
+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct cldma_drv_info *drv_info;
+ struct cldma_dev *cd = dev;
+ struct queue_info *que;
+ struct tx_req *req;
+ struct txq *txq;
+ int ret;
+
+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
+ drv_info = cd->cldma_drv_info[que->hif_id];
+ if (unlikely(!drv_info))
+ return -EINVAL;
+
+ txq = drv_info->txq[que->txqno];
+ if (unlikely(!txq))
+ return -EINVAL;
+
+ if (!atomic_read(&txq->req_budget))
+ return -EAGAIN;
+
+ req = txq->req_pool + txq->wr_idx;
+ req->gpd->tx_gpd.debug_id = 0x01;
+ ret = mtk_cldma_txbuf_set(drv_info, skb, req, txq->nr_bds);
+ if (ret)
+ return ret;
+
+ req->gpd->tx_gpd.data_buff_len = cpu_to_le16(skb->len);
+
+ req->data_len = skb->len;
+ req->skb = skb;
+ req->data_vm_addr = skb->data;
+
+ wmb(); /* ensure req and data msg set done before HWO setup */
+
+ req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
+
+ wmb(); /* ensure HWO setup done before index update */
+
+ txq->wr_idx = (txq->wr_idx + 1) % txq->nr_gpds;
+ atomic_dec(&txq->req_budget);
+
+ return 0;
+}
+
+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno)
+{
+ struct cldma_drv_info *drv_info;
+ struct cldma_dev *cd = dev;
+ struct txq *txq;
+
+ if (unlikely(hif_id >= NR_CLDMA || qno >= HW_QUE_NUM || !cd))
+ return -EINVAL;
+
+ drv_info = cd->cldma_drv_info[hif_id];
+ if (!drv_info)
+ return -EINVAL;
+ txq = drv_info->txq[qno];
+ if (!txq)
+ return -EINVAL;
+ return atomic_read(&txq->req_budget);
+}
+
+static int (*trb_act_tbl[TRB_CMD_MAX])(struct cldma_dev *cd, struct sk_buff *skb) = {
+ [TRB_CMD_ENABLE] = mtk_cldma_open,
+ [TRB_CMD_TX] = mtk_cldma_tx,
+ [TRB_CMD_DISABLE] = mtk_cldma_close,
+};
+
+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb)
+{
+ struct cldma_dev *cd;
+ struct trb *trb;
+
+ if (!dev || !skb)
+ return -EINVAL;
+
+ cd = (struct cldma_dev *)dev;
+ trb = (struct trb *)skb->cb;
+
+ if (!(trb->cmd > TRB_CMD_MIN && trb->cmd < TRB_CMD_STOP))
+ return -EINVAL;
+
+ return trb_act_tbl[trb->cmd](cd, skb);
+}
+
+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que)
+{
+ struct cldma_drv_info *drv_info;
+ struct cldma_dev *cd = dev;
+ struct mtk_md_dev *mdev;
+ struct txq *txq;
+ struct rxq *rxq;
+
+ mdev = cd->trans->mdev;
+ drv_info = cd->cldma_drv_info[que->hif_id];
+
+ if (!drv_info) {
+ dev_err((mdev)->dev, "CLDMA%d has not been initialized\n",
+ mtk_cldma_hw_id_tbl[que->hif_id]);
+ return -EINVAL;
+ }
+
+ txq = drv_info->txq[que->txqno];
+ rxq = drv_info->rxq[que->rxqno];
+ if (!txq || !rxq) {
+ dev_err((mdev)->dev,
+ "CLDMA%d txq%d rxq%d has not been enabled\n",
+ mtk_cldma_hw_id_tbl[que->hif_id], que->txqno, que->rxqno);
+ return -EINVAL;
+ }
+
+ if (que->tx_mtu != txq->que->tx_mtu || que->rx_mtu != rxq->que->rx_mtu) {
+ dev_err((mdev)->dev,
+ "Channel:%08x tx_mtu:%08x rx_mtu:%08x do not match ch cfg\n",
+ que->tx_chl, que->tx_mtu, que->rx_mtu);
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
new file mode 100644
index 000000000000..74ce4f2f0b30
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_CLDMA_H__
+#define __MTK_CLDMA_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "mtk_ctrl_plane.h"
+#include "mtk_trans_ctrl.h"
+
+struct mtk_fsm_param;
+
+#define TXQ(N) (N)
+#define RXQ(N) (N)
+
+#define CLDMA_GPD_FLAG_HWO BIT(0)
+#define CLDMA_GPD_FLAG_BDP BIT(1)
+#define CLDMA_GPD_FLAG_BPS BIT(2)
+#define CLDMA_GPD_FLAG_IOC BIT(7)
+#define CLDMA_BD_FLAG_EOL BIT(0)
+
+union gpd {
+ struct {
+ u8 gpd_flags;
+ u8 non_used1;
+ __le16 data_allow_len;
+ __le32 next_gpd_ptr_h;
+ __le32 next_gpd_ptr_l;
+ __le32 data_buff_ptr_h;
+ __le32 data_buff_ptr_l;
+ __le16 data_recv_len;
+ u8 non_used2;
+ u8 debug_id;
+ } rx_gpd;
+
+ struct {
+ u8 gpd_flags;
+ u8 non_used1;
+ u8 non_used2;
+ u8 debug_id;
+ __le32 next_gpd_ptr_h;
+ __le32 next_gpd_ptr_l;
+ __le32 data_buff_ptr_h;
+ __le32 data_buff_ptr_l;
+ __le16 data_buff_len;
+ __le16 non_used3;
+ } tx_gpd;
+} __packed;
+
+union bd {
+ struct {
+ u8 bd_flags;
+ u8 non_used1;
+ __le16 data_allow_len;
+ __le32 next_bd_ptr_h;
+ __le32 next_bd_ptr_l;
+ __le32 data_buff_ptr_h;
+ __le32 data_buff_ptr_l;
+ __le16 data_recv_len;
+ __le16 non_used2;
+ } rx_bd;
+
+ struct {
+ u8 bd_flags;
+ u8 non_used1;
+ __le16 non_used2;
+ __le32 next_bd_ptr_h;
+ __le32 next_bd_ptr_l;
+ __le32 data_buff_ptr_h;
+ __le32 data_buff_ptr_l;
+ __le16 data_buffer_len;
+ u8 extension_len;
+ u8 non_used3;
+ } tx_bd;
+} __packed;
+
+struct bd_dsc {
+ union bd *bd;
+ struct sk_buff *skb;
+ dma_addr_t bd_dma_addr;
+ dma_addr_t data_dma_addr;
+ size_t data_len;
+};
+
+struct rx_req {
+ union gpd *gpd;
+ u32 mtu;
+ struct sk_buff *skb;
+ size_t data_len;
+ dma_addr_t gpd_dma_addr;
+ dma_addr_t data_dma_addr;
+ u32 frag_size;
+ struct bd_dsc *bd_dsc_pool;
+};
+
+struct rxq {
+ struct cldma_drv_info *drv_info;
+ u32 rxqno;
+ struct queue_info *que;
+ struct work_struct rx_done_work;
+ struct rx_req *req_pool;
+ u32 nr_gpds;
+ u32 free_idx;
+ unsigned short rx_done_cnt;
+ void *arg;
+ int (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);
+ u32 nr_bds;
+ atomic_t need_exit;
+};
+
+struct tx_req {
+ union gpd *gpd;
+ u32 mtu;
+ void *data_vm_addr;
+ size_t data_len;
+ dma_addr_t data_dma_addr;
+ dma_addr_t gpd_dma_addr;
+ struct sk_buff *skb;
+ int (*trb_complete)(struct sk_buff *skb);
+ u32 frag_size;
+ struct bd_dsc *bd_dsc_pool;
+};
+
+struct txq {
+ struct cldma_drv_info *drv_info;
+ u32 txqno;
+ struct queue_info *que;
+ struct work_struct tx_done_work;
+ struct tx_req *req_pool;
+ u32 nr_gpds;
+ atomic_t req_budget;
+ u32 wr_idx;
+ u32 free_idx;
+ bool tx_started;
+ bool is_stopping;
+ unsigned short tx_done_cnt;
+ u32 nr_bds;
+};
+
+struct cldma_dev {
+ struct cldma_drv_info *cldma_drv_info[NR_CLDMA];
+ struct mtk_ctrl_trans *trans;
+};
+
+struct cldma_drv_info_desc {
+ u32 hw_ver;
+ struct cldma_drv_ops *drv_ops;
+ struct cldma_hw_regs *hw_regs;
+};
+
+int mtk_cldma_init(struct mtk_ctrl_trans *trans);
+void mtk_cldma_exit(struct mtk_ctrl_trans *trans);
+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb);
+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno);
+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb);
+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans);
+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que);
+
+#define drv_ops_name(NAME) cldma_drv_ops_##NAME
+#define cldma_regs_name(NAME) mtk_cldma_regs_##NAME
+
+#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c
new file mode 100644
index 000000000000..b5d3894dd62c
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "mtk_cldma_drv.h"
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+
+#define WAIT_QUEUE_STOP (70)
+
+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info)
+{
+ struct cldma_hw_regs *hw_regs;
+ struct mtk_md_dev *mdev;
+ int base;
+ u32 val;
+
+ mdev = drv_info->mdev;
+ base = drv_info->base_addr;
+ hw_regs = drv_info->hw_regs;
+
+ /* set CLDMA to 64 bit mode GPD */
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_ul_cfg);
+ val = (val & (~(0x7 << 5))) | ((0x4) << 5);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_cfg, val);
+
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_so_cfg);
+ val = (val & (~(0x7 << 10))) | ((0x4) << 10) | (1 << 2);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_cfg, val);
+
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_rx_work_to_reg_mask_set, ALLQ);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_set,
+ ALLQ << 16);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_clr,
+ ALLQ << 24);
+
+ /* enable interrupt to PCIe */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
+
+ /* disable illegal memory check */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_dummy_0, 1);
+}
+
+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, dma_addr_t addr)
+{
+ struct cldma_hw_regs *hw_regs;
+ unsigned int addr_l;
+ unsigned int addr_h;
+ int base;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX) {
+ addr_l = base + hw_regs->reg_cldma_ul_start_addrl_0 + qno * HW_QUEUE_NUM;
+ addr_h = base + hw_regs->reg_cldma_ul_start_addrh_0 + qno * HW_QUEUE_NUM;
+ } else {
+ addr_l = base + hw_regs->reg_cldma_so_start_addrl_0 + qno * HW_QUEUE_NUM;
+ addr_h = base + hw_regs->reg_cldma_so_start_addrh_0 + qno * HW_QUEUE_NUM;
+ }
+
+ mtk_pci_write32(drv_info->mdev, addr_l, (u32)addr);
+ mtk_pci_write32(drv_info->mdev, addr_h, (u32)((u64)addr >> 32));
+}
+
+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type)
+{
+ struct cldma_hw_regs *hw_regs;
+ int base;
+ u32 addr;
+ u32 val;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_l2timsr0;
+ else
+ addr = base + hw_regs->reg_cldma_l2rimsr0;
+
+ if (qno == ALLQ)
+ val = qno << type;
+ else
+ val = BIT(qno) << type;
+
+ mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type)
+{
+ struct cldma_hw_regs *hw_regs;
+ int base;
+ u32 addr;
+ u32 val;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_l2timcr0;
+ else
+ addr = base + hw_regs->reg_cldma_l2rimcr0;
+
+ if (qno == ALLQ)
+ val = qno << type;
+ else
+ val = BIT(qno) << type;
+
+ mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type)
+{
+ struct cldma_hw_regs *hw_regs;
+ struct mtk_md_dev *mdev;
+ int base;
+ u32 addr;
+ u32 val;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+ mdev = drv_info->mdev;
+
+ if (type == QUEUE_ERROR) {
+ if (dir == DIR_TX) {
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar0);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar0, val);
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar1);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar1, val);
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar2);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar2, val);
+ } else {
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3risar0);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3risar0, val);
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3risar1);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3risar1, val);
+ }
+ }
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_l2tisar0;
+ else
+ addr = base + hw_regs->reg_cldma_l2risar0;
+
+ if (qno == ALLQ)
+ val = qno << type;
+ else
+ val = BIT(qno) << type;
+
+ mtk_pci_write32(mdev, addr, val);
+ val = mtk_pci_read32(mdev, addr);
+}
+
+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type)
+{
+ struct cldma_hw_regs *hw_regs;
+ u32 addr, val, sta;
+ int base;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_l2tisar0;
+ else
+ addr = base + hw_regs->reg_cldma_l2risar0;
+
+ val = mtk_pci_read32(drv_info->mdev, addr);
+ if (val == LINK_ERROR_VAL)
+ sta = val;
+ else if (qno == ALLQ)
+ sta = (val >> type) & 0xFF;
+ else
+ sta = (val >> type) & BIT(qno);
+
+ return sta;
+}
+
+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+ struct cldma_hw_regs *hw_regs;
+ u32 val = BIT(qno);
+ int base;
+ u32 addr;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_ul_start_cmd;
+ else
+ addr = base + hw_regs->reg_cldma_so_start_cmd;
+
+ mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+ struct cldma_hw_regs *hw_regs;
+ u32 val = BIT(qno);
+ int base;
+ u32 addr;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_ul_resume_cmd;
+ else
+ addr = base + hw_regs->reg_cldma_so_resume_cmd;
+
+ mtk_pci_write32(drv_info->mdev, addr, val);
+}
+
+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+ struct cldma_hw_regs *hw_regs;
+ int base;
+ u32 addr;
+ u32 val;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_ul_status;
+ else
+ addr = base + hw_regs->reg_cldma_so_status;
+
+ val = mtk_pci_read32(drv_info->mdev, addr);
+
+ if (qno == ALLQ || val == LINK_ERROR_VAL)
+ return val;
+
+ return val & BIT(qno);
+}
+
+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
+{
+ u32 val = (qno == ALLQ) ? qno : BIT(qno);
+ struct cldma_hw_regs *hw_regs;
+ unsigned int active;
+ int cnt = 0;
+ int base;
+ u32 addr;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+
+ if (dir == DIR_TX)
+ addr = base + hw_regs->reg_cldma_ul_stop_cmd;
+ else
+ addr = base + hw_regs->reg_cldma_so_stop_cmd;
+
+ mtk_pci_write32(drv_info->mdev, addr, val);
+
+ do {
+ active = drv_info->drv_ops->cldma_queue_status(drv_info, dir, qno);
+ if (active == LINK_ERROR_VAL || !active)
+ break;
+ usleep_range(WAIT_QUEUE_STOP, 2 * WAIT_QUEUE_STOP);
+ } while (++cnt < 10);
+
+ return active;
+}
+
+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info)
+{
+ mtk_pci_write32(drv_info->mdev, drv_info->base_addr +
+ drv_info->hw_regs->reg_cldma_ip_busy, 0x01);
+}
+
+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta)
+{
+ struct cldma_hw_regs *hw_regs;
+ struct mtk_md_dev *mdev;
+ u32 tx_mask, rx_mask;
+ int base;
+
+ mdev = drv_info->mdev;
+ base = drv_info->base_addr;
+ hw_regs = drv_info->hw_regs;
+
+ *tx_sta = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2tisar0);
+ tx_mask = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2timr0);
+ *rx_sta = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2risar0);
+ rx_mask = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2rimr0);
+
+ *tx_sta = (*tx_sta) & (~tx_mask);
+ *rx_sta = (*rx_sta) & (~rx_mask);
+
+ if (*tx_sta) {
+ /* TX XFER_DONE and QUEUE_ERROR mask */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2timsr0, *tx_sta);
+ /* TX XFER_DONE clear */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2tisar0,
+ (*tx_sta) & (0xFF << QUEUE_XFER_DONE));
+ }
+
+ if (*rx_sta) {
+ /* RX XFER_DONE and QUEUE_ERROR mask */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2rimsr0, *rx_sta);
+ /* RX XFER_DONE clear */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2risar0,
+ (*rx_sta) & (0xFF << QUEUE_XFER_DONE));
+ }
+}
+
+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno)
+{
+ u32 addr, val;
+
+ addr = drv_info->base_addr + drv_info->hw_regs->reg_cldma_ul_start_addrl_0 +
+ qno * HW_QUEUE_NUM;
+ val = mtk_pci_read32(drv_info->mdev, addr);
+
+ return val;
+}
+
+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno)
+{
+ struct cldma_hw_regs *hw_regs;
+ u32 curr_addr_h, curr_addr_l;
+ struct mtk_md_dev *mdev;
+ u64 curr_addr;
+ int base;
+ u64 addr;
+
+ hw_regs = drv_info->hw_regs;
+ base = drv_info->base_addr;
+ mdev = drv_info->mdev;
+
+ addr = base + hw_regs->reg_cldma_so_current_addrh_0 +
+ (u64)qno * HW_QUEUE_NUM;
+ curr_addr_h = mtk_pci_read32(mdev, addr);
+ addr = base + hw_regs->reg_cldma_so_current_addrl_0 +
+ (u64)qno * HW_QUEUE_NUM;
+ curr_addr_l = mtk_pci_read32(mdev, addr);
+ curr_addr = ((u64)curr_addr_h << 32) | curr_addr_l;
+ if (curr_addr_h == LINK_ERROR_VAL && curr_addr_l == LINK_ERROR_VAL)
+ curr_addr = 0;
+ return curr_addr;
+}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
new file mode 100644
index 000000000000..8763c23abf54
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#ifndef __MTK_CLDMA_DRV_H__
+#define __MTK_CLDMA_DRV_H__
+
+#define HW_QUEUE_NUM (8)
+#define ALLQ (0xFF)
+#define LINK_ERROR_VAL (0xFFFFFFFF)
+#define CLDMA0_HW_ID (0)
+#define CLDMA1_HW_ID (1)
+#define CLDMA4_HW_ID (4)
+
+struct cldma_hw_regs {
+ u8 cldma_rx_skb_pool_max_size;
+ u8 cldma_rx_skb_reload_threshold;
+ u8 tq_err_int_offset;
+ u8 tq_active_start_err_int_offset;
+ u8 rq_err_int_offset;
+ u8 rq_active_start_err_int_offset;
+ u16 reg_cldma_so_cfg;
+ u16 reg_cldma_so_start_addrl_0;
+ u16 reg_cldma_so_start_addrh_0;
+ u16 reg_cldma_so_current_addrl_0;
+ u16 reg_cldma_so_current_addrh_0;
+ u16 reg_cldma_so_status;
+ u16 reg_cldma_debug_id_en;
+ u16 reg_cldma_so_last_update_addrl_0;
+ u16 reg_cldma_so_last_update_addrh_0;
+ u16 reg_cldma_l2rimr0;
+ u16 reg_cldma_l2rimr1;
+ u16 reg_cldma_l2rimcr0;
+ u16 reg_cldma_l2rimcr1;
+ u16 reg_cldma_l2rimsr0;
+ u16 reg_cldma_l2rimsr1;
+ u16 reg_cldma_int_mask;
+ u16 reg_cldma4_int_mask;
+ u16 reg_cldma_slp_mem_ctl;
+ u16 reg_cldma_busy_mask;
+ u16 reg_cldma_ip_busy_to_pcie_mask;
+ u16 reg_cldma_ip_busy_to_pcie_mask_set;
+ u16 reg_cldma_ip_busy_to_pcie_mask_clr;
+ u16 reg_cldma_ip_busy_to_ap_mask;
+ u16 reg_cldma_ip_busy_to_ap_mask_set;
+ u16 reg_cldma_ip_busy_to_ap_mask_clr;
+ u16 reg_cldma_ip_busy_to_md_mask_set;
+ u16 reg_cldma_rx_work_to_reg_mask_set;
+ u16 reg_infra_rst4_set;
+ u16 reg_infra_rst4_clr;
+ u16 reg_infra_rst2_set;
+ u16 reg_infra_rst2_clr;
+ u16 reg_infra_rst0_set;
+ u16 reg_infra_rst0_clr;
+ u32 tq_err_int_bitmask;
+ u32 tq_active_start_err_int_bitmask;
+ u32 rq_err_int_bitmask;
+ u32 cldma0_base_addr;
+ u32 cldma1_base_addr;
+ u32 cldma4_base_addr;
+ u32 rq_active_start_err_int_bitmask;
+ u32 reg_cldma_ul_start_addrl_0;
+ u32 reg_cldma_ul_start_addrh_0;
+ u32 reg_cldma_ul_current_addrl_0;
+ u32 reg_cldma_ul_current_addrh_0;
+ u32 reg_cldma_ul_status;
+ u32 reg_cldma_ul_start_cmd;
+ u32 reg_cldma_ul_resume_cmd;
+ u32 reg_cldma_ul_stop_cmd;
+ u32 reg_cldma_ul_error;
+ u32 reg_cldma_ul_cfg;
+ u32 reg_cldma_ul_dummy_0;
+ u32 reg_cldma_so_error;
+ u32 reg_cldma_so_start_cmd;
+ u32 reg_cldma_so_resume_cmd;
+ u32 reg_cldma_so_stop_cmd;
+ u32 reg_cldma_so_dummy_0;
+ u32 reg_cldma_l2tisar0;
+ u32 reg_cldma_l2tisar1;
+ u32 reg_cldma_l2timr0;
+ u32 reg_cldma_l2timr1;
+ u32 reg_cldma_l2timcr0;
+ u32 reg_cldma_l2timcr1;
+ u32 reg_cldma_l2timsr0;
+ u32 reg_cldma_l2timsr1;
+ u32 reg_cldma_l2risar0;
+ u32 reg_cldma_l2risar1;
+ u32 reg_cldma_l3tisar0;
+ u32 reg_cldma_l3tisar1;
+ u32 reg_cldma_l3tisar2;
+ u32 reg_cldma_l3risar0;
+ u32 reg_cldma_l3risar1;
+ u32 reg_cldma_ip_busy;
+};
+
+enum mtk_ip_busy_src {
+ IP_BUSY_TXDONE = 0,
+ IP_BUSY_TXEMPTY = 8,
+ IP_BUSY_TXACTIVE = 16,
+ IP_BUSY_RXDONE = 24
+};
+
+enum mtk_intr_type {
+ QUEUE_XFER_DONE = 0,
+ QUEUE_EMPTY = 8,
+ QUEUE_ERROR = 16,
+ QUEUE_ACTIVE_START = 24,
+ INVALID_TYPE
+};
+
+enum mtk_tx_rx {
+ DIR_TX,
+ DIR_RX,
+ DIR_MAX
+};
+
+struct cldma_drv_info {
+ int hif_id;
+ int hw_id;
+ int base_addr;
+ int pci_ext_irq_id;
+ struct mtk_md_dev *mdev;
+ struct cldma_dev *cd;
+ struct txq *txq[HW_QUEUE_NUM];
+ struct rxq *rxq[HW_QUEUE_NUM];
+ struct dma_pool *gpd_dma_pool;
+ struct dma_pool *bd_dma_pool;
+ struct workqueue_struct *wq;
+ struct cldma_hw_regs *hw_regs;
+ struct cldma_drv_ops *drv_ops;
+};
+
+struct cldma_drv_ops {
+ void (*cldma_drv_init)(struct cldma_drv_info *drv_info);
+ void (*cldma_drv_reset)(struct cldma_drv_info *drv_info);
+ void (*cldma_setup_start_addr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, dma_addr_t addr);
+ void (*cldma_mask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+ void (*cldma_unmask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+ void (*cldma_clr_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+ u32 (*cldma_check_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+ void (*cldma_start_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+ void (*cldma_resume_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+ u32 (*cldma_queue_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+ u32 (*cldma_stop_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+ void (*cldma_clear_ip_busy)(struct cldma_drv_info *drv_info);
+ void (*cldma_get_intr_status)(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);
+ u32 (*cldma_get_tx_start_addr)(struct cldma_drv_info *drv_info, u32 qno);
+ u64 (*cldma_get_rx_curr_addr)(struct cldma_drv_info *drv_info, u32 qno);
+};
+
+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info);
+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, dma_addr_t addr);
+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
+ u32 qno, enum mtk_intr_type type);
+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info);
+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);
+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno);
+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno);
+
+#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
new file mode 100644
index 000000000000..d9145d146a5c
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "mtk_cldma.h"
+#include "mtk_cldma_drv.h"
+#include "mtk_cldma_drv_m9xx.h"
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+#include "mtk_trans_ctrl.h"
+
+struct cldma_hw_regs mtk_cldma_regs_m9xx = {
+ .cldma0_base_addr = CLDMA0_BASE_ADDR,
+ .cldma1_base_addr = CLDMA1_BASE_ADDR,
+ .cldma4_base_addr = CLDMA4_BASE_ADDR,
+ .cldma_rx_skb_pool_max_size = CLDMA_RX_SKB_POOL_MAX_SIZE,
+ .cldma_rx_skb_reload_threshold = CLDMA_RX_SKB_RELOAD_THRESHOLD,
+ .tq_err_int_offset = TQ_ERR_INT_OFFSET,
+ .tq_err_int_bitmask = TQ_ERR_INT_BITMASK,
+ .tq_active_start_err_int_offset = TQ_ACTIVE_START_ERR_INT_OFFSET,
+ .tq_active_start_err_int_bitmask = TQ_ACTIVE_START_ERR_INT_BITMASK,
+ .rq_err_int_offset = RQ_ERR_INT_OFFSET,
+ .rq_err_int_bitmask = RQ_ERR_INT_BITMASK,
+ .rq_active_start_err_int_offset = RQ_ACTIVE_START_ERR_INT_OFFSET,
+ .rq_active_start_err_int_bitmask = RQ_ACTIVE_START_ERR_INT_BITMASK,
+ .reg_cldma_ul_start_addrl_0 = REG_CLDMA_UL_START_ADDRL_0,
+ .reg_cldma_ul_start_addrh_0 = REG_CLDMA_UL_START_ADDRH_0,
+ .reg_cldma_ul_current_addrl_0 = REG_CLDMA_UL_CURRENT_ADDRL_0,
+ .reg_cldma_ul_current_addrh_0 = REG_CLDMA_UL_CURRENT_ADDRH_0,
+ .reg_cldma_ul_status = REG_CLDMA_UL_STATUS,
+ .reg_cldma_ul_start_cmd = REG_CLDMA_UL_START_CMD,
+ .reg_cldma_ul_resume_cmd = REG_CLDMA_UL_RESUME_CMD,
+ .reg_cldma_ul_stop_cmd = REG_CLDMA_UL_STOP_CMD,
+ .reg_cldma_ul_error = REG_CLDMA_UL_ERROR,
+ .reg_cldma_ul_cfg = REG_CLDMA_UL_CFG,
+ .reg_cldma_ul_dummy_0 = REG_CLDMA_UL_DUMMY_0,
+ .reg_cldma_so_error = REG_CLDMA_SO_ERROR,
+ .reg_cldma_so_start_cmd = REG_CLDMA_SO_START_CMD,
+ .reg_cldma_so_resume_cmd = REG_CLDMA_SO_RESUME_CMD,
+ .reg_cldma_so_stop_cmd = REG_CLDMA_SO_STOP_CMD,
+ .reg_cldma_so_dummy_0 = REG_CLDMA_SO_DUMMY_0,
+ .reg_cldma_so_cfg = REG_CLDMA_SO_CFG,
+ .reg_cldma_so_start_addrl_0 = REG_CLDMA_SO_START_ADDRL_0,
+ .reg_cldma_so_start_addrh_0 = REG_CLDMA_SO_START_ADDRH_0,
+ .reg_cldma_so_current_addrl_0 = REG_CLDMA_SO_CUR_ADDRL_0,
+ .reg_cldma_so_current_addrh_0 = REG_CLDMA_SO_CUR_ADDRH_0,
+ .reg_cldma_so_status = REG_CLDMA_SO_STATUS,
+ .reg_cldma_debug_id_en = REG_CLDMA_DEBUG_ID_EN,
+ .reg_cldma_so_last_update_addrl_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRL_0,
+ .reg_cldma_so_last_update_addrh_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRH_0,
+ .reg_cldma_l2tisar0 = REG_CLDMA_L2TISAR0,
+ .reg_cldma_l2tisar1 = REG_CLDMA_L2TISAR1,
+ .reg_cldma_l2timr0 = REG_CLDMA_L2TIMR0,
+ .reg_cldma_l2timr1 = REG_CLDMA_L2TIMR1,
+ .reg_cldma_l2timcr0 = REG_CLDMA_L2TIMCR0,
+ .reg_cldma_l2timcr1 = REG_CLDMA_L2TIMCR1,
+ .reg_cldma_l2timsr0 = REG_CLDMA_L2TIMSR0,
+ .reg_cldma_l2timsr1 = REG_CLDMA_L2TIMSR1,
+ .reg_cldma_l3tisar0 = REG_CLDMA_L3TISAR0,
+ .reg_cldma_l3tisar1 = REG_CLDMA_L3TISAR1,
+ .reg_cldma_l3tisar2 = REG_CLDMA_L3TISAR2,
+ .reg_cldma_l2risar0 = REG_CLDMA_L2RISAR0,
+ .reg_cldma_l2risar1 = REG_CLDMA_L2RISAR1,
+ .reg_cldma_l2rimr0 = REG_CLDMA_L2RIMR0,
+ .reg_cldma_l2rimr1 = REG_CLDMA_L2RIMR1,
+ .reg_cldma_l2rimcr0 = REG_CLDMA_L2RIMCR0,
+ .reg_cldma_l2rimcr1 = REG_CLDMA_L2RIMCR1,
+ .reg_cldma_l2rimsr0 = REG_CLDMA_L2RIMSR0,
+ .reg_cldma_l2rimsr1 = REG_CLDMA_L2RIMSR1,
+ .reg_cldma_l3risar0 = REG_CLDMA_L3RISAR0,
+ .reg_cldma_l3risar1 = REG_CLDMA_L3RISAR1,
+ .reg_cldma_ip_busy = REG_CLDMA_IP_BUSY,
+ .reg_cldma_int_mask = REG_CLDMA_INT_EAP_USIP_MASK,
+ .reg_cldma4_int_mask = REG_CLDMA_INT_WF_MASK,
+ .reg_cldma_ip_busy_to_pcie_mask = REG_CLDMA_IP_BUSY_TO_PCIE_MASK,
+ .reg_cldma_ip_busy_to_pcie_mask_set = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET,
+ .reg_cldma_ip_busy_to_pcie_mask_clr = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR,
+ .reg_cldma_ip_busy_to_ap_mask = REG_CLDMA_IP_BUSY_TO_AP_MASK,
+ .reg_cldma_ip_busy_to_ap_mask_set = REG_CLDMA_IP_BUSY_TO_AP_MASK_SET,
+ .reg_cldma_ip_busy_to_ap_mask_clr = REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR,
+ .reg_cldma_ip_busy_to_md_mask_set = REG_CLDMA_IP_BUSY_TO_MD_MASK_SET,
+ .reg_cldma_rx_work_to_reg_mask_set = REG_CLDMA_RX_WORK_TO_REG_MASK_SET,
+ .reg_infra_rst0_set = REG_INFRA_RST0_SET,
+ .reg_infra_rst0_clr = REG_INFRA_RST0_CLR,
+};
+
+static void mtk_cldma_drv_init_m9xx(struct cldma_drv_info *drv_info)
+{
+ struct cldma_hw_regs *hw_regs;
+ struct mtk_md_dev *mdev;
+ int base;
+ u32 val;
+
+ mdev = drv_info->mdev;
+ base = drv_info->base_addr;
+ hw_regs = drv_info->hw_regs;
+
+ /* set CLDMA to 64 bit mode GPD */
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_ul_cfg);
+
+ val = (val & (~(0x7 << 5))) | ((0x4) << 5);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_cfg, val);
+
+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_so_cfg);
+ val = (val & (~(0x7 << 10))) | ((0x4) << 10) | (1 << 2);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_cfg, val);
+
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_rx_work_to_reg_mask_set, ALLQ);
+
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_set,
+ ALLQ << 16);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_clr,
+ ALLQ << 24);
+
+ /* enable interrupt to PCIe */
+ if (drv_info->hw_id == CLDMA4_HW_ID)
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma4_int_mask, 0);
+ else
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
+
+ /* disable illegal memory check */
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_dummy_0, 1);
+}
+
+static void mtk_cldma_drv_reset_m9xx(struct cldma_drv_info *drv_info)
+{
+ struct cldma_hw_regs *hw_regs;
+ struct mtk_md_dev *mdev;
+ u32 val;
+
+ mdev = drv_info->mdev;
+ hw_regs = drv_info->hw_regs;
+
+ val = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_set);
+
+ val |= 1 << (REG_CLDMA0_RST_SET_BIT + drv_info->hw_id);
+ mtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_set, val);
+ udelay(1);
+ val = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_clr);
+ val |= 1 << (REG_CLDMA0_RST_CLR_BIT + drv_info->hw_id);
+ mtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_clr, val);
+}
+
+struct cldma_drv_ops cldma_drv_ops_m9xx = {
+ .cldma_drv_init = mtk_cldma_drv_init_m9xx,
+ .cldma_drv_reset = mtk_cldma_drv_reset_m9xx,
+ .cldma_setup_start_addr = mtk_cldma_setup_start_addr,
+ .cldma_mask_intr = mtk_cldma_mask_intr,
+ .cldma_unmask_intr = mtk_cldma_unmask_intr,
+ .cldma_clr_intr_status = mtk_cldma_clr_intr_status,
+ .cldma_check_intr_status = mtk_cldma_check_intr_status,
+ .cldma_start_queue = mtk_cldma_start_queue,
+ .cldma_resume_queue = mtk_cldma_resume_queue,
+ .cldma_queue_status = mtk_cldma_queue_status,
+ .cldma_stop_queue = mtk_cldma_stop_queue,
+ .cldma_clear_ip_busy = mtk_cldma_clear_ip_busy,
+ .cldma_get_intr_status = mtk_cldma_get_intr_status,
+ .cldma_get_tx_start_addr = mtk_cldma_get_tx_start_addr,
+ .cldma_get_rx_curr_addr = mtk_cldma_get_rx_curr_addr,
+};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
new file mode 100644
index 000000000000..2c63c43ff065
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2023, MediaTek Inc.
+ */
+
+#ifndef __MTK_CLDMA_DRV_M9XX_H__
+#define __MTK_CLDMA_DRV_M9XX_H__
+
+#define CLDMA0_BASE_ADDR (0x1021C000)
+#define CLDMA1_BASE_ADDR (0x1021E000)
+#define CLDMA4_BASE_ADDR (0x10224000)
+
+#define CLDMA_RX_SKB_POOL_MAX_SIZE (64)
+#define CLDMA_RX_SKB_RELOAD_THRESHOLD (16)
+
+/* L2TISAR0 */
+#define TQ_ERR_INT_OFFSET (16)
+#define TQ_ERR_INT_BITMASK (0x00FF0000)
+#define TQ_ACTIVE_START_ERR_INT_OFFSET (24)
+#define TQ_ACTIVE_START_ERR_INT_BITMASK (0xFF000000)
+
+/* L2RISAR0 */
+#define RQ_ERR_INT_OFFSET (16)
+#define RQ_ERR_INT_BITMASK (0x00FF0000)
+#define RQ_ACTIVE_START_ERR_INT_OFFSET (24)
+#define RQ_ACTIVE_START_ERR_INT_BITMASK (0xFF000000)
+
+/* CLDMA IN(Tx) */
+#define REG_CLDMA_UL_START_ADDRL_0 (0x0004)
+#define REG_CLDMA_UL_START_ADDRH_0 (0x0008)
+#define REG_CLDMA_UL_CURRENT_ADDRL_0 (0x0044)
+#define REG_CLDMA_UL_CURRENT_ADDRH_0 (0x0048)
+#define REG_CLDMA_UL_STATUS (0x0084)
+#define REG_CLDMA_UL_START_CMD (0x0088)
+#define REG_CLDMA_UL_RESUME_CMD (0x008C)
+#define REG_CLDMA_UL_STOP_CMD (0x0090)
+#define REG_CLDMA_UL_ERROR (0x0094)
+#define REG_CLDMA_UL_CFG (0x0098)
+#define REG_CLDMA_UL_DUMMY_0 (0x009C)
+
+/* CLDMA OUT(Rx) */
+#define REG_CLDMA_SO_ERROR (0x0400 + 0x0100)
+#define REG_CLDMA_SO_START_CMD (0x0400 + 0x01BC)
+#define REG_CLDMA_SO_RESUME_CMD (0x0400 + 0x01C0)
+#define REG_CLDMA_SO_STOP_CMD (0x0400 + 0x01C4)
+#define REG_CLDMA_SO_DUMMY_0 (0x0400 + 0x0108)
+#define REG_CLDMA_SO_CFG (0x0400 + 0x0004)
+#define REG_CLDMA_SO_START_ADDRL_0 (0x0400 + 0x0078)
+#define REG_CLDMA_SO_START_ADDRH_0 (0x0400 + 0x007C)
+#define REG_CLDMA_SO_CUR_ADDRL_0 (0x0400 + 0x00B8)
+#define REG_CLDMA_SO_CUR_ADDRH_0 (0x0400 + 0x00BC)
+#define REG_CLDMA_SO_STATUS (0x0400 + 0x00F8)
+#define REG_CLDMA_DEBUG_ID_EN (0x0400 + 0x00FC)
+#define REG_CLDMA_SO_LAST_UPDATE_ADDRL_0 (0x0400 + 0x01C8)
+#define REG_CLDMA_SO_LAST_UPDATE_ADDRH_0 (0x0400 + 0x01CC)
+
+/* CLDMA MISC */
+#define REG_CLDMA_L2TISAR0 (0x0800 + 0x0010)
+#define REG_CLDMA_L2TISAR1 (0x0800 + 0x0014)
+#define REG_CLDMA_L2TIMR0 (0x0800 + 0x0018)
+#define REG_CLDMA_L2TIMR1 (0x0800 + 0x001C)
+#define REG_CLDMA_L2TIMCR0 (0x0800 + 0x0020)
+#define REG_CLDMA_L2TIMCR1 (0x0800 + 0x0024)
+#define REG_CLDMA_L2TIMSR0 (0x0800 + 0x0028)
+#define REG_CLDMA_L2TIMSR1 (0x0800 + 0x002C)
+#define REG_CLDMA_L3TISAR0 (0x0800 + 0x0030)
+#define REG_CLDMA_L3TISAR1 (0x0800 + 0x0034)
+#define REG_CLDMA_L2RISAR0 (0x0800 + 0x0050)
+#define REG_CLDMA_L2RISAR1 (0x0800 + 0x0054)
+#define REG_CLDMA_L3RISAR0 (0x0800 + 0x0070)
+#define REG_CLDMA_L3RISAR1 (0x0800 + 0x0074)
+#define REG_CLDMA_IP_BUSY (0x0800 + 0x00B4)
+#define REG_CLDMA_L3TISAR2 (0x0800 + 0x00C0)
+
+#define REG_CLDMA_L2RIMR0 (0x0800 + 0x00E8)
+#define REG_CLDMA_L2RIMR1 (0x0800 + 0x00EC)
+#define REG_CLDMA_L2RIMCR0 (0x0800 + 0x00F0)
+#define REG_CLDMA_L2RIMCR1 (0x0800 + 0x00F4)
+#define REG_CLDMA_L2RIMSR0 (0x0800 + 0x00F8)
+#define REG_CLDMA_L2RIMSR1 (0x0800 + 0x00FC)
+
+#define REG_CLDMA_INT_EAP_USIP_MASK (0x0800 + 0x011C)
+#define REG_CLDMA_INT_WF_MASK (0x0800 + 0x0120)
+#define REG_CLDMA_RQ1_GPD_DONE_CNT (0x0800 + 0x0174)
+#define REG_CLDMA_TQ1_GPD_DONE_CNT (0x0800 + 0x0184)
+
+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK (0x0800 + 0x0194)
+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET (0x0800 + 0x0198)
+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR (0x0800 + 0x019C)
+
+#define REG_CLDMA_IP_BUSY_TO_AP_MASK (0x0800 + 0x0200)
+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_SET (0x0800 + 0x0204)
+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR (0x0800 + 0x0208)
+#define REG_CLDMA_IP_BUSY_TO_MD_MASK_SET (0x0800 + 0x0210)
+#define REG_CLDMA_RX_WORK_TO_REG_MASK_SET (0x0800 + 0x021C)
+
+/* CLDMA RESET */
+#define REG_INFRA_RST0_SET (0x120)
+#define REG_INFRA_RST0_CLR (0x124)
+#define REG_CLDMA0_RST_SET_BIT (8)
+#define REG_CLDMA0_RST_CLR_BIT (8)
+
+#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
new file mode 100644
index 000000000000..c1bb787ee981
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include "mtk_cldma.h"
+#include "mtk_trans_ctrl.h"
+
+#define TRB_SRV_NUM (1)
+
+static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
+ {0},
+ {0},
+};
+
+static const struct queue_info mtk_queue_info_m9xx[] = {
+};
+
+struct mtk_ctrl_info mtk_ctrl_info_m9xx = {
+ .queue_info = (struct queue_info *)mtk_queue_info_m9xx,
+ .queue_info_num = ARRAY_SIZE(mtk_queue_info_m9xx),
+ .srv_cfg = (int **)mtk_srv_cfg_m9xx,
+ .trb_srv_num = TRB_SRV_NUM,
+};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
index 90b33dd6effd..6efbcd0cba73 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -891,6 +891,28 @@ static void mtk_pci_free_irq(struct mtk_md_dev *mdev)
pci_free_irq_vectors(pdev);
}
+static int mtk_pci_dev_init(struct mtk_md_dev *mdev)
+{
+ int ret;
+
+ ret = mtk_trans_ctrl_init(mdev);
+ if (ret) {
+ dev_err(mdev->dev, "Failed to initialize control plane: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mtk_pci_dev_exit(struct mtk_md_dev *mdev)
+{
+ mtk_trans_ctrl_exit(mdev);
+}
+
+static int mtk_pci_dev_start(struct mtk_md_dev *mdev)
+{
+ return 0;
+}
static const struct mtk_dev_ops pci_hw_ops = {
.get_dev_state = mtk_pci_get_dev_state,
.ack_dev_state = mtk_pci_ack_dev_state,
@@ -965,6 +987,12 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (ret)
goto free_mhccif;
+ ret = mtk_pci_dev_init(mdev);
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to init dev.\n");
+ goto free_irq;
+ }
+
pci_set_master(pdev);
mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
@@ -981,10 +1009,20 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto clear_master;
}
+ ret = mtk_pci_dev_start(mdev);
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to start dev.\n");
+ goto free_saved_state;
+ }
+
return 0;
+free_saved_state:
+ pci_load_and_free_saved_state(pdev, &priv->saved_state);
clear_master:
pci_clear_master(pdev);
+ mtk_pci_dev_exit(mdev);
+free_irq:
mtk_pci_free_irq(mdev);
free_mhccif:
mtk_mhccif_exit(mdev);
@@ -1012,6 +1050,7 @@ static void mtk_pci_remove(struct pci_dev *pdev)
}
pci_clear_master(pdev);
+ mtk_pci_dev_exit(mdev);
mtk_pci_free_irq(mdev);
mtk_mhccif_exit(mdev);
pci_load_and_free_saved_state(pdev, &priv->saved_state);
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
index 3f0667e8a846..73299ae03f89 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
@@ -21,6 +21,7 @@
#define REG_IMASK_HOST_MSIX_SET_GRP0_0 0x3000
#define REG_IMASK_HOST_MSIX_CLR_GRP0_0 0x3080
#define REG_IMASK_HOST_MSIX_GRP0_0 0x3100
+#define REG_DEV_INFRA_BASE 0x10001000
/* mhccif registers */
#define MHCCIF_RC2EP_SW_BSY 0x4
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
new file mode 100644
index 000000000000..32f00c15f383
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/freezer.h>
+#include <linux/hashtable.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/nospec.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+#include "mtk_cldma.h"
+#include "mtk_ctrl_plane.h"
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_trans_ctrl.h"
+
+#define MTK_DFLT_PORT_NAME_LEN (20)
+
+static struct mtk_ctrl_info_desc mtk_ctrl_info_tbl[] = {
+ {2304, &ctrl_info_name(m9xx)},
+ {0, NULL},
+};
+
+#define RX_CH_ID_SHIFT 16
+#define PORT_MTU_MASK 0xFFFF
+#define QUEUE_CHL_MASK 0xFFFF
+
+static bool mtk_queue_list_is_full(struct mtk_ctrl_trans *trans, struct queue_info *que)
+{
+ return trans->trans_list[que->hif_id].skb_list[que->txqno].qlen >= SKB_LIST_MAX_LEN;
+}
+
+static bool mtk_ctrl_chs_is_busy_or_empty(struct trb_srv *srv)
+{
+ struct srv_que *srv_que;
+ int i;
+
+ for (i = 0; i < NR_CLDMA; i++)
+ list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
+ if (!skb_queue_empty(&srv->trans->trans_list[i].skb_list[srv_que->qno]) &&
+ mtk_cldma_get_tx_budget(srv->trans->dev, i, srv_que->qno))
+ return false;
+
+ return true;
+}
+
+static void mtk_ctrl_ch_flush(struct sk_buff_head *skb_list)
+{
+ struct sk_buff *skb;
+ struct trb *trb;
+
+ while (!skb_queue_empty(skb_list)) {
+ skb = skb_dequeue(skb_list);
+ trb = (struct trb *)skb->cb;
+ trb->status = -EIO;
+ trb->trb_complete(skb);
+ }
+}
+
+static void mtk_ctrl_chs_flush(struct trb_srv *srv)
+{
+ struct srv_que *srv_que;
+ int i;
+
+ for (i = 0; i < NR_CLDMA; i++)
+ list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
+ mtk_ctrl_ch_flush(&srv->trans->trans_list[i].skb_list[srv_que->qno]);
+}
+
+static int mtk_ch_status_check(struct mtk_ctrl_trans *trans, struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct trb_open_priv *trb_open_priv;
+ struct queue_info *que;
+ int ret = 0;
+
+ que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & QUEUE_CHL_MASK);
+
+ switch (trb->cmd) {
+ case TRB_CMD_ENABLE:
+ trb_open_priv = (struct trb_open_priv *)skb->data;
+ trb_open_priv->log_rg_offset = que->log_rg_offset;
+ trans->usr_cnt[que->hif_id][que->txqno]++;
+ if (trans->usr_cnt[que->hif_id][que->txqno] == 1)
+ break;
+ trb_open_priv->tx_mtu = que->tx_mtu;
+ trb_open_priv->rx_mtu = que->rx_mtu;
+ trb_open_priv->tx_frag_size = que->tx_frag_size;
+ trb_open_priv->rx_frag_size = que->rx_frag_size;
+ if (mtk_cldma_check_ch_cfg(trans->dev, que)) {
+ trb->status = -EINVAL;
+ ret = -EINVAL;
+ } else {
+ trb->status = -EBUSY;
+ ret = -EBUSY;
+ }
+ trb->trb_complete(skb);
+ break;
+ case TRB_CMD_DISABLE:
+ if (trans->usr_cnt[que->hif_id][que->txqno] > 0) {
+ trans->usr_cnt[que->hif_id][que->txqno]--;
+ if (!trans->usr_cnt[que->hif_id][que->txqno])
+ break;
+ }
+ trb->status = -EBUSY;
+ trb->trb_complete(skb);
+ ret = -EBUSY;
+ break;
+ default:
+ dev_err((trans->mdev)->dev, "Invalid trb command(%d)\n", trb->cmd);
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_list, u32 qno)
+{
+ struct sk_buff_head *skb_list = &trans_list->skb_list[qno];
+ struct mtk_ctrl_trans *trans = srv->trans;
+ struct sk_buff *skb, *skb_next;
+ struct trb *trb, *trb_next;
+ bool kick = false;
+ int loop = 0;
+ int err;
+
+ do {
+ skb = skb_peek(skb_list);
+ if (!skb)
+ break;
+ trb = (struct trb *)skb->cb;
+
+ switch (trb->cmd) {
+ case TRB_CMD_ENABLE:
+ case TRB_CMD_DISABLE:
+ skb_unlink(skb, skb_list);
+ err = mtk_ch_status_check(trans, skb);
+ if (!err) {
+ kick = true;
+ if (trb->cmd == TRB_CMD_DISABLE)
+ mtk_ctrl_ch_flush(skb_list);
+ }
+ break;
+ case TRB_CMD_TX:
+ err = mtk_cldma_submit_tx(trans->dev, skb);
+ if (err) {
+ if (trans_list->tx_burst_cnt[qno]) {
+ kick = true;
+ break;
+ }
+ if (err == -EAGAIN)
+ return;
+
+ skb_unlink(skb, skb_list);
+ trb->status = err;
+ trb->trb_complete(skb);
+ break;
+ }
+
+ trans_list->tx_burst_cnt[qno]++;
+ if (trans_list->tx_burst_cnt[qno] >= TX_BURST_MAX_CNT ||
+ skb_queue_is_last(skb_list, skb)) {
+ kick = true;
+ } else {
+ skb_next = skb_peek_next(skb, skb_list);
+ trb_next = (struct trb *)skb_next->cb;
+ if (trb_next->cmd != TRB_CMD_TX)
+ kick = true;
+ }
+
+ skb_unlink(skb, skb_list);
+ break;
+ default:
+ skb_unlink(skb, skb_list);
+ }
+
+ if (kick) {
+ mtk_cldma_trb_process(trans->dev, skb);
+ trans_list->tx_burst_cnt[qno] = 0;
+ kick = false;
+ }
+
+ loop++;
+ } while (loop < TRB_NUM_PER_ROUND);
+}
+
+static void mtk_ctrl_trb_process(struct trb_srv *srv)
+{
+ struct mtk_ctrl_trans *trans = srv->trans;
+ struct srv_que *srv_que;
+ int i;
+
+ for (i = 0; i < NR_CLDMA; i++)
+ list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
+ mtk_ctrl_trb_handler(srv, &trans->trans_list[i], srv_que->qno);
+}
+
+static int mtk_ctrl_trb_thread(void *args)
+{
+ struct trb_srv *srv = args;
+
+ for (;;) {
+ wait_event_interruptible(srv->trb_waitq,
+ !mtk_ctrl_chs_is_busy_or_empty(srv) ||
+ kthread_should_stop() || kthread_should_park());
+ if (kthread_should_stop())
+ break;
+
+ if (kthread_should_park())
+ kthread_parkme();
+
+ do {
+ mtk_ctrl_trb_process(srv);
+ cond_resched();
+ } while (!mtk_ctrl_chs_is_busy_or_empty(srv) && !kthread_should_stop() &&
+ !kthread_should_park());
+ }
+ mtk_ctrl_chs_flush(srv);
+ return 0;
+}
+
+static int mtk_ctrl_trb_srv_init(struct mtk_ctrl_trans *trans)
+{
+ struct srv_que *srv_que;
+ struct trb_srv *srv;
+ int i, j;
+ int ret;
+
+ for (i = 0; i < trans->trb_srv_num; i++) {
+ srv = devm_kzalloc(trans->mdev->dev, sizeof(*srv), GFP_KERNEL);
+ if (!srv) {
+ ret = -ENOMEM;
+ goto err_free_srv;
+ }
+
+ srv->trans = trans;
+ srv->srv_id = i;
+ trans->trb_srv[i] = srv;
+
+ init_waitqueue_head(&srv->trb_waitq);
+ for (j = 0; j < NR_CLDMA; j++)
+ INIT_LIST_HEAD(&srv->srv_q_list[j]);
+ }
+
+ for (i = 0; i < NR_CLDMA; i++)
+ for (j = 0; j < HW_QUE_NUM; j++) {
+ if (trans->srv_cfg[i][j] < 0 ||
+ trans->srv_cfg[i][j] >= trans->trb_srv_num)
+ trans->srv_cfg[i][j] = 0;
+ srv_que = devm_kzalloc(trans->mdev->dev, sizeof(*srv_que), GFP_KERNEL);
+ if (!srv_que) {
+ ret = -ENOMEM;
+ goto err_free_srv_que;
+ }
+ srv_que->hif_id = i;
+ srv_que->qno = j;
+ list_add_tail(&srv_que->list,
+ &trans->trb_srv[trans->srv_cfg[i][j]]->srv_q_list[i]);
+ }
+
+ for (i = 0; i < trans->trb_srv_num; i++) {
+ trans->trb_srv[i]->trb_thread = kthread_run(mtk_ctrl_trb_thread, trans->trb_srv[i],
+ "mtk_trb_srv%d_%s", i,
+ trans->mdev->dev_str);
+ if (IS_ERR(trans->trb_srv[i]->trb_thread)) {
+ ret = PTR_ERR(trans->trb_srv[i]->trb_thread);
+ trans->trb_srv[i]->trb_thread = NULL;
+ goto err_stop_kthread;
+ }
+ }
+
+ return 0;
+err_stop_kthread:
+ while (--i >= 0)
+ kthread_stop(trans->trb_srv[i]->trb_thread);
+err_free_srv_que:
+ for (i = 0; i < trans->trb_srv_num; i++) {
+ for (j = 0; j < NR_CLDMA; j++) {
+ struct srv_que *next_srv_que;
+
+ list_for_each_entry_safe(srv_que, next_srv_que,
+ &trans->trb_srv[i]->srv_q_list[j], list) {
+ list_del(&srv_que->list);
+ devm_kfree(trans->mdev->dev, srv_que);
+ }
+ }
+ }
+err_free_srv:
+ for (i = 0; i < trans->trb_srv_num; i++) {
+ if (!trans->trb_srv[i])
+ break;
+ devm_kfree(trans->mdev->dev, trans->trb_srv[i]);
+ trans->trb_srv[i] = NULL;
+ }
+
+ return ret;
+}
+
+static void mtk_ctrl_trb_srv_exit(struct mtk_ctrl_trans *trans)
+{
+ struct srv_que *srv_que, *next_srv_que;
+ struct trb_srv *srv;
+ int i, j;
+
+ for (i = 0; i < trans->trb_srv_num; i++) {
+ srv = trans->trb_srv[i];
+ kthread_stop(srv->trb_thread);
+ for (j = 0; j < NR_CLDMA; j++) {
+ list_for_each_entry_safe(srv_que, next_srv_que,
+ &trans->trb_srv[i]->srv_q_list[j], list) {
+ list_del(&srv_que->list);
+ devm_kfree(trans->mdev->dev, srv_que);
+ }
+ }
+ devm_kfree(trans->mdev->dev, srv);
+ trans->trb_srv[i] = NULL;
+ }
+}
+
+static void mtk_ctrl_remove_radix_tree(struct mtk_ctrl_trans *trans)
+{
+ struct radix_tree_iter iter;
+ struct queue_info *queue;
+ void __rcu **slot;
+
+ radix_tree_for_each_slot(slot, &trans->queue_tbl, &iter, 0) {
+ queue = radix_tree_deref_slot(slot);
+ if (!queue)
+ continue;
+ radix_tree_delete(&trans->queue_tbl, iter.index);
+ kfree(queue);
+ }
+}
+
+static void mtk_ctrl_queue_info_update(struct radix_tree_root *queue_tbl, u32 port_chl_mtu)
+{
+ struct queue_info *queue;
+ u32 rx_chl, mtu;
+
+ if (!port_chl_mtu)
+ return;
+
+ rx_chl = port_chl_mtu >> RX_CH_ID_SHIFT;
+ mtu = port_chl_mtu & PORT_MTU_MASK;
+ queue = radix_tree_lookup(queue_tbl, rx_chl);
+ if (!queue)
+ return;
+
+ queue->tx_mtu = mtu;
+ queue->rx_mtu = mtu;
+ queue->tx_frag_size = mtu;
+ queue->rx_frag_size = mtu;
+}
+
+static unsigned int ctrl_port_chl_mtu;
+
+static int mtk_pcie_hif_init(struct mtk_md_dev *mdev)
+{
+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ struct queue_info *queue, *queue_info;
+ struct mtk_ctrl_trans *trans;
+ int i, j;
+ int ret;
+
+ trans = ctrl_blk->ctrl_hw_priv;
+ trans->ctrl_blk = ctrl_blk;
+ queue_info = trans->queue_info;
+
+ INIT_RADIX_TREE(&trans->queue_tbl, GFP_KERNEL);
+ for (i = 0; i < trans->queue_info_num; i++) {
+ queue = kmemdup(queue_info + i, sizeof(*queue), GFP_KERNEL);
+ if (!queue) {
+ ret = -ENOMEM;
+ goto err_free_radix_tree;
+ }
+ if (queue->txqno >= HW_QUE_NUM || queue->rxqno >= HW_QUE_NUM ||
+ queue->hif_id >= NR_CLDMA) {
+ dev_err((mdev)->dev, "Failed to get correct queue info %x\n",
+ queue->rx_chl);
+ kfree(queue);
+ ret = -EINVAL;
+ goto err_free_radix_tree;
+ }
+ ret = radix_tree_insert(&trans->queue_tbl, queue->rx_chl & QUEUE_CHL_MASK, queue);
+ if (ret) {
+ dev_err((mdev)->dev, "Insert %x fail, ret: %d", queue->rx_chl, ret);
+ kfree(queue);
+ goto err_free_radix_tree;
+ }
+ trans->queues_cnt++;
+ }
+
+ mtk_ctrl_queue_info_update(&trans->queue_tbl, ctrl_port_chl_mtu);
+
+ for (i = 0; i < NR_CLDMA; i++) {
+ for (j = 0; j < HW_QUE_NUM; j++) {
+ skb_queue_head_init(&trans->trans_list[i].skb_list[j]);
+ trans->trans_list[i].tx_burst_cnt[j] = 0;
+ }
+ }
+ ret = mtk_cldma_init(trans);
+ if (ret)
+ goto err_free_radix_tree;
+
+ ret = mtk_ctrl_trb_srv_init(trans);
+ if (ret)
+ goto err_cldma_exit;
+
+ atomic_set(&trans->available, 1);
+
+ return 0;
+
+err_cldma_exit:
+ mtk_cldma_exit(trans);
+err_free_radix_tree:
+ mtk_ctrl_remove_radix_tree(trans);
+
+ return ret;
+}
+
+static int mtk_pcie_hif_exit(struct mtk_md_dev *mdev)
+{
+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ struct mtk_ctrl_trans *trans;
+
+ trans = ctrl_blk->ctrl_hw_priv;
+
+ atomic_set(&trans->available, 0);
+ mtk_ctrl_trb_srv_exit(trans);
+ mtk_ctrl_remove_radix_tree(trans);
+ mtk_cldma_exit(trans);
+
+ return 0;
+}
+
+static int mtk_pcie_hif_submit_skb(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send)
+{
+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ struct mtk_ctrl_trans *trans;
+ struct queue_info *que;
+ struct trb *trb;
+
+ trans = ctrl_blk->ctrl_hw_priv;
+ trb = (struct trb *)skb->cb;
+
+ if (trb->cmd == TRB_CMD_STOP || trb->cmd == TRB_CMD_RECOVER) {
+ trb->trb_complete(skb);
+ return 0;
+ }
+
+ que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & QUEUE_CHL_MASK);
+ if (!que) {
+ dev_warn((mdev)->dev, "lookup que fail, ch_id: %x, que: 0x%p\n",
+ trb->channel_id, que);
+ return -EINVAL;
+ }
+
+ if (!atomic_read(&trans->available))
+ return -EIO;
+
+ if (mtk_queue_list_is_full(trans, que) && !force_send)
+ return -EAGAIN;
+
+ if (trb->cmd == TRB_CMD_DISABLE)
+ skb_queue_head(&trans->trans_list[que->hif_id].skb_list[que->txqno], skb);
+ else
+ skb_queue_tail(&trans->trans_list[que->hif_id].skb_list[que->txqno], skb);
+
+ wake_up(&trans->trb_srv[trans->srv_cfg[que->hif_id][que->txqno]]->trb_waitq);
+
+ return 0;
+}
+
+static int mtk_pcie_hif_cmd_func(struct mtk_md_dev *mdev, int cmd, void *data)
+{
+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ struct mtk_ctrl_trans *trans;
+ struct queue_info *que;
+
+ switch (cmd) {
+ case HIF_CTRL_CMD_CHECK_TX_FULL:
+ trans = ctrl_blk->ctrl_hw_priv;
+ que = radix_tree_lookup(&trans->queue_tbl,
+ ((union ctrl_hif_cmd_data *)data)->rx_ch & QUEUE_CHL_MASK);
+ if (!que) {
+ dev_warn((mdev)->dev, "Failed to find que to check tx full\n");
+ return -EINVAL;
+ }
+ return mtk_queue_list_is_full(trans, que);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct mtk_ctrl_hif_ops pcie_ctrl_ops = {
+ .init = mtk_pcie_hif_init,
+ .exit = mtk_pcie_hif_exit,
+ .submit_skb = mtk_pcie_hif_submit_skb,
+ .send_cmd = mtk_pcie_hif_cmd_func,
+};
+
+static void mtk_trans_get_ctrl_info(struct mtk_ctrl_cfg *cfg,
+ struct mtk_ctrl_trans *trans, u32 hw_ver)
+{
+ struct mtk_ctrl_info_desc *ctrl_info_desc;
+ struct mtk_ctrl_info *ctrl_info;
+ u8 i;
+
+ for (i = 0; (ctrl_info_desc = &mtk_ctrl_info_tbl[i]) && ctrl_info_desc &&
+ ctrl_info_desc->ctrl_info; i++) {
+ if (ctrl_info_desc->hw_ver != hw_ver)
+ continue;
+
+ ctrl_info = ctrl_info_desc->ctrl_info;
+ memcpy(trans->srv_cfg, ctrl_info->srv_cfg,
+ sizeof(int) * NR_CLDMA * HW_QUE_NUM);
+ trans->queue_info = ctrl_info->queue_info;
+ trans->queue_info_num = ctrl_info->queue_info_num;
+ trans->trb_srv_num = ctrl_info->trb_srv_num;
+ }
+}
+
+int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
+{
+ struct mtk_ctrl_trans *trans;
+ struct mtk_ctrl_blk *ctrl_blk;
+ int err;
+
+ trans = devm_kzalloc(mdev->dev, sizeof(*trans), GFP_KERNEL);
+ if (!trans)
+ return -ENOMEM;
+ trans->mdev = mdev;
+ trans->queues_cnt = 0;
+
+ mtk_trans_get_ctrl_info(NULL, trans, mdev->hw_ver);
+ if (!trans->queue_info ||
+ trans->trb_srv_num <= 0 || trans->trb_srv_num > TRB_SRV_MAX_NUM ||
+ trans->queue_info_num <= 0) {
+ dev_err((mdev)->dev, "Failed to get ctrl info!\n");
+ goto err_free_cfg;
+ }
+
+ err = mtk_ctrl_init(mdev, &pcie_ctrl_ops);
+ if (err)
+ goto err_free_cfg;
+
+ ctrl_blk = mdev->ctrl_blk;
+ ctrl_blk->ctrl_hw_priv = trans;
+
+ return 0;
+
+err_free_cfg:
+ devm_kfree(mdev->dev, trans);
+ return -ENOMEM;
+}
+
+int mtk_trans_ctrl_exit(struct mtk_md_dev *mdev)
+{
+ struct mtk_ctrl_trans *trans;
+ struct mtk_ctrl_blk *ctrl_blk;
+
+ ctrl_blk = mdev->ctrl_blk;
+ trans = ctrl_blk->ctrl_hw_priv;
+
+ devm_kfree(mdev->dev, ctrl_blk->cfg);
+ mtk_ctrl_exit(mdev);
+ devm_kfree(mdev->dev, trans);
+
+ return 0;
+}
+
+module_param(ctrl_port_chl_mtu, uint, 0644);
+MODULE_PARM_DESC(ctrl_port_chl_mtu, "This is used to config the ctrl port mtu!\n");
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
index d6de4c43b529..0d25d1f51671 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
@@ -13,9 +13,95 @@
#include "mtk_dev.h"
+#define TRB_SRV_MAX_NUM (1)
+#define HW_QUE_NUM (8)
+#define TX_GPD_NUM (16)
+#define RX_GPD_NUM (TX_GPD_NUM)
+#define MIN_GPD_NUM (2)
+#define SKB_LIST_MAX_LEN (16)
+#define MTU_RSV_ROOM (0x100)
+#define TRB_NUM_PER_ROUND (TX_GPD_NUM)
+#define TX_BURST_MAX_CNT (TX_GPD_NUM / 4 + 1)
+
+#define HIF_ID(peer_id) ((peer_id) - 1)
+
+enum mtk_hif_id {
+ CLDMA0,
+ CLDMA1,
+ CLDMA4,
+ NR_CLDMA
+};
+
+struct queue_info {
+ u32 tx_chl;
+ u32 rx_chl;
+ enum mtk_hif_id hif_id;
+ u32 txqno;
+ u32 rxqno;
+ u32 tx_mtu;
+ u32 rx_mtu;
+ u32 tx_nr_gpds;
+ u32 rx_nr_gpds;
+ u32 tx_frag_size;
+ u32 rx_frag_size;
+ u8 log_rg_offset;
+};
+
+struct trans_list {
+ struct sk_buff_head skb_list[HW_QUE_NUM];
+ u8 tx_burst_cnt[HW_QUE_NUM];
+};
+
struct mtk_ctrl_trans {
struct mtk_ctrl_blk *ctrl_blk;
+ struct trb_srv *trb_srv[TRB_SRV_MAX_NUM];
+ struct trans_list trans_list[NR_CLDMA];
+ void *dev;
+ struct radix_tree_root queue_tbl;
struct mtk_md_dev *mdev;
+ int usr_cnt[NR_CLDMA][HW_QUE_NUM];
+ u32 tx_mtu_cfg[NR_CLDMA][HW_QUE_NUM];
+ u32 rx_mtu_cfg[NR_CLDMA][HW_QUE_NUM];
+ atomic_t available;
+ int queues_cnt;
+ int srv_cfg[NR_CLDMA][HW_QUE_NUM];
+ struct queue_info *queue_info;
+ int queue_info_num;
+ int trb_srv_num;
+};
+
+struct srv_que {
+ u32 hif_id;
+ u32 qno;
+ struct list_head list;
};
+struct trb_srv {
+ u32 srv_id;
+ struct list_head srv_q_list[NR_CLDMA];
+ struct mtk_ctrl_trans *trans;
+ wait_queue_head_t trb_waitq;
+ struct task_struct *trb_thread;
+};
+
+struct mtk_ctrl_info {
+ struct mtk_ctrl_cfg *ctrl_cfg;
+ int **srv_cfg;
+ struct queue_info *queue_info;
+ u32 queue_info_num;
+ u32 trb_srv_num;
+};
+
+struct mtk_ctrl_info_desc {
+ u32 hw_ver;
+ struct mtk_ctrl_info *ctrl_info;
+};
+
+#define ctrl_info_name(NAME) mtk_ctrl_info_##NAME
+
+extern struct mtk_ctrl_info mtk_ctrl_info_m9xx;
+
+int mtk_trans_ctrl_init(struct mtk_md_dev *mdev);
+int mtk_trans_ctrl_exit(struct mtk_md_dev *mdev);
+
#endif
--
2.34.1
^ permalink raw reply related
* [RFC] drm/imx: upstream direction for i.MX95 display support
From: Piyush Patle @ 2026-06-24 10:03 UTC (permalink / raw)
To: dri-devel, imx, linux-arm-kernel
Cc: victor.liu, marex, daniel.baluta, Frank.Li, shawnguo, tzimmermann,
maarten.lankhorst, mripard, airlied, simona
Hi all,
This is an RFC to settle the i.MX95 display architecture before any code is
(re)posted. It is a question, not a submission.
It follows Marek's earlier [1] and Liu Ying's reply there proposing a separate
i.MX95 driver plus a shared helper library rather than extending the existing
i.MX8QXP DC driver (drivers/gpu/drm/imx/dc/). That question was never resolved,
and it gates any serious submission.
The implementation I evaluated is based on the existing NXP downstream
dpu95 driver. My work has focused on bringing it up on current
mainline, DT integration, FRDM enablement and validation rather than
developing a new driver. I am not proposing to repost that driver as-is;
I would rather settle the architecture first.
The current dc/ implementation is a multi-device component driver with one
platform_driver per block bound via the component framework. The downstream
i.MX95 driver is a single monolithic platform_driver mapping all blocks from
one register base. Unifying appears to require reconciling two bind models,
rather than only adding match_data.
DomainBlend is i.MX95-only and sits on the atomic CRTC path, with no
i.MX8QXP analogue.
The block decomposition also differs: i.MX95 has
dither/hscaler/vscaler/fetcheco/fetchyuv/domainblend, while i.MX8QXP uses
fetchwarp.
There is also anticipated divergence which is not yet upstream (i.MX8QXP
prefetch/PRG, LTS and tiling modifiers, and the downstream i.MX95 blit
engine), although mainline dc/ is KMS-only today.
A single parametrised driver may still be possible, but these
differences led me to revisit the question before preparing a series.
The ported stack is functional on an i.MX95 15x15 FRDM with an IT6263
LVDS-to-HDMI bridge on LVDS channel 1. The DPU probes successfully,
EDID is read through the bridge, and modesetting works at
1280x720@60 and 1920x1080@60. Weston and sway both run correctly.
Tested pipeline DPU -> pixel-interleaver -> pixel-link -> LDB ->
LVDS PHY -> IT6263 -> HDMI, using JEIDA-24 mapping. DSI is not covered.
One question for Liu Ying is whether the separate-driver plus shared
helper-library approach is still the preferred direction, and where the
helper boundary would be drawn (which blocks/ops are shared versus
implemented per driver).
If that approach is still preferred, I would be interested in working on
the helper-library extraction. Before spending time on it, I would like
to understand whether it matches the intended upstream direction or
whether similar work is already planned.
Likewise, it would be useful to understand whether extending dc/ is still
considered preferable, and how the component and monolithic driver models
would be reconciled given the differences described above.
Thanks,
Piyush Patle
References
[1] https://lore.kernel.org/dri-devel/20251011170213.128907-1-marek.vasut@mailbox.org/
^ permalink raw reply
* [PATCH v2 2/2] arm64: dts: qcom: kaanapali: fix traceNoC probe issue
From: Jie Gan @ 2026-06-24 9:49 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Tingwei Zhang, Jingyi Wang, Jie Gan, Abel Vesa,
Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Yuanfang Zhang
Cc: Konrad Dybcio, linux-arm-msm, devicetree, linux-kernel, coresight,
linux-arm-kernel
In-Reply-To: <20260624-fix-tracenoc-probe-issue-v2-0-786520f62f21@oss.qualcomm.com>
The AMBA bus attempts to read the CID/PID of a device before invoking
its probe function if the arm,primecell-periphid property is absent.
This causes a deferred probe issue for the TraceNoC device, as the
CID/PID cannot be read from the periphid register.
Add the arm,primecell-periphid property to bypass the AMBA bus
check and resolve the probe issue.
Fixes: f73959d86c15 ("arm64: dts: qcom: kaanapali: add coresight nodes")
Signed-off-by: Jie Gan <jie.gan@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/kaanapali.dtsi | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/boot/dts/qcom/kaanapali.dtsi b/arch/arm64/boot/dts/qcom/kaanapali.dtsi
index 7aa9653bd456..25820f7c04cd 100644
--- a/arch/arm64/boot/dts/qcom/kaanapali.dtsi
+++ b/arch/arm64/boot/dts/qcom/kaanapali.dtsi
@@ -5009,6 +5009,7 @@ tn@111b8000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ arm,primecell-periphid = <0x000f0c00>;
in-ports {
#address-cells = <1>;
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/2] dt-bindings: arm: qcom,coresight-tnoc: allow arm,primecell-periphid
From: Jie Gan @ 2026-06-24 9:49 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Tingwei Zhang, Jingyi Wang, Jie Gan, Abel Vesa,
Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Yuanfang Zhang
Cc: Konrad Dybcio, linux-arm-msm, devicetree, linux-kernel, coresight,
linux-arm-kernel
In-Reply-To: <20260624-fix-tracenoc-probe-issue-v2-0-786520f62f21@oss.qualcomm.com>
The TNOC device is an AMBA primecell and may carry the standard
arm,primecell-periphid property, which is used to supply the
peripheral ID when it cannot be read from the device registers.
Reference primecell.yaml and set additionalProperties to true so the
binding accepts arm,primecell-periphid along with the other common
primecell properties.
Signed-off-by: Jie Gan <jie.gan@oss.qualcomm.com>
---
Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml
index ef648a15b806..9624fc0adfdc 100644
--- a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml
+++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml
@@ -32,6 +32,9 @@ select:
required:
- compatible
+allOf:
+ - $ref: /schemas/arm/primecell.yaml#
+
properties:
$nodename:
pattern: "^tn(@[0-9a-f]+)$"
@@ -78,7 +81,7 @@ required:
- in-ports
- out-ports
-additionalProperties: false
+additionalProperties: true
examples:
- |
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/2] Fix traceNoC probe issue on Kaanapali
From: Jie Gan @ 2026-06-24 9:49 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Tingwei Zhang, Jingyi Wang, Jie Gan, Abel Vesa,
Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Yuanfang Zhang
Cc: Konrad Dybcio, linux-arm-msm, devicetree, linux-kernel, coresight,
linux-arm-kernel
Patch 1 changes the binding to allow the TraceNoC device accepts
arm,primecell-periphid property.
Patch 2 fixes the deferred probe issue for the TraceNoC device by
adding the arm,primecell-periphid property to bypass the AMBA check.
Signed-off-by: Jie Gan <jie.gan@oss.qualcomm.com>
---
Changes in v2:
- address the ATID issue reported by Sashiko.
- update binding to accept arm,primecell-periphid property.
- Link to v1: https://lore.kernel.org/r/20260624-fix-tracenoc-probe-issue-v1-1-bcc785198fc5@oss.qualcomm.com
---
Jie Gan (2):
dt-bindings: arm: qcom,coresight-tnoc: allow arm,primecell-periphid
arm64: dts: qcom: kaanapali: fix traceNoC probe issue
Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml | 5 ++++-
arch/arm64/boot/dts/qcom/kaanapali.dtsi | 1 +
2 files changed, 5 insertions(+), 1 deletion(-)
---
base-commit: 4e5dfb7c84012007c3c7061126491bbc92d71bf1
change-id: 20260624-fix-tracenoc-probe-issue-c6429da28df4
Best regards,
--
Jie Gan <jie.gan@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH 2/5] dt-bindings: display: panel-lvds: Add compatible for Opto Logic SCX1001511GGC49
From: Krzysztof Kozlowski @ 2026-06-24 9:43 UTC (permalink / raw)
To: Leonardo Costa
Cc: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
sam, leonardo.costa, dri-devel, devicetree, linux-kernel,
linux-arm-kernel
In-Reply-To: <20260623195741.495734-3-leoreis.costa@gmail.com>
On Tue, Jun 23, 2026 at 04:57:38PM -0300, Leonardo Costa wrote:
> From: Leonardo Costa <leonardo.costa@toradex.com>
>
> The Opto Logic SCX1001511GGC49 is a 10.1" WXGA (1280x800) TFT LCD LVDS
> panel.
>
> Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
> ---
> Documentation/devicetree/bindings/display/panel/panel-lvds.yaml | 2 ++
> 1 file changed, 2 insertions(+)
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 1/5] dt-bindings: vendor-prefixes: Add Opto Logic
From: Krzysztof Kozlowski @ 2026-06-24 9:42 UTC (permalink / raw)
To: Leonardo Costa
Cc: laurent.pinchart, neil.armstrong, jesszhan0024, maarten.lankhorst,
mripard, tzimmermann, airlied, simona, robh, krzk+dt, conor+dt,
nm, vigneshr, kristo, prabhakar.mahadev-lad.rj, thierry.reding,
sam, leonardo.costa, dri-devel, devicetree, linux-kernel,
linux-arm-kernel
In-Reply-To: <20260623195741.495734-2-leoreis.costa@gmail.com>
On Tue, Jun 23, 2026 at 04:57:37PM -0300, Leonardo Costa wrote:
> From: Leonardo Costa <leonardo.costa@toradex.com>
>
> Add vendor prefix for Opto Logic, a Swiss display solutions provider and
> printing systems manufacturer.
>
> Link: https://optologic.ch/
> Signed-off-by: Leonardo Costa <leonardo.costa@toradex.com>
> ---
> Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
> 1 file changed, 2 insertions(+)
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v3 07/15] drm/tidss: oldi: Remove define for unused register OLDI_LB_CTRL
From: Swamil Jain @ 2026-06-24 9:41 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-7-7fefdc5d1adf@ideasonboard.com>
On 5/29/26 14:15, Tomi Valkeinen wrote:
> OLDI_LB_CTRL define is not used, and doesn't seem to exist at least on
> some SoCs. Let's remove the define.
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_oldi.h | 1 -
> 1 file changed, 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_oldi.h b/drivers/gpu/drm/tidss/tidss_oldi.h
> index 8cd535c5ee65..a361e6dbfce3 100644
> --- a/drivers/gpu/drm/tidss/tidss_oldi.h
> +++ b/drivers/gpu/drm/tidss/tidss_oldi.h
> @@ -20,7 +20,6 @@ struct tidss_oldi;
>
> /* Register offsets */
> #define OLDI_PD_CTRL 0x100
> -#define OLDI_LB_CTRL 0x104
>
> /* Power control bits */
> #define OLDI_PWRDOWN_TX(n) BIT(n)
>
^ permalink raw reply
* Re: [RFC PATCH] irqchip/gic-v3-its: enable dynamic MSI-X allocation
From: Jinqian Yang @ 2026-06-24 9:29 UTC (permalink / raw)
To: Marc Zyngier
Cc: lpieralisi, tglx, alex, linux-kernel, linux-arm-kernel,
liuyonglong, wangzhou1, linuxarm
In-Reply-To: <86o6h0quvj.wl-maz@kernel.org>
On 2026/6/24 15:07, Marc Zyngier wrote:
> On Wed, 24 Jun 2026 03:53:45 +0100,
> Jinqian Yang <yangjinqian1@huawei.com> wrote:
>>
>> On ARM64 platforms with GICv3 ITS, VFIO PCI passthrough currently
>> cannot dynamically allocate MSI-X vectors after MSI-X has been
>> enabled. When QEMU needs to extend the vector range, it must
>> disable MSI-X, free all interrupts, then re-enable with a larger
>> allocation. This creates an interrupt loss window for already-active
>> vectors.
>>
>> Consider HNS3 with RoCE: NIC and RDMA share one PCI device and
>> ITS DeviceID, with MSI-X vectors partitioned as NIC (lower range)
>> then RoCE (starting at base_vector = num_nic_msi). In VFIO
>> passthrough, loading hns_roce after hns3 forces QEMU to tear down
>> all interrupts before re-allocating the larger range. During this
>> process, NIC interrupts may be lost. Testing confirmed that this
>> occasionally occurs, causing the network port reset to fail.
>
> Well, that's what you get for not exposing differentiated functions.
> Eventually, you face the reality that this is a poor design.
>
Fair point, though this is not unique to HNS3.. All major NIC+RDMA
vendors share the same PCI function.
>>
>> ITS_MSI_FLAGS_SUPPORTED lacks MSI_FLAG_PCI_MSIX_ALLOC_DYN, causing
>> pci_msix_can_alloc_dyn() to return false. VFIO then sets
>> has_dyn_msix=false and never clears VFIO_IRQ_INFO_NORESIZE for
>> MSI-X, keeping the old "disable and reallocate" behavior.
>>
>> The essential prerequisite for enabling this flag is the fix to
>> msi_prepare() call timing (commit 1396e89e09f0 ("genirq/msi: Move
>> prepare() call to per-device allocation")): msi_prepare() is
>> now called once at per-device domain creation with hwsize, so ITS
>> creates an ITT with sufficient capacity for all MSI-X vectors.
>> Without this fix, msi_prepare() was called per-allocation with
>> semi-random nvec, maybe resulting in an ITT too small for dynamic
>> vector addition.
>
> How is this paragraph relevant? The kernel has had this fix for over a
> year, and backporting this series is not something I plan to ever do.
>
Will remove from commit msg.
>>
>> With this in place, dynamic MSI-X allocation works correctly:
>> msi_domain_alloc_irq_at() uses populate_alloc_info() to copy the
>> pre-prepared alloc_data without re-invoking msi_prepare(), so each
>> new vector simply gets a LPI entry in the already-allocated ITT,
>> without affecting existing vectors.
>>
>> Signed-off-by: Jinqian Yang <yangjinqian1@huawei.com>
>> ---
>> drivers/irqchip/irq-gic-its-msi-parent.c | 3 ++-
>> 1 file changed, 2 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c
>> index b9257103a999..b2b9d2068bb1 100644
>> --- a/drivers/irqchip/irq-gic-its-msi-parent.c
>> +++ b/drivers/irqchip/irq-gic-its-msi-parent.c
>> @@ -18,7 +18,8 @@
>>
>> #define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
>> MSI_FLAG_PCI_MSIX | \
>> - MSI_FLAG_MULTI_PCI_MSI)
>> + MSI_FLAG_MULTI_PCI_MSI | \
>> + MSI_FLAG_PCI_MSIX_ALLOC_DYN)
>>
>> static int its_translate_frame_address(struct fwnode_handle *msi_node, phys_addr_t *pa)
>> {
>
> What has this been tested with? In which conditions?
>
Tested on Hisilicon HIP09 (ARM64, GICv3/GICv4.1) with latest
upstream kernel and QEMU 8.2.
VFIO passthrough of HNS3 NIC to VM: load both hns3 and
hns_roce_hw_v2 drivers, then trigger FLR. Without the flag,
QEMU disables/re-enables MSI-X around FLR, causing occasional
link up failure due to interrupt loss.
Thanks,
Jinqian
^ permalink raw reply
* Re: [PATCH RFC 3/3] arm64: Add HOTPLUG_PARALLEL support for secondary CPUs
From: Jinjie Ruan @ 2026-06-24 9:29 UTC (permalink / raw)
To: Will Deacon
Cc: Michael Kelley, catalin.marinas@arm.com,
tsbogend@alpha.franken.de, pjw@kernel.org, palmer@dabbelt.com,
aou@eecs.berkeley.edu, alex@ghiti.fr, tglx@kernel.org,
mingo@redhat.com, bp@alien8.de, dave.hansen@linux.intel.com,
hpa@zytor.com, peterz@infradead.org, kees@kernel.org,
nathan@kernel.org, linusw@kernel.org, ojeda@kernel.org,
david.kaplan@amd.com, lukas.bulwahn@redhat.com,
ryan.roberts@arm.com, maz@kernel.org, timothy.hayes@arm.com,
lpieralisi@kernel.org, thuth@redhat.com, oupton@kernel.org,
yeoreum.yun@arm.com, miko.lenczewski@arm.com, broonie@kernel.org,
kevin.brodsky@arm.com, james.clark@linaro.org, tabba@google.com,
mrigendra.chaubey@gmail.com, arnd@arndb.de,
anshuman.khandual@arm.com, x86@kernel.org,
linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, linux-mips@vger.kernel.org,
linux-riscv@lists.infradead.org
In-Reply-To: <ajqZJVBCu_D-BkTy@willie-the-truck>
On 6/23/2026 10:33 PM, Will Deacon wrote:
> On Mon, Jun 22, 2026 at 05:16:30PM +0800, Jinjie Ruan wrote:
>>
>>
>> On 6/18/2026 8:21 PM, Will Deacon wrote:
>>> Hi Jinjie,
>>>
>>> On Mon, Jun 15, 2026 at 04:51:48PM +0800, Jinjie Ruan wrote:
>>>> On 6/12/2026 11:45 PM, Michael Kelley wrote:
>>>>> From: Jinjie Ruan <ruanjinjie@huawei.com> Sent: Thursday, June 11, 2026 6:38 AM
>>>>>>
>>>>>> Support for parallel secondary CPU bringup is already utilized by x86,
>>>>>> MIPS, and RISC-V. This patch brings this capability to the arm64
>>>>>> architecture.
>>>>>>
>>>>>> Rework the global `secondary_data` accessed during early boot into
>>>>>> a per-CPU array. This array maps logical CPU IDs to MPIDR_EL1 values,
>>>>>> enabling the early boot code in head.S to resolve each secondary CPU's
>>>>>> logical ID concurrently.
>>>>>>
>>>>>> To fully enable HOTPLUG_PARALLEL, this patch implements:
>>>>>> 1) An arm64-specific arch_cpuhp_kick_ap_alive() handler.
>>>>>> 2) Callbacks to cpuhp_ap_sync_alive() inside secondary_start_kernel().
>>>>>>
>>>>>> Successfully tested on QEMU ARM64 virt machine (KVM on, 128 vCPUs).
>>>>>>
>>>>>> | test kernel | secondary CPUs boot time |
>>>>>> | --------------------- | -------------------- |
>>>>>> | Without this patch | 155.672 |
>>>>>> | cpuhp.parallel=0 | 62.897 |
>>>>>> | cpuhp.parallel=1 | 166.703 |
>>>>>
>>>>> The last two rows seem mixed up. I would expect parallel=0 to
>>>>> result in a longer boot time.
>>>>
>>>> Hi, Michael,
>>>>
>>>> The results are correct and not mixed up.
>>>>
>>>> Compared to the original non‑HOTPLUG_PARALLEL approach, the advantage of
>>>> cpuhp.parallel=0 lies in its use of cpu_relax(`yield` on arm64) instead
>>>> of the wait_for_completion_timeout() mechanism (which may cause sleep
>>>> and context switching). This significantly reduces the overhead of VM
>>>> exits and context switches in a KVM guest, thereby cutting the secondary
>>>> CPU boot time by more than half.
>>>
>>> I don't think that's a particularly compelling reason to enable this for
>>> arm64, in all honesty. The yield instruction typically doesn't do
>>> anything on actual arm64 silicon, so this probably means that you're
>>> introducing busy-loops which tend to be bad for power and scalability.
>>>
>>> I implemented this a while ago [1] but didn't manage to see much in terms
>>> of performance improvement and so I didn't bother to send the patches out
>>> after talking about it at KVM forum [2]. However, as mentioned at the end
>>> of that talk, it _is_ still useful for confidential VMs using PSCI so
>>> let me dust off my old series and send it out to see what you think.
>>
>> Hi Will,
>>
>> Thanks for the insights! Your point about using PSCI v0.2's Context ID
>> to avoid the NR_CPUS array for input parameters (like
>> secondary_data.task) is incredibly elegant.
>>
>> However, if I understand your series correctly, it seems your approach
>> primarily targets preventing the concurrent use of secondary_data.task,
>> but it doesn't seem to account for the potential data trampling on
>> secondary_data.status when multiple secondary CPUs are brought up
>> simultaneously.
>>
>> update_cpu_boot_status()
>> -> WRITE_ONCE(secondary_data.status.flags[val], 1)
>>
>> arch_cpuhp_cleanup_kick_cpu()
>> -> status = READ_ONCE(secondary_data.status)
>
> I need to dust it back off but IIRC I made that thing a byte array, with
> a separate byte for each failure reason.
>
> Will
Hi, Will,
Thanks for the clarification. A byte array with a separate byte per
failure reason does prevent trampling between different failure types.
However, the issue arises if multiple secondary CPUs fail for the exact
same reason simultaneously. In that scenario, they will still attempt to
write to the same byte index at the same time. As a result, the primary
CPU reading the status later won't be able to distinguish which specific
CPUs encountered the problem, or how many of them failed.
I test your patch with error inject, which configures CPU4 and CPU6,
along with CPU16 and CPU18, to generate distinct boot failures, while
making CPU17 hit the same boot failure as CPU16. The output is not
correct as below:
[ 0.332528] smp: Bringing up secondary CPUs ...
[ 10.674114] CPU1 failed to report alive state
[ 10.674392] CPU1 detected lack of support for 52-bit VAs
[ 10.674610] CPUs may be stuck in kernel
[ 21.016707] CPU2 failed to report alive state
[ 31.357320] CPU3 failed to report alive state
[ 41.693228] CPU4 failed to report alive state
[ 52.033112] CPU5 failed to report alive state
[ 62.378198] CPU6 failed to report alive state
[ 72.716467] CPU7 failed to report alive state
[ 83.046746] CPU8 failed to report alive state
[ 93.338020] CPU9 failed to report alive state
[ 103.591986] CPU10 failed to report alive state
[ 113.893741] CPU11 failed to report alive state
[ 124.230870] CPU12 failed to report alive state
[ 134.567597] CPU13 failed to report alive state
[ 144.905256] CPU14 failed to report alive state
[ 155.247633] CPU15 failed to report alive state
[ 165.584891] CPU16 failed to report alive state
[ 175.920794] CPU17 failed to report alive state
[ 186.256323] CPU18 failed to report alive state
[ 196.596136] CPU19 failed to report alive state
The expected output is as below:
CPU4 failed to report alive state
CPU4: is stuck in kernel
CPU4: does not support 52-bit VAs
CPU6 failed to report alive state
CPU6: is stuck in kernel
CPU6: does not support 4K granule
GICv3: CPU8: found redistributor 8 region 0:0x00000000081a0000
GICv3: CPU8: using allocated LPI pending table @0x0000000100360000
CPU8: Booted secondary processor 0x0000000008 [0x410fd034]
...
CPU16 failed to report alive state
psci: CPU16 killed (polled 0 ms)
CPU16: died during early boot
CPU17: will not boot
CPU17 failed to report alive state
psci: CPU17 killed (polled 0 ms)
CPU17: died during early boot
CPU18 failed to report alive state
Kernel panic - not syncing: CPU18 detected unsupported configuration
Best regards,
Jinjie
>
^ permalink raw reply
* [PATCH v3 11/12] arm64: smp: Rework early boot data into per-CPU arrays
From: Jinjie Ruan @ 2026-06-24 9:25 UTC (permalink / raw)
To: catalin.marinas, will, tsbogend, tglx, mingo, bp, dave.hansen,
hpa, peterz, kees, nathan, linusw, ojeda, david.kaplan,
lukas.bulwahn, ryan.roberts, maz, timothy.hayes, lpieralisi,
thuth, menglong8.dong, oupton, yeoreum.yun, miko.lenczewski,
broonie, kevin.brodsky, james.clark, yangyicong, tabba, osandov,
arnd, anshuman.khandual, david, akpm, ljs, dev.jain, yang,
chaitanyas.prakash, kprateek.nayak, chenl311, sshegde,
thorsten.blum, chang.seok.bae, tim.c.chen, x86, linux-kernel,
linux-arm-kernel, linux-mips
Cc: ruanjinjie
In-Reply-To: <20260624092537.2916971-1-ruanjinjie@huawei.com>
Rework the global `secondary_data` and `__early_cpu_boot_status`
accessed during early boot into per-CPU arrays to allow secondary
CPUs to boot in parallel.
And reuse `__cpu_logical_map` array in the early boot code in head.S
to resolve each secondary CPU's logical ID concurrently.
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
arch/arm64/include/asm/smp.h | 16 ++++++------
arch/arm64/kernel/asm-offsets.c | 2 ++
arch/arm64/kernel/head.S | 44 +++++++++++++++++++++++++++------
arch/arm64/kernel/setup.c | 9 +++++++
arch/arm64/kernel/smp.c | 11 +++++----
arch/arm64/mm/mmu.c | 2 +-
6 files changed, 62 insertions(+), 22 deletions(-)
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index e2151a01731f..7e69219e6e33 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -34,14 +34,9 @@
/*
* Logical CPU mapping.
*/
-extern u64 __cpu_logical_map[NR_CPUS];
+extern void set_cpu_logical_map(unsigned int cpu, u64 hwid);
extern u64 cpu_logical_map(unsigned int cpu);
-static inline void set_cpu_logical_map(unsigned int cpu, u64 hwid)
-{
- __cpu_logical_map[cpu] = hwid;
-}
-
struct seq_file;
/*
@@ -92,8 +87,11 @@ struct secondary_data {
long status;
};
-extern struct secondary_data secondary_data;
-extern long __early_cpu_boot_status;
+static_assert((sizeof(struct secondary_data) & (sizeof(struct secondary_data) - 1)) == 0,
+ "secondary_data size must be a power of 2 for assembly lsl assembly!");
+
+extern struct secondary_data cpu_boot_data[NR_CPUS];
+extern long __early_cpu_boot_status[NR_CPUS];
extern void secondary_entry(void);
extern void arch_send_call_function_single_ipi(int cpu);
@@ -124,7 +122,7 @@ static inline void __noreturn cpu_park_loop(void)
static inline void update_cpu_boot_status(unsigned int cpu, int val)
{
- WRITE_ONCE(secondary_data.status, val);
+ WRITE_ONCE(cpu_boot_data[cpu].status, val);
/* Ensure the visibility of the status update */
dsb(ishst);
}
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index b6367ff3a49c..566e2222af5b 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -11,6 +11,7 @@
#include <linux/arm_sdei.h>
#include <linux/sched.h>
#include <linux/ftrace.h>
+#include <linux/log2.h>
#include <linux/kexec.h>
#include <linux/mm.h>
#include <linux/kvm_host.h>
@@ -97,6 +98,7 @@ int main(void)
BLANK();
#endif
DEFINE(CPU_BOOT_TASK, offsetof(struct secondary_data, task));
+ DEFINE(SECONDARY_DATA_SHIFT, ilog2(sizeof(struct secondary_data)));
BLANK();
DEFINE(FTR_OVR_VAL_OFFSET, offsetof(struct arm64_ftr_override, val));
DEFINE(FTR_OVR_MASK_OFFSET, offsetof(struct arm64_ftr_override, mask));
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 87a822e5c4ca..f58de58c4edc 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -12,6 +12,7 @@
#include <linux/linkage.h>
#include <linux/init.h>
#include <linux/pgtable.h>
+#include <linux/threads.h>
#include <asm/asm_pointer_auth.h>
#include <asm/assembler.h>
@@ -348,6 +349,31 @@ pen: ldr x4, [x3]
b pen
SYM_FUNC_END(secondary_holding_pen)
+ /*
+ * Convert the physical MPIDR of the current secondary CPU
+ * to its logical CPUID by traversing __cpu_logical_map
+ * in parallel.
+ */
+ .macro mpidr_to_cpuid, mpidr, cpuid, tmp1, tmp2
+ mov_q \tmp1, MPIDR_HWID_BITMASK
+ and \mpidr, \mpidr, \tmp1
+
+ adr_l \tmp1, __cpu_logical_map
+ mov \cpuid, #0
+.Lfind_cpuid\@:
+ ldr \tmp2, [\tmp1, \cpuid, lsl #3]
+ cmp \tmp2, #-1
+ b.eq .Lnext_cpu\@
+ cmp \tmp2, \mpidr
+ b.eq .Lfound_cpuid\@
+.Lnext_cpu\@:
+ add \cpuid, \cpuid, #1
+ cmp \cpuid, #NR_CPUS
+ b.ne .Lfind_cpuid\@
+ b __secondary_too_slow
+.Lfound_cpuid\@:
+ .endm
+
/*
* Secondary entry point that jumps straight into the kernel. Only to
* be used where CPUs are brought online dynamically by the kernel.
@@ -363,6 +389,8 @@ SYM_FUNC_START_LOCAL(secondary_startup)
* Common entry point for secondary CPUs.
*/
mov x20, x0 // preserve boot mode
+ mrs x0, mpidr_el1
+ mpidr_to_cpuid mpidr=x0, cpuid=x19, tmp1=x1, tmp2=x3
#ifdef CONFIG_ARM64_VA_BITS_52
alternative_if ARM64_HAS_VA52
@@ -386,12 +414,12 @@ SYM_FUNC_START_LOCAL(__secondary_switched)
mov x0, x20
bl finalise_el2
- str_l xzr, __early_cpu_boot_status, x3
adr_l x5, vectors
msr vbar_el1, x5
isb
- adr_l x0, secondary_data
+ adr_l x0, cpu_boot_data
+ add x0, x0, x19, lsl #SECONDARY_DATA_SHIFT
ldr x2, [x0, #CPU_BOOT_TASK]
cbz x2, __secondary_too_slow
@@ -430,13 +458,14 @@ SYM_FUNC_END(set_cpu_boot_mode_flag)
*
* update_early_cpu_boot_status tmp, status
* - Corrupts tmp1, tmp2
- * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * - Writes 'status' to __early_cpu_boot_status[cpu] and makes sure
* it is committed to memory.
*/
- .macro update_early_cpu_boot_status status, tmp1, tmp2
- mov \tmp2, #\status
+ .macro update_early_cpu_boot_status status, cpu_reg, tmp1, tmp2
adr_l \tmp1, __early_cpu_boot_status
+ mov \tmp2, #\status
+ add \tmp1, \tmp1, \cpu_reg, lsl #3
str \tmp2, [\tmp1]
dmb sy
dc ivac, \tmp1 // Invalidate potentially stale cache line
@@ -486,7 +515,7 @@ SYM_FUNC_START(__cpu_secondary_check52bitva)
#endif
update_early_cpu_boot_status \
- CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1
+ CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x19, x0, x1
1: wfe
wfi
b 1b
@@ -498,7 +527,7 @@ SYM_FUNC_END(__cpu_secondary_check52bitva)
SYM_FUNC_START_LOCAL(__no_granule_support)
/* Indicate that this CPU can't boot and is stuck in the kernel */
update_early_cpu_boot_status \
- CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2
+ CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x19, x1, x2
1:
wfe
wfi
@@ -508,6 +537,7 @@ SYM_FUNC_END(__no_granule_support)
SYM_FUNC_START_LOCAL(__primary_switch)
adrp x1, reserved_pg_dir
adrp x2, __pi_init_idmap_pg_dir
+ mov x19, #0
bl __enable_mmu
adrp x1, early_init_stack
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
index 23c05dc7a8f2..856c00ab6f19 100644
--- a/arch/arm64/kernel/setup.c
+++ b/arch/arm64/kernel/setup.c
@@ -273,6 +273,15 @@ arch_initcall(reserve_memblock_reserved_regions);
u64 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = INVALID_HWID };
+void set_cpu_logical_map(unsigned int cpu, u64 hwid)
+{
+ unsigned long start = (unsigned long)&__cpu_logical_map[cpu];
+ unsigned long end = start + sizeof(__cpu_logical_map[cpu]);
+
+ __cpu_logical_map[cpu] = hwid;
+ dcache_clean_inval_poc(start, end);
+}
+
u64 cpu_logical_map(unsigned int cpu)
{
return __cpu_logical_map[cpu];
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 14b94df26b44..98ddbe50081d 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -61,7 +61,8 @@
* so we need some other way of telling a new secondary core
* where to place its SVC stack
*/
-struct secondary_data secondary_data;
+struct secondary_data cpu_boot_data[NR_CPUS] ____cacheline_aligned;
+
/* Number of CPUs which aren't online, but looping in kernel text. */
static int cpus_stuck_in_kernel;
@@ -115,7 +116,7 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
* We need to tell the secondary core where to find its stack and the
* page tables.
*/
- secondary_data.task = idle;
+ cpu_boot_data[cpu].task = idle;
update_cpu_boot_status(cpu, CPU_MMU_OFF);
/* Now bring the CPU into our world */
@@ -136,10 +137,10 @@ void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
* We failed to synchronise with the CPU, so check if it left us
* any breadcrumbs.
*/
- secondary_data.task = NULL;
- status = READ_ONCE(secondary_data.status);
+ cpu_boot_data[cpu].task = NULL;
+ status = READ_ONCE(cpu_boot_data[cpu].status);
if (status == CPU_MMU_OFF)
- status = READ_ONCE(__early_cpu_boot_status);
+ status = READ_ONCE(__early_cpu_boot_status[cpu]);
switch (status & CPU_BOOT_STATUS_MASK) {
default:
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index dd85e093ffdb..71c160c6c383 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -62,7 +62,7 @@ static bool rodata_is_rw __ro_after_init = true;
* The booting CPU updates the failed status @__early_cpu_boot_status,
* with MMU turned off.
*/
-long __section(".mmuoff.data.write") __early_cpu_boot_status;
+long __section(".mmuoff.data.write") __early_cpu_boot_status[NR_CPUS];
static DEFINE_SPINLOCK(swapper_pgdir_lock);
static DEFINE_MUTEX(fixmap_lock);
--
2.34.1
^ permalink raw reply related
* [PATCH v3 10/12] arm64: smp: Pass CPU ID to update_cpu_boot_status()
From: Jinjie Ruan @ 2026-06-24 9:25 UTC (permalink / raw)
To: catalin.marinas, will, tsbogend, tglx, mingo, bp, dave.hansen,
hpa, peterz, kees, nathan, linusw, ojeda, david.kaplan,
lukas.bulwahn, ryan.roberts, maz, timothy.hayes, lpieralisi,
thuth, menglong8.dong, oupton, yeoreum.yun, miko.lenczewski,
broonie, kevin.brodsky, james.clark, yangyicong, tabba, osandov,
arnd, anshuman.khandual, david, akpm, ljs, dev.jain, yang,
chaitanyas.prakash, kprateek.nayak, chenl311, sshegde,
thorsten.blum, chang.seok.bae, tim.c.chen, x86, linux-kernel,
linux-arm-kernel, linux-mips
Cc: ruanjinjie
In-Reply-To: <20260624092537.2916971-1-ruanjinjie@huawei.com>
To support CONFIG_HOTPLUG_PARALLEL, the CPU boot status tracking must
be refactored from a single global variable (secondary_data.status)
to a per-CPU tracking structure to prevent multi-core race conditions.
Add a 'cpu' parameter to update_cpu_boot_status() and update all its
callsites to pass the corresponding CPU ID. This allows updating the
boot status at a per-CPU granularity during parallel bringup.
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
arch/arm64/include/asm/smp.h | 6 +++---
arch/arm64/kernel/cpufeature.c | 2 +-
arch/arm64/kernel/smp.c | 8 ++++----
arch/arm64/mm/context.c | 5 +++--
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 10ea4f543069..e2151a01731f 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -122,7 +122,7 @@ static inline void __noreturn cpu_park_loop(void)
}
}
-static inline void update_cpu_boot_status(int val)
+static inline void update_cpu_boot_status(unsigned int cpu, int val)
{
WRITE_ONCE(secondary_data.status, val);
/* Ensure the visibility of the status update */
@@ -134,9 +134,9 @@ static inline void update_cpu_boot_status(int val)
* which calls for a kernel panic. Update the boot status and park the calling
* CPU.
*/
-static inline void __noreturn cpu_panic_kernel(void)
+static inline void __noreturn cpu_panic_kernel(unsigned int cpu)
{
- update_cpu_boot_status(CPU_PANIC_KERNEL);
+ update_cpu_boot_status(cpu, CPU_PANIC_KERNEL);
cpu_park_loop();
}
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index a1a13f3e01ed..abc3aef9c206 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -3685,7 +3685,7 @@ static void verify_local_cpu_caps(u16 scope_mask)
caps->desc, system_has_cap, cpu_has_cap);
if (cpucap_panic_on_conflict(caps))
- cpu_panic_kernel();
+ cpu_panic_kernel(smp_processor_id());
else
cpu_die_early();
}
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 6b9586a69429..14b94df26b44 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -116,7 +116,7 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
* page tables.
*/
secondary_data.task = idle;
- update_cpu_boot_status(CPU_MMU_OFF);
+ update_cpu_boot_status(cpu, CPU_MMU_OFF);
/* Now bring the CPU into our world */
ret = boot_secondary(cpu, idle);
@@ -262,7 +262,7 @@ asmlinkage notrace void secondary_start_kernel(void)
pr_info("CPU%u: Booted secondary processor 0x%010lx [0x%08x]\n",
cpu, (unsigned long)mpidr,
read_cpuid_id());
- update_cpu_boot_status(CPU_BOOT_SUCCESS);
+ update_cpu_boot_status(cpu, CPU_BOOT_SUCCESS);
set_cpu_online(cpu, true);
/*
@@ -420,11 +420,11 @@ void __noreturn cpu_die_early(void)
rcutree_report_cpu_dead();
if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
- update_cpu_boot_status(CPU_KILL_ME);
+ update_cpu_boot_status(cpu, CPU_KILL_ME);
__cpu_try_die(cpu);
}
- update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
+ update_cpu_boot_status(cpu, CPU_STUCK_IN_KERNEL);
cpu_park_loop();
}
diff --git a/arch/arm64/mm/context.c b/arch/arm64/mm/context.c
index 0f4a28b87469..e78ae989ad57 100644
--- a/arch/arm64/mm/context.c
+++ b/arch/arm64/mm/context.c
@@ -63,6 +63,7 @@ static u32 get_cpu_asid_bits(void)
/* Check if the current cpu's ASIDBits is compatible with asid_bits */
void verify_cpu_asid_bits(void)
{
+ unsigned int cpu = smp_processor_id();
u32 asid = get_cpu_asid_bits();
if (asid < asid_bits) {
@@ -71,8 +72,8 @@ void verify_cpu_asid_bits(void)
* fewer ASID bits than the boot CPU.
*/
pr_crit("CPU%d: smaller ASID size(%u) than boot CPU (%u)\n",
- smp_processor_id(), asid, asid_bits);
- cpu_panic_kernel();
+ cpu, asid, asid_bits);
+ cpu_panic_kernel(cpu);
}
}
--
2.34.1
^ permalink raw reply related
* [PATCH v3 07/12] arm64: cpu_ops: Make 'cpu_operations' pointer global instead of per-cpu
From: Jinjie Ruan @ 2026-06-24 9:25 UTC (permalink / raw)
To: catalin.marinas, will, tsbogend, tglx, mingo, bp, dave.hansen,
hpa, peterz, kees, nathan, linusw, ojeda, david.kaplan,
lukas.bulwahn, ryan.roberts, maz, timothy.hayes, lpieralisi,
thuth, menglong8.dong, oupton, yeoreum.yun, miko.lenczewski,
broonie, kevin.brodsky, james.clark, yangyicong, tabba, osandov,
arnd, anshuman.khandual, david, akpm, ljs, dev.jain, yang,
chaitanyas.prakash, kprateek.nayak, chenl311, sshegde,
thorsten.blum, chang.seok.bae, tim.c.chen, x86, linux-kernel,
linux-arm-kernel, linux-mips
Cc: ruanjinjie
In-Reply-To: <20260624092537.2916971-1-ruanjinjie@huawei.com>
From: Will Deacon <will@kernel.org>
'cpu_ops' is an NR_CPUS-length array of 'cpu_operations' pointers, which
theoretically allows for different CPUs to have different bringup and
hotplug backends.
In reality, this complexity exists only to deal with the case where CPU0
is not hotpluggable, so replace the array with a single, global pointer
and record separately whether or not they apply to the boot CPU. Update
the logic in init_cpu_ops() to enforce that only a single set of
'cpu_ops' is required.
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
arch/arm64/kernel/cpu_ops.c | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c
index e133011f64b5..eacfb88a0c0c 100644
--- a/arch/arm64/kernel/cpu_ops.c
+++ b/arch/arm64/kernel/cpu_ops.c
@@ -20,7 +20,8 @@ extern const struct cpu_operations acpi_parking_protocol_ops;
#endif
extern const struct cpu_operations cpu_psci_ops;
-static const struct cpu_operations *cpu_ops[NR_CPUS] __ro_after_init;
+static const struct cpu_operations *cpu_ops __ro_after_init;
+static bool boot_cpu_has_enable_method __ro_after_init;
static const struct cpu_operations *const dt_supported_cpu_ops[] __initconst = {
&smp_spin_table_ops,
@@ -40,6 +41,9 @@ static const struct cpu_operations * __init cpu_get_ops(const char *name)
{
const struct cpu_operations *const *ops;
+ if (!name)
+ return NULL;
+
ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops;
while (*ops) {
@@ -49,6 +53,7 @@ static const struct cpu_operations * __init cpu_get_ops(const char *name)
ops++;
}
+ pr_warn("Unsupported enable-method: %s\n", name);
return NULL;
}
@@ -94,25 +99,31 @@ static const char *__init cpu_read_enable_method(int cpu)
return enable_method;
}
/*
- * Read a cpu's enable method and record it in cpu_ops.
+ * Read a cpu's enable method and update/check cpu_ops.
*/
int __init init_cpu_ops(int cpu)
{
const char *enable_method = cpu_read_enable_method(cpu);
+ const struct cpu_operations *ops = cpu_get_ops(enable_method);
- if (!enable_method)
+ if (!ops)
return -ENODEV;
- cpu_ops[cpu] = cpu_get_ops(enable_method);
- if (!cpu_ops[cpu]) {
- pr_warn("Unsupported enable-method: %s\n", enable_method);
- return -EOPNOTSUPP;
- }
+ if (!cpu_ops)
+ cpu_ops = ops;
+ else if (cpu_ops != ops)
+ return -EBUSY;
+
+ if (cpu == 0)
+ boot_cpu_has_enable_method = true;
return 0;
}
const struct cpu_operations *get_cpu_ops(int cpu)
{
- return cpu_ops[cpu];
+ if (cpu || boot_cpu_has_enable_method)
+ return cpu_ops;
+
+ return NULL;
}
--
2.34.1
^ permalink raw reply related
* [PATCH v3 12/12] arm64: Add HOTPLUG_PARALLEL support for secondary CPUs
From: Jinjie Ruan @ 2026-06-24 9:25 UTC (permalink / raw)
To: catalin.marinas, will, tsbogend, tglx, mingo, bp, dave.hansen,
hpa, peterz, kees, nathan, linusw, ojeda, david.kaplan,
lukas.bulwahn, ryan.roberts, maz, timothy.hayes, lpieralisi,
thuth, menglong8.dong, oupton, yeoreum.yun, miko.lenczewski,
broonie, kevin.brodsky, james.clark, yangyicong, tabba, osandov,
arnd, anshuman.khandual, david, akpm, ljs, dev.jain, yang,
chaitanyas.prakash, kprateek.nayak, chenl311, sshegde,
thorsten.blum, chang.seok.bae, tim.c.chen, x86, linux-kernel,
linux-arm-kernel, linux-mips
Cc: ruanjinjie
In-Reply-To: <20260624092537.2916971-1-ruanjinjie@huawei.com>
Support for parallel secondary CPU bringup is already utilized by x86,
MIPS and RISC-V. This patch brings this capability to the arm64
architecture.
To fully enable HOTPLUG_PARALLEL, this patch implements an arm64-specific
arch_cpuhp_init_parallel_bringup() handler.
In parallel bringup, early `set_cpu_present(cpu, 0)` inside
cpu_die_early() removes the secondary CPU prematurely, causing the primary
CPU's second-stage cpuhp_bringup_mask() sweep to skip it and drop
failure logs.
Remove this early unregistration from the secondary CPU, deferring the
set_cpu_present(cpu, 0) call to the primary CPU's cleanup path to ensure
robust parallel boot timeout detection.
Tested natively with ATF on QEMU arm64 virt machine with 64 cores
and also tested with KVM arm64 guest with 128 vCPUs.
Tested-by: Michael Kelley <mhklinux@outlook.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
arch/arm64/Kconfig | 2 +-
arch/arm64/kernel/smp.c | 12 ++++++++++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 24496e9967a8..a9d8030e7492 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -231,7 +231,7 @@ config ARM64
select HAVE_KPROBES
select HAVE_KRETPROBES
select HAVE_GENERIC_VDSO
- select HOTPLUG_SPLIT_STARTUP if SMP
+ select HOTPLUG_PARALLEL if SMP
select HOTPLUG_SMT if HOTPLUG_CPU
select IRQ_DOMAIN
select IRQ_FORCED_THREADING
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 98ddbe50081d..a973b2d3bab1 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -93,6 +93,15 @@ static inline int op_cpu_kill(unsigned int cpu)
}
#endif
+extern const struct cpu_operations cpu_psci_ops;
+
+/* Establish whether parallel bringup can be supported. */
+bool __init arch_cpuhp_init_parallel_bringup(void)
+{
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
+
+ return ops == &cpu_psci_ops;
+}
/*
* Boot a secondary CPU, and assign it the specified idle task.
@@ -137,6 +146,7 @@ void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
* We failed to synchronise with the CPU, so check if it left us
* any breadcrumbs.
*/
+ set_cpu_present(cpu, 0);
cpu_boot_data[cpu].task = NULL;
status = READ_ONCE(cpu_boot_data[cpu].status);
if (status == CPU_MMU_OFF)
@@ -416,8 +426,6 @@ void __noreturn cpu_die_early(void)
pr_crit("CPU%d: will not boot\n", cpu);
- /* Mark this CPU absent */
- set_cpu_present(cpu, 0);
rcutree_report_cpu_dead();
if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
--
2.34.1
^ permalink raw reply related
* [PATCH v3 09/12] arm64: cpufeature: Ensure atomic updates to system_cpucaps bitmap
From: Jinjie Ruan @ 2026-06-24 9:25 UTC (permalink / raw)
To: catalin.marinas, will, tsbogend, tglx, mingo, bp, dave.hansen,
hpa, peterz, kees, nathan, linusw, ojeda, david.kaplan,
lukas.bulwahn, ryan.roberts, maz, timothy.hayes, lpieralisi,
thuth, menglong8.dong, oupton, yeoreum.yun, miko.lenczewski,
broonie, kevin.brodsky, james.clark, yangyicong, tabba, osandov,
arnd, anshuman.khandual, david, akpm, ljs, dev.jain, yang,
chaitanyas.prakash, kprateek.nayak, chenl311, sshegde,
thorsten.blum, chang.seok.bae, tim.c.chen, x86, linux-kernel,
linux-arm-kernel, linux-mips
Cc: ruanjinjie
In-Reply-To: <20260624092537.2916971-1-ruanjinjie@huawei.com>
Parallel CPU bringup allows multiple secondary CPUs to concurrently
execute update_cpu_capabilities() during early boot.
The current non-atomic __set_bit() and __clear_bit() helpers perform
unserialized updates on the shared global bitmap, risking data races
and feature flag erasure.
Upgrade these operations to set_bit() and clear_bit() to ensure all
concurrent modifications are properly serialized via arm64 atomics.
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
arch/arm64/kernel/cpufeature.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index be75e60d56ca..a1a13f3e01ed 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -3548,7 +3548,7 @@ static void update_cpu_capabilities(u16 scope_mask)
if (!caps->matches(caps, cpucap_default_scope(caps))) {
if (match_all)
- __clear_bit(caps->capability, system_cpucaps);
+ clear_bit(caps->capability, system_cpucaps);
continue;
}
@@ -3559,7 +3559,7 @@ static void update_cpu_capabilities(u16 scope_mask)
if (!match_all && caps->desc && !caps->cpus)
pr_info("detected: %s\n", caps->desc);
- __set_bit(caps->capability, system_cpucaps);
+ set_bit(caps->capability, system_cpucaps);
if (boot_cpu && (caps->type & SCOPE_BOOT_CPU))
set_bit(caps->capability, boot_cpucaps);
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox