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
next prev parent reply other threads:[~2026-11-07 1:57 UTC|newest]
Thread overview: 18+ 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-09 5:43 ` Gary Yang
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
2026-11-09 2:07 ` Gary Yang
2026-11-09 7:29 ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.