From d769aaf5b389a4d6ebd4687cc50413b83634cee8 Mon Sep 17 00:00:00 2001 From: Ryan <81541646+RyanTheTide@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:28:13 +1100 Subject: [PATCH] usb: typec: tipd: reconcile firmware-entered J616s DP mode Publish the entered partner DisplayPort alternate mode, consume SPMI events latched before IRQ registration, expose pin E, and provide the connector fwnode fallback. The tether route gate follow-up belongs with #451 and is included in the batch3 candidate. Assisted-by: LLM --- drivers/usb/typec/tipd/core.c | 46 ++++++++++++++++++++++++++++--- drivers/usb/typec/tipd/spmi.c | 9 ++++-- drivers/usb/typec/tipd/tps6598x.h | 1 + 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index aa630b95dafe9..dd6f7b99e698a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -290,6 +290,11 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) static void tps6598x_drop_partner(struct tps6598x *tps, u32 status) { + if (tps->partner_altmode_dp) { + typec_altmode_update_active(tps->partner_altmode_dp, false); + typec_unregister_altmode(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + } if (!IS_ERR(tps->partner)) typec_unregister_partner(tps->partner); tps->partner = NULL; @@ -1171,6 +1176,11 @@ static void cd321x_update_work(struct work_struct *work) /* Process partner disconnection or change */ if (!new_connected || partner_changed) { + if (tps->partner_altmode_dp) { + typec_altmode_update_active(tps->partner_altmode_dp, false); + typec_unregister_altmode(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + } if (!IS_ERR(tps->partner)) typec_unregister_partner(tps->partner); tps->partner = NULL; @@ -1245,6 +1255,29 @@ static void cd321x_update_work(struct work_struct *work) typec_partner_set_identity(tps->partner); } + /* The CD321x firmware owns VDM discovery and Enter Mode. Publish the + * discovered DP mode only after it reports an entered DP connection; + * keep Linux's DP Alt Mode driver from issuing a second Enter Mode. + */ + if (dp_connected && !tps->partner_altmode_dp) { + struct typec_altmode_desc desc = { + .svid = USB_TYPEC_DP_SID, + .mode = USB_TYPEC_DP_MODE, + .vdo = le32_to_cpu(st.dp_sid_status.mode_data), + .mode_selection = true, + }; + + tps->partner_altmode_dp = typec_partner_register_altmode(tps->partner, + &desc); + if (IS_ERR(tps->partner_altmode_dp)) { + ret = PTR_ERR(tps->partner_altmode_dp); + tps->partner_altmode_dp = NULL; + goto retry; + } + } + if (tps->partner_altmode_dp) + typec_altmode_update_active(tps->partner_altmode_dp, dp_connected); + /* Update the TypeC MUX/PHY state */ if (dp_route_was_active && dp_connected && !was_disconnected && !dp_mode_changed && st.dp_sid_event) { @@ -1997,7 +2030,8 @@ static int cd321x_register_port_altmodes(struct cd321x *cd321x) memset(&desc, 0, sizeof(desc)); desc.svid = USB_TYPEC_DP_SID; desc.mode = USB_TYPEC_DP_MODE; - desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)); + desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | + BIT(DP_PIN_ASSIGN_D) | BIT(DP_PIN_ASSIGN_E)); desc.vdo |= DP_CAP_DFP_D; amode = typec_port_register_altmode(cd321x->tps.port, &desc); if (IS_ERR(amode)) @@ -2111,6 +2145,7 @@ EXPORT_SYMBOL_IF_KUNIT(cd321x_init_work); static int cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { + struct fwnode_handle *connector_fwnode; struct cd321x *cd321x = container_of(tps, struct cd321x, tps); int ret; @@ -2130,9 +2165,12 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) goto err_unregister_altmodes; } - cd321x->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); - if (IS_ERR(cd321x->connector_fwnode)) - cd321x->connector_fwnode = NULL; + if (fwnode_property_present(fwnode, "displayport")) + connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); + else + connector_fwnode = fwnode_handle_get(fwnode); + if (!IS_ERR_OR_NULL(connector_fwnode)) + cd321x->connector_fwnode = connector_fwnode; cd321x->tbt_switch = cd321x_tbt_switch_get(tps->dev, fwnode); if (IS_ERR(cd321x->tbt_switch)) { diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index ba89b4b57a255..4f0ceca25ee7f 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -318,9 +318,14 @@ static int sn201202x_probe(struct spmi_device *device) return ret; ret = tipd_init(tps); - if (ret) + if (ret) { spmi_command_sleep(device); - return ret; + return ret; + } + + /* Events can latch before the IRQ is registered. Consume that state now. */ + tps->data->irq_handler(tps->irq, tps); + return 0; } static void sn201202x_remove(struct spmi_device *device) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index caac3cb771bd4..f6d51724a1f5d 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -371,6 +371,7 @@ struct tps6598x { const struct tipd_data *data; struct tipd_debugfs *debugfs; + struct typec_altmode *partner_altmode_dp; }; /* TPS_REG_USB4_STATUS */