This change allows TCPM to support limit_src_current_set callback. When limit_src_current_set is enabled, tcpm updates the local source capabilities to only publish vSafe5V fixed pdo with the current limit passed through limit_src_current_set callback. When limit_src_current_set is disabled, tcpm revert back to publishing the default source caps. This patch is co-authored with kyletso@xxxxxxxxxx and also uses some of parts of the code that was reverted by c17c7cf147ac56312156eaaaf8b2e19c9a59a71a. Signed-off-by: Badhri Jagan Sridharan <badhri@xxxxxxxxxx> --- drivers/usb/typec/tcpm/tcpm.c | 108 ++++++++++++++++++++++++++++++---- include/linux/usb/tcpm.h | 4 ++ 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 5fce795b69c7..491ad6621671 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -475,6 +475,15 @@ struct tcpm_port { * SNK_READY for non-pd link. */ bool slow_charger_loop; + + /* + * Max current published in vSafe5V fixed pdo when limit_src_current_enabled + * is active. + */ + u32 limit_src_current_ma; + /* True when source current limiting is enabled */ + bool limit_src_current_enabled; + #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -5907,12 +5916,99 @@ static int tcpm_port_type_set(struct typec_port *p, enum typec_port_type type) return 0; } +int tcpm_update_source_capabilities_locked(struct tcpm_port *port) +{ + int ret = 0; + + switch (port->state) { + case SRC_UNATTACHED: + case SRC_ATTACH_WAIT: + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + break; + case SRC_SEND_CAPABILITIES: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + port->upcoming_state = SRC_SEND_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + break; + } + break; + default: + break; + } + return ret; +} + +static int tcpm_fw_get_src_pdo(struct tcpm_port *port, struct fwnode_handle *fwnode, u32 *src_pdo, + unsigned int *nr_src_pdo) +{ + int ret; + + ret = fwnode_property_count_u32(fwnode, "source-pdos"); + if (ret == 0) + return -EINVAL; + else if (ret < 0) + return ret; + + *nr_src_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", src_pdo, *nr_src_pdo); + + return ret; +} + +static int tcpm_limit_src_current_set(struct typec_port *p, u32 limit_src_current_ma, bool enable) +{ + struct tcpm_port *port = typec_get_drvdata(p); + int ret = 0; + + mutex_lock(&port->lock); + if (limit_src_current_ma == port->limit_src_current_ma && + enable == port->limit_src_current_enabled) + goto port_unlock; + + ret = tcpm_fw_get_src_pdo(port, port->tcpc->fwnode, port->src_pdo, &port->nr_src_pdo); + if (ret) + return ret; + + if (enable) { + u32 current_vSafe5V_max_current_ma = + ((port->src_pdo[0] & (PDO_CURR_MASK << PDO_FIXED_CURR_SHIFT)) >> + PDO_FIXED_CURR_SHIFT) * 10; + /* + * Check to see if limited source current does not exceed the + * max current published in default source cap. + */ + if (limit_src_current_ma > current_vSafe5V_max_current_ma) { + ret = -EINVAL; + goto port_unlock; + } + port->src_pdo[0] &= ~(PDO_CURR_MASK << PDO_FIXED_CURR_SHIFT); + port->src_pdo[0] |= PDO_FIXED_CURR(limit_src_current_ma); + port->nr_src_pdo = 1; + } + + ret = tcpm_update_source_capabilities_locked(port); + if (!ret) { + port->limit_src_current_ma = limit_src_current_ma; + port->limit_src_current_enabled = enable; + } + +port_unlock: + mutex_unlock(&port->lock); + return ret; +} + static const struct typec_operations tcpm_ops = { .try_role = tcpm_try_role, .dr_set = tcpm_dr_set, .pr_set = tcpm_pr_set, .vconn_set = tcpm_vconn_set, - .port_type_set = tcpm_port_type_set + .port_type_set = tcpm_port_type_set, + .limit_src_current_set = tcpm_limit_src_current_set }; void tcpm_tcpc_reset(struct tcpm_port *port) @@ -5970,15 +6066,7 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, /* Get Source PDOs for the PD port or Source Rp value for the non-PD port */ if (port->pd_supported) { - ret = fwnode_property_count_u32(fwnode, "source-pdos"); - if (ret == 0) - return -EINVAL; - else if (ret < 0) - return ret; - - port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); - ret = fwnode_property_read_u32_array(fwnode, "source-pdos", - port->src_pdo, port->nr_src_pdo); + ret = tcpm_fw_get_src_pdo(port, fwnode, port->src_pdo, &port->nr_src_pdo); if (ret) return ret; ret = tcpm_validate_caps(port, port->src_pdo, port->nr_src_pdo); diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index bffc8d3e14ad..18372d34a9f4 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -165,5 +165,9 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port, enum tcpm_transmit_status status); void tcpm_pd_hard_reset(struct tcpm_port *port); void tcpm_tcpc_reset(struct tcpm_port *port); +bool tcpm_is_debouncing(struct tcpm_port *tcpm); +bool tcpm_is_toggling(struct tcpm_port *port); +int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo); #endif /* __LINUX_USB_TCPM_H */ -- 2.35.0.263.gb82422642f-goog