Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH v2 07/16] usb: hub: Power on connected M.2 E-key connectors
From: Andy Shevchenko @ 2026-06-10 14:31 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
	Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
	Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
	linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
	linux-mediatek, linux-arm-kernel, linux-kernel,
	Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-8-wenst@chromium.org>

On Wed, Jun 10, 2026 at 04:40:41PM +0800, Chen-Yu Tsai wrote:
> The new M.2 E-key connector can have a USB connection. For the USB device
> on this connector to work, its power must be enabled and the W_DISABLE2#
> signal deasserted. The connector driver handles this and provides a
> toggle over the power sequencing API.
> 
> This feature currently only supports a directly connected (no mux in
> between) M.2 E-key connector. Existing USB connector types are not
> covered. The USB A connector was recently added to the onboard devices
> driver. USB B connectors have historically been managed by the USB
> gadget or dual-role device controller drivers. USB C connectors are
> handled by TCPM drivers.
> 
> The power sequencing API does not know whether a power sequence provider
> is not needed or not available yet, so we only request it for connectors
> that we know need it, which at this time is just the E-key connector.
> 
> On the USB side, the port firmware node (if present) is tied to the
> usb_port device. This device is used to acquire the power sequencing
> descriptor. This allows the provider to tell the different ports on one
> hub apart.
> 
> This feature is not implemented in the onboard USB devices driver. The
> power sequencing API expects the consumer device to make the request,
> but there is no device node to instantiate a platform device to tie
> the driver to. The connector is not a child node of the USB host or
> hub, and the graph connection is from a USB port to the connector.
> And the connector itself already has a driver.
> 
> Power sequencing is not directly enabled in the connector driver as
> that would completely decouple the timing of it from the USB subsystem.
> It would not be possible for the USB subsystem to toggle the power
> for a power cycle or to disable the port.
> 
> This change depends on another change to make the power sequencing
> framework bool instead of tristate. The USB core and hub driver are
> bool, so if the power sequencing framework is built as a module, the
> kernel will fail to link.

>  int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub,
>  			   int port1, bool set)
>  {
> -	int ret;
> +	struct usb_port *pwrseq_port = hub->ports[port1 - 1];
> +	int ret = 0;

Don't touch ret here. It's easier to maintain when assignment is closer to it's
first user (because it's getting validated there).

> +	/* non-SuperSpeed USB port holds pwrseq descriptor reference. */
> +	if (hub->ports[port1 - 1]->is_superspeed && hub->ports[port1 - 1]->peer)
> +		pwrseq_port = hub->ports[port1 - 1]->peer;

	ret = 0;

> +	if (set && !pwrseq_port->pwrseq_on)
> +		ret = pwrseq_power_on(pwrseq_port->pwrseq);
> +	else if (!set && pwrseq_port->pwrseq_on)
> +		ret = pwrseq_power_off(pwrseq_port->pwrseq);
> +	if (ret)
> +		return ret;
>  
>  	if (set)
>  		ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
>  	else
>  		ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
>  
> -	if (ret)
> +	if (ret) {
> +		if (set && !pwrseq_port->pwrseq_on)
> +			pwrseq_power_off(pwrseq_port->pwrseq);
> +		else if (!set && pwrseq_port->pwrseq_on)
> +			pwrseq_power_on(pwrseq_port->pwrseq);
>  		return ret;

Can we rather have a couple of helpers? It might be hard to follow all this.
In such a case you won't even need the ret assignment here.


> +	}
>  
> -	if (set)
> +	if (set) {
>  		set_bit(port1, hub->power_bits);
> -	else
> +		pwrseq_port->pwrseq_on = 1;
> +	} else {
>  		clear_bit(port1, hub->power_bits);
> +		pwrseq_port->pwrseq_on = 0;
> +	}

Just

	pwrseq_port->pwrseq_on = set; // or explicit comparison
	assign_bit(port1, hub->power_bits, pwrseq_port->pwrseq_on);

>  	return 0;
>  }

...

> +static bool port_pwrseq_is_supported(struct usb_port *port_dev)
> +{
> +	struct device *dev = &port_dev->dev;
> +	struct fwnode_handle *port = dev->fwnode;

+ blank line here, because for RAII we assume the C99 definitions inside
the code, so one can insert the code in between. Doing it before ep validation
may lead to interesting errors in the future.

> +	struct fwnode_handle *ep __free(fwnode_handle) =
> +			fwnode_graph_get_next_port_endpoint(port, NULL);
> +	if (!ep)
> +		return false;
> +
> +	struct fwnode_handle *remote __free(fwnode_handle) =
> +			fwnode_graph_get_remote_port_parent(ep);
> +	if (!remote)
> +		return false;
> +
> +	if (!fwnode_device_is_compatible(remote, "pcie-m2-e-connector")) {
> +		dev_dbg(dev, "remote endpoint %pfw is not a supported connector", remote);
> +		return false;
> +	}
> +
> +	return true;
> +}

...

> +	if (IS_ERR(port_dev->pwrseq)) {
> +		retval = PTR_ERR(port_dev->pwrseq);
> +		dev_err_probe(&port_dev->dev, retval,
> +			      "failed to get power sequencing descriptor\n");

		retval = dev_err_probe(PTR_ERR(...));

> +		goto err_put_kn;
> +	}

...

>  	retval = component_add(&port_dev->dev, &connector_ops);
>  	if (retval) {
>  		dev_warn(&port_dev->dev, "failed to add component\n");

dev_warn_probe() // however it's not in your patch and was before...

> -		goto err_put_kn;
> +		goto err_pwrseq_off;
>  	}

...

> +err_pwrseq_off:
> +	if (port_dev->pwrseq_on)
> +		pwrseq_power_off(port_dev->pwrseq);

Hmm... I would rather see pwrseq framework to provide something like
_is_powered_on().

	if (pwrseq_is_powered_on())
		_power_off();

...

> +	if (port_dev->pwrseq_on)
> +		pwrseq_power_off(port_dev->pwrseq);

Ditto.

And perhaps even _power_off_if_on() that combines the check and the call.

However it seems that is reference counted and this _power_off() calls won't
guarantee actual power off.

-- 
With Best Regards,
Andy Shevchenko




^ permalink raw reply

* Re: [PATCH v2 1/4] dt-bindings: remoteproc: imx_rproc: document optional "memory-region-names"
From: Frank Li @ 2026-06-10 14:29 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Mathieu Poirier, Laurentiu Mihalcea, Bjorn Andersson, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Sascha Hauer, Peng Fan,
	Fabio Estevam, Daniel Baluta, Francesco Dolcini, linux-remoteproc,
	devicetree, imx, linux-arm-kernel, linux-kernel
In-Reply-To: <20260610-accomplished-antique-mink-cf0ead@quoll>

On Wed, Jun 10, 2026 at 09:39:25AM +0200, Krzysztof Kozlowski wrote:
> On Tue, Jun 09, 2026 at 11:33:03AM -0600, Mathieu Poirier wrote:
> > On Tue, 9 Jun 2026 at 11:06, Frank Li <Frank.li@oss.nxp.com> wrote:
> > >
> > > On Tue, Jun 09, 2026 at 10:40:06AM -0600, Mathieu Poirier wrote:
> > > > [You don't often get email from mathieu.poirier@linaro.org. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> > > >
> > > > On Fri, Jun 05, 2026 at 04:36:18AM -0700, Laurentiu Mihalcea wrote:
> > > > > From: Laurentiu Mihalcea <laurentiu.mihalcea@nxp.com>
> > > > >
> > > > > The names of the carveout regions are derived using the names of the
> > > > > reserved memory devicetree nodes, which are referenced using the
> > > > > "memory-region" property. This adds a restriction on the names of said
> > > > > devicetree nodes, often bearing specific names such as: "vdevbuffer",
> > > > > "vdev0vring0", "rsc-table", etc... This goes against the devicetree
> > > > > specification's recommendation, which states that the devicetree node
> > > > > names should be generic.
> > > >
> > > > I don't see what is so restrictive in using the node name of the reserved-memory
> > > > regions.  Function of_reserved_mem_region_to_resource() is already doing all the
> > > > parsing, packaging everything in a neat and easy to use "struct resource".  What
> > > > will you gain with this new "memory-region-names" that can't be done with the
> > > > current solution?
> > >
> > > DT Binding check can't find such wrong if node name is not what expected.
> > > Binding can't restrict memory's node name because there ware not specific
> > > compatible string for it.
> > >
> >
> > But what "wrong" could that be, and what kind of restriction are you
> > hoping to enforce?  What specific problem are you hoping to solve?
> >
> > I'll wait to see what the DT people think about this - I personally
> > don't see the value in it.
>
> I see no point in this commit, but maybe because the commit msg is just
> misleading. It mixes node names with names for phandles which are two
> separate things.

For example:

rsc_table: rsc-table@90000000
{	ret  = <0x90000000>;
	no-map;
}

m4 {
	...
	memory-region = <&rsc_table>;
}

If you change node name "rsc-table" to "memory", driver will failure
because it parse node name "rsc-table", which phandle point to. but no
binding to restrict node name to "rsc-table". So rsc-table became hidden
ABI.

if use memory-region-names, we can restrict memory-region-name to
"rsc-table" earsily.

Frank

>
> Plus this change actually makes nothing - no names are restricted to any
> meaningful values!
>
> Best regards,
> Krzysztof
>


^ permalink raw reply

* Re: [RFC PATCH v3 0/9] accel: rocket: Add RK3568 NPU support
From: Diederik de Haas @ 2026-06-10 14:28 UTC (permalink / raw)
  To: Midgy Balon, Diederik de Haas
  Cc: Chaoyi Chen, tomeu, ogabbay, heiko, robh, krzk+dt, conor+dt, joro,
	will, robin.murphy, dri-devel, linux-rockchip, devicetree,
	linux-arm-kernel, iommu, linux-kernel, Simon Xue, Finley Xiao,
	Jonas Karlman
In-Reply-To: <CA+GS1Y1xAq-9eMyMmoVE6NG9KLG7XRxgPoSr5RkW=6fT5D820g@mail.gmail.com>

On Wed Jun 10, 2026 at 3:36 PM CEST, Midgy Balon wrote:
> Hello Chaoyi & Diederik,
>
> I compared the RK3568 and RK3588 NPU power-domain + DTS as you
> suggested, and it lines up
> exactly with what you described.
>
> The difference is the `need_regulator` capability. RK3588's NPU domain is
> `DOMAIN_RK3588("npu", …, false, true)` — the trailing `true` is
> `regulator`/`need_regulator`.
> The mainline RK3568 macro `DOMAIN_RK3568(name, pwr, req, wakeup)` has
> no regulator parameter at
> all, so `RK3568_PD_NPU` can't be marked need_regulator. My v4 adds
> that: a regulator-capable
> RK3568 NPU domain (need_regulator = true) plus `domain-supply =
> <&vdd_npu>` on the NPU node —
> i.e. the same shape as RK3588.
>
> And the fix you referenced (Frank Zhang's "pmdomain: rockchip: Fix init genpd as
> GENPD_STATE_ON before regulator ready", plus "quiet regulator error on
> -EPROBE_DEFER") is
> already in my base (v7.1-rc6), so the `if (need_regulator)
> rockchip_pd_power(pd, false)`
> default-off path is in effect. That's what resolves the actual problem
> for me: with rocket
> built as a module (the normal config), need_regulator on the NPU
> domain, and those pmdomain
> patches in place, the board boots cleanly and NPU jobs run with no RCU
> stall / no deadlock. My
> earlier hang was an artifact of a self-contained rocket=y image
> probing in the initcalls before
> the I2C regulator core was up — as a module it loads ~6.8 s in, well
> after, so it's gone.
>
> I also went back and checked the `fw_devlink=permissive` question
> myself — and good news, it
> turns out it is NOT needed. I rebooted the exact same kernel with
> permissive removed from the
> cmdline (strict fw_devlink, the default), and the board boots cleanly,
> the NPU probes
> (`rocket fde40000.npu: Rockchip NPU core 0 version: 0`), and NPU jobs
> submit and run five times
> in a row with no deadlock and no RCU stall. So strict fw_devlink
> resolves the NPU/PMIC ordering
> fine via deferred probe.
>
> The one remaining thing is cosmetic: at power-domain-controller probe
> (~2.94 s) I still get,
> in BOTH modes (with or without permissive):
>
>   rockchip-pm-domain …: Failed to create device link (0x180) with
> supplier 0-0020 …power-domain@6
>
> i.e. genpd can't form the link to the rk809 (the I2C PMIC supplying
> vdd_npu) because the PMIC
> isn't registered yet at that point. It's non-fatal — the domain
> defaults off (Frank's patch),
> the rail comes up via the regulator core, the NPU probes a few seconds
> later, and all jobs run.
>
> One question: on RK3588 with need_regulator, do you also see that
> "Failed to create device
> link … supplier <pmic>" line at pmdomain probe, or does it order
> cleanly? If RK3588 is clean,
> is there a DTS detail (e.g. the regulator's bus/probe order) I should
> mirror on RK3568 to make
> the link form in time — or is this line just expected/harmless and
> best left as-is?

[    2.110935] rockchip-pm-domain fd8d8000.power-management:power-controller: Failed to create device link (0x180) with supplier 2-0042 for /power-management@fd8d8000/power-controller/power-domain@8
[    2.557459] sdhci-dwcmshc fe2e0000.mmc: Can't reduce the clock below 52MHz in HS200/HS400 mode
[    2.647174] rockchip-pm-domain fd8d8000.power-management:power-controller: Failed to create device link (0x180) with supplier 2-0042 for /power-management@fd8d8000/power-controller/power-domain@8
[    2.945089] rockchip-pm-domain fd8d8000.power-management:power-controller: Failed to create device link (0x180) with supplier spi2.0 for /power-management@fd8d8000/power-controller/power-domain@12

8 = NPU; 12 = GPU

on both nanopc-t6-lts and nanopc-t6-plus (both RK3588).
And on a 6.18 dmesg output I have for Rock 5B, I see the ~ same, but then
it's 1-0042 instead of 2-0042. 

I don't know if it's bad or harmless, but it is consistent.

HTH,
  Diederik

> @Diederik — thanks; the DCDC_REG2 change and Jonas's USB-suspend
> series look like generally
> useful RK356x robustness fixes, though for this specific NPU
> device-link the need_regulator +
> Frank's pmdomain patches seem to be the relevant piece. I'll keep them
> in mind for suspend.
>
> The convolution-output / compute-completion issue is still separate
> and open (@Finley — that's
> the PVTPLL/NoC one); the power-domain side is in good shape for v4.
>
> Thanks y'all for your help :)
>
> Kind regards,
> Midgy
>
> Le mer. 10 juin 2026 à 12:05, Diederik de Haas
> <diederik@cknow-tech.com> a écrit :
>>
>> Hi,
>>
>> On Wed Jun 10, 2026 at 3:14 AM CEST, Chaoyi Chen wrote:
>> > Hi Midgy,
>> >
>> > On 6/9/2026 7:11 PM, Midgy Balon wrote:
>> >> Hello Chaoyi,
>> >>
>> >> You were right - building rocket as a module fixes it. Thanks for the pointer.
>> >>
>> >> I rebuilt with CONFIG_DRM_ACCEL_ROCKET=m (everything else the same:
>> >> need_regulator on
>> >> the RK3568 NPU power domain via a DOMAIN_M_R variant, domain-supply =
>> >> <&vdd_npu>, and the
>> >> regulator-always-on workaround dropped). The board now boots cleanly
>> >> and, more importantly,
>> >> an NPU job submit no longer hangs: I ran the test workload five times
>> >> with no RCU stall and
>> >> no freeze.
>> >>
>> >> So with rocket=m the need_regulator approach works on RK3568, and I'll
>> >> keep it for v4
>> >> (domain-supply + need_regulator, instead of marking vdd_npu
>> >> always-on). rocket=m is the
>> >> normal configuration anyway; my earlier hang came from building it =y
>> >> in a self-contained
>> >> image, so it probed in the initcalls (around 2 s) and the genpd ->
>> >> I2C-PMIC regulator
>> >> transition ran before the system was ready. As a module it loads from
>> >> udev much later
>> >> (~6.8 s here), after the I2C controller and regulator core are fully up.
>> >>
>> >> On your question of when the device-link error is printed - it is at
>> >> power-domain
>> >> controller probe, not at the rocket probe:
>> >>
>> >>   [    2.700618] vdd_npu: Bringing 500000uV into 825000-825000uV
>> >>   [    2.749637] rockchip-pm-domain fdd90000.power-management:power-controller:
>> >>                  Failed to create device link (0x180) with supplier 0-0020 for
>> >>                  /power-management@fdd90000/power-controller/power-domain@6
>> >>   [    2.945955] platform fde40000.npu: Adding to iommu group 3
>> >>   ...
>> >>   [    6.840374] rocket: loading out-of-tree module taints kernel.
>> >>   [    6.877647] [drm] Initialized rocket 0.0.0 for rknn on minor 0
>> >>   [    6.879950] rocket fde40000.npu: Rockchip NPU core 0 version: 0
>> >>
>> >> So the device-link to the rk809 PMIC (0-0020) fails to form at ~2.75
>> >> s, well before rocket
>> >> loads at ~6.8 s. It is non-fatal here - the vdd_npu rail is brought up
>> >> by the regulator core
>> >> and all jobs run - and there is no "failed to get ack on domain npu"
>> >> NoC warning this boot
>> >> (the always-on kernel had one). The complete boot log is attached.
>> >>
>> >> Two notes / one question:
>> >> - This boot used fw_devlink=permissive on the command line. Is the
>> >> "Failed to create device
>> >>   link ... supplier 0-0020" at pmdomain probe expected/benign, or is
>> >> there a clean way to make
>> >>   it order correctly (so it also works without permissive, and a =y
>> >> build wouldn't deadlock in
>> >>   the initcalls)?
>> >
>> > We encountered the same issue on the RK3588 NPU before. And it was
>> > resolved with the following patch at that time.
>> >
>> > https://lore.kernel.org/all/20251216055247.13150-1-rmxpzlb@gmail.com/
>> >
>> > Please compare the differences in NPU pmdomain and DTS configuration
>> > between the RK3568 and RK3588.
>>
>> About a month ago on #linux-rockchip we were discussing PM 'stuff':
>> https://libera.catirclogs.org/linux-rockchip/2026-05-15#39939137;
>> which references this paste
>> https://paste.sr.ht/~diederik/89d9f84e22474e837b55286d213b67f03859ce2e
>> I've since removed the DCDC_REG2 for PineTab2 and the 'fix' should likely
>> be extended to cover all RK3566/RK3568 devices though.
>>
>> It's what I made at the time hoping to fix a suspend/resume issue when
>> trying upstream TF-A. It didn't fix the issue at the time, but may still
>> be useful/needed and I think it's what Chaoyi hinted at.
>>
>> Just yesterday, Jonas posted this patch which may be useful/needed too:
>> https://lore.kernel.org/linux-rockchip/20260609154124.445182-1-jonas@kwiboo.se/
>>
>> HTH,
>>   Diederik
>>
>> >> - (The convolution output is still uniform zero-point / the job times
>> >> out - that is the
>> >>   separate NPU compute-completion issue, unrelated to the power-domain
>> >> work. Finley, that is
>> >>   the one I flagged earlier re PVTPLL/NoC.)
>> >>
>> >> Kind regards,
>> >> Midgy
>> >>
>>
>
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip



^ permalink raw reply

* [PATCH 3/3] arm64: dts: rockchip: Add Youyeetoo YY3588
From: Daniele Briguglio @ 2026-06-10 13:59 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
	Daniele Briguglio
In-Reply-To: <20260610-yy3588-board-v1-0-4bb7176b6826@superkali.me>

The YY3588 is a single board computer built around the Rockchip RK3588.

Specification:
- Rockchip RK3588 SoC
- 4/8/16/32 GB LPDDR4/4x
- up to 256 GB eMMC
- microSD card slot
- 1x 1000Base-T (Realtek RTL8211F) and 1x 2500Base-T (Realtek RTL8125)
- HDMI 2.1 output
- HDMI input
- 4x USB 3.0 Type-A via onboard hub, 1x USB 2.0 Type-A
- USB Type-C with USB 3.0
- M.2 M-key with PCIe 3.0 x4
- Mini PCIe slot for WiFi/BT or 4G modules
- SATA 3.0
- ES8388 audio codec with headphone jack and onboard microphone
- fan connector, RTC, recovery key
- 12 V DC input

Both Ethernet ports, eMMC, SD card, USB, Type-C, HDMI output, WiFi
on the Mini PCIe slot, audio, the recovery key and the fan have been
tested on the board.

Link: https://wiki.youyeetoo.com/YY3588
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
 arch/arm64/boot/dts/rockchip/Makefile              |    1 +
 .../boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts  | 1190 ++++++++++++++++++++
 2 files changed, 1191 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index 761d82b4f..6cab03c9c 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -213,6 +213,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-toybrick-x0.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-turing-rk1.dtb
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-youyeetoo-yy3588.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-coolpi-4b.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-evb1-v10.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-gameforce-ace.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts b/arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts
new file mode 100644
index 000000000..28d8790a2
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts
@@ -0,0 +1,1190 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2024 Youyeetoo
+ * Copyright (c) 2026 Daniele Briguglio <hello@superkali.me>
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+#include <dt-bindings/usb/pd.h>
+#include "rk3588.dtsi"
+
+/ {
+	model = "Youyeetoo YY3588";
+	compatible = "youyeetoo,yy3588", "rockchip,rk3588";
+
+	aliases {
+		ethernet0 = &gmac1;
+		ethernet1 = &r8125;
+		mmc0 = &sdhci;
+		mmc1 = &sdmmc;
+	};
+
+	adc-keys {
+		compatible = "adc-keys";
+		io-channels = <&saradc 1>;
+		io-channel-names = "buttons";
+		keyup-threshold-microvolt = <1800000>;
+		poll-interval = <100>;
+
+		button-recovery {
+			label = "Recovery";
+			linux,code = <KEY_VENDOR>;
+			press-threshold-microvolt = <17000>;
+		};
+	};
+
+	analog-sound {
+		compatible = "simple-audio-card";
+		pinctrl-names = "default";
+		pinctrl-0 = <&hp_det>;
+		simple-audio-card,name = "rockchip-es8388";
+		simple-audio-card,aux-devs = <&speaker_amp>;
+		simple-audio-card,format = "i2s";
+		simple-audio-card,hp-det-gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_LOW>;
+		simple-audio-card,mclk-fs = <256>;
+		simple-audio-card,pin-switches = "Headphones", "Speaker";
+		simple-audio-card,routing =
+			"Headphones", "LOUT2",
+			"Headphones", "ROUT2",
+			"Speaker Amp INL", "LOUT1",
+			"Speaker Amp INR", "ROUT1",
+			"Speaker", "Speaker Amp OUTL",
+			"Speaker", "Speaker Amp OUTR",
+			"LINPUT1", "Microphone Jack",
+			"RINPUT1", "Microphone Jack",
+			"LINPUT2", "Onboard Microphone",
+			"RINPUT2", "Onboard Microphone";
+		simple-audio-card,widgets =
+			"Microphone", "Microphone Jack",
+			"Microphone", "Onboard Microphone",
+			"Headphone", "Headphones",
+			"Speaker", "Speaker";
+
+		simple-audio-card,cpu {
+			sound-dai = <&i2s0_8ch>;
+		};
+
+		simple-audio-card,codec {
+			sound-dai = <&es8388>;
+			system-clock-frequency = <12288000>;
+		};
+	};
+
+	chosen {
+		stdout-path = "serial2:1500000n8";
+	};
+
+	hdmi0-con {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi0_con_in: endpoint {
+				remote-endpoint = <&hdmi0_out_con>;
+			};
+		};
+	};
+
+	leds {
+		compatible = "gpio-leds";
+		pinctrl-names = "default";
+		pinctrl-0 = <&led_pins>;
+
+		led-0 {
+			color = <LED_COLOR_ID_BLUE>;
+			function = LED_FUNCTION_STATUS;
+			gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_HIGH>;
+		};
+
+		led-1 {
+			function = LED_FUNCTION_HEARTBEAT;
+			gpios = <&gpio1 RK_PD7 GPIO_ACTIVE_HIGH>;
+			linux,default-trigger = "heartbeat";
+		};
+	};
+
+	/* PI6C557-05BLE PCIe 3.0 reference clock generator */
+	pcie30_port0_refclk: pcie-oscillator {
+		compatible = "gated-fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <100000000>;
+		clock-output-names = "pcie30_refclk";
+		vdd-supply = <&vcc3v3_pi6c_05>;
+	};
+
+	fan: pwm-fan {
+		compatible = "pwm-fan";
+		#cooling-cells = <2>;
+		cooling-levels = <0 50 100 150 200 255>;
+		fan-supply = <&vcc12v_dcin>;
+		pwms = <&pwm2 0 50000 0>;
+	};
+
+	pcie20_avdd0v85: regulator-pcie20-avdd0v85 {
+		compatible = "regulator-fixed";
+		regulator-name = "pcie20_avdd0v85";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <850000>;
+		regulator-max-microvolt = <850000>;
+		vin-supply = <&vdd_0v85_s0>;
+	};
+
+	pcie20_avdd1v8: regulator-pcie20-avdd1v8 {
+		compatible = "regulator-fixed";
+		regulator-name = "pcie20_avdd1v8";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <1800000>;
+		regulator-max-microvolt = <1800000>;
+		vin-supply = <&avcc_1v8_s0>;
+	};
+
+	pcie30_avdd0v75: regulator-pcie30-avdd0v75 {
+		compatible = "regulator-fixed";
+		regulator-name = "pcie30_avdd0v75";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <750000>;
+		regulator-max-microvolt = <750000>;
+		vin-supply = <&avdd_0v75_s0>;
+	};
+
+	pcie30_avdd1v8: regulator-pcie30-avdd1v8 {
+		compatible = "regulator-fixed";
+		regulator-name = "pcie30_avdd1v8";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <1800000>;
+		regulator-max-microvolt = <1800000>;
+		vin-supply = <&avcc_1v8_s0>;
+	};
+
+	vbus5v0_typec: regulator-vbus5v0-typec {
+		compatible = "regulator-fixed";
+		enable-active-high;
+		gpio = <&gpio4 RK_PB0 GPIO_ACTIVE_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&typec5v_pwren>;
+		regulator-name = "vbus5v0_typec";
+		regulator-min-microvolt = <5000000>;
+		regulator-max-microvolt = <5000000>;
+		vin-supply = <&vcc5v0_sys>;
+	};
+
+	vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc_1v1_nldo_s3";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <1100000>;
+		regulator-max-microvolt = <1100000>;
+		vin-supply = <&vcc5v0_sys>;
+	};
+
+	vcc12v_dcin: regulator-vcc12v-dcin {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc12v_dcin";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <12000000>;
+		regulator-max-microvolt = <12000000>;
+	};
+
+	vcc3v3_pi6c_05: regulator-vcc3v3-pi6c-05 {
+		compatible = "regulator-fixed";
+		enable-active-high;
+		gpios = <&gpio2 RK_PC5 GPIO_ACTIVE_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pi6c_05_pwren>;
+		regulator-name = "vcc3v3_pi6c_05";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		startup-delay-us = <5000>;
+		vin-supply = <&vcc5v0_sys>;
+	};
+
+	vcc3v3_sd_s0: regulator-vcc3v3-sd-s0 {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc3v3_sd_s0";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		vin-supply = <&vcc3v3_sys>;
+	};
+
+	vcc3v3_sys: regulator-vcc3v3-sys {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc3v3_sys";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		vin-supply = <&vcc12v_dcin>;
+	};
+
+	vcc5v0_sys: regulator-vcc5v0-sys {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc5v0_sys";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <5000000>;
+		regulator-max-microvolt = <5000000>;
+		vin-supply = <&vcc12v_dcin>;
+	};
+
+	speaker_amp: speaker-amplifier {
+		compatible = "simple-audio-amplifier";
+		enable-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&spk_en>;
+		sound-name-prefix = "Speaker Amp";
+		VCC-supply = <&vcc5v0_sys>;
+	};
+};
+
+&combphy0_ps {
+	status = "okay";
+};
+
+&combphy1_ps {
+	status = "okay";
+};
+
+&combphy2_psu {
+	status = "okay";
+};
+
+&cpu_b0 {
+	cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b1 {
+	cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b2 {
+	cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_b3 {
+	cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_l0 {
+	cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l1 {
+	cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l2 {
+	cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l3 {
+	cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&gmac1 {
+	clock_in_out = "input";
+	phy-handle = <&rgmii_phy>;
+	/* RX delay is added by the PHY, TX delay by the GMAC */
+	phy-mode = "rgmii-rxid";
+	pinctrl-names = "default";
+	pinctrl-0 = <&gmac1_miim
+		     &gmac1_tx_bus2
+		     &gmac1_rx_bus2
+		     &gmac1_rgmii_clk
+		     &gmac1_rgmii_bus
+		     &gmac1_clkinout>;
+	tx_delay = <0x43>;
+	status = "okay";
+};
+
+&gpu {
+	mali-supply = <&vdd_gpu_s0>;
+	status = "okay";
+};
+
+&hdmi0 {
+	frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>;
+	status = "okay";
+};
+
+&hdmi0_in {
+	hdmi0_in_vp0: endpoint {
+		remote-endpoint = <&vp0_out_hdmi0>;
+	};
+};
+
+&hdmi0_out {
+	hdmi0_out_con: endpoint {
+		remote-endpoint = <&hdmi0_con_in>;
+	};
+};
+
+&hdmi0_sound {
+	status = "okay";
+};
+
+&hdmi_receiver_cma {
+	status = "okay";
+};
+
+&hdmi_receiver {
+	hpd-gpios = <&gpio1 RK_PC6 GPIO_ACTIVE_LOW>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_5v_det>;
+	status = "okay";
+};
+
+&hdptxphy0 {
+	status = "okay";
+};
+
+&i2c0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c0m2_xfer>;
+	status = "okay";
+
+	vdd_cpu_big0_s0: regulator@42 {
+		compatible = "rockchip,rk8602";
+		reg = <0x42>;
+		fcs,suspend-voltage-selector = <1>;
+		regulator-name = "vdd_cpu_big0_s0";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <550000>;
+		regulator-max-microvolt = <1050000>;
+		regulator-ramp-delay = <2300>;
+		vin-supply = <&vcc5v0_sys>;
+
+		regulator-state-mem {
+			regulator-off-in-suspend;
+		};
+	};
+
+	vdd_cpu_big1_s0: regulator@43 {
+		compatible = "rockchip,rk8603", "rockchip,rk8602";
+		reg = <0x43>;
+		fcs,suspend-voltage-selector = <1>;
+		regulator-name = "vdd_cpu_big1_s0";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <550000>;
+		regulator-max-microvolt = <1050000>;
+		regulator-ramp-delay = <2300>;
+		vin-supply = <&vcc5v0_sys>;
+
+		regulator-state-mem {
+			regulator-off-in-suspend;
+		};
+	};
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1m2_xfer>;
+	status = "okay";
+
+	vdd_npu_s0: regulator@42 {
+		compatible = "rockchip,rk8602";
+		reg = <0x42>;
+		fcs,suspend-voltage-selector = <1>;
+		regulator-name = "vdd_npu_s0";
+		regulator-boot-on;
+		regulator-min-microvolt = <550000>;
+		regulator-max-microvolt = <950000>;
+		regulator-ramp-delay = <2300>;
+		vin-supply = <&vcc5v0_sys>;
+
+		regulator-state-mem {
+			regulator-off-in-suspend;
+		};
+	};
+};
+
+&i2c6 {
+	clock-frequency = <400000>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c6m0_xfer>;
+	status = "okay";
+
+	usbc0: usb-typec@22 {
+		compatible = "fcs,fusb302";
+		reg = <0x22>;
+		interrupt-parent = <&gpio0>;
+		interrupts = <RK_PD3 IRQ_TYPE_LEVEL_LOW>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&usbc0_int>;
+		vbus-supply = <&vbus5v0_typec>;
+
+		usb_con: connector {
+			compatible = "usb-c-connector";
+			data-role = "dual";
+			label = "USB-C";
+			power-role = "source";
+			source-pdos = <PDO_FIXED(5000, 1500, PDO_FIXED_USB_COMM)>;
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+
+					usbc0_hs: endpoint {
+						remote-endpoint = <&usb_host0_xhci_drd_sw>;
+					};
+				};
+
+				port@1 {
+					reg = <1>;
+
+					usbc0_ss: endpoint {
+						remote-endpoint = <&usbdp_phy0_typec_ss>;
+					};
+				};
+
+				port@2 {
+					reg = <2>;
+
+					usbc0_sbu: endpoint {
+						remote-endpoint = <&usbdp_phy0_typec_sbu>;
+					};
+				};
+			};
+		};
+	};
+
+	hym8563: rtc@51 {
+		compatible = "haoyu,hym8563";
+		reg = <0x51>;
+		#clock-cells = <0>;
+		clock-output-names = "hym8563";
+	};
+};
+
+&i2c7 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c7m0_xfer>;
+	status = "okay";
+
+	es8388: audio-codec@11 {
+		compatible = "everest,es8388", "everest,es8328";
+		reg = <0x11>;
+		assigned-clocks = <&cru I2S0_8CH_MCLKOUT>;
+		assigned-clock-rates = <12288000>;
+		clocks = <&cru I2S0_8CH_MCLKOUT>;
+		AVDD-supply = <&vcc3v3_sys>;
+		DVDD-supply = <&vcc_1v8_s3>;
+		HPVDD-supply = <&vcc3v3_sys>;
+		PVDD-supply = <&vcc_1v8_s3>;
+		#sound-dai-cells = <0>;
+	};
+};
+
+&i2s0_8ch {
+	pinctrl-0 = <&i2s0_lrck
+		     &i2s0_mclk
+		     &i2s0_sclk
+		     &i2s0_sdi0
+		     &i2s0_sdo0>;
+	status = "okay";
+};
+
+&i2s5_8ch {
+	status = "okay";
+};
+
+&i2s7_8ch {
+	status = "okay";
+};
+
+&mdio1 {
+	rgmii_phy: ethernet-phy@1 {
+		/* RTL8211F */
+		compatible = "ethernet-phy-id001c.c916";
+		reg = <0x1>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&rtl8211f_rst>;
+		reset-assert-us = <20000>;
+		reset-deassert-us = <100000>;
+		reset-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>;
+	};
+};
+
+&package_thermal {
+	polling-delay = <1000>;
+
+	trips {
+		package_fan0: package-fan0 {
+			temperature = <55000>;
+			hysteresis = <2000>;
+			type = "active";
+		};
+
+		package_fan1: package-fan1 {
+			temperature = <65000>;
+			hysteresis = <2000>;
+			type = "active";
+		};
+	};
+
+	cooling-maps {
+		map0 {
+			trip = <&package_fan0>;
+			cooling-device = <&fan THERMAL_NO_LIMIT 1>;
+		};
+
+		map1 {
+			trip = <&package_fan1>;
+			cooling-device = <&fan 2 THERMAL_NO_LIMIT>;
+		};
+	};
+};
+
+&pcie2x1l0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pcie2_0_rst>;
+	reset-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>;
+	vpcie3v3-supply = <&vcc3v3_sys>;
+	status = "okay";
+};
+
+&pcie2x1l1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pcie2_1_rst>;
+	reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
+	vpcie3v3-supply = <&vcc3v3_sys>;
+	status = "okay";
+
+	pcie@0,0 {
+		reg = <0x300000 0 0 0 0>;
+		#address-cells = <3>;
+		#size-cells = <2>;
+		ranges;
+		device_type = "pci";
+		bus-range = <0x30 0x3f>;
+
+		r8125: ethernet@0,0 {
+			reg = <0x310000 0 0 0 0>;
+		};
+	};
+};
+
+&pcie30phy {
+	status = "okay";
+};
+
+&pcie3x4 {
+	clocks = <&cru ACLK_PCIE_4L_MSTR>, <&cru ACLK_PCIE_4L_SLV>,
+		 <&cru ACLK_PCIE_4L_DBI>, <&cru PCLK_PCIE_4L>,
+		 <&cru CLK_PCIE_AUX0>, <&cru CLK_PCIE4L_PIPE>,
+		 <&pcie30_port0_refclk>;
+	clock-names = "aclk_mst", "aclk_slv",
+		      "aclk_dbi", "pclk",
+		      "aux", "pipe",
+		      "ref";
+	pinctrl-names = "default";
+	pinctrl-0 = <&pcie3x4_perstn>;
+	reset-gpios = <&gpio1 RK_PB4 GPIO_ACTIVE_HIGH>;
+	vpcie3v3-supply = <&vcc3v3_sys>;
+	status = "okay";
+};
+
+&pinctrl {
+	hdmirx {
+		hdmirx_5v_det: hdmirx-5v-det {
+			rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	leds {
+		led_pins: led-pins {
+			rockchip,pins = <1 RK_PD6 RK_FUNC_GPIO &pcfg_pull_none>,
+					<1 RK_PD7 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	pcie {
+		pcie2_0_rst: pcie2-0-rst {
+			rockchip,pins = <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		pcie2_1_rst: pcie2-1-rst {
+			rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		pi6c_05_pwren: pi6c-05-pwren {
+			rockchip,pins = <2 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		pcie3x4_perstn: pcie3x4-perstn {
+			rockchip,pins = <1 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	rtl8211f {
+		rtl8211f_rst: rtl8211f-rst {
+			rockchip,pins = <3 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	sound {
+		hp_det: hp-det {
+			rockchip,pins = <1 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>;
+		};
+
+		spk_en: spk-en {
+			rockchip,pins = <4 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	usb {
+		typec5v_pwren: typec5v-pwren {
+			rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		usbc0_int: usbc0-int {
+			rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>;
+		};
+	};
+};
+
+&pwm2 {
+	pinctrl-0 = <&pwm2m2_pins>;
+	status = "okay";
+};
+
+&rknn_core_0 {
+	npu-supply = <&vdd_npu_s0>;
+	sram-supply = <&vdd_npu_s0>;
+	status = "okay";
+};
+
+&rknn_core_1 {
+	npu-supply = <&vdd_npu_s0>;
+	sram-supply = <&vdd_npu_s0>;
+	status = "okay";
+};
+
+&rknn_core_2 {
+	npu-supply = <&vdd_npu_s0>;
+	sram-supply = <&vdd_npu_s0>;
+	status = "okay";
+};
+
+&rknn_mmu_0 {
+	status = "okay";
+};
+
+&rknn_mmu_1 {
+	status = "okay";
+};
+
+&rknn_mmu_2 {
+	status = "okay";
+};
+
+&saradc {
+	vref-supply = <&avcc_1v8_s0>;
+	status = "okay";
+};
+
+&sata0 {
+	status = "okay";
+};
+
+&sdhci {
+	bus-width = <8>;
+	mmc-hs400-1_8v;
+	mmc-hs400-enhanced-strobe;
+	no-sd;
+	no-sdio;
+	non-removable;
+	status = "okay";
+};
+
+&sdmmc {
+	bus-width = <4>;
+	cap-mmc-highspeed;
+	cap-sd-highspeed;
+	disable-wp;
+	max-frequency = <150000000>;
+	no-mmc;
+	no-sdio;
+	sd-uhs-sdr104;
+	vmmc-supply = <&vcc3v3_sd_s0>;
+	vqmmc-supply = <&vccio_sd_s0>;
+	status = "okay";
+};
+
+&spi2 {
+	assigned-clocks = <&cru CLK_SPI2>;
+	assigned-clock-rates = <200000000>;
+	num-cs = <1>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>;
+	status = "okay";
+
+	pmic@0 {
+		compatible = "rockchip,rk806";
+		reg = <0x0>;
+		interrupt-parent = <&gpio0>;
+		interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>,
+			    <&rk806_dvs2_null>, <&rk806_dvs3_null>;
+		spi-max-frequency = <1000000>;
+		system-power-controller;
+
+		vcc1-supply = <&vcc5v0_sys>;
+		vcc2-supply = <&vcc5v0_sys>;
+		vcc3-supply = <&vcc5v0_sys>;
+		vcc4-supply = <&vcc5v0_sys>;
+		vcc5-supply = <&vcc5v0_sys>;
+		vcc6-supply = <&vcc5v0_sys>;
+		vcc7-supply = <&vcc5v0_sys>;
+		vcc8-supply = <&vcc5v0_sys>;
+		vcc9-supply = <&vcc5v0_sys>;
+		vcc10-supply = <&vcc5v0_sys>;
+		vcc11-supply = <&vcc_2v0_pldo_s3>;
+		vcc12-supply = <&vcc5v0_sys>;
+		vcc13-supply = <&vcc_1v1_nldo_s3>;
+		vcc14-supply = <&vcc_1v1_nldo_s3>;
+		vcca-supply = <&vcc5v0_sys>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+
+		rk806_dvs1_null: dvs1-null-pins {
+			pins = "gpio_pwrctrl1";
+			function = "pin_fun0";
+		};
+
+		rk806_dvs2_null: dvs2-null-pins {
+			pins = "gpio_pwrctrl2";
+			function = "pin_fun0";
+		};
+
+		rk806_dvs3_null: dvs3-null-pins {
+			pins = "gpio_pwrctrl3";
+			function = "pin_fun0";
+		};
+
+		regulators {
+			vdd_gpu_s0: vdd_gpu_mem_s0: dcdc-reg1 {
+				regulator-boot-on;
+				regulator-min-microvolt = <550000>;
+				regulator-max-microvolt = <950000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vdd_gpu_s0";
+				regulator-enable-ramp-delay = <400>;
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vdd_cpu_lit_s0: vdd_cpu_lit_mem_s0: dcdc-reg2 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <550000>;
+				regulator-max-microvolt = <950000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vdd_cpu_lit_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vdd_log_s0: dcdc-reg3 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <675000>;
+				regulator-max-microvolt = <750000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vdd_log_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+					regulator-suspend-microvolt = <750000>;
+				};
+			};
+
+			vdd_vdenc_s0: vdd_vdenc_mem_s0: dcdc-reg4 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <550000>;
+				regulator-max-microvolt = <950000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vdd_vdenc_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vdd_ddr_s0: dcdc-reg5 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <675000>;
+				regulator-max-microvolt = <900000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vdd_ddr_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+					regulator-suspend-microvolt = <850000>;
+				};
+			};
+
+			vdd2_ddr_s3: dcdc-reg6 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-name = "vdd2_ddr_s3";
+
+				regulator-state-mem {
+					regulator-on-in-suspend;
+				};
+			};
+
+			vcc_2v0_pldo_s3: dcdc-reg7 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <2000000>;
+				regulator-max-microvolt = <2000000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vdd_2v0_pldo_s3";
+
+				regulator-state-mem {
+					regulator-on-in-suspend;
+					regulator-suspend-microvolt = <2000000>;
+				};
+			};
+
+			vcc_3v3_s3: dcdc-reg8 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <3300000>;
+				regulator-max-microvolt = <3300000>;
+				regulator-name = "vcc_3v3_s3";
+
+				regulator-state-mem {
+					regulator-on-in-suspend;
+					regulator-suspend-microvolt = <3300000>;
+				};
+			};
+
+			vddq_ddr_s0: dcdc-reg9 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-name = "vddq_ddr_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vcc_1v8_s3: dcdc-reg10 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-name = "vcc_1v8_s3";
+
+				regulator-state-mem {
+					regulator-on-in-suspend;
+					regulator-suspend-microvolt = <1800000>;
+				};
+			};
+
+			avcc_1v8_s0: pldo-reg1 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-name = "avcc_1v8_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+					regulator-suspend-microvolt = <1800000>;
+				};
+			};
+
+			vcc_1v8_s0: pldo-reg2 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-name = "vcc_1v8_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+					regulator-suspend-microvolt = <1800000>;
+				};
+			};
+
+			avdd_1v2_s0: pldo-reg3 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <1200000>;
+				regulator-max-microvolt = <1200000>;
+				regulator-name = "avdd_1v2_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vcc_3v3_s0: pldo-reg4 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <3300000>;
+				regulator-max-microvolt = <3300000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vcc_3v3_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vccio_sd_s0: pldo-reg5 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <3300000>;
+				regulator-ramp-delay = <12500>;
+				regulator-name = "vccio_sd_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			pldo6_s3: pldo-reg6 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-name = "pldo6_s3";
+
+				regulator-state-mem {
+					regulator-on-in-suspend;
+					regulator-suspend-microvolt = <1800000>;
+				};
+			};
+
+			vdd_0v75_s3: nldo-reg1 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <750000>;
+				regulator-max-microvolt = <750000>;
+				regulator-name = "vdd_0v75_s3";
+
+				regulator-state-mem {
+					regulator-on-in-suspend;
+					regulator-suspend-microvolt = <750000>;
+				};
+			};
+
+			vdd_ddr_pll_s0: nldo-reg2 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <850000>;
+				regulator-max-microvolt = <850000>;
+				regulator-name = "vdd_ddr_pll_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+					regulator-suspend-microvolt = <850000>;
+				};
+			};
+
+			avdd_0v75_s0: nldo-reg3 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <750000>;
+				regulator-max-microvolt = <750000>;
+				regulator-name = "avdd_0v75_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vdd_0v85_s0: nldo-reg4 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <850000>;
+				regulator-max-microvolt = <850000>;
+				regulator-name = "vdd_0v85_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+
+			vdd_0v75_s0: nldo-reg5 {
+				regulator-always-on;
+				regulator-boot-on;
+				regulator-min-microvolt = <750000>;
+				regulator-max-microvolt = <750000>;
+				regulator-name = "vdd_0v75_s0";
+
+				regulator-state-mem {
+					regulator-off-in-suspend;
+				};
+			};
+		};
+	};
+};
+
+&tsadc {
+	status = "okay";
+};
+
+&u2phy0 {
+	status = "okay";
+};
+
+&u2phy0_otg {
+	status = "okay";
+};
+
+&u2phy1 {
+	status = "okay";
+};
+
+&u2phy1_otg {
+	status = "okay";
+};
+
+&u2phy2 {
+	status = "okay";
+};
+
+&u2phy2_host {
+	phy-supply = <&vcc5v0_sys>;
+	status = "okay";
+};
+
+&u2phy3 {
+	status = "okay";
+};
+
+&u2phy3_host {
+	phy-supply = <&vcc5v0_sys>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-0 = <&uart1m0_xfer>;
+	status = "okay";
+};
+
+&uart2 {
+	pinctrl-0 = <&uart2m0_xfer>;
+	status = "okay";
+};
+
+&uart6 {
+	pinctrl-0 = <&uart6m0_xfer>;
+	status = "okay";
+};
+
+&uart7 {
+	pinctrl-0 = <&uart7m0_xfer>;
+	status = "okay";
+};
+
+&uart9 {
+	pinctrl-0 = <&uart9m0_xfer>;
+	status = "okay";
+};
+
+&usb_host0_ehci {
+	status = "okay";
+};
+
+&usb_host0_ohci {
+	status = "okay";
+};
+
+&usb_host0_xhci {
+	usb-role-switch;
+	status = "okay";
+
+	port {
+		usb_host0_xhci_drd_sw: endpoint {
+			remote-endpoint = <&usbc0_hs>;
+		};
+	};
+};
+
+&usb_host1_ehci {
+	status = "okay";
+};
+
+&usb_host1_ohci {
+	status = "okay";
+};
+
+&usb_host1_xhci {
+	dr_mode = "host";
+	status = "okay";
+};
+
+&usbdp_phy0 {
+	mode-switch;
+	orientation-switch;
+	sbu1-dc-gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>;
+	sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>;
+	status = "okay";
+
+	port {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		usbdp_phy0_typec_ss: endpoint@0 {
+			reg = <0>;
+			remote-endpoint = <&usbc0_ss>;
+		};
+
+		usbdp_phy0_typec_sbu: endpoint@1 {
+			reg = <1>;
+			remote-endpoint = <&usbc0_sbu>;
+		};
+	};
+};
+
+&usbdp_phy1 {
+	status = "okay";
+};
+
+&vop {
+	status = "okay";
+};
+
+&vop_mmu {
+	status = "okay";
+};
+
+&vp0 {
+	vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
+		reg = <ROCKCHIP_VOP2_EP_HDMI0>;
+		remote-endpoint = <&hdmi0_in_vp0>;
+	};
+};

-- 
2.47.3



^ permalink raw reply related

* [PATCH 0/3] arm64: dts: rockchip: Add Youyeetoo YY3588
From: Daniele Briguglio @ 2026-06-10 13:58 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
	Daniele Briguglio

This series adds support for the Youyeetoo YY3588, a single board
computer built around the Rockchip RK3588.

Both Ethernet ports, eMMC, SD card, USB, Type-C, HDMI output, WiFi on
the Mini PCIe slot, audio, the recovery key and the fan have been
tested on the board.

Board documentation: https://wiki.youyeetoo.com/YY3588

Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
Daniele Briguglio (3):
      dt-bindings: vendor-prefixes: Add youyeetoo
      dt-bindings: arm: rockchip: Add Youyeetoo YY3588
      arm64: dts: rockchip: Add Youyeetoo YY3588

 .../devicetree/bindings/arm/rockchip.yaml          |    5 +
 .../devicetree/bindings/vendor-prefixes.yaml       |    2 +
 arch/arm64/boot/dts/rockchip/Makefile              |    1 +
 .../boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts  | 1190 ++++++++++++++++++++
 4 files changed, 1198 insertions(+)
---
base-commit: 8545eda00fdf3d7e17933ce0f706d005b1bad42d
change-id: 20260610-yy3588-board-ecc20882cae7

Best regards,
--  
Daniele Briguglio <hello@superkali.me>



^ permalink raw reply

* [PATCH 2/3] dt-bindings: arm: rockchip: Add Youyeetoo YY3588
From: Daniele Briguglio @ 2026-06-10 13:58 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
	Daniele Briguglio
In-Reply-To: <20260610-yy3588-board-v1-0-4bb7176b6826@superkali.me>

The YY3588 is a single board computer based on the Rockchip RK3588.
Add devicetree binding documentation for it.

Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
 Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml
index 1a9dde186..e7894d2b8 100644
--- a/Documentation/devicetree/bindings/arm/rockchip.yaml
+++ b/Documentation/devicetree/bindings/arm/rockchip.yaml
@@ -1347,6 +1347,11 @@ properties:
           - const: xunlong,orangepi-cm5
           - const: rockchip,rk3588s
 
+      - description: Youyeetoo YY3588
+        items:
+          - const: youyeetoo,yy3588
+          - const: rockchip,rk3588
+
       - description: Zkmagic A95X Z2
         items:
           - const: zkmagic,a95x-z2

-- 
2.47.3



^ permalink raw reply related

* [PATCH 1/3] dt-bindings: vendor-prefixes: Add youyeetoo
From: Daniele Briguglio @ 2026-06-10 13:58 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
	Daniele Briguglio
In-Reply-To: <20260610-yy3588-board-v1-0-4bb7176b6826@superkali.me>

Youyeetoo is the single board computer brand of Hong Kong Cybodev
Tech Limited.

Link: https://www.youyeetoo.com
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 28784d66a..c0d05ba73 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1915,6 +1915,8 @@ patternProperties:
     description: YSH & ATIL
   "^yones-toptech,.*":
     description: Yones Toptech Co., Ltd.
+  "^youyeetoo,.*":
+    description: Hong Kong Cybodev Tech Limited (youyeetoo)
   "^ys,.*":
     description: Shenzhen Yashi Changhua Intelligent Technology Co., Ltd.
   "^ysoft,.*":

-- 
2.47.3



^ permalink raw reply related

* Re: [PATCH v2 04/16] usb: hub: Return actual error from hub_configure() in hub_probe()
From: Andy Shevchenko @ 2026-06-10 14:20 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
	Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
	Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
	linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
	linux-mediatek, linux-arm-kernel, linux-kernel,
	Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-5-wenst@chromium.org>

On Wed, Jun 10, 2026 at 04:40:38PM +0800, Chen-Yu Tsai wrote:
> The addition of power sequencing descriptor handling in the USB hub code
> requires dealing with deferred probing from pwrseq_get(). The power
> sequencing provider may not yet be available when the USB hub probes.
> 
> Return the actual error code from hub_configure() when it fails, so that
> the driver core can notice the deferred probe request.

Makes sense to me.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

One nit-pick, though.

...

> -	if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) {
> +	ret = hub_configure(hub, &desc->endpoint[0].desc);
> +	if (ret >= 0) {
>  		onboard_dev_create_pdevs(hdev, &hub->onboard_devs);
>  
>  		return 0;
>  	}
>  
>  	hub_disconnect(intf);
> -	return -ENODEV;
> +	return ret;

Can we convert to regular pattern, id est checking for errors first?

	ret = hub_configure(hub, &desc->endpoint[0].desc);
	if (ret < 0) {
		hub_disconnect(intf);
		return ret;
	}

	onboard_dev_create_pdevs(hdev, &hub->onboard_devs);

	return 0;

-- 
With Best Regards,
Andy Shevchenko




^ permalink raw reply

* Re: [PATCH v2 05/16] usb: hub: Associate port@ fwnode with USB port device
From: Andy Shevchenko @ 2026-06-10 14:16 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
	Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
	Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
	linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
	linux-mediatek, linux-arm-kernel, linux-kernel,
	Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-6-wenst@chromium.org>

On Wed, Jun 10, 2026 at 04:40:39PM +0800, Chen-Yu Tsai wrote:
> When a USB hub port is connected to a connector in a firmware node
> graph, the port itself has a node in the graph.
> 
> Associate the port's firmware node with the USB port's device,
> usb_port::dev. This is used in later changes for the M.2 slot power
> sequencing provider to match against the requesting port.

Okay, would this affect ACPI-based systems? if so, how?
Can you elaborate on that, please?

-- 
With Best Regards,
Andy Shevchenko




^ permalink raw reply

* Re: [PATCH net-next v2] net: airoha: simplify WAN device check in airoha_dev_init()
From: Alexander Lobakin @ 2026-06-10 14:14 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260610-airoha-eth-simplify-dev-init-v2-1-8f244e69b0d4@kernel.org>

From: Lorenzo Bianconi <lorenzo@kernel.org>
Date: Wed, 10 Jun 2026 15:25:13 +0200

> airoha_register_gdm_devices() iterates eth->ports[] in order, so GDM2's
> netdev is always registered before GDM3/GDM4. This means the explicit
> check for eth->ports[1] && eth->ports[1]->devs[0] is a redundant
> special-case of what airoha_get_wan_gdm_dev() already covers, since
> GDM2 is always marked as WAN during its own ndo_init.
> Remove the redundant check and rely solely on airoha_get_wan_gdm_dev()
> which handles both the GDM2-present and GDM2-absent cases.
> 
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>

Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>

Thanks,
Olek


^ permalink raw reply

* Re: [PATCH net-next v4 0/2] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand
From: Alexander Lobakin @ 2026-06-10 14:08 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev,
	Madhur Agrawal
In-Reply-To: <20260610-airoha-ethtool-priv_flags-v4-0-60e89cf28fea@kernel.org>

From: Lorenzo Bianconi <lorenzo@kernel.org>
Date: Wed, 10 Jun 2026 15:33:35 +0200

> Add the capability to configure GDM3/GDM4 as WAN/LAN on demand when QoS
> offload is created or destroyed.
> Make dev->qdma an RCU pointer so the TX path can safely dereference it
> without holding RTNL.
> Introduce airoha_qdma_start() and airoha_qdma_stop() helpers.
Series:

Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>

Thanks,
Olek


^ permalink raw reply

* Re: [PATCH v2 02/16] device property: Add fwnode_graph_get_next_port_endpoint()
From: Andy Shevchenko @ 2026-06-10 14:08 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
	Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
	Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
	linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
	linux-mediatek, linux-arm-kernel, linux-kernel,
	Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-3-wenst@chromium.org>

On Wed, Jun 10, 2026 at 04:40:36PM +0800, Chen-Yu Tsai wrote:
> Due to design constraints of the power sequencing API, the consumer
> must first be sure that the other side is actually a provider, or it
> will continually get -EPROBE_DEFER when requesting the power
> sequencing descriptor.
> 
> In the upcoming USB power sequencing integration, the USB hub driver
> first needs to check whether a graph connection exists, and whether
> the other side of the connection is a supported connector type. The
> USB port is tied to a "port" firmware node, and this new helper will
> be used to get the endpoint under the known "port" firmware node.

...

> +/**
> + * fwnode_graph_get_next_port_endpoint - Get next endpoint firmware node in port
> + * @port: Pointer to the target port firmware node
> + * @prev: Previous endpoint node or %NULL to get the first
> + *
> + * The caller is responsible for calling fwnode_handle_put() on the returned
> + * fwnode pointer. Note that this function also puts a reference to @prev
> + * unconditionally.
> + *
> + * Return: an endpoint firmware node pointer or %NULL if no more endpoints
> + * are available.

Yeah, you see, even here is inconsistency with previously added kernel-doc.

> + */
> +struct fwnode_handle *fwnode_graph_get_next_port_endpoint(const struct fwnode_handle *port,
> +							  struct fwnode_handle *prev)
> +{
> +	struct fwnode_handle *ep;

Unused?

> +	while (1) {

This is usually harder to read and follow. It's like "pay much attention on
the code", but here no rocket science, no code to really pay attention to.

> +		prev = fwnode_get_next_child_node(port, prev);
> +		if (!prev)
> +			break;
> +
> +		if (WARN(!fwnode_name_eq(prev, "endpoint"),
> +			 "non endpoint node is used (%pfw)", prev))
> +			continue;
> +
> +		break;
> +	}
> +
> +	return prev;
> +}

So, this can be rewritten as

	ep = prev;
	do {
		ep = fwnode_get_next_child_node(port, ep);
		if (fwnode_name_eq(ep, "endpoint"))
			break;

		WARN_ON(ep, ...);
	} while (ep);

	return ep;

But also big question why? to WARN*(). There is no use in the entire
property.c.

-- 
With Best Regards,
Andy Shevchenko




^ permalink raw reply

* Re: [PATCH v10 5/6] pinctrl: s32cc: implement GPIO functionality
From: Khristine Andreea Barbulescu @ 2026-06-10 13:58 UTC (permalink / raw)
  To: Enric Balletbo i Serra
  Cc: Linus Walleij, Bartosz Golaszewski, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Chester Lin, Matthias Brugger,
	Ghennadi Procopciuc, Larisa Grigore, Lee Jones, Shawn Guo,
	Sascha Hauer, Fabio Estevam, Dong Aisheng, Jacky Bai,
	Greg Kroah-Hartman, Rafael J. Wysocki, Srinivas Kandagatla,
	Alberto Ruiz, Christophe Lizzi, devicetree, Eric Chanudet, imx,
	linux-arm-kernel, linux-gpio, linux-kernel, NXP S32 Linux Team,
	Pengutronix Kernel Team, Vincent Guittot
In-Reply-To: <CALE0LRu1aJAY_-7imYFFPbEwWPpodArXbxtjE-ur3UQnVt5fHw@mail.gmail.com>

On 6/2/2026 12:26 PM, Enric Balletbo i Serra wrote:
> Hi Khristine,
> 
> Thank you for the patch. I got some checkpatch warnings, could you
> take a look? And some minor comments below.
> 
> On Tue, Jun 2, 2026 at 10:02 AM Khristine Andreea Barbulescu
> <khristineandreea.barbulescu@oss.nxp.com> wrote:
>>
>> From: Andrei Stefanescu <andrei.stefanescu@oss.nxp.com>
>>
>> The updated SIUL2 block groups pinctrl, GPIO data access
>> and interrupt control within the same hardware unit.
>> The SIUL2 driver is therefore structured as a monolithic
>> pinctrl/GPIO driver.
>>
>> GPIO data access and direction handling are implemented using the
>> gpio-regmap library backed by a virtual regmap. The virtual regmap
>> translates the gpio-regmap register model to the underlying SIUL2
>> registers: MSCR for direction, PGPDI for input values and PGPDO for
>> output values.
>>
>> The existing pinctrl GPIO callbacks are used for the request/free path:
>> they switch the pad to GPIO mode on request and restore the previous
>> MSCR configuration when the GPIO is released.
>>
>> This change came as a result of upstream review in the
>> following series:
>> https://lore.kernel.org/linux-gpio/20260120115923.3463866-4-khristineandreea.barbulescu@oss.nxp.com/T/#m543c9edbdde74bdc68b6a2364e8b975356c33043
>> https://lore.kernel.org/all/20260504131148.3622697-7-khristineandreea.barbulescu@oss.nxp.com/
>>
>> Support both SIUL2 DT layouts:
>> - legacy pinctrl-only binding
>> - extended pinctrl/GPIO/irqchip binding
>>
>> Signed-off-by: Andrei Stefanescu <andrei.stefanescu@oss.nxp.com>
>> Signed-off-by: Khristine Andreea Barbulescu <khristineandreea.barbulescu@oss.nxp.com>
>> ---
>>  drivers/pinctrl/nxp/Kconfig         |   1 +
>>  drivers/pinctrl/nxp/pinctrl-s32.h   |  32 +-
>>  drivers/pinctrl/nxp/pinctrl-s32cc.c | 685 +++++++++++++++++++++++++---
>>  drivers/pinctrl/nxp/pinctrl-s32g2.c |  46 +-
>>  4 files changed, 686 insertions(+), 78 deletions(-)
>>
>> diff --git a/drivers/pinctrl/nxp/Kconfig b/drivers/pinctrl/nxp/Kconfig
>> index abca7ef97003..59fc6adf5b0b 100644
>> --- a/drivers/pinctrl/nxp/Kconfig
>> +++ b/drivers/pinctrl/nxp/Kconfig
>> @@ -5,6 +5,7 @@ config PINCTRL_S32CC
>>         select GENERIC_PINCTRL_GROUPS
>>         select GENERIC_PINMUX_FUNCTIONS
>>         select GENERIC_PINCONF
>> +       select GPIO_REGMAP
>>         select REGMAP_MMIO
>>
>>  config PINCTRL_S32G2
>> diff --git a/drivers/pinctrl/nxp/pinctrl-s32.h b/drivers/pinctrl/nxp/pinctrl-s32.h
>> index 8715befd5f05..c2fc5eda7eb4 100644
>> --- a/drivers/pinctrl/nxp/pinctrl-s32.h
>> +++ b/drivers/pinctrl/nxp/pinctrl-s32.h
>> @@ -2,7 +2,7 @@
>>   *
>>   * S32 pinmux core definitions
>>   *
>> - * Copyright 2016-2020, 2022 NXP
>> + * Copyright 2016-2020, 2022, 2026 NXP
>>   * Copyright (C) 2022 SUSE LLC
>>   * Copyright 2015-2016 Freescale Semiconductor, Inc.
>>   * Copyright (C) 2012 Linaro Ltd.
>> @@ -34,11 +34,39 @@ struct s32_pin_range {
>>         unsigned int end;
>>  };
>>
>> +/**
>> + * struct s32_gpio_range - contiguous GPIO pin range within a SIUL2 module
>> + * @gpio_base: first GPIO line offset in the GPIO range
>> + * @pin_base: first pinctrl pin number mapped by this GPIO range
>> + * @gpio_num: number of consecutive GPIO pins in the range
>> + */
>> +struct s32_gpio_range {
>> +       unsigned int gpio_base;
>> +       unsigned int pin_base;
>> +       unsigned int gpio_num;
>> +};
>> +
>> +/**
>> + * struct s32_gpio_pad_map - mapping between GPIO ranges and PGPD pads
>> + * @gpio_start: first GPIO line offset in the range
>> + * @gpio_end: last GPIO line offset in the range
>> + * @pad: PGPD pad number serving the range
>> + */
>> +struct s32_gpio_pad_map {
>> +       unsigned int gpio_start;
>> +       unsigned int gpio_end;
>> +       unsigned int pad;
>> +};
>> +
>>  struct s32_pinctrl_soc_data {
>>         const struct pinctrl_pin_desc *pins;
>>         unsigned int npins;
>>         const struct s32_pin_range *mem_pin_ranges;
>>         unsigned int mem_regions;
>> +       const struct s32_gpio_range *gpio_ranges;
>> +       unsigned int num_gpio_ranges;
>> +       const struct s32_gpio_pad_map *gpio_pad_maps;
>> +       unsigned int num_gpio_pad_maps;
>>  };
>>
>>  struct s32_pinctrl_soc_info {
>> @@ -53,6 +81,8 @@ struct s32_pinctrl_soc_info {
>>
>>  #define S32_PINCTRL_PIN(pin)   PINCTRL_PIN(pin, #pin)
>>  #define S32_PIN_RANGE(_start, _end) { .start = _start, .end = _end }
>> +#define S32_GPIO_RANGE(gpio, pin, num) \
>> +       { .gpio_base = gpio, .pin_base = pin, .gpio_num = num }
>>
>>  int s32_pinctrl_probe(struct platform_device *pdev,
>>                       const struct s32_pinctrl_soc_data *soc_data);
>> diff --git a/drivers/pinctrl/nxp/pinctrl-s32cc.c b/drivers/pinctrl/nxp/pinctrl-s32cc.c
>> index 89a4eb2000ee..8843926345ec 100644
>> --- a/drivers/pinctrl/nxp/pinctrl-s32cc.c
>> +++ b/drivers/pinctrl/nxp/pinctrl-s32cc.c
>> @@ -2,7 +2,7 @@
>>  /*
>>   * Core driver for the S32 CC (Common Chassis) pin controller
>>   *
>> - * Copyright 2017-2022,2024 NXP
>> + * Copyright 2017-2022,2024-2026 NXP
>>   * Copyright (C) 2022 SUSE LLC
>>   * Copyright 2015-2016 Freescale Semiconductor, Inc.
>>   */
>> @@ -10,6 +10,7 @@
>>  #include <linux/bitops.h>
>>  #include <linux/err.h>
>>  #include <linux/gpio/driver.h>
>> +#include <linux/gpio/regmap.h>
>>  #include <linux/init.h>
>>  #include <linux/io.h>
>>  #include <linux/module.h>
>> @@ -39,6 +40,40 @@
>>  #define S32_MSCR_ODE           BIT(20)
>>  #define S32_MSCR_OBE           BIT(21)
>>
>> +#define S32_GPIO_OP_SHIFT      16
>> +#define S32_GPIO_OP_MASK       GENMASK(19, 16)
>> +
>> +#define S32_GPIO_OP_DIR                0 /* MSCR direction */
>> +#define S32_GPIO_OP_DAT                BIT(S32_GPIO_OP_SHIFT) /* PGPDI read */
>> +#define S32_GPIO_OP_SET                BIT(S32_GPIO_OP_SHIFT + 1) /* PGPDO write */
>> +
>> +/*
>> + * [15:12] = GPIO bank / gpio range index
>> + * [11:0]  = real register offset or pin id
>> + */
>> +#define S32_GPIO_BANK_SHIFT    12
>> +#define S32_GPIO_BANK_MASK    GENMASK(15, 12)
>> +#define S32_GPIO_REG_MASK    GENMASK(11, 0)
>> +
>> +#define S32_GPIO_ENCODE(bank, off) \
>> +       ((((bank) << S32_GPIO_BANK_SHIFT) & S32_GPIO_BANK_MASK) | \
>> +               ((off) & S32_GPIO_REG_MASK))
>> +
>> +#define S32_GPIO_DECODE_BANK(reg) \
>> +       (((reg) & S32_GPIO_BANK_MASK) >> S32_GPIO_BANK_SHIFT)
>> +
>> +#define S32_GPIO_DECODE_OFF(reg) \
>> +       ((reg) & S32_GPIO_REG_MASK)
>> +
>> +/*
>> + * PGPDOs are 16bit registers that come in big endian
>> + * order if they are grouped in pairs of two.
>> + *
>> + * For example, the order is PGPDO1, PGPDO0, PGPDO3, PGPDO2...
>> + */
>> +#define S32_PGPD(N)            (((N) ^ 1) * 2)
>> +#define S32_PGPD_SIZE          16
>> +
>>  enum s32_write_type {
>>         S32_PINCONF_UPDATE_ONLY,
>>         S32_PINCONF_OVERWRITE,
>> @@ -72,6 +107,18 @@ struct s32_pinctrl_mem_region {
>>         char name[8];
>>  };
>>
>> +/*
>> + * struct s32_gpio_regmaps - GPIO register maps for a SIUL2 instance
>> + * @pgpdo: regmap for Parallel GPIO Pad Data Out registers
>> + * @pgpdi: regmap for Parallel GPIO Pad Data In registers
>> + * @range: GPIO range info
>> + */
>> +struct s32_gpio_regmaps {
>> +       struct regmap *pgpdo;
>> +       struct regmap *pgpdi;
>> +       const struct s32_gpio_range *range;
>> +};
>> +
>>  /*
>>   * struct gpio_pin_config - holds pin configuration for GPIO's
>>   * @pin_id: Pin ID for this GPIO
>> @@ -98,6 +145,12 @@ struct s32_pinctrl_context {
>>   * @pctl: a pointer to the pinctrl device structure
>>   * @regions: reserved memory regions with start/end pin
>>   * @info: structure containing information about the pin
>> + * @gpio_regmaps: PGPDO/PGPDI regmaps for each SIUL2 module
>> + * @num_gpio_regmaps: number of GPIO regmap entries
>> + * @gpio_regmap: regmap bridging gpio-regmap to SIUL2 registers
>> + * @gpio_rgm: gpio-regmap instance registered for this controller
>> + * @ngpio: total number of GPIO line offsets
>> + * @gpio_names: GPIO line names array passed to gpio-regmap
>>   * @gpio_configs: saved configurations for GPIO pins
>>   * @gpio_configs_lock: lock for the `gpio_configs` list
>>   * @saved_context: configuration saved over system sleep
>> @@ -107,6 +160,12 @@ struct s32_pinctrl {
>>         struct pinctrl_dev *pctl;
>>         struct s32_pinctrl_mem_region *regions;
>>         struct s32_pinctrl_soc_info *info;
>> +       struct s32_gpio_regmaps *gpio_regmaps;
>> +       unsigned int num_gpio_regmaps;
>> +       struct regmap *gpio_regmap;
>> +       struct gpio_regmap *gpio_rgm;
>> +       unsigned int ngpio;
>> +       const char *const *gpio_names;
>>         struct list_head gpio_configs;
>>         spinlock_t gpio_configs_lock;
>>  #ifdef CONFIG_PM_SLEEP
>> @@ -356,88 +415,84 @@ static int s32_pmx_get_funcs_count(struct pinctrl_dev *pctldev)
>>         return info->nfunctions;
>>  }
>>
>> -static const char *s32_pmx_get_func_name(struct pinctrl_dev *pctldev,
>> -                                        unsigned int selector)
>> -{
>> -       struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
>> -       const struct s32_pinctrl_soc_info *info = ipctl->info;
>> -
>> -       return info->functions[selector].name;
>> -}
>> -
>> -static int s32_pmx_get_groups(struct pinctrl_dev *pctldev,
>> -                             unsigned int selector,
>> -                             const char * const **groups,
>> -                             unsigned int * const num_groups)
>> -{
>> -       struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
>> -       const struct s32_pinctrl_soc_info *info = ipctl->info;
>> -
>> -       *groups = info->functions[selector].groups;
>> -       *num_groups = info->functions[selector].ngroups;
>> -
>> -       return 0;
>> -}
>> -
>>  static int s32_pmx_gpio_request_enable(struct pinctrl_dev *pctldev,
>>                                        struct pinctrl_gpio_range *range,
>> -                                      unsigned int offset)
>> +                                      unsigned int pin)
>>  {
>>         struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
>> -       struct gpio_pin_config *gpio_pin;
>> +       struct gpio_pin_config *gpio_pin __free(kfree) = NULL;
>>         unsigned int config;
>> -       unsigned long flags;
>>         int ret;
>>
>> -       ret = s32_regmap_read(pctldev, offset, &config);
>> +       ret = s32_regmap_read(pctldev, pin, &config);
>>         if (ret)
>>                 return ret;
>>
>> -       /* Save current configuration */
>> -       gpio_pin = kmalloc_obj(*gpio_pin);
>> +       gpio_pin = kmalloc(sizeof(*gpio_pin), GFP_KERNEL);
> 
> Why? Isn't kmalloc_obj safer?
> 
>>         if (!gpio_pin)
>>                 return -ENOMEM;
>>
>> -       gpio_pin->pin_id = offset;
>> +       gpio_pin->pin_id = pin;
>>         gpio_pin->config = config;
>> -       INIT_LIST_HEAD(&gpio_pin->list);
>> -
>> -       spin_lock_irqsave(&ipctl->gpio_configs_lock, flags);
>> -       list_add(&gpio_pin->list, &ipctl->gpio_configs);
>> -       spin_unlock_irqrestore(&ipctl->gpio_configs_lock, flags);
>>
>>         /* GPIO pin means SSS = 0 */
>> -       config &= ~S32_MSCR_SSS_MASK;
>> +       ret = s32_regmap_update(pctldev, pin,
>> +                               S32_MSCR_SSS_MASK | S32_MSCR_IBE,
>> +                               S32_MSCR_IBE);
>> +       if (ret)
>> +               return ret;
>> +
>> +       scoped_guard(spinlock_irqsave, &ipctl->gpio_configs_lock)
>> +               list_add(&no_free_ptr(gpio_pin)->list, &ipctl->gpio_configs);
>>
>> -       return s32_regmap_write(pctldev, offset, config);
>> +       return 0;
>>  }
>>
>>  static void s32_pmx_gpio_disable_free(struct pinctrl_dev *pctldev,
>>                                       struct pinctrl_gpio_range *range,
>> -                                     unsigned int offset)
>> +                                     unsigned int pin)
>>  {
>>         struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
>> -       struct gpio_pin_config *gpio_pin, *tmp;
>> +       struct gpio_pin_config *gpio_pin, *found = NULL;
>>         unsigned long flags;
>> -       int ret;
>>
>>         spin_lock_irqsave(&ipctl->gpio_configs_lock, flags);
>> -
>> -       list_for_each_entry_safe(gpio_pin, tmp, &ipctl->gpio_configs, list) {
>> -               if (gpio_pin->pin_id == offset) {
>> -                       ret = s32_regmap_write(pctldev, gpio_pin->pin_id,
>> -                                                gpio_pin->config);
>> -                       if (ret != 0)
>> -                               goto unlock;
>> -
>> +       list_for_each_entry(gpio_pin, &ipctl->gpio_configs, list) {
>> +               if (gpio_pin->pin_id == pin) {
>>                         list_del(&gpio_pin->list);
>> -                       kfree(gpio_pin);
>> +                       found = gpio_pin;
>>                         break;
>>                 }
>>         }
>> -
>> -unlock:
>>         spin_unlock_irqrestore(&ipctl->gpio_configs_lock, flags);
>> +
>> +       if (found) {
>> +               s32_regmap_write(pctldev, found->pin_id, found->config);
>> +               kfree(found);
>> +       }
>> +}
>> +
>> +static const char *s32_pmx_get_func_name(struct pinctrl_dev *pctldev,
>> +                                        unsigned int selector)
>> +{
>> +       struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
>> +       const struct s32_pinctrl_soc_info *info = ipctl->info;
>> +
>> +       return info->functions[selector].name;
>> +}
>> +
>> +static int s32_pmx_get_groups(struct pinctrl_dev *pctldev,
>> +                             unsigned int selector,
>> +                             const char * const **groups,
>> +                             unsigned int * const num_groups)
>> +{
>> +       struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
>> +       const struct s32_pinctrl_soc_info *info = ipctl->info;
>> +
>> +       *groups = info->functions[selector].groups;
>> +       *num_groups = info->functions[selector].ngroups;
>> +
>> +       return 0;
>>  }
>>
>>  static int s32_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
>> @@ -649,9 +704,9 @@ static void s32_pinconf_dbg_show(struct pinctrl_dev *pctldev,
>>
>>         ret = s32_regmap_read(pctldev, pin_id, &config);
>>         if (ret)
>> -               return;
>> -
>> -       seq_printf(s, "0x%x", config);
>> +               seq_printf(s, "error %d", ret);
>> +       else
>> +               seq_printf(s, "0x%x", config);
>>  }
>>
>>  static void s32_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
>> @@ -662,15 +717,13 @@ static void s32_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
>>         struct s32_pin_group *grp;
>>         unsigned int config;
>>         const char *name;
>> -       int i, ret;
>> +       int i;
>>
>>         seq_puts(s, "\n");
>>         grp = &info->groups[selector];
>>         for (i = 0; i < grp->data.npins; i++) {
>>                 name = pin_get_name(pctldev, grp->data.pins[i]);
>> -               ret = s32_regmap_read(pctldev, grp->data.pins[i], &config);
>> -               if (ret)
>> -                       return;
>> +               s32_regmap_read(pctldev, grp->data.pins[i], &config);
>>                 seq_printf(s, "%s: 0x%x\n", name, config);
>>         }
>>  }
>> @@ -683,6 +736,450 @@ static const struct pinconf_ops s32_pinconf_ops = {
>>         .pin_config_group_dbg_show = s32_pinconf_group_dbg_show,
>>  };
>>
>> +static void s32_gpio_free_saved_configs(void *data)
>> +{
>> +       struct s32_pinctrl *ipctl = data;
>> +       struct gpio_pin_config *gpio_pin, *tmp;
>> +       unsigned long flags;
>> +
>> +       spin_lock_irqsave(&ipctl->gpio_configs_lock, flags);
>> +       list_for_each_entry_safe(gpio_pin, tmp, &ipctl->gpio_configs, list) {
>> +               list_del(&gpio_pin->list);
>> +               kfree(gpio_pin);
>> +       }
>> +       spin_unlock_irqrestore(&ipctl->gpio_configs_lock, flags);
>> +}
>> +
>> +static unsigned int s32_pin2pad(unsigned int pin)
>> +{
>> +       return pin / S32_PGPD_SIZE;
>> +}
>> +
>> +static u16 s32_pin2mask(unsigned int pin)
>> +{
>> +       /*
>> +        * From Reference manual :
>> +        * PGPDOx[PPDOy] = GPDO(x × 16) + (15 - y)[PDO_(x × 16) + (15 - y)]
>> +        */
>> +       return BIT(S32_PGPD_SIZE - 1 - pin % S32_PGPD_SIZE);
>> +}
>> +
>> +static int s32_gpio_get_range(struct s32_pinctrl *ipctl,
>> +                             unsigned int gpio,
>> +                             unsigned int *pin,
>> +                             unsigned int *bank)
>> +{
>> +       const struct s32_pinctrl_soc_data *soc_data = ipctl->info->soc_data;
>> +       const struct s32_gpio_range *range;
>> +       int i;
>> +
>> +       for (i = 0; i < soc_data->num_gpio_ranges; i++) {
>> +               range = &soc_data->gpio_ranges[i];
>> +
>> +               if (gpio < range->gpio_base ||
>> +                   gpio >= range->gpio_base + range->gpio_num)
>> +                       continue;
>> +
>> +               if (pin)
>> +                       *pin = range->pin_base + gpio - range->gpio_base;
>> +
>> +               if (bank)
>> +                       *bank = i;
>> +
>> +               return 0;
>> +       }
>> +
>> +       return -EINVAL;
>> +}
>> +
>> +static int s32_gpio_pad_map_xlate(struct s32_pinctrl *ipctl,
>> +                                 unsigned int gpio,
>> +                                 unsigned int *reg_offset,
>> +                                 u16 *mask)
>> +{
>> +       const struct s32_pinctrl_soc_data *soc_data = ipctl->info->soc_data;
>> +       const struct s32_gpio_pad_map *map;
>> +       unsigned int bit;
>> +       int i;
>> +
>> +       if (!soc_data->gpio_pad_maps || !soc_data->num_gpio_pad_maps)
>> +               return -EINVAL;
>> +
>> +       for (i = 0; i < soc_data->num_gpio_pad_maps; i++) {
>> +               map = &soc_data->gpio_pad_maps[i];
>> +
>> +               if (gpio < map->gpio_start || gpio > map->gpio_end)
>> +                       continue;
>> +
>> +               bit = gpio - map->gpio_start;
>> +               *mask = BIT(S32_PGPD_SIZE - 1 - bit);
>> +               *reg_offset = S32_PGPD(map->pad);
>> +
>> +               return 0;
>> +       }
>> +
>> +       return -EINVAL;
>> +}
>> +
>> +static int s32_gpio_xlate_pgpd(struct s32_pinctrl *ipctl,
>> +                              unsigned int pin,
>> +                              unsigned int *reg_offset,
>> +                              u16 *mask)
>> +{
>> +       /*
>> +        * SIUL2_1 does not expose GPIO data registers as a linear pad sequence.
>> +        * Valid PGPD offsets there correspond to PGPD7, PGPD9, PGPD10, PGPD11.
>> +        */
>> +       if (pin >= 112)
> 
> The magic number 112 requires better documentation or a define
> 
> Or maybe, (NOT TESTED) instead of hardcoding, check if a pad map
> exists for this pin
> 
>     /* Try pad map first (needed for SIUL2_1's sparse layout) */
>     ret = s32_gpio_pad_map_xlate(ipctl, pin, reg_offset, mask);
>     if (ret != -EINVAL)
>         return ret;
> 
>     /* Fall back to linear layout (SIUL2_0) */
>     *mask = s32_pin2mask(pin);
>     *reg_offset = S32_PGPD(s32_pin2pad(pin));
>     return 0;
> 
> Does it make sense?
> 
>> +               return s32_gpio_pad_map_xlate(ipctl, pin, reg_offset, mask);
>> +
>> +       *mask = s32_pin2mask(pin);
>> +       *reg_offset = S32_PGPD(s32_pin2pad(pin));
>> +
>> +       return 0;
>> +}
>> +
>> +static int s32_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
>> +                                  unsigned int base, unsigned int offset,
>> +                                  unsigned int *reg, unsigned int *mask)
>> +{
>> +       struct s32_pinctrl *ipctl = gpio_regmap_get_drvdata(gpio);
>> +       unsigned int pgpd_reg, pin, bank;
>> +       u16 pgpd_mask;
>> +       int ret;
>> +
>> +       ret = s32_gpio_get_range(ipctl, offset, &pin, &bank);
>> +       if (ret)
>> +               return ret;
>> +
>> +       switch (base) {
>> +       case S32_GPIO_OP_DIR:
>> +               /*
>> +                * Direction is controlled through MSCR OBE.
>> +                * Encode the real pin id in the virtual register.
>> +                */
>> +               *reg = S32_GPIO_OP_DIR | pin;
>> +               *mask = S32_MSCR_OBE;
>> +               return 0;
>> +
>> +       case S32_GPIO_OP_DAT:
>> +       case S32_GPIO_OP_SET:
>> +               ret = s32_gpio_xlate_pgpd(ipctl, pin, &pgpd_reg, &pgpd_mask);
>> +               if (ret)
>> +                       return ret;
>> +               /*
>> +                * Encode both the GPIO bank and the real PGPD register offset.
>> +                */
>> +               *reg = base | S32_GPIO_ENCODE(bank, pgpd_reg);
>> +               *mask = pgpd_mask;
>> +               return 0;
>> +       default:
>> +               return -EINVAL;
>> +       }
>> +}
>> +
>> +static int s32_gpio_reg_read(void *context, unsigned int reg,
>> +                            unsigned int *val)
>> +{
>> +       struct s32_pinctrl *ipctl = context;
>> +       unsigned int op = reg & S32_GPIO_OP_MASK;
>> +       unsigned int vreg = reg & ~S32_GPIO_OP_MASK;
>> +       unsigned int bank;
>> +       unsigned int offset;
>> +       struct regmap *map;
>> +
>> +       switch (op) {
>> +       case S32_GPIO_OP_DIR:
>> +               /*
>> +                * Lower bits contain the real MSCR pin id.
>> +                */
>> +               offset = S32_GPIO_DECODE_OFF(vreg);
>> +
>> +               return s32_regmap_read(ipctl->pctl, offset, val);
>> +
>> +       case S32_GPIO_OP_DAT:
>> +               bank = S32_GPIO_DECODE_BANK(vreg);
>> +               offset = S32_GPIO_DECODE_OFF(vreg);
>> +
>> +               if (bank >= ipctl->num_gpio_regmaps)
>> +                       return -EINVAL;
>> +
>> +               map = ipctl->gpio_regmaps[bank].pgpdi;
>> +               if (!map)
>> +                       return -ENODEV;
>> +
>> +               return regmap_read(map, offset, val);
>> +
>> +       case S32_GPIO_OP_SET:
>> +               /*
>> +                * gpio-regmap uses update_bits() for set, so it needs to read
>> +                * the output register before writing the updated value.
>> +                */
>> +               bank = S32_GPIO_DECODE_BANK(vreg);
>> +               offset = S32_GPIO_DECODE_OFF(vreg);
>> +
>> +               if (bank >= ipctl->num_gpio_regmaps)
>> +                       return -EINVAL;
>> +
>> +               map = ipctl->gpio_regmaps[bank].pgpdo;
>> +               if (!map)
>> +                       return -ENODEV;
>> +
>> +               return regmap_read(map, offset, val);
>> +
>> +       default:
>> +               return -EINVAL;
>> +       }
>> +}
>> +
>> +static int s32_gpio_reg_write(void *context, unsigned int reg,
>> +                             unsigned int val)
>> +{
>> +       struct s32_pinctrl *ipctl = context;
>> +       unsigned int op = reg & S32_GPIO_OP_MASK;
>> +       unsigned int vreg = reg & ~S32_GPIO_OP_MASK;
>> +       unsigned int bank, offset, config;
>> +       struct regmap *map;
>> +
>> +       switch (op) {
>> +       case S32_GPIO_OP_DIR:
>> +               /*
>> +                * gpio-regmap sets S32_MSCR_OBE for output and clears it for
>> +                * input. Keep IBE enabled for GPIOs in both cases.
>> +                */
>> +               offset = S32_GPIO_DECODE_OFF(vreg);
>> +
>> +               config = S32_MSCR_IBE;
>> +               if (val & S32_MSCR_OBE)
>> +                       config |= S32_MSCR_OBE;
>> +
>> +               return s32_regmap_update(ipctl->pctl, offset,
>> +                                        S32_MSCR_OBE | S32_MSCR_IBE,
>> +                                        config);
>> +
>> +       case S32_GPIO_OP_SET:
>> +               bank = S32_GPIO_DECODE_BANK(vreg);
>> +               offset = S32_GPIO_DECODE_OFF(vreg);
>> +
>> +               if (bank >= ipctl->num_gpio_regmaps)
>> +                       return -EINVAL;
>> +
>> +               map = ipctl->gpio_regmaps[bank].pgpdo;
>> +               if (!map)
>> +                       return -ENODEV;
>> +
>> +               return regmap_write(map, offset, val);
>> +
>> +       default:
>> +               return -EINVAL;
>> +       }
>> +}
>> +
>> +static const struct regmap_bus s32_gpio_regmap_bus = {
>> +       .reg_read = s32_gpio_reg_read,
>> +       .reg_write = s32_gpio_reg_write,
>> +};
>> +
>> +static const struct regmap_config s32_gpio_regmap_config = {
>> +       .name = "s32-gpio",
>> +       .reg_bits = 32,
>> +       .val_bits = 32,
>> +       .reg_stride = 1,
>> +       .max_register = S32_GPIO_OP_SET | S32_GPIO_BANK_MASK | S32_GPIO_REG_MASK,
>> +       .cache_type = REGCACHE_NONE,
>> +};
>> +
>> +static int s32_gpio_get_ngpio(const struct s32_pinctrl_soc_data *soc_data,
>> +                             unsigned int *ngpio)
>> +{
>> +       const struct s32_gpio_range *range;
>> +       unsigned int end, max = 0;
>> +       int i;
>> +
>> +       if (!soc_data->gpio_ranges || !soc_data->num_gpio_ranges)
>> +               return -EINVAL;
>> +
>> +       for (i = 0; i < soc_data->num_gpio_ranges; i++) {
>> +               range = &soc_data->gpio_ranges[i];
>> +
>> +               if (!range->gpio_num)
>> +                       return -EINVAL;
>> +
>> +               end = range->gpio_base + range->gpio_num;
>> +
>> +               /*
>> +                * gpio_ranges must be ordered by gpio_base and must not overlap.
>> +                * The GPIO line space size is derived from the highest range end.
>> +                */
>> +               if (i > 0 && range->gpio_base < max)
>> +                       return -EINVAL;
>> +
>> +               if (end > max)
>> +                       max = end;
>> +       }
>> +
>> +       *ngpio = max;
>> +
>> +       return 0;
>> +}
>> +
>> +static int s32_init_gpio_regmap(struct platform_device *pdev,
>> +                               struct s32_pinctrl *ipctl)
>> +{
>> +       ipctl->gpio_regmap =
>> +               devm_regmap_init(&pdev->dev, &s32_gpio_regmap_bus,
>> +                                ipctl, &s32_gpio_regmap_config);
>> +       if (IS_ERR(ipctl->gpio_regmap))
>> +               return dev_err_probe(&pdev->dev,
>> +                                    PTR_ERR(ipctl->gpio_regmap),
>> +                                    "Failed to init GPIO regmap\n");
>> +
>> +       return 0;
>> +}
>> +
>> +static int s32_init_valid_mask(struct gpio_chip *chip, unsigned long *mask,
>> +                              unsigned int ngpios)
>> +{
>> +       struct gpio_regmap *gpio = gpiochip_get_data(chip);
>> +       struct s32_pinctrl *ipctl = gpio_regmap_get_drvdata(gpio);
>> +       unsigned int gpio_num, pin, reg_offset;
>> +       u16 pgpd_mask;
>> +       int ret;
>> +
>> +       bitmap_zero(mask, ngpios);
>> +
>> +       for (gpio_num = 0; gpio_num < ngpios; gpio_num++) {
>> +               ret = s32_gpio_get_range(ipctl, gpio_num, &pin, NULL);
>> +               if (ret)
>> +                       continue;
>> +
>> +               ret = s32_gpio_xlate_pgpd(ipctl, pin, &reg_offset, &pgpd_mask);
>> +               if (ret)
>> +                       continue;
>> +
>> +               bitmap_set(mask, gpio_num, 1);
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int s32_gpio_populate_names(struct s32_pinctrl *ipctl)
>> +{
>> +       char **names;
>> +       unsigned int gpio;
>> +       unsigned int pin;
>> +       char port;
>> +       int ret;
>> +
>> +       names = devm_kcalloc(ipctl->dev, ipctl->ngpio, sizeof(*names),
>> +                            GFP_KERNEL);
>> +       if (!names)
>> +               return -ENOMEM;
>> +
>> +       for (gpio = 0; gpio < ipctl->ngpio; gpio++) {
>> +               ret = s32_gpio_get_range(ipctl, gpio, &pin, NULL);
>> +               if (ret)
>> +                       continue;
>> +
>> +               port = 'A' + pin / 16;
>> +
>> +               names[gpio] = devm_kasprintf(ipctl->dev, GFP_KERNEL,
>> +                                            "P%c_%02u", port, pin & 0xf);
>> +               if (!names[gpio])
>> +                       return -ENOMEM;
>> +       }
>> +
>> +       ipctl->gpio_names = (const char *const *)names;
>> +
>> +       return 0;
>> +}
>> +
>> +static int s32_pinctrl_init_gpio_regmaps(struct platform_device *pdev,
>> +                                        struct s32_pinctrl *ipctl)
>> +{
>> +       const struct s32_pinctrl_soc_data *soc_data = ipctl->info->soc_data;
>> +       static const struct regmap_config pgpd_config = {
>> +               .reg_bits = 32,
>> +               .val_bits = 16,
>> +               .reg_stride = 2,
>> +       };
>> +       struct regmap_config cfg;
>> +       struct resource *res;
>> +       void __iomem *base;
>> +       unsigned int pgpdo_idx, pgpdi_idx;
>> +       unsigned int i;
>> +
>> +       if (!soc_data->gpio_ranges || !soc_data->num_gpio_ranges)
>> +               return 0;
>> +
>> +       ipctl->num_gpio_regmaps = soc_data->num_gpio_ranges;
>> +       ipctl->gpio_regmaps = devm_kcalloc(&pdev->dev, ipctl->num_gpio_regmaps,
>> +                                          sizeof(*ipctl->gpio_regmaps),
>> +                                          GFP_KERNEL);
>> +       if (!ipctl->gpio_regmaps)
>> +               return -ENOMEM;
>> +
>> +       for (i = 0; i < ipctl->num_gpio_regmaps; i++) {
>> +               ipctl->gpio_regmaps[i].range = &soc_data->gpio_ranges[i];
>> +
>> +               /*
>> +                * GPIO resources are placed after the pinctrl regions
>> +                */
>> +               pgpdo_idx = soc_data->mem_regions + i * 2;
>> +               pgpdi_idx = soc_data->mem_regions + i * 2 + 1;
>> +
>> +               /* PGPDO */
>> +               res = platform_get_resource(pdev, IORESOURCE_MEM, pgpdo_idx);
>> +               if (!res)
>> +                       return dev_err_probe(&pdev->dev, -ENOENT,
>> +                                                "Missing PGPDO resource %u\n", i);
>> +
>> +               base = devm_ioremap_resource(&pdev->dev, res);
>> +               if (IS_ERR(base))
>> +                       return PTR_ERR(base);
>> +
>> +               cfg = pgpd_config;
>> +               cfg.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "pgpdo%u", i);
>> +               if (!cfg.name)
>> +                       return -ENOMEM;
>> +
>> +               cfg.max_register = resource_size(res) - cfg.reg_stride;
>> +
>> +               ipctl->gpio_regmaps[i].pgpdo =
>> +                       devm_regmap_init_mmio(&pdev->dev, base, &cfg);
>> +               if (IS_ERR(ipctl->gpio_regmaps[i].pgpdo))
>> +                       return dev_err_probe(&pdev->dev,
>> +                                                PTR_ERR(ipctl->gpio_regmaps[i].pgpdo),
>> +                                                "Failed to init PGPDO regmap %u\n", i);
>> +
>> +               /* PGPDI */
>> +               res = platform_get_resource(pdev, IORESOURCE_MEM, pgpdi_idx);
>> +               if (!res)
>> +                       return dev_err_probe(&pdev->dev, -ENOENT,
>> +                                                "Missing PGPDI resource %u\n", i);
>> +
>> +               base = devm_ioremap_resource(&pdev->dev, res);
>> +               if (IS_ERR(base))
>> +                       return PTR_ERR(base);
>> +
>> +               cfg = pgpd_config;
>> +               cfg.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "pgpdi%u", i);
>> +               if (!cfg.name)
>> +                       return -ENOMEM;
>> +
>> +               cfg.max_register = resource_size(res) - cfg.reg_stride;
>> +
>> +               ipctl->gpio_regmaps[i].pgpdi =
>> +                       devm_regmap_init_mmio(&pdev->dev, base, &cfg);
>> +               if (IS_ERR(ipctl->gpio_regmaps[i].pgpdi))
>> +                       return dev_err_probe(&pdev->dev,
>> +                                                PTR_ERR(ipctl->gpio_regmaps[i].pgpdi),
>> +                                                "Failed to init PGPDI regmap %u\n", i);
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>>  #ifdef CONFIG_PM_SLEEP
>>  static bool s32_pinctrl_should_save(struct s32_pinctrl *ipctl,
>>                                     unsigned int pin)
>> @@ -710,7 +1207,6 @@ int s32_pinctrl_suspend(struct device *dev)
>>         const struct s32_pinctrl_soc_info *info = ipctl->info;
>>         struct s32_pinctrl_context *saved_context = &ipctl->saved_context;
>>         int i;
>> -       int ret;
>>         unsigned int config;
>>
>>         for (i = 0; i < info->soc_data->npins; i++) {
>> @@ -719,9 +1215,7 @@ int s32_pinctrl_suspend(struct device *dev)
>>                 if (!s32_pinctrl_should_save(ipctl, pin->number))
>>                         continue;
>>
>> -               ret = s32_regmap_read(ipctl->pctl, pin->number, &config);
>> -               if (ret)
>> -                       return -EINVAL;
>> +               s32_regmap_read(ipctl->pctl, pin->number, &config);
>>
>>                 saved_context->pads[i] = config;
>>         }
>> @@ -736,7 +1230,7 @@ int s32_pinctrl_resume(struct device *dev)
>>         const struct s32_pinctrl_soc_info *info = ipctl->info;
>>         const struct pinctrl_pin_desc *pin;
>>         struct s32_pinctrl_context *saved_context = &ipctl->saved_context;
>> -       int ret, i;
>> +       int i;
>>
>>         for (i = 0; i < info->soc_data->npins; i++) {
>>                 pin = &info->soc_data->pins[i];
>> @@ -744,10 +1238,8 @@ int s32_pinctrl_resume(struct device *dev)
>>                 if (!s32_pinctrl_should_save(ipctl, pin->number))
>>                         continue;
>>
>> -               ret = s32_regmap_write(ipctl->pctl, pin->number,
>> -                                        saved_context->pads[i]);
>> -               if (ret)
>> -                       return ret;
>> +               s32_regmap_write(ipctl->pctl, pin->number,
>> +                                saved_context->pads[i]);
>>         }
>>
>>         return 0;
>> @@ -927,13 +1419,15 @@ static int s32_pinctrl_probe_dt(struct platform_device *pdev,
>>  int s32_pinctrl_probe(struct platform_device *pdev,
>>                       const struct s32_pinctrl_soc_data *soc_data)
>>  {
>> -       struct s32_pinctrl *ipctl;
>> -       int ret;
>> -       struct pinctrl_desc *s32_pinctrl_desc;
>> -       struct s32_pinctrl_soc_info *info;
>>  #ifdef CONFIG_PM_SLEEP
>>         struct s32_pinctrl_context *saved_context;
>>  #endif
>> +       struct gpio_regmap_config gpio_cfg = {};
>> +       struct pinctrl_desc *s32_pinctrl_desc;
>> +       struct s32_pinctrl_soc_info *info;
>> +       struct s32_pinctrl *ipctl;
>> +       unsigned int ngpio;
>> +       int ret;
>>
>>         if (!soc_data || !soc_data->pins || !soc_data->npins) {
>>                 dev_err(&pdev->dev, "wrong pinctrl info\n");
>> @@ -959,6 +1453,11 @@ int s32_pinctrl_probe(struct platform_device *pdev,
>>         INIT_LIST_HEAD(&ipctl->gpio_configs);
>>         spin_lock_init(&ipctl->gpio_configs_lock);
>>
>> +       ret = devm_add_action_or_reset(&pdev->dev,
>> +                                      s32_gpio_free_saved_configs, ipctl);
>> +       if (ret)
>> +               return ret;
>> +
>>         s32_pinctrl_desc =
>>                 devm_kzalloc(&pdev->dev, sizeof(*s32_pinctrl_desc), GFP_KERNEL);
>>         if (!s32_pinctrl_desc)
>> @@ -978,6 +1477,11 @@ int s32_pinctrl_probe(struct platform_device *pdev,
>>                 return ret;
>>         }
>>
>> +       ret = s32_pinctrl_init_gpio_regmaps(pdev, ipctl);
>> +       if (ret)
>> +               return dev_err_probe(&pdev->dev, ret,
>> +                                    "Failed to init GPIO regmaps\n");
>> +
>>         ret = devm_pinctrl_register_and_init(&pdev->dev, s32_pinctrl_desc,
>>                                              ipctl, &ipctl->pctl);
>>         if (ret)
>> @@ -999,7 +1503,42 @@ int s32_pinctrl_probe(struct platform_device *pdev,
>>                 return dev_err_probe(&pdev->dev, ret,
>>                                      "Failed to enable pinctrl\n");
>>
>> -       dev_info(&pdev->dev, "Initialized S32 pinctrl driver\n");
>> +       /* Setup GPIO if GPIO ranges are defined */
>> +       if (!soc_data->gpio_ranges || !soc_data->num_gpio_ranges)
>> +               return 0;
>> +
>> +       ret = s32_gpio_get_ngpio(soc_data, &ngpio);
>> +       if (ret)
>> +               return dev_err_probe(&pdev->dev, ret, "Invalid GPIO ranges\n");
>> +
>> +       ipctl->ngpio = ngpio;
>> +
>> +       ret = s32_gpio_populate_names(ipctl);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = s32_init_gpio_regmap(pdev, ipctl);
>> +       if (ret)
>> +               return ret;
>> +
>> +       gpio_cfg.parent = &pdev->dev;
>> +       gpio_cfg.fwnode = dev_fwnode(&pdev->dev);
>> +       gpio_cfg.label = dev_name(&pdev->dev);
>> +       gpio_cfg.regmap = ipctl->gpio_regmap;
>> +       gpio_cfg.ngpio = ngpio;
>> +       gpio_cfg.names = ipctl->gpio_names;
>> +       gpio_cfg.reg_dir_out_base = GPIO_REGMAP_ADDR(S32_GPIO_OP_DIR);
>> +       gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(S32_GPIO_OP_DAT);
>> +       gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(S32_GPIO_OP_SET);
>> +       gpio_cfg.reg_mask_xlate = s32_gpio_reg_mask_xlate;
>> +       gpio_cfg.init_valid_mask = s32_init_valid_mask;
>> +       gpio_cfg.drvdata = ipctl;
>> +
>> +       ipctl->gpio_rgm = devm_gpio_regmap_register(&pdev->dev, &gpio_cfg);
>> +       if (IS_ERR(ipctl->gpio_rgm))
>> +               return dev_err_probe(&pdev->dev,
>> +                                    PTR_ERR(ipctl->gpio_rgm),
>> +                                    "Unable to add gpio_regmap chip\n");
>>
>>         return 0;
>>  }
>> diff --git a/drivers/pinctrl/nxp/pinctrl-s32g2.c b/drivers/pinctrl/nxp/pinctrl-s32g2.c
>> index c49d28793b69..0bd6e6ab5ad1 100644
>> --- a/drivers/pinctrl/nxp/pinctrl-s32g2.c
>> +++ b/drivers/pinctrl/nxp/pinctrl-s32g2.c
>> @@ -3,7 +3,7 @@
>>   * NXP S32G pinctrl driver
>>   *
>>   * Copyright 2015-2016 Freescale Semiconductor, Inc.
>> - * Copyright 2017-2018, 2020-2022 NXP
>> + * Copyright 2017-2018, 2020-2022, 2025-2026 NXP
>>   * Copyright (C) 2022 SUSE LLC
>>   */
>>
>> @@ -773,17 +773,47 @@ static const struct s32_pin_range s32_pin_ranges_siul2[] = {
>>         S32_PIN_RANGE(942, 1007),
>>  };
>>
>> -static const struct s32_pinctrl_soc_data s32_pinctrl_data = {
>> +static const struct s32_gpio_range s32_gpio_ranges_siul2[] = {
>> +       S32_GPIO_RANGE(0, 0, 102),
>> +       S32_GPIO_RANGE(112, 112, 79),
>> +};
>> +
>> +/*
>> + * SIUL2_1 GPIO ranges mapped to sparse PGPD pads.
>> + *
>> + * SIUL2_1 does not expose GPIO data registers as a linear pad
>> + * sequence. Each entry describes a contiguous GPIO offset range
>> + * and the PGPD pad servicing that range.
>> + */
>> +static const struct s32_gpio_pad_map s32g_gpio_pad_maps[] = {
>> +       { 112, 122, 7  }, /* PH_00 .. PH_10 -> PGPD7  */
>> +       { 144, 159, 9  }, /* PJ_00 .. PJ_15 -> PGPD9  */
>> +       { 160, 175, 10 }, /* PK_00 .. PK_15 -> PGPD10 */
>> +       { 176, 190, 11 }, /* PL_00 .. PL_14 -> PGPD11 */
>> +};
>> +
>> +/* Legacy data for old DT bindings without GPIO support */
>> +static const struct s32_pinctrl_soc_data legacy_s32g_pinctrl_data = {
>> +       .pins = s32_pinctrl_pads_siul2,
>> +       .npins = ARRAY_SIZE(s32_pinctrl_pads_siul2),
>> +       .mem_pin_ranges = s32_pin_ranges_siul2,
>> +       .mem_regions = ARRAY_SIZE(s32_pin_ranges_siul2),
>> +};
>> +
>> +static const struct s32_pinctrl_soc_data s32g_pinctrl_data = {
>>         .pins = s32_pinctrl_pads_siul2,
>>         .npins = ARRAY_SIZE(s32_pinctrl_pads_siul2),
>>         .mem_pin_ranges = s32_pin_ranges_siul2,
>>         .mem_regions = ARRAY_SIZE(s32_pin_ranges_siul2),
>> +       .gpio_ranges = s32_gpio_ranges_siul2,
>> +       .num_gpio_ranges = ARRAY_SIZE(s32_gpio_ranges_siul2),
>> +       .gpio_pad_maps = s32g_gpio_pad_maps,
>> +       .num_gpio_pad_maps = ARRAY_SIZE(s32g_gpio_pad_maps),
>>  };
>>
>>  static const struct of_device_id s32_pinctrl_of_match[] = {
>>         {
>>                 .compatible = "nxp,s32g2-siul2-pinctrl",
>> -               .data = &s32_pinctrl_data,
>>         },
>>         { /* sentinel */ }
>>  };
>> @@ -792,8 +822,16 @@ MODULE_DEVICE_TABLE(of, s32_pinctrl_of_match);
>>  static int s32g_pinctrl_probe(struct platform_device *pdev)
>>  {
>>         const struct s32_pinctrl_soc_data *soc_data;
>> +       struct device_node *np = pdev->dev.of_node;
>>
>> -       soc_data = of_device_get_match_data(&pdev->dev);
>> +       /*
>> +        * Legacy DTs only describe the pinctrl resources.
>> +        * New DT changes extend the same node with GPIO resources.
>> +        */
>> +       if (of_property_present(np, "gpio-controller"))
>> +               soc_data = &s32g_pinctrl_data;
>> +       else
>> +               soc_data = &legacy_s32g_pinctrl_data;
>>
>>         return s32_pinctrl_probe(pdev, soc_data);
>>  }
>> --
>> 2.34.1
>>
> 

Hi Enric,

Thanks for your feedback!
 
I have addressed the two points from your review in
the new patch series (v11):
- `kmalloc_obj` usage is now correct in the GPIO request path
- The SIUL2_1 sparse PGPD layout is handled via a `sparse`
flag on `struct s32_gpio_range` and a pad-map-driven xlate,
removing the magic number 112 that was previously used
as a boundary check.
 
Regarding the remaining checkpatch warning:
I don t think `cfg` should be declared `const` because
it is a per-iteration copy of the `pgpd_config` template
that gets two fields written at runtime: `cfg.name` and
`cfg.max_register`(derived from the resource size).
Making the base template `pgpd_config` const (which it is)
I think is the right approach here.

Best regards,
Khristine



^ permalink raw reply

* Re: [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support
From: Wei-Lin Chang @ 2026-06-10 13:57 UTC (permalink / raw)
  To: Oliver Upton
  Cc: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm,
	Paolo Bonzini, Shuah Khan, Marc Zyngier, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama
In-Reply-To: <ioz7qsirq2v665bqbjin5ekwss46pfg2sfmndkvail2athbo75@cgt7zikhtx6r>

Hi Oliver,

On Fri, May 29, 2026 at 02:38:53PM +0100, Wei-Lin Chang wrote:
> Hi Oliver,
> 
> On Tue, May 19, 2026 at 02:31:09PM -0700, Oliver Upton wrote:
> > Hi Wei-Lin,
> > 
> > Thank you very much for the series.
> > 
> > I haven't had time to read through much of this yet, but I noticed
> > Itaru's comment about leaving stage-1 translation disabled for the L2.
> > 
> > Since selftests expects things like atomics to work we will definitely
> > need the stage-1 MMU to be enabled w/ Normal mappings (this was broken
> > in the past [*]). I wonder if we can (ab)use the pre-existing stage-1
> > tables that the selftests library creates on behalf of the L1. After all,
> > L1 and L2 are both effectively in the same virtual address space.
> > 
> > [*] https://lore.kernel.org/kvmarm/20250405001042.1470552-1-rananta@google.com/
> 
> Thanks for the pointer, I see, only inner/outer shareable, write-back
> normal memory guarantees atomics to work. Interestingly, treating the
> atomic instruction as a NOP is one of the permitted outcomes if other
> memory attributes are used...
> 
> However, the L2s in this series aren't using atomic instructions, and
> right now for the current organization of this series, the L1 guest
> hypervisor infrastructure is in lib/ for future NV selftests to share L2
> creation code, but L2 code are entirely per selftest. This means other
> selftest L2s can reuse the L1 stage-1 tables themselves if the implicit
> memory attributes that comes from MMU being off is not desired.
> 
> Sorry if that sounded defensive, I am not opposed to always reusing the
> L1 stage-1 tables in L2, just wanted to make sure the point gets across.
> 
> Always reusing L1 stage-1 tables in L2 allows L2 access to lib/ (which
> should be helpful for testing recursive NV), and L1-L2 code sharing
> intra-selftest. OTOH things are a bit simpler with MMU off in this case
> with testing the shadow stage2.
> 
> What do you think?

Gentle ping on this :)
Would love to hear what you think.

Thanks,
Wei-Lin Chang

> 
> Thanks,
> Wei-Lin Chang
> 
> > 
> > Thanks,
> > Oliver
> > 
> > On Sat, May 16, 2026 at 07:29:54PM +0100, Wei-Lin Chang wrote:
> > > Hi,
> > > 
> > > This is v3 of adding basic support for running nested guests (L2) in
> > > kselftest. This time a framework for enabling stage-2 in the guest is
> > > added, including the s2_mmu struct, s2 translation control setup, and
> > > a stage-2 page table generator. Similar to L2 vCPU management, all
> > > stage-2 work is done in the guest instead of userspace.
> > > 
> > > An additional shadow_stage2 test is added which leverages the framework
> > > to run L2 with stage-2 enabled.
> > > 
> > > * Changes from v2 [1]:
> > > 
> > >   - Update vm_paddr_t to gpa_t, vm_vaddr_t to gva_t.
> > > 
> > >   - Added framework for enabling stage-2 in the guest.
> > > 
> > >   - Added shadow_stage2 test.
> > > 
> > > Thanks!
> > > 
> > > [1]: https://lore.kernel.org/kvmarm/20260412142216.3806482-1-weilin.chang@arm.com/
> > > 
> > > Wei-Lin Chang (9):
> > >   KVM: arm64: selftests: Add GPR save/restore functions for NV
> > >   KVM: arm64: selftests: Add helpers for guest hypervisors
> > >   KVM: arm64: selftests: Add hello_nested basic NV selftest
> > >   KVM: arm64: selftests: Enhance hello_nested test
> > >   KVM: arm64: selftests: Add shadow_stage2 test
> > >   KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated
> > >     pool
> > >   KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule
> > >     size
> > >   KVM: arm64: selftests: Add infrastructure for using stage-2 in guest
> > >   KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for
> > >     the nested guest
> > > 
> > >  tools/testing/selftests/kvm/Makefile.kvm      |   5 +
> > >  .../selftests/kvm/arm64/hello_nested.c        | 134 +++++++++
> > >  .../selftests/kvm/arm64/shadow_stage2.c       | 165 +++++++++++
> > >  .../selftests/kvm/include/arm64/nested.h      |  85 ++++++
> > >  tools/testing/selftests/kvm/lib/arm64/entry.S | 137 +++++++++
> > >  .../selftests/kvm/lib/arm64/hyp-entry.S       |  77 +++++
> > >  .../testing/selftests/kvm/lib/arm64/nested.c  | 264 ++++++++++++++++++
> > >  7 files changed, 867 insertions(+)
> > >  create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
> > >  create mode 100644 tools/testing/selftests/kvm/arm64/shadow_stage2.c
> > >  create mode 100644 tools/testing/selftests/kvm/include/arm64/nested.h
> > >  create mode 100644 tools/testing/selftests/kvm/lib/arm64/entry.S
> > >  create mode 100644 tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
> > >  create mode 100644 tools/testing/selftests/kvm/lib/arm64/nested.c
> > > 
> > > -- 
> > > 2.43.0
> > > 
> > > 


^ permalink raw reply

* Re: [PATCH v2 01/16] device property: Add fwnode_graph_get_port_by_id()
From: Andy Shevchenko @ 2026-06-10 13:57 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
	Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
	Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
	linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
	linux-mediatek, linux-arm-kernel, linux-kernel,
	Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-2-wenst@chromium.org>

On Wed, Jun 10, 2026 at 04:40:35PM +0800, Chen-Yu Tsai wrote:
> In some cases the driver needs a reference to the port firmware node.
> Once such case is the upcoming USB power sequencing integration. The
> USB hub port is tied to the corresponding port firmware node if it
> exists.
> 
> Provide a helper for this.

Okay, if it's really needed.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

...

> +/**
> + * fwnode_graph_get_port_by_id - get the port matching a given id
> + * @fwnode: parent fwnode_handle containing the graph
> + * @id: id of the port
> + *
> + * Return: A 'port' firmware node pointer with refcount incremented.
> + *
> + * The caller is responsible for calling fwnode_handle_put() on the returned
> + * fwnode pointer.

Note, the Return section must be last one in the kernel-doc. The last paragraph
sounds to me as a better fit for main description. Basically check how other
kernel-doc(s) in this file are organised and follow that pattern.

> + */

-- 
With Best Regards,
Andy Shevchenko




^ permalink raw reply

* Re: spi: uniphier: KASAN wild-memory-access in complete() on early IRQ
From: Masami Hiramatsu @ 2026-06-10 13:57 UTC (permalink / raw)
  To: Jaeyoung Chung
  Cc: Mark Brown, Kunihiko Hayashi, linux-spi, linux-arm-kernel,
	linux-kernel, Sangyun Kim, Kyungwook Boo
In-Reply-To: <20260610115622.773149-1-jjy600901@snu.ac.kr>

On Wed, 10 Jun 2026 20:56:21 +0900
Jaeyoung Chung <jjy600901@snu.ac.kr> wrote:

> Hi,
> 
> uniphier_spi_probe() in drivers/spi/spi-uniphier.c registers the
> interrupt handler uniphier_spi_handler() with devm_request_irq() before
> it initializes priv->xfer_done with init_completion(). If an interrupt
> arrives after devm_request_irq() and before init_completion(), the
> handler calls complete() on an uninitialized completion, causing a
> kernel panic.
> 
> The probe path, in uniphier_spi_probe():
> 
>     host = spi_alloc_host(&pdev->dev, sizeof(*priv)); /* priv kzalloc-zeroed */
>     ...
>     ret = devm_request_irq(&pdev->dev, irq, uniphier_spi_handler,
>                            0, "uniphier-spi", priv);  /* register handler */
>     ...
>     init_completion(&priv->xfer_done);                /* initialize completion */
> 
> The interrupt handler uniphier_spi_handler() calls complete() on its
> done path:
> 
>     done:
>         complete(&priv->xfer_done);
> 
> If the device raises an interrupt before init_completion() runs,
> complete() acquires the uninitialized wait.lock and walks the zeroed
> task_list in swake_up_locked(). The zeroed task_list makes list_empty()
> return false, so swake_up_locked() dereferences a NULL list entry,
> triggering a KASAN wild-memory-access.
> 
> Suggested fix: move init_completion(&priv->xfer_done) above
> devm_request_irq(), so the completion is valid before the handler can run.
> 
> Reported-by: Sangyun Kim <sangyun.kim@snu.ac.kr>
> Reported-by: Kyungwook Boo <bookyungwook@gmail.com>
> 

Good catch! All resources used by a callback, should be initialized before
registering it.
Kinihiko, can you fix it by reordering the initialization?

Thank you, 

> Thanks,
> Jaeyoung Chung


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>


^ permalink raw reply

* Re: [PATCH v2 0/7] KVM: arm64: Forward FFA_NOTIFICATION* calls to TrustZone
From: Vincent Donnefort @ 2026-06-10 13:56 UTC (permalink / raw)
  To: Will Deacon
  Cc: Sebastian Ene, catalin.marinas, maz, oupton, joey.gouly, korneld,
	kvmarm, linux-arm-kernel, linux-kernel, android-kvm,
	mrigendra.chaubey, perlarsen, suzuki.poulose, yuzenghui
In-Reply-To: <ailXKA1VG8vyKNfV@willie-the-truck>

On Wed, Jun 10, 2026 at 01:23:04PM +0100, Will Deacon wrote:
> On Wed, Jun 10, 2026 at 01:15:44PM +0100, Vincent Donnefort wrote:
> > On Wed, Jun 10, 2026 at 11:15:14AM +0100, Will Deacon wrote:
> > > On Wed, Jun 10, 2026 at 10:26:59AM +0100, Vincent Donnefort wrote:
> > > > On Mon, Jun 08, 2026 at 04:55:42PM +0000, Sebastian Ene wrote:
> > > > > Remove the FFA_NOTIFICATION* calls from the blocklist used by the pKVM
> > > > > FF-A proxy. This restriction was preventing the use of asynchronous
> > > > > signaling mechanisms defined by the Arm FF-A specification to
> > > > > communicate with the secure services.
> > > > > While these calls are markes as optional, there is no reason why the
> > > > > hypervisor proxy would block them because:
> > > > > 
> > > > > 1. Host is the Sole Non-Secure Endpoint: The Host operates as the
> > > > >    only Non-Secure VM ID (VM ID 0) recognized by the Secure World.
> > > > >    Because all forwarded notifications are inherently attributed to
> > > > >    the Host by the SPMC, there is no risk of VM ID spoofing
> > > > >    originating from the Normal World.
> > > > > 
> > > > > 2. No Memory Pointers or Addresses: The FFA_NOTIFICATION_* ABIs
> > > > >    operate strictly via register-based parameters, passing only
> > > > >    VM IDs, VCPU IDs, flags, and bitmaps. Because these calls do
> > > > >    not contain memory addresses, offsets, or pointers, forwarding
> > > > >    them doesn't pose a risk of memory-based confused deputy attack
> > > > >    (e.g., tricking the SPMC into overwriting protected memory).
> > > > > 
> > > > > While the pKVM proxy behaves as a relayer, it doesn't currently have its
> > > > > own FF-A ID(only the host has the ID 0). The behavior of the setup
> > > > > flow is covered by the spec in the: '10.9 Notification support without
> > > > > a Hypervisor'.
> > > > 
> > > > As it is only a relayer. Is it really important to check SBZ arguments and
> > > > fields on behalf of Trustzone? It doesn't feel it brings any security. If the
> > > > host passes broken arguments, I don't believe this puts pKVM at risk. Does it? 
> > > 
> > > I think the problem would be if an update to FF-A allocated some of the
> > > currently SBZ bits to implement some functionality that we would want
> > > to filter at EL2.
> > 
> > I suppose that would bump the FF-A version and the proxy would reject it?
> 
> Maybe? I don't think they'd _have_ to bump the version number.
> 
> > If we really want to check for those arguments to be 0:
> > 
> >  * Shouldn't we extend this check to other FF-A invocations?
> 
> yes, that's what the diff was doing in the reply here:
> 
> https://lore.kernel.org/all/af3fW468-f1KXCrC@google.com/
> 
> but, as I said here:
> 
> https://lore.kernel.org/all/ahmxiFXXTupafbXw@willie-the-truck/
> 
> I don't particularly like the table-driven indirection (the checks
> should just be inlined).

Ha, sorry I'm late to the party. 

Perhaps this series should start with adding ffa_check_unused_args_sbz() to the
existing allowed FF-A invocations?

> 
> >  * Do we really want to also look into the !SBZ arguments to verify what we can?
> >    (I'm thinking about the checks on flags)
> 
> For known arguments, we only need to verify things that can affect EL2.
> I suspect we don't care about a bunch of it.
> 
> Will


^ permalink raw reply

* Re: [PATCH v2 0/7] clk: qcom: Add sane defaults and drop defconfig
From: Stephan Gerhold @ 2026-06-10 13:54 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Konrad Dybcio, linux-arm-msm, linux-clk, linux-kernel,
	linux-arm-kernel, Dmitry Baryshkov
In-Reply-To: <20260609-clk-qcom-defaults-v2-0-0c67c06dca11@oss.qualcomm.com>

On Tue, Jun 09, 2026 at 05:32:34PM +0200, Krzysztof Kozlowski wrote:
> Changes in v2:
> - Significant rework:
> - Add more commits, also for arm32 drivers
> - Split defconfig changes to separate commits, so clock can still go
>   this cycle and defconfig later. Also, less conflicts.
> - Link to v1: https://patch.msgid.link/20260416-clk-qcom-defaults-v1-0-579e75c4cfe5@oss.qualcomm.com
> - Dropped most review tags, due to changes.
> 
> We should not be really asking whether to enable clock controller
> drivers. This is obvious choice.
> 
> And if it does not seem obvious, then consider [1].
> 
> [1] https://lore.kernel.org/all/CAHk-%3Dwhigg3hvOy7c1j1MXFy6o6CHp0g4Tc3Y-MAk%2BXDssHU0A@mail.gmail.com/
> 
> If the approach is fine, I will do similarly with inteconnect and
> pinctrl (and maybe others).
> 

Perhaps we could add some option that disables the defaults for users
who need to compile a minimal kernel for space-constrained systems? All
the clock drivers combined can take up quite a bit of space because of
all the clock definitions (a quick test suggests 2.6 MiB on ARM64 for
all gcc-*.o), which can be already quite problematic e.g. for MDM*/SDX*
systems with sometimes only 256 MiB RAM or less.

The defaults applied in this patch set can be individually disabled, but
this becomes quite a mess when you really just want to have a minimal
configuration for a single SoC. Whenever a new SoC is added, you need to
go through all the menus and disable all the new options that were
added because they are going to be enabled by default.

This could be easily solved with an additional option, e.g.

config ARCH_QCOM_DEFAULT
	bool "Select important Qualcomm SoC drivers by default"
	default ARCH_QCOM

config ..._GCC_...
	default ARCH_QCOM_DEFAULT

Then you just need to disable that one option once, and all future new
options won't get enabled by default. The media subsystem has something
similar (MEDIA_SUBDRV_AUTOSELECT).

Thanks,
Stephan


^ permalink raw reply

* Re: [PATCH] KVM: arm64: vgic: Check the interrupt is still ours before migrating it
From: Hyunwoo Kim @ 2026-06-10 13:52 UTC (permalink / raw)
  To: Oliver Upton
  Cc: Marc Zyngier, joey.gouly, seiden, suzuki.poulose, yuzenghui,
	catalin.marinas, will, Sascha.Bischoff, jic23, timothy.hayes,
	andre.przywara, linux-arm-kernel, kvmarm, imv4bel
In-Reply-To: <aiKMNIUMv9GQiIbD@kernel.org>

On Fri, Jun 05, 2026 at 01:43:32AM -0700, Oliver Upton wrote:
> On Fri, Jun 05, 2026 at 08:42:52AM +0100, Marc Zyngier wrote:
> > On Fri, 05 Jun 2026 07:00:37 +0100,
> > Oliver Upton <oupton@kernel.org> wrote:
> > > 
> > > On Fri, Jun 05, 2026 at 05:59:15AM +0900, Hyunwoo Kim wrote:
> > > > vgic_prune_ap_list() drops both ap_list_lock and irq_lock while migrating
> > > > an interrupt to another vCPU. After reacquiring the locks it only checks
> > > > that the affinity is unchanged (target_vcpu == vgic_target_oracle(irq))
> > > > before moving the interrupt, which assumes that an interrupt whose affinity
> > > > is preserved is still queued on this vCPU's ap_list.
> > > > 
> > > > That assumption no longer holds if the interrupt is taken off the ap_list
> > > > while the locks are dropped. vgic_flush_pending_lpis() removes the
> > > > interrupt from the list and sets irq->vcpu to NULL, but leaves
> > > > enabled/pending/target_vcpu untouched. As the interrupt is still enabled
> > > > and pending, vgic_target_oracle() returns the same target_vcpu, so the
> > > > affinity check passes and list_del() is run a second time on an entry that
> > > > has already been removed.
> > > > 
> > > > Also check that the interrupt is still assigned to this vCPU
> > > > (irq->vcpu == vcpu) before moving it.
> > > > 
> > > > Fixes: 0919e84c0fc1 ("KVM: arm/arm64: vgic-new: Add IRQ sync/flush framework")
> > > > Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
> > > 
> > > Looking at this and the other VGIC patch you sent (which should've been
> > > a combined series), are you trying to deal with a vCPU writing to
> > > another vCPU's redistributor? I.e. vCPU B setting GICR_CTLR.EnableLPIs=0
> > > behind the back of vCPU A?
> > > 
> > > That is extremely relevant information as the off-the-cuff reaction is
> > > that no race exists. But since the GIC architecture is awesome and
> > > allows for this sort of insanity, it obviously does....
> > > 
> > > Anyway, for LPIs resident on a particular RD, there's zero expectation
> > > that the pending state is preserved when EnableLPIs=0. So I'd rather
> > > vgic_flush_pending_lpis() just invalidate the pending state.
> > 
> > Just clearing the pending state introduces a potential problem as we
> > now have an interrupt that is neither active nor pending on the AP
> > list. It is not impossible to solve (we now have similar behaviours
> > with SPI deactivation from another vcpu), but that requires posting a
> > KVM_REQ_VGIC_PROCESS_UPDATE to the target vcpu.
> 
> Right, I was suggesting that in addition to deleting the LPI from the AP
> list we actually invalidate the pending state so that someone sitting on
> a pointer to a to-be-freed LPI sees vgic_target_oracle() returning
> NULL
> 
> > > Beyond that, I see two other fixes for lifetime issues around the
> > > vgic_irq in the middle of migration. I'd like to see explicit RCU
> > > protection around the release && reacquire of the ap_list_lock rather
> > > than depending on the precondition that IRQs are disabled.
> > 
> > I'm not sure I follow. Are you suggesting turning the AP list into an
> > RCU protected list?
> 
> No, sorry, I should expand a little.
> 
> We store a reference on the vgic_irq struct in the AP list, which is
> stable so long as the ap_list_lock is held. It should be possible for
> the refcount to drop to 0 between releasing the ap_list_lock and
> reacquiring it.
> 
> So either vgic_prune_ap_list() takes an additional reference on the
> vgic_irq before dropping the ap_list_lock or rely on RCU to protect
> vgic_irq structs observed with a non-zero refcount.

What are your thoughts on this approach?


Best regards,
Hyunwoo Kim

---

diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 933983bb2005..7fb871c3ccd8 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -523,7 +523,7 @@ static void __kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
 	 * Retire all pending LPIs on this vcpu anyway as we're
 	 * going to destroy it.
 	 */
-	vgic_flush_pending_lpis(vcpu);
+	vgic_flush_pending_lpis(vcpu, true);

 	INIT_LIST_HEAD(&vgic_cpu->ap_list_head);
 	kfree(vgic_cpu->private_irqs);
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 5913a20d8301..f85d63f17af0 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -303,7 +303,7 @@ static void vgic_mmio_write_v3r_ctlr(struct kvm_vcpu *vcpu,
 		if (ctlr != GICR_CTLR_ENABLE_LPIS)
 			return;

-		vgic_flush_pending_lpis(vcpu);
+		vgic_flush_pending_lpis(vcpu, false);
 		vgic_its_invalidate_all_caches(vcpu->kvm);
 		atomic_set_release(&vgic_cpu->ctlr, 0);
 	} else {
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 1e9fe8764584..09629a38fc0a 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -192,7 +192,7 @@ static void vgic_release_deleted_lpis(struct kvm *kvm)
 	xa_unlock_irqrestore(&dist->lpi_xa, flags);
 }

-void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
+void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu, bool destroy)
 {
 	struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
 	struct vgic_irq *irq, *tmp;
@@ -204,6 +204,13 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
 	list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) {
 		if (irq_is_lpi(vcpu->kvm, irq->intid)) {
 			raw_spin_lock(&irq->irq_lock);
+			/* Leave interrupts pending a migration for prune. */
+			if (!destroy && irq->vcpu != vgic_target_oracle(irq)) {
+				raw_spin_unlock(&irq->irq_lock);
+				continue;
+			}
+			/* Pending state is not preserved across EnableLPIs=0. */
+			irq->pending_latch = false;
 			list_del(&irq->ap_list);
 			irq->vcpu = NULL;
 			raw_spin_unlock(&irq->irq_lock);
@@ -797,6 +804,9 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu)

 		/* This interrupt looks like it has to be migrated. */

+		/* Keep the interrupt alive while the locks are dropped. */
+		vgic_get_irq_ref(irq);
+
 		raw_spin_unlock(&irq->irq_lock);
 		raw_spin_unlock(&vgic_cpu->ap_list_lock);

@@ -839,6 +849,8 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu)
 		raw_spin_unlock(&vcpuB->arch.vgic_cpu.ap_list_lock);
 		raw_spin_unlock(&vcpuA->arch.vgic_cpu.ap_list_lock);

+		deleted_lpis |= vgic_put_irq_norelease(vcpu->kvm, irq);
+
 		if (target_vcpu_needs_kick) {
 			kvm_make_request(KVM_REQ_IRQ_PENDING, target_vcpu);
 			kvm_vcpu_kick(target_vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 9d941241c8a2..c1ac24ede899 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -341,7 +341,7 @@ void vgic_v3_put(struct kvm_vcpu *vcpu);
 bool vgic_has_its(struct kvm *kvm);
 int kvm_vgic_register_its_device(void);
 void vgic_enable_lpis(struct kvm_vcpu *vcpu);
-void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu);
+void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu, bool destroy);
 int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi);
 int vgic_v3_has_attr_regs(struct kvm_device *dev, struct kvm_device_attr *attr);
 int vgic_v3_dist_uaccess(struct kvm_vcpu *vcpu, bool is_write,


^ permalink raw reply related

* Re: (subset) [PATCH V3 0/8] PCI: imx6: Integrate pwrctrl API and update device trees
From: Manivannan Sadhasivam @ 2026-06-10 13:48 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
	lpieralisi, kwilczynski, bhelgaas, hongxing.zhu, l.stach,
	Sherry Sun (OSS)
  Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
	sherry.sun
In-Reply-To: <20260520084904.2424253-1-sherry.sun@oss.nxp.com>


On Wed, 20 May 2026 16:48:56 +0800, Sherry Sun (OSS) wrote:
> From: Sherry Sun <sherry.sun@nxp.com>
> 
> This series integrates the PCI pwrctrl framework into the pci-imx6
> driver and updates i.MX EVK board device trees to support it.
> 
> Patches 2-8 update device trees for i.MX EVK boards which maintained
> by NXP to move power supply properties from the PCIe controller node
> to the Root Port child node, which is required for pwrctrl framework.
> Affected boards:
> - i.MX6Q/DL SABRESD
> - i.MX6SX SDB
> - i.MX8MM EVK
> - i.MX8MP EVK
> - i.MX8MQ EVK
> - i.MX8DXL/QM/QXP EVK
> - i.MX95 15x15/19x19 EVK
> 
> [...]

Applied, thanks!

[1/8] PCI: imx6: Integrate new pwrctrl API for pci-imx6
      commit: 85c1fcfa740d4c737f5575fc7251883e54227a51

Best regards,
-- 
Manivannan Sadhasivam <mani@kernel.org>



^ permalink raw reply

* Re: [RFC PATCH v3 0/9] accel: rocket: Add RK3568 NPU support
From: Midgy Balon @ 2026-06-10 13:38 UTC (permalink / raw)
  To: Diederik de Haas
  Cc: Chaoyi Chen, tomeu, ogabbay, heiko, robh, krzk+dt, conor+dt, joro,
	will, robin.murphy, dri-devel, linux-rockchip, devicetree,
	linux-arm-kernel, iommu, linux-kernel, Simon Xue, Finley Xiao,
	Jonas Karlman
In-Reply-To: <DJ5A9XL183RZ.20J3ASQ3EWUZ6@cknow-tech.com>

Hello Chaoyi & Diederik,

I compared the RK3568 and RK3588 NPU power-domain + DTS as you
suggested, and it lines up
exactly with what you described.

The difference is the `need_regulator` capability. RK3588's NPU domain is
`DOMAIN_RK3588("npu", …, false, true)` — the trailing `true` is
`regulator`/`need_regulator`.
The mainline RK3568 macro `DOMAIN_RK3568(name, pwr, req, wakeup)` has
no regulator parameter at
all, so `RK3568_PD_NPU` can't be marked need_regulator. My v4 adds
that: a regulator-capable
RK3568 NPU domain (need_regulator = true) plus `domain-supply =
<&vdd_npu>` on the NPU node —
i.e. the same shape as RK3588.

And the fix you referenced (Frank Zhang's "pmdomain: rockchip: Fix init genpd as
GENPD_STATE_ON before regulator ready", plus "quiet regulator error on
-EPROBE_DEFER") is
already in my base (v7.1-rc6), so the `if (need_regulator)
rockchip_pd_power(pd, false)`
default-off path is in effect. That's what resolves the actual problem
for me: with rocket
built as a module (the normal config), need_regulator on the NPU
domain, and those pmdomain
patches in place, the board boots cleanly and NPU jobs run with no RCU
stall / no deadlock. My
earlier hang was an artifact of a self-contained rocket=y image
probing in the initcalls before
the I2C regulator core was up — as a module it loads ~6.8 s in, well
after, so it's gone.

I also went back and checked the `fw_devlink=permissive` question
myself — and good news, it
turns out it is NOT needed. I rebooted the exact same kernel with
permissive removed from the
cmdline (strict fw_devlink, the default), and the board boots cleanly,
the NPU probes
(`rocket fde40000.npu: Rockchip NPU core 0 version: 0`), and NPU jobs
submit and run five times
in a row with no deadlock and no RCU stall. So strict fw_devlink
resolves the NPU/PMIC ordering
fine via deferred probe.

The one remaining thing is cosmetic: at power-domain-controller probe
(~2.94 s) I still get,
in BOTH modes (with or without permissive):

  rockchip-pm-domain …: Failed to create device link (0x180) with
supplier 0-0020 …power-domain@6

i.e. genpd can't form the link to the rk809 (the I2C PMIC supplying
vdd_npu) because the PMIC
isn't registered yet at that point. It's non-fatal — the domain
defaults off (Frank's patch),
the rail comes up via the regulator core, the NPU probes a few seconds
later, and all jobs run.

One question: on RK3588 with need_regulator, do you also see that
"Failed to create device
link … supplier <pmic>" line at pmdomain probe, or does it order
cleanly? If RK3588 is clean,
is there a DTS detail (e.g. the regulator's bus/probe order) I should
mirror on RK3568 to make
the link form in time — or is this line just expected/harmless and
best left as-is?

@Diederik — thanks; the DCDC_REG2 change and Jonas's USB-suspend
series look like generally
useful RK356x robustness fixes, though for this specific NPU
device-link the need_regulator +
Frank's pmdomain patches seem to be the relevant piece. I'll keep them
in mind for suspend.

The convolution-output / compute-completion issue is still separate
and open (@Finley — that's
the PVTPLL/NoC one); the power-domain side is in good shape for v4.

Thanks y'all for your help :)

Kind regards,
Midgy

Le mer. 10 juin 2026 à 12:05, Diederik de Haas
<diederik@cknow-tech.com> a écrit :
>
> Hi,
>
> On Wed Jun 10, 2026 at 3:14 AM CEST, Chaoyi Chen wrote:
> > Hi Midgy,
> >
> > On 6/9/2026 7:11 PM, Midgy Balon wrote:
> >> Hello Chaoyi,
> >>
> >> You were right - building rocket as a module fixes it. Thanks for the pointer.
> >>
> >> I rebuilt with CONFIG_DRM_ACCEL_ROCKET=m (everything else the same:
> >> need_regulator on
> >> the RK3568 NPU power domain via a DOMAIN_M_R variant, domain-supply =
> >> <&vdd_npu>, and the
> >> regulator-always-on workaround dropped). The board now boots cleanly
> >> and, more importantly,
> >> an NPU job submit no longer hangs: I ran the test workload five times
> >> with no RCU stall and
> >> no freeze.
> >>
> >> So with rocket=m the need_regulator approach works on RK3568, and I'll
> >> keep it for v4
> >> (domain-supply + need_regulator, instead of marking vdd_npu
> >> always-on). rocket=m is the
> >> normal configuration anyway; my earlier hang came from building it =y
> >> in a self-contained
> >> image, so it probed in the initcalls (around 2 s) and the genpd ->
> >> I2C-PMIC regulator
> >> transition ran before the system was ready. As a module it loads from
> >> udev much later
> >> (~6.8 s here), after the I2C controller and regulator core are fully up.
> >>
> >> On your question of when the device-link error is printed - it is at
> >> power-domain
> >> controller probe, not at the rocket probe:
> >>
> >>   [    2.700618] vdd_npu: Bringing 500000uV into 825000-825000uV
> >>   [    2.749637] rockchip-pm-domain fdd90000.power-management:power-controller:
> >>                  Failed to create device link (0x180) with supplier 0-0020 for
> >>                  /power-management@fdd90000/power-controller/power-domain@6
> >>   [    2.945955] platform fde40000.npu: Adding to iommu group 3
> >>   ...
> >>   [    6.840374] rocket: loading out-of-tree module taints kernel.
> >>   [    6.877647] [drm] Initialized rocket 0.0.0 for rknn on minor 0
> >>   [    6.879950] rocket fde40000.npu: Rockchip NPU core 0 version: 0
> >>
> >> So the device-link to the rk809 PMIC (0-0020) fails to form at ~2.75
> >> s, well before rocket
> >> loads at ~6.8 s. It is non-fatal here - the vdd_npu rail is brought up
> >> by the regulator core
> >> and all jobs run - and there is no "failed to get ack on domain npu"
> >> NoC warning this boot
> >> (the always-on kernel had one). The complete boot log is attached.
> >>
> >> Two notes / one question:
> >> - This boot used fw_devlink=permissive on the command line. Is the
> >> "Failed to create device
> >>   link ... supplier 0-0020" at pmdomain probe expected/benign, or is
> >> there a clean way to make
> >>   it order correctly (so it also works without permissive, and a =y
> >> build wouldn't deadlock in
> >>   the initcalls)?
> >
> > We encountered the same issue on the RK3588 NPU before. And it was
> > resolved with the following patch at that time.
> >
> > https://lore.kernel.org/all/20251216055247.13150-1-rmxpzlb@gmail.com/
> >
> > Please compare the differences in NPU pmdomain and DTS configuration
> > between the RK3568 and RK3588.
>
> About a month ago on #linux-rockchip we were discussing PM 'stuff':
> https://libera.catirclogs.org/linux-rockchip/2026-05-15#39939137;
> which references this paste
> https://paste.sr.ht/~diederik/89d9f84e22474e837b55286d213b67f03859ce2e
> I've since removed the DCDC_REG2 for PineTab2 and the 'fix' should likely
> be extended to cover all RK3566/RK3568 devices though.
>
> It's what I made at the time hoping to fix a suspend/resume issue when
> trying upstream TF-A. It didn't fix the issue at the time, but may still
> be useful/needed and I think it's what Chaoyi hinted at.
>
> Just yesterday, Jonas posted this patch which may be useful/needed too:
> https://lore.kernel.org/linux-rockchip/20260609154124.445182-1-jonas@kwiboo.se/
>
> HTH,
>   Diederik
>
> >> - (The convolution output is still uniform zero-point / the job times
> >> out - that is the
> >>   separate NPU compute-completion issue, unrelated to the power-domain
> >> work. Finley, that is
> >>   the one I flagged earlier re PVTPLL/NoC.)
> >>
> >> Kind regards,
> >> Midgy
> >>
>


^ permalink raw reply

* Re: [PATCH 3/3] soc: samsung: exynos-pmu: fix error paths in cpuhotplug/idle states setup
From: Peter Griffin @ 2026-06-10 13:34 UTC (permalink / raw)
  To: Alexey Klimov
  Cc: Krzysztof Kozlowski, Alim Akhtar, Sam Protsenko,
	linux-samsung-soc, linux-arm-kernel, linux-kernel, stable,
	Sashiko
In-Reply-To: <20260605-exynos-pmu-cpuhp-idle-fixes-v1-3-0cd05c81a82d@linaro.org>

Hi Alexey,

Thanks for your patch!

On Fri, 5 Jun 2026 at 21:19, Alexey Klimov <alexey.klimov@linaro.org> wrote:
>
> The setup_cpuhp_and_cpuidle() initialisation sequence currently ignores
> the return values of cpuhp_setup_state(), cpu_pm_register_notifier(), and
> register_reboot_notifier(). If any of these registrations fail during
> probe() routine, the driver returns 0, leaving the driver partially
> configured.

I originally made the failure non-fatal because the system still boots
without the notifiers registered (and all other Arm64 Exynos SoCs
upstream don't register notifiers and AFAICT have broken cpu hotplug
and cpu idle).

In hindsight, that seems like a mistake. I think your patch to fully
unwind everything in case of failure makes more sense.  See small
comment below about destroy_cpuhp_and_cpuidle()

>
> Furthermore, if anything after setup_cpuhp_and_cpuidle() fails in probe()
> routine, for instance devm_mfd_add_devices(), the probe() lacks an error
> path and leaves notifiers and cpu hotplug states registered.
>
> Introduce variables for the cpu hotplug state IDs in exynos_pmu_context
> struct, that should be initialised to CPUHP_INVALID by default. Check all
> return codes in setup_cpuhp_and_cpuidle(), and add an error path to remove
> registered states on failure. Finally, add destroy_cpuhp_and_cpuidle()
> helper to safely tear down notifiers and cpu hotplug states.
>
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://sashiko.dev/#/patchset/20260513-exynos850-cpuhotplug-v4-0-54fec5f65362@linaro.org?part=3
> Fixes: 78b72897a5c8 ("soc: samsung: exynos-pmu: Enable CPU Idle for gs101")
> Cc: stable@vger.kernel.org
> Signed-off-by: Alexey Klimov <alexey.klimov@linaro.org>
> ---
>  drivers/soc/samsung/exynos-pmu.c | 57 ++++++++++++++++++++++++++++++++++------
>  1 file changed, 49 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c
> index 9636287f6794..846313a28e9a 100644
> --- a/drivers/soc/samsung/exynos-pmu.c
> +++ b/drivers/soc/samsung/exynos-pmu.c
> @@ -38,6 +38,8 @@ struct exynos_pmu_context {
>         unsigned long *in_cpuhp;
>         bool sys_insuspend;
>         bool sys_inreboot;
> +       int cpuhp_prepare_state;
> +       int cpuhp_online_state;
>  };
>
>  void __iomem *pmu_base_addr;
> @@ -404,6 +406,17 @@ static struct notifier_block exynos_cpupm_reboot_nb = {
>         .notifier_call = exynos_cpupm_reboot_notifier,
>  };
>
> +static void destroy_cpuhp_and_cpuidle(void)
> +{
> +       cpu_pm_unregister_notifier(&gs101_cpu_pm_notifier);
> +       unregister_reboot_notifier(&exynos_cpupm_reboot_nb);
> +
> +       if (pmu_context->cpuhp_prepare_state != CPUHP_INVALID)
> +               cpuhp_remove_state(pmu_context->cpuhp_prepare_state);
> +       if (pmu_context->cpuhp_online_state != CPUHP_INVALID)
> +               cpuhp_remove_state(pmu_context->cpuhp_online_state);
> +}
> +
>  static int setup_cpuhp_and_cpuidle(struct device *dev)
>  {
>         struct device_node *intr_gen_node;
> @@ -465,16 +478,42 @@ static int setup_cpuhp_and_cpuidle(struct device *dev)
>                 gs101_cpuhp_pmu_online(cpu);
>
>         /* register CPU hotplug callbacks */
> -       cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "soc/exynos-pmu:prepare",
> -                         gs101_cpuhp_pmu_online, NULL);
> +       pmu_context->cpuhp_prepare_state = CPUHP_INVALID;
> +       pmu_context->cpuhp_online_state = CPUHP_INVALID;
>
> -       cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/exynos-pmu:online",
> -                         NULL, gs101_cpuhp_pmu_offline);
> +       ret = cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "soc/exynos-pmu:prepare",
> +                               gs101_cpuhp_pmu_online, NULL);
> +       if (ret < 0)
> +               return ret;
> +
> +       pmu_context->cpuhp_prepare_state = ret;
> +
> +       ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/exynos-pmu:online",
> +                               NULL, gs101_cpuhp_pmu_offline);
> +       if (ret < 0)
> +               goto clean_cpuhp_states;
> +
> +       pmu_context->cpuhp_online_state = ret;
>
>         /* register CPU PM notifiers for cpuidle */
> -       cpu_pm_register_notifier(&gs101_cpu_pm_notifier);
> -       register_reboot_notifier(&exynos_cpupm_reboot_nb);
> -       return 0;
> +       ret = cpu_pm_register_notifier(&gs101_cpu_pm_notifier);
> +       if (ret)
> +               goto clean_cpuhp_states;
> +
> +       ret = register_reboot_notifier(&exynos_cpupm_reboot_nb);
> +       if (!ret)
> +               /* Success */
> +               return ret;
> +
> +       cpu_pm_unregister_notifier(&gs101_cpu_pm_notifier);
> +
> +clean_cpuhp_states:
> +       if (pmu_context->cpuhp_prepare_state != CPUHP_INVALID)
> +               cpuhp_remove_state(pmu_context->cpuhp_prepare_state);
> +       if (pmu_context->cpuhp_online_state != CPUHP_INVALID)
> +               cpuhp_remove_state(pmu_context->cpuhp_online_state);
> +
> +       return ret;
>  }
>
>  static int exynos_pmu_probe(struct platform_device *pdev)
> @@ -548,8 +587,10 @@ static int exynos_pmu_probe(struct platform_device *pdev)
>
>         ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, exynos_pmu_devs,
>                                    ARRAY_SIZE(exynos_pmu_devs), NULL, 0, NULL);
> -       if (ret)
> +       if (ret) {
> +               destroy_cpuhp_and_cpuidle();

You only want to do this if pmu_cpuhp == true, as currently only gs101
registers the notifiers.

Thanks,

Peter


^ permalink raw reply

* [PATCH net-next v4 2/2] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
From: Lorenzo Bianconi @ 2026-06-10 13:33 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal
In-Reply-To: <20260610-airoha-ethtool-priv_flags-v4-0-60e89cf28fea@kernel.org>

GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware
QoS offload to function. Without it, HTB and ETS offload on these ports
do not work.
Previously, GDM3/GDM4 ports were automatically configured as WAN with
GDM2 loopback enabled during ndo_init(). Add the capability to configure
GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed.
Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting
HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables
GDM2 loopback, with proper rollback on failure. Hook the counterpart
airoha_disable_qos_for_gdm34() into TC_HTB_DESTROY to restore LAN mode
when the offloaded qdisc is torn down.
Since airoha_dev_set_qdma() can now be called on a running device to
migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX
path can safely dereference it without holding RTNL.
Hold flow_offload_mutex in airoha_dev_set_qdma() around the QDMA pointer
update and __airoha_ppe_set_cpu_port() call, serializing against
concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER offload path.
Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected()
with a lockdep condition accepting either rtnl_lock or flow_offload_mutex,
and use it across all control-path dereferences of the RCU-protected
dev->qdma pointer.
Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback.

Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c  | 220 ++++++++++++++++++++++++++----
 drivers/net/ethernet/airoha/airoha_eth.h  |  24 +++-
 drivers/net/ethernet/airoha/airoha_ppe.c  |  18 ++-
 drivers/net/ethernet/airoha/airoha_regs.h |   1 +
 4 files changed, 231 insertions(+), 32 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index aeac66df5f3b..10232470a333 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -921,9 +921,11 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
 			if (!dev)
 				continue;
 
-			if (dev->qdma != qdma)
-				continue;
-
+			/* Do not filter by dev->qdma here: the device
+			 * may have migrated to a different QDMA block
+			 * since the packet was submitted, so completions
+			 * can arrive on the old block.
+			 */
 			netdev = netdev_from_priv(dev);
 			for (j = 0; j < netdev->num_tx_queues; j++) {
 				if (airoha_qdma_get_txq(qdma, j) != qid)
@@ -1811,13 +1813,14 @@ static int airoha_dev_open(struct net_device *netdev)
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
 	struct airoha_gdm_port *port = dev->port;
 	u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
-	struct airoha_qdma *qdma = dev->qdma;
+	struct airoha_qdma *qdma;
 
 	netif_tx_start_all_queues(netdev);
 	err = airoha_set_vip_for_gdm_port(dev, true);
 	if (err)
 		return err;
 
+	qdma = airoha_qdma_deref(dev);
 	if (netdev_uses_dsa(netdev))
 		airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
 			      GDM_STAG_EN_MASK);
@@ -1879,7 +1882,7 @@ static int airoha_dev_stop(struct net_device *netdev)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
 	struct airoha_gdm_port *port = dev->port;
-	struct airoha_qdma *qdma = dev->qdma;
+	struct airoha_qdma *qdma;
 	int i;
 
 	netif_tx_disable(netdev);
@@ -1887,6 +1890,7 @@ static int airoha_dev_stop(struct net_device *netdev)
 	for (i = 0; i < netdev->num_tx_queues; i++)
 		netdev_tx_reset_subqueue(netdev, i);
 
+	qdma = airoha_qdma_deref(dev);
 	if (--port->users)
 		airoha_set_port_mtu(dev->eth, port);
 	else
@@ -1979,6 +1983,52 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
 	return 0;
 }
 
+static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
+{
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
+	int i, src_port;
+	u32 pse_port;
+
+	src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
+	if (src_port < 0)
+		return src_port;
+
+	airoha_fe_clear(eth,
+			REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
+			SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
+
+	airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+		      GDM_STRIP_CRC_MASK);
+	airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+				    FE_PSE_PORT_DROP);
+	airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
+			LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
+	pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
+						 : FE_PSE_PORT_PPE1;
+	airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+				    pse_port);
+
+	airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
+		      FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
+
+	for (i = 0; i < eth->soc->num_ppe; i++)
+		airoha_ppe_clear_cpu_port(dev, i, AIROHA_GDM2_IDX);
+
+	/* Enable VIP and IFC for GDM2 */
+	airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
+	airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
+
+	if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+		u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
+
+		airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
+			      FC_MAP6_DEF_VALUE & mask);
+	}
+
+	return 0;
+}
+
 static struct airoha_gdm_dev *
 airoha_get_wan_gdm_dev(struct airoha_eth *eth)
 {
@@ -2005,15 +2055,36 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth)
 static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
 {
 	struct net_device *netdev = netdev_from_priv(dev);
+	struct airoha_qdma *cur_qdma, *qdma;
 	struct airoha_eth *eth = dev->eth;
 	int ppe_id;
 
 	/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
-	dev->qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
-	netdev->irq = dev->qdma->irq_banks[0].irq;
+	qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
+	cur_qdma = airoha_qdma_deref(dev);
+	if (netif_running(netdev))
+		airoha_qdma_start(qdma);
+
+	/* Serialize QDMA pointer and PPE CPU port configuration against
+	 * concurrent airoha_ppe_hw_init() in airoha_ppe_setup_tc_block_cb().
+	 */
+	mutex_lock(&flow_offload_mutex);
+
+	rcu_assign_pointer(dev->qdma, qdma);
+	netdev->irq = qdma->irq_banks[0].irq;
 
 	ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
-	airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
+	__airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
+
+	mutex_unlock(&flow_offload_mutex);
+
+	if (!cur_qdma)
+		return;
+
+	synchronize_rcu();
+
+	if (netif_running(netdev))
+		airoha_qdma_stop(cur_qdma);
 }
 
 static int airoha_dev_init(struct net_device *netdev)
@@ -2177,9 +2248,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 				   struct net_device *netdev)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_qdma *qdma = dev->qdma;
 	u32 nr_frags, tag, msg0, msg1, len;
 	struct airoha_queue_entry *e;
+	struct airoha_qdma *qdma;
 	struct netdev_queue *txq;
 	struct airoha_queue *q;
 	LIST_HEAD(tx_list);
@@ -2188,6 +2259,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 	u16 index;
 	u8 fport;
 
+	rcu_read_lock();
+	qdma = rcu_dereference(dev->qdma);
 	qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
 	tag = airoha_get_dsa_tag(skb, netdev);
 
@@ -2234,6 +2307,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 		netif_tx_stop_queue(txq);
 		q->txq_stopped = true;
 		spin_unlock_bh(&q->lock);
+		rcu_read_unlock();
+
 		return NETDEV_TX_BUSY;
 	}
 
@@ -2296,6 +2371,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 				FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
 
 	spin_unlock_bh(&q->lock);
+	rcu_read_unlock();
 
 	return NETDEV_TX_OK;
 
@@ -2311,6 +2387,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 error:
 	dev_kfree_skb_any(skb);
 	netdev->stats.tx_dropped++;
+	rcu_read_unlock();
 
 	return NETDEV_TX_OK;
 }
@@ -2392,17 +2469,19 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
 					 const u16 *weights, u8 n_weights)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_qdma *qdma;
 	int i;
 
+	qdma = airoha_qdma_deref(dev);
 	for (i = 0; i < AIROHA_NUM_TX_RING; i++)
-		airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
+		airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel),
 				  TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
 
 	for (i = 0; i < n_weights; i++) {
 		u32 status;
 		int err;
 
-		airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
+		airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG,
 			       TWRR_RW_CMD_MASK |
 			       FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
 			       FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
@@ -2410,12 +2489,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
 		err = read_poll_timeout(airoha_qdma_rr, status,
 					status & TWRR_RW_CMD_DONE,
 					USEC_PER_MSEC, 10 * USEC_PER_MSEC,
-					true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
+					true, qdma, REG_TXWRR_WEIGHT_CFG);
 		if (err)
 			return err;
 	}
 
-	airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+	airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3),
 			CHAN_QOS_MODE_MASK(channel),
 			__field_prep(CHAN_QOS_MODE_MASK(channel), mode));
 
@@ -2479,13 +2558,15 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
 					struct tc_ets_qopt_offload *opt)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_qdma *qdma = dev->qdma;
+	u64 cpu_tx_packets, fwd_tx_packets, tx_packets;
+	struct airoha_qdma *qdma;
 
-	u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
-	u64 fwd_tx_packets = airoha_qdma_rr(qdma,
-					    REG_CNTR_VAL((channel << 1) + 1));
-	u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
-			 (fwd_tx_packets - dev->fwd_tx_packets);
+	qdma = airoha_qdma_deref(dev);
+	cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
+	fwd_tx_packets = airoha_qdma_rr(qdma,
+					REG_CNTR_VAL((channel << 1) + 1));
+	tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
+		     (fwd_tx_packets - dev->fwd_tx_packets);
 
 	_bstats_update(opt->stats.bstats, 0, tx_packets);
 	dev->cpu_tx_packets = cpu_tx_packets;
@@ -2745,16 +2826,18 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
 					 u32 bucket_size)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_qdma *qdma;
 	int i, err;
 
+	qdma = airoha_qdma_deref(dev);
 	for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
-		err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
+		err = airoha_qdma_set_trtcm_config(qdma, channel,
 						   REG_EGRESS_TRTCM_CFG, i,
 						   !!rate, TRTCM_METER_MODE);
 		if (err)
 			return err;
 
-		err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
+		err = airoha_qdma_set_trtcm_token_bucket(qdma, channel,
 							 REG_EGRESS_TRTCM_CFG,
 							 i, rate, bucket_size);
 		if (err)
@@ -2790,11 +2873,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
 	int err, num_tx_queues = netdev->real_num_tx_queues;
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_qdma *qdma = dev->qdma;
+	struct airoha_qdma *qdma;
 
 	/* Here we need to check the requested QDMA channel is not already
 	 * in use by another net_device running on the same QDMA block.
 	 */
+	qdma = airoha_qdma_deref(dev);
 	if (test_and_set_bit(channel, qdma->qos_channel_map)) {
 		NL_SET_ERR_MSG_MOD(opt->extack,
 				   "qdma qos channel already in use");
@@ -2828,7 +2912,7 @@ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
 				    u32 rate, u32 bucket_size,
 				    enum trtcm_unit_type unit_type)
 {
-	struct airoha_qdma *qdma = dev->qdma;
+	struct airoha_qdma *qdma = airoha_qdma_deref(dev);
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
@@ -3002,11 +3086,12 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
 static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_qdma *qdma = dev->qdma;
+	struct airoha_qdma *qdma;
 
 	netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
 	airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
 
+	qdma = airoha_qdma_deref(dev);
 	clear_bit(queue, qdma->qos_channel_map);
 	clear_bit(queue, dev->qos_sq_bmap);
 }
@@ -3027,6 +3112,89 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
 	return 0;
 }
 
+static int airoha_enable_qos_for_gdm34(struct net_device *netdev,
+				       struct netlink_ext_ack *extack)
+{
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
+	int err;
+
+	if (port->id != AIROHA_GDM3_IDX &&
+	    port->id != AIROHA_GDM4_IDX) {
+		/* HW QoS is always supported by GDM1 and GDM2 */
+		return 0;
+	}
+
+	if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */
+		return 0;
+
+	/* Verify the WAN device is not already configured */
+	if (airoha_get_wan_gdm_dev(eth)) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "WAN device already configured");
+		return -EBUSY;
+	}
+
+	dev->flags |= AIROHA_PRIV_F_WAN;
+	airoha_dev_set_qdma(dev);
+	err = airoha_enable_gdm2_loopback(dev);
+	if (err)
+		goto error_disable_wan;
+
+	err = airoha_set_macaddr(dev, netdev->dev_addr);
+	if (err)
+		goto error_disable_loopback;
+
+	if (netif_running(netdev)) {
+		u32 pse_port;
+
+		pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
+							 : FE_PSE_PORT_PPE1;
+		airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
+					    pse_port);
+	}
+
+	return 0;
+
+error_disable_loopback:
+	/* Restore previous LAN configuration */
+	airoha_disable_gdm2_loopback(dev);
+error_disable_wan:
+	dev->flags &= ~AIROHA_PRIV_F_WAN;
+	airoha_dev_set_qdma(dev);
+
+	return err;
+}
+
+static void airoha_disable_qos_for_gdm34(struct net_device *netdev)
+{
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
+	int err;
+
+	if (port->id != AIROHA_GDM3_IDX &&
+	    port->id != AIROHA_GDM4_IDX) {
+		return;
+	}
+
+	if (airoha_is_lan_gdm_dev(dev)) /* Already disabled */
+		return;
+
+	err = airoha_disable_gdm2_loopback(dev);
+	if (err)
+		netdev_warn(netdev,
+			    "failed disabling GDM2 loopback: %d\n", err);
+
+	dev->flags &= ~AIROHA_PRIV_F_WAN;
+	airoha_dev_set_qdma(dev);
+	airoha_set_macaddr(dev, netdev->dev_addr);
+	if (netif_running(netdev))
+		airoha_set_gdm_port_fwd_cfg(dev->eth,
+					    REG_GDM_FWD_CFG(port->id),
+					    FE_PSE_PORT_PPE1);
+}
+
 static int airoha_tc_htb_destroy(struct net_device *netdev)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
@@ -3035,6 +3203,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev)
 	for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
 		airoha_tc_remove_htb_queue(netdev, q);
 
+	airoha_disable_qos_for_gdm34(netdev);
+
 	return 0;
 }
 
@@ -3059,7 +3229,7 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
 {
 	switch (opt->command) {
 	case TC_HTB_CREATE:
-		break;
+		return airoha_enable_qos_for_gdm34(dev, opt->extack);
 	case TC_HTB_DESTROY:
 		return airoha_tc_htb_destroy(dev);
 	case TC_HTB_NODE_MODIFY:
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 8f42973f9cf5..8795af0010b6 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -543,8 +543,8 @@ enum airoha_priv_flags {
 };
 
 struct airoha_gdm_dev {
+	struct airoha_qdma __rcu *qdma;
 	struct airoha_gdm_port *port;
-	struct airoha_qdma *qdma;
 	struct airoha_eth *eth;
 
 	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
@@ -676,7 +676,27 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev);
 bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
 			     struct airoha_gdm_dev *dev);
 
-void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
+extern struct mutex flow_offload_mutex;
+
+static inline struct airoha_qdma *
+airoha_qdma_deref(struct airoha_gdm_dev *dev)
+{
+	return rcu_dereference_protected(dev->qdma,
+					 lockdep_rtnl_is_held() ||
+					 lockdep_is_held(&flow_offload_mutex));
+}
+
+void __airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
+void airoha_ppe_clear_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
+
+static inline void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev,
+					   u8 ppe_id, u8 fport)
+{
+	mutex_lock(&flow_offload_mutex);
+	__airoha_ppe_set_cpu_port(dev, ppe_id, fport);
+	mutex_unlock(&flow_offload_mutex);
+}
+
 bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
 void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
 			  u16 hash, bool rx_wlan);
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 91bcc55a6ac6..0ee0dd385645 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -15,7 +15,7 @@
 #include "airoha_regs.h"
 #include "airoha_eth.h"
 
-static DEFINE_MUTEX(flow_offload_mutex);
+DEFINE_MUTEX(flow_offload_mutex);
 static DEFINE_SPINLOCK(ppe_lock);
 
 static const struct rhashtable_params airoha_flow_table_params = {
@@ -84,10 +84,10 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
 			     AIROHA_FOE_IB1_BIND_TIMESTAMP);
 }
 
-void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
+void __airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
 {
-	struct airoha_qdma *qdma = dev->qdma;
-	struct airoha_eth *eth = qdma->eth;
+	struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+	struct airoha_eth *eth = dev->eth;
 	u8 qdma_id = qdma - &eth->qdma[0];
 	u32 fe_cpu_port;
 
@@ -97,6 +97,14 @@ void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
 		      __field_prep(DFT_CPORT_MASK(fport), fe_cpu_port));
 }
 
+void airoha_ppe_clear_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
+{
+	mutex_lock(&flow_offload_mutex);
+	airoha_fe_clear(dev->eth, REG_PPE_DFT_CPORT(ppe_id, fport),
+			DFT_CPORT_MASK(fport));
+	mutex_unlock(&flow_offload_mutex);
+}
+
 static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
 {
 	u32 sram_ppe_num_data_entries = PPE_SRAM_NUM_ENTRIES, sram_num_entries;
@@ -195,7 +203,7 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
 			ppe_id = !airoha_is_lan_gdm_dev(dev) &&
 				 airoha_ppe_is_enabled(eth, 1);
 			fport = airoha_get_fe_port(dev);
-			airoha_ppe_set_cpu_port(dev, ppe_id, fport);
+			__airoha_ppe_set_cpu_port(dev, ppe_id, fport);
 		}
 	}
 }
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 436f3c8779c1..4e17dfbcf2b8 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -376,6 +376,7 @@
 
 #define REG_SRC_PORT_FC_MAP6		0x2298
 #define FC_ID_OF_SRC_PORT_MASK(_n)	GENMASK(4 + ((_n) << 3), ((_n) << 3))
+#define FC_MAP6_DEF_VALUE		0x1b1a1918
 
 #define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v4 1/2] net: airoha: refactor QDMA start/stop into reusable helpers
From: Lorenzo Bianconi @ 2026-06-10 13:33 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal
In-Reply-To: <20260610-airoha-ethtool-priv_flags-v4-0-60e89cf28fea@kernel.org>

Factor out airoha_qdma_start() and airoha_qdma_stop() from
airoha_dev_open() and airoha_dev_stop(). These helpers will be reused
by the QDMA hot-migration logic introduced in the next patch to
dynamically switch GDM3/GDM4 ports between LAN and WAN QDMA blocks.
Add a DMA engine busy poll in airoha_qdma_stop() to wait for in-flight
DMA transfers to complete before cleaning up TX queues.

Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 53 ++++++++++++++++++++++----------
 1 file changed, 36 insertions(+), 17 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 5a8e84fa9918..aeac66df5f3b 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1771,6 +1771,40 @@ static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
 	spin_unlock(&port->stats.lock);
 }
 
+static void airoha_qdma_start(struct airoha_qdma *qdma)
+{
+	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+			GLOBAL_CFG_TX_DMA_EN_MASK |
+			GLOBAL_CFG_RX_DMA_EN_MASK);
+	atomic_inc(&qdma->users);
+}
+
+static void airoha_qdma_stop(struct airoha_qdma *qdma)
+{
+	u32 status;
+
+	if (!atomic_dec_and_test(&qdma->users))
+		return;
+
+	airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+			  GLOBAL_CFG_TX_DMA_EN_MASK |
+			  GLOBAL_CFG_RX_DMA_EN_MASK);
+
+	if (read_poll_timeout(airoha_qdma_rr, status,
+			      !(status & (GLOBAL_CFG_TX_DMA_BUSY_MASK |
+					  GLOBAL_CFG_RX_DMA_BUSY_MASK)),
+			      USEC_PER_MSEC, 50 * USEC_PER_MSEC, true,
+			      qdma, REG_QDMA_GLOBAL_CFG))
+		dev_warn(qdma->eth->dev, "QDMA DMA engine busy timeout\n");
+
+	for (int i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+		if (!qdma->q_tx[i].ndesc)
+			continue;
+
+		airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
+	}
+}
+
 static int airoha_dev_open(struct net_device *netdev)
 {
 	int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
@@ -1806,10 +1840,7 @@ static int airoha_dev_open(struct net_device *netdev)
 	}
 	port->users++;
 
-	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
-			GLOBAL_CFG_TX_DMA_EN_MASK |
-			GLOBAL_CFG_RX_DMA_EN_MASK);
-	atomic_inc(&qdma->users);
+	airoha_qdma_start(qdma);
 
 	if (!airoha_is_lan_gdm_dev(dev) &&
 	    airoha_ppe_is_enabled(qdma->eth, 1))
@@ -1862,19 +1893,7 @@ static int airoha_dev_stop(struct net_device *netdev)
 		airoha_set_gdm_port_fwd_cfg(qdma->eth,
 					    REG_GDM_FWD_CFG(port->id),
 					    FE_PSE_PORT_DROP);
-
-	if (atomic_dec_and_test(&qdma->users)) {
-		airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
-				  GLOBAL_CFG_TX_DMA_EN_MASK |
-				  GLOBAL_CFG_RX_DMA_EN_MASK);
-
-		for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
-			if (!qdma->q_tx[i].ndesc)
-				continue;
-
-			airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
-		}
-	}
+	airoha_qdma_stop(qdma);
 
 	return 0;
 }

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v4 0/2] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand
From: Lorenzo Bianconi @ 2026-06-10 13:33 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal

Add the capability to configure GDM3/GDM4 as WAN/LAN on demand when QoS
offload is created or destroyed.
Make dev->qdma an RCU pointer so the TX path can safely dereference it
without holding RTNL.
Introduce airoha_qdma_start() and airoha_qdma_stop() helpers.

---
Changes in v4:
- Move back QDMA TX/RX DMA enable to airoha_dev_open()/airoha_dev_stop().
- Configure GDM3/4 as WAN if GDM2 is not available in ndo_init()
  callback.
- Protect qdma pointer in airoha_gdm_dev struct using RCU.
- Rely on rtnl_dereference() to access qdma pointer in the control path.
- Add airoha_qdma_start() and airoha_qdma_stop() utility routines in
  patch 1/2
- Link to v3: https://lore.kernel.org/r/20260608-airoha-ethtool-priv_flags-v3-1-3e8e3dc3f715@kernel.org

Changes in v3:
- Do not introduce ethtool private flags support to configure LAN/WAN
  for GDM3/4 and rely on tc qdisc offload for it instead.
- Set GDM3/4 ports as LAN by default.
- Move QDMA TX/RX DMA enable from airoha_dev_open() to airoha_probe()
  and the corresponding disable from airoha_dev_stop() to airoha_qdma_cleanup().
- Link to v2: https://lore.kernel.org/r/20260607-airoha-ethtool-priv_flags-v2-1-742c7aa1e182@kernel.org

Changes in v2:
- Rework airoha_dev_set_wan_flag routine
- Enable GDM_STRIP_CRC_MASK in airoha_disable_gdm2_loopback()
- Do not always reset REG_SRC_PORT_FC_MAP6 in
  airoha_disable_gdm2_loopback() but use the same condition used in
  airoha_enable_gdm2_loopback().
- Link to v1: https://lore.kernel.org/r/20260606-airoha-ethtool-priv_flags-v1-1-401b2c9fe9f1@kernel.org

---
Lorenzo Bianconi (2):
      net: airoha: refactor QDMA start/stop into reusable helpers
      net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload

 drivers/net/ethernet/airoha/airoha_eth.c  | 273 +++++++++++++++++++++++++-----
 drivers/net/ethernet/airoha/airoha_eth.h  |  24 ++-
 drivers/net/ethernet/airoha/airoha_ppe.c  |  18 +-
 drivers/net/ethernet/airoha/airoha_regs.h |   1 +
 4 files changed, 267 insertions(+), 49 deletions(-)
---
base-commit: 5855479abc796c3b5d7b2f2ca147d68fc56cae1f
change-id: 20260606-airoha-ethtool-priv_flags-b6aa70caa780

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>



^ permalink raw reply


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