Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v4 3/3] arm64: dts: nuvoton: npcm845-evb: Add peripheral nodes
From: sashiko-bot @ 2026-06-10 16:23 UTC (permalink / raw)
  To: Tomer Maimon; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260610160816.2828713-4-tmaimon77@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Missing `/bits/ 8` for `fan-tach-ch` and `cooling-levels` causes incorrect 32-bit integer array emission, corrupting the thermal fan driver state.
- [High] The `tip_reserved` memory node lacks the `no-map` directive, causing speculative external aborts.
- [Medium] Incorrect Device Tree property name `spix-mode` prevents the FIU controller from enabling SPIX mode.
--

commit 011b4946d76e73a81f86b002033d53a6d052bd40
Author: Tomer Maimon <tmaimon77@gmail.com>

arm64: dts: nuvoton: npcm845-evb: Add peripheral nodes

Enable peripheral support for the NPCM845 evaluation board by wiring up
the SoC controller nodes to the board design.

> diff --git a/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts b/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts
> index 5edf5d13342da..ea92888d9b9e2 100644
> --- a/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts
> +++ b/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts

[ ... ]

> @@ -26,12 +62,389 @@ refclk: refclk-25mhz {
>  		clock-frequency = <25000000>;
>  		#clock-cells = <0>;
>  	};
> +
> +	reserved-memory {
> +		#address-cells = <2>;
> +		#size-cells = <2>;
> +		ranges;
> +
> +		tip_reserved: tip@0 {
> +			reg = <0x0 0x0 0x0 0x6200000>;

[Severity: High]
Does this reserved memory region need the no-map directive?

Without no-map, the kernel establishes a direct, cacheable linear mapping
over this region. Since this block is isolated by hardware firewalls for the
Trusted Information Processing firmware, could speculative execution or
prefetching by the CPU into this linear mapping trigger fatal synchronous
external aborts or SErrors?

> +		};
> +	};

[ ... ]

> +&fiux {
> +	spix-mode;

[Severity: Medium]
Is this property missing the vendor prefix?

The SPI driver for the NPCM FIU (drivers/spi/spi-npcm-fiu.c) appears to
check specifically for the "nuvoton,spix-mode" property. If it is only named
"spix-mode" here, will the driver fail to detect it and silently fall back
to standard mode?

> +};

[ ... ]

> +&pwm_fan {
> +	status = "okay";
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&pwm0_pins &pwm1_pins
> +		&pwm2_pins &pwm3_pins
> +		&pwm4_pins &pwm5_pins
> +		&pwm6_pins &pwm7_pins
> +		&fanin0_pins &fanin1_pins
> +		&fanin2_pins &fanin3_pins
> +		&fanin4_pins &fanin5_pins
> +		&fanin6_pins &fanin7_pins>;
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	fan@0 {
> +		reg = <0x00>;
> +		fan-tach-ch = <0x0 0x1>;
> +		cooling-levels = <127 255>;

[Severity: High]
Should these arrays be explicitly sized using /bits/ 8?

The Device Tree compiler natively emits 32-bit big-endian integers for this
syntax. However, if the driver (npcm750-pwm-fan.c) explicitly reads both
properties using of_property_read_u8_array(), an 8-byte payload like
<127 255> would be read as 8 individual u8 elements:
0, 0, 0, 127, 0, 0, 0, 255.

Does this result in registering intermediate thermal cooling states with a 0
duty cycle, inadvertently turning the fan off during active cooling requests?

> +	};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260610160816.2828713-1-tmaimon77@gmail.com?part=3

^ permalink raw reply

* Re: [PATCH v4 2/3] arm64: dts: nuvoton: npcm845: Add peripheral nodes
From: sashiko-bot @ 2026-06-10 16:20 UTC (permalink / raw)
  To: Tomer Maimon; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260610160816.2828713-3-tmaimon77@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] DTC Warning (unit_address_vs_reg): The `apb` node includes a unit address but lacks a `reg` property.
- [Low] DT Binding Schema Violation: `nuvoton,sysgcr` property is undocumented and forbidden by the ChipIdea USB2 schema.
--

commit b1b96db36d76cd5dd1f935613798ed29aade275d
Author: Tomer Maimon <tmaimon77@gmail.com>

arm64: dts: nuvoton: npcm845: Add peripheral nodes

Extend the NPCM845 SoC DTSI with the peripheral controller nodes needed
by the evaluation board and downstream platforms. Add the Ethernet MACs,
USB device controllers and PHY, MMC controller, FIU controllers, memory
controller, RNG, ADC, PWM/FAN controller, and I2C buses. Also add the
OP-TEE firmware node needed to describe these blocks.

> diff --git a/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi b/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
> index 7976baafb9942..1e21b2704784f 100644
> --- a/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
[ ... ]
> @@ -51,7 +57,255 @@ clk: rstc: reset-controller@f0801000 {
>  			#clock-cells = <1>;
>  		};
>  
> -		apb {
> +		gmac1: ethernet@f0804000 {
[ ... ]
> +		udc8: usb@f0838000 {
> +			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
> +			reg = <0x0 0xf0838000 0x0 0x1000
> +			       0x0 0xfffef000 0x0 0x800>;
> +			interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
> +			clocks = <&clk NPCM8XX_CLK_SU>;
> +			clock-names = "clk_usb_bridge";
> +
> +			nuvoton,sysgcr = <&gcr 0x9C 0xC000 0xC000>;

[Severity: Low]
Does this undocumented nuvoton,sysgcr property cause a dtbs_check warning?

The USB Device Controllers bind to "nuvoton,npcm750-udc", which is evaluated 
against Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml. This schema 
specifies unevaluatedProperties: false but does not document nuvoton,sysgcr 
internally or in any dependencies.

> +			phys = <&udc0_phy>;
> +			phy_type = "utmi_wide";
> +			dr_mode = "peripheral";
> +			status = "disabled";
> +		};
> +
> +		udc9: usb@f0839000 {
> +			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
> +			reg = <0x0 0xf0839000 0x0 0x1000
> +			       0x0 0xfffef800 0x0 0x800>;
> +			interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
> +			clocks = <&clk NPCM8XX_CLK_SU>;
> +			clock-names = "clk_usb_bridge";
> +
> +			nuvoton,sysgcr = <&gcr 0x9C 0x3000 0x3000>;

[Severity: Low]
Is this the same undocumented property issue as seen above in udc8?

> +			phys = <&udc0_phy>;
> +			phy_type = "utmi_wide";
> +			dr_mode = "peripheral";
> +			status = "disabled";
> +		};
> +
[ ... ]
> +		fiux: spi@fb001000 {
> +			compatible = "nuvoton,npcm845-fiu";
> +			#address-cells = <1>;
> +			#size-cells = <0>;
> +			reg = <0x0 0xfb001000 0x0 0x1000>,
> +			<0x0 0xf8000000 0x0 0x2000000>;
> +			reg-names = "control", "memory";
> +			clocks =  <&clk NPCM8XX_CLK_SPIX>;
> +			status = "disabled";
> +		};
> +
> +		apb: bus@f0000000 {

[Severity: Low]
Will changing this node name to include the @f0000000 unit address 
trigger a unit_address_vs_reg DTC warning?

According to the Devicetree specification, if a node name includes a 
unit-address, the node must also have a reg property where the first 
address matches the unit-address. Because the apb node defines ranges 
but lacks a reg property, this might introduce build noise.

>  			#address-cells = <1>;
>  			#size-cells = <1>;
>  			compatible = "simple-bus";
> @@ -59,14 +313,6 @@ apb {
>  			ranges = <0x0 0x0 0xf0000000 0x00300000>,
>  				<0xfff00000 0x0 0xfff00000 0x00016000>;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260610160816.2828713-1-tmaimon77@gmail.com?part=2

^ permalink raw reply

* Re: [PATCH v8 1/3] dt-bindings: timer: mips,p8700-gcru
From: Conor Dooley @ 2026-06-10 16:18 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Aleksa Paunovic via B4 Relay, robh, devicetree, conor+dt
In-Reply-To: <20260610082646.B29F71F00893@smtp.kernel.org>

[-- Attachment #1: Type: text/plain, Size: 1589 bytes --]

On Wed, Jun 10, 2026 at 08:26:46AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Mismatch between the node's unit-address and the first address in its 'reg' property in the device tree example.
> --
> 
> commit 28ad318962744fd052dc7a14265d2214d613bc60
> Author: Aleksa Paunovic <aleksa.paunovic@htecgroup.com>
> 
> dt-bindings: timer: mips,p8700-gcru
> 
> Add dt-bindings for the GCR.U memory mapped timer device for RISC-V
> platforms. The GCR.U memory region contains shadow copies of the RISC-V
> mtime register and the hrtime Global Configuration Register.
> 
> > diff --git a/Documentation/devicetree/bindings/timer/mips,p8700-gcru.yaml b/Documentation/devicetree/bindings/timer/mips,p8700-gcru.yaml
> > new file mode 100644
> > index 0000000000000..3498255762cce
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/timer/mips,p8700-gcru.yaml
> 
> [ ... ]
> 
> > +examples:
> > +  - |
> > +    timer@1617f000 {
> > +        compatible = "mips,p8700-gcru";
> > +        reg = <0x1617f050 0x8>,
> 
> [Severity: Low]
> Does the unit-address in the node name (1617f000) need to exactly match the 
> first address specified in the reg property (1617f050)? This mismatch will 
> likely trigger a unit_address_vs_reg warning during dt_binding_check.

Yes, it should.

> 
> > +              <0x1617f090 0x8>;
> > +    };
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260610-riscv-time-mmio-v8-0-a865206675c6@htecgroup.com?part=1

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH net-next v6 12/12] net: airoha: add phylink support
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260609151212.29469-13-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Calling `phylink_destroy()` before `unregister_netdev()` causes a Use-After-Free during device teardown.
- [High] Unconditional PCS parsing in `airoha_setup_phylink()` causes probe failure for the GDM1 port.
- [Medium] The RX fragment size is incorrectly configured using the TX fragment size variable due to a copy-paste error.
- [Medium] The `phylink` instance is leaked in the `airoha_probe()` error path for successfully allocated but unregistered devices.
--

commit 7158f1ddaf486067bb751bd3d43f1a8bfc87794f
Author: Christian Marangi <ansuelsmth@gmail.com>

    net: airoha: add phylink support
    
    Add phylink support for each GDM port. For GDM1 add the internal interface
    mode as the only supported mode. For GDM2/3/4 add the required
    configuration of the PCS to make the external PHY or attached SFP cage
    work.
    
    These needs to be defined in the GDM port node using the pcs-handle
    property.

[ ... ]

> @@ -3148,6 +3161,153 @@ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
>  	return false;
>  }
>  
> +/* Nothing to do in MAC, everything is handled in PCS */
> +static void airoha_mac_config(struct phylink_config *config, unsigned int mode,
> +			      const struct phylink_link_state *state)
> +{
> +}
> +
> +static void airoha_mac_link_up(struct phylink_config *config, struct phy_device *phy,
> +			       unsigned int mode, phy_interface_t interface,
> +			       int speed, int duplex, bool tx_pause, bool rx_pause)
> +{

[ ... ]

> +	if (dev->nbq == 1) {
> +		mask = GDMA4_SGMII1_RX_FRAG_SIZE_MASK;
> +		val = FIELD_PREP(GDMA4_SGMII1_RX_FRAG_SIZE_MASK,
> +				 frag_size_tx);
> +	} else {
> +		mask = GDMA4_SGMII0_RX_FRAG_SIZE_MASK;
> +		val = FIELD_PREP(GDMA4_SGMII0_RX_FRAG_SIZE_MASK,
> +				 frag_size_tx);
> +	}

[Severity: Medium]
Should this use frag_size_rx instead of frag_size_tx when configuring the
RX fragment size? This looks like a typo that could cause the hardware to be
configured incorrectly when the RX and TX fragment sizes differ.

> +	airoha_fe_rmw(eth, REG_GDMA4_RMBI_FRAG, mask, val);
> +}

[ ... ]

> +static int airoha_setup_phylink(struct net_device *netdev)
> +{
> +	struct airoha_gdm_dev *dev = netdev_priv(netdev);
> +	struct device_node *np = netdev->dev.of_node;
> +	struct airoha_gdm_port *port = dev->port;
> +	struct phylink_config *config;
> +	phy_interface_t phy_mode;
> +	struct phylink *phylink;
> +	int err;
> +
> +	err = of_get_phy_mode(np, &phy_mode);
> +	if (err) {
> +		dev_err(&netdev->dev, "incorrect phy-mode\n");
> +		return err;
> +	}
> +
> +	config = &dev->phylink_config;
> +
> +	config->dev = &netdev->dev;
> +	config->type = PHYLINK_NETDEV;
> +	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |  MAC_10000FD;
> +	if (port->id > AIROHA_GDM1_IDX)
> +		config->mac_capabilities |= MAC_10 | MAC_100 | MAC_1000 |
> +					    MAC_2500FD | MAC_5000FD;
> +
> +	err = fwnode_phylink_pcs_parse(dev_fwnode(&netdev->dev), NULL,
> +				       &config->num_available_pcs);
> +	if (err)
> +		return err;

[Severity: High]
Does this unconditional call to fwnode_phylink_pcs_parse() cause probe to
fail for the GDM1 port?

The commit message mentions that GDM1 does not require a PCS. If its device
tree node lacks a pcs-handle property, fwnode_phylink_pcs_parse() will return
-ENODEV and abort the probe. Should this parsing logic be conditionally
skipped if port->id == AIROHA_GDM1_IDX?

> +
> +	config->fill_available_pcs = airoha_fill_available_pcs;
> +
> +	/*
> +	 * GDM1 only supports internal for Embedded Switch
> +	 * and doesn't require a PCS.
> +	 */

[ ... ]

> @@ -3435,8 +3595,10 @@ static int airoha_probe(struct platform_device *pdev)
>  				continue;
>  
>  			netdev = netdev_from_priv(dev);
> -			if (netdev->reg_state == NETREG_REGISTERED)
> +			if (netdev->reg_state == NETREG_REGISTERED) {
> +				phylink_destroy(dev->phylink);
>  				unregister_netdev(netdev);
> +			}

[Severity: Medium]
Does this leak the phylink instance for devices that were successfully
allocated but failed to register?

In airoha_alloc_gdm_device(), the phylink structure is successfully allocated
prior to device registration. If airoha_probe() fails before or during device
registration, the NETREG_REGISTERED check here skips calling
phylink_destroy() for unregistered devices, which would leave the memory
leaked.


[Severity: High]
Is it safe to destroy the phylink instance before calling
unregister_netdev()?

If the network interface is running (IFF_UP), unregister_netdev() will
synchronously bring the device down by invoking its ndo_stop callback, which
is airoha_dev_stop().

Inside airoha_dev_stop(), the driver attempts to halt phylink:
	phylink_stop(dev->phylink);
	phylink_disconnect_phy(dev->phylink);

Will this result in a use-after-free since dev->phylink was already destroyed
here?

>  			of_node_put(netdev->dev.of_node);
>  		}
>  		airoha_metadata_dst_free(port);

[ ... ]

> @@ -3472,6 +3634,7 @@ static void airoha_remove(struct platform_device *pdev)
>  				continue;
>  
>  			netdev = netdev_from_priv(dev);
> +			phylink_destroy(dev->phylink);
>  			unregister_netdev(netdev);

[Severity: High]
Similarly to the error path in airoha_probe(), does destroying the phylink
instance before calling unregister_netdev() cause a use-after-free when
airoha_dev_stop() tries to use dev->phylink?

>  			of_node_put(netdev->dev.of_node);
>  		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=12

^ permalink raw reply

* Re: [PATCH net-next v6 11/12] net: pcs: airoha: add PCS driver for Airoha AN7581 SoC
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260609151212.29469-12-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Critical] Passing the address of a local stack variable (`&priv`) to `fwnode_pcs_add_provider()` causes a fatal use-after-free when dereferenced later.
- [High] A division by zero will occur during RX PR calibration if the frequency output perfectly matches the target.
- [High] Integer underflow and misuse of the `abs()` macro on unsigned types breaks frequency calibration.
- [High] The `pcs-airoha` module is silently not generated when configured as a loadable module (`M`).
- [High] Shared device-level hardware resets and PLL configurations disrupt other active ports on multi-port devices.
- [High] Missing bounds check on user-provided device tree arguments leads to out-of-bounds array access.
- [High] Missing `FORCE_SEL` bits in `regmap_update_bits` values causes the hardware to ignore the forced configurations.
- [Medium] `regmap_set_bits` performs a read-modify-write on Write-1-to-Clear (W1C) interrupt status registers, silently dropping unhandled interrupts.
- [Low] Missing `fallthrough;` or `break;` annotation in `switch` statement causes an implicit fallthrough compiler warning.
--

commit a1f0ba4cb5341a223ae17b71c3dfd06228567ad5
Author: Christian Marangi <ansuelsmth@gmail.com>
Date:   Tue Jun 9 15:12:07 2026 +0000

net: pcs: airoha: add PCS driver for Airoha AN7581 SoC

This commit introduces the PCS driver for the Airoha AN7581 SoC, supporting
Ethernet, PON, PCIe, and USB SERDES. It implements phylink PCS operations and
handles the configuration of the PCS and PMA blocks.

> diff --git a/drivers/net/pcs/airoha/Makefile b/drivers/net/pcs/airoha/Makefile
> new file mode 100644
> index 0000000000000..25cb8f090c214
> --- /dev/null
> +++ b/drivers/net/pcs/airoha/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-y                          := pcs-airoha.o

[Severity: High]
Will this correctly build as a loadable module? It appears that using obj-y
forces static inclusion, which means the module won't be generated when
CONFIG_PCS_AIROHA is set to 'm'. Should this use obj-$(CONFIG_PCS_AIROHA)
instead?

> diff --git a/drivers/net/pcs/airoha/pcs-airoha-common.c b/drivers/net/pcs/airoha/pcs-airoha-common.c
> new file mode 100644
> index 0000000000000..ead0bc8083967
> --- /dev/null
> +++ b/drivers/net/pcs/airoha/pcs-airoha-common.c
[ ... ]
> +static int airoha_pcs_setup_scu(struct airoha_pcs_priv *priv,
> +                               int index, phy_interface_t interface)
> +{
[ ... ]
> +       /* TODO better handle reset from MAC */
> +       ret = reset_control_bulk_assert(ARRAY_SIZE(priv->rsts),
> +                                       priv->rsts);

[Severity: High]
Does unconditionally triggering these global reset lines disrupt other active
ports on multi-port devices like PCIe? It seems that initializing a second
port will assert these resets and could drop the link on an already active
first port.

[ ... ]
> +static void airoha_pcs_interrupt_init_usxgmii(struct airoha_pcs_priv *priv,
> +                                             int index)
> +{
[ ... ]
> +       /* Clear any pending interrupt */
> +       regmap_set_bits(maps->usxgmii_pcs, AIROHA_PCS_USXGMII_PCS_INT_STA_2,
> +                       AIROHA_PCS_USXGMII_RPCS_FSM_DEC_ERR_INT |
> +                       AIROHA_PCS_USXGMII_R_TYPE_E_INT |
> +                       AIROHA_PCS_USXGMII_R_TYPE_T_INT |
> +                       AIROHA_PCS_USXGMII_R_TYPE_D_INT);

[Severity: Medium]
Could using regmap_set_bits() on a Write-1-to-Clear (W1C) interrupt status
register cause unhandled interrupts to be silently dropped? This performs a
read-modify-write, which might write back 1s to pending interrupts not
targeted by this bitmask. Should this use a direct regmap_write() instead?

[ ... ]
> +static void airoha_pcs_an_restart(struct phylink_pcs *pcs)
> +{
[ ... ]
> +       case PHY_INTERFACE_MODE_USXGMII:
> +               regmap_set_bits(maps->usxgmii_pcs, AIROHA_PCS_USXGMII_PCS_AN_CONTROL_0,
> +                               AIROHA_PCS_USXGMII_AN_RESTART);
> +               udelay(3);
> +               regmap_clear_bits(maps->usxgmii_pcs, AIROHA_PCS_USXGMII_PCS_AN_CONTROL_0,
> +                                 AIROHA_PCS_USXGMII_AN_RESTART);
> +       default:
> +               return;

[Severity: Low]
Does this missing break or fallthrough annotation trigger compiler warnings?
It looks like the PHY_INTERFACE_MODE_USXGMII case falls through to default
without explicitly declaring it.

[ ... ]
> +static struct phylink_pcs *airoha_pcs_get(struct fwnode_reference_args *pcsspec,
> +                                         void *data)
> +{
[ ... ]
> +       if (pcsspec->nargs)
> +               index = pcsspec->args[0];
> +
> +       return &priv->ports[index].pcs;

[Severity: High]
Is there a risk of an out-of-bounds array access here? The index is extracted
directly from the device tree via pcsspec->args[0] without verifying if it is
less than priv->data->num_port.

[ ... ]
> +static int airoha_pcs_probe(struct platform_device *pdev)
> +{
[ ... ]
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
[ ... ]
> +       return fwnode_pcs_add_provider(dev_fwnode(dev), airoha_pcs_get,
> +                                      &priv);

[Severity: Critical]
Could passing the address of the local stack variable &priv cause a
use-after-free? When the MAC driver later calls airoha_pcs_get(), it will
dereference this stack pointer after airoha_pcs_probe() has returned. Should
this pass 'priv' instead of '&priv'?

> diff --git a/drivers/net/pcs/airoha/pcs-an7581.c b/drivers/net/pcs/airoha/pcs-an7581.c
> new file mode 100644
> index 0000000000000..22cf7b4b0108f
> --- /dev/null
> +++ b/drivers/net/pcs/airoha/pcs-an7581.c
[ ... ]
> +static void an7581_pcs_jcpll_bringup(struct airoha_pcs_priv *priv,
> +                                    int index, phy_interface_t interface)
> +{
[ ... ]
> +       /* This comment only apply to Serdes PCIe that expose
> +        * 2 PCS.
> +        *
> +        * The Serdes PCIe expose 2 PCS but always require
> +        * the PMA for the first PCS to be configured
> +        * for correct functionality for JCPLL.
> +        */
> +       pcs_pma = priv->pcs_pma[0];

[Severity: High]
Does forcing pcs_pma to index 0 clobber the active PLL configuration of Port
0 when Port 1 is being initialized? It appears this might disrupt the active
connection on the first port.

[ ... ]
> +static void an7581_pcs_tx_bringup(struct airoha_pcs_priv *priv,
> +                                 int index, phy_interface_t interface)
> +{
[ ... ]
> +       regmap_update_bits(pcs_pma, AIROHA_PCS_PMA_PXP_TX_TERM_SEL,
> +                          AIROHA_PCS_PMA_FORCE_SEL_DA_TX_CKIN_DIVISOR |
> +                          AIROHA_PCS_PMA_FORCE_DA_TX_CKIN_DIVISOR |
> +                          AIROHA_PCS_PMA_FORCE_SEL_DA_TX_TERM_SEL |
> +                          AIROHA_PCS_PMA_FORCE_DA_TX_TERM_SEL,
> +                          AIROHA_PCS_PMA_FORCE_SEL_DA_TX_CKIN_DIVISOR |
> +                          FIELD_PREP(AIROHA_PCS_PMA_FORCE_DA_TX_CKIN_DIVISOR,
> +                                     ckin_divisor) |
> +                          FIELD_PREP(AIROHA_PCS_PMA_FORCE_DA_TX_TERM_SEL, 0x0));

[Severity: High]
Will the hardware ignore this forced configuration? The mask includes
AIROHA_PCS_PMA_FORCE_SEL_DA_TX_TERM_SEL, but the value part omits it, which
might clear the force override bit.

[ ... ]
> +static u32 an7581_pcs_rx_prcal_idac_major(struct airoha_pcs_priv *priv,
> +                                         int index, u32 target_fl_out)
> +{
[ ... ]
> +               fl_out = an7581_pcs_apply_cdr_pr_idac(priv, index, cdr_pr_idac_tmp);
> +
> +               /* Use absolute values to find the closest one to target */
> +               fl_out_diff_new = abs(fl_out - target_fl_out);

[Severity: High]
Since fl_out and target_fl_out are both unsigned integers, doesn't this
subtraction cause an underflow if fl_out is less than target_fl_out? Also,
the abs() macro generally expects signed types. Could this cause the
calibration loop to become inaccurate?

[ ... ]
> +static u32 an7581_pcs_rx_prcal_idac_minor(struct airoha_pcs_priv *priv, int index,
> +                                         u32 target_fl_out, u32 cdr_pr_idac_major)
> +{
[ ... ]
> +               fl_out_diff_new = abs(fl_out - target_fl_out);
[ ... ]
> +               if (fl_out_diff_new > fl_out_diff &&
> +                   (abs(fl_out_diff_new - fl_out_diff) * 100) / fl_out_diff > 10) {

[Severity: High]
Is there a risk of a divide-by-zero condition here? If the initial fl_out
reading perfectly matches target_fl_out, fl_out_diff will evaluate to 0,
which would cause the division to panic the kernel.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=11

^ permalink raw reply

* Re: [PATCH net-next v6 10/12] dt-bindings: net: pcs: Document support for Airoha Ethernet PCS
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: devicetree, conor+dt, robh
In-Reply-To: <20260609151212.29469-11-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `phys` property is defined without a `maxItems` or `items` constraint, allowing an unbounded number of PHY phandles.
--

commit 4a128a4fb15024e87f0aaa51566224ca71799731
Author: Christian Marangi <ansuelsmth@gmail.com>

dt-bindings: net: pcs: Document support for Airoha Ethernet PCS
    
Document support for Airoha Ethernet PCS for AN7581 SoC.
Airoha AN7581 SoC expose multiple Physical Coding Sublayer (PCS) for
the various Serdes port supporting different Media Independent Interface.
This follow the new PCS provider with the use of #pcs-cells property.

> diff --git a/Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml b/Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml
> new file mode 100644
> index 0000000000000..98d8ab7a90f5f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml

[ ... ]

> +properties:
> +  compatible:
> +    enum:
> +      - airoha,an7581-pcs-eth
> +      - airoha,an7581-pcs-pon
> +      - airoha,an7581-pcs-pcie
> +      - airoha,an7581-pcs-usb
> +
> +  reg:
> +    minItems: 6
> +    maxItems: 15
> +
> +  reg-names:
> +    minItems: 6
> +    maxItems: 15
> +
> +  airoha,scu:
> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description: phandle to the SCU node required to configure
> +      the serdes line to the correct interface mode.
> +
> +  phys: true

[Severity: Low]
Does this phys declaration need a length constraint?

In dt-schema, standard array properties like phys require explicit length
constraints (e.g., maxItems: 1 or an items list) to ensure the device tree node
provides the exact number of resources the hardware expects. Defining phys: true
permits the property but fails to enforce any bounds on the number of entries,
allowing a device tree node with extraneous PHY phandles to silently pass schema
validation.

> +
> +  "#pcs-cells": true

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=10

^ permalink raw reply

* Re: [PATCH net-next v6 09/12] net: phylink: add .pcs_link_down PCS OP
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260609151212.29469-10-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Memory leak/Use-After-Free due to missing notifier unregistration.
- [High] Hardware Teardown Sequence Error violating symmetric LIFO teardown.
- [High] Data Race and list corruption in `pcs_list`.
- [Low] Incorrect grammar in callback documentation.
--

commit db8b84b7398b2a4cfe29ace55d1aefced615f8e2
Author: Christian Marangi <ansuelsmth@gmail.com>

net: phylink: add .pcs_link_down PCS OP

This commit introduces a new pcs_link_down callback for PCS drivers to define
specific teardown operations. It updates phylink_link_down to invoke this
new operation before taking down the MAC. This allows PCS drivers to perform
necessary resets or counter clearing when the link goes down.

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 65b67c33e12fd..38428b375eca3 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c

[ ... ]

> @@ -1735,6 +1741,8 @@ static void phylink_link_down(struct phylink *pl)
>  
>  	phylink_deactivate_lpi(pl);
>  
> +	phylink_pcs_link_down(pl->pcs);
> +
>  	pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
>  				   pl->cur_interface);

[Severity: High]
Does this break the symmetric LIFO teardown order? During phylink_link_up(),
the PCS is initialized before the MAC. Calling phylink_pcs_link_down() before
mac_link_down() leaves the MAC nominally active while the underlying PCS
connection is severed. Could this inconsistency lead to MAC hardware lockups,
AXI bus hangs, or transmit queue timeouts?

> diff --git a/include/linux/phylink.h b/include/linux/phylink.h
> index b65f91c78d8cc..79a1496fa519c 100644
> --- a/include/linux/phylink.h
> +++ b/include/linux/phylink.h
> @@ -528,6 +528,7 @@ struct phylink_pcs {
>   * @pcs_an_restart: restart 802.3z BaseX autonegotiation.
>   * @pcs_link_up: program the PCS for the resolved link configuration
>   *               (where necessary).
> + * @pcs_link_down: torn down link between MAC and PCS.

[Severity: Low]
this isn't a bug, but there is a grammar mistake here (torn down instead of
tear down).

[Severity: Critical]
Is there a missing notifier unregistration for the fwnode_pcs_nb block?
Looking at phylink_create(), it registers the embedded notifier block:

drivers/net/phy/phylink.c:phylink_create() {
        ...
        register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
        ...
}

However, phylink_destroy() and the error paths in phylink_create() do not
seem to unregister it. When the phylink struct is freed via kfree(pl), the
embedded notifier remains linked in the global chain. Will any subsequent
PCS provider registration system-wide iterate over the freed memory and cause
a kernel panic? (Note: introduced by the earlier patch net: phylink: support
late PCS provider attach in the same series).

[Severity: High]
Is there a data race when mutating pcs_list in phylink_destroy()?

The pcs_provider_notify() callback asynchronously adds newly discovered PCS
providers to pl->pcs_list while holding the rtnl_lock(). However,
phylink_destroy() iterates over and deletes entries from pl->pcs_list without
holding the rtnl_lock():

drivers/net/phy/phylink.c:phylink_destroy() {
        ...
        /* Remove every PCS from phylink PCS list */
        list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
                list_del(&pcs->list);
        ...
}

If there is a concurrent PCS provider registration via the global notifier
chain, could this lead to unsynchronized mutations and list corruption?
(Note: introduced by the earlier patch net: phylink: support late PCS
provider attach in the same series).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=9

^ permalink raw reply

* Re: [PATCH net-next v6 05/12] net: phylink: support late PCS provider attach
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260609151212.29469-6-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Use-After-Free due to missing notifier unregistration when phylink instance is destroyed.
- [High] Use-After-Free due to incomplete PCS detachment logic when the phylink instance is stopped.
- [High] Data race between `pcs_provider_notify()` and asynchronous readers leads to `pl->pcs_list` corruption.
- [Medium] Memory leak of `fwnode_handle` references in `fwnode_phylink_pcs_get_from_fwnode()`.
--

commit e4be489886d6b3bd45151876412cae4ab8aa7956
Author: Christian Marangi <ansuelsmth@gmail.com>

net: phylink: support late PCS provider attach

This commit adds support for late PCS provider attachment to a phylink
instance. It implements a global notifier for the PCS provider, allowing
phylink instances to subscribe and attach the PCS when it becomes available.

> diff --git a/drivers/net/pcs/pcs.c b/drivers/net/pcs/pcs.c
> index 72f56f55d198d..911d95cf1b09c 100644
> --- a/drivers/net/pcs/pcs.c
> +++ b/drivers/net/pcs/pcs.c

[ ... ]

> @@ -147,6 +158,35 @@ struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode, int index)
>  }
>  EXPORT_SYMBOL_GPL(fwnode_pcs_get);
>  
> +struct phylink_pcs *
> +fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
> +				   struct fwnode_handle *pcs_fwnode)
> +{
> +	struct fwnode_reference_args pcsspec;
> +	int index = 0;
> +	int ret;
> +
> +	/* Loop until we find a matching PCS node or
> +	 * fwnode_parse_pcsspec() returns error
> +	 * if we don't have any other PCS reference to check.
> +	 */
> +	while (true) {
> +		ret = fwnode_parse_pcsspec(fwnode, index, NULL, &pcsspec);
> +		if (ret)
> +			return ERR_PTR(ret);
> +
> +		/* Exit loop if we found the matching PCS node */
> +		if (pcsspec.fwnode == pcs_fwnode)
> +			break;

[Severity: Medium]
Does this leak a fwnode_handle reference?

fwnode_parse_pcsspec() internally acquires a reference to pcsspec.fwnode.
However, on both the break path (when a match is found) and the loop
continuation path, this reference is never released with
fwnode_handle_put().

Will this leak a reference for every successfully parsed PCS specification?

> +
> +		/* Check the next PCS reference */
> +		index++;
> +	}
> +
> +	return fwnode_pcs_get(fwnode, index);
> +}
> +EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_get_from_fwnode);

[ ... ]

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index b45bee9273007..65b67c33e12fd 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c

[ ... ]

> @@ -1998,6 +2000,51 @@ static int phylink_fill_available_pcs(struct phylink *pl,
>  	return ret;
>  }
>  
> +static int pcs_provider_notify(struct notifier_block *self,
> +			       unsigned long val, void *data)
> +{
> +	struct phylink *pl = container_of(self, struct phylink, fwnode_pcs_nb);
> +	struct fwnode_handle *pcs_fwnode = data;
> +	struct phylink_pcs *pcs;
> +
> +	/* Check if the just added PCS provider is
> +	 * in the phylink instance pcs-handle property.
> +	 */
> +	pcs = fwnode_phylink_pcs_get_from_fwnode(dev_fwnode(pl->config->dev),
> +						 pcs_fwnode);
> +	if (IS_ERR(pcs))
> +		return NOTIFY_DONE;
> +
> +	/* Add the PCS */
> +	rtnl_lock();
> +
> +	list_add(&pcs->list, &pl->pcs_list);

[Severity: High]
Is it safe to mutate this list while only holding the rtnl_lock?

pcs_provider_notify() adds a new PCS to pl->pcs_list under the
rtnl_lock().

However, asynchronous readers like phylink_major_config() executed from
the phylink_resolve workqueue traverse this same list using
list_for_each_entry() while only holding pl->state_mutex.

Since list_add() is not atomic and the locks are disjoint, can concurrent
access cause readers to traverse a partially updated list structure?

> +
> +	/* Link phylink if we are started */
> +	if (!pl->phylink_disable_state)
> +		pcs->phylink = pl;

[Severity: High]
Does this create a use-after-free when a phylink instance is stopped?

When the interface is stopped, phylink_stop() clears pcs->phylink by
setting it to NULL for every entry in pl->pcs_list.

If the underlying PCS driver is unbound while stopped, it calls
phylink_release_pcs(), which checks if pcs->phylink is NULL and returns
early, failing to remove the PCS from pl->pcs_list:

phylink_release_pcs() {
    pl = pcs->phylink;
    if (!pl)
        return;
    list_del(&pcs->list);
}

Will this leave a freed PCS structure dangling in pl->pcs_list which
could be traversed when phylink_start() is called later?

> +
> +	/* Refresh supported interfaces */
> +	phy_interface_copy(pl->supported_interfaces,
> +			   pl->config->supported_interfaces);
> +	list_for_each_entry(pcs, &pl->pcs_list, list)
> +		phy_interface_or(pl->supported_interfaces,
> +				 pl->supported_interfaces,
> +				 pcs->supported_interfaces);

[ ... ]

> @@ -2053,6 +2100,11 @@ struct phylink *phylink_create(struct phylink_config *config,
>  				 pl->supported_interfaces,
>  				 pcs->supported_interfaces);
>  
> +	if (!phy_interface_empty(config->pcs_interfaces)) {
> +		pl->fwnode_pcs_nb.notifier_call = pcs_provider_notify;
> +		register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
> +	}

[Severity: High]
If the phylink instance is destroyed, is this notifier ever unregistered?

If phylink_destroy() frees the phylink instance but doesn't unregister
the notifier block, will it leave a dangling pointer in the global
fwnode_pcs_notify_list? If so, won't this cause a kernel panic when the
next PCS provider is added?

> +
>  	pl->config = config;
>  	if (config->type == PHYLINK_NETDEV) {
>  		pl->netdev = to_net_dev(config->dev);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=5

^ permalink raw reply

* Re: [PATCH net-next v6 03/12] net: phylink: add phylink_release_pcs() to externally release a PCS
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260609151212.29469-4-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Unsynchronized modification of `pl->pcs_list` and TOCTOU access to `pl->pcs` in `phylink_release_pcs()` leads to data races, list corruption, and use-after-free.
- [High] `phylink_release_pcs()` silently fails to remove the PCS from `pl->pcs_list` if the interface is stopped, leading to a Use-After-Free.
- [Medium] Releasing an actively running PCS bypasses `phylink_pcs_disable()`, leaving the hardware permanently enabled.
--

commit ef373ddee33a54496ae5ebacb3521d9195428672
Author: Christian Marangi <ansuelsmth@gmail.com>

net: phylink: add phylink_release_pcs() to externally release a PCS

Add phylink_release_pcs() to externally release a PCS from a phylink
instance. This can be used to handle case when a single PCS needs to be
removed and the phylink instance needs to be refreshed.

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 4d6ffda0cdd61..b45bee9273007 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
> @@ -923,6 +923,55 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
>  	}
>  }
>  
> +/**
> + * phylink_release_pcs - Removes a PCS from the phylink PCS available list
> + * @pcs: a pointer to the phylink_pcs struct to be released
> + *
> + * This function release a PCS from the phylink PCS available list if
> + * actually in use. It also refreshes the supported interfaces of the
> + * phylink instance by copying the supported interfaces from the phylink
> + * conf and merging the supported interfaces of the remaining available PCS
> + * in the list and trigger a resolve.
> + */
> +void phylink_release_pcs(struct phylink_pcs *pcs)
> +{
> +	struct phylink *pl;
> +
> +	ASSERT_RTNL();
> +
> +	pl = pcs->phylink;
> +	if (!pl)
> +		return;

[Severity: High]
Does this early return cause a use-after-free if the interface is stopped?

When the interface is brought down, phylink_stop() clears pcs->phylink = NULL
for all available PCS instances, while keeping them linked in pl->pcs_list.

If phylink_release_pcs() is called while the interface is stopped, it encounters
a NULL pointer here and returns immediately without executing
list_del(&pcs->list). The caller likely assumes the PCS was successfully
detached and frees its memory, leaving a dangling pointer in pl->pcs_list. 

When the interface is later brought up, phylink_start() will iterate over
pl->pcs_list and dereference the freed memory.

> +
> +	list_del(&pcs->list);
> +	pcs->phylink = NULL;
> +
> +	/* Check if we are removing the PCS currently
> +	 * in use by phylink. If this is the case,
> +	 * force phylink resolve to reconfigure the interface
> +	 * mode and set the phylink PCS to NULL.
> +	 */
> +	if (pl->pcs == pcs) {

[Severity: High]
Can modifying the list and checking pl->pcs outside the state_mutex race with
the workqueue?

While this function enforces the RTNL lock, pl->pcs_list is concurrently
traversed by phylink_major_config() from the phylink_resolve workqueue, which
holds only pl->state_mutex. Modifying the list outside of pl->state_mutex could
cause the workqueue to dereference LIST_POISON1.

Additionally, checking if (pl->pcs == pcs) before acquiring state_mutex creates
a Time-of-Check to Time-of-Use race. A concurrent phylink_resolve could update
pl->pcs between the lockless check and acquiring state_mutex, causing this code
to inadvertently clear a newly configured PCS.

> +		mutex_lock(&pl->state_mutex);
> +
> +		pl->force_major_config = true;
> +		pl->pcs = NULL;

[Severity: Medium]
If pl->pcs is cleared to NULL here, will the released PCS ever be cleanly
disabled?

During the subsequent reconfiguration triggered by phylink_run_resolve(),
phylink_major_config() detects a PCS change and attempts to disable the old PCS
by calling phylink_pcs_disable(pl->pcs). 

However, since pl->pcs was already cleared to NULL here, the
pcs->ops->pcs_disable() callback will never be invoked for the released PCS,
leaving it hardware-enabled.

> +
> +		mutex_unlock(&pl->state_mutex);
> +	}
> +
> +	/* Refresh supported interfaces */
> +	phy_interface_copy(pl->supported_interfaces,
> +			   pl->config->supported_interfaces);
> +	list_for_each_entry(pcs, &pl->pcs_list, list)
> +		phy_interface_or(pl->supported_interfaces,
> +				 pl->supported_interfaces,
> +				 pcs->supported_interfaces);
> +
> +	phylink_run_resolve(pl);
> +}
> +EXPORT_SYMBOL_GPL(phylink_release_pcs);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=3

^ permalink raw reply

* Re: [PATCH net-next v6 04/12] net: pcs: implement Firmware node support for PCS driver
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260609151212.29469-5-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Unprotected list modification and iteration in phylink PCS list handling causes race conditions.
- [Medium] `fwnode_pcs_get_from_pcsspec()` incorrectly returns `-ENODEV` instead of `-EPROBE_DEFER` when a provider is not found.
- [Low] `fwnode_parse_pcsspec()` passes a negative error code as an unsigned index, masking the original error and causing out-of-bounds lookups.
--

commit 436ed2d38da7824de31cb0cb85a006f98cbdeb4b
Author: Christian Marangi <ansuelsmth@gmail.com>

net: pcs: implement Firmware node support for PCS driver

This commit implements the foundation for firmware node support in PCS drivers.
It introduces a simple provider API where a PCS driver can expose multiple PCS
instances with a get function. It also adds a generic parsing function for MAC
drivers to declare PCS in DT or ACPI.

> diff --git a/drivers/net/pcs/pcs.c b/drivers/net/pcs/pcs.c
> new file mode 100644
> index 0000000000000..72f56f55d198d
> --- /dev/null
> +++ b/drivers/net/pcs/pcs.c

[ ... ]

> +static int fwnode_parse_pcsspec(const struct fwnode_handle *fwnode, int index,
> +				const char *name,
> +				struct fwnode_reference_args *out_args)
> +{
> +	int ret;
> +
> +	if (!fwnode)
> +		return -ENOENT;
> +
> +	if (name)
> +		index = fwnode_property_match_string(fwnode, "pcs-names",
> +						     name);
> +
> +	ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
> +						 "#pcs-cells",
> +						 -1, index, out_args);

[Severity: Low]
If name is provided but fwnode_property_match_string() fails, it returns a
negative error code. Are we passing this negative value directly into
fwnode_property_get_reference_args(), which expects an unsigned int?

This could wrap around to a large positive integer, causing an out-of-bounds
fetch attempt that internally fails with -ENOENT. The original error code
would therefore be masked by -ENOENT.

Should this function check if (index < 0) return index; before proceeding?

> +	if (ret || (name && index < 0))
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static struct phylink_pcs *
> +fwnode_pcs_get_from_pcsspec(struct fwnode_reference_args *pcsspec)
> +{
> +	struct fwnode_pcs_provider *provider;
> +	struct phylink_pcs *pcs = ERR_PTR(-ENODEV);

[Severity: Medium]
As stated in the kernel doc for fwnode_pcs_get(), it must return
-EPROBE_DEFER if the PCS is not present in the global providers list.
Is it intentional that fwnode_pcs_get_from_pcsspec() defaults to returning
-ENODEV here when a provider is not found?

This could break the standard kernel probe deferral mechanism for the consumer
driver, preventing it from deferring its probe to wait for the PCS driver to
load if fw_devlink is disabled or bypassed.

> +
> +	if (!pcsspec)
> +		return ERR_PTR(-EINVAL);
> +
> +	mutex_lock(&fwnode_pcs_mutex);
> +	list_for_each_entry(provider, &fwnode_pcs_providers, link) {
> +		if (provider->fwnode == pcsspec->fwnode) {
> +			pcs = provider->get(pcsspec, provider->data);
> +			if (!IS_ERR(pcs))
> +				break;
> +		}
> +	}
> +	mutex_unlock(&fwnode_pcs_mutex);
> +
> +	return pcs;
> +}

Regarding the integration of this feature with the phylink subsystem, is there a
risk of unprotected list modification and iteration causing a race condition?

[Severity: High]
The pl->pcs_list appears to be protected by mismatched locks. In
phylink_release_pcs() and pcs_provider_notify(), list modifications are
performed under rtnl_lock() but without holding pl->state_mutex:

drivers/net/phy/phylink.c:phylink_release_pcs() {
    ...
    list_del(&pcs->list);
    ...
}

Concurrently, the phylink_resolve() workqueue executes
phylink_major_config(), which iterates over pl->pcs_list using
list_for_each_entry() while holding pl->state_mutex but without holding
rtnl_lock():

drivers/net/phy/phylink.c:phylink_major_config() {
    ...
    list_for_each_entry(pcs, &pl->pcs_list, list) {
    ...
}

Since list modification and iteration are not synchronized by a common lock,
could this lead to linked-list corruption, torn reads, and use-after-free
crashes if the workqueue accesses a PCS that is being removed?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=4

^ permalink raw reply

* Re: [PATCH net-next v6 02/12] net: phylink: introduce internal phylink PCS handling
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260609151212.29469-3-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:

New issues:
- [High] phylink_stop() permanently deletes PCS-supported interfaces from the phylink mask.
- [High] Inactive PCSs attached to the phylink instance can spuriously trigger link changes.
- [Medium] PCS selection logic inconsistently applies pcs_interfaces, causing validation and configuration mismatches.
- [Low] Mutual exclusion check for PCS methods is placed in a validation loop, causing excessive log spam.

Pre-existing issues:
- [High] Missing cleanup of `pcs->phylink` for legacy `.mac_select_pcs` code paths leads to Use-After-Free.
- [High] Missing `IS_ERR` check on `mac_select_pcs` return value in `phylink_inband_caps`.
--

commit 0ed3c25ac9713ad466d414b45a844576296e0982
Author: Christian Marangi <ansuelsmth@gmail.com>
Subject: net: phylink: introduce internal phylink PCS handling
Summary: This patch introduces internal PCS handling for phylink as an alternative to the MAC .mac_select_pcs callback. It allows MAC drivers to provide an array of available PCSs during creation, which phylink manages as a linked list to dynamically select and validate the appropriate PCS.

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 4d59c0dd78dbf..4d6ffda0cdd61 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
[ ... ]
> @@ -518,22 +523,59 @@ static void phylink_validate_mask_caps(unsigned long *supported,
>  	linkmode_and(state->advertising, state->advertising, mask);
>  }
>  
> +static int phylink_validate_pcs_interface(struct phylink_pcs *pcs,
> +					  phy_interface_t interface)
> +{
> +	/* If PCS define an empty supported_interfaces value, assume
> +	 * all interface are supported.
> +	 */
> +	if (phy_interface_empty(pcs->supported_interfaces))
> +		return 0;
> +
> +	/* Ensure that this PCS supports the interface mode */
> +	if (!test_bit(interface, pcs->supported_interfaces))
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
>  static int phylink_validate_mac_and_pcs(struct phylink *pl,
>  					unsigned long *supported,
>  					struct phylink_link_state *state)
>  {
> -	struct phylink_pcs *pcs = NULL;
>  	unsigned long capabilities;
> +	struct phylink_pcs *pcs;
> +	bool pcs_found = false;
>  	int ret;
>  
>  	/* Get the PCS for this interface mode */
>  	if (pl->mac_ops->mac_select_pcs) {
> +		/* Make sure either PCS internal validation or .mac_select_pcs
> +		 * is used. Return error if both are defined.
> +		 */
> +		if (!list_empty(&pl->pcs_list)) {
> +			phylink_err(pl, "either phylink_pcs_add() or .mac_select_pcs must be used\n");
> +			return -EINVAL;
> +		}

[Severity: Low]
Is this the best place for this structural check? Since
phylink_validate_mac_and_pcs() is called iteratively for every supported
interface mode during mask validation, won't a misconfigured driver
trigger this error message dozens of times? Could this check be moved to a
one-time initialization path like phylink_create()?

> +
>  		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
>  		if (IS_ERR(pcs))
>  			return PTR_ERR(pcs);
> +
> +		pcs_found = !!pcs;
> +	} else {
> +		/* Check every assigned PCS and search for one that supports
> +		 * the interface.
> +		 */
> +		list_for_each_entry(pcs, &pl->pcs_list, list) {
> +			if (!phylink_validate_pcs_interface(pcs, state->interface)) {
> +				pcs_found = true;
> +				break;
> +			}
> +		}
>  	}

[Severity: Medium]
Does this validation logic properly account for the pcs_interfaces mask?
In phylink_major_config(), the PCS list is only searched if the interface
requires a dedicated PCS via test_bit(state->interface,
pl->config->pcs_interfaces). Since this loop unconditionally checks the
PCS list, could it incorrectly constrain validation by the PCS capabilities
even if the MAC handles the interface directly?

[ ... ]
> @@ -965,12 +1004,22 @@ static unsigned int phylink_inband_caps(struct phylink *pl,
>  					 phy_interface_t interface)
>  {
>  	struct phylink_pcs *pcs;
> +	bool pcs_found = false;
>  
> -	if (!pl->mac_ops->mac_select_pcs)
> -		return 0;
> +	if (pl->mac_ops->mac_select_pcs) {
> +		pcs = pl->mac_ops->mac_select_pcs(pl->config,
> +						  interface);
> +		pcs_found = !!pcs;

[Severity: High]
This is a pre-existing issue, but does this correctly handle error
pointers returned by mac_select_pcs()? If it returns an ERR_PTR on
failure, !!pcs evaluates to true, causing phylink_pcs_inband_caps() to
dereference the error pointer later in this function. Could we add an
IS_ERR() check here?

> +	} else {
> +		list_for_each_entry(pcs, &pl->pcs_list, list) {
> +			if (!phylink_validate_pcs_interface(pcs, interface)) {
> +				pcs_found = true;
> +				break;
> +			}
> +		}
> +	}

[Severity: Medium]
Similar to phylink_validate_mac_and_pcs(), this iterates the PCS list
without checking if the interface requires a PCS via
pl->config->pcs_interfaces. Is it possible for this to return inaccurate
inband capabilities if a MAC supports an interface directly without using
an available PCS?

[ ... ]
> @@ -1953,10 +2081,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
>   */
>  void phylink_destroy(struct phylink *pl)
>  {
> +	struct phylink_pcs *pcs, *tmp;
> +
>  	sfp_bus_del_upstream(pl->sfp_bus);
>  	if (pl->link_gpio)
>  		gpiod_put(pl->link_gpio);
>  
> +	/* Remove every PCS from phylink PCS list */
> +	list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
> +		list_del(&pcs->list);
> +
>  	cancel_work_sync(&pl->resolve);
>  	kfree(pl);
>  }

[Severity: High]
This is a pre-existing issue, but is it possible for a legacy PCS to
access freed memory here? For MAC drivers using the legacy
.mac_select_pcs callback, phylink_major_config() sets pcs->phylink = pl,
but that pointer is never cleared during teardown. If the PCS driver
outlives the phylink instance and triggers phylink_pcs_change(), it would
dereference the freed phylink pointer.

[ ... ]
> @@ -2437,6 +2571,7 @@ static irqreturn_t phylink_link_handler(int irq, void *data)
>   */
>  void phylink_start(struct phylink *pl)
>  {
> +	struct phylink_pcs *pcs;
>  	bool poll = false;
>  
>  	ASSERT_RTNL();
> @@ -2463,6 +2598,10 @@ void phylink_start(struct phylink *pl)
>  
>  	pl->pcs_state = PCS_STATE_STARTED;
>  
> +	/* link available PCS to phylink struct */
> +	list_for_each_entry(pcs, &pl->pcs_list, list)
> +		pcs->phylink = pl;
> +

[Severity: High]
Does attaching all available PCSs to the phylink instance create a risk
of spurious link changes? Since all PCSs in the list receive the phylink
pointer, any of them can call phylink_pcs_change(). Because
phylink_pcs_change() does not verify if the calling PCS is the actively
selected one (pl->pcs), couldn't an interrupt from an inactive PCS
(like a loss of signal) falsely trigger a link state change and disrupt
the active connection?

[ ... ]
> @@ -2507,6 +2646,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
>   */
>  void phylink_stop(struct phylink *pl)
>  {
> +	struct phylink_pcs *pcs;
> +
>  	ASSERT_RTNL();
>  
>  	if (pl->sfp_bus)
> @@ -2524,6 +2665,14 @@ void phylink_stop(struct phylink *pl)
>  	pl->pcs_state = PCS_STATE_DOWN;
>  
>  	phylink_pcs_disable(pl->pcs);
> +
> +	/* Drop link between phylink and PCS */
> +	list_for_each_entry(pcs, &pl->pcs_list, list)
> +		pcs->phylink = NULL;
> +
> +	/* Restore original supported interfaces */
> +	phy_interface_copy(pl->supported_interfaces,
> +			   pl->config->supported_interfaces);
>  }

[Severity: High]
Will unconditionally clearing the supported interfaces permanently
break PCS capabilities on a link down/up cycle? During phylink_create(),
pl->supported_interfaces aggregates capabilities from all attached PCSs.
When phylink_stop() resets this to the base MAC capabilities, the PCS
capabilities are lost because phylink_start() does not reconstruct the
aggregated mask. Could this prevent the link from coming back up in modes
that rely on a PCS after the interface is bounced?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=2

^ permalink raw reply

* Re: [PATCH net-next v6 06/12] net: Document PCS subsystem
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260609151212.29469-7-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Late PCS registration handling is structurally broken and causes `phylink_create` to fail permanently.
- [Low] Multiple grammatical and spelling errors in the PCS subsystem documentation.
--

commit 84279427a07ae5692e53d4ed76f418a074b2aea9
Author: Christian Marangi <ansuelsmth@gmail.com>

net: Document PCS subsystem
    
This commit adds extensive documentation of the new PCS subsystem and the
fwnode implementation with producer/consumer API.

> diff --git a/Documentation/networking/pcs.rst b/Documentation/networking/pcs.rst
> new file mode 100644
> index 0000000000000..9436ba43cebdf
> --- /dev/null
> +++ b/Documentation/networking/pcs.rst

[ ... ]

> +Looking up PCS Devices (fwnode implementation)
> +-----------------------------------------------
> +
> +The lookup of a PCS device follows the common producer/consumer implementation
> +used by similar subsystem with a ``#pcs-cells`` on the producer and a
> +``pcs-handle`` property on the consumer::

[Severity: Low]
This isn't a bug, but there are a few minor grammatical and spelling errors in
the newly added documentation. Should "subsystem" be "subsystems" here?

Additionally, a few other minor typos are present in this document:
- "The fwnode implementation expose" -> "exposes"
- "should be used to relase them" -> "release"
- "takes three arg" -> "takes three arguments"

[ ... ]

> +Late PCS registration handling
> +------------------------------
> +
> +It's possible that a PCS becomes available after the MAC finished probing.
> +Contrary to the usual producer/consumer implementation, when a PCS is not
> +registered and can't be found, the fwnode parser helper returns ``-EINVAL``
> +instead of ``-EPROBE_DEFER``.
> +
> +This is to prevent race condition with particular devices that register
> +MAC and PCS with USB or PCIe and require the MAC to be registered before
> +the PCS.
> +
> +The phylink logic correctly handle this special case and keep the phylink
> +instance in a fail condition.

[Severity: High]
Is this description of the failure state accurate with the current
implementation?

Looking at phylink_create() in drivers/net/phy/phylink.c, if a PCS is
missing, it appears to abort the creation entirely:

phylink_create() {
    ...
    ret = phylink_fill_available_pcs(pl, config);
    if (ret) {
        kfree(pl);
        return ERR_PTR(ret);
    }
    ...
}

Similarly, if fwnode_phylink_pcs_parse() in drivers/net/pcs/pcs.c cannot
find a PCS, it returns an error:

fwnode_phylink_pcs_parse() {
    ...
    pcs = fwnode_pcs_get(fwnode, i);
    if (IS_ERR(pcs))
        return PTR_ERR(pcs);
    ...
}

If this error propagates up through the MAC driver's fill_available_pcs()
callback to phylink_fill_available_pcs(), wouldn't phylink_create()
immediately free the phylink instance and return the error before the late
registration notifier is ever registered?

Could you clarify how the instance is kept in a fail condition if it gets
freed during phylink_create()?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=6

^ permalink raw reply

* Re: [PATCH net-next v6 01/12] net: phylink: keep and use MAC supported_interfaces in phylink struct
From: sashiko-bot @ 2026-06-10 16:16 UTC (permalink / raw)
  To: Christian Marangi; +Cc: devicetree, conor+dt, robh
In-Reply-To: <20260609151212.29469-2-ansuelsmth@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Late modification to `config->supported_interfaces` by drivers is silently ignored due to by-value copying in `phylink_create()`, breaking support for devices like MTK GMAC2.
--

commit eaea53b185dbdc4c75406c542c97543c4c451c9d
Author: Christian Marangi <ansuelsmth@gmail.com>

net: phylink: keep and use MAC supported_interfaces in phylink struct

This patch copies supported_interfaces from phylink_config into the phylink
structure to prepare for internal PCS handling. The internal copy is then
used in place of the config structure's bitmap.

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 087ac63f9193d..4d59c0dd78dbf 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
[ ... ]
> @@ -1868,6 +1873,9 @@ struct phylink *phylink_create(struct phylink_config *config,
>  	mutex_init(&pl->state_mutex);
>  	INIT_WORK(&pl->resolve, phylink_resolve);
>  
> +	phy_interface_copy(pl->supported_interfaces,
> +			   config->supported_interfaces);
> +

[Severity: High]
Will this by-value copy break drivers that modify supported_interfaces after
calling phylink_create()?

For example, looking at the mtk_eth_soc driver in mtk_add_mac(), the
supported_interfaces bitmap is modified after phylink_create() returns:

drivers/net/ethernet/mediatek/mtk_eth_soc.c:mtk_add_mac() {
        ...
        mac->phylink = phylink_create(&mac->phylink_config, ...);
        ...
        if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_2P5GPHY) &&
            id == MTK_GMAC2_ID)
                __set_bit(PHY_INTERFACE_MODE_INTERNAL,
                          mac->phylink_config.supported_interfaces);
        ...
}

Since phylink_create() has already taken a snapshot of the interfaces,
this late addition of PHY_INTERFACE_MODE_INTERNAL will be dropped. 
Could this cause phylink_validate() to incorrectly reject the internal PHY
capability for the MTK GMAC2?

>  	pl->config = config;
>  	if (config->type == PHYLINK_NETDEV) {
>  		pl->netdev = to_net_dev(config->dev);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260609151212.29469-1-ansuelsmth@gmail.com?part=1

^ permalink raw reply

* [PATCH v4 3/3] arm64: dts: nuvoton: npcm845-evb: Add peripheral nodes
From: Tomer Maimon @ 2026-06-10 16:08 UTC (permalink / raw)
  To: andrew, robh, krzk+dt, conor+dt
  Cc: openbmc, devicetree, linux-kernel, avifishman70, tmaimon77,
	tali.perry1, venture, yuenn, benjaminfair
In-Reply-To: <20260610160816.2828713-1-tmaimon77@gmail.com>

Enable peripheral support for the NPCM845 evaluation board by wiring up
the SoC controller nodes to the board design. Add board aliases,
GPIO-backed MDIO buses for the external PHYs, a reserved-memory region
for TIP firmware, and flash partitions for the FIU boot flash. Enable
the Ethernet MACs, MMC controller, FIU controllers, USB device
controllers, ADC, RNG, PWM/FAN controller, I2C buses, and PECI
interface with the board-specific pinctrl and PHY connections they
require.

Signed-off-by: Tomer Maimon <tmaimon77@gmail.com>
---
 .../boot/dts/nuvoton/nuvoton-npcm845-evb.dts  | 413 ++++++++++++++++++
 1 file changed, 413 insertions(+)

diff --git a/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts b/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts
index 5edf5d13342d..ea92888d9b9e 100644
--- a/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts
+++ b/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845-evb.dts
@@ -10,6 +10,42 @@ / {
 
 	aliases {
 		serial0 = &serial0;
+		ethernet1 = &gmac1;
+		ethernet2 = &gmac2;
+		ethernet3 = &gmac3;
+		mdio-gpio0 = &mdio0;
+		mdio-gpio1 = &mdio1;
+		fiu0 = &fiu0;
+		fiu1 = &fiu3;
+		fiu2 = &fiux;
+		fiu3 = &fiu1;
+		i2c0 = &i2c0;
+		i2c1 = &i2c1;
+		i2c2 = &i2c2;
+		i2c3 = &i2c3;
+		i2c4 = &i2c4;
+		i2c5 = &i2c5;
+		i2c6 = &i2c6;
+		i2c7 = &i2c7;
+		i2c8 = &i2c8;
+		i2c9 = &i2c9;
+		i2c10 = &i2c10;
+		i2c11 = &i2c11;
+		i2c12 = &i2c12;
+		i2c13 = &i2c13;
+		i2c14 = &i2c14;
+		i2c15 = &i2c15;
+		i2c16 = &i2c16;
+		i2c17 = &i2c17;
+		i2c18 = &i2c18;
+		i2c19 = &i2c19;
+		i2c20 = &i2c20;
+		i2c21 = &i2c21;
+		i2c22 = &i2c22;
+		i2c23 = &i2c23;
+		i2c24 = &i2c24;
+		i2c25 = &i2c25;
+		i2c26 = &i2c26;
 	};
 
 	chosen {
@@ -26,12 +62,389 @@ refclk: refclk-25mhz {
 		clock-frequency = <25000000>;
 		#clock-cells = <0>;
 	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		tip_reserved: tip@0 {
+			reg = <0x0 0x0 0x0 0x6200000>;
+		};
+	};
+
+	mdio0: mdio-0 {
+		compatible = "virtual,mdio-gpio";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		gpios = <&gpio1 25 GPIO_ACTIVE_HIGH>,
+			<&gpio1 26 GPIO_ACTIVE_HIGH>;
+
+		phy0: ethernet-phy@0 {
+			reg = <0>;
+		};
+	};
+
+	mdio1: mdio-1 {
+		compatible = "virtual,mdio-gpio";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		gpios = <&gpio2 27 GPIO_ACTIVE_HIGH>,
+			<&gpio2 28 GPIO_ACTIVE_HIGH>;
+
+		phy1: ethernet-phy@0 {
+			reg = <0>;
+		};
+	};
+};
+
+&gmac1 {
+	phy-mode = "rgmii-id";
+	snps,eee-force-disable;
+	status = "okay";
+};
+
+&gmac2 {
+	phy-mode = "rmii";
+	pinctrl-names = "default";
+	pinctrl-0 = <&r1_pins
+			&r1oen_pins>;
+	phy-handle = <&phy0>;
+	status = "okay";
+};
+
+&gmac3 {
+	phy-mode = "rmii";
+	pinctrl-names = "default";
+	pinctrl-0 = <&r2_pins
+			&r2oen_pins>;
+	phy-handle = <&phy1>;
+	status = "okay";
 };
 
 &serial0 {
 	status = "okay";
 };
 
+&fiu0 {
+	status = "okay";
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		spi-rx-bus-width = <1>;
+		reg = <0>;
+		spi-max-frequency = <5000000>;
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+			bmc@0 {
+				label = "bmc";
+				reg = <0x00000000 0x04000000>;
+			};
+			u-boot@0 {
+				label = "u-boot";
+				reg = <0x00000000 0x007C0000>;
+			};
+			u-boot-env@7c0000 {
+				label = "u-boot-env";
+				reg = <0x007C0000 0x00040000>;
+			};
+			kernel@800000 {
+				label = "kernel";
+				reg = <0x00800000 0x00800000>;
+			};
+			rofs@1000000 {
+				label = "rofs";
+				reg = <0x01000000 0x02C00000>;
+			};
+			rwfs@3c00000 {
+				label = "rwfs";
+				reg = <0x03C00000 0x00400000>;
+			};
+		};
+	};
+};
+
+&fiu1 {
+	status = "okay";
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		spi-rx-bus-width = <2>;
+		spi-tx-bus-width = <2>;
+		reg = <0>;
+		spi-max-frequency = <5000000>;
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+			partition@0 {
+				label = "spi1-system1";
+				reg = <0x0 0x0>;
+			};
+		};
+	};
+};
+
+&fiu3 {
+	pinctrl-0 = <&spi3_pins>, <&spi3quad_pins>;
+	status = "okay";
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		spi-rx-bus-width = <1>;
+		reg = <0>;
+		spi-max-frequency = <5000000>;
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+			partition@0 {
+				label = "spi3-system1";
+				reg = <0x0 0x0>;
+			};
+		};
+	};
+};
+
+&fiux {
+	spix-mode;
+};
+
+&sdhci {
+	status = "okay";
+};
+
+&udc0 {
+	status = "okay";
+};
+
+&udc1 {
+	status = "okay";
+};
+
+&udc2 {
+	status = "okay";
+};
+
+&udc3 {
+	status = "okay";
+};
+
+&udc4 {
+	status = "okay";
+};
+
+&udc5 {
+	status = "okay";
+};
+
+&udc6 {
+	status = "okay";
+};
+
+&udc7 {
+	status = "okay";
+};
+
+&mc {
+	status = "okay";
+};
+
+&peci {
+	status = "okay";
+};
+
+&rng {
+	status = "okay";
+};
+
+&adc {
+	#io-channel-cells = <1>;
+	status = "okay";
+};
+
 &watchdog1 {
 	status = "okay";
 };
+
+&pwm_fan {
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&pwm0_pins &pwm1_pins
+		&pwm2_pins &pwm3_pins
+		&pwm4_pins &pwm5_pins
+		&pwm6_pins &pwm7_pins
+		&fanin0_pins &fanin1_pins
+		&fanin2_pins &fanin3_pins
+		&fanin4_pins &fanin5_pins
+		&fanin6_pins &fanin7_pins>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+	fan@0 {
+		reg = <0x00>;
+		fan-tach-ch = <0x0 0x1>;
+		cooling-levels = <127 255>;
+	};
+	fan@1 {
+		reg = <0x01>;
+		fan-tach-ch = <0x2 0x3>;
+		cooling-levels = <127 255>;
+	};
+	fan@2 {
+		reg = <0x02>;
+		fan-tach-ch = <0x4 0x5>;
+		cooling-levels = <127 255>;
+	};
+	fan@3 {
+		reg = <0x03>;
+		fan-tach-ch = <0x6 0x7>;
+		cooling-levels = <127 255>;
+	};
+	fan@4 {
+		reg = <0x04>;
+		fan-tach-ch = <0x8 0x9>;
+		cooling-levels = <127 255>;
+	};
+	fan@5 {
+		reg = <0x05>;
+		fan-tach-ch = <0xa 0xb>;
+		cooling-levels = <127 255>;
+	};
+	fan@6 {
+		reg = <0x06>;
+		fan-tach-ch = <0xc 0xd>;
+		cooling-levels = <127 255>;
+	};
+	fan@7 {
+		reg = <0x07>;
+		fan-tach-ch = <0xe 0xf>;
+		cooling-levels = <127 255>;
+	};
+};
+
+&i2c0 {
+	status = "okay";
+};
+
+&i2c1 {
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+	eeprom@50 {
+		compatible = "atmel,24c256";
+		reg = <0x50>;
+	};
+};
+
+&i2c2 {
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+	ipmb@10 {
+		compatible = "ipmb-dev";
+		reg = <0x10>;
+		i2c-protocol;
+	};
+};
+
+&i2c3 {
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+	ipmb@11 {
+		compatible = "ipmb-dev";
+		reg = <0x11>;
+		i2c-protocol;
+	};
+};
+
+&i2c4 {
+	status = "okay";
+};
+
+&i2c5 {
+	status = "okay";
+};
+
+&i2c6 {
+	status = "okay";
+};
+
+&i2c7 {
+	status = "okay";
+};
+
+&i2c8 {
+	status = "okay";
+};
+
+&i2c9 {
+	status = "okay";
+};
+
+&i2c10 {
+	status = "okay";
+};
+
+&i2c11 {
+	status = "okay";
+};
+
+&i2c12 {
+	status = "okay";
+};
+
+&i2c13 {
+	status = "okay";
+};
+
+&i2c14 {
+	status = "okay";
+};
+
+&i2c15 {
+	status = "okay";
+};
+
+&i2c16 {
+	status = "okay";
+};
+
+&i2c17 {
+	status = "okay";
+};
+
+&i2c18 {
+	status = "okay";
+};
+
+&i2c19 {
+	status = "okay";
+};
+
+&i2c20 {
+	status = "okay";
+};
+
+&i2c21 {
+	status = "okay";
+};
+
+&i2c22 {
+	status = "okay";
+};
+
+&i2c23 {
+	status = "okay";
+};
+
+&i2c24 {
+	status = "okay";
+};
+
+&i2c25 {
+	status = "okay";
+};
+
+&i2c26 {
+	status = "okay";
+};
-- 
2.34.1

^ permalink raw reply related

* [PATCH v4 2/3] arm64: dts: nuvoton: npcm845: Add peripheral nodes
From: Tomer Maimon @ 2026-06-10 16:08 UTC (permalink / raw)
  To: andrew, robh, krzk+dt, conor+dt
  Cc: openbmc, devicetree, linux-kernel, avifishman70, tmaimon77,
	tali.perry1, venture, yuenn, benjaminfair
In-Reply-To: <20260610160816.2828713-1-tmaimon77@gmail.com>

Extend the NPCM845 SoC DTSI with the peripheral controller nodes needed
by the evaluation board and downstream platforms. Add the Ethernet MACs,
USB device controllers and PHY, MMC controller, FIU controllers, memory
controller, RNG, ADC, PWM/FAN controller, and I2C buses. Also add the
OP-TEE firmware node needed to describe these blocks.

Signed-off-by: Tomer Maimon <tmaimon77@gmail.com>
---
 .../dts/nuvoton/nuvoton-common-npcm8xx.dtsi   | 700 +++++++++++++++++-
 .../boot/dts/nuvoton/nuvoton-npcm845.dtsi     |  11 +-
 2 files changed, 700 insertions(+), 11 deletions(-)

diff --git a/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi b/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
index 7976baafb994..1e21b2704784 100644
--- a/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
@@ -4,6 +4,7 @@
 #include <dt-bindings/clock/nuvoton,npcm845-clk.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/gpio/gpio.h>
 
 / {
 	#address-cells = <2>;
@@ -35,6 +36,11 @@ gic: interrupt-controller@dfff9000 {
 		};
 	};
 
+	udc0_phy: usb-phy {
+		compatible = "usb-nop-xceiv";
+		#phy-cells = <0>;
+	};
+
 	ahb {
 		#address-cells = <2>;
 		#size-cells = <2>;
@@ -51,7 +57,255 @@ clk: rstc: reset-controller@f0801000 {
 			#clock-cells = <1>;
 		};
 
-		apb {
+		gmac1: ethernet@f0804000 {
+			device_type = "network";
+			compatible = "snps,dwmac-3.72a", "snps,dwmac";
+			reg = <0x0 0xf0804000 0x0 0x2000>;
+			interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "macirq";
+			clocks	= <&clk NPCM8XX_CLK_AHB>;
+			clock-names = "stmmaceth";
+			pinctrl-names = "default";
+			pinctrl-0 = <&rg2_pins
+					&rg2mdio_pins>;
+			status = "disabled";
+		};
+
+		gmac2: ethernet@f0806000 {
+			device_type = "network";
+			compatible = "snps,dwmac-3.72a", "snps,dwmac";
+			reg = <0x0 0xf0806000 0x0 0x2000>;
+			interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "macirq";
+			clocks	= <&clk NPCM8XX_CLK_AHB>;
+			clock-names = "stmmaceth";
+			pinctrl-names = "default";
+			pinctrl-0 = <&r1_pins
+					&r1err_pins
+					&r1md_pins>;
+			status = "disabled";
+		};
+
+		gmac3: ethernet@f0808000 {
+			device_type = "network";
+			compatible = "snps,dwmac-3.72a", "snps,dwmac";
+			reg = <0x0 0xf0808000 0x0 0x2000>;
+			interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "macirq";
+			clocks	= <&clk NPCM8XX_CLK_AHB>;
+			clock-names = "stmmaceth";
+			pinctrl-names = "default";
+			pinctrl-0 = <&r2_pins
+					&r2err_pins
+					&r2md_pins>;
+			status = "disabled";
+		};
+
+		mc: memory-controller@f0824000 {
+			compatible = "nuvoton,npcm845-memory-controller";
+			reg = <0x0 0xf0824000 0x0 0x1000>;
+			interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
+		};
+
+		udc0: usb@f0830000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0830000 0x0 0x1000
+			       0x0 0xfffeb000 0x0 0x800>;
+			interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc1: usb@f0831000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0831000 0x0 0x1000
+			       0x0 0xfffeb800 0x0 0x800>;
+			interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc2: usb@f0832000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0832000 0x0 0x1000
+			       0x0 0xfffec000 0x0 0x800>;
+			interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc3: usb@f0833000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0833000 0x0 0x1000
+			       0x0 0xfffec800 0x0 0x800>;
+			interrupts = <GIC_SPI 54 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc4: usb@f0834000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0834000 0x0 0x1000
+			       0x0 0xfffed000 0x0 0x800>;
+			interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc5: usb@f0835000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0835000 0x0 0x1000
+			       0x0 0xfffed800 0x0 0x800>;
+			interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc6: usb@f0836000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0836000 0x0 0x1000
+			       0x0 0xfffee000 0x0 0x800>;
+			interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc7: usb@f0837000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0837000 0x0 0x1000
+			       0x0 0xfffee800 0x0 0x800>;
+			interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc8: usb@f0838000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0838000 0x0 0x1000
+			       0x0 0xfffef000 0x0 0x800>;
+			interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			nuvoton,sysgcr = <&gcr 0x9C 0xC000 0xC000>;
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		udc9: usb@f0839000 {
+			compatible = "nuvoton,npcm845-udc", "nuvoton,npcm750-udc";
+			reg = <0x0 0xf0839000 0x0 0x1000
+			       0x0 0xfffef800 0x0 0x800>;
+			interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_SU>;
+			clock-names = "clk_usb_bridge";
+
+			nuvoton,sysgcr = <&gcr 0x9C 0x3000 0x3000>;
+			phys = <&udc0_phy>;
+			phy_type = "utmi_wide";
+			dr_mode = "peripheral";
+			status = "disabled";
+		};
+
+		sdhci: mmc@f0842000 {
+			compatible = "nuvoton,npcm845-sdhci";
+			reg = <0x0 0xf0842000 0x0 0x100>;
+			interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk NPCM8XX_CLK_AHB>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&mmc8_pins
+				     &mmc_pins>;
+			status = "disabled";
+		};
+
+		fiu0: spi@fb000000 {
+			compatible = "nuvoton,npcm845-fiu";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x0 0xfb000000 0x0 0x1000>;
+			reg-names = "control";
+			clocks =  <&clk NPCM8XX_CLK_SPI0>;
+			status = "disabled";
+		};
+
+		fiu1: spi@fb002000 {
+			compatible = "nuvoton,npcm845-fiu";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x0 0xfb002000 0x0 0x1000>;
+			reg-names = "control";
+			clocks =  <&clk NPCM8XX_CLK_SPI1>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins>;
+			status = "disabled";
+		};
+
+		fiu3: spi@c0000000 {
+			compatible = "nuvoton,npcm845-fiu";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x0 0xc0000000 0x0 0x1000>;
+			reg-names = "control";
+			clocks =  <&clk NPCM8XX_CLK_SPI3>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi3_pins>;
+			status = "disabled";
+		};
+
+		fiux: spi@fb001000 {
+			compatible = "nuvoton,npcm845-fiu";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x0 0xfb001000 0x0 0x1000>,
+			<0x0 0xf8000000 0x0 0x2000000>;
+			reg-names = "control", "memory";
+			clocks =  <&clk NPCM8XX_CLK_SPIX>;
+			status = "disabled";
+		};
+
+		apb: bus@f0000000 {
 			#address-cells = <1>;
 			#size-cells = <1>;
 			compatible = "simple-bus";
@@ -59,14 +313,6 @@ apb {
 			ranges = <0x0 0x0 0xf0000000 0x00300000>,
 				<0xfff00000 0x0 0xfff00000 0x00016000>;
 
-			timer0: timer@8000 {
-				compatible = "nuvoton,npcm845-timer";
-				interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
-				reg = <0x8000 0x1C>;
-				clocks = <&refclk>;
-				clock-names = "refclk";
-			};
-
 			serial0: serial@0 {
 				compatible = "nuvoton,npcm845-uart", "nuvoton,npcm750-uart";
 				reg = <0x0 0x1000>;
@@ -170,6 +416,442 @@ peci: peci-controller@100000 {
 				cmd-timeout-ms = <1000>;
 				status = "disabled";
 			};
+
+			rng: rng@b000 {
+				compatible = "nuvoton,npcm845-rng";
+				reg = <0xb000 0x8>;
+				status = "disabled";
+			};
+
+			adc: adc@c000 {
+				compatible = "nuvoton,npcm845-adc";
+				reg = <0xC000 0x8>;
+				interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&clk NPCM8XX_CLK_ADC>;
+				resets = <&rstc 0x20 27>;
+				status = "disabled";
+			};
+
+			i2c0: i2c@80000 {
+				reg = <0x80000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb0_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c1: i2c@81000 {
+				reg = <0x81000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb1_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c2: i2c@82000 {
+				reg = <0x82000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb2_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c3: i2c@83000 {
+				reg = <0x83000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb3_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c4: i2c@84000 {
+				reg = <0x84000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 132 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb4_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c5: i2c@85000 {
+				reg = <0x85000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb5_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c6: i2c@86000 {
+				reg = <0x86000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb6_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c7: i2c@87000 {
+				reg = <0x87000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 135 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb7_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c8: i2c@88000 {
+				reg = <0x88000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 136 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb8_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c9: i2c@89000 {
+				reg = <0x89000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 137 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb9_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c10: i2c@8a000 {
+				reg = <0x8a000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 138 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb10_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c11: i2c@8b000 {
+				reg = <0x8b000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb11_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c12: i2c@8c000 {
+				reg = <0x8c000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb12_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c13: i2c@8d000 {
+				reg = <0x8d000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 141 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb13_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c14: i2c@8e000 {
+				reg = <0x8e000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 142 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb14_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c15: i2c@8f000 {
+				reg = <0x8f000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 143 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb15_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c16: i2c@fff00000 {
+				reg = <0xfff00000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb16_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c17: i2c@fff01000 {
+				reg = <0xfff01000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 145 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb17_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c18: i2c@fff02000 {
+				reg = <0xfff02000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb18_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c19: i2c@fff03000 {
+				reg = <0xfff03000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb19_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c20: i2c@fff04000 {
+				reg = <0xfff04000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb20_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c21: i2c@fff05000 {
+				reg = <0xfff05000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb21_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c22: i2c@fff06000 {
+				reg = <0xfff06000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 150 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb22_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c23: i2c@fff07000 {
+				reg = <0xfff07000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 151 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&smb23_pins>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c24: i2c@fff08000 {
+				reg = <0xfff08000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c25: i2c@fff09000 {
+				reg = <0xfff09000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 153 IRQ_TYPE_LEVEL_HIGH>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			i2c26: i2c@fff0a000 {
+				reg = <0xfff0a000 0x1000>;
+				compatible = "nuvoton,npcm845-i2c";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				clocks =  <&clk NPCM8XX_CLK_APB2>;
+				clock-frequency = <100000>;
+				interrupts = <GIC_SPI 154 IRQ_TYPE_LEVEL_HIGH>;
+				nuvoton,sys-mgr = <&gcr>;
+				status = "disabled";
+			};
+
+			pwm_fan:pwm-fan-controller@103000 {
+				compatible = "nuvoton,npcm845-pwm-fan";
+				reg = <0x103000 0x3000>,
+					<0x180000 0x8000>;
+				reg-names = "pwm", "fan";
+				clocks =  <&clk NPCM8XX_CLK_APB3>,
+					<&clk NPCM8XX_CLK_APB4>;
+				clock-names = "pwm","fan";
+				interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pwm0_pins &pwm1_pins
+						&pwm2_pins &pwm3_pins
+						&pwm4_pins &pwm5_pins
+						&pwm6_pins &pwm7_pins
+						&pwm8_pins &pwm9_pins
+						&pwm10_pins &pwm11_pins
+						&fanin0_pins &fanin1_pins
+						&fanin2_pins &fanin3_pins
+						&fanin4_pins &fanin5_pins
+						&fanin6_pins &fanin7_pins
+						&fanin8_pins &fanin9_pins
+						&fanin10_pins &fanin11_pins
+						&fanin12_pins &fanin13_pins
+						&fanin14_pins &fanin15_pins>;
+				status = "disabled";
+			};
+
+			pspi: spi@201000 {
+				compatible = "nuvoton,npcm845-pspi";
+				reg = <0x201000 0x1000>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pspi_pins>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+				interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
+				clocks =  <&clk NPCM8XX_CLK_APB5>;
+				clock-names = "clk_apb5";
+				resets = <&rstc 0x24 23>;
+				status = "disabled";
+			};
+
 		};
 	};
 
diff --git a/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845.dtsi b/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845.dtsi
index 8239d9a9f0d2..21dea323612d 100644
--- a/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/nuvoton-npcm845.dtsi
@@ -64,8 +64,8 @@ arm-pmu {
 	};
 
 	psci {
-		compatible = "arm,psci-1.0";
-		method = "smc";
+		compatible      = "arm,psci-1.0";
+		method          = "smc";
 	};
 
 	timer {
@@ -75,4 +75,11 @@ timer {
 			     <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
 			     <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
 	};
+
+	firmware {
+		optee {
+			compatible = "linaro,optee-tz";
+			method = "smc";
+		};
+	};
 };
-- 
2.34.1

^ permalink raw reply related

* [PATCH v4 1/3] arm64: dts: nuvoton: npcm845: Reorder timer0 and PECI nodes
From: Tomer Maimon @ 2026-06-10 16:08 UTC (permalink / raw)
  To: andrew, robh, krzk+dt, conor+dt
  Cc: openbmc, devicetree, linux-kernel, avifishman70, tmaimon77,
	tali.perry1, venture, yuenn, benjaminfair
In-Reply-To: <20260610160816.2828713-1-tmaimon77@gmail.com>

Move the timer0 and PECI nodes so the APB children are ordered by
ascending unit address. Keep the existing timer0 clock-names property
unchanged.

Signed-off-by: Tomer Maimon <tmaimon77@gmail.com>
---
 .../dts/nuvoton/nuvoton-common-npcm8xx.dtsi   | 26 ++++++++++++-------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi b/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
index c781190b42c5..7976baafb994 100644
--- a/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/nuvoton-common-npcm8xx.dtsi
@@ -59,15 +59,6 @@ apb {
 			ranges = <0x0 0x0 0xf0000000 0x00300000>,
 				<0xfff00000 0x0 0xfff00000 0x00016000>;
 
-			peci: peci-controller@100000 {
-				compatible = "nuvoton,npcm845-peci";
-				reg = <0x100000 0x1000>;
-				interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
-				clocks = <&clk NPCM8XX_CLK_APB3>;
-				cmd-timeout-ms = <1000>;
-				status = "disabled";
-			};
-
 			timer0: timer@8000 {
 				compatible = "nuvoton,npcm845-timer";
 				interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
@@ -139,6 +130,14 @@ serial6: serial@6000 {
 				status = "disabled";
 			};
 
+			timer0: timer@8000 {
+				compatible = "nuvoton,npcm845-timer";
+				interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
+				reg = <0x8000 0x1C>;
+				clocks = <&refclk>;
+				clock-names = "refclk";
+			};
+
 			watchdog0: watchdog@801c {
 				compatible = "nuvoton,npcm845-wdt", "nuvoton,npcm750-wdt";
 				interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
@@ -162,6 +161,15 @@ watchdog2: watchdog@a01c {
 				status = "disabled";
 				clocks = <&refclk>;
 			};
+
+			peci: peci-controller@100000 {
+				compatible = "nuvoton,npcm845-peci";
+				reg = <0x100000 0x1000>;
+				interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&clk NPCM8XX_CLK_APB3>;
+				cmd-timeout-ms = <1000>;
+				status = "disabled";
+			};
 		};
 	};
 
-- 
2.34.1

^ permalink raw reply related

* [PATCH v4 0/3] arm64: dts: nuvoton: add NPCM845 SoC and EVB support
From: Tomer Maimon @ 2026-06-10 16:08 UTC (permalink / raw)
  To: andrew, robh, krzk+dt, conor+dt
  Cc: openbmc, devicetree, linux-kernel, avifishman70, tmaimon77,
	tali.perry1, venture, yuenn, benjaminfair

This series fix warnings and adds device tree support for peripherals on
the Nuvoton NPCM845 SoC and its Evaluation Board (EVB).
The first patch fix warning and arrange node order.
The second patch introduces peripheral nodes for Ethernet, MMC, SPI, USB,
RNG, ADC, PWM-FAN, I2C, and OP-TEE firmware in the NPCM845 SoC device tree.
The third patch enables these peripherals for the NPCM845-EVB, adding
MDIO nodes, reserved memory, aliases, and board-specific configurations
like PHY modes and SPI flash partitions.

The NPCM8XX device tree tested on NPCM845 evaluation board.

This series depends on:
https://lore.kernel.org/all/20260610121822.2524634-2-tmaimon77@gmail.com/
https://lore.kernel.org/all/20260610121822.2524634-3-tmaimon77@gmail.com/
https://lore.kernel.org/all/20260610121822.2524634-4-tmaimon77@gmail.com/

Addressed comments from:
	- Rob Herring: https://patchwork.ozlabs.org/project/openbmc/cover/20250925200625.573902-1-tmaimon77@gmail.com/#3588084

Changes since version 3:
        - reomve tmp100.

Changes since version 2:
        - Fix dts warning
        - Arrange node order by ascending unit address.

Changes since version 1:
        - Fix commit message
        - Fix dtbs_check warnings.

Tomer Maimon (3):
  arm64: dts: nuvoton: npcm845: Reorder timer0 and PECI nodes
  arm64: dts: nuvoton: npcm845: Add peripheral nodes
  arm64: dts: nuvoton: npcm845-evb: Add peripheral nodes

 .../dts/nuvoton/nuvoton-common-npcm8xx.dtsi   | 726 +++++++++++++++++-
 .../boot/dts/nuvoton/nuvoton-npcm845-evb.dts  | 413 ++++++++++
 .../boot/dts/nuvoton/nuvoton-npcm845.dtsi     |  11 +-
 3 files changed, 1130 insertions(+), 20 deletions(-)

-- 
2.34.1

^ permalink raw reply

* Re: [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver
From: sashiko-bot @ 2026-06-10 16:08 UTC (permalink / raw)
  To: Prasad Kumpatla; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260610155708.151067-3-prasad.kumpatla@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Manual snd_soc_component_exit_regmap() call on a devm-managed regmap causes a double free and potential Use-After-Free.
- [High] Volume control changes do not take effect dynamically during active playback.
- [Medium] ALSA Kcontrol .put callbacks unconditionally return 0 instead of 1 when values are successfully changed.
- [Medium] Shared configuration variables are modified by ALSA kcontrols and read by DAI callbacks without locking.
- [Low] Passing 'int *' to regmap_read() causes incompatible pointer type compiler warnings.
--

commit e03e869e67ca57141b83b27db8520ee9f2d72031
Author: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
Date:   Wed Jun 10 15:57:08 2026 +0000

ASoC: codecs: add Qualcomm WSA885X I2C codec driver

Add an ASoC codec driver for the Qualcomm WSA885X smart speaker
amplifier accessed over I2C. The driver provides the control-side
support needed for playback bring-up, including register programming,
serial interface setup, clock handling, mute and gain control, reset
handling and interrupt support.

> diff --git a/sound/soc/codecs/wsa885x-i2c.c b/sound/soc/codecs/wsa885x-i2c.c
> new file mode 100644
> index 0000000000000..a7d8f8d48a21c
> --- /dev/null
> +++ b/sound/soc/codecs/wsa885x-i2c.c
[ ... ]
> +static int wsa885x_wait_for_pde_state(struct wsa885x_i2c_priv *wsa885x, int ps)
> +{
> +	int act_ps = -1, cnt = 0, clock_valid = -1;
> +	int rc = 0;
> +
> +	if (!wsa885x || !wsa885x->regmap)
> +		return -EINVAL;
> +
> +	if (ps < 0 || ps > 3)
> +		return -EINVAL;
> +
> +	do {
> +		usleep_range(1000, 1500);
> +		rc = regmap_read(wsa885x->regmap,
> +				 WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS,
> +				 &act_ps);

[Severity: Low]
Does this trigger a compiler warning? regmap_read() expects an unsigned
int * for its third argument, but act_ps is declared as an int. Should
act_ps and clock_valid be declared as unsigned int to match the API
signature?

[ ... ]
> +static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
> +{
[ ... ]
> +	} else {
> +		regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs,
> +				       ARRAY_SIZE(unmute_prep_head_regs));
> +		regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE,
> +			     wsa885x->usage_mode);
> +		regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs,
> +				       ARRAY_SIZE(unmute_prep_tail_regs));
> +		regmap_write(wsa885x->regmap,
> +			     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
> +			     (s8)wsa885x->stereo_vol_db);
> +		regmap_write(wsa885x->regmap,
> +			     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB,
> +			     (s8)wsa885x->stereo_vol_db);

[Severity: Medium]
Can this lead to a race condition where mismatched channel volumes are
applied? The stereo_vol_db is read twice sequentially here without lock
protection.

If a concurrent call to wsa885x_stereo_gain_offset_put() occurs, could it
update stereo_vol_db exactly between these two regmap writes?

[ ... ]
> +static void wsa885x_component_remove(struct snd_soc_component *component)
> +{
> +	if (!component)
> +		return;
> +
> +	snd_soc_component_exit_regmap(component);
> +}

[Severity: Critical]
Is it safe to manually exit the regmap here? Since the regmap was allocated
using devm_regmap_init_i2c(), devres will attempt to free it automatically.
Calling snd_soc_component_exit_regmap() here causes an early free, which
can result in a double free.

Additionally, because the interrupt handler is registered via
devm_request_threaded_irq() before the component is registered, the IRQ
remains active after this component removal. If the interrupt fires in this
window, will wsa885x_interrupt_handler() trigger a use-after-free when
accessing wsa885x->regmap?

[ ... ]
> +static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol,
> +					  struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component;
> +	struct wsa885x_i2c_priv *wsa885x;
> +	long val;
> +
> +	if (!kcontrol || !ucontrol)
> +		return -EINVAL;
> +
> +	component = snd_kcontrol_chip(kcontrol);
> +	if (!component)
> +		return -EINVAL;
> +
> +	wsa885x = snd_soc_component_get_drvdata(component);
> +	if (!wsa885x)
> +		return -EINVAL;
> +
> +	val = ucontrol->value.integer.value[0];
> +
> +	if (val < 0 || val > WSA885X_FU21_VOL_STEPS) {
> +		dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val);
> +		return -EINVAL;
> +	}
> +	wsa885x->stereo_vol_db = (int)val - 84;
> +	return 0;
> +}

[Severity: High]
Will this update take effect immediately during playback? The cached
volume is updated, but it doesn't look like the new value is written to the
hardware registers here. Are users expected to pause and restart the stream
for the volume change to apply?

[Severity: Medium]
Should this return 1 instead of 0 when the value actually changes?
ALSA kcontrol .put callbacks are expected to return 1 if the control was
modified so the core can generate a state change event for userspace.

[ ... ]
> +static int wsa885x_i2c_usage_modes_put(struct snd_kcontrol *kcontrol,
> +				       struct snd_ctl_elem_value *ucontrol)
> +{
[ ... ]
> +	val = ucontrol->value.integer.value[0];
> +
> +	if (val < 0 || val > WSA885X_USAGE_MODE_MAX)
> +		return -EINVAL;
> +
> +	wsa885x_i2c->usage_mode = val;
> +
> +	return 0;
> +}

[Severity: Medium]
Does this need to return 1 when the usage_mode changes so ALSA generates
a state change event, similar to the volume control above?

[ ... ]
> +static int wsa885x_i2c_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
> +					struct snd_ctl_elem_value *ucontrol)
> +{
[ ... ]
> +	mask = ucontrol->value.integer.value[0];
> +
> +	if (!wsa885x_is_valid_rx_slot_mask(mask))
> +		return -EINVAL;
> +
> +	wsa885x_i2c->rx_slot_mask = mask;
> +
> +	return 0;
> +}

[Severity: Medium]
Does this also need to return 1 when the mask changes?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260610155708.151067-1-prasad.kumpatla@oss.qualcomm.com?part=2

^ permalink raw reply

* Re: [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c
From: sashiko-bot @ 2026-06-10 16:08 UTC (permalink / raw)
  To: Prasad Kumpatla; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260610155708.151067-2-prasad.kumpatla@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] The schema incorrectly uses `additionalProperties: false` while including a `$ref` to `dai-common.yaml#`, which will erroneously reject valid standard DAI properties.
- [Medium] The binding uses a custom GPIO property (`interrupt-gpios`) to model a hardware interrupt line instead of the standard `interrupts` property.
- [Medium] The `qcom,wsa885x-init-table` property inappropriately encodes raw hardware register scripts into the Device Tree.
- [Low] Wildcard ('x') used in the compatible string instead of specific hardware part numbers.
--

commit 535bce1da75b7ff8bde3534f15f04d5dbbcc99a2
Author: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>

dt-bindings: sound: add qcom,wsa885x-i2c

This commit documents the Qualcomm WSA885X I2C smart amplifier binding. It 
describes the required supplies, powerdown and interrupt GPIOs, battery 
configuration, and an initialization table for programming the device.

> diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
> new file mode 100644
> index 0000000000000..1069f470dea5d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
> @@ -0,0 +1,89 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/qcom,wsa885x-i2c.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm WSA885x I2C smart speaker amplifier

[ ... ]

> +allOf:
> +  - $ref: dai-common.yaml#
> +
> +properties:
> +  compatible:
> +    const: qcom,wsa885x-i2c

[Severity: Low]
Is it standard to use a wildcard (x) in the compatible string here? 

Devicetree typically requires specific hardware part numbers, such as 
qcom,wsa8850-i2c, to ensure future variant quirks or different tuning 
requirements can be handled properly without ambiguity.

> +
> +  reg:
> +    maxItems: 1
> +
> +  '#sound-dai-cells':
> +    const: 0
> +
> +  powerdown-gpios:
> +    description: GPIO controlling the SD_N powerdown pin.
> +    maxItems: 1
> +
> +  interrupt-gpios:
> +    description: GPIO used for the codec interrupt output.
> +    maxItems: 1

[Severity: Medium]
Does this hardware interrupt line need to be modeled as a raw GPIO property 
rather than using the standard interrupts and interrupt-parent properties? 

Modeling it as a standard interrupt allows proper subsystem integration and 
routing, whereas interrupt-gpios bypasses the standard IRQ hierarchy.

> +
> +  vdd-1p8-supply: true
> +
> +  vdd-io-supply: true
> +
> +  qcom,battery-config:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Speaker battery configuration, 1 for 1S and 2 for 2S.
> +    default: 1
> +    enum: [1, 2]
> +
> +  qcom,wsa885x-init-table:
> +    $ref: /schemas/types.yaml#/definitions/uint32-array
> +    minItems: 2
> +    maxItems: 256
> +    description: |
> +      Sequence of register/value pairs applied during codec hardware
> +      initialization. Entries are encoded as alternating register address and
> +      register value cells. The number of entries must be even (register/value
> +      pairs); maxItems is 256 (128 pairs).

[Severity: Medium]
Does this property encode raw hardware register scripts directly into the 
devicetree? 

The devicetree is generally meant to describe static hardware topology rather 
than act as a repository for magic register initialization sequences. 
Typically, this sort of initialization logic resides within the driver code 
itself, keyed off specific compatible strings.

> +
> +required:
> +  - compatible
> +  - reg
> +  - '#sound-dai-cells'
> +  - powerdown-gpios
> +  - interrupt-gpios
> +  - vdd-1p8-supply
> +  - vdd-io-supply
> +
> +additionalProperties: false

[Severity: Medium]
Since this binding includes a reference to dai-common.yaml# in the allOf 
block above, will using additionalProperties: false cause standard DAI 
properties to be erroneously rejected? 

The additionalProperties keyword only accounts for properties explicitly 
defined in the local schema. The standard approach in this scenario is to 
use unevaluatedProperties: false so that properties from the referenced 
schema are correctly validated.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260610155708.151067-1-prasad.kumpatla@oss.qualcomm.com?part=1

^ permalink raw reply

* [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver
From: Prasad Kumpatla @ 2026-06-10 15:57 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Linus Walleij, Bartosz Golaszewski, Prasad Kumpatla
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, linux-gpio
In-Reply-To: <20260610155708.151067-1-prasad.kumpatla@oss.qualcomm.com>

Add an ASoC codec driver for the Qualcomm WSA885X smart speaker
amplifier accessed over I2C.

The driver provides the control-side support needed for playback
bring-up, including register programming, serial interface setup, clock
handling, mute and gain control, reset handling and interrupt support.

Program the init table during codec initialization and reapply it only
after an explicit device reset so the static device configuration is
not rewritten on every playback start. Also program the TDM control
slot-count field from the runtime slot configuration so the same codec
path can be used with 2-slot, 4-slot, or 8-slot Audio IF backends.

Keep the stream-time power-state sequencing in the DAI callbacks and
use normal regmap access for the control path.

Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
---
 sound/soc/codecs/Kconfig       |   11 +
 sound/soc/codecs/Makefile      |    2 +
 sound/soc/codecs/wsa885x-i2c.c | 1643 ++++++++++++++++++++++++++++++++
 3 files changed, 1656 insertions(+)
 create mode 100644 sound/soc/codecs/wsa885x-i2c.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 252f683be..7116b54f3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -367,6 +367,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_WSA881X
 	imply SND_SOC_WSA883X
 	imply SND_SOC_WSA884X
+	imply SND_SOC_WSA885X_I2C
 	imply SND_SOC_ZL38060
 	help
 	  Normally ASoC codec drivers are only built if a machine driver which
@@ -2770,6 +2771,16 @@ config SND_SOC_WSA884X
 	  This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D
 	  Smart Speaker Amplifier.
 
+config SND_SOC_WSA885X_I2C
+	tristate "WSA885X I2C Codec"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  This enables support for Qualcomm WSA885X Smart Speaker
+	  Amplifiers connected over an I2C control bus. The codec
+	  driver programs the amplifier register map and exposes the
+	  DAI and mixer controls used by Qualcomm audio machine drivers.
+
 config SND_SOC_ZL38060
 	tristate "Microsemi ZL38060 Connected Home Audio Processor"
 	depends on SPI_MASTER
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index aa0396e5b..18cfff959 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -426,6 +426,7 @@ snd-soc-wm-hubs-y := wm_hubs.o
 snd-soc-wsa881x-y := wsa881x.o
 snd-soc-wsa883x-y := wsa883x.o
 snd-soc-wsa884x-y := wsa884x.o
+snd-soc-wsa885x-i2c-y := wsa885x-i2c.o
 snd-soc-zl38060-y := zl38060.o
 # Amp
 snd-soc-max9877-y := max9877.o
@@ -876,6 +877,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS)	+= snd-soc-wm-hubs.o
 obj-$(CONFIG_SND_SOC_WSA881X)	+= snd-soc-wsa881x.o
 obj-$(CONFIG_SND_SOC_WSA883X)	+= snd-soc-wsa883x.o
 obj-$(CONFIG_SND_SOC_WSA884X)	+= snd-soc-wsa884x.o
+obj-$(CONFIG_SND_SOC_WSA885X_I2C)	+= snd-soc-wsa885x-i2c.o
 obj-$(CONFIG_SND_SOC_ZL38060)	+= snd-soc-zl38060.o
 
 # Amp
diff --git a/sound/soc/codecs/wsa885x-i2c.c b/sound/soc/codecs/wsa885x-i2c.c
new file mode 100644
index 000000000..a7d8f8d48
--- /dev/null
+++ b/sound/soc/codecs/wsa885x-i2c.c
@@ -0,0 +1,1643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+/* WSA885X I2C codec driver */
+
+#include <linux/gpio/consumer.h>
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <linux/interrupt.h>
+
+/* Control Registers - Audio Processing */
+#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S    0x0000
+#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK       0x0004
+#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX   0x0140
+#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID   0x0208
+#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX       0x0240
+#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER        0x0340
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0    0x4405
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1    0x4406
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB      0x4409
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB      0x6409
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB      0x440a
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB      0x640a
+#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS       0x0a04
+#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS       0x0a40
+#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE         0x0b10
+#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX       0x0e40
+
+/* Analog Top Registers - Power and Clock Control */
+#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d
+#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG      0x8024
+#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG      0x8025
+#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL        0x8034
+
+/* Analog PLL Registers */
+#define WSA885X_ANA_PLL_DIV_CTL_0      0x8090
+#define WSA885X_ANA_PLL_DIV_CTL_1      0x8091
+#define WSA885X_ANA_TOP_PLL_VCO_CTL    0x8092
+#define WSA885X_ANA_TOP_PLL_LOOPFILT_0         0x8093
+#define WSA885X_ANA_TOP_PLL_OVRD_CTL   0x8098
+#define WSA885X_ANA_TOP_PLL_STATUS_0   0x809a
+#define WSA885X_ANA_TOP_PLL_STATUS_1   0x809b
+
+/* Analog Boost Control Registers */
+#define WSA885X_ANA_TOP_BOOST_STB_CTRL2        0x805b
+#define WSA885X_ANA_TOP_BOOST_STB_CTRL3        0x805c
+#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2        0x805e
+#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3        0x805f
+#define WSA885X_ANA_TOP_BOOST_MISC     0x8063
+#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2   0x8065
+#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4   0x8067
+
+/* Analog IV Sense ADC Registers */
+#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2  0x80ca
+#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3  0x80cb
+#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL    0x80cc
+#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2      0x80d0
+
+/* Analog Speaker Power Stage Registers */
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3       0x8108
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3       0x810b
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3       0x810e
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3       0x8111
+#define WSA885X_ANA_TOP_SPK_TOP_SPARE3       0x813c
+#define WSA885X_SPK_TOP_LF_CH1_CTRL11       0x811c
+#define WSA885X_SPK_TOP_LF_CH1_TUNE1       0x811d
+#define WSA885X_SPK_TOP_LF_CH2_TUNE1       0x8129
+#define WSA885X_SPK_TOP_LF_CH1_CTRL9       0x811a
+#define WSA885X_SPK_TOP_LF_CH2_CTRL9       0x8126
+#define WSA885X_SPK_TOP_LF_CH2_CTRL11       0x8128
+#define WSA885X_SPK_TOP_COMMON_CTRL2        0x8102
+#define WSA885X_SPK_TOP_COMMON_TUNE1       0x8103
+#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1       0x80ba
+#define WSA885X_DIG_CTRL0_TOP_CLK_CFG  0x8418
+#define WSA885X_DIG_CTRL0_SDCA_COMMIT          0x8419
+#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE    0x841a
+#define WSA885X_DIG_CTRL0_SYS_CLK_SEL          0x841b
+#define WSA885X_DIG_CTRL0_CDC_CLK_CTL          0x841c
+#define WSA885X_DIG_CTRL0_PA_FSM_CTL   0x8420
+#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0       0x8423
+#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1       0x8424
+#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1         0x842b
+#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1         0x8435
+#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL    0x8458
+#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL   0x8470
+#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1      0x84b4
+#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1      0x84b7
+#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1    0x84A0
+#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1    0x84A4
+
+/* Digital Control 1 Registers - I2S/TDM Interface */
+#define WSA885X_DIG_CTRL1_I2S_CTL0     0x85A0
+#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX      0x85A2
+#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX      0x85A3
+#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7
+#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9
+#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX        0x85AA
+#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX        0x85AB
+#define WSA885X_DIG_CTRL1_I2S_RESET_CTL        0x85AE
+
+/* CDC RX Path Registers - Audio Data Path */
+#define WSA885X_CDC_RX0_RX_PATH_CFG0   0x8601
+#define WSA885X_CDC_RX0_RX_PATH_CFG1   0x8602
+#define WSA885X_CDC_RX0_RX_PATH_CTL    0x8606
+#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613
+#define WSA885X_CDC_RX1_RX_PATH_CFG0   0x8621
+#define WSA885X_CDC_RX1_RX_PATH_CFG1   0x8622
+#define WSA885X_CDC_RX1_RX_PATH_CTL    0x8626
+#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633
+
+/* CDC Compander Registers - Dynamic Range Control */
+#define WSA885X_CDC_COMPANDER0_CTL0    0x8640
+#define WSA885X_CDC_COMPANDER0_CTL7    0x8647
+#define WSA885X_CDC_COMPANDER1_CTL0    0x8660
+#define WSA885X_CDC_COMPANDER1_CTL7    0x8667
+
+/* CDC Speaker Protection Registers - IV Sense */
+#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1
+#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1
+#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9
+#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9
+
+/* CDC Class-H Registers - Headroom Control */
+#define WSA885X_CDC_CLSH_V1P8_BP_CTL1  0x86CD
+#define WSA885X_CDC_CLSH_V1P8_BP_CTL0  0x86CC
+#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0      0x86C7
+#define WSA885X_CDC_CLSH_CLSH_V_HD_PA  0x86C3
+#define WSA885X_CDC_CLSH_V1P8_BP_CTL2  0x86CE
+
+/* Driver Constants */
+#define WSA885X_CLK_RATE_FIXED 73728000
+#define WSA885X_SUPPLIES_NUM   2
+#define WSA885X_NUM_REGS       0x03
+
+/* Interrupt Registers */
+#define WSA885X_INTR_STATUS0   0x8584
+#define WSA885X_INTR_MASK0     0x8581
+#define WSA885X_INTR_CLEAR0    0x8587
+
+/* Power and PA FSM Control Registers */
+#define WSA885X_PA0_FSM_CTL0   0x842A
+#define WSA885X_PA1_FSM_CTL0   0x8434
+
+/* Digital Control GPIO and Interrupt Registers */
+#define WSA885X_DIG_CTRL1_PIN_CT       0x8510
+#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL   0x8518
+#define WSA885X_DIG_CTRL1_INTR_MODE    0x8580
+
+#define WSA885X_I2S_CTL0_PCM_RATE_MASK         GENMASK(4, 1)
+#define WSA885X_I2S_CTL0_ENABLE_MASK           BIT(0)
+#define WSA885X_I2S_CTL0_PCM_RATE(v)           \
+	FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v))
+#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ         0x0
+#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ        0x1
+#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ        0x2
+#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ  0x3
+#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ  0x4
+#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5
+#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK     GENMASK(2, 0)
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK     GENMASK(6, 4)
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v))
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v))
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK     GENMASK(2, 0)
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK     GENMASK(6, 4)
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v))
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v))
+#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK   BIT(0)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2    \
+	FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4    \
+	FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8    \
+	FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3)
+#define WSA885X_I2S_TDM_CH_TX_CH0_EN           BIT(0)
+#define WSA885X_I2S_TDM_CH_TX_CH1_EN           BIT(1)
+#define WSA885X_I2S_TDM_CH_TX_CH2_EN           BIT(2)
+#define WSA885X_I2S_TDM_CH_TX_CH3_EN           BIT(3)
+#define WSA885X_I2S_RESET_CTL_RESET_MASK       BIT(0)
+#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2)
+#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3)
+#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK   BIT(2)
+
+#define WSA885X_I2S_TX_SLOT_ISENSE0            0x1
+#define WSA885X_I2S_TX_SLOT_ISENSE1            0x2
+#define WSA885X_I2S_TX_SLOT_CUR_SENSE0         0x5
+#define WSA885X_I2S_TX_SLOT_CUR_SENSE1         0x6
+
+/* RX Sample Rate Index Values - Audio Playback Path */
+#define WSA885X_RX_RATE_8000HZ          0x00
+#define WSA885X_RX_RATE_16000HZ         0x01
+#define WSA885X_RX_RATE_32000HZ         0x02
+#define WSA885X_RX_RATE_44100HZ         0x03
+#define WSA885X_RX_RATE_48000HZ         0x04
+#define WSA885X_RX_RATE_96000HZ         0x05
+#define WSA885X_RX_RATE_192000HZ        0x06
+#define WSA885X_RX_RATE_384000HZ        0x07
+
+/* VI Sample Rate Index Values - Voltage/Current Sensing Path */
+#define WSA885X_VI_RATE_8000HZ          0x00
+#define WSA885X_VI_RATE_16000HZ         0x01
+#define WSA885X_VI_RATE_44100HZ         0x02
+#define WSA885X_VI_RATE_48000HZ         0x03
+#define WSA885X_VI_RATE_96000HZ         0x04
+#define WSA885X_VI_RATE_22050HZ         0x05
+#define WSA885X_VI_RATE_24000HZ         0x06
+#define WSA885X_VI_RATE_192000HZ        0x07
+#define WSA885X_VI_RATE_384000HZ        0x08
+
+/* Channel Configuration Masks */
+#define WSA885X_CHANNEL_STEREO          0x03
+#define WSA885X_CHANNEL_MONO_LEFT       0x01
+#define WSA885X_CHANNEL_MONO_RIGHT      0x02
+
+#define WSA885X_PLL_LOCK_BIT            BIT(0)
+
+#define WSA885X_FU21_VOL_STEPS 124
+#define WSA885X_USAGE_MODE_MAX 8
+#define WSA885X_INIT_TABLE_MAX_ITEMS 256
+static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0);
+
+static bool wsa885x_is_valid_rx_slot_mask(u32 mask)
+{
+	return mask == WSA885X_CHANNEL_MONO_LEFT ||
+	       mask == WSA885X_CHANNEL_MONO_RIGHT ||
+	       mask == WSA885X_CHANNEL_STEREO;
+}
+
+static const char *const wsa885x_supply_name[] = {
+	"vdd-1p8",
+	"vdd-io",
+};
+
+enum {
+	WSA885X_BATT_1S = 1,
+	WSA885X_BATT_2S,
+};
+
+enum {
+	WSA885X_IRQ_INT_SAF2WAR = 0,
+	WSA885X_IRQ_INT_WAR2SAF,
+	WSA885X_IRQ_INT_DISABLE,
+	WSA885X_IRQ_INT_PA0_OCP,
+	WSA885X_IRQ_INT_PA1_OCP,
+	WSA885X_IRQ_INT_CLIP0,
+	WSA885X_IRQ_INT_CLIP1,
+	WSA885X_IRQ_INT_CLK_WD,
+	WSA885X_IRQ_INT_INTR_GPIO1_PIN,
+	WSA885X_IRQ_INT_INTR_GPIO2_PIN,
+	WSA885X_IRQ_INT_UVLO,
+	WSA885X_IRQ_INT_BOP,
+	WSA885X_IRQ_INT_PA0_FSM_ERR,
+	WSA885X_IRQ_INT_PA1_FSM_ERR,
+	WSA885X_IRQ_INT_MAIN_FSM_ERR,
+	WSA885X_IRQ_INT_PCM_DATA0_WD,
+	WSA885X_IRQ_INT_PCM_DATA1_WD,
+	WSA885X_IRQ_INT_PCM_DATA0_DC,
+	WSA885X_IRQ_INT_PCM_DATA1_DC,
+	WSA885X_IRQ_INT_PLL_UNLOCKED,
+	WSA885X_IRQ_INT_PROT_MODE_CHANGE,
+	WSA885X_IRQ_INT_PB_CLOCK_VALID,
+	WSA885X_IRQ_INT_SENSE_CLOCK_VALID,
+	WSA885X_IRQ_MAX,
+};
+
+struct wsa885x_i2c_priv {
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct device *dev;
+	struct snd_soc_component *component;
+	struct regulator_bulk_data supplies[WSA885X_SUPPLIES_NUM];
+	struct gpio_desc *sd_n;
+	u32 sample_rate;
+	u32 *init_table;
+	u32 init_table_size;
+	u32 usage_mode;
+	u32 rx_slot_mask;
+	struct gpio_desc *intr_pin;
+	u32 batt_conf;
+	int stereo_vol_db;
+};
+
+struct wsa885x_reg_update {
+	unsigned int reg;
+	unsigned int mask;
+	unsigned int val;
+};
+
+static const struct regmap_range_cfg wsa885x_regmap_ranges[] = {
+	{
+		.range_min = 0,
+		.range_max = 0x88ff,
+		.selector_reg = 0x0,
+		.selector_mask = 0xFF,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 0x100,
+	},
+};
+
+static const struct reg_default wsa885x_codec_reg_defaults[] = {
+	{WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04},
+	{WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03},
+	{WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03},
+	{WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00},
+	{WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19},
+	{WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22},
+	{WSA885X_ANA_PLL_DIV_CTL_0, 0x0c},
+	{WSA885X_ANA_PLL_DIV_CTL_1, 0x50},
+	{WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00},
+	{WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4},
+	{WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00},
+	{WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00},
+	{WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03},
+	{WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c},
+	{WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5},
+	{WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13},
+	{WSA885X_ANA_TOP_BOOST_MISC, 0x79},
+	{WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00},
+	{WSA885X_SPK_TOP_COMMON_CTRL2, 0x08},
+	{WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09},
+	{WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00},
+	{WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00},
+	{WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00},
+	{WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00},
+	{WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09},
+	{WSA885X_SPK_TOP_COMMON_TUNE1, 0x03},
+	{WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00},
+	{WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e},
+	{WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40},
+	{WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff},
+	{WSA885X_ANA_TOP_PLL_STATUS_0, 0x00},
+	{WSA885X_ANA_TOP_PLL_STATUS_1, 0x00},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9},
+	{WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00},
+	{WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00},
+	{WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00},
+	{WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00},
+	{WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00},
+	{WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05},
+	{WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00},
+	{WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45},
+	{WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45},
+	{WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f},
+	{WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00},
+	{WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01},
+	{WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01},
+	{WSA885X_DIG_CTRL1_I2S_CTL0, 0x06},
+	{WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08},
+	{WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89},
+	{WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64},
+	{WSA885X_CDC_RX0_RX_PATH_CTL, 0x24},
+	{WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01},
+	{WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89},
+	{WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64},
+	{WSA885X_CDC_RX1_RX_PATH_CTL, 0x04},
+	{WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01},
+	{WSA885X_CDC_COMPANDER0_CTL0, 0x01},
+	{WSA885X_CDC_COMPANDER0_CTL7, 0x2a},
+	{WSA885X_CDC_COMPANDER1_CTL0, 0x01},
+	{WSA885X_CDC_COMPANDER1_CTL7, 0x2a},
+	{WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50},
+	{WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c},
+	{WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d},
+	{WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03},
+	{WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05},
+};
+
+static void wsa885x_gpio_set(struct wsa885x_i2c_priv *wsa885x, bool val)
+{
+	if (!wsa885x || !wsa885x->sd_n)
+		return;
+
+	gpiod_set_value_cansleep(wsa885x->sd_n, val);
+}
+
+static void wsa885x_multi_update_bits(struct regmap *regmap,
+				      const struct wsa885x_reg_update *updates,
+				      size_t num_updates)
+{
+	size_t i;
+
+	if (!regmap || !updates)
+		return;
+
+	for (i = 0; i < num_updates; i++)
+		regmap_update_bits(regmap, updates[i].reg,
+				   updates[i].mask, updates[i].val);
+}
+
+static void wsa885x_toggle_irq_bit(struct wsa885x_i2c_priv *wsa885x,
+				   unsigned int reg, unsigned int mask)
+{
+	if (!wsa885x || !wsa885x->regmap)
+		return;
+
+	regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+	regmap_update_bits(wsa885x->regmap, reg, mask, mask);
+}
+
+static void wsa885x_pulse_irq_bit(struct wsa885x_i2c_priv *wsa885x,
+				  unsigned int reg, unsigned int mask)
+{
+	if (!wsa885x || !wsa885x->regmap)
+		return;
+
+	regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+	regmap_update_bits(wsa885x->regmap, reg, mask, mask);
+	regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+}
+
+static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val)
+{
+	if (!slot_num_val)
+		return -EINVAL;
+
+	switch (slots) {
+	case 2:
+		*slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2;
+		return 0;
+	case 4:
+		*slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4;
+		return 0;
+	case 8:
+		*slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 },
+	};
+	unsigned int slot_num_val;
+	int ret;
+
+	if (!regmap)
+		return -EINVAL;
+
+	ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val);
+	if (ret)
+		return ret;
+
+	regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs));
+
+	regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0,
+			   WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK,
+			   slot_num_val);
+	regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0,
+			   WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK,
+			   WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK);
+	regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+		     WSA885X_I2S_TDM_CH_TX_CH0_EN);
+	regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+			   WSA885X_I2S_TDM_CH_TX_CH1_EN,
+			   WSA885X_I2S_TDM_CH_TX_CH1_EN);
+
+	return 0;
+}
+
+static int wsa885x_wait_for_pll_lock(struct wsa885x_i2c_priv *wsa885x)
+{
+	unsigned int status = 0;
+	int cnt = 0;
+	int ret = 0;
+
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	do {
+		usleep_range(1000, 1100);
+		ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status);
+		if (ret) {
+			dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret);
+			return ret;
+		}
+
+		if (status & WSA885X_PLL_LOCK_BIT)
+			return 0;
+	} while (++cnt < 20);
+
+	dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status);
+	return -ETIMEDOUT;
+}
+
+static int wsa885x_2s_conf(struct wsa885x_i2c_priv *wsa885x)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 },
+		{ WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d },
+		{ WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d },
+		{ WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 },
+		{ WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA },
+	};
+
+	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int wsa885x_apply_init_table(struct wsa885x_i2c_priv *wsa885x)
+{
+	int i;
+	int ret;
+
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	if (!wsa885x->init_table_size)
+		return 0;
+
+	if (!wsa885x->init_table)
+		return -EINVAL;
+
+	for (i = 0; i < wsa885x->init_table_size / 2; i++) {
+		u32 reg = wsa885x->init_table[2 * i];
+		u32 val = wsa885x->init_table[2 * i + 1];
+
+		if (wsa885x->batt_conf == WSA885X_BATT_2S && reg == WSA885X_SPK_TOP_LF_CH1_CTRL11)
+			continue;
+
+		if (wsa885x->batt_conf == WSA885X_BATT_2S && reg == WSA885X_SPK_TOP_LF_CH2_CTRL11)
+			continue;
+
+		ret = regmap_write(wsa885x->regmap, reg, val);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int wsa885x_hw_init(struct wsa885x_i2c_priv *wsa885x)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e },
+		{ WSA885X_DIG_CTRL1_INTR_MODE, 0x01 },
+		{ WSA885X_DIG_CTRL1_PIN_CT, 0x04 },
+	};
+	int ret;
+
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	ret = wsa885x_apply_init_table(wsa885x);
+	if (ret)
+		return ret;
+
+	if (wsa885x->batt_conf == WSA885X_BATT_2S) {
+		ret = wsa885x_2s_conf(wsa885x);
+		if (ret)
+			return ret;
+	}
+
+	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int wsa885x_unmask_interrupts(struct wsa885x_i2c_priv *wsa885x)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_INTR_MASK0, 0x00 },
+		{ WSA885X_INTR_MASK0 + 1, 0x00 },
+		{ WSA885X_INTR_MASK0 + 2, 0xf8 },
+	};
+
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int wsa885x_wait_for_pde_state(struct wsa885x_i2c_priv *wsa885x, int ps)
+{
+	int act_ps = -1, cnt = 0, clock_valid = -1;
+	int rc = 0;
+
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	if (ps < 0 || ps > 3)
+		return -EINVAL;
+
+	do {
+		usleep_range(1000, 1500);
+		rc = regmap_read(wsa885x->regmap,
+				 WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS,
+				 &act_ps);
+		if (rc) {
+			dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc);
+			return rc;
+		}
+		if (act_ps == ps)
+			return 0;
+	} while (++cnt < 5);
+	if (regmap_read(wsa885x->regmap,
+			WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID,
+			&clock_valid))
+		dev_err(wsa885x->dev,
+			"PDE power state %d request failed, actual_ps %d, clock_valid read failed\n",
+			ps, act_ps);
+	else
+		dev_err(wsa885x->dev,
+			"PDE power state %d request failed, actual_ps %d, clock_valid:%d\n",
+			ps, act_ps, clock_valid);
+
+	return -ETIMEDOUT;
+}
+
+static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct wsa885x_i2c_priv *wsa885x;
+	u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx;
+
+	(void)substream;
+
+	if (!params || !dai || !dai->component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	wsa885x->sample_rate = params_rate(params);
+
+	switch (wsa885x->sample_rate) {
+	case 8000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ;
+		break;
+	case 16000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ;
+		break;
+	case 32000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ;
+		/* The VI sensing path has no 32 kHz rate index; use the
+		 * nearest supported rate (48 kHz) for the CS24 clock.
+		 */
+		cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ;
+		break;
+	case 44100:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ;
+		break;
+	case 48000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ;
+		break;
+	case 88200:
+	case 96000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ;
+		break;
+	case 176400:
+	case 192000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ;
+		break;
+	case 352800:
+	case 384000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ;
+		break;
+	default:
+		dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params));
+		return -EINVAL;
+	}
+
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0,
+			   WSA885X_I2S_CTL0_PCM_RATE_MASK |
+			   WSA885X_I2S_CTL0_ENABLE_MASK,
+			   WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) |
+			   WSA885X_I2S_CTL0_ENABLE_MASK);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00);
+	regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX,
+		     cs21_sample_rate_idx);
+	regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX,
+		     cs24_sample_rate_idx);
+	regmap_write(wsa885x->regmap,
+		     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
+		     (s8)wsa885x->stereo_vol_db);
+	regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00);
+	regmap_write(wsa885x->regmap,
+		     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB,
+		     (s8)wsa885x->stereo_vol_db);
+	regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01);
+
+	return 0;
+}
+
+static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai,
+				      unsigned int tx_slot_mask,
+				      unsigned int rx_slot_mask, int slots,
+				      int slot_width)
+{
+	static const struct wsa885x_reg_update stereo_updates[] = {
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK,
+		  WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK,
+		  WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) },
+	};
+	static const struct wsa885x_reg_update mono_left_updates[] = {
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) },
+	};
+	static const struct wsa885x_reg_update mono_right_updates[] = {
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) },
+	};
+	struct wsa885x_i2c_priv *wsa885x;
+	unsigned int slot_num_val;
+	int ret;
+
+	(void)tx_slot_mask;
+	(void)slot_width;
+
+	if (!dai || !dai->component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val);
+	if (ret) {
+		dev_err(wsa885x->dev, "%s: unsupported slot count %d\n",
+			__func__, slots);
+		return ret;
+	}
+
+	if (rx_slot_mask) {
+		if (!wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) {
+			dev_err(wsa885x->dev,
+				"%s: unsupported rx_slot_mask 0x%x\n",
+				__func__, rx_slot_mask);
+			return -EINVAL;
+		}
+		wsa885x->rx_slot_mask = rx_slot_mask;
+	} else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) {
+		wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO;
+	}
+
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL,
+			   WSA885X_I2S_RESET_CTL_RESET_MASK,
+			   WSA885X_I2S_RESET_CTL_RESET_MASK);
+
+	if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) {
+		wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates,
+					  ARRAY_SIZE(stereo_updates));
+		ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots);
+		if (ret)
+			return ret;
+		regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+				   WSA885X_I2S_TDM_CH_TX_CH2_EN,
+				   WSA885X_I2S_TDM_CH_TX_CH2_EN);
+		regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+				   WSA885X_I2S_TDM_CH_TX_CH3_EN,
+				   WSA885X_I2S_TDM_CH_TX_CH3_EN);
+	} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) {
+		wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates,
+					  ARRAY_SIZE(mono_left_updates));
+		ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots);
+		if (ret)
+			return ret;
+	} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) {
+		wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates,
+					  ARRAY_SIZE(mono_right_updates));
+		ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots);
+		if (ret)
+			return ret;
+	}
+
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0,
+			   WSA885X_I2S_CTL0_ENABLE_MASK,
+			   WSA885X_I2S_CTL0_ENABLE_MASK);
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL,
+			   WSA885X_I2S_RESET_CTL_RESET_MASK, 0);
+
+	return 0;
+}
+
+static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+				    unsigned int freq, int dir)
+{
+	static const struct reg_sequence pll_prep[] = {
+		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 },
+		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 },
+		{ WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 },
+		{ WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 },
+		{ WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 },
+	};
+	static const struct reg_sequence pll_cleanup[] = {
+		{ WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 },
+		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 },
+		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 },
+	};
+	struct wsa885x_i2c_priv *wsa885x;
+	u32 pll_div;
+	int ret = 0;
+
+	/*
+	 * This device has a single fixed PLL clock source; clk_id and dir
+	 * are not used. The PLL divisor is derived solely from freq.
+	 */
+	(void)clk_id;
+	(void)dir;
+
+	if (!dai || !dai->component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	if (!freq)
+		return -EINVAL;
+	if (WSA885X_CLK_RATE_FIXED % freq)
+		return -EINVAL;
+	pll_div = WSA885X_CLK_RATE_FIXED / freq;
+	if (pll_div > 0xff)
+		return -EINVAL;
+
+	regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep));
+	regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02);
+
+	ret = wsa885x_wait_for_pll_lock(wsa885x);
+	if (ret) {
+		dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n");
+		regmap_multi_reg_write(wsa885x->regmap, pll_cleanup,
+				       ARRAY_SIZE(pll_cleanup));
+		return ret;
+	}
+
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01);
+	regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00);
+
+	return 0;
+}
+
+static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	static const struct reg_sequence mute_regs[] = {
+		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 },
+	};
+	static const struct reg_sequence mute_commit_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 },
+		{ WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 },
+	};
+	static const struct reg_sequence unmute_prep_head_regs[] = {
+		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
+	};
+	static const struct reg_sequence unmute_prep_tail_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 },
+	};
+	static const struct reg_sequence unmute_volume_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 },
+	};
+	static const struct reg_sequence unmute_commit_regs[] = {
+		{ WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 },
+	};
+	static const struct reg_sequence unmute_finish_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 },
+		{ WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 },
+	};
+	struct wsa885x_i2c_priv *wsa885x;
+	int ret = 0, ps0 = 0, ps3 = 3;
+
+	if (!dai || !dai->component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	if (stream != SNDRV_PCM_STREAM_PLAYBACK)
+		return 0;
+
+	if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX)
+		return -EINVAL;
+
+	if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask))
+		wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO;
+
+	if (mute) {
+		regmap_multi_reg_write(wsa885x->regmap, mute_regs,
+				       ARRAY_SIZE(mute_regs));
+		ret = wsa885x_wait_for_pde_state(wsa885x, ps3);
+		if (ret) {
+			dev_err(wsa885x->dev,
+				"PS3 transition failed: %d\n", ret);
+		} else {
+			regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs,
+					       ARRAY_SIZE(mute_commit_regs));
+		}
+	} else {
+		regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs,
+				       ARRAY_SIZE(unmute_prep_head_regs));
+		regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE,
+			     wsa885x->usage_mode);
+		regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs,
+				       ARRAY_SIZE(unmute_prep_tail_regs));
+		regmap_write(wsa885x->regmap,
+			     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
+			     (s8)wsa885x->stereo_vol_db);
+		regmap_write(wsa885x->regmap,
+			     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB,
+			     (s8)wsa885x->stereo_vol_db);
+		regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs,
+				       ARRAY_SIZE(unmute_volume_regs));
+		regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs,
+				       ARRAY_SIZE(unmute_commit_regs));
+		ret = wsa885x_wait_for_pde_state(wsa885x, ps0);
+		if (ret)
+			goto exit;
+
+		if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) {
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03);
+		} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) {
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01);
+		} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) {
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02);
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x01);
+		}
+
+		regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs,
+				       ARRAY_SIZE(unmute_finish_regs));
+	}
+exit:
+	return ret;
+}
+
+static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 },
+		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
+		{ WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00 },
+		{ WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 },
+		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 },
+		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 },
+	};
+	struct wsa885x_i2c_priv *wsa885x;
+
+	if (!substream || !dai || !dai->component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+	if (!wsa885x || !wsa885x->regmap)
+		return -EINVAL;
+
+	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+		return 0;
+
+	regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops wsa885x_i2c_dai_ops = {
+	.hw_params = wsa885x_codec_hw_params,
+	.set_tdm_slot = wsa885x_codec_set_tdm_slot,
+	.set_sysclk = wsa885x_codec_set_sysclk,
+	.mute_stream = wsa885x_codec_mute_stream,
+	.hw_free = wsa885x_codec_hw_free,
+};
+
+static struct snd_soc_dai_driver wsa885x_i2c_dai[] = {
+	{
+		.name = "wsa885x_dai_drv",
+		.playback = {
+			.stream_name = "WSA885X I2C TDM Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+				.rates = SNDRV_PCM_RATE_8000_192000 |
+					 SNDRV_PCM_RATE_352800 |
+					 SNDRV_PCM_RATE_384000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+					   SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &wsa885x_i2c_dai_ops,
+	},
+};
+
+static void wsa885x_gpio_powerdown(void *data)
+{
+	struct wsa885x_i2c_priv *wsa885x = data;
+
+	if (!wsa885x)
+		return;
+
+	wsa885x_gpio_set(wsa885x, true);
+}
+
+static bool wsa885x_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case WSA885X_ANA_TOP_PLL_STATUS_0:
+	case WSA885X_ANA_TOP_PLL_STATUS_1:
+	case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS:
+	case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID:
+	case WSA885X_INTR_STATUS0:
+	case WSA885X_INTR_STATUS0 + 1:
+	case WSA885X_INTR_STATUS0 + 2:
+	case WSA885X_INTR_CLEAR0:
+	case WSA885X_INTR_CLEAR0 + 1:
+	case WSA885X_INTR_CLEAR0 + 2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool wsa885x_readable_register(struct device *dev, unsigned int reg)
+{
+	if (reg == WSA885X_INTR_CLEAR0 ||
+	    reg == WSA885X_INTR_CLEAR0 + 1 ||
+	    reg == WSA885X_INTR_CLEAR0 + 2)
+		return false;
+	return reg <= 0x88ff;
+}
+
+static bool wsa885x_writeable_register(struct device *dev, unsigned int reg)
+{
+	if (reg > 0x88ff)
+		return false;
+
+	switch (reg) {
+	case WSA885X_ANA_TOP_PLL_STATUS_0:
+	case WSA885X_ANA_TOP_PLL_STATUS_1:
+	case WSA885X_INTR_STATUS0:
+	case WSA885X_INTR_STATUS0 + 1:
+	case WSA885X_INTR_STATUS0 + 2:
+	case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS:
+	case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config wsa885x_regmap_cfg = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x88FF,
+	.ranges = wsa885x_regmap_ranges,
+	.num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges),
+	.reg_defaults = wsa885x_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults),
+	.volatile_reg = wsa885x_volatile_register,
+	.writeable_reg = wsa885x_writeable_register,
+	.readable_reg = wsa885x_readable_register,
+	.cache_type = REGCACHE_MAPLE,
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+static int wsa885x_component_probe(struct snd_soc_component *component)
+{
+	struct wsa885x_i2c_priv *wsa885x =
+		snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (!wsa885x || !wsa885x->regmap)
+		return -ENODEV;
+
+	wsa885x->component = component;
+	snd_soc_component_init_regmap(component, wsa885x->regmap);
+
+	ret = wsa885x_hw_init(wsa885x);
+	if (ret)
+		return ret;
+
+	/* Restore interrupt masks after reg_defaults programming. */
+	return wsa885x_unmask_interrupts(wsa885x);
+}
+
+static void wsa885x_component_remove(struct snd_soc_component *component)
+{
+	if (!component)
+		return;
+
+	snd_soc_component_exit_regmap(component);
+}
+
+static void wsa885x_regulator_disable(void *data)
+{
+	struct wsa885x_i2c_priv *wsa885x = data;
+
+	if (!wsa885x)
+		return;
+
+	regulator_bulk_disable(WSA885X_SUPPLIES_NUM, wsa885x->supplies);
+}
+
+static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component;
+	struct wsa885x_i2c_priv *wsa885x;
+	int val;
+
+	if (!kcontrol || !ucontrol)
+		return -EINVAL;
+
+	component = snd_kcontrol_chip(kcontrol);
+	if (!component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(component);
+	if (!wsa885x)
+		return -EINVAL;
+
+	val = wsa885x->stereo_vol_db + 84;
+	if (val < 0 || val > WSA885X_FU21_VOL_STEPS)
+		return -ERANGE;
+
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component;
+	struct wsa885x_i2c_priv *wsa885x;
+	long val;
+
+	if (!kcontrol || !ucontrol)
+		return -EINVAL;
+
+	component = snd_kcontrol_chip(kcontrol);
+	if (!component)
+		return -EINVAL;
+
+	wsa885x = snd_soc_component_get_drvdata(component);
+	if (!wsa885x)
+		return -EINVAL;
+
+	val = ucontrol->value.integer.value[0];
+
+	if (val < 0 || val > WSA885X_FU21_VOL_STEPS) {
+		dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val);
+		return -EINVAL;
+	}
+	wsa885x->stereo_vol_db = (int)val - 84;
+	return 0;
+}
+
+static int wsa885x_i2c_usage_modes_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component;
+	struct wsa885x_i2c_priv *wsa885x_i2c;
+
+	if (!kcontrol || !ucontrol)
+		return -EINVAL;
+
+	component = snd_kcontrol_chip(kcontrol);
+	if (!component)
+		return -EINVAL;
+
+	wsa885x_i2c = snd_soc_component_get_drvdata(component);
+	if (!wsa885x_i2c)
+		return -EINVAL;
+
+	if (wsa885x_i2c->usage_mode > WSA885X_USAGE_MODE_MAX)
+		return -ERANGE;
+
+	ucontrol->value.integer.value[0] = wsa885x_i2c->usage_mode;
+
+	return 0;
+}
+
+static int wsa885x_i2c_usage_modes_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component;
+	struct wsa885x_i2c_priv *wsa885x_i2c;
+	long val;
+
+	if (!kcontrol || !ucontrol)
+		return -EINVAL;
+
+	component = snd_kcontrol_chip(kcontrol);
+	if (!component)
+		return -EINVAL;
+
+	wsa885x_i2c = snd_soc_component_get_drvdata(component);
+	if (!wsa885x_i2c)
+		return -EINVAL;
+
+	val = ucontrol->value.integer.value[0];
+
+	if (val < 0 || val > WSA885X_USAGE_MODE_MAX)
+		return -EINVAL;
+
+	wsa885x_i2c->usage_mode = val;
+
+	return 0;
+}
+
+static int wsa885x_i2c_rx_slot_mask_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component;
+	struct wsa885x_i2c_priv *wsa885x_i2c;
+	u32 mask;
+
+	if (!kcontrol || !ucontrol)
+		return -EINVAL;
+
+	component = snd_kcontrol_chip(kcontrol);
+	if (!component)
+		return -EINVAL;
+
+	wsa885x_i2c = snd_soc_component_get_drvdata(component);
+	if (!wsa885x_i2c)
+		return -EINVAL;
+
+	mask = wsa885x_i2c->rx_slot_mask;
+	if (!wsa885x_is_valid_rx_slot_mask(mask))
+		return -ERANGE;
+
+	ucontrol->value.integer.value[0] = mask;
+
+	return 0;
+}
+
+static int wsa885x_i2c_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component;
+	struct wsa885x_i2c_priv *wsa885x_i2c;
+	long mask;
+
+	if (!kcontrol || !ucontrol)
+		return -EINVAL;
+
+	component = snd_kcontrol_chip(kcontrol);
+	if (!component)
+		return -EINVAL;
+
+	wsa885x_i2c = snd_soc_component_get_drvdata(component);
+	if (!wsa885x_i2c)
+		return -EINVAL;
+
+	mask = ucontrol->value.integer.value[0];
+
+	if (!wsa885x_is_valid_rx_slot_mask(mask))
+		return -EINVAL;
+
+	wsa885x_i2c->rx_slot_mask = mask;
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new wsa885x_snd_controls[] = {
+	SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0,
+		       wsa885x_i2c_usage_modes_get,
+		       wsa885x_i2c_usage_modes_put),
+
+	SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM,
+			   0, WSA885X_FU21_VOL_STEPS, 0,
+			   wsa885x_stereo_gain_offset_get,
+			   wsa885x_stereo_gain_offset_put,
+			   wsa885x_fu21_digital_gain),
+
+	SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0,
+		       wsa885x_i2c_rx_slot_mask_get,
+		       wsa885x_i2c_rx_slot_mask_put),
+};
+
+static const struct snd_soc_component_driver wsa885x_i2c_component = {
+	.name = "wsa885x-i2c",
+	.probe = wsa885x_component_probe,
+	.remove = wsa885x_component_remove,
+	.controls = wsa885x_snd_controls,
+	.num_controls = ARRAY_SIZE(wsa885x_snd_controls),
+};
+
+static irqreturn_t wsa885x_handle_i2c_irq(int irq_idx, void *data)
+{
+	struct wsa885x_i2c_priv *wsa885x = data;
+
+	if (!wsa885x)
+		return IRQ_NONE;
+
+	if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX)
+		return IRQ_NONE;
+
+	switch (irq_idx) {
+	case WSA885X_IRQ_INT_SAF2WAR:
+	case WSA885X_IRQ_INT_WAR2SAF:
+	case WSA885X_IRQ_INT_DISABLE:
+	case WSA885X_IRQ_INT_INTR_GPIO1_PIN:
+	case WSA885X_IRQ_INT_INTR_GPIO2_PIN:
+	case WSA885X_IRQ_INT_PA0_OCP:
+	case WSA885X_IRQ_INT_PA1_OCP:
+	case WSA885X_IRQ_INT_CLIP0:
+	case WSA885X_IRQ_INT_CLIP1:
+	case WSA885X_IRQ_INT_CLK_WD:
+	case WSA885X_IRQ_INT_BOP:
+	case WSA885X_IRQ_INT_UVLO:
+	case WSA885X_IRQ_INT_PCM_DATA0_DC:
+	case WSA885X_IRQ_INT_PCM_DATA1_DC:
+	case WSA885X_IRQ_INT_PLL_UNLOCKED:
+	case WSA885X_IRQ_INT_PROT_MODE_CHANGE:
+	case WSA885X_IRQ_INT_PB_CLOCK_VALID:
+	case WSA885X_IRQ_INT_SENSE_CLOCK_VALID:
+		break;
+	case WSA885X_IRQ_INT_PCM_DATA0_WD:
+	case WSA885X_IRQ_INT_PCM_DATA1_WD:
+		if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD)
+			wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1,
+					       WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK);
+		else
+			wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1,
+					       WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK);
+		break;
+	case WSA885X_IRQ_INT_PA0_FSM_ERR:
+	case WSA885X_IRQ_INT_PA1_FSM_ERR:
+	case WSA885X_IRQ_INT_MAIN_FSM_ERR:
+		if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) {
+			wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0,
+					      WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK);
+		} else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) {
+			wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0,
+					      WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK);
+		} else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) {
+			wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0,
+					      WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t wsa885x_interrupt_handler(int irq, void *data)
+{
+	static const unsigned int status_reg[WSA885X_NUM_REGS] = {
+		WSA885X_INTR_STATUS0,
+		WSA885X_INTR_STATUS0 + 1,
+		WSA885X_INTR_STATUS0 + 2,
+	};
+	static const unsigned int clear_reg[WSA885X_NUM_REGS] = {
+		WSA885X_INTR_CLEAR0,
+		WSA885X_INTR_CLEAR0 + 1,
+		WSA885X_INTR_CLEAR0 + 2,
+	};
+	unsigned int status[WSA885X_NUM_REGS] = { 0 };
+	struct wsa885x_i2c_priv *wsa885x = data;
+	irqreturn_t handled = IRQ_NONE;
+	irqreturn_t irq_ret;
+	int i, bit, ret;
+	int irq_num;
+
+	(void)irq;
+	if (!wsa885x || !wsa885x->regmap)
+		return IRQ_NONE;
+
+	for (i = 0; i < WSA885X_NUM_REGS; i++) {
+		ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]);
+		if (ret) {
+			dev_err(wsa885x->dev,
+				"Failed to read status_reg[%d] (0x%x): %d\n",
+				i, status_reg[i], ret);
+			status[i] = 0;
+			continue;
+		}
+	}
+
+	for (i = 0; i < WSA885X_NUM_REGS; i++) {
+		for (bit = 0; bit < 8; bit++) {
+			if (status[i] & BIT(bit)) {
+				irq_num = i * 8 + bit;
+				/* INTR_CLEAR registers are write-only; use regmap_write
+				 * instead of regmap_update_bits to avoid the read-modify-write
+				 * that regmap_update_bits performs on non-readable registers.
+				 */
+				regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit));
+				regmap_write(wsa885x->regmap, clear_reg[i], 0);
+				if (irq_num >= WSA885X_IRQ_MAX) {
+					dev_warn_ratelimited(wsa885x->dev,
+							     "Unexpected IRQ bit %d (reg %d)\n",
+							     bit, i);
+					handled = IRQ_HANDLED;
+					continue;
+				}
+				irq_ret = wsa885x_handle_i2c_irq(irq_num, wsa885x);
+				if (irq_ret == IRQ_HANDLED)
+					handled = IRQ_HANDLED;
+			}
+		}
+	}
+	return handled;
+}
+
+static int wsa885x_register_irq(struct wsa885x_i2c_priv *wsa885x)
+{
+	int irq_number;
+
+	irq_number = gpiod_to_irq(wsa885x->intr_pin);
+	if (irq_number < 0)
+		return dev_err_probe(wsa885x->dev, irq_number,
+				     "Failed to get interrupt IRQ\n");
+
+	return devm_request_threaded_irq(wsa885x->dev, irq_number, NULL,
+					wsa885x_interrupt_handler,
+					IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+					dev_name(wsa885x->dev), wsa885x);
+}
+
+static int wsa885x_i2c_probe(struct i2c_client *client)
+{
+	struct wsa885x_i2c_priv *wsa885x;
+	const struct snd_soc_component_driver *component_driver = &wsa885x_i2c_component;
+	const char *init_table_prop = "qcom,wsa885x-init-table";
+	int ret, i, count;
+	struct device *dev = &client->dev;
+
+	wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL);
+	if (!wsa885x)
+		return -ENOMEM;
+
+	wsa885x->client = client;
+	wsa885x->dev = dev;
+	wsa885x->stereo_vol_db = -84;
+	wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO;
+	wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg);
+
+	if (IS_ERR(wsa885x->regmap))
+		return PTR_ERR(wsa885x->regmap);
+
+	/*
+	 * Use a signed int for the count check: device_property_count_u32()
+	 * returns a negative errno on error.  Storing it directly into the
+	 * uint32_t field before checking causes the negative value to wrap to
+	 * a huge positive number, bypassing the <= 0 guard and triggering a
+	 * multi-GB kmalloc that fails with -ENOMEM.
+	 */
+	count = device_property_count_u32(dev, init_table_prop);
+
+	if (count > 0) {
+		if (count % 2) {
+			dev_err(dev, "%s: Invalid number of elements in %s (%d)\n",
+				__func__, init_table_prop, count);
+			return -EINVAL;
+		}
+		if (count > WSA885X_INIT_TABLE_MAX_ITEMS) {
+			dev_err(dev, "%s: %s has too many elements (%d > %u)\n",
+				__func__, init_table_prop, count,
+				WSA885X_INIT_TABLE_MAX_ITEMS);
+			return -EINVAL;
+		}
+		wsa885x->init_table_size = count;
+
+		wsa885x->init_table = devm_kcalloc(dev, wsa885x->init_table_size,
+						   sizeof(*wsa885x->init_table), GFP_KERNEL);
+		if (!wsa885x->init_table)
+			return -ENOMEM;
+
+		if (device_property_read_u32_array(dev, init_table_prop,
+						   wsa885x->init_table,
+						   wsa885x->init_table_size)) {
+			dev_err(dev, "%s: Failed to read %s\n",
+				__func__, init_table_prop);
+			return -EINVAL;
+		}
+	}
+
+	ret = device_property_read_u32(dev, "qcom,battery-config",
+				       &wsa885x->batt_conf);
+	if (ret) {
+		wsa885x->batt_conf = WSA885X_BATT_1S;
+	} else if (wsa885x->batt_conf != WSA885X_BATT_1S &&
+		   wsa885x->batt_conf != WSA885X_BATT_2S) {
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid battery config %u (expected 1S or 2S)\n",
+				     wsa885x->batt_conf);
+	}
+
+	for (i = 0; i < WSA885X_SUPPLIES_NUM; i++)
+		wsa885x->supplies[i].supply = wsa885x_supply_name[i];
+
+	ret = devm_regulator_bulk_get(dev, WSA885X_SUPPLIES_NUM, wsa885x->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+	ret = regulator_bulk_enable(WSA885X_SUPPLIES_NUM, wsa885x->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable regulators\n");
+
+	ret = devm_add_action_or_reset(dev, wsa885x_regulator_disable, wsa885x);
+	if (ret)
+		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
+
+	wsa885x->sd_n = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_HIGH);
+	if (IS_ERR(wsa885x->sd_n))
+		return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n),
+							 "Shutdown Control GPIO not found\n");
+
+	wsa885x_gpio_set(wsa885x, false);
+
+	ret = devm_add_action_or_reset(dev, wsa885x_gpio_powerdown, wsa885x);
+	if (ret)
+		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
+
+	i2c_set_clientdata(client, wsa885x);
+
+	wsa885x->intr_pin = devm_gpiod_get(dev, "interrupt", GPIOD_IN);
+	if (IS_ERR(wsa885x->intr_pin))
+		return dev_err_probe(dev, PTR_ERR(wsa885x->intr_pin),
+							 "Interrupt GPIO not found\n");
+
+	ret = wsa885x_register_irq(wsa885x);
+	if (ret)
+		return dev_err_probe(dev, ret, "wsa885x irq registration failed\n");
+
+	ret = devm_snd_soc_register_component(dev, component_driver,
+					      wsa885x_i2c_dai,
+					      ARRAY_SIZE(wsa885x_i2c_dai));
+	if (ret)
+		return dev_err_probe(dev, ret, "Codec component registration failed\n");
+
+	return 0;
+}
+
+static const struct of_device_id wsa885x_i2c_dt_match[] = {
+	{
+		.compatible = "qcom,wsa885x-i2c",
+	},
+	{}
+};
+
+static const struct i2c_device_id wsa885x_id_i2c[] = {
+	{"wsa885x_i2c", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, wsa885x_id_i2c);
+MODULE_DEVICE_TABLE(of, wsa885x_i2c_dt_match);
+
+static struct i2c_driver wsa885x_i2c_driver = {
+	.driver = {
+		.name = "wsa885x_i2c",
+		.of_match_table = wsa885x_i2c_dt_match,
+	},
+	.probe = wsa885x_i2c_probe,
+	.id_table = wsa885x_id_i2c,
+};
+
+module_i2c_driver(wsa885x_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC WSA885X I2C Smart PA Codec Driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c
From: Prasad Kumpatla @ 2026-06-10 15:57 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Linus Walleij, Bartosz Golaszewski, Prasad Kumpatla
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, linux-gpio
In-Reply-To: <20260610155708.151067-1-prasad.kumpatla@oss.qualcomm.com>

Document the Qualcomm WSA885X I2C smart amplifier binding.

Describe the required supplies, powerdown and interrupt GPIOs, the
optional battery configuration, and the optional init-table property
used to program the device during codec initialization.

This matches the driver programming model and documents the DT data
needed to use the codec on platforms with Audio IF playback.

Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
---
 .../bindings/sound/qcom,wsa885x-i2c.yaml      | 89 +++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml

diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
new file mode 100644
index 000000000..1069f470d
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/qcom,wsa885x-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm WSA885x I2C smart speaker amplifier
+
+maintainers:
+  - Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+  - Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
+
+description: |
+  WSA885x is a Qualcomm Aqstic smart speaker amplifier with an I2C control
+  interface and a digital audio interface exposed through ASoC DAI callbacks.
+
+allOf:
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    const: qcom,wsa885x-i2c
+
+  reg:
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 0
+
+  powerdown-gpios:
+    description: GPIO controlling the SD_N powerdown pin.
+    maxItems: 1
+
+  interrupt-gpios:
+    description: GPIO used for the codec interrupt output.
+    maxItems: 1
+
+  vdd-1p8-supply: true
+
+  vdd-io-supply: true
+
+  qcom,battery-config:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: Speaker battery configuration, 1 for 1S and 2 for 2S.
+    default: 1
+    enum: [1, 2]
+
+  qcom,wsa885x-init-table:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 2
+    maxItems: 256
+    description: |
+      Sequence of register/value pairs applied during codec hardware
+      initialization. Entries are encoded as alternating register address and
+      register value cells. The number of entries must be even (register/value
+      pairs); maxItems is 256 (128 pairs).
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+  - powerdown-gpios
+  - interrupt-gpios
+  - vdd-1p8-supply
+  - vdd-io-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        speaker@c {
+            compatible = "qcom,wsa885x-i2c";
+            reg = <0x0c>;
+            #sound-dai-cells = <0>;
+            powerdown-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>;
+            interrupt-gpios = <&tlmm 77 GPIO_ACTIVE_LOW>;
+            vdd-1p8-supply = <&vreg_l2g_1p8>;
+            vdd-io-supply = <&vreg_l1g_1p2>;
+            qcom,battery-config = <1>;
+            qcom,wsa885x-init-table = <0x8606 0x24 0x8626 0x24>;
+        };
+    };
+...
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 0/2] ASoC: add Qualcomm WSA885X I2C codec support
From: Prasad Kumpatla @ 2026-06-10 15:57 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Linus Walleij, Bartosz Golaszewski, Prasad Kumpatla
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, linux-gpio

Add support for the Qualcomm WSA885X smart speaker amplifier accessed
over I2C.

The series first documents the qcom,wsa885x-i2c devicetree binding,
including supplies, GPIOs, battery configuration, and the init-table
data used during codec initialization.

It then adds the ASoC codec driver with register programming, serial
interface setup, clock handling, mute and gain control, reset handling,
interrupt support, runtime TDM slot-count configuration, and stream-time
power-state sequencing.

validated speaker playback on Shikra and Hawi platforms.

Prasad Kumpatla (2):
  dt-bindings: sound: add qcom,wsa885x-i2c
  ASoC: codecs: add Qualcomm WSA885X I2C codec driver

 .../bindings/sound/qcom,wsa885x-i2c.yaml      |   89 +
 sound/soc/codecs/Kconfig                      |   11 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/wsa885x-i2c.c                | 1643 +++++++++++++++++
 4 files changed, 1745 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
 create mode 100644 sound/soc/codecs/wsa885x-i2c.c


base-commit: 49e02880ec0a8c378e811bc9d85da188d7c6204c
-- 
2.34.1


^ permalink raw reply

* Re: [PATCH] dt-bindings: iio: accel: Convert lis302 binding to YAML schema
From: Jonathan Cameron @ 2026-06-10 15:56 UTC (permalink / raw)
  To: Md Shofiqul Islam
  Cc: linux-iio, devicetree, dlechner, nuno.sa, andy, robh, krzk+dt,
	conor+dt, krzk, linux-kernel
In-Reply-To: <20260610110051.1228-1-shofiqtest@gmail.com>

On Wed, 10 Jun 2026 14:00:51 +0300
Md Shofiqul Islam <shofiqtest@gmail.com> wrote:

> Convert the STMicroelectronics LIS302DL/LIS3LV02D accelerometer device
> tree binding from plain text format to YAML schema format.
> 
> The binding covers two variants matched via their respective bus drivers:
> - SPI: st,lis302dl-spi (drivers/misc/lis3lv02d/lis3lv02d_spi.c)
> - I2C: st,lis3lv02d   (drivers/misc/lis3lv02d/lis3lv02d_i2c.c)
> 
> Document all vendor-specific properties read by the driver via
> of_property_read_*(), including click detection, IRQ routing, free-fall/
> wake-up engines, high-pass filtering, axis remapping, output data rate,
> and self-test limits.
> 
> Also correct the click threshold property names: the driver reads
> "st,click-threshold-{x,y,z}" but the old .txt documented them as
> "st,click-thresh-{x,y,z}".
> 
> Validated with: make dt_binding_check   DT_SCHEMA_FILES=Documentation/devicetree/bindings/iio/accel/st,lis302dl.yaml
> 
> Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com>

Hi.

So the conundrum here is whether we want to keep carrying this binding
as it dates to a previous era.

The driver never made it to IIO and is still in drivers/misc.
The majority of what is the text document should never have been
in DT in the first place. I'll guess this dates all the way back
to the wild west days before we had regular binding review.



> diff --git a/Documentation/devicetree/bindings/iio/accel/st,lis302dl.yaml b/Documentation/devicetree/bindings/iio/accel/st,lis302dl.yaml
> new file mode 100644
> index 000000000000..befc419f7f39
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/accel/st,lis302dl.yaml
> @@ -0,0 +1,343 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/accel/st,lis302dl.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: STMicroelectronics LIS302DL/LIS3LV02D 3-Axis Accelerometer
> +
> +maintainers:
> +  - Jonathan Cameron <jic23@kernel.org>

NACK for that.  I'll only maintain bindings that are both in a good
form and typically even then only ones I have written.


> +
> +description: |
> +  STMicroelectronics LIS302DL (SPI) and LIS3LV02D (I2C) 3-axis MEMS
> +  accelerometers. Supports click detection, free-fall/wake-up interrupts,
> +  high-pass filtering, axis remapping, and self-test functions.
> +
> +  Driver located at drivers/misc/lis3lv02d/.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - st,lis302dl-spi

That wants deprecating. We don't include the bus in a compatible
as it can be trivially derived from where the device is declared.

> +      - st,lis3lv02d
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  Vdd-supply:
> +    description: Main power supply regulator (I2C variant).

That is very odd. Power supply that is only there when using one bus?
If there was an alternative for SPI and some weird naming thing
maybe but it seems that if not on I2C the device works fine without
power :)

> +
> +  Vdd_IO-supply:
> +    description: I/O power supply regulator (I2C variant).
> +
> +  st,click-single-x:
> +    type: boolean
> +    description: Enable single-click detection on X axis.
Everything from this one down to...

> +
> +  st,click-double-x:
> +    type: boolean
> +    description: Enable double-click detection on X axis.
> +
> +  st,click-single-y:
> +    type: boolean
> +    description: Enable single-click detection on Y axis.
> +
> +  st,click-double-y:
> +    type: boolean
> +    description: Enable double-click detection on Y axis.
> +
> +  st,click-single-z:
> +    type: boolean
> +    description: Enable single-click detection on Z axis.
> +
> +  st,click-double-z:
> +    type: boolean
> +    description: Enable double-click detection on Z axis.
> +
> +  st,click-threshold-x:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Click detection threshold for X axis.
> +
> +  st,click-threshold-y:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Click detection threshold for Y axis.
> +
> +  st,click-threshold-z:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Click detection threshold for Z axis.
> +
> +  st,click-time-limit:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Click time limit, 0 to 127.5 ms in 0.5 ms steps.
> +
> +  st,click-latency:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Click latency, 0 to 255 ms in 1 ms steps.
> +
> +  st,click-window:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Click window, 0 to 255 ms in 1 ms steps.

This one are userspace decisions.

> +
> +  st,irq1-disable:
> +    type: boolean
> +    description: Disable IRQ1 pin.
This one and the next lot 
> +
> +  st,irq1-ff-wu-1:
> +    type: boolean
> +    description: Route free-fall/wake-up 1 event to IRQ1 pin.
> +
> +  st,irq1-ff-wu-2:
> +    type: boolean
> +    description: Route free-fall/wake-up 2 event to IRQ1 pin.
> +
> +  st,irq1-data-ready:
> +    type: boolean
> +    description: Route data-ready event to IRQ1 pin.
> +
> +  st,irq1-click:
> +    type: boolean
> +    description: Route click event to IRQ1 pin.
> +
> +  st,irq2-disable:
> +    type: boolean
> +    description: Disable IRQ2 pin.
> +
> +  st,irq2-ff-wu-1:
> +    type: boolean
> +    description: Route free-fall/wake-up 1 event to IRQ2 pin.
> +
> +  st,irq2-ff-wu-2:
> +    type: boolean
> +    description: Route free-fall/wake-up 2 event to IRQ2 pin.
> +
> +  st,irq2-data-ready:
> +    type: boolean
> +    description: Route data-ready event to IRQ2 pin.
> +
> +  st,irq2-click:
> +    type: boolean
> +    description: Route click event to IRQ2 pin.
> +

are driver internal decisions. The dt-binding should tell
us which pins are wired, not make decisions on how the driver
uses them.

> +  st,irq-open-drain:
> +    type: boolean
> +    description: Configure IRQ lines as open-drain.
This one is fine but there is a generic binding for it IIRC.
> +
> +  st,irq-active-low:
> +    type: boolean
> +    description: Configure IRQ lines as active-low.
This one we normally do via the admittedly slightly dubious approach
of assuming the IRQ flags tell us this one.
> +
> +  st,wu-duration-1:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Duration register for free-fall/wake-up interrupt 1.
Back to stuff that should be userspace controlled.
> +
> +  st,wu-duration-2:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Duration register for free-fall/wake-up interrupt 2.
> +
> +  st,wakeup-x-lo:
> +    type: boolean
> +    description: Enable wake-up on X axis lower threshold crossing.
> +
> +  st,wakeup-x-hi:
> +    type: boolean
> +    description: Enable wake-up on X axis upper threshold crossing.
> +
> +  st,wakeup-y-lo:
> +    type: boolean
> +    description: Enable wake-up on Y axis lower threshold crossing.
> +
> +  st,wakeup-y-hi:
> +    type: boolean
> +    description: Enable wake-up on Y axis upper threshold crossing.
> +
> +  st,wakeup-z-lo:
> +    type: boolean
> +    description: Enable wake-up on Z axis lower threshold crossing.
> +
> +  st,wakeup-z-hi:
> +    type: boolean
> +    description: Enable wake-up on Z axis upper threshold crossing.
> +
> +  st,wakeup-threshold:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Threshold for wake-up engine 1.
> +
> +  st,wakeup2-x-lo:
> +    type: boolean
> +    description: Enable wake-up engine 2 on X axis lower threshold.
> +
> +  st,wakeup2-x-hi:
> +    type: boolean
> +    description: Enable wake-up engine 2 on X axis upper threshold.
> +
> +  st,wakeup2-y-lo:
> +    type: boolean
> +    description: Enable wake-up engine 2 on Y axis lower threshold.
> +
> +  st,wakeup2-y-hi:
> +    type: boolean
> +    description: Enable wake-up engine 2 on Y axis upper threshold.
> +
> +  st,wakeup2-z-lo:
> +    type: boolean
> +    description: Enable wake-up engine 2 on Z axis lower threshold.
> +
> +  st,wakeup2-z-hi:
> +    type: boolean
> +    description: Enable wake-up engine 2 on Z axis upper threshold.
> +
> +  st,wakeup2-threshold:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Threshold for wake-up engine 2.
> +
> +  st,highpass-cutoff-hz:
> +    enum: [1, 2, 4, 8]
> +    description: High-pass filter cut-off frequency in Hz.
> +
> +  st,hipass1-disable:
> +    type: boolean
> +    description: Disable high-pass filter 1.
> +
> +  st,hipass2-disable:
> +    type: boolean
> +    description: Disable high-pass filter 2.

End of userspace stuff.

> +
> +  st,axis-x:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: |
> +      Map physical X axis. Negative values invert the direction.
> +      Valid range -3 to 3, excluding 0.
> +
> +  st,axis-y:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: |
> +      Map physical Y axis. Negative values invert the direction.
> +      Valid range -3 to 3, excluding 0.
> +
> +  st,axis-z:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: |
> +      Map physical Z axis. Negative values invert the direction.
> +      Valid range -3 to 3, excluding 0.

The 3 are fine but should be deprecated and replaced with mount-matrix
which makes it a userspace problem on the whole.  There is little
reason to ever have this stuff down in the driver.

> +

> +  st,default-rate:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Default output data rate in Hz.
Nope. Driver should pick a value, then control from userspace.
No reason to have a default in DT.

> +
> +  st,min-limit-x:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: Minimum self-test limit for X axis.
> +
> +  st,min-limit-y:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: Minimum self-test limit for Y axis.
> +
> +  st,min-limit-z:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: Minimum self-test limit for Z axis.
> +
> +  st,max-limit-x:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: Maximum self-test limit for X axis.
> +
> +  st,max-limit-y:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: Maximum self-test limit for Y axis.
> +
> +  st,max-limit-z:
> +    $ref: /schemas/types.yaml#/definitions/int32
> +    description: Maximum self-test limit for Z axis.
Those are actually plausible things to have in DT. Maybe...
Depends a bit on what governs how they are set and whether
there are always 'good enough' numbers we can hard code in
the driver.


> +
> +required:
> +  - compatible
> +  - reg
Supplies etc.

> +
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          enum:
> +            - st,lis302dl-spi
> +    then:
> +      required:
> +        - spi-max-frequency
> +        - interrupts
Seems unlikely the other part doesn't have an interrupt or
that the device is useless with out one.  Note we don't care if the
driver requires it - that has nothing to do with the binding.

> +  - if:
> +      properties:
> +        compatible:
> +          enum:
> +            - st,lis3lv02d
> +    then:
> +      required:
> +        - Vdd-supply
> +        - Vdd_IO-supply
as above. This smells like documenting the driver, not what the wiring is.
I would be very surprised if the other part doesn't have power.

> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        accelerometer@0 {
> +            compatible = "st,lis302dl-spi";
> +            reg = <0>;
> +            spi-max-frequency = <1000000>;
> +            interrupt-parent = <&gpio>;
> +            interrupts = <104 IRQ_TYPE_EDGE_RISING>;
> +            st,click-single-x;
> +            st,click-single-y;
> +            st,click-single-z;
> +            st,click-threshold-x = <10>;
> +            st,click-threshold-y = <10>;
> +            st,click-threshold-z = <10>;
> +            st,irq1-click;
> +            st,irq2-click;
> +            st,wakeup-x-lo;
> +            st,wakeup-x-hi;
> +            st,wakeup-y-lo;
> +            st,wakeup-y-hi;
> +            st,wakeup-z-lo;
> +            st,wakeup-z-hi;
> +        };
> +    };
> +  - |
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        accelerometer@18 {
> +            compatible = "st,lis3lv02d";
> +            reg = <0x18>;
> +            Vdd-supply = <&lis3_reg>;
> +            Vdd_IO-supply = <&lis3_reg>;
> +            st,click-single-x;
> +            st,click-single-y;
> +            st,click-single-z;
> +            st,click-threshold-x = <10>;
> +            st,click-threshold-y = <10>;
> +            st,click-threshold-z = <10>;
> +            st,irq1-click;
> +            st,irq2-click;
> +            st,wakeup-x-lo;
> +            st,wakeup-x-hi;
> +            st,wakeup-y-lo;
> +            st,wakeup-y-hi;
> +            st,wakeup-z-lo;
> +            st,wakeup-z-hi;
> +            st,min-limit-x = <120>;
> +            st,min-limit-y = <120>;
> +            st,min-limit-z = <140>;
> +            st,max-limit-x = <550>;
> +            st,max-limit-y = <550>;
> +            st,max-limit-z = <750>;
> +        };
> +    };
> +...


^ permalink raw reply

* Re: [PATCH 3/3] media: renesas: vsp1: Make reset control optional to support platforms without a reset line
From: Laurent Pinchart @ 2026-06-10 15:49 UTC (permalink / raw)
  To: Prabhakar
  Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Kieran Bingham, Philipp Zabel, Geert Uytterhoeven,
	Magnus Damm, linux-media, linux-renesas-soc, devicetree,
	linux-kernel, Biju Das, Fabrizio Castro, Lad Prabhakar
In-Reply-To: <20260430100929.1088281-4-prabhakar.mahadev-lad.rj@bp.renesas.com>

Hi Prabhakar,

Thank you for the patch.

On Thu, Apr 30, 2026 at 11:09:29AM +0100, Prabhakar wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> 
> Switch the VSP1 driver to use devm_reset_control_get_optional_shared()
> when requesting its reset control. Some newer Renesas SoCs integrating
> VSP1 such as RZ/T2H do not provide a reset line for the VSP IP block.
> 
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>

Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>

> ---
>  drivers/media/platform/renesas/vsp1/vsp1_drv.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drv.c b/drivers/media/platform/renesas/vsp1/vsp1_drv.c
> index 627b5046fa80..605fac57bd93 100644
> --- a/drivers/media/platform/renesas/vsp1/vsp1_drv.c
> +++ b/drivers/media/platform/renesas/vsp1/vsp1_drv.c
> @@ -947,7 +947,7 @@ static int vsp1_probe(struct platform_device *pdev)
>  	if (irq < 0)
>  		return irq;
>  
> -	vsp1->rstc = devm_reset_control_get_shared(&pdev->dev, NULL);
> +	vsp1->rstc = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
>  	if (IS_ERR(vsp1->rstc))
>  		return dev_err_probe(&pdev->dev, PTR_ERR(vsp1->rstc),
>  				     "failed to get reset control\n");

-- 
Regards,

Laurent Pinchart

^ permalink raw reply

* Re: [PATCH v6 00/11] ASoC: renesas: fsi: Fix system hang by adding SPU clock
From: Bui Duc Phuc @ 2026-06-10 15:49 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, Geert Uytterhoeven, Liam Girdwood, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Magnus Damm, Jaroslav Kysela,
	Takashi Iwai, linux-sound, linux-renesas-soc, devicetree,
	linux-kernel
In-Reply-To: <178104804456.232889.7203992010119048881.b4-ty@b4>

Hi Mark,

> Applied to
>
>    https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-7.2
>

Thank you for your review and for applying the patch.

Best regards,
Phuc

^ 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