From e0e623d023f4781e93cdca1c3774723fe989e520 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Thu, 28 May 2020 14:23:54 +0200 Subject: [PATCH 09/22] usb: typec: tcpm: add hacky generic altmode support This is a hack and it is based on extcon. Do not try to mainline unless you are in need for some retroactive abortion by the maintainers. Signed-off-by: Tobias Schramm --- drivers/usb/typec/tcpm/tcpm.c | 139 +++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index a6fae1f86505..2908771f4d4e 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -369,6 +370,11 @@ struct tcpm_port { /* Sink caps have been queried */ bool sink_cap_done; +#ifdef CONFIG_EXTCON + struct extcon_dev *extcon; + unsigned int *extcon_cables; +#endif + #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -654,6 +660,35 @@ static void tcpm_debugfs_exit(const struct tcpm_port *port) { } #endif +static void tcpm_update_extcon_data(struct tcpm_port *port, bool attached) { +#ifdef CONFIG_EXTCON + unsigned int *capability = port->extcon_cables; + if (port->data_role == TYPEC_HOST) { + extcon_set_state(port->extcon, EXTCON_USB, false); + extcon_set_state(port->extcon, EXTCON_USB_HOST, attached); + } else { + extcon_set_state(port->extcon, EXTCON_USB, true); + extcon_set_state(port->extcon, EXTCON_USB_HOST, attached); + } + while (*capability != EXTCON_NONE) { + if (attached) { + union extcon_property_value val; + val.intval = (port->polarity == TYPEC_POLARITY_CC2); + extcon_set_property(port->extcon, *capability, + EXTCON_PROP_USB_TYPEC_POLARITY, val); + } else { + extcon_set_state(port->extcon, *capability, false); + } + extcon_sync(port->extcon, *capability); + capability++; + } + tcpm_log(port, "Extcon update (%s): %s, %s", + attached ? "attached" : "detached", + port->data_role == TYPEC_HOST ? "host" : "device", + port->polarity == TYPEC_POLARITY_CC1 ? "normal" : "flipped"); +#endif +} + static int tcpm_pd_transmit(struct tcpm_port *port, enum tcpm_transmit_type type, const struct pd_message *msg) @@ -881,6 +916,8 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached, typec_set_data_role(port->typec_port, data); typec_set_pwr_role(port->typec_port, role); + tcpm_update_extcon_data(port, attached); + return 0; } @@ -1132,7 +1169,7 @@ static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt) paltmode->mode = i; paltmode->vdo = p[i]; - tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x", + tcpm_log(port, "Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x", pmdata->altmodes, paltmode->svid, paltmode->mode, paltmode->vdo); @@ -1154,6 +1191,9 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) modep->altmode_desc[i].svid); altmode = NULL; } + else + tcpm_log(port, "Registered altmode 0x%04x", modep->altmode_desc[i].svid); + port->partner_altmode[i] = altmode; } } @@ -1249,9 +1289,11 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, modep->svid_index++; if (modep->svid_index < modep->nsvids) { u16 svid = modep->svids[modep->svid_index]; + tcpm_log(port, "More modes available, sending discover"); response[0] = VDO(svid, 1, CMD_DISCOVER_MODES); rlen = 1; } else { + tcpm_log(port, "Got all patner modes, registering"); tcpm_register_partner_altmodes(port); } break; @@ -2836,6 +2878,7 @@ static int tcpm_src_attach(struct tcpm_port *port) static void tcpm_typec_disconnect(struct tcpm_port *port) { if (port->connected) { + tcpm_update_extcon_data(port, false); typec_unregister_partner(port->partner); port->partner = NULL; port->connected = false; @@ -2902,6 +2945,8 @@ static void tcpm_detach(struct tcpm_port *port) } tcpm_reset_port(port); + + tcpm_update_extcon_data(port, false); } static void tcpm_src_detach(struct tcpm_port *port) @@ -4732,6 +4777,64 @@ void tcpm_tcpc_reset(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); +unsigned int default_supported_cables[] = { + EXTCON_NONE +}; + +static int tcpm_fw_get_caps_late(struct tcpm_port *port, + struct fwnode_handle *fwnode) +{ + int ret, i; + ret = fwnode_property_count_u32(fwnode, "typec-altmodes"); + if (ret > 0) { + u32 *props; + if (ret % 4) { + dev_err(port->dev, "Length of typec altmode array must be divisible by 4"); + return -EINVAL; + } + + props = devm_kzalloc(port->dev, sizeof(u32) * ret, GFP_KERNEL); + if (!props) { + dev_err(port->dev, "Failed to allocate memory for altmode properties"); + return -ENOMEM; + } + + if(fwnode_property_read_u32_array(fwnode, "typec-altmodes", props, ret) < 0) { + dev_err(port->dev, "Failed to read altmodes from port"); + return -EINVAL; + } + + i = 0; + while (ret > 0 && i < ARRAY_SIZE(port->port_altmode)) { + struct typec_altmode *alt; + struct typec_altmode_desc alt_desc = { + .svid = props[i * 4], + .mode = props[i * 4 + 1], + .vdo = props[i * 4 + 2], + .roles = props[i * 4 + 3], + }; + + + tcpm_log(port, "Adding altmode SVID: 0x%04x, mode: %d, vdo: %u, role: %d", + alt_desc.svid, alt_desc.mode, alt_desc.vdo, alt_desc.roles); + alt = typec_port_register_altmode(port->typec_port, + &alt_desc); + if (IS_ERR(alt)) { + tcpm_log(port, + "%s: failed to register port alternate mode 0x%x", + dev_name(port->dev), alt_desc.svid); + break; + } + typec_altmode_set_drvdata(alt, port); + alt->ops = &tcpm_altmode_ops; + port->port_altmode[i] = alt; + i++; + ret -= 4; + } + } + return 0; +} + static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { @@ -4742,6 +4845,23 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, if (!fwnode) return -EINVAL; +#ifdef CONFIG_EXTCON + ret = fwnode_property_count_u32(fwnode, "extcon-cables"); + if (ret > 0) { + port->extcon_cables = devm_kzalloc(port->dev, sizeof(u32) * ret, GFP_KERNEL); + if (!port->extcon_cables) { + dev_err(port->dev, "Failed to allocate memory for extcon cable types. "\ + "Using default tyes"); + goto extcon_default; + } + fwnode_property_read_u32_array(fwnode, "extcon-cables", port->extcon_cables, ret); + } else { +extcon_default: + dev_info(port->dev, "No cable types defined, using default cables"); + port->extcon_cables = default_supported_cables; + } +#endif + /* USB data support is optional */ ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); if (ret == 0) { @@ -5114,6 +5234,17 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_destroy_wq; port->try_role = port->typec_caps.prefer_role; +#ifdef CONFIG_EXTCON + port->extcon = devm_extcon_dev_allocate(dev, port->extcon_cables); + if (IS_ERR(port->extcon)) { + dev_err(dev, "Failed to allocate extcon device: %ld", PTR_ERR(port->extcon)); + goto out_destroy_wq; + } + if((err = devm_extcon_dev_register(dev, port->extcon))) { + dev_err(dev, "Failed to register extcon device: %d", err); + goto out_destroy_wq; + } +#endif port->typec_caps.fwnode = tcpc->fwnode; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ @@ -5141,6 +5272,12 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_role_sw_put; } + err = tcpm_fw_get_caps_late(port, tcpc->fwnode); + if (err < 0) { + dev_err(dev, "Failed to get altmodes from fwnode"); + goto out_destroy_wq; + } + mutex_lock(&port->lock); tcpm_init(port); mutex_unlock(&port->lock); -- 2.30.0