Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v2 2/8] irqchip/qcom-pdc: Move all statics to struct pdc_desc
From: Maulik Shah (mkshah) @ 2026-06-15  5:36 UTC (permalink / raw)
  To: Thomas Gleixner, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Linus Walleij
  Cc: linux-arm-msm, linux-kernel, devicetree, linux-gpio, Sneh Mankad
In-Reply-To: <87bjdr7ikt.ffs@fw13>



On 6/3/2026 8:54 PM, Thomas Gleixner wrote:
> On Tue, May 26 2026 at 16:24, Maulik Shah wrote:
>> @@ -221,9 +231,9 @@ static void pdc_enable_intr(struct irq_data *d, bool on)
>>  {
>>  	unsigned long flags;
>>  
>> -	raw_spin_lock_irqsave(&pdc_lock, flags);
>> -	__pdc_enable_intr(d->hwirq, on);
>> -	raw_spin_unlock_irqrestore(&pdc_lock, flags);
>> +	raw_spin_lock_irqsave(&pdc->lock, flags);
> 
> While at it please convert this to:
> 
>       guard(raw_spinlock_irqsave)(...);
> 
> The _irqsave is not really required when invoked from the irqchip
> callbacks with irq desc lock held and interrupts disabled, but that's
> also magically invoked from other contexts. So you could spare that
> irqsave by wrapping the other callsite into:
> 
>         guard(irq)() or scoped_guard(irq)

Ack. Will update in v3.

> 

>> +	for (i = 0; i < pdc->region_cnt; i++) {
> 
> for (int i = 0; ....

Ack. Will update in v3.

> 
>> +		if (pin >= pdc->region[i].pin_base &&
>> +		    pin < pdc->region[i].pin_base + pdc->region[i].cnt)
>> +			return &pdc->region[i];
>>  
>> +	raw_spin_lock_init(&pdc->lock);
>> +
>>  	pdc_domain = irq_domain_create_hierarchy(parent_domain,
>>  					IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP,
>>  					PDC_MAX_IRQS,
> 
> Please fix up the coding style here according to
> 
> https://docs.kernel.org/process/maintainer-tip.html


Ack. Will update in v3.

Thanks,
Maulik


^ permalink raw reply

* Re: [PATCH v2 4/8] irqchip/qcom-pdc: Differentiate between direct SPI and GPIO as SPI
From: Maulik Shah (mkshah) @ 2026-06-15  5:36 UTC (permalink / raw)
  To: Thomas Gleixner, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Linus Walleij
  Cc: linux-arm-msm, linux-kernel, devicetree, linux-gpio, Sneh Mankad
In-Reply-To: <875x3z7igr.ffs@fw13>



On 6/3/2026 8:57 PM, Thomas Gleixner wrote:
> On Tue, May 26 2026 at 16:24, Maulik Shah wrote:
>>  /**
>> @@ -92,6 +99,8 @@ struct pdc_cfg {
>>   * @base:           PDC base register for DRV2 / HLOS
>>   * @prev_base:      PDC DRV1 base, applicable only for x1e RTL bug.
>>   * @version:        PDC version
>> + * @num_spis:       Total number of direct SPI interrupts
>> + * @num_gpios:      Total number of GPIOs forwarded as SPI interrupts
>>   * @region:         PDC interrupt continuous range
>>   * @region_cnt:     Total PDC ranges
>>   * @x1e_quirk:      x1e H/W Bug handling
>> @@ -104,6 +113,8 @@ struct pdc_desc {
>>  	void __iomem *base;
>>  	void __iomem *prev_base;
>>  	u32 version;
>> +	u32 num_spis;
>> +	u32 num_gpios;
> 
> Please fix up the struct definition coding style.
>   

Ack. Will update in v3.

Thanks,
Maulik

^ permalink raw reply

* Re: [PATCH v10 2/2] leds: ltc3220: Add Support for LTC3220 18 channel LED Driver
From: sashiko-bot @ 2026-06-15  5:38 UTC (permalink / raw)
  To: Edelweise Escala; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260615-ltc3220-driver-v10-2-916562c88598@analog.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Duplicate LED `reg` values bypass the validation check, leading to a doubly-linked list corruption in the LED core.
- [Medium] Missing LED class suspend/resume calls in system PM callbacks.
- [Medium] The `pattern_set` callback silently ignores the `repeat` parameter, breaking continuous patterns.
- [Low] Setting blink mode on a deactivated LED causes an unnecessary I2C read and hardware glitch.
--

commit ff936b952a6a427e0232229537a10438e4f34a11
Author: Edelweise Escala <edelweise.escala@analog.com>
Subject: leds: ltc3220: Add Support for LTC3220 18 channel LED Driver

Add driver for the LTC3220 18-channel LED driver with I2C interface,
individual brightness control, and hardware-assisted blink/gradation features.

> diff --git a/drivers/leds/leds-ltc3220.c b/drivers/leds/leds-ltc3220.c
> new file mode 100644
> index 0000000000000..f95174ae451ba
> --- /dev/null
> +++ b/drivers/leds/leds-ltc3220.c
[ ... ]
> +static int ltc3220_pattern_set(struct led_classdev *led_cdev,
> +			       struct led_pattern *pattern,
> +			       u32 len, int repeat)
> +{

[Severity: Medium]
Does this callback silently ignore the repeat parameter? 

If the user requests infinite repetition (for example, repeat is -1), the 
driver returns 0 without programming continuous breathing. Should this return 
-EOPNOTSUPP when repetition is requested, so the LED core can fall back to 
software-driven repetition instead of halting after a single ramp?

> +	struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg,
> +							 led_cdev);
[ ... ]
> +static int ltc3220_blink_set(struct led_classdev *led_cdev,
> +			     unsigned long *delay_on,
> +			     unsigned long *delay_off)
> +{
[ ... ]
> +	/* Ensure LED has non-zero brightness before enabling blink mode */
> +	if (uled_cfg->reg_value == 0) {
> +		ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
> +				   led_cdev->max_brightness);
> +		if (ret)
> +			return ret;
> +
> +		uled_cfg->reg_value = led_cdev->max_brightness;
> +	}
> +
> +	return regmap_update_bits(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index),
> +				  LTC3220_LED_MODE_MASK,
> +				  FIELD_PREP(LTC3220_LED_MODE_MASK, LTC3220_BLINK_MODE));
> +}

[Severity: Low]
When ltc3220_blink_set() is called on an LED with zero brightness, does this 
sequence cause a visual glitch and an unnecessary I2C read? 

The first regmap_write() turns the LED on solid in normal mode, and then 
regmap_update_bits() incurs an I2C read to switch it to blink mode. Could 
the driver construct the final 8-bit value from the cached brightness and 
write it directly using a single regmap_write()?

[ ... ]
> +static int ltc3220_suspend(struct device *dev)
> +{
> +	struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev));
> +
> +	return regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG,
> +				  LTC3220_SHUTDOWN_MASK, LTC3220_SHUTDOWN_MASK);
> +}

[Severity: Medium]
Does this suspend callback miss notifying the LED core about the suspension? 

By not iterating over the registered LEDs and calling led_classdev_suspend(), 
the LED core remains unaware the device is suspended and will not pause 
triggers. Could an active software trigger attempt an I2C transaction while 
the system is suspending, potentially causing bus timeouts?

[ ... ]
> +static int ltc3220_probe(struct i2c_client *client)
> +{
[ ... ]
> +	/* First pass: validate configuration and set up LED structures */
> +	device_for_each_child_node_scoped(&client->dev, child) {
> +		struct ltc3220_uled_cfg *led;
> +		u32 source;
[ ... ]
> +		/* LED node reg/index/address goes from 1 to 18 */
> +		led_index = source - 1;
> +		led = &ltc3220->uled_cfg[led_index];
> +
> +		if (led->led_cdev.dev)
> +			return dev_err_probe(&client->dev, -EINVAL,
> +					     "Duplicate LED reg %u found", source);

[Severity: High]
Is led_cdev.dev always null during the first pass of ltc3220_probe()? 

Since ltc3220 is allocated with devm_kzalloc(), led_cdev.dev will be zero. It 
is only assigned during devm_led_classdev_register_ext() in the second pass, 
so this duplicate checking logic appears to be bypassed.

If a device tree contains duplicate reg values, will it silently overwrite 
the same uled_cfg structure during this first pass?

[ ... ]
> +	/* Second pass: register LEDs after validation */
> +	device_for_each_child_node_scoped(&client->dev, child) {
> +		struct led_init_data init_data = {};
> +		struct ltc3220_uled_cfg *led;
> +		u32 source;
> +
> +		ret = fwnode_property_read_u32(child, "reg", &source);
> +		if (ret)
> +			return ret;
> +
> +		init_data.fwnode = child;
> +		init_data.devicename = "ltc3220";
> +
> +		led_index = source - 1;
> +		led = &ltc3220->uled_cfg[led_index];
> +
> +		ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data);

[Severity: High]
Following from the duplicate check bypass above, if a duplicate reg value is 
present, will devm_led_classdev_register_ext() call list_add_tail() twice on 
the exact same node address? This would corrupt the LED core's doubly-linked 
list and reliably cause a kernel panic upon iteration or removal.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-ltc3220-driver-v10-0-916562c88598@analog.com?part=2

^ permalink raw reply

* Re: [PATCH v3 2/4] dt-bindings: display: panel: Add documentation for lg,sw49410-lh609qh1
From: Krzysztof Kozlowski @ 2026-06-15  5:38 UTC (permalink / raw)
  To: Paul Sajna
  Cc: Neil Armstrong, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding, Sam Ravnborg,
	Jessica Zhang, linux-kernel, dri-devel, devicetree,
	David Heidelberg, phone-devel, Amir Dahan
In-Reply-To: <20260614-judyln-panel-v3-2-07f4134441bd@postmarketos.org>

On Sun, Jun 14, 2026 at 05:07:59PM -0700, Paul Sajna wrote:
> Document how to use lg,sw49410-lh609qh1 in a devicetree

"Add Foo bar MIPI DSI panel, which suppors somehing something resolution
etc."

You describe hardware.

> 
> Signed-off-by: Paul Sajna <sajattack@postmarketos.org>
> ---
>  .../bindings/display/panel/lg,sw49410.yaml         | 79 ++++++++++++++++++++++
>  1 file changed, 79 insertions(+)
> 

A nit, subject: drop second/last, redundant "documentation for". The
"dt-bindings" prefix is already stating that this is documentation.
See also:
https://elixir.bootlin.com/linux/v7.1-rc7/source/Documentation/devicetree/bindings/submitting-patches.rst#L23

> diff --git a/Documentation/devicetree/bindings/display/panel/lg,sw49410.yaml b/Documentation/devicetree/bindings/display/panel/lg,sw49410.yaml
> new file mode 100644
> index 000000000000..4c4bf4d89353
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/lg,sw49410.yaml
> @@ -0,0 +1,79 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/panel/lg,sw49410.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: LG LH609QH1 MIPI-DSI panel with SW49410 controller
> +
> +maintainers:
> +  - Paul Sajna <sajattack@postmarketos.org>
> +
> +description:
> +  LG LH609QH1 6.1" 1440x3120 MIPI DSI panel with SW49410 controller found in LG G7 ThinQ smartphone.

Please wrap code according to the preferred limit expressed in Kernel
coding style (checkpatch is not a coding style description, but only a
tool).  However don't wrap blindly (see Kernel coding style).

> +
> +allOf:
> +  - $ref: panel-common.yaml#
> +
> +properties:
> +  compatible:
> +    items:
> +      - const: lg,sw49410
> +      - const: lg,sw49410-lh609qh1

This is very confusing - why two compatibles and why same devices - both
sw49410 - are compatible with each other?

> +
> +  reg:
> +    maxItems: 1
> +
> +  backlight:
> +    description: Backlight device reference

Drop property here, not needed.

> +
> +  reset-gpios:
> +    description: Reset pin reference
> +
> +  vsp-supply:
> +    description: Positive voltage supply
> +
> +  vsn-supply:
> +    description: Negative voltage supply
> +
> +required:
> +  - compatible
> +  - reg
> +  - reset-gpios
> +  - port
> +  - vsp-supply
> +  - vsn-supply
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    dsi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        panel@0 {
> +            reg = <0>;
> +            compatible = "lg,sw49410-lh609qh1";
> +
> +            backlight = <&pmi8998_wled>;
> +            reset-gpios = <&tlmm 6 GPIO_ACTIVE_LOW>;
> +            width-mm = <65>;
> +            height-mm = <140>;
> +
> +            vsp-supply = <&lab>;
> +            vsn-supply = <&ibb>;
> +
> +            pinctrl-0 = <&sde_dsi_active &sde_te_active_sleep>;
> +            pinctrl-1 = <&sde_dsi_sleep &sde_te_active_sleep>;
> +            pinctrl-names = "default", "sleep";
> +
> +            port {
> +              panel_in: endpoint {

Messed/inconsistent indentation.


Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v3 2/4] dt-bindings: display: panel: Add documentation for lg,sw49410-lh609qh1
From: Krzysztof Kozlowski @ 2026-06-15  5:39 UTC (permalink / raw)
  To: Paul Sajna
  Cc: Neil Armstrong, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding, Sam Ravnborg,
	Jessica Zhang, linux-kernel, dri-devel, devicetree,
	David Heidelberg, phone-devel, Amir Dahan
In-Reply-To: <20260614-judyln-panel-v3-2-07f4134441bd@postmarketos.org>

On Sun, Jun 14, 2026 at 05:07:59PM -0700, Paul Sajna wrote:
> +    dsi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        panel@0 {
> +            reg = <0>;
> +            compatible = "lg,sw49410-lh609qh1";

Ah, and you never tested this, so I should not even review it.

Be sure you test your code BEFORE you send to mailing list. Our build
infrastructure is not a replacement of your testing.

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v3 4/4] Revert "dt-bindings: display: panel: panel-simple: Add lg,sw49410 compatible"
From: Krzysztof Kozlowski @ 2026-06-15  5:40 UTC (permalink / raw)
  To: Paul Sajna
  Cc: Neil Armstrong, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding, Sam Ravnborg,
	Jessica Zhang, linux-kernel, dri-devel, devicetree,
	David Heidelberg, phone-devel, Amir Dahan
In-Reply-To: <20260614-judyln-panel-v3-4-07f4134441bd@postmarketos.org>

On Sun, Jun 14, 2026 at 05:08:01PM -0700, Paul Sajna wrote:
> This reverts commit a74c2e55ab66519ffa2069ac9ae83cd937bff4c4.
> 
> It isn't actually panel-simple-compatible, and we knew as much by the

This is not a separate commit. Your work is non-bisectable - this commit
leaves undocumented compatible.


Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v3 1/4] drm: panel: Add LG LH609QH1 Panel with SW49410 controller
From: Krzysztof Kozlowski @ 2026-06-15  5:40 UTC (permalink / raw)
  To: Paul Sajna
  Cc: Neil Armstrong, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding, Sam Ravnborg,
	Jessica Zhang, linux-kernel, dri-devel, devicetree,
	David Heidelberg, phone-devel, Amir Dahan
In-Reply-To: <20260614-judyln-panel-v3-1-07f4134441bd@postmarketos.org>

On Sun, Jun 14, 2026 at 05:07:58PM -0700, Paul Sajna wrote:
> From: Amir Dahan <system64fumo@tuta.io>
> 
> Add panel driver used by LG G7 ThinQ (judyln)
> 
> Signed-off-by: Amir Dahan <system64fumo@tuta.io>
> Co-developed-by: Paul Sajna <sajattack@postmarketos.org>
> Signed-off-by: Paul Sajna <sajattack@postmarketos.org>
> ---

Please organize the patch documenting the compatible (DT bindings)
before the patch using that compatible.
See also: https://elixir.bootlin.com/linux/v6.14-rc6/source/Documentation/devicetree/bindings/submitting-patches.rst#L46

Best regards,
Krzysztof


^ permalink raw reply

* [PATCH 0/3] phy: nuvoton: extend MA35D1 USB2 PHY driver for dual-port OTG support
From: Joey Lu @ 2026-06-15  5:49 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
	Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
	Joey Lu

The MA35D1 SoC has two USB PHY ports managed by a shared hardware block:

  - PHY0 (USB0): OTG port shared between the DWC2 gadget controller and
    the EHCI0/OHCI0 host controllers.  A hardware mux automatically routes
    USB0 signals to the correct controller based on the USB ID pin.

  - PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.

A previous series [1] added a separate phy-ma35d1-otg.c driver for this.
Following reviewer suggestion to reuse the existing phy-ma35d1-usb2.c
driver rather than introduce a new one, that series has been dropped and
this series instead extends the existing driver.

Changes in this series:

  Patch 1 updates the nuvoton,ma35d1-usb2-phy binding: the PHY node
  becomes a child of the syscon node (reg = <0x60 0x14>), nuvoton,sys
  phandle and clocks are removed, and #phy-cells changes from 0 to 1
  for per-port selection.  Optional nuvoton,rcalcode and
  nuvoton,oc-active-high properties are added.

  Patch 2 updates the MA35D1 DTS: sys gains simple-mfd, usb-phy@60 is
  added as a syscon child, and DWC2/EHCI0/EHCI1/OHCI0/OHCI1 nodes are
  added.  Board files enable the nodes and add HSUSB pinctrl.

  Patch 3 extends phy-ma35d1-usb2.c: a loop creates two struct phy
  objects; a unified .init handles both ports with parametric register
  macros; clock management is removed (each controller gates its own
  clock); a read-only USB role switch is registered for PHY0 reporting
  the USB ID pin via PWRONOTP[16].

Link: [1] https://lore.kernel.org/linux-phy/20260604101220.1092822-1-a0987203069@gmail.com/T/#t

Joey Lu (3):
  dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG
    support
  arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY
    node
  phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support

 .../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml |  67 +++--
 .../boot/dts/nuvoton/ma35d1-iot-512m.dts      |  36 +++
 .../boot/dts/nuvoton/ma35d1-som-256m.dts      |  36 +++
 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi       |  68 ++++-
 drivers/phy/nuvoton/phy-ma35d1-usb2.c         | 263 +++++++++++++-----
 5 files changed, 378 insertions(+), 92 deletions(-)


base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
-- 
2.43.0


^ permalink raw reply

* [PATCH 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
From: Joey Lu @ 2026-06-15  5:49 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
	Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
	Joey Lu
In-Reply-To: <20260615054911.48821-1-a0987203069@gmail.com>

The MA35D1 has two USB PHY ports managed by the same hardware block:

  - PHY0 (index 0): OTG port shared between the DWC2 gadget controller
    and EHCI0/OHCI0 host controllers.  A hardware mux follows the USB
    ID pin automatically.

  - PHY1 (index 1): dedicated host-only port for EHCI1/OHCI1.

Extend the existing binding to cover both ports:

  - The PHY node is now a child of the system-management syscon node
    with a reg property.  The nuvoton,sys phandle and clocks
    properties are removed; the driver derives the regmap from its
    parent, and clock gating is owned by each individual USB controller.

  - #phy-cells changes from 0 to 1: the cell selects the PHY port.

  - Two optional board-tuning properties are added: nuvoton,rcalcode
    for per-port resistor trim and nuvoton,oc-active-high for
    over-current polarity.

Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
 .../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 62 ++++++++++++++-----
 1 file changed, 48 insertions(+), 14 deletions(-)

diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
index fff858c909a0..dde045aff44e 100644
--- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
@@ -8,38 +8,72 @@ title: Nuvoton MA35D1 USB2 phy
 
 maintainers:
   - Hui-Ping Chen <hpchen0nvt@gmail.com>
+  - Joey Lu <yclu4@nuvoton.com>
+
+description:
+  USB 2.0 PHY for the Nuvoton MA35D1 SoC.  The PHY node is a child of the
+  system-management syscon node and covers both PHY ports.
+
+  PHY0 (index 0) is the OTG port whose signals are routed to either the DWC2
+  gadget controller or the EHCI0/OHCI0 host controller by a hardware mux that
+  follows the USB ID pin automatically.
+
+  PHY1 (index 1) is a dedicated host-only port used by EHCI1/OHCI1.
 
 properties:
   compatible:
     enum:
       - nuvoton,ma35d1-usb2-phy
 
+  reg:
+    maxItems: 1
+
   "#phy-cells":
-    const: 0
+    const: 1
+    description:
+      The single cell selects the PHY port. 0 selects the OTG port (USB0,
+      shared with DWC2 gadget controller) and 1 selects the host-only port
+      (USB1).
 
-  clocks:
-    maxItems: 1
+  nuvoton,rcalcode:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 1
+    maxItems: 2
+    items:
+      minimum: 0
+      maximum: 15
+    description:
+      Resistor calibration trim codes for PHY0 and, optionally, PHY1.
+      Each value is written to the RCALCODE field in USBPMISCR for the
+      corresponding PHY. The 4-bit value adjusts the PHY's internal
+      termination resistance. When absent the hardware reset default is used.
 
-  nuvoton,sys:
-    $ref: /schemas/types.yaml#/definitions/phandle
+  nuvoton,oc-active-high:
+    type: boolean
     description:
-      phandle to syscon for checking the PHY clock status.
+      When present, the over-current detect input from the VBUS power switch
+      is treated as active-high. The default (property absent) is active-low.
+      This setting is shared by both USB host ports.
 
 required:
   - compatible
+  - reg
   - "#phy-cells"
-  - clocks
-  - nuvoton,sys
 
 additionalProperties: false
 
 examples:
   - |
-    #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+    system-management@40460000 {
+        compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
+        reg = <0x0 0x40460000 0x0 0x200>;
+        #reset-cells = <1>;
+        #address-cells = <1>;
+        #size-cells = <1>;
 
-    usb_phy: usb-phy {
-        compatible = "nuvoton,ma35d1-usb2-phy";
-        clocks = <&clk USBD_GATE>;
-        nuvoton,sys = <&sys>;
-        #phy-cells = <0>;
+        usb-phy@60 {
+            compatible = "nuvoton,ma35d1-usb2-phy";
+            reg = <0x60 0x14>;
+            #phy-cells = <1>;
+        };
     };
-- 
2.43.0


^ permalink raw reply related

* [PATCH 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
From: Joey Lu @ 2026-06-15  5:49 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
	Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
	Joey Lu
In-Reply-To: <20260615054911.48821-1-a0987203069@gmail.com>

Add device tree nodes for the MA35D1 USB subsystem:

  - sys node gains simple-mfd + address/size-cells so it can contain
    the usb-phy@60 child.

  - usb-phy@60 is added as a child of sys, using the combined
    nuvoton,ma35d1-usb2-phy driver with #phy-cells = <1>.  No clock
    properties: clock gating is handled by each controller node.

  - DWC2 gadget (usb@40200000), EHCI0/1, and OHCI0/1 nodes are
    added.  Each controller names its clock gate directly and
    references the PHY by index (0 for the OTG port, 1 for the
    dedicated host port).

  - Board files (ma35d1-som-256m.dts, ma35d1-iot-512m.dts) enable the
    PHY, dwc2, ehci0/1, and ohci0/1 nodes and add pinctrl for the
    HSUSB signals (VBUSVLD, PWREN, OVC).

Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
 .../boot/dts/nuvoton/ma35d1-iot-512m.dts      | 36 ++++++++++
 .../boot/dts/nuvoton/ma35d1-som-256m.dts      | 36 ++++++++++
 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi       | 68 ++++++++++++++++++-
 3 files changed, 139 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
index 9482bec1aa57..32fea36da7f4 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
@@ -95,6 +95,16 @@ pinctrl_uart14: uart14-pins {
 			power-source = <1>;
 		};
 	};
+
+	hsusb {
+		pinctrl_hsusb: hsusb-pins {
+			nuvoton,pins = <5 15 1>, /* VBUSVLD */
+				       <11 12 9>,    /* PWREN */
+				       <11 13 9>;    /* OVC */
+			bias-disable;
+			power-source = <1>;
+		};
+	};
 };
 
 &uart0 {
@@ -126,3 +136,29 @@ &uart14 {
 	pinctrl-0 = <&pinctrl_uart14>;
 	status = "okay";
 };
+
+&usb_phy {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_hsusb>;
+	status = "okay";
+};
+
+&usb {
+	status = "okay";
+};
+
+&ehci0 {
+	status = "okay";
+};
+
+&ehci1 {
+	status = "okay";
+};
+
+&ohci0 {
+	status = "okay";
+};
+
+&ohci1 {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
index f6f20a17e501..85d1c5db8bd9 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
@@ -98,6 +98,16 @@ pinctrl_uart16: uart16-pins {
 			power-source = <1>;
 		};
 	};
+
+	hsusb {
+		pinctrl_hsusb: hsusb-pins {
+			nuvoton,pins = <5 15 1>, /* VBUSVLD */
+				       <11 12 9>,    /* PWREN */
+				       <11 13 9>;    /* OVC */
+			bias-disable;
+			power-source = <1>;
+		};
+	};
 };
 
 &uart0 {
@@ -129,3 +139,29 @@ &uart16 {
 	pinctrl-0 = <&pinctrl_uart16>;
 	status = "okay";
 };
+
+&usb_phy {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_hsusb>;
+	status = "okay";
+};
+
+&usb {
+	status = "okay";
+};
+
+&ehci0 {
+	status = "okay";
+};
+
+&ehci1 {
+	status = "okay";
+};
+
+&ohci0 {
+	status = "okay";
+};
+
+&ohci1 {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
index e51b98f5bdce..73ff1d78d284 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -83,9 +83,18 @@ soc {
 		ranges;
 
 		sys: system-management@40460000 {
-			compatible = "nuvoton,ma35d1-reset", "syscon";
+			compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
 			reg = <0x0 0x40460000 0x0 0x200>;
 			#reset-cells = <1>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			usb_phy: usb-phy@60 {
+				compatible = "nuvoton,ma35d1-usb2-phy";
+				reg = <0x60 0x14>;
+				#phy-cells = <1>;
+				status = "disabled";
+			};
 		};
 
 		clk: clock-controller@40460200 {
@@ -379,5 +388,62 @@ uart16: serial@40880000 {
 			clocks = <&clk UART16_GATE>;
 			status = "disabled";
 		};
+
+		usb: usb@40200000 {
+			compatible = "snps,dwc2";
+			reg = <0x0 0x40200000 0x0 0x1000>;
+			interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk USBD_GATE>;
+			clock-names = "otg";
+			phys = <&usb_phy 0>;
+			phy-names = "usb2-phy";
+			dr_mode = "peripheral";
+			g-np-tx-fifo-size = <16>;
+			g-rx-fifo-size = <0x100>;
+			g-tx-fifo-size = <256 256 64 64 64 32 32 32>;
+			status = "disabled";
+		};
+
+		ehci0: usb@40140000 {
+			compatible = "generic-ehci";
+			reg = <0x0 0x40140000 0x0 0x1000>;
+			interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk HUSBH0_GATE>;
+			phys = <&usb_phy 0>;
+			phy-names = "usb";
+			companion = <&ohci0>;
+			status = "disabled";
+		};
+
+		ehci1: usb@401C0000 {
+			compatible = "generic-ehci";
+			reg = <0x0 0x401c0000 0x0 0x1000>;
+			interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk HUSBH1_GATE>;
+			phys = <&usb_phy 1>;
+			phy-names = "usb";
+			companion = <&ohci1>;
+			status = "disabled";
+		};
+
+		ohci0: usb@40150000 {
+			compatible = "generic-ohci";
+			reg = <0x0 0x40150000 0x0 0x1000>;
+			interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk HUSBH0_GATE>;
+			phys = <&usb_phy 0>;
+			phy-names = "usb";
+			status = "disabled";
+		};
+
+		ohci1: usb@401D0000 {
+			compatible = "generic-ohci";
+			reg = <0x0 0x401d0000 0x0 0x1000>;
+			interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk HUSBH1_GATE>;
+			phys = <&usb_phy 1>;
+			phy-names = "usb";
+			status = "disabled";
+		};
 	};
 };
-- 
2.43.0


^ permalink raw reply related

* [PATCH 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
From: Joey Lu @ 2026-06-15  5:49 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
	Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
	Joey Lu
In-Reply-To: <20260615054911.48821-1-a0987203069@gmail.com>

The existing driver handled only PHY0 in device mode (DWC2 gadget).
Extend it to manage both PHY ports and integrate OTG support, per
reviewer suggestion to reuse the existing driver rather than add a
separate one.

The MA35D1 SoC has two USB PHY ports:

  - PHY0 (USB0): OTG port shared between the DWC2 gadget controller
    and EHCI0/OHCI0 host controllers.  A hardware mux in the SoC
    automatically routes the USB0 signals to the appropriate
    controller based on the USB ID pin state.

  - PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.

Key changes:

  Dual-port support
    A loop in probe() creates two struct phy objects, one per port,
    each with its own phy_set_drvdata() context.  A custom xlate
    function selects the correct phy by the single #phy-cells argument.

  Unified .init callback
    A single ma35_usb_phy_init() handles both ports using parametric
    register macros (USBPMISCR_PHY_*(n)).  If the SUSPEND bit is
    already set the init is skipped entirely, preventing the shared
    PHY0 from being reset while a live link is active.  On cold boot,
    PHY0 polls for either host-mode clocks (HSTCKSTB + CK12MSTB) or
    device-mode clock (DEVCKSTB) since the hardware selects the role
    automatically; PHY1 polls for host-mode clocks only.

  Clock management removed
    .power_on/.power_off and all struct clk handling are removed.
    Each USB controller (DWC2, EHCI, OHCI) already gates its own
    clock directly through its DTS clocks binding.  Having the PHY
    driver redundantly enable the same gates added unnecessary
    coupling without benefit.

  OTG role switch for PHY0
    A read-only USB role switch is registered, reporting the current
    OTG role by reading the USB ID pin state from PWRONOTP[16].
    .set returns -EOPNOTSUPP since the hardware mux is fully
    automatic.  allow_userspace_control is kept true to preserve the
    sysfs attribute for observation; writes are rejected by .set.

  syscon regmap via parent
    The driver obtains the regmap by calling
    syscon_node_to_regmap(pdev->dev.parent->of_node), removing the
    need for the nuvoton,sys phandle.

Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
 drivers/phy/nuvoton/phy-ma35d1-usb2.c | 263 ++++++++++++++++++--------
 1 file changed, 189 insertions(+), 74 deletions(-)

diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
index 9a459b700ed4..336680161104 100644
--- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c
+++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
@@ -1,11 +1,15 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (C) 2024 Nuvoton Technology Corp.
+ * Nuvoton MA35D1 USB 2.0 PHY driver
+ *
+ * Supports PHY0 (USB0 OTG port, shared between DWC2 gadget and EHCI0/OHCI0)
+ * and PHY1 (USB1 host-only port, used by EHCI1/OHCI1).  The hardware mux on
+ * PHY0 switches automatically via the USB ID pin.
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
  */
 #include <linux/bitfield.h>
-#include <linux/clk.h>
 #include <linux/delay.h>
-#include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/mfd/syscon.h>
 #include <linux/module.h>
@@ -13,131 +17,242 @@
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
+#include <linux/usb/role.h>
 
-/* USB PHY Miscellaneous Control Register */
-#define MA35_SYS_REG_USBPMISCR	0x60
-#define PHY0POR			BIT(0)  /* PHY Power-On Reset Control Bit */
-#define PHY0SUSPEND			BIT(1)  /* PHY Suspend; 0: suspend, 1: operaion */
-#define PHY0COMN			BIT(2)  /* PHY Common Block Power-Down Control */
-#define PHY0DEVCKSTB			BIT(10) /* PHY 60 MHz UTMI clock stable bit */
+#define MA35_SYS_PWRONOTP		0x04
+#define PWRONOTP_USBP0ID		BIT(16)	/* USB0 ID pin state */
+
+#define MA35_SYS_USBPMISCR		0x60
+#define USBPMISCR_PHY_POR(n)		BIT(0  + (n) * 16)
+#define USBPMISCR_PHY_SUSPEND(n)	BIT(1  + (n) * 16)
+#define USBPMISCR_PHY_COMN(n)		BIT(2  + (n) * 16)
+#define USBPMISCR_PHY_HSTCKSTB(n)	BIT(8  + (n) * 16)
+#define USBPMISCR_PHY_CK12MSTB(n)	BIT(9  + (n) * 16)
+#define USBPMISCR_PHY_DEVCKSTB(n)	BIT(10 + (n) * 16)
+/* Mask for control bits (POR, SUSPEND, COMN) of one PHY */
+#define USBPMISCR_PHY_CTL_MASK(n)	(0x7u << ((n) * 16))
+/* Host-mode ready: SUSPEND set */
+#define USBPMISCR_PHY_HOST_READY(n)	(USBPMISCR_PHY_SUSPEND(n)  | \
+					 USBPMISCR_PHY_HSTCKSTB(n) | \
+					 USBPMISCR_PHY_CK12MSTB(n))
+/* Device-mode ready: SUSPEND set */
+#define USBPMISCR_PHY_DEV_READY(n)	(USBPMISCR_PHY_SUSPEND(n)  | \
+					 USBPMISCR_PHY_DEVCKSTB(n))
+/* RCALCODE: 4-bit resistor trim at bits [15:12] (PHY0) or [31:28] (PHY1) */
+#define USBPMISCR_RCAL_SHIFT(n)		(12 + (n) * 16)
+#define USBPMISCR_RCAL_MASK(n)		GENMASK(USBPMISCR_RCAL_SHIFT(n) + 3, \
+						USBPMISCR_RCAL_SHIFT(n))
+
+#define MA35_SYS_MISCFCR0		0x70
+/* Bit 12: USB host over-current detect polarity (shared, both ports) */
+#define MISCFCR0_UHOVRCURH		BIT(12)
+
+#define MA35_PHY_NUM			2
+
+struct ma35_phy_port {
+	struct phy *phy;
+	unsigned int idx;
+};
 
 struct ma35_usb_phy {
-	struct clk *clk;
 	struct device *dev;
 	struct regmap *sysreg;
+	struct ma35_phy_port port[MA35_PHY_NUM];
+	struct usb_role_switch *role_sw;
 };
 
-static int ma35_usb_phy_power_on(struct phy *phy)
+static int ma35_usb_phy_init(struct phy *phy)
 {
-	struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
+	struct ma35_phy_port *port = phy_get_drvdata(phy);
+	struct ma35_usb_phy *p = container_of(port - port->idx,
+					      struct ma35_usb_phy, port[0]);
+	unsigned int n = port->idx;
 	unsigned int val;
 	int ret;
 
-	ret = clk_prepare_enable(p_phy->clk);
-	if (ret < 0) {
-		dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
-		return ret;
-	}
+	regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val);
 
-	regmap_read(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, &val);
-	if (val & PHY0SUSPEND) {
-		/*
-		 * USB PHY0 is in operation mode already
-		 * make sure USB PHY 60 MHz UTMI Interface Clock ready
-		 */
-		ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val,
-						val & PHY0DEVCKSTB, 10, 1000);
-		if (ret == 0)
-			return 0;
-	}
+	if (val & USBPMISCR_PHY_SUSPEND(n))
+		return 0;
 
-	/*
-	 * reset USB PHY0.
-	 * wait until USB PHY0 60 MHz UTMI Interface Clock ready
-	 */
-	regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND));
+	regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+			   USBPMISCR_PHY_CTL_MASK(n),
+			   USBPMISCR_PHY_POR(n) | USBPMISCR_PHY_SUSPEND(n));
 	udelay(20);
 
-	/* make USB PHY0 enter operation mode */
-	regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, PHY0SUSPEND);
+	regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+			   USBPMISCR_PHY_CTL_MASK(n),
+			   USBPMISCR_PHY_SUSPEND(n));
 
-	/* make sure USB PHY 60 MHz UTMI Interface Clock ready */
-	ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val,
-					val & PHY0DEVCKSTB, 10, 1000);
-	if (ret == -ETIMEDOUT) {
-		dev_err(p_phy->dev, "Check PHY clock, Timeout: %d\n", ret);
-		clk_disable_unprepare(p_phy->clk);
+	if (n == 0) {
+		ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR,
+					       val,
+					       ((val & USBPMISCR_PHY_HOST_READY(0)) ==
+						USBPMISCR_PHY_HOST_READY(0)) ||
+					       ((val & USBPMISCR_PHY_DEV_READY(0)) ==
+						USBPMISCR_PHY_DEV_READY(0)),
+					       10, 1000);
+	} else {
+		ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR,
+					       val,
+					       (val & USBPMISCR_PHY_HOST_READY(n)) ==
+					       USBPMISCR_PHY_HOST_READY(n),
+					       10, 1000);
+	}
+
+	if (ret) {
+		dev_err(p->dev, "USB PHY%u clock not stable (USBPMISCR=0x%08x)\n",
+			n, val);
 		return ret;
 	}
 
 	return 0;
 }
 
-static int ma35_usb_phy_power_off(struct phy *phy)
+static const struct phy_ops ma35_usb_phy_ops = {
+	.init		= ma35_usb_phy_init,
+	.owner		= THIS_MODULE,
+};
+
+static int ma35_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
+{
+	return -EOPNOTSUPP;
+}
+
+static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
+{
+	struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
+	u32 val;
+
+	regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);
+
+	return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE;
+}
+
+static int ma35_role_switch_init(struct platform_device *pdev,
+				 struct ma35_usb_phy *p)
 {
-	struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
+	struct usb_role_switch_desc sw_desc = {0};
+
+	sw_desc.set = ma35_role_sw_set;
+	sw_desc.get = ma35_role_sw_get;
+	sw_desc.allow_userspace_control = true;
+	sw_desc.driver_data = p;
+	sw_desc.fwnode = dev_fwnode(&pdev->dev);
+
+	p->role_sw = usb_role_switch_register(&pdev->dev, &sw_desc);
+	if (IS_ERR(p->role_sw))
+		return dev_err_probe(&pdev->dev, PTR_ERR(p->role_sw),
+				     "failed to register role switch\n");
 
-	clk_disable_unprepare(p_phy->clk);
 	return 0;
 }
 
-static const struct phy_ops ma35_usb_phy_ops = {
-	.power_on = ma35_usb_phy_power_on,
-	.power_off = ma35_usb_phy_power_off,
-	.owner = THIS_MODULE,
-};
+static void ma35_role_switch_exit(struct ma35_usb_phy *p)
+{
+	if (p->role_sw) {
+		usb_role_switch_unregister(p->role_sw);
+		p->role_sw = NULL;
+	}
+}
+
+static struct phy *ma35_usb_phy_xlate(struct device *dev,
+				      const struct of_phandle_args *args)
+{
+	struct ma35_usb_phy *p = dev_get_drvdata(dev);
+
+	if (args->args[0] >= MA35_PHY_NUM)
+		return ERR_PTR(-EINVAL);
+
+	return p->port[args->args[0]].phy;
+}
 
 static int ma35_usb_phy_probe(struct platform_device *pdev)
 {
 	struct phy_provider *provider;
-	struct ma35_usb_phy *p_phy;
-	struct phy *phy;
+	struct ma35_usb_phy *p;
+	int n, ret;
+	u32 code;
 
-	p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL);
-	if (!p_phy)
+	p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+	if (!p)
 		return -ENOMEM;
 
-	p_phy->dev = &pdev->dev;
-	platform_set_drvdata(pdev, p_phy);
+	p->dev = &pdev->dev;
+	platform_set_drvdata(pdev, p);
+
+	p->sysreg = syscon_node_to_regmap(pdev->dev.parent->of_node);
+	if (IS_ERR(p->sysreg))
+		return dev_err_probe(&pdev->dev, PTR_ERR(p->sysreg),
+				     "failed to get parent SYS regmap\n");
 
-	p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys");
-	if (IS_ERR(p_phy->sysreg))
-		return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg),
-				     "Failed to get SYS registers\n");
+	for (n = 0; n < MA35_PHY_NUM; n++) {
+		if (of_property_read_u32_index(pdev->dev.of_node,
+					       "nuvoton,rcalcode", n, &code))
+			continue;
 
-	p_phy->clk = of_clk_get(pdev->dev.of_node, 0);
-	if (IS_ERR(p_phy->clk))
-		return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->clk),
-				     "failed to find usb_phy clock\n");
+		if (code > 15)
+			return dev_err_probe(&pdev->dev, -EINVAL,
+					     "rcalcode[%d] %u out of range (0-15)\n",
+					     n, code);
 
-	phy = devm_phy_create(&pdev->dev, NULL, &ma35_usb_phy_ops);
-	if (IS_ERR(phy))
-		return dev_err_probe(&pdev->dev, PTR_ERR(phy), "Failed to create PHY\n");
+		regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+				   USBPMISCR_RCAL_MASK(n),
+				   code << USBPMISCR_RCAL_SHIFT(n));
+	}
+
+	if (of_property_read_bool(pdev->dev.of_node, "nuvoton,oc-active-high"))
+		regmap_update_bits(p->sysreg, MA35_SYS_MISCFCR0,
+				   MISCFCR0_UHOVRCURH, MISCFCR0_UHOVRCURH);
+
+	for (n = 0; n < MA35_PHY_NUM; n++) {
+		p->port[n].idx = n;
+
+		p->port[n].phy = devm_phy_create(&pdev->dev, pdev->dev.of_node,
+						 &ma35_usb_phy_ops);
+		if (IS_ERR(p->port[n].phy))
+			return dev_err_probe(&pdev->dev, PTR_ERR(p->port[n].phy),
+					     "failed to create PHY%d\n", n);
+
+		phy_set_drvdata(p->port[n].phy, &p->port[n]);
+	}
 
-	phy_set_drvdata(phy, p_phy);
+	ret = ma35_role_switch_init(pdev, p);
+	if (ret)
+		return ret;
 
-	provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+	provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate);
 	if (IS_ERR(provider))
 		return dev_err_probe(&pdev->dev, PTR_ERR(provider),
-				     "Failed to register PHY provider\n");
+				     "failed to register PHY provider\n");
+
 	return 0;
 }
 
+static void ma35_usb_phy_remove(struct platform_device *pdev)
+{
+	struct ma35_usb_phy *p = platform_get_drvdata(pdev);
+
+	ma35_role_switch_exit(p);
+}
+
 static const struct of_device_id ma35_usb_phy_of_match[] = {
-	{ .compatible = "nuvoton,ma35d1-usb2-phy", },
-	{ },
+	{ .compatible = "nuvoton,ma35d1-usb2-phy" },
+	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, ma35_usb_phy_of_match);
 
 static struct platform_driver ma35_usb_phy_driver = {
 	.probe		= ma35_usb_phy_probe,
-	.driver	= {
-		.name	= "ma35d1-usb2-phy",
-		.of_match_table = ma35_usb_phy_of_match,
+	.remove		= ma35_usb_phy_remove,
+	.driver		= {
+		.name		= "ma35d1-usb2-phy",
+		.of_match_table	= ma35_usb_phy_of_match,
 	},
 };
 module_platform_driver(ma35_usb_phy_driver);
 
 MODULE_DESCRIPTION("Nuvoton ma35d1 USB2.0 PHY driver");
 MODULE_AUTHOR("Hui-Ping Chen <hpchen0nvt@gmail.com>");
+MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>");
 MODULE_LICENSE("GPL");
-- 
2.43.0


^ permalink raw reply related

* RE: [PATCH net-next v5 14/15] dt-bindings: net: add onsemi's S2500
From: Selvamani Rajagopal @ 2026-06-15  5:50 UTC (permalink / raw)
  To: Rob Herring
  Cc: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, Parthiban Veerasooran, Richard Cochran,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, Jonathan Corbet,
	Shuah Khan, netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	devicetree@vger.kernel.org, linux-doc@vger.kernel.org, Jerry Ray
In-Reply-To: <20260615041056.GA1426553-robh@kernel.org>

> Subject: Re: [PATCH net-next v5 14/15] dt-bindings: net: add onsemi's S2500
> 
> 
> >
> > ---
> 
> Everything after this is dropped from the commit message when applying.
> Your Sob needs to be above it.

Yes. I realized after seeing the error in patchwork. Fixing it.

> 
> And you are missing tags from prior versions. It is your responsibility
> to add them.


Until v3, it wasn't threaded due to the way I submitted. But I could locate the link and add them.

> 
> > changes in v5
> > - no changes
> > changes in v4:
> > - added spi-max-frequency as suggested by AI review
> > - changed interrupt to IRQ_TYPE_EDGE_FALLING as it is
> > being taken care in net (stable) branch
> > changes in v3
> > - Removed URL link that failed verification
> > changes in v2
> > - removed spi-max-frequency entry
> > - changed the compatible string to s2500
> > changes in v1
> > - Added the first version of YAML file for onsemi MAC-PHY
> >
> > Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> > ---
> > .../devicetree/bindings/net/onnn,s2500.yaml | 67 ++++++++++++++++++++++
> > 1 file changed, 67 insertions(+)


^ permalink raw reply

* Re: [PATCH v3 3/3] phy: qcom-qmp-ufs: Add UFS PHY support on Hawi
From: Palash Kambar @ 2026-06-15  5:51 UTC (permalink / raw)
  To: Vinod Koul
  Cc: neil.armstrong, robh, krzk+dt, conor+dt, mani, alim.akhtar,
	bvanassche, andersson, dmitry.baryshkov, abel.vesa, luca.weiss,
	linux-arm-msm, linux-phy, devicetree, linux-kernel, linux-scsi,
	nitin.rawat
In-Reply-To: <airUb6wT-I-7cOXK@vaman>



On 6/11/2026 8:59 PM, Vinod Koul wrote:
> On 26-05-26, 14:39, palash.kambar@oss.qualcomm.com wrote:
>> From: Palash Kambar <palash.kambar@oss.qualcomm.com>
>>
>> Add the init sequence tables and config for the UFS QMP phy found in
>> the Hawi SoC.
> 
> This fails to build for me on phy/next
> 
> In file included from drivers/phy/qualcomm/phy-qcom-qmp-ufs.c:24:
> drivers/phy/qualcomm/phy-qcom-qmp-ufs.c:1878:26: error: ‘QSERDES_V8_COM_PLL_IVCO_MODE1’ undeclared here (not in a function); did you mean ‘QSERDES_V6_COM_PLL_IVCO_MODE1’?
>  1878 |         QMP_PHY_INIT_CFG(QSERDES_V8_COM_PLL_IVCO_MODE1, 0x1f),
>       |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> drivers/phy/qualcomm/phy-qcom-qmp-common.h:22:27: note: in definition of macro ‘QMP_PHY_INIT_CFG’
>    22 |                 .offset = o,            \
>       |                           ^
> drivers/phy/qualcomm/phy-qcom-qmp-ufs.c:1879:26: error: ‘QSERDES_V8_COM_CMN_IETRIM’ undeclared here (not in a function); did you mean ‘QSERDES_V6_COM_CMN_IETRIM’?
>  1879 |         QMP_PHY_INIT_CFG(QSERDES_V8_COM_CMN_IETRIM, 0x07),
>       |                          ^~~~~~~~~~~~~~~~~~~~~~~~~
> drivers/phy/qualcomm/phy-qcom-qmp-common.h:22:27: note: in definition of macro ‘QMP_PHY_INIT_CFG’
>    22 |                 .offset = o,            \
> 
> And so on. Looks like QSERDES_V8_COM_PLL_IVCO_MODE1 etc are not define.
> Please rebase test and send again
> 

Hi Vinod,

Sure, will rebase and check. Thanks.

^ permalink raw reply

* RE: [PATCH v10 2/2] leds: ltc3220: Add Support for LTC3220 18 channel LED Driver
From: Escala, Edelweise @ 2026-06-15  5:52 UTC (permalink / raw)
  To: sashiko-reviews@lists.linux.dev, Lee Jones
  Cc: robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org,
	Pavel Machek, Krzysztof Kozlowski, linux-leds@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260615053804.0B02E1F000E9@smtp.kernel.org>

After the new version another set of sashiko review. Will also handle fixing these

> -----Original Message-----
> From: sashiko-bot@kernel.org <sashiko-bot@kernel.org>
> Sent: Monday, June 15, 2026 1:38 PM
> To: Escala, Edelweise <Edelweise.Escala@analog.com>
> Cc: robh@kernel.org; conor+dt@kernel.org; devicetree@vger.kernel.org
> Subject: Re: [PATCH v10 2/2] leds: ltc3220: Add Support for LTC3220 18 channel
> LED Driver
> 
> [External]
> 
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to
> consider:
> - [High] Duplicate LED `reg` values bypass the validation check, leading to a
> doubly-linked list corruption in the LED core.
> - [Medium] Missing LED class suspend/resume calls in system PM callbacks.
> - [Medium] The `pattern_set` callback silently ignores the `repeat` parameter,
> breaking continuous patterns.
> - [Low] Setting blink mode on a deactivated LED causes an unnecessary I2C read
> and hardware glitch.
> --
> 
> commit ff936b952a6a427e0232229537a10438e4f34a11
> Author: Edelweise Escala <edelweise.escala@analog.com>
> Subject: leds: ltc3220: Add Support for LTC3220 18 channel LED Driver
> 
> Add driver for the LTC3220 18-channel LED driver with I2C interface, individual
> brightness control, and hardware-assisted blink/gradation features.
> 
> > diff --git a/drivers/leds/leds-ltc3220.c b/drivers/leds/leds-ltc3220.c
> > new file mode 100644 index 0000000000000..f95174ae451ba
> > --- /dev/null
> > +++ b/drivers/leds/leds-ltc3220.c
> [ ... ]
> > +static int ltc3220_pattern_set(struct led_classdev *led_cdev,
> > +			       struct led_pattern *pattern,
> > +			       u32 len, int repeat)
> > +{
> 
> [Severity: Medium]
> Does this callback silently ignore the repeat parameter?
> 
> If the user requests infinite repetition (for example, repeat is -1), the driver
> returns 0 without programming continuous breathing. Should this return -
> EOPNOTSUPP when repetition is requested, so the LED core can fall back to
> software-driven repetition instead of halting after a single ramp?
> 
> > +	struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct
> ltc3220_uled_cfg,
> > +							 led_cdev);
> [ ... ]
> > +static int ltc3220_blink_set(struct led_classdev *led_cdev,
> > +			     unsigned long *delay_on,
> > +			     unsigned long *delay_off)
> > +{
> [ ... ]
> > +	/* Ensure LED has non-zero brightness before enabling blink mode */
> > +	if (uled_cfg->reg_value == 0) {
> > +		ret = regmap_write(ltc3220->regmap,
> LTC3220_ULED_REG(uled_cfg->led_index),
> > +				   led_cdev->max_brightness);
> > +		if (ret)
> > +			return ret;
> > +
> > +		uled_cfg->reg_value = led_cdev->max_brightness;
> > +	}
> > +
> > +	return regmap_update_bits(ltc3220->regmap,
> LTC3220_ULED_REG(uled_cfg->led_index),
> > +				  LTC3220_LED_MODE_MASK,
> > +				  FIELD_PREP(LTC3220_LED_MODE_MASK,
> LTC3220_BLINK_MODE)); }
> 
> [Severity: Low]
> When ltc3220_blink_set() is called on an LED with zero brightness, does this
> sequence cause a visual glitch and an unnecessary I2C read?
> 
> The first regmap_write() turns the LED on solid in normal mode, and then
> regmap_update_bits() incurs an I2C read to switch it to blink mode. Could the
> driver construct the final 8-bit value from the cached brightness and write it
> directly using a single regmap_write()?
> 
> [ ... ]
> > +static int ltc3220_suspend(struct device *dev) {
> > +	struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev));
> > +
> > +	return regmap_update_bits(ltc3220->regmap,
> LTC3220_COMMAND_REG,
> > +				  LTC3220_SHUTDOWN_MASK,
> LTC3220_SHUTDOWN_MASK); }
> 
> [Severity: Medium]
> Does this suspend callback miss notifying the LED core about the suspension?
> 
> By not iterating over the registered LEDs and calling led_classdev_suspend(), the
> LED core remains unaware the device is suspended and will not pause triggers.
> Could an active software trigger attempt an I2C transaction while the system is
> suspending, potentially causing bus timeouts?
> 
> [ ... ]
> > +static int ltc3220_probe(struct i2c_client *client) {
> [ ... ]
> > +	/* First pass: validate configuration and set up LED structures */
> > +	device_for_each_child_node_scoped(&client->dev, child) {
> > +		struct ltc3220_uled_cfg *led;
> > +		u32 source;
> [ ... ]
> > +		/* LED node reg/index/address goes from 1 to 18 */
> > +		led_index = source - 1;
> > +		led = &ltc3220->uled_cfg[led_index];
> > +
> > +		if (led->led_cdev.dev)
> > +			return dev_err_probe(&client->dev, -EINVAL,
> > +					     "Duplicate LED reg %u found",
> source);
> 
> [Severity: High]
> Is led_cdev.dev always null during the first pass of ltc3220_probe()?
> 
> Since ltc3220 is allocated with devm_kzalloc(), led_cdev.dev will be zero. It is only
> assigned during devm_led_classdev_register_ext() in the second pass, so this
> duplicate checking logic appears to be bypassed.
> 
> If a device tree contains duplicate reg values, will it silently overwrite the same
> uled_cfg structure during this first pass?
> 
> [ ... ]
> > +	/* Second pass: register LEDs after validation */
> > +	device_for_each_child_node_scoped(&client->dev, child) {
> > +		struct led_init_data init_data = {};
> > +		struct ltc3220_uled_cfg *led;
> > +		u32 source;
> > +
> > +		ret = fwnode_property_read_u32(child, "reg", &source);
> > +		if (ret)
> > +			return ret;
> > +
> > +		init_data.fwnode = child;
> > +		init_data.devicename = "ltc3220";
> > +
> > +		led_index = source - 1;
> > +		led = &ltc3220->uled_cfg[led_index];
> > +
> > +		ret = devm_led_classdev_register_ext(&client->dev, &led-
> >led_cdev,
> > +&init_data);
> 
> [Severity: High]
> Following from the duplicate check bypass above, if a duplicate reg value is
> present, will devm_led_classdev_register_ext() call list_add_tail() twice on the
> exact same node address? This would corrupt the LED core's doubly-linked list
> and reliably cause a kernel panic upon iteration or removal.
> 
> --
> Sashiko AI review ·
> https://urldefense.com/v3/__https://sashiko.dev/*/patchset/20260615-ltc3220-
> driver-v10-0-
> 916562c88598@analog.com?part=2__;Iw!!A3Ni8CS0y2Y!4ZC1ljfp19dgTz1tKIoor
> RXY65a_q24Uz9NwdL22nTU8VrPKjf97nkBWY8TZBbk8NJ7uQlaukxYptqOUCWxA
> VtNI08A$

^ permalink raw reply

* Re: [PATCH v4 2/2] pinctrl: ultrarisc: Add UltraRISC DP1000 pinctrl driver
From: Jia Wang @ 2026-06-15  5:53 UTC (permalink / raw)
  To: Nathan Chancellor
  Cc: Jia Wang, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bartosz Golaszewski, linux-gpio, devicetree,
	linux-kernel
In-Reply-To: <20260613164847.GA3152104@ax162>

On 2026-06-13 12:48 -0400, Nathan Chancellor wrote:
> On Wed, Jun 10, 2026 at 01:29:56PM +0800, Jia Wang wrote:
> > diff --git a/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c
> > new file mode 100644
> > index 000000000000..8fb5b0ea5b93
> > --- /dev/null
> > +++ b/drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.c
> ...
> > +static bool ur_function_is_gpio(struct pinctrl_dev *pctldev,
> > +				unsigned int selector)
> > +{
> > +	const struct function_desc *function;
> > +
> > +	function = pinmux_generic_get_function(pctldev, selector);
> > +	if (!function)
> > +		return false;
> > +
> > +	for (u32 i = 0; i < function->func->ngroups; i++) {
> > +		const char *func_name;
> > +		int group_selector;
> > +
> > +		group_selector = pinctrl_get_group_selector(pctldev,
> > +							    function->func->groups[i]);
> > +		if (group_selector < 0)
> > +			return false;
> > +
> > +		func_name = ur_get_group_function(pctldev, group_selector, 0);
> > +		if (!func_name || strcmp(func_name, "gpio"))
> > +			return false;
> > +	}
> > +
> > +	return true;
> > +}
> 
> The usage of pinctrl_get_group_selector() in this function breaks the
> build when this driver is a module because pinctrl_get_group_selector()
> is not exported for modules:
> 
>   ERROR: modpost: "pinctrl_get_group_selector" [drivers/pinctrl/ultrarisc/pinctrl-ultrarisc.ko] undefined!
> 
> Also reported by the robots:
> 
>   https://lore.kernel.org/202606130210.ytVPxHlm-lkp@intel.com/
>

Hi Nathan,

Thanks for spotting this and for the report.

Linus has since proposed a fix by exporting pinctrl_get_group_selector():

https://lore.kernel.org/linux-gpio/20260613-export-get-group-selector-v1-1-fc4451a9ff0e@kernel.org/

Thanks again for catching this.
 
> -- 
> Cheers,
> Nathan
> 

Best regards,
Wang Jia



^ permalink raw reply

* [PATCH 2/2] usb: typec: ucsi: Add ITE IT885x Type-C PD controller driver
From: Amber Kao @ 2026-06-15 13:47 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Jeson Yang, Yaode Fang, Bling Chiang, Eric Su,
	Doreen Lin, Heikki Krogerus
  Cc: linux-usb, devicetree, linux-kernel, Amber Kao
In-Reply-To: <20260615-ucsi-itepd-feature-v1-0-a826cfd0df6a@ite.com.tw>

Add core, UCSI, and Alternate Mode support for the ITE IT885x
Type-C Power Delivery controller over I2C. The driver uses the
auxiliary bus to spawn UCSI and Alternate Mode child devices from
the main I2C core driver.

Cc: Yaode Fang <Yaode.Fang@ite.com.tw>
Cc: Jeson Yang <jeson.yang@ite.com.tw>
Cc: Bling Chiang <Bling.Chiang@ite.com.tw>
Cc: Eric Su <Eric.Su@ite.com.tw>
Cc: Doreen Lin <doreen.lin@ite.com.tw>

Signed-off-by: Amber Kao <amber.kao@ite.com.tw>
---
 MAINTAINERS                            |   4 +
 drivers/usb/typec/ucsi/Kconfig         |  15 +
 drivers/usb/typec/ucsi/Makefile        |   1 +
 drivers/usb/typec/ucsi/itepd.c         | 481 ++++++++++++++++++++++++++++
 drivers/usb/typec/ucsi/itepd.h         |  64 ++++
 drivers/usb/typec/ucsi/itepd_altmode.c | 438 ++++++++++++++++++++++++++
 drivers/usb/typec/ucsi/ucsi_itepd.c    | 558 +++++++++++++++++++++++++++++++++
 7 files changed, 1561 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 94afe3729059..c936928a7028 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13730,6 +13730,10 @@ R:	Doreen Lin <doreen.lin@ite.com.tw>
 L:	linux-usb@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml
+F:	drivers/usb/typec/ucsi/itepd.c
+F:	drivers/usb/typec/ucsi/itepd.h
+F:	drivers/usb/typec/ucsi/itepd_altmode.c
+F:	drivers/usb/typec/ucsi/ucsi_itepd.c
 
 IVTV VIDEO4LINUX DRIVER
 M:	Andy Walls <awalls@md.metrocast.net>
diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
index 87dd992a4b9e..470070f7e217 100644
--- a/drivers/usb/typec/ucsi/Kconfig
+++ b/drivers/usb/typec/ucsi/Kconfig
@@ -104,4 +104,19 @@ config UCSI_HUAWEI_GAOKUN
 	  To compile the driver as a module, choose M here: the module will be
 	  called ucsi_huawei_gaokun.
 
+config TYPEC_UCSI_ITEPD
+	tristate "ITE IT885x Type-C PD and UCSI Driver"
+	depends on I2C
+	depends on DRM
+	select AUXILIARY_BUS
+	help
+	  This driver enables core, UCSI, and Alternate Mode support for
+	  the ITE IT885x Type-C Power Delivery controller over I2C.
+
+	  The driver uses the auxiliary bus to spawn the UCSI and Altmode
+	  child devices from the main I2C core driver.
+
+	  To compile the driver as a module, choose M here: the modules
+	  will be called itepd, ucsi_itepd and itepd_altmode.
+
 endif
diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
index c7e38bf01350..437a23f0031c 100644
--- a/drivers/usb/typec/ucsi/Makefile
+++ b/drivers/usb/typec/ucsi/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_UCSI_PMIC_GLINK)		+= ucsi_glink.o
 obj-$(CONFIG_CROS_EC_UCSI)		+= cros_ec_ucsi.o
 obj-$(CONFIG_UCSI_LENOVO_YOGA_C630)	+= ucsi_yoga_c630.o
 obj-$(CONFIG_UCSI_HUAWEI_GAOKUN)	+= ucsi_huawei_gaokun.o
+obj-$(CONFIG_TYPEC_UCSI_ITEPD)		+= itepd.o ucsi_itepd.o itepd_altmode.o
diff --git a/drivers/usb/typec/ucsi/itepd.c b/drivers/usb/typec/ucsi/itepd.c
new file mode 100644
index 000000000000..39ac1c136157
--- /dev/null
+++ b/drivers/usb/typec/ucsi/itepd.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026, ITE. All Rights Reserved
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+
+#include "itepd.h"
+
+#define ITEPD_UCSI_VERSION_REG			(0x80)
+#define ITEPD_UCSI_CCI_REG				(0x84)
+#define ITEPD_UCSI_MSG_IN_REG			(0x88)
+#define ITEPD_UCSI_CONTROL_REG			(0x98)
+#define ITEPD_UCSI_MSG_OUT_REG			(0xA0)
+
+#define ITEPD_VENDOR_WC_INT				(0xBC)
+#define ITEPD_VENDOR_INT				(0xBD)
+	#define ITEPD_ALERT_VDM_EVENT			BIT(0)
+	#define ITEPD_ALERT_UCSI_EVENT			BIT(1)
+
+struct itepd {
+	struct device *dev;
+	struct i2c_client *client;
+	int irq;
+	struct mutex i2c_lock; /* Protects I2C read/write operations */
+	struct mutex cb_lock;  /* Protects concurrent access to callback state */
+	unsigned long client_mask;
+	struct auxiliary_device *ucsi_aux;
+	struct auxiliary_device *altmode_aux;
+	struct itepd_ucsi_cb *ucsi_cb;
+	struct itepd_altmode_cb *altmode_cb;
+
+	struct itepd_altmode_data altmode_data[ITEPD_MAX_PORTS];
+};
+
+/*
+ * ITE Read/Write Function
+ */
+
+static int itepd_read_reg(struct itepd *itepd, u8 reg, void *data, u32 len)
+{
+	struct i2c_client *client = itepd->client;
+	struct i2c_msg msg[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0x0,
+			.len	= 1,
+			.buf	= &reg,
+		},
+		{
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= len,
+			.buf	= (u8 *)data,
+		}
+	};
+	int ret;
+
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	if (ret < 0) {
+		dev_err(itepd->dev, "i2c_transfer read failed %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int itepd_write_reg(struct itepd *itepd, u8 reg, const void *data, u32 len)
+{
+	struct i2c_client *client = itepd->client;
+	unsigned char *buf;
+	struct i2c_msg msg[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0x0,
+		}
+	};
+	int ret;
+
+	buf = kzalloc(len + sizeof(reg), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = reg;
+	memcpy(buf + sizeof(reg), (u8 *)data, len);
+
+	msg[0].len = len + sizeof(reg);
+	msg[0].buf = buf;
+
+	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+	if (ret < 0) {
+		dev_err(itepd->dev, "i2c_transfer write failed %d\n", ret);
+		kfree(buf);
+		return ret;
+	}
+
+	kfree(buf);
+	return 0;
+}
+
+/**
+ * itepd_cmd_receive() - Receive UCSI command from ITEPD controller
+ * @dev: Pointer to the device structure
+ * @cmd: The command to be executed
+ * @val: Buffer to store the received data
+ * @val_len: Length of the buffer
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+
+int itepd_cmd_receive(struct device *dev, unsigned int cmd, void *val, size_t val_len)
+{
+	struct itepd *itepd = i2c_get_clientdata(to_i2c_client(dev->parent));
+	int ret;
+
+	if (!itepd)
+		return -ENXIO;
+
+	switch (cmd) {
+	case ITEPD_RECEIVE_UCSI_VERSION:
+		mutex_lock(&itepd->i2c_lock);
+		ret = itepd_read_reg(itepd, ITEPD_UCSI_VERSION_REG, val,
+				     min_t(size_t, val_len, 0x28));
+		mutex_unlock(&itepd->i2c_lock);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(itepd_cmd_receive);
+
+int itepd_cmd_send(struct device *dev, unsigned int cmd, const void *val, size_t val_len)
+{
+	struct itepd *itepd = i2c_get_clientdata(to_i2c_client(dev->parent));
+	int ret;
+
+	if (!itepd)
+		return -ENXIO;
+
+	switch (cmd) {
+	case ITEPD_SEND_UCSI_CONTROL:
+		mutex_lock(&itepd->i2c_lock);
+		ret = itepd_write_reg(itepd, ITEPD_UCSI_CONTROL_REG, val,
+				      min_t(size_t, val_len, 8));
+		mutex_unlock(&itepd->i2c_lock);
+		break;
+	case ITEPD_SEND_UCSI_MESSAGE_OUT:
+		mutex_lock(&itepd->i2c_lock);
+		ret = itepd_write_reg(itepd, ITEPD_UCSI_MSG_OUT_REG, val, val_len);
+		mutex_unlock(&itepd->i2c_lock);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(itepd_cmd_send);
+
+int itepd_register_cb(struct device *dev, u8 id, void *cb)
+{
+	struct itepd *itepd = i2c_get_clientdata(to_i2c_client(dev->parent));
+
+	if (!itepd)
+		return -EPROBE_DEFER;
+
+	switch (id) {
+	case ITEPD_CLIENT_UCSI:
+		if (itepd->ucsi_aux && dev == &itepd->ucsi_aux->dev) {
+			mutex_lock(&itepd->cb_lock);
+			itepd->ucsi_cb = (struct itepd_ucsi_cb *)cb;
+			mutex_unlock(&itepd->cb_lock);
+		} else {
+			return -ENODEV;
+		}
+		break;
+	case ITEPD_CLIENT_ALTMODE:
+		if (itepd->altmode_aux && dev == &itepd->altmode_aux->dev) {
+			mutex_lock(&itepd->cb_lock);
+			itepd->altmode_cb = (struct itepd_altmode_cb *)cb;
+			mutex_unlock(&itepd->cb_lock);
+		} else {
+			return -ENODEV;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(itepd_register_cb);
+
+int itepd_mode(struct device *dev, u8 port, u8 mux, u32 config, u32 status)
+{
+	struct itepd *itepd = i2c_get_clientdata(to_i2c_client(dev->parent));
+
+	if (!itepd)
+		return -ENXIO;
+
+	if (itepd->ucsi_aux && dev == &itepd->ucsi_aux->dev) {
+		itepd->altmode_data[port].port = port;
+		itepd->altmode_data[port].mux = mux;
+		itepd->altmode_data[port].dp_config = config;
+		itepd->altmode_data[port].dp_status = status;
+		if (itepd->altmode_cb)
+			itepd->altmode_cb->notify(itepd->altmode_cb->priv,
+						  &itepd->altmode_data[port]);
+	} else {
+		return -ENODEV;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(itepd_mode);
+
+int itepd_hpd(struct device *dev, u8 port, u32 status)
+{
+	struct itepd *itepd = i2c_get_clientdata(to_i2c_client(dev->parent));
+
+	if (!itepd)
+		return -ENXIO;
+
+	if (itepd->ucsi_aux && dev == &itepd->ucsi_aux->dev) {
+		itepd->altmode_data[port].dp_status = status;
+		if (itepd->altmode_cb)
+			itepd->altmode_cb->notify(itepd->altmode_cb->priv,
+						  &itepd->altmode_data[port]);
+	} else {
+		return -ENODEV;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(itepd_hpd);
+
+/*
+ * ITE Interrupt Function
+ */
+
+static irqreturn_t itepd_irq_process(struct itepd *itepd)
+{
+	u8 event;
+	u8 clear = 0;
+	u8 len;
+	u32 cci;
+	u8 msg_in[0x28];
+	bool notify_ucsi = false;
+	int ret;
+
+	mutex_lock(&itepd->i2c_lock);
+
+	ret = itepd_read_reg(itepd, ITEPD_VENDOR_INT, &event, 1);
+	if (ret) {
+		mutex_unlock(&itepd->i2c_lock);
+		return IRQ_HANDLED;
+	}
+
+	mutex_lock(&itepd->cb_lock);
+	if (event & ITEPD_ALERT_VDM_EVENT)
+		clear |= ITEPD_ALERT_VDM_EVENT;
+
+	if (event & ITEPD_ALERT_UCSI_EVENT) {
+		clear |= ITEPD_ALERT_UCSI_EVENT;
+		if (itepd->ucsi_cb) {
+			ret = itepd_read_reg(itepd, ITEPD_UCSI_CCI_REG, &cci, sizeof(cci));
+			if (ret)
+				goto err_mutex_unlock_cb;
+			len = itepd->ucsi_cb->get_len(itepd->ucsi_cb->priv, cci);
+
+			if (len > 0) {
+				ret = itepd_read_reg(itepd, ITEPD_UCSI_MSG_IN_REG, msg_in,
+						     min_t(size_t, len, 0x28));
+				if (ret)
+					goto err_mutex_unlock_cb;
+			}
+			notify_ucsi = true;
+		}
+	}
+
+	if (clear) {
+		ret = itepd_write_reg(itepd, ITEPD_VENDOR_WC_INT, &clear, 1);
+		if (ret)
+			goto err_mutex_unlock_cb;
+	}
+
+	if (notify_ucsi)
+		itepd->ucsi_cb->notify(itepd->ucsi_cb->priv, cci, msg_in);
+
+	mutex_unlock(&itepd->cb_lock);
+	mutex_unlock(&itepd->i2c_lock);
+
+	return IRQ_HANDLED;
+
+err_mutex_unlock_cb:
+	mutex_unlock(&itepd->cb_lock);
+	clear = (ITEPD_ALERT_VDM_EVENT | ITEPD_ALERT_UCSI_EVENT);
+	itepd_write_reg(itepd, ITEPD_VENDOR_WC_INT, &clear, 1);
+	mutex_unlock(&itepd->i2c_lock);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t itepd_irq_thread_fn(int irq, void *data)
+{
+	struct itepd *itepd = data;
+
+	return itepd_irq_process(itepd);
+}
+
+/*
+ * ITE AUX Function
+ */
+static void itepd_ucsi_aux_release(struct device *dev)
+{
+	struct auxiliary_device *adev = container_of(dev, struct auxiliary_device, dev);
+
+	of_node_put(dev->of_node);
+	kfree(adev);
+}
+
+static void itepd_altmode_aux_release(struct device *dev)
+{
+	struct auxiliary_device *adev = container_of(dev, struct auxiliary_device, dev);
+
+	of_node_put(dev->of_node);
+	kfree(adev);
+}
+
+static int itepd_add_aux_device(struct itepd *itepd,
+				struct auxiliary_device **aux_out,
+				const char *name,
+				void (*release)(struct device *))
+{
+	struct auxiliary_device *aux;
+	int ret;
+
+	aux = kzalloc_obj(*aux, GFP_KERNEL);
+	if (!aux)
+		return -ENOMEM;
+
+	aux->name = name;
+	aux->dev.parent = itepd->dev;
+	aux->dev.release = release;
+	device_set_of_node_from_dev(&aux->dev, itepd->dev);
+
+	ret = auxiliary_device_init(aux);
+	if (ret) {
+		of_node_put(aux->dev.of_node);
+		kfree(aux);
+		return ret;
+	}
+
+	ret = auxiliary_device_add(aux);
+	if (ret) {
+		auxiliary_device_uninit(aux);
+		return ret;
+	}
+
+	*aux_out = aux;
+	return 0;
+}
+
+static void itepd_del_aux_device(struct auxiliary_device *aux)
+{
+	auxiliary_device_delete(aux);
+	auxiliary_device_uninit(aux);
+}
+
+/*
+ * ITE Probe/Remove
+ */
+
+static int itepd_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct itepd *itepd;
+	const unsigned long *match_data;
+	struct gpio_desc *desc;
+	int ret;
+
+	itepd = devm_kzalloc(dev, sizeof(struct itepd), GFP_KERNEL);
+	if (!itepd)
+		return -ENOMEM;
+
+	itepd->dev = dev;
+	itepd->client = client;
+	itepd->irq = client->irq;
+	mutex_init(&itepd->i2c_lock);
+	mutex_init(&itepd->cb_lock);
+
+	match_data = (unsigned long *)of_device_get_match_data(dev);
+
+	if (!match_data)
+		return -EINVAL;
+	itepd->client_mask = *match_data;
+
+	i2c_set_clientdata(client, itepd);
+
+	if (itepd->irq > 0) {
+		ret = request_threaded_irq(itepd->irq, NULL, itepd_irq_thread_fn,
+					   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					   dev_name(dev), itepd);
+		if (ret < 0) {
+			dev_err(dev, "request_threaded_irq failed - %d\n", ret);
+			return ret;
+		}
+	}
+
+	desc = devm_gpiod_get(dev, NULL, GPIOD_IN);
+
+	if (IS_ERR(desc)) {
+		dev_info(dev, "get gpios from DTS failed\n");
+	} else {
+		if (gpiod_get_value(desc))
+			itepd_irq_process(itepd);
+	}
+
+	if (itepd->client_mask & BIT(ITEPD_CLIENT_ALTMODE)) {
+		ret = itepd_add_aux_device(itepd, &itepd->altmode_aux, "altmode",
+					   itepd_altmode_aux_release);
+		if (ret)
+			goto out_free_irq;
+	}
+
+	if (itepd->client_mask & BIT(ITEPD_CLIENT_UCSI)) {
+		ret = itepd_add_aux_device(itepd, &itepd->ucsi_aux, "ucsi", itepd_ucsi_aux_release);
+		if (ret)
+			goto out_release_altmode_aux;
+	}
+
+	return 0;
+
+out_release_altmode_aux:
+	if (itepd->client_mask & BIT(ITEPD_CLIENT_ALTMODE))
+		itepd_del_aux_device(itepd->altmode_aux);
+out_free_irq:
+	free_irq(itepd->irq, itepd);
+	return ret;
+}
+
+static void itepd_remove(struct i2c_client *client)
+{
+	struct itepd *itepd = i2c_get_clientdata(client);
+
+	if (itepd->client_mask & BIT(ITEPD_CLIENT_UCSI))
+		itepd_del_aux_device(itepd->ucsi_aux);
+	if (itepd->client_mask & BIT(ITEPD_CLIENT_ALTMODE))
+		itepd_del_aux_device(itepd->altmode_aux);
+	free_irq(itepd->irq, itepd);
+}
+
+static const unsigned long itepd_rb3gen2_client_mask =
+	BIT(ITEPD_CLIENT_ALTMODE) | BIT(ITEPD_CLIENT_UCSI);
+
+static const struct of_device_id itepd_of_match_table[] = {
+	{ .compatible = "ite,itepd-it885x", .data = &itepd_rb3gen2_client_mask },
+	{}
+};
+MODULE_DEVICE_TABLE(of, itepd_of_match_table);
+
+static struct i2c_driver itepd_driver = {
+	.driver = {
+		.name = "itepd",
+		.of_match_table = itepd_of_match_table,
+	},
+	.probe = itepd_probe,
+	.remove = itepd_remove,
+};
+
+module_i2c_driver(itepd_driver);
+
+MODULE_AUTHOR("Jeson Yang <jeson.yang@ite.com.tw>");
+MODULE_DESCRIPTION("ITEPD driver for ITE Type-C PD controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/typec/ucsi/itepd.h b/drivers/usb/typec/ucsi/itepd.h
new file mode 100644
index 000000000000..339b3ab0dc88
--- /dev/null
+++ b/drivers/usb/typec/ucsi/itepd.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025-2026, ITE. All Rights Reserved
+ */
+#ifndef __SOC_ITE_ITEPD_H__
+#define __SOC_ITE_ITEPD_H__
+
+#define ITEPD_MAX_PORTS						2
+
+enum {
+	ITEPD_CLIENT_UCSI,
+	ITEPD_CLIENT_ALTMODE,
+};
+
+enum {
+	ITEPD_SEND_UCSI_CONTROL,
+	ITEPD_SEND_UCSI_MESSAGE_OUT,
+};
+
+enum {
+	ITEPD_RECEIVE_UCSI_VERSION,
+};
+
+enum {
+	ITEPD_USBPD_MUX_USB_0 = 0,
+	ITEPD_USBPD_MUX_USB_1,
+	ITEPD_USBPD_MUX_DP_0,
+	ITEPD_USBPD_MUX_DP_1,
+	ITEPD_USBPD_MUX_USB_DP_0,
+	ITEPD_USBPD_MUX_USB_DP_1,
+	ITEPD_USBPD_MUX_TBT_0,
+	ITEPD_USBPD_MUX_TBT_1,
+	ITEPD_USBPD_MUX_USB4_0,
+	ITEPD_USBPD_MUX_USB4_1,
+	ITEPD_USBPD_MUX_OFF
+};
+
+#define ITEPD_USBPD_MUX_FLIPPED				BIT(0)
+
+struct itepd_altmode_data {
+	u8 port;
+	u8 mux;
+	u32 dp_config;
+	u32 dp_status;
+};
+
+struct itepd_ucsi_cb {
+	u8 (*get_len)(void *priv, u32 cci);
+	void (*notify)(void *priv, u32 cci, u8 *data);
+	void *priv;
+};
+
+struct itepd_altmode_cb {
+	void (*notify)(void *priv, struct itepd_altmode_data *data);
+	void *priv;
+};
+
+int itepd_cmd_send(struct device *dev, unsigned int cmd, const void *val, size_t val_len);
+int itepd_cmd_receive(struct device *dev, unsigned int cmd, void *val, size_t val_len);
+int itepd_register_cb(struct device *dev, u8 id, void *cb);
+int itepd_mode(struct device *dev, u8 port, u8 mux, u32 config, u32 status);
+int itepd_hpd(struct device *dev, u8 port, u32 status);
+
+#endif
diff --git a/drivers/usb/typec/ucsi/itepd_altmode.c b/drivers/usb/typec/ucsi/itepd_altmode.c
new file mode 100644
index 000000000000..a88ae2fb6e79
--- /dev/null
+++ b/drivers/usb/typec/ucsi/itepd_altmode.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026, ITE. All Rights Reserved
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_retimer.h>
+
+#include <drm/drm_bridge.h>
+
+#include "itepd.h"
+
+struct itepd_altmode;
+
+struct itepd_altmode_port {
+	struct itepd_altmode *altmode;
+	unsigned int index;
+
+	struct workqueue_struct *ordered_wq;
+
+	struct typec_switch *typec_switch;
+	struct typec_mux *typec_mux;
+	struct typec_retimer *typec_retimer;
+	struct drm_bridge bridge;
+
+	enum typec_orientation orientation;
+};
+
+struct itepd_altmode {
+	struct device *dev;
+
+	struct itepd_altmode_port ports[ITEPD_MAX_PORTS];
+};
+
+struct itepd_altmode_work {
+	struct itepd_altmode_port *alt_port;
+	struct itepd_altmode_data data;
+	struct work_struct work;
+
+	/*
+	 * DP mode state buffers kept on the heap (inside this kmalloc'd work
+	 * item) so that itepd_altmode_dp() does not need large local variables
+	 * and avoids triggering CONFIG_FRAME_WARN / checkstack.
+	 */
+	struct typec_altmode		dp_alt;
+	struct typec_displayport_data	dp_data;
+	struct typec_mux_state		mux_state;
+	struct typec_retimer_state	retimer_state;
+};
+
+static enum typec_orientation itepd_altmode_mux_to_orientation(u8 mux)
+{
+	if (mux >= ITEPD_USBPD_MUX_OFF)
+		return TYPEC_ORIENTATION_NONE;
+	else
+		return (mux & ITEPD_USBPD_MUX_FLIPPED) ?
+			TYPEC_ORIENTATION_REVERSE : TYPEC_ORIENTATION_NORMAL;
+}
+
+static void itepd_altmode_safe(struct itepd_altmode_port *alt_port,
+			       struct itepd_altmode_work *worker)
+{
+	struct itepd_altmode *altmode = alt_port->altmode;
+	struct typec_mux_state mux_state = {};
+	struct typec_retimer_state retimer_state = {};
+	int ret;
+
+	mux_state.alt = NULL;
+	mux_state.data = NULL;
+	mux_state.mode = TYPEC_STATE_SAFE;
+
+	ret = typec_mux_set(alt_port->typec_mux, &mux_state);
+	if (ret)
+		dev_err(altmode->dev, "failed to switch mux to safe mode\n");
+
+	retimer_state.alt = NULL;
+	retimer_state.data = NULL;
+	retimer_state.mode = TYPEC_STATE_SAFE;
+
+	ret = typec_retimer_set(alt_port->typec_retimer, &retimer_state);
+	if (ret)
+		dev_err(altmode->dev, "failed to setup retimer to safe mode\n");
+}
+
+static void itepd_altmode_usb(struct itepd_altmode_port *alt_port,
+			      struct itepd_altmode_work *worker)
+{
+	struct itepd_altmode *altmode = alt_port->altmode;
+	struct typec_mux_state mux_state = {};
+	struct typec_retimer_state retimer_state = {};
+	int ret;
+
+	mux_state.alt = NULL;
+	mux_state.data = NULL;
+	mux_state.mode = TYPEC_STATE_USB;
+
+	ret = typec_mux_set(alt_port->typec_mux, &mux_state);
+	if (ret)
+		dev_err(altmode->dev, "failed to switch mux to USB\n");
+
+	retimer_state.alt = NULL;
+	retimer_state.data = NULL;
+	retimer_state.mode = TYPEC_STATE_USB;
+
+	ret = typec_retimer_set(alt_port->typec_retimer, &retimer_state);
+	if (ret)
+		dev_err(altmode->dev, "failed to setup retimer to USB\n");
+}
+
+static void itepd_altmode_dp(struct itepd_altmode_port *alt_port,
+			     struct itepd_altmode_work *worker)
+{
+	struct itepd_altmode *altmode = alt_port->altmode;
+	u32 pin_assign;
+	unsigned int mode;
+	int ret;
+
+	/* Use heap buffers in worker to avoid large stack frames. */
+	memset(&worker->dp_alt, 0, sizeof(worker->dp_alt));
+	memset(&worker->dp_data, 0, sizeof(worker->dp_data));
+	memset(&worker->mux_state, 0, sizeof(worker->mux_state));
+	memset(&worker->retimer_state, 0, sizeof(worker->retimer_state));
+
+	worker->dp_alt.svid   = USB_TYPEC_DP_SID;
+	worker->dp_alt.mode   = USB_TYPEC_DP_MODE;
+	worker->dp_alt.active = 1;
+
+	worker->dp_data.status = worker->data.dp_status &
+				 (DP_STATUS_ENABLED | DP_STATUS_HPD_STATE |
+				  DP_STATUS_IRQ_HPD);
+	worker->dp_data.conf = worker->data.dp_config & DP_CONF_PIN_ASSIGNEMENT_MASK;
+
+	pin_assign = DP_CONF_GET_PIN_ASSIGN(worker->data.dp_config);
+	if (pin_assign == BIT(DP_PIN_ASSIGN_C))
+		mode = DP_PIN_ASSIGN_C;
+	else if (pin_assign == BIT(DP_PIN_ASSIGN_D))
+		mode = DP_PIN_ASSIGN_D;
+	else
+		mode = 0; /* unknown pin assignment — fall back to safe */
+
+	worker->mux_state.alt  = &worker->dp_alt;
+	worker->mux_state.data = &worker->dp_data;
+	worker->mux_state.mode = mode ? TYPEC_MODAL_STATE(mode) : TYPEC_STATE_SAFE;
+
+	ret = typec_mux_set(alt_port->typec_mux, &worker->mux_state);
+	if (ret)
+		dev_err(altmode->dev, "failed to switch mux to DP\n");
+
+	worker->retimer_state.alt  = &worker->dp_alt;
+	worker->retimer_state.data = &worker->dp_data;
+	worker->retimer_state.mode = worker->mux_state.mode;
+
+	ret = typec_retimer_set(alt_port->typec_retimer, &worker->retimer_state);
+	if (ret)
+		dev_err(altmode->dev, "failed to setup retimer to DP\n");
+}
+
+static void itepd_altmode_worker(struct work_struct *work)
+{
+	struct itepd_altmode_work *worker =
+		container_of(work, struct itepd_altmode_work, work);
+	struct itepd_altmode_port *alt_port = worker->alt_port;
+
+	alt_port->orientation = itepd_altmode_mux_to_orientation(worker->data.mux);
+	typec_switch_set(alt_port->typec_switch, alt_port->orientation);
+
+	switch (worker->data.mux) {
+	case ITEPD_USBPD_MUX_OFF:
+		itepd_altmode_safe(alt_port, worker);
+		drm_bridge_hpd_notify(&alt_port->bridge,
+				      connector_status_disconnected);
+		break;
+
+	case ITEPD_USBPD_MUX_DP_0:
+	case ITEPD_USBPD_MUX_DP_1:
+	case ITEPD_USBPD_MUX_USB_DP_0:
+	case ITEPD_USBPD_MUX_USB_DP_1:
+		itepd_altmode_dp(alt_port, worker);
+		if (worker->data.dp_status & DP_STATUS_HPD_STATE)
+			drm_bridge_hpd_notify(&alt_port->bridge,
+					      connector_status_connected);
+		else
+			drm_bridge_hpd_notify(&alt_port->bridge,
+					      connector_status_disconnected);
+		break;
+
+	case ITEPD_USBPD_MUX_USB_0:
+	case ITEPD_USBPD_MUX_USB_1:
+	case ITEPD_USBPD_MUX_TBT_0:
+	case ITEPD_USBPD_MUX_TBT_1:
+	case ITEPD_USBPD_MUX_USB4_0:
+	case ITEPD_USBPD_MUX_USB4_1:
+		itepd_altmode_usb(alt_port, worker);
+		drm_bridge_hpd_notify(&alt_port->bridge,
+				      connector_status_disconnected);
+		break;
+
+	default:
+		dev_err(alt_port->altmode->dev,
+			"unknown mux state %u on port %u, forcing safe mode\n",
+			worker->data.mux, alt_port->index);
+		itepd_altmode_safe(alt_port, worker);
+		drm_bridge_hpd_notify(&alt_port->bridge,
+				      connector_status_disconnected);
+		break;
+	}
+
+	kfree(worker);
+}
+
+static int itepd_altmode_attach(struct drm_bridge *bridge,
+				struct drm_encoder *encoder,
+				enum drm_bridge_attach_flags flags)
+{
+	return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
+}
+
+static const struct drm_bridge_funcs itepd_altmode_bridge_funcs = {
+	.attach = itepd_altmode_attach,
+};
+
+static void itepd_altmode_put_retimer(void *data)
+{
+	typec_retimer_put(data);
+}
+
+static void itepd_altmode_put_mux(void *data)
+{
+	typec_mux_put(data);
+}
+
+static void itepd_altmode_put_switch(void *data)
+{
+	typec_switch_put(data);
+}
+
+static void itepd_altmode_notify(void *priv, struct itepd_altmode_data *data)
+{
+	struct itepd_altmode *altmode = priv;
+	struct itepd_altmode_port *alt_port;
+	struct itepd_altmode_work *worker;
+
+	if (data->port >= ARRAY_SIZE(altmode->ports)) {
+		dev_err(altmode->dev, "invalid connector number, skip notify\n");
+		return;
+	}
+
+	alt_port = &altmode->ports[data->port];
+	if (!alt_port->altmode)
+		return;
+
+	worker = kmalloc_obj(*worker, GFP_KERNEL);
+	if (!worker) {
+		dev_err(altmode->dev, "out of memory, skip notify\n");
+		return;
+	}
+
+	memcpy(&worker->data, data, sizeof(struct itepd_altmode_data));
+	worker->alt_port = alt_port;
+
+	INIT_WORK(&worker->work, itepd_altmode_worker);
+	queue_work(alt_port->ordered_wq, &worker->work);
+}
+
+static void itepd_altmode_destroy_wq(void *data)
+{
+	struct workqueue_struct *wq = data;
+
+	flush_workqueue(wq);
+	destroy_workqueue(wq);
+}
+
+static int itepd_altmode_probe(struct auxiliary_device *adev,
+			       const struct auxiliary_device_id *id)
+{
+	struct itepd_altmode *altmode;
+	struct itepd_altmode_port *alt_port;
+	struct itepd_altmode_cb *cb;
+	struct fwnode_handle *fwnode;
+	struct device *dev = &adev->dev;
+	u32 port;
+	int ret;
+
+	altmode = devm_kzalloc(dev, sizeof(*altmode), GFP_KERNEL);
+	if (!altmode)
+		return -ENOMEM;
+
+	cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
+	if (!cb)
+		return -ENOMEM;
+
+	altmode->dev = dev;
+
+	device_for_each_child_node(dev, fwnode) {
+		ret = fwnode_property_read_u32(fwnode, "reg", &port);
+		if (ret < 0) {
+			dev_err(dev, "missing reg property of %pOFn\n", fwnode);
+			fwnode_handle_put(fwnode);
+			return ret;
+		}
+
+		if (port >= ARRAY_SIZE(altmode->ports)) {
+			dev_warn(dev, "invalid connector number, ignoring\n");
+			continue;
+		}
+
+		if (altmode->ports[port].altmode) {
+			dev_err(dev, "multiple connector definition for port %u\n", port);
+			fwnode_handle_put(fwnode);
+			return -EINVAL;
+		}
+
+		alt_port = &altmode->ports[port];
+		alt_port->altmode = altmode;
+		alt_port->index = port;
+
+		alt_port->ordered_wq = alloc_ordered_workqueue("itepd_altmode_%u", 0, port);
+		if (!alt_port->ordered_wq) {
+			fwnode_handle_put(fwnode);
+			return -ENOMEM;
+		}
+
+		ret = devm_add_action_or_reset(dev, itepd_altmode_destroy_wq,
+					       alt_port->ordered_wq);
+		if (ret) {
+			fwnode_handle_put(fwnode);
+			return ret;
+		}
+
+		alt_port->bridge.funcs = &itepd_altmode_bridge_funcs;
+		alt_port->bridge.of_node = to_of_node(fwnode);
+		alt_port->bridge.ops = DRM_BRIDGE_OP_HPD;
+		alt_port->bridge.type = DRM_MODE_CONNECTOR_DisplayPort;
+
+		alt_port->typec_mux = fwnode_typec_mux_get(fwnode);
+		if (IS_ERR(alt_port->typec_mux)) {
+			fwnode_handle_put(fwnode);
+			return dev_err_probe(dev, PTR_ERR(alt_port->typec_mux),
+					     "failed to acquire mode-switch for port: %d\n",
+					     port);
+		}
+
+		ret = devm_add_action_or_reset(dev, itepd_altmode_put_mux,
+					       alt_port->typec_mux);
+		if (ret) {
+			fwnode_handle_put(fwnode);
+			return ret;
+		}
+
+		alt_port->typec_retimer = fwnode_typec_retimer_get(fwnode);
+		if (IS_ERR(alt_port->typec_retimer)) {
+			fwnode_handle_put(fwnode);
+			return dev_err_probe(dev, PTR_ERR(alt_port->typec_retimer),
+					     "failed to acquire retimer-switch for port: %d\n",
+					     port);
+		}
+
+		ret = devm_add_action_or_reset(dev, itepd_altmode_put_retimer,
+					       alt_port->typec_retimer);
+		if (ret) {
+			fwnode_handle_put(fwnode);
+			return ret;
+		}
+
+		alt_port->typec_switch = fwnode_typec_switch_get(fwnode);
+		if (IS_ERR(alt_port->typec_switch)) {
+			fwnode_handle_put(fwnode);
+			return dev_err_probe(dev, PTR_ERR(alt_port->typec_switch),
+					     "failed to acquire orientation-switch for port: %d\n",
+					     port);
+		}
+
+		ret = devm_add_action_or_reset(dev, itepd_altmode_put_switch,
+					       alt_port->typec_switch);
+		if (ret) {
+			fwnode_handle_put(fwnode);
+			return ret;
+		}
+	}
+
+	for (port = 0; port < ARRAY_SIZE(altmode->ports); port++) {
+		alt_port = &altmode->ports[port];
+		if (!alt_port->altmode)
+			continue;
+
+		ret = devm_drm_bridge_add(dev, &alt_port->bridge);
+		if (ret)
+			return ret;
+	}
+
+	dev_set_drvdata(dev, altmode);
+
+	cb->notify = itepd_altmode_notify;
+	cb->priv = altmode;
+
+	ret = itepd_register_cb(dev, ITEPD_CLIENT_ALTMODE, cb);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void itepd_altmode_remove(struct auxiliary_device *adev)
+{
+	itepd_register_cb(&adev->dev, ITEPD_CLIENT_ALTMODE, NULL);
+	/*
+	 * devm unwind handles workqueue flush/destroy and typec resource
+	 * release in reverse probe order.
+	 */
+}
+
+static const struct auxiliary_device_id itepd_altmode_id_table[] = {
+	{ .name = "itepd.altmode", },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, itepd_altmode_id_table);
+
+static struct auxiliary_driver itepd_altmode_driver = {
+	.name = "itepd_altmode",
+	.probe = itepd_altmode_probe,
+	.remove = itepd_altmode_remove,
+	.id_table = itepd_altmode_id_table,
+};
+
+module_auxiliary_driver(itepd_altmode_driver);
+
+MODULE_AUTHOR("Jeson Yang <jeson.yang@ite.com.tw>");
+MODULE_DESCRIPTION("USB Type-C alternate mode driver for ITE Type-C PD controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/typec/ucsi/ucsi_itepd.c b/drivers/usb/typec/ucsi/ucsi_itepd.c
new file mode 100644
index 000000000000..ea55ff7b866e
--- /dev/null
+++ b/drivers/usb/typec/ucsi/ucsi_itepd.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026, ITE. All Rights Reserved
+ */
+#include <linux/unaligned.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/workqueue.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/slab.h>
+
+#include "itepd.h"
+#include "ucsi.h"
+
+struct ucsi_itepd {
+	struct device *dev;
+	struct ucsi *ucsi;
+	struct completion complete;
+	struct mutex received_lock; /* Protects command response data:*/
+	struct workqueue_struct *ordered_wq;
+	bool connected[ITEPD_MAX_PORTS];
+	bool dp_en[ITEPD_MAX_PORTS];
+	bool con_change_processed[ITEPD_MAX_PORTS];
+	u8 cmd_port;
+	u8 resp_received;
+	u8 msg_in[0x28];
+	u8 dp_idx[ITEPD_MAX_PORTS];
+	u8 orientation[ITEPD_MAX_PORTS];
+	u8 mux[ITEPD_MAX_PORTS];
+	u32 cci;
+	u64 cmd;
+};
+
+struct ucsi_itepd_work {
+	struct ucsi_itepd *ucsi_itepd;
+	u8 port;
+	u32 cci;
+	struct work_struct work;
+};
+
+static struct ucsi_itepd *__ucsi_itepd;
+
+static void ucsi_itepd_handle_dp_altmode(struct ucsi_itepd *ucsi_itepd,
+					 u8 port, u8 *mux, u32 *config, u32 *status)
+{
+	u8 orientation = ucsi_itepd->orientation[port];
+	u8 data[10];
+	u64 cmd;
+	int ret;
+
+	cmd = UCSI_COMMAND(UCSI_GET_CURRENT_CAM) | UCSI_CONNECTOR_NUMBER(port + 1);
+	ret = ucsi_send_command(ucsi_itepd->ucsi, cmd, data, 1);
+	if (ret < 0)
+		return;
+
+	if (data[0] != ucsi_itepd->dp_idx[port])
+		return;
+
+	cmd = UCSI_COMMAND(UCSI_GET_CAM_CS) |
+	      UCSI_CONNECTOR_NUMBER(port + 1) |
+	      ((u64)(ucsi_itepd->dp_idx[port]) << 24);
+	ret = ucsi_send_command(ucsi_itepd->ucsi, cmd, data, 10);
+	if (ret < 0)
+		return;
+
+	*config = get_unaligned_le32(data + 6);
+	*status = get_unaligned_le32(data + 1);
+
+	if (DP_CONF_GET_PIN_ASSIGN(*config) == BIT(DP_PIN_ASSIGN_C)) {
+		*mux = (orientation == 1) ? ITEPD_USBPD_MUX_DP_1 : ITEPD_USBPD_MUX_DP_0;
+		ucsi_itepd->dp_en[port] = true;
+	} else if (DP_CONF_GET_PIN_ASSIGN(*config) == BIT(DP_PIN_ASSIGN_D)) {
+		*mux = (orientation == 1) ? ITEPD_USBPD_MUX_USB_DP_1 : ITEPD_USBPD_MUX_USB_DP_0;
+		ucsi_itepd->dp_en[port] = true;
+	}
+}
+
+static void ucsi_itepd_connector_partner_change(struct ucsi_itepd *ucsi_itepd,
+						u8 port, struct ucsi_connector *con)
+{
+	u8 orientation = ucsi_itepd->orientation[port];
+	u8 mux = ucsi_itepd->mux[port];
+	u32 config = 0;
+	u32 status = 0;
+
+	if (!(UCSI_CONSTAT(con, CHANGE) &
+	      (UCSI_CONSTAT_PARTNER_CHANGE | UCSI_CONSTAT_CONNECT_CHANGE)))
+		return;
+
+	if (!UCSI_CONSTAT(con, CONNECTED)) {
+		mux = ITEPD_USBPD_MUX_OFF;
+		ucsi_itepd->dp_en[port] = false;
+	} else if (UCSI_CONSTAT(con, PARTNER_FLAG_ALT_MODE)) {
+		ucsi_itepd_handle_dp_altmode(ucsi_itepd, port, &mux, &config, &status);
+	} else {
+		mux = (orientation == 1) ? ITEPD_USBPD_MUX_USB_1 : ITEPD_USBPD_MUX_USB_0;
+		ucsi_itepd->dp_en[port] = false;
+	}
+
+	if (mux != ucsi_itepd->mux[port]) {
+		itepd_mode(ucsi_itepd->dev, port, mux, config, status);
+		ucsi_itepd->mux[port] = mux;
+	}
+}
+
+static void ucsi_itepd_connector_change_work(struct work_struct *work)
+{
+	struct ucsi_itepd_work *worker = container_of(work, struct ucsi_itepd_work, work);
+	struct ucsi_itepd *ucsi_itepd = worker->ucsi_itepd;
+	u8 data[11];
+	u8 num_vdos;
+	u32 status;
+	u64 cmd;
+	int ret;
+
+	if (ucsi_itepd->con_change_processed[worker->port])
+		goto out;
+
+	ucsi_itepd->con_change_processed[worker->port] = true;
+	if (ucsi_itepd->dp_en[worker->port]) {
+		/* UCSI_GET_ATTENTION_VDO (0x16) */
+		cmd = UCSI_COMMAND(UCSI_GET_ATTENTION_VDO) |
+		      UCSI_CONNECTOR_NUMBER(worker->port + 1);
+		ret = ucsi_send_command(ucsi_itepd->ucsi, cmd, data, 11);
+	}
+	if (ret < 0)
+		goto out;
+	num_vdos = data[2] & 0x07;
+	status = get_unaligned_le32(data + 7);
+
+	if (num_vdos)
+		itepd_hpd(ucsi_itepd->dev, worker->port, status);
+
+	ucsi_connector_change(ucsi_itepd->ucsi, UCSI_CCI_CONNECTOR(worker->cci));
+out:
+	kfree(worker);
+}
+
+static void ucsi_itepd_command_hook(struct ucsi_itepd *ucsi_itepd, u64 *cmd)
+{
+	/* Translate UCSI 1.2 commands/fields to ITE PD controller (v2.1) */
+	switch (UCSI_COMMAND(*cmd)) {
+	case UCSI_SET_NOTIFICATION_ENABLE:
+		if (*cmd & UCSI_ENABLE_NTFY_CMD_COMPLETE)
+			/* Enable Attention Notification for alt. mode */
+			*cmd |= FIELD_PREP(GENMASK_ULL(32, 16), BIT(3));
+		break;
+	case UCSI_GET_PDOS:
+		*cmd &= ~GENMASK_ULL(38, 37);
+		break;
+	case UCSI_GET_ERROR_STATUS:
+		*cmd &= ~GENMASK_ULL(22, 16);
+		*cmd |= UCSI_CONNECTOR_NUMBER(ucsi_itepd->cmd_port + 1);
+		break;
+	default:
+		break;
+	}
+
+	/* Track the connector number associated with this command */
+	switch (UCSI_COMMAND(*cmd)) {
+	case UCSI_PPM_RESET:
+	case UCSI_CANCEL:
+	case UCSI_SET_NOTIFICATION_ENABLE:
+	case UCSI_GET_CAPABILITY:
+		ucsi_itepd->cmd_port = 0;
+		break;
+	case UCSI_CONNECTOR_RESET:
+	case UCSI_GET_CONNECTOR_CAPABILITY:
+	case UCSI_SET_CCOM:		/* 0x08 - SET_UOM in older specs */
+	case UCSI_SET_UOR:
+	case UCSI_SET_PDR:
+	case UCSI_GET_CAM_SUPPORTED:
+	case UCSI_GET_CURRENT_CAM:
+	case UCSI_SET_NEW_CAM:
+	case UCSI_GET_PDOS:
+	case UCSI_GET_CABLE_PROPERTY:
+	case UCSI_GET_CONNECTOR_STATUS:
+	case UCSI_SET_POWER_LEVEL:	/* 0x14 */
+	case UCSI_GET_PD_MESSAGE:	/* 0x15 */
+	case UCSI_GET_ATTENTION_VDO:	/* 0x16 */
+	case UCSI_GET_CAM_CS:		/* 0x18 */
+	case 0x19:
+	case 0x1A:
+	case 0x1B:
+	case UCSI_SET_SINK_PATH:	/* 0x1C */
+	case 0x1D:
+	case UCSI_READ_POWER_LEVEL:	/* 0x1E */
+	case 0x1F:
+		ucsi_itepd->cmd_port =
+			FIELD_GET(GENMASK(22, 16), *cmd) - 1;
+		break;
+	case UCSI_GET_ALTERNATE_MODES:
+		ucsi_itepd->cmd_port =
+			FIELD_GET(GENMASK(30, 24), *cmd) - 1;
+		break;
+	}
+
+	ucsi_itepd->cmd = *cmd;
+}
+
+static void ucsi_itepd_response_hook(struct ucsi_itepd *ucsi_itepd,
+				     u32 *cci, u8 *msg_in)
+{
+	u8 recipient;
+	u8 offset;
+	struct ucsi_itepd_work *worker;
+
+	if (((*cci & UCSI_CCI_COMMAND_COMPLETE) == 0) &&
+	    UCSI_CCI_CONNECTOR(*cci)) {
+		worker = kmalloc_obj(*worker, GFP_KERNEL);
+		if (!worker) {
+			dev_err(ucsi_itepd->dev,
+				"out of memory, skip attention check\n");
+			ucsi_connector_change(ucsi_itepd->ucsi,
+					      UCSI_CCI_CONNECTOR(*cci));
+		} else {
+			worker->port = UCSI_CCI_CONNECTOR(*cci) - 1;
+			worker->ucsi_itepd = ucsi_itepd;
+			worker->cci = *cci;
+
+			INIT_WORK(&worker->work,
+				  ucsi_itepd_connector_change_work);
+			queue_work(ucsi_itepd->ordered_wq, &worker->work);
+		}
+	}
+
+	if ((*cci & UCSI_CCI_COMMAND_COMPLETE) &&
+	    ((*cci & UCSI_CCI_ERROR) == 0)) {
+		switch (UCSI_COMMAND(ucsi_itepd->cmd)) {
+		case UCSI_GET_CONNECTOR_STATUS:
+			ucsi_itepd->connected[ucsi_itepd->cmd_port] =
+				!!(msg_in[2] & BIT(3));
+			ucsi_itepd->orientation[ucsi_itepd->cmd_port] =
+				FIELD_GET(BIT(6), msg_in[10]);
+			break;
+
+		case UCSI_GET_ALTERNATE_MODES:
+			recipient = FIELD_GET(GENMASK_ULL(18, 16),
+					      ucsi_itepd->cmd);
+			if (recipient == UCSI_RECIPIENT_CON) {
+				offset = FIELD_GET(GENMASK_ULL(39, 32),
+						   ucsi_itepd->cmd);
+				if (((struct ucsi_altmode *)msg_in)->svid ==
+				    USB_TYPEC_DP_SID) {
+					ucsi_itepd->dp_idx[ucsi_itepd->cmd_port] =
+						offset;
+				} else if (((struct ucsi_altmode *)
+					    (msg_in + 6))->svid ==
+					   USB_TYPEC_DP_SID) {
+					ucsi_itepd->dp_idx[ucsi_itepd->cmd_port] =
+						offset + 1;
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+/*
+ * ITE PD notify callback
+ */
+static u8 ucsi_itepd_get_len(void *priv, u32 cci)
+{
+	if (cci & UCSI_CCI_COMMAND_COMPLETE)
+		return UCSI_CCI_LENGTH(cci);
+	return 0;
+}
+
+static void ucsi_itepd_notify(void *priv, u32 cci, u8 *data)
+{
+	struct ucsi_itepd *ucsi_itepd = (struct ucsi_itepd *)priv;
+	bool comp = false;
+	u8 msg_in[0x28];
+	u8 len = UCSI_CCI_LENGTH(cci);
+
+	memcpy(msg_in, data, min_t(u8, len, ARRAY_SIZE(msg_in)));
+	ucsi_itepd_response_hook(ucsi_itepd, &cci, msg_in);
+
+	mutex_lock(&ucsi_itepd->received_lock);
+	if (cci & UCSI_CCI_COMMAND_COMPLETE) {
+		ucsi_itepd->resp_received = 1;
+		ucsi_itepd->cci = cci;
+		memset(ucsi_itepd->msg_in, 0, ARRAY_SIZE(ucsi_itepd->msg_in));
+		if (len)
+			memcpy(ucsi_itepd->msg_in, msg_in,
+			       min_t(u8, len, ARRAY_SIZE(ucsi_itepd->msg_in)));
+		comp = true;
+	}
+	if (cci & UCSI_CCI_RESET_COMPLETE) {
+		ucsi_itepd->resp_received = 1;
+		ucsi_itepd->cci = cci;
+		memset(ucsi_itepd->msg_in, 0, ARRAY_SIZE(ucsi_itepd->msg_in));
+		comp = true;
+	}
+	mutex_unlock(&ucsi_itepd->received_lock);
+
+	if (cci & UCSI_CCI_ACK_COMPLETE)
+		comp = true;
+
+	if (comp)
+		complete(&ucsi_itepd->complete);
+}
+
+/*
+ * New ucsi_operations implementation
+ */
+
+static int ucsi_itepd_read_version(struct ucsi *ucsi, u16 *version)
+{
+	struct ucsi_itepd *ucsi_itepd = ucsi_get_drvdata(ucsi);
+
+	return itepd_cmd_receive(ucsi_itepd->dev,
+				 ITEPD_RECEIVE_UCSI_VERSION,
+				 version, sizeof(*version));
+}
+
+static int ucsi_itepd_read_cci(struct ucsi *ucsi, u32 *cci)
+{
+	struct ucsi_itepd *ucsi_itepd = ucsi_get_drvdata(ucsi);
+
+	mutex_lock(&ucsi_itepd->received_lock);
+	if (ucsi_itepd->resp_received) {
+		ucsi_itepd->resp_received = 0;
+		*cci = ucsi_itepd->cci;
+	} else {
+		*cci = 0;
+	}
+	mutex_unlock(&ucsi_itepd->received_lock);
+
+	return 0;
+}
+
+/*
+ * poll_cci: called when notifications are temporarily disabled (e.g. during
+ * PPM reset).  For this hardware we can reuse read_cci — the firmware always
+ * pushes CCI updates via the notify callback regardless.
+ */
+static int ucsi_itepd_poll_cci(struct ucsi *ucsi, u32 *cci)
+{
+	return ucsi_itepd_read_cci(ucsi, cci);
+}
+
+static int ucsi_itepd_read_message_in(struct ucsi *ucsi,
+				      void *val, size_t val_len)
+{
+	struct ucsi_itepd *ucsi_itepd = ucsi_get_drvdata(ucsi);
+
+	mutex_lock(&ucsi_itepd->received_lock);
+	memcpy(val, ucsi_itepd->msg_in,
+	       min(val_len, ARRAY_SIZE(ucsi_itepd->msg_in)));
+	mutex_unlock(&ucsi_itepd->received_lock);
+
+	return 0;
+}
+
+/*
+ * async_control: fire a command to the PPM and return immediately.
+ * The old async_write(UCSI_CONTROL, …) path, now receiving the raw
+ * u64 command directly.
+ */
+static int ucsi_itepd_async_control(struct ucsi *ucsi, u64 command)
+{
+	struct ucsi_itepd *ucsi_itepd = ucsi_get_drvdata(ucsi);
+
+	ucsi_itepd_command_hook(ucsi_itepd, &command);
+
+	mutex_lock(&ucsi_itepd->received_lock);
+	ucsi_itepd->resp_received = 0;
+	mutex_unlock(&ucsi_itepd->received_lock);
+
+	return itepd_cmd_send(ucsi_itepd->dev,
+			      ITEPD_SEND_UCSI_CONTROL,
+			      &command, sizeof(command));
+}
+
+/*
+ * sync_control: blocking command — send and wait for completion.
+ * On success the caller gets cci and data filled in.
+ */
+static int ucsi_itepd_sync_control(struct ucsi *ucsi, u64 command,
+				   u32 *cci, void *data, size_t size)
+{
+	struct ucsi_itepd *ucsi_itepd = ucsi_get_drvdata(ucsi);
+	int ret;
+
+	reinit_completion(&ucsi_itepd->complete);
+
+	ret = ucsi_itepd_async_control(ucsi, command);
+	if (ret)
+		return ret;
+
+	if (!wait_for_completion_timeout(&ucsi_itepd->complete,
+					 msecs_to_jiffies(5000)))
+		return -ETIMEDOUT;
+
+	/* Hand back CCI and (optionally) message data to the core */
+	if (cci) {
+		mutex_lock(&ucsi_itepd->received_lock);
+		*cci = ucsi_itepd->cci;
+		mutex_unlock(&ucsi_itepd->received_lock);
+	}
+	if (data && size)
+		ucsi_itepd_read_message_in(ucsi, data, size);
+
+	return 0;
+}
+
+static bool ucsi_itepd_update_altmodes(struct ucsi *ucsi, u8 recipient,
+				       struct ucsi_altmode *orig, struct ucsi_altmode *updated)
+{
+	/* No altmode squashing needed for this hardware */
+	return false;
+}
+
+static void ucsi_itepd_update_connector(struct ucsi_connector *con)
+{
+	if (con->num > ITEPD_MAX_PORTS || con->num < 1)
+		return;
+
+	con->typec_cap.orientation_aware = true;
+}
+
+static void ucsi_itepd_connector_status(struct ucsi_connector *con)
+{
+	struct ucsi_itepd *ucsi_itepd = ucsi_get_drvdata(con->ucsi);
+
+	if (con->num > ITEPD_MAX_PORTS || con->num < 1)
+		return;
+
+	if (ucsi_itepd->connected[con->num - 1])
+		typec_set_orientation(con->port,
+				      ucsi_itepd->orientation[con->num - 1] ?
+				      TYPEC_ORIENTATION_REVERSE :
+				      TYPEC_ORIENTATION_NORMAL);
+	else
+		typec_set_orientation(con->port, TYPEC_ORIENTATION_NONE);
+
+	/*
+	 * Pass the ucsi_connector (which now holds the cached bitmap status)
+	 * rather than the old struct ucsi_connector_status pointer.
+	 */
+	ucsi_itepd_connector_partner_change(ucsi_itepd, con->num - 1, con);
+
+	ucsi_itepd->con_change_processed[con->num - 1] = false;
+}
+
+static const struct ucsi_operations ucsi_itepd_ops = {
+	.read_version		= ucsi_itepd_read_version,
+	.read_cci		    = ucsi_itepd_read_cci,
+	.poll_cci		    = ucsi_itepd_poll_cci,
+	.read_message_in	= ucsi_itepd_read_message_in,
+	.sync_control		= ucsi_itepd_sync_control,
+	.async_control		= ucsi_itepd_async_control,
+	.update_altmodes	= ucsi_itepd_update_altmodes,
+	.update_connector	= ucsi_itepd_update_connector,
+	.connector_status	= ucsi_itepd_connector_status,
+};
+
+static int ucsi_itepd_probe(struct auxiliary_device *adev,
+			    const struct auxiliary_device_id *id)
+{
+	struct device *dev = &adev->dev;
+	struct ucsi_itepd *ucsi_itepd;
+	struct itepd_ucsi_cb *ucsi_itepd_cb;
+	int ret;
+
+	ucsi_itepd = devm_kzalloc(dev, sizeof(*ucsi_itepd), GFP_KERNEL);
+	if (!ucsi_itepd)
+		return -ENOMEM;
+
+	ucsi_itepd_cb = devm_kzalloc(dev, sizeof(*ucsi_itepd_cb), GFP_KERNEL);
+	if (!ucsi_itepd_cb)
+		return -ENOMEM;
+
+	ucsi_itepd->dev = dev;
+	init_completion(&ucsi_itepd->complete);
+	mutex_init(&ucsi_itepd->received_lock);
+
+	ucsi_itepd->ordered_wq = alloc_ordered_workqueue("fifo_wq", 0);
+	if (!ucsi_itepd->ordered_wq)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, ucsi_itepd);
+	__ucsi_itepd = ucsi_itepd;
+
+	ucsi_itepd_cb->get_len = ucsi_itepd_get_len;
+	ucsi_itepd_cb->notify  = ucsi_itepd_notify;
+	ucsi_itepd_cb->priv    = ucsi_itepd;
+
+	ret = itepd_register_cb(dev, ITEPD_CLIENT_UCSI, ucsi_itepd_cb);
+	if (ret)
+		goto out_destroy_wq;
+
+	ucsi_itepd->ucsi = ucsi_create(dev, &ucsi_itepd_ops);
+	if (IS_ERR(ucsi_itepd->ucsi)) {
+		ret = PTR_ERR(ucsi_itepd->ucsi);
+		goto out_unregister_cb;
+	}
+
+	ucsi_set_drvdata(ucsi_itepd->ucsi, ucsi_itepd);
+
+	ret = ucsi_register(ucsi_itepd->ucsi);
+	if (ret)
+		goto out_ucsi_destroy;
+
+	return 0;
+
+out_ucsi_destroy:
+	ucsi_destroy(ucsi_itepd->ucsi);
+out_unregister_cb:
+	itepd_register_cb(dev, ITEPD_CLIENT_UCSI, NULL);
+out_destroy_wq:
+	destroy_workqueue(ucsi_itepd->ordered_wq);
+
+	return ret;
+}
+
+static void ucsi_itepd_remove(struct auxiliary_device *adev)
+{
+	struct ucsi_itepd *ucsi_itepd = dev_get_drvdata(&adev->dev);
+
+	if (ucsi_itepd->ordered_wq) {
+		flush_workqueue(ucsi_itepd->ordered_wq);
+		destroy_workqueue(ucsi_itepd->ordered_wq);
+	}
+
+	ucsi_unregister(ucsi_itepd->ucsi);
+	ucsi_destroy(ucsi_itepd->ucsi);
+	usleep_range(2000, 2500);
+	itepd_register_cb(&adev->dev, ITEPD_CLIENT_UCSI, NULL);
+}
+
+static const struct auxiliary_device_id ucsi_itepd_id_table[] = {
+	{ .name = "itepd.ucsi", },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, ucsi_itepd_id_table);
+
+static struct auxiliary_driver ucsi_itepd_driver = {
+	.name     = "ucsi_itepd",
+	.probe    = ucsi_itepd_probe,
+	.remove   = ucsi_itepd_remove,
+	.id_table = ucsi_itepd_id_table,
+};
+
+module_auxiliary_driver(ucsi_itepd_driver);
+
+MODULE_AUTHOR("Jeson Yang <jeson.yang@ite.com.tw>");
+MODULE_DESCRIPTION("UCSI driver for ITE Type-C PD controller");
+MODULE_LICENSE("GPL");

-- 
2.53.0


^ permalink raw reply related

* [PATCH 0/2] Add support for ITE IT885x USB PD controller
From: Amber Kao @ 2026-06-15 13:47 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Jeson Yang, Yaode Fang, Bling Chiang, Eric Su,
	Doreen Lin, Heikki Krogerus
  Cc: linux-usb, devicetree, linux-kernel, Amber Kao

This series adds Device Tree bindings and driver support for the ITE IT885x
USB Type-C Power Delivery controller over I2C. The driver uses the auxiliary
bus to spawn UCSI and Alternate Mode child devices from the main I2C core
driver.

Note: This driver has not been tested on physical hardware. Runtime
verification was performed using i2c-stub emulation with force-binding
on an x86_64 virtual machine.

Testing performed:
- checkpatch.pl: no errors or warnings
- dtschema (dt_binding_check): no errors or warnings
- Sparse static analysis (C=2 W=1): no errors or warnings
- checkstack.pl: no functions exceed 512-byte stack limit on x86_64 and arm64
- Dependency check (allnoconfig + make): all dependency is been added
- Kconfig tristate: tested =m, =y, =n and allmodconfig
- Cross-compilation: x86_64, arm64, arm32, powerpc64
- CONFIG_SMP and CONFIG_PREEMPT: both on and off
- Strict warning mode (W=1 EXTRA_CFLAGS=-W): no new warnings
- Fault injection (CONFIG_FAILSLAB): error paths verified on modified kernel

Question regarding Fault Injection testing:
Since physical hardware is currently unavailable for testing, my approach to
verifying the error handling paths was to temporarily modify the driver code
(e.g., forcing a function to return -ENOMEM), run the mock tests, and then
revert the code once passed. Is this ad-hoc instrumentation approach
acceptable for submission under these constraints, or is there an officially
recommended fault injection method that the community prefers for this kind of validation?

Signed-off-by: Amber Kao <amber.kao@ite.com.tw>
---
Amber Kao (2):
      dt-bindings: usb: Add ITE IT885x support
      usb: typec: ucsi: Add ITE IT885x Type-C PD controller driver

 .../devicetree/bindings/usb/ite,itepd-it885x.yaml  | 109 ++++
 MAINTAINERS                                        |  15 +
 drivers/usb/typec/ucsi/Kconfig                     |  15 +
 drivers/usb/typec/ucsi/Makefile                    |   1 +
 drivers/usb/typec/ucsi/itepd.c                     | 481 ++++++++++++++++++
 drivers/usb/typec/ucsi/itepd.h                     |  64 +++
 drivers/usb/typec/ucsi/itepd_altmode.c             | 438 ++++++++++++++++
 drivers/usb/typec/ucsi/ucsi_itepd.c                | 558 +++++++++++++++++++++
 8 files changed, 1681 insertions(+)
---
base-commit: 8fde5d1d47f69db6082dfa34500c27f8485389a5
change-id: 20260605-ucsi-itepd-feature-95e6dcee4fc5

Best regards,
--  
Amber Kao <amber.kao@ite.com.tw>


^ permalink raw reply

* [PATCH 1/2] dt-bindings: usb: Add ITE IT885x support
From: Amber Kao @ 2026-06-15 13:47 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Jeson Yang, Yaode Fang, Bling Chiang, Eric Su,
	Doreen Lin, Heikki Krogerus
  Cc: linux-usb, devicetree, linux-kernel, Amber Kao
In-Reply-To: <20260615-ucsi-itepd-feature-v1-0-a826cfd0df6a@ite.com.tw>

Add device tree binding documentation for the ITE IT885x.
The ITE IT885x is an I2C-based USB Type-C Power Delivery (PD) controller.

Cc: Yaode Fang <Yaode.Fang@ite.com.tw>
Cc: Jeson Yang <jeson.yang@ite.com.tw>
Cc: Bling Chiang <Bling.Chiang@ite.com.tw>
Cc: Eric Su <Eric.Su@ite.com.tw>
Cc: Doreen Lin <doreen.lin@ite.com.tw>
Signed-off-by: Amber Kao <amber.kao@ite.com.tw>
---
 .../devicetree/bindings/usb/ite,itepd-it885x.yaml  | 109 +++++++++++++++++++++
 MAINTAINERS                                        |  11 +++
 2 files changed, 120 insertions(+)

diff --git a/Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml b/Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml
new file mode 100644
index 000000000000..59e4eaa32ff1
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml
@@ -0,0 +1,109 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/ite,itepd-it885x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ITE IT885x USB Type-C Power Delivery Controller
+
+maintainers:
+  - Jeson Yang <jeson.yang@ite.com.tw>
+
+description:
+  The ITE IT885x is an I2C-based USB Type-C Power Delivery (PD) controller.
+
+properties:
+  compatible:
+    const: ite,itepd-it885x
+
+  reg:
+    maxItems: 1
+
+  gpios:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  wakeup-source: true
+
+  pinctrl-names:
+    minItems: 1
+
+  pinctrl-0: true
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+patternProperties:
+  "^connector(@[0-9a-f]+)?$":
+    $ref: /schemas/connector/usb-connector.yaml#
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        itepd@40 {
+            compatible = "ite,itepd-it885x";
+            reg = <0x40>;
+            gpios = <&tlmm 129 GPIO_ACTIVE_LOW>;
+            interrupts-extended = <&tlmm 129 IRQ_TYPE_EDGE_FALLING>;
+            wakeup-source;
+
+            pinctrl-names = "default";
+            pinctrl-0 = <&itepd_int_default>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            connector@0 {
+                compatible = "usb-c-connector";
+                reg = <0>;
+                label = "USB-C";
+                power-role = "dual";
+                data-role = "dual";
+
+                ports {
+                    #address-cells = <1>;
+                    #size-cells = <0>;
+
+                    port@0 {
+                        reg = <0>;
+                        endpoint {
+                            remote-endpoint = <&eud_con>;
+                        };
+                    };
+
+                    port@1 {
+                        reg = <1>;
+                        endpoint {
+                            remote-endpoint = <&redriver_ss_out>;
+                        };
+                    };
+
+                    port@2 {
+                        reg = <2>;
+                        endpoint {
+                            remote-endpoint = <&fsa4480_sbu_mux>;
+                        };
+                    };
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 461a3eed6129..94afe3729059 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13720,6 +13720,17 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	Documentation/devicetree/bindings/display/bridge/ite,it66121.yaml
 F:	drivers/gpu/drm/bridge/ite-it66121.c
 
+ITE IT885x TYPE-C PD CONTROLLER DRIVER
+M:	Amber Kao <amber.kao@ite.com.tw>
+M:	Jeson Yang <jeson.yang@ite.com.tw>
+R:	Yaode Fang <Yaode.Fang@ite.com.tw>
+R:	Bling Chiang <Bling.Chiang@ite.com.tw>
+R:	Eric Su <Eric.Su@ite.com.tw>
+R:	Doreen Lin <doreen.lin@ite.com.tw>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml
+
 IVTV VIDEO4LINUX DRIVER
 M:	Andy Walls <awalls@md.metrocast.net>
 L:	linux-media@vger.kernel.org

-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
From: sashiko-bot @ 2026-06-15  5:57 UTC (permalink / raw)
  To: Joey Lu
  Cc: linux-phy, conor+dt, olteanv, robh, devicetree, neil.armstrong,
	vkoul
In-Reply-To: <20260615054911.48821-3-a0987203069@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The unit addresses for EHCI1 and OHCI1 nodes use uppercase hexadecimal characters.
--

commit 05d96a79d90d16847229b2f5b5cfd71d8eb6ef98
Author: Joey Lu <a0987203069@gmail.com>

arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node

This commit adds device tree nodes for the MA35D1 USB subsystem,
including a dual-port PHY node and nodes for the DWC2, EHCI, and OHCI
controllers. It also enables the corresponding nodes and configures
pinctrl in the board files.

> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> index e51b98f5bdce4..73ff1d78d2848 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi

[ ... ]

> @@ -379,5 +388,62 @@ uart16: serial@40880000 {
>  			clocks = <&clk UART16_GATE>;
>  			status = "disabled";
>  		};

[ ... ]

> +		ehci1: usb@401C0000 {

[Severity: Low]
Should this unit address use lowercase hexadecimal characters?

The Devicetree specification requires unit addresses to be formatted using
lowercase hex. Using uppercase 'C' here (unlike the 'c' in the reg property
below) can trigger W_simple_bus_reg warnings when checking device trees.

> +			compatible = "generic-ehci";
> +			reg = <0x0 0x401c0000 0x0 0x1000>;
> +			interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
> +			clocks = <&clk HUSBH1_GATE>;
> +			phys = <&usb_phy 1>;
> +			phy-names = "usb";
> +			companion = <&ohci1>;
> +			status = "disabled";
> +		};
> +
> +		ohci0: usb@40150000 {
> +			compatible = "generic-ohci";
> +			reg = <0x0 0x40150000 0x0 0x1000>;
> +			interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
> +			clocks = <&clk HUSBH0_GATE>;
> +			phys = <&usb_phy 0>;
> +			phy-names = "usb";
> +			status = "disabled";
> +		};
> +
> +		ohci1: usb@401D0000 {

[Severity: Low]
Similarly, could this node be named usb@401d0000 to comply with the
standard Devicetree lowercase hex conventions?

> +			compatible = "generic-ohci";
> +			reg = <0x0 0x401d0000 0x0 0x1000>;
> +			interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
> +			clocks = <&clk HUSBH1_GATE>;
> +			phys = <&usb_phy 1>;
> +			phy-names = "usb";
> +			status = "disabled";
> +		};
>  	};
>  };

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

^ permalink raw reply

* Re: [PATCH 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
From: sashiko-bot @ 2026-06-15  5:58 UTC (permalink / raw)
  To: Joey Lu
  Cc: devicetree, vkoul, robh, conor+dt, neil.armstrong, olteanv,
	linux-phy
In-Reply-To: <20260615054911.48821-2-a0987203069@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The device tree example fails `dt_binding_check` validation due to a unit address mismatch and violations of the parent reset controller schema.
--

commit abaf23416a1205bdc22b4891ef46a49e088031a4
Author: Joey Lu <a0987203069@gmail.com>

dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support

This extends the Nuvoton MA35D1 USB2 PHY binding to support two ports (OTG
and host-only). The PHY node becomes a child of the syscon node, and
properties are updated to reflect the new architecture.

> diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> @@ -8,38 +8,72 @@ title: Nuvoton MA35D1 USB2 phy

[ ... ]

>  examples:
>    - |
> -    #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> +    system-management@40460000 {
> +        compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
> +        reg = <0x0 0x40460000 0x0 0x200>;
> +        #reset-cells = <1>;
> +        #address-cells = <1>;
> +        #size-cells = <1>;
>  
> -    usb_phy: usb-phy {
> -        compatible = "nuvoton,ma35d1-usb2-phy";
> -        clocks = <&clk USBD_GATE>;
> -        nuvoton,sys = <&sys>;
> -        #phy-cells = <0>;
> +        usb-phy@60 {
> +            compatible = "nuvoton,ma35d1-usb2-phy";
> +            reg = <0x60 0x14>;
> +            #phy-cells = <1>;
> +        };
>      };

[Severity: Low]
Will this example pass dt_binding_check validation?

The schema for the parent node (nuvoton,ma35d1-reset.yaml) enforces
additionalProperties: false and requires exactly two compatible items
("nuvoton,ma35d1-reset" and "syscon"). It does not permit child nodes,
the "simple-mfd" compatible, or the #address-cells and #size-cells
properties.

Additionally, does the 4-cell reg array <0x0 0x40460000 0x0 0x200> mismatch
the unit address @40460000? Without a ranges property, the dt-schema wrapper
defaults to 1 cell for address and size, which parses this as two distinct
ranges and triggers a dtc unit_address_vs_reg warning.

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

^ permalink raw reply

* Re: [PATCH 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
From: sashiko-bot @ 2026-06-15  5:59 UTC (permalink / raw)
  To: Joey Lu
  Cc: conor+dt, neil.armstrong, devicetree, linux-phy, robh, olteanv,
	vkoul
In-Reply-To: <20260615054911.48821-4-a0987203069@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Missing cleanup of the USB role switch on the probe error path leads to a memory leak and a triggerable use-after-free vulnerability.
- [Medium] Missing bounds check on `args_count` in `ma35_usb_phy_xlate` leads to an uninitialized stack memory read and potential out-of-bounds access.
- [Medium] Unchecked return values of `regmap_read()` lead to the use of uninitialized stack variables.
- [Medium] Failure to explicitly clear `MISCFCR0_UHOVRCURH` when `nuvoton,oc-active-high` is absent allows bootloader state to bleed over.
--

commit c78d99f91b7305f8a53b899cb3cb3fa45c43c783
Author: Joey Lu <a0987203069@gmail.com>

phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support

The existing driver handled only PHY0 in device mode (DWC2 gadget). Extend it
to manage both PHY ports and integrate OTG support, per reviewer suggestion to
reuse the existing driver rather than add a separate one.

> diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
> index 9a459b700ed48..336680161104f 100644
> --- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c
> +++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c

[ ... ]

> +static int ma35_usb_phy_init(struct phy *phy)
>  {
> -	struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
> +	struct ma35_phy_port *port = phy_get_drvdata(phy);
> +	struct ma35_usb_phy *p = container_of(port - port->idx,
> +					      struct ma35_usb_phy, port[0]);
> +	unsigned int n = port->idx;
>  	unsigned int val;
>  	int ret;
>  
> -	ret = clk_prepare_enable(p_phy->clk);
> -	if (ret < 0) {
> -		dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
> -		return ret;
> -	}
> +	regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val);

[Severity: Medium]
Could this leave val uninitialized if regmap_read() fails?

If regmap_read() fails (e.g. due to a bus error), val remains uninitialized.
Evaluating it in the next line could lead to skipping the PHY initialization
incorrectly.

> +	if (val & USBPMISCR_PHY_SUSPEND(n))
> +		return 0;

[ ... ]

> +static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
> +{
> +	struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
> +	u32 val;
> +
> +	regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);
> +
> +	return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE;

[Severity: Medium]
Does this code return an unpredictable role if regmap_read() fails?

If regmap_read() encounters an error, val will remain uninitialized, which
could lead to nondeterministic driver behavior when evaluated for the returned
role.

> +}

[ ... ]

> +static struct phy *ma35_usb_phy_xlate(struct device *dev,
> +				      const struct of_phandle_args *args)
> +{
> +	struct ma35_usb_phy *p = dev_get_drvdata(dev);
> +
> +	if (args->args[0] >= MA35_PHY_NUM)

[Severity: Medium]
Can this code access uninitialized memory if args->args_count is 0?

If the device tree incorrectly specifies #phy-cells = <0>, args_count will
be 0 and the args->args array (allocated on the caller's stack) might be
uninitialized. Could we add a bounds check for args->args_count >= 1 before
accessing args->args[0]?

> +		return ERR_PTR(-EINVAL);
> +
> +	return p->port[args->args[0]].phy;
> +}

[ ... ]

>  static int ma35_usb_phy_probe(struct platform_device *pdev)
>  {

[ ... ]

> +	if (of_property_read_bool(pdev->dev.of_node, "nuvoton,oc-active-high"))
> +		regmap_update_bits(p->sysreg, MA35_SYS_MISCFCR0,
> +				   MISCFCR0_UHOVRCURH, MISCFCR0_UHOVRCURH);

[Severity: Medium]
Does this code fail to clear MISCFCR0_UHOVRCURH when "nuvoton,oc-active-high"
is absent?

If a bootloader previously set this bit to active-high, Linux will fail to
clear it upon boot when active-low is expected, potentially breaking USB
functionality.

> +
> +	for (n = 0; n < MA35_PHY_NUM; n++) {

[ ... ]

> +	ret = ma35_role_switch_init(pdev, p);
> +	if (ret)
> +		return ret;
>  
> -	provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
> +	provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate);
>  	if (IS_ERR(provider))
>  		return dev_err_probe(&pdev->dev, PTR_ERR(provider),
> -				     "Failed to register PHY provider\n");
> +				     "failed to register PHY provider\n");

[Severity: High]
Does this error path leak the USB role switch created by
ma35_role_switch_init()?

If devm_of_phy_provider_register() fails, the function returns immediately
without unregistering the role switch.

The driver core will then free the devm-allocated memory (including the
struct ma35_usb_phy *p passed as driver data). Since the role switch remains
registered, any later interaction with it (e.g. from sysfs) will invoke
ma35_role_sw_get():

static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
{
	struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
	u32 val;

	regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);

This dereferences the freed p pointer, causing a Use-After-Free.

> +
>  	return 0;
>  }

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

^ permalink raw reply

* Re: [PATCH 1/3] dt-bindings: media: qcom,qcm2290-venus: document shikra Iris compatible
From: Vikash Garodia @ 2026-06-15  6:00 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Bryan O'Donoghue, Dikshita Agarwal, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jorge Ramirez-Ortiz, Bjorn Andersson, Konrad Dybcio,
	linux-arm-msm, linux-media, devicetree, linux-kernel
In-Reply-To: <q3sr74ncqnmzsjrd6jdbkpplxpnsnifhnvxsdplvogr2kf25p3@taadnzd2qrcu>


On 6/12/2026 1:19 PM, Dmitry Baryshkov wrote:
> On Wed, Jun 10, 2026 at 01:20:59PM +0530, Vikash Garodia wrote:
>>
>> On 6/10/2026 2:07 AM, Bryan O'Donoghue wrote:
>>> On 09/06/2026 17:15, Vikash Garodia wrote:
>>>> Document the iris video accelerator used on shikra platforms by adding
>>>> the qcom,shikra-iris compatible.
>>>>
>>>> Although QCM2290 and shikra share the same video hardware and overall
>>>> integration, their SMMU programming differs. QCM2290 exposes separate
>>>> stream IDs for the video hardware and the Xtensa path, requiring two
>>>> explicit IOMMU entries, whereas shikra uses a masked SMR to collapse
>>>> equivalent stream IDs into a single mapping. Due to QCM2290’s SID layout
>>>> and Xtensa isolation requirements, such SMR masking is not applicable on
>>>> QCM2290 platforms.
>>>> Since shikra uses the same video hardware as QCM2290 and shares the same
>>>> programming model and capabilities, it is added as a fallback compatible
>>>> to qcom,qcm2290-venus, with conditional handling to allow either one or
>>>> two IOMMU entries.
>>>>
>>>> Signed-off-by: Vikash Garodia <vikash.garodia@oss.qualcomm.com>
>>>> ---
>>>>    .../bindings/media/qcom,qcm2290-venus.yaml           | 20
>>>> ++++++++++ ++++++----
>>>>    1 file changed, 16 insertions(+), 4 deletions(-)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/media/qcom,qcm2290-
>>>> venus.yaml b/Documentation/devicetree/bindings/media/qcom,qcm2290-
>>>> venus.yaml
>>>> index 5977e7d0a71b4fb5681f1c2094439c251366f01f..895533b9756690d075fd7729e3f805c8e72ff0df
>>>> 100644
>>>> --- a/Documentation/devicetree/bindings/media/qcom,qcm2290-venus.yaml
>>>> +++ b/Documentation/devicetree/bindings/media/qcom,qcm2290-venus.yaml
>>>> @@ -15,12 +15,27 @@ description:
>>>>
>>>>    allOf:
>>>>      - $ref: qcom,venus-common.yaml#
>>>> +  - if:
>>>> +      properties:
>>>> +        compatible:
>>>> +          contains:
>>>> +            const: qcom,shikra-iris
>>>
>>> Should this be "iris" in a venus yaml ?
>>
>> given the vpu core was first enabled on venus and now it same core on a
>> different SOC being enabled on iris. There are 2 ways to do this
>> 1. Add the compat to existing schema which defines the binding for that core
>> i.e existing patch
>> 2. Write a new schema (or say duplicate it) with ...shikra-iris.yaml, but
>> again, the shikra compat would fallback to "qcom,qcm2290-venus" compat.
>> hence even iris yaml would then need to carry the venus *string*.
>>
>> I followed the first option.
> 
> It is a correct approach. However the block should not be called Iris.
> If I remember correctly, the core is still named Venus.
> 

Indeed. I was wrong in associating this with the driver enabling it, but 
it is much better to tag them to the core. Will keep it as 
qcom,shikra-venus.

Regards,
Vikash

^ permalink raw reply

* Re: [PATCH v2 1/2] dt-bindings: media: qcom,sm8550-iris: Allow IOVA reservation memory-region
From: Krzysztof Kozlowski @ 2026-06-15  6:01 UTC (permalink / raw)
  To: Daniel J Blueman
  Cc: Bryan O'Donoghue, Vikash Garodia, Abhinav Kumar,
	Bjorn Andersson, Konrad Dybcio, Mauro Carvalho Chehab,
	Stephan Gerhold, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	linux-arm-msm, linux-media, devicetree, linux-kernel, stable
In-Reply-To: <20260614145113.84243-1-daniel@quora.org>

On Sun, Jun 14, 2026 at 10:51:11PM +0800, Daniel J Blueman wrote:
> In addition to the firmware-loaded codec carveout, some Iris platforms
> need to declare an IOMMU IOVA reservation (a reserved-memory node with
> iommu-addresses) to keep DMA away from IOVA ranges that earlier
> firmware stages have already mapped through the SMMU.
> 
> Permit a second memory-region phandle for this purpose, and describe
> the meaning of each entry so the ordering is unambiguous.
> 
> Fixes: 9065340ac04d ("arm64: dts: qcom: x1e80100: Add IRIS video codec")
> Cc: stable@vger.kernel.org
> Signed-off-by: Daniel J Blueman <daniel@quora.org>
> ---

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v2 3/3] dt-bindings: arm-smmu: Document GPU SMMU for Shikra SoC
From: Krzysztof Kozlowski @ 2026-06-15  6:03 UTC (permalink / raw)
  To: Akhil P Oommen
  Cc: Rob Clark, Sean Paul, Konrad Dybcio, Dmitry Baryshkov,
	Abhinav Kumar, Jessica Zhang, Marijn Suijten, David Airlie,
	Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Will Deacon, Robin Murphy, Joerg Roedel (AMD), Bibek Kumar Patro,
	linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
	linux-arm-kernel, iommu
In-Reply-To: <20260615-shikra-gpu-v2-3-2f2d1347c3fb@oss.qualcomm.com>

On Mon, Jun 15, 2026 at 03:02:59AM +0530, Akhil P Oommen wrote:
> From: Bibek Kumar Patro <bibek.patro@oss.qualcomm.com>
> 
> Add specific compatible strings to document the GPU SMMU present
> in the Shikra SoC.
> 
> Signed-off-by: Bibek Kumar Patro <bibek.patro@oss.qualcomm.com>
> Signed-off-by: Akhil P Oommen <akhilpo@oss.qualcomm.com>
> ---
>  Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 2 ++
>  1 file changed, 2 insertions(+)

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH 1/2] dt-bindings: usb: Add ITE IT885x support
From: sashiko-bot @ 2026-06-15  6:03 UTC (permalink / raw)
  To: Amber Kao; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260615-ucsi-itepd-feature-v1-1-a826cfd0df6a@ite.com.tw>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The device tree binding defines a generic `gpios` property and redundantly maps it to the exact same hardware pin as the `interrupts` property, which is a structural anti-pattern.
- [Low] The device tree binding example uses a vendor-specific node name instead of a generic functional name.
--

commit db50e109009ce6583e099c466366e81f9dc8a5ee
Author: Amber Kao <amber.kao@ite.com.tw>

dt-bindings: usb: Add ITE IT885x support

Add device tree binding documentation for the ITE IT885x. The ITE IT885x is
an I2C-based USB Type-C Power Delivery (PD) controller.

> diff --git a/Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml b/Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/ite,itepd-it885x.yaml
[ ... ]
> +properties:
> +  compatible:
> +    const: ite,itepd-it885x
> +
> +  reg:
> +    maxItems: 1
> +
> +  gpios:
> +    maxItems: 1

[Severity: Low]
Does this property need a functional prefix? Device tree schemas generally
require GPIO consumers to have a prefix, such as irq-gpios, rather than
using the bare gpios property.

[ ... ]
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        itepd@40 {

[Severity: Low]
Should this node name use a generic functional name instead of a vendor
specific one? A generic name like typec@40 or usb-typec@40 would align
better with the devicetree specification recommendations.

> +            compatible = "ite,itepd-it885x";
> +            reg = <0x40>;
> +            gpios = <&tlmm 129 GPIO_ACTIVE_LOW>;
> +            interrupts-extended = <&tlmm 129 IRQ_TYPE_EDGE_FALLING>;

[Severity: Low]
Is it necessary to map the exact same physical pin to both gpios and
interrupts-extended here? If this is used merely to poll the interrupt line
state during probe, the hardware description shouldn't encode software
workarounds.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-ucsi-itepd-feature-v1-0-a826cfd0df6a@ite.com.tw?part=1

^ 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