Linux USB
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Gary Yang" <gary.yang@cixtech.com>
Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH 2/4] usb: typec: add CIX Type-C PD controller driver
Date: Sat, 07 Nov 2026 01:57:31 +0000	[thread overview]
Message-ID: <sashiko-outbox-202469@kernel.org> (raw)
In-Reply-To: <20261107011012.3655873-3-gary.yang@cixtech.com>

Thank you for your contribution! Sashiko AI review found 12 potential
issues to consider:
- [High] usb: typec: cix_pd: unbalanced disable_irq() on shared IRQ in
  cix_pd_remove()
- [Medium] usb: typec: cix_pd: NULL altmode passed to mux in
  cix_pd_typec_port_update()
- [Medium] usb: typec: cix_pd: invalid unconditional DP partner altmode
  registration
- [Medium] usb: typec: cix_pd: wrong USB 2.0 orientation and mux state
  in port update
- [Medium] usb: typec: cix_pd: role cached on switch failure and skipped
  on restore
- [Medium] usb: typec: Kconfig: missing USB_ROLE_SWITCH dependency for
  TYPEC_CIX_PD
- [Medium] usb: typec: cix_pd: missing .poweroff callback and broken PM
  state handling
- [Low] usb: typec: cix_pd: missing static qualifier on DEVICE_ATTR_RW()
  definition
- [Low] usb: typec: cix_pd: cix_pd_typec_parse_port_props() duplicates
  typec_get_fw_cap()
- [Low] usb: typec: cix_pd: deferred-probe churn and mux state in
  cix_pd_init_ports()
- [Low] usb: typec: cix_pd: non-static and undocumented
  disable_pd_interrupt sysfs attr
- [Low] usb: typec: cix_pd: positional struct i2c_device_id initializer
  in cix_pd_id
--

commit 580ec433cd2d8c9792fdacbbc473c28e5cbfb4c8
Author: Gary Yang <gary.yang@cixtech.com>

usb: typec: add CIX Type-C PD controller driver

This commit adds an I2C driver for the CIX Type-C PD controller (initially
supporting the Realtek RTS5453H) to report orientation, USB role, and
DisplayPort alternate mode state to the Type-C class, mux, and USB role
switch frameworks.

> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> index 2f80c2792dbda..b81a0d946d002 100644
> --- a/drivers/usb/typec/Kconfig
> +++ b/drivers/usb/typec/Kconfig
> @@ -110,6 +110,17 @@ config TYPEC_WUSB3801
>  	  If you choose to build this driver as a dynamically linked module, the
>  	  module will be called wusb3801.ko.
>  
> +config TYPEC_CIX_PD
> +	tristate "CIX Type-C PD controller driver"
> +	depends on I2C
> +	select REGMAP_I2C

[Severity: Medium]
Does TYPEC_CIX_PD need a dependency on USB_ROLE_SWITCH?  Building with
CONFIG_TYPEC_CIX_PD=y and CONFIG_USB_ROLE_SWITCH=m fails to link with
undefined references to fwnode_usb_role_switch_get(),
usb_role_switch_set_role(), and usb_role_switch_put(), while
CONFIG_USB_ROLE_SWITCH=n causes fwnode_usb_role_switch_get() to return
ERR_PTR(-ENODEV) and fail cix_pd_probe() unconditionally.

> diff --git a/drivers/usb/typec/cix_pd.c b/drivers/usb/typec/cix_pd.c
> new file mode 100644
> index 0000000000000..b65e1a4760b7d
> --- /dev/null
> +++ b/drivers/usb/typec/cix_pd.c
[ ... ]
> +static const struct typec_altmode_desc cix_pd_dp_altmode = {
> +	.svid = USB_TYPEC_DP_SID,
> +	.mode = USB_TYPEC_DP_MODE,
> +};
> +
> +static int cix_pd_typec_port_update(struct cix_pd *typec)
> +	struct typec_displayport_data *dp_data = &typec->dp_data;
> +	enum typec_orientation orientation;
> +	enum usb_role role;
> +	bool connected;
> +	int ret = 0;
> +
> +	if (!(typec->data_status[1] & USB3_2_CONNECTION) &&
> +	    !(typec->data_status[2] & DP_CONNECTION))
> +		orientation = TYPEC_ORIENTATION_NONE;
> +	else if (typec->data_status[1] & CONNECTION_ORIENTATION)
> +		orientation = TYPEC_ORIENTATION_REVERSE;
> +	else
> +		orientation = TYPEC_ORIENTATION_NORMAL;
> +
> +	ret = typec_set_orientation(typec->port, orientation);
> +	if (ret)
> +		return ret;
> +
> +	if (!(typec->data_status[1] & USB3_2_CONNECTION) &&
> +	    !(typec->data_status[1] & USB2_CONNECTION))
> +		role = USB_ROLE_NONE;
> +	else if (typec->data_status[1] & USB_DATA_ROLE)
> +		role = USB_ROLE_DEVICE;
> +	else
> +		role = USB_ROLE_HOST;
> +
> +	if (typec->data_status[2] & DEBUG_ACCESSORY_ATTACHED)
> +		dev_err(typec->dev,
> +			"debug accessory mode not supported, try another usb cable\n");
> +
> +	/*
> +	 * Update the partner before building the mux state so that the DP
> +	 * alternate mode handed to the mux is always a registered one.
> +	 * Debug accessories are treated as not connected, since they are
> +	 * not supported.
> +	 */
> +	connected = !(typec->data_status[2] & DEBUG_ACCESSORY_ATTACHED) &&
> +		    ((typec->data_status[1] & (USB2_CONNECTION | USB3_2_CONNECTION)) ||
> +		     (typec->data_status[2] & DP_CONNECTION));
> +	if (!connected) {
> +		if (typec->partner) {
> +			dev_info(typec->dev, "typec port(%d): unregister partner\n",
> +				 typec->id);
> +			typec_unregister_altmode(typec->partner_altmode);
> +			typec->partner_altmode = NULL;
> +			typec_unregister_partner(typec->partner);
> +			typec->partner = NULL;
> +		}
> +	} else {
> +		if (!typec->partner) {
> +			dev_info(typec->dev, "typec port(%d): register partner\n",
> +				 typec->id);
> +			typec->partner = typec_register_partner(typec->port,
> +								&typec->desc);
> +			if (IS_ERR(typec->partner)) {
> +				dev_err(typec->dev,
> +					"typec port(%d): failed to register partner: %ld\n",
> +					typec->id, PTR_ERR(typec->partner));
> +				typec->partner = NULL;
> +			}
> +		}
> +
> +		if (typec->partner && !typec->partner_altmode) {
> +			typec->partner_altmode =
> +				typec_partner_register_altmode(typec->partner,
> +							       &cix_pd_dp_altmode);
> +			if (IS_ERR(typec->partner_altmode)) {
> +				dev_err(typec->dev,
> +					"typec port(%d): failed to register DP altmode: %ld\n",
> +					typec->id,
> +					PTR_ERR(typec->partner_altmode));
> +				typec->partner_altmode = NULL;
> +			}
> +		}
> +	}

[Severity: Medium]
Should typec->partner_altmode only be registered when DP_CONNECTION is
set?

Calling typec_partner_register_altmode() for every connected partner
exposes a DisplayPort alternate mode in sysfs even for plain USB 2.0 or
USB 3.2 devices.  In addition, cix_pd_dp_altmode leaves .vdo = 0 and the
registration runs before typec_set_data_role(), so when
CONFIG_TYPEC_DP_ALTMODE is enabled, dp_altmode_probe() fails with -EPROTO
or -ENODEV on every attach.  typec_altmode_update_active() is also never
called on typec->partner_altmode, and cix_pd_typec_parse_port_props() does
not set cap->no_mode_control = true even though firmware manages alternate
modes autonomously.

> +	if ((typec->data_status[1] & USB3_2_CONNECTION) &&
> +	    (typec->data_status[2] & DP_CONNECTION)) {
> +		/* usb + dp */
> +		dp_data->status = DP_STATUS_ENABLED;
> +		if (typec->data_status[2] & HPD_STATE)
> +			dp_data->status |= DP_STATUS_HPD_STATE;
> +		if (typec->data_status[2] & HPD_IRQ)
> +			dp_data->status |= DP_STATUS_IRQ_HPD;
> +
> +		typec->state.data = dp_data;
> +		typec->state.mode = TYPEC_DP_STATE_D;
> +		typec->state.alt = typec->partner_altmode;
> +	} else if (typec->data_status[2] & DP_CONNECTION) {
> +		/* dp only */
> +		dp_data->status = DP_STATUS_ENABLED;
> +		if (typec->data_status[2] & HPD_STATE)
> +			dp_data->status |= DP_STATUS_HPD_STATE;
> +		if (typec->data_status[2] & HPD_IRQ)
> +			dp_data->status |= DP_STATUS_IRQ_HPD;
> +		typec->state.data = dp_data;
> +		typec->state.mode = TYPEC_DP_STATE_E;
> +		typec->state.alt = typec->partner_altmode;

[Severity: Medium]
Can this pass a NULL state.alt to typec_mux_set() when state.mode is
TYPEC_DP_STATE_D or TYPEC_DP_STATE_E?

If typec_register_partner() or typec_partner_register_altmode() fails, or
if DEBUG_ACCESSORY_ATTACHED is set alongside DP_CONNECTION,
typec->partner_altmode is NULL, but execution continues into the
DP_CONNECTION branches and calls typec_mux_set() with a modal state and
state.alt == NULL, which mux drivers dereference directly (e.g.
state->alt->svid).  DEBUG_ACCESSORY_ATTACHED is also ignored when
computing orientation, role, and mux state above, despite the comment
stating that debug accessories are treated as not connected.

> +	} else if (typec->data_status[1] & USB3_2_CONNECTION) {
> +		/* usb only */
> +		dp_data->status = 0;
> +		typec->state.mode = TYPEC_STATE_USB;
> +		typec->state.alt = NULL;
> +		typec->state.data = NULL;
> +	} else {
> +		/* disconnect */
> +		dp_data->status = 0;
> +		typec->state.mode = TYPEC_STATE_SAFE;
> +		typec->state.alt = NULL;
> +		typec->state.data = NULL;
> +	}

[Severity: Medium]
Does cix_pd_typec_port_update() mishandle USB 2.0-only connections in the
orientation and mux state checks?

When USB2_CONNECTION is set without USB3_2_CONNECTION or DP_CONNECTION,
connected is true and a partner is registered, but the orientation check
at the top of the function sets TYPEC_ORIENTATION_NONE and the mux check
here falls into the disconnect branch (TYPEC_STATE_SAFE) and logs
"disconnect state" on attach.

> +	ret = typec_mux_set(typec->mux, &typec->state);
> +	if (ret)
> +		return ret;
> +
> +	if ((typec->data_status[3] & POWER_MASK) == POWER_SOURCE) {
> +		typec_set_pwr_role(typec->port, TYPEC_SOURCE);
> +	} else if ((typec->data_status[3] & POWER_MASK) == POWER_SINK) {
> +		typec_set_pwr_role(typec->port, TYPEC_SINK);
> +	} else {
> +		dev_dbg(typec->dev, "typec port(%d): unknown power state %d\n",
> +			typec->id, typec->data_status[3]);
> +	}
> +
> +	if (typec->role != role) {
> +		typec->role = role;
> +		ret = usb_role_switch_set_role(typec->role_sw, role);
> +		if (role == USB_ROLE_DEVICE)
> +			typec_set_data_role(typec->port, TYPEC_DEVICE);
> +		else if (role == USB_ROLE_HOST)
> +			typec_set_data_role(typec->port, TYPEC_HOST);
> +	}

[Severity: Medium]
If usb_role_switch_set_role() fails, does caching typec->role = role first
prevent later status updates from retrying the role switch?  It also calls
typec_set_data_role() despite the failure, and during hibernation restore
(cix_pd_restore() -> cix_pd_event_handler()), usb_role_switch_set_role()
will be skipped if the post-hibernation role matches the pre-hibernation
typec->role.

[ ... ]

> +static int cix_pd_typec_parse_port_props(struct typec_capability *cap,
> +					 struct fwnode_handle *fwnode,
> +					 struct device *dev)
> +{
> +	const char *buf;
> +	int ret;
> +
> +	memset(cap, 0, sizeof(*cap));
> +	ret = fwnode_property_read_string(fwnode, "power-role", &buf);
> +	if (ret) {
> +		dev_err(dev, "power-role not found: %d\n", ret);
> +		return ret;
> +	}
> +	ret = typec_find_port_power_role(buf);
> +	if (ret < 0)
> +		return ret;
> +	cap->type = ret;
> +	ret = fwnode_property_read_string(fwnode, "data-role", &buf);
> +	if (ret) {
> +		dev_err(dev, "data-role not found: %d\n", ret);
> +		return ret;
> +	}
> +	ret = typec_find_port_data_role(buf);
> +	if (ret < 0)
> +		return ret;
> +	cap->data = ret;
> +	ret = fwnode_property_read_string(fwnode, "try-power-role", &buf);
> +	if (ret) {
> +		dev_err(dev, "try-power-role not found: %d\n", ret);
> +		return ret;
> +	}
> +	ret = typec_find_power_role(buf);
> +	if (ret < 0)
> +		return ret;
> +	cap->prefer_role = ret;
> +	cap->fwnode = fwnode_handle_get(fwnode);
> +	cap->orientation_aware = 1;
> +	return 0;
> +}

[Severity: Low]
Could this use the exported typec_get_fw_cap() helper instead of
open-coding the fwnode property parsing?  cix_pd_typec_parse_port_props()
also requires "try-power-role" and "data-role" unconditionally, whereas
typec_get_fw_cap() and usb-connector.yaml treat "data-role" as optional
and only parse "try-power-role" for DRP ports.

[ ... ]

> +static void cix_pd_unregister_ports(struct cix_pd *typec)
> +{
> +	if (!IS_ERR_OR_NULL(typec->port)) {
> +		typec->state.alt = NULL;
> +		typec->state.mode = TYPEC_STATE_USB;
> +		typec->state.data = NULL;
> +		usb_role_switch_set_role(typec->role_sw, USB_ROLE_NONE);
> +		typec_set_orientation(typec->port, TYPEC_ORIENTATION_NONE);
> +		typec_mux_set(typec->mux, &typec->state);
[ ... ]
> +static int cix_pd_init_ports(struct cix_pd *typec)
> +{
[ ... ]
> +		typec->port = typec_register_port(dev, &typec->caps);
> +		if (IS_ERR(typec->port)) {
> +			ret = PTR_ERR(typec->port);
> +			typec->port = NULL;
> +			dev_err(dev, "Failed to register port: %d\n", ret);
> +			goto unregister_ports;
> +		}
> +
> +		desc.roles = typec->caps.data;
> +		typec->port_altmode = typec_port_register_altmode(typec->port,
> +								  &desc);
> +		if (IS_ERR(typec->port_altmode)) {
> +			ret = PTR_ERR(typec->port_altmode);
> +			typec->port_altmode = NULL;
> +			dev_err(dev, "Failed to register DP altmode: %d\n", ret);
> +			goto unregister_ports;
> +		}
> +
> +		ret = cix_pd_typec_get_handles(typec, fwnode, dev);
> +		if (ret)
> +			goto unregister_ports;
> +	}

[Severity: Low]
Should cix_pd_typec_get_handles() be called before typec_register_port(),
and should the port and altmode registration errors use dev_err_probe()?

typec_register_port() and typec_port_register_altmode() can return
ERR_PTR(-EPROBE_DEFER), so dev_err() logs error messages on deferred
probe, and deferring in cix_pd_typec_get_handles() after registering the
port causes unnecessary sysfs registration and removal churn.  Also, in
cix_pd_unregister_ports(), should typec->state.mode be set to
TYPEC_STATE_SAFE rather than TYPEC_STATE_USB when disconnecting the mux?

[ ... ]

> +static void cix_pd_sync_irq(struct cix_pd *rts)
> +{
> +	bool disable = rts->pd_int_disabled || rts->suspended;
> +
> +	if (!rts->irq_requested || rts->irq_disabled == disable)
> +		return;
> +
> +	rts->irq_disabled = disable;
> +	if (disable)
> +		disable_irq(rts->irq);
> +	else
> +		enable_irq(rts->irq);
> +}
[ ... ]
> +DEVICE_ATTR_RW(disable_pd_interrupt);

[Severity: Low]
Should DEVICE_ATTR_RW(disable_pd_interrupt) be declared static so
dev_attr_disable_pd_interrupt does not pollute the global namespace or
trigger a -Wmissing-variable-declarations warning?

Also, this sysfs attribute is undocumented in Documentation/ABI/, is
created via device_create_file() after device registration instead of
.dev_groups (or debugfs for a compliance-test knob), and its show output
("enabled" / "disabled") is rejected with -EINVAL by kstrtobool() in
disable_pd_interrupt_store().

> +static int cix_pd_probe(struct i2c_client *client)
> +{
[ ... ]
> +	if (client->irq > 0) {
> +		irq_set_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
> +		ret = request_threaded_irq(client->irq, NULL, cix_pd_irq_handle,
> +					   IRQF_SHARED | IRQF_ONESHOT,
> +					   dev_name(&client->dev), rts);
> +		if (ret) {
> +			dev_err(&client->dev, "failed to request irq: %d\n", ret);
> +			goto err_unregister_ports;
> +		}
> +		rts->irq_requested = true;
> +	}
[ ... ]
> +static void cix_pd_remove(struct i2c_client *client)
> +{
> +	struct cix_pd *rts = i2c_get_clientdata(client);
> +
> +	device_remove_file(&client->dev, &dev_attr_disable_pd_interrupt);
> +
> +	mutex_lock(&rts->irq_lock);
> +	if (rts->irq_requested) {
> +		rts->suspended = true;
> +		cix_pd_sync_irq(rts);
> +		if (rts->wake_enabled) {
> +			disable_irq_wake(rts->irq);
> +			rts->wake_enabled = false;
> +		}
> +		free_irq(rts->irq, rts);
> +		rts->irq_requested = false;
> +	}
> +	mutex_unlock(&rts->irq_lock);
> +
> +	cix_pd_unregister_ports(rts);
> +}

[Severity: High]
Does cix_pd_remove() leave the shared IRQ permanently disabled for all
other controller instances sharing client->irq?

Because the interrupt is requested with IRQF_SHARED, calling
cix_pd_sync_irq() in cix_pd_remove() (or earlier via
disable_pd_interrupt_store()) calls disable_irq(rts->irq), which
increments desc->depth on the shared irq_desc, and then free_irq() is
called without a matching enable_irq().  When another instance remains
registered on the shared line, __free_irq() does not call irq_shutdown(),
leaving desc->depth elevated and the hardware interrupt masked forever for
sibling ports.

More generally, calling disable_irq() in cix_pd_sync_irq() and setting
IRQ_DISABLE_UNLAZY on a shared line affects all sibling instances:
disabling interrupts on one port via sysfs masks the line for all ports,
and in cix_pd_suspend() calling disable_irq() with IRQ_DISABLE_UNLAZY
masks the hardware IRQ before enable_irq_wake() (and increments
desc->depth once per instance, so suspend_device_irq() cannot re-enable it
when two instances share the line).  Also, irq_clear_status_flags() is not
called if request_threaded_irq() fails in cix_pd_probe(), and
cix_pd_irq_handle() unconditionally acknowledges the interrupt and returns
IRQ_HANDLED without checking whether that instance actually asserted the
shared interrupt.

> +static const struct of_device_id cix_pd_of_match[] = {
> +	{ .compatible = "realtek,rts5453h", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, cix_pd_of_match);
> +
> +static const struct i2c_device_id cix_pd_id[] = {
> +	{ "rts5453h" },
> +	{}
> +};

[Severity: Low]
This isn't a bug, but I2C subsystem conventions prefer named initializers
for new struct i2c_device_id entries (i.e. { .name = "rts5453h" }).

[ ... ]

> +static int __maybe_unused cix_pd_restore(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct cix_pd *rts = i2c_get_clientdata(client);
> +	int ret;
> +
> +	ret = cix_pd_resume(dev);
> +	if (ret)
> +		return ret;
> +
> +	/* Re-trigger PD event if PD event occurs during std */
> +	ret = cix_pd_event_handler(rts, 0);
> +	if (ret)
> +		dev_err(&client->dev, "typec event handling error %d\n", ret);
> +
> +	return ret;
> +}
[ ... ]
> +static const struct dev_pm_ops cix_pd_pm_ops = {
> +	.suspend = pm_sleep_ptr(cix_pd_suspend),
> +	.resume = pm_sleep_ptr(cix_pd_resume),
> +	.freeze = pm_sleep_ptr(cix_pd_suspend),
> +	.thaw = pm_sleep_ptr(cix_pd_resume),
> +	.restore = pm_sleep_ptr(cix_pd_restore),
> +};

[Severity: Medium]
Is cix_pd_pm_ops missing a .poweroff callback for hibernation?

During hibernation, the PM core runs .freeze, creates the image, runs
.thaw (cix_pd_resume(), which clears rts->suspended, disables wake, and
re-enables the IRQ), and then runs .poweroff; without .poweroff, the IRQ
is left enabled and wakeup is not armed when powering off.  In addition,
cix_pd_restore() returns the internal -ESHUTDOWN error code from
cix_pd_event_handler() directly to the PM core instead of translating it
to -EIO as cix_pd_probe() does, and cix_pd_resume() does not call
cix_pd_event_handler() to refresh port state after S3 suspend.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261107011012.3655873-1-gary.yang@cixtech.com?part=2

  reply	other threads:[~2026-11-07  1:57 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07  1:10 [PATCH 0/4] usb: typec: add CIX Type-C PD controller driver Gary Yang
2026-11-07  1:10 ` [PATCH 1/4] dt-bindings: usb: add Realtek RTS5453H Type-C port controller Gary Yang
2026-11-07  1:23   ` sashiko-bot
2026-11-07 10:54   ` Krzysztof Kozlowski
2026-11-07  1:10 ` [PATCH 2/4] usb: typec: add CIX Type-C PD controller driver Gary Yang
2026-11-07  1:57   ` sashiko-bot [this message]
2026-11-07 10:56   ` Krzysztof Kozlowski
2026-11-07 13:11   ` kernel test robot
2026-11-07 18:05   ` kernel test robot
2026-11-07  1:10 ` [PATCH 3/4] arm64: dts: cix: fix I2C bus address and size cells on Sky1 Gary Yang
2026-11-07  1:58   ` sashiko-bot
2026-11-07 10:53   ` Krzysztof Kozlowski
2026-11-07  1:10 ` [PATCH 4/4] arm64: dts: cix: add RTS5453 PD controllers to Orion O6 Gary Yang
2026-11-07  2:03   ` sashiko-bot
2026-11-07 10:53   ` Krzysztof Kozlowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-202469@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gary.yang@cixtech.com \
    --cc=linux-usb@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox