[PATCH v1 2/2] usb: typec: tcpm: Enable limit_src_current_set callback

From: Badhri Jagan Sridharan
Date: Mon Feb 07 2022 - 00:54:59 EST


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