[PATCH v1] tcpm: Honour pSnkStdby requirement during negotiation

From: Badhri Jagan Sridharan
Date: Tue Aug 04 2020 - 02:51:17 EST


>From PD Spec:
The Sink Shall transition to Sink Standby before a positive or
negative voltage transition of VBUS. During Sink Standby
the Sink Shall reduce its power draw to pSnkStdby. This allows
the Source to manage the voltage transition as well as
supply sufficient operating current to the Sink to maintain PD
operation during the transition. The Sink Shall
complete this transition to Sink Standby within tSnkStdby
after evaluating the Accept Message from the Source. The
transition when returning to Sink operation from Sink Standby
Shall be completed within tSnkNewPower. The
pSnkStdby requirement Shall only apply if the Sink power draw
is higher than this level.

The above requirement needs to be met to prevent hard resets
from port partner.

Introducing psnkstdby_after_accept flag to accommodate systems
where the input current limit loops are not fast enough to meet
tSnkStby(15 msec).

When not set, port requests PD_P_SNK_STDBY upon entering SNK_DISCOVERY and
the actual currrent limit after RX of PD_CTRL_PSRDY for PD link,
SNK_READY for non-pd link.

When set, port requests CC advertisement based current limit during
SNK_DISCOVERY, current gets limited to PD_P_SNK_STDBY during
SNK_TRANSITION_SINK, PD based current limit gets set after RX of
PD_CTRL_PSRDY. However, in this case it has to be made sure that the
tSnkStdby (15 msec) is met.

Signed-off-by: Badhri Jagan Sridharan <badhri@xxxxxxxxxx>
---
drivers/usb/typec/tcpm/tcpm.c | 52 +++++++++++++++++++++++++++--------
include/linux/usb/pd.h | 5 +++-
2 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 3ef37202ee37..e46da41940b9 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -293,9 +293,12 @@ struct tcpm_port {
unsigned int operating_snk_mw;
bool update_sink_caps;

- /* Requested current / voltage */
+ /* Set current / voltage */
u32 current_limit;
u32 supply_voltage;
+ /* current / voltage requested to partner */
+ u32 req_current_limit;
+ u32 req_supply_voltage;

/* Used to export TA voltage and current */
struct power_supply *psy;
@@ -323,13 +326,27 @@ struct tcpm_port {
struct pd_mode_data mode_data;
struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX];
struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX];
-
/* Deadline in jiffies to exit src_try_wait state */
unsigned long max_wait;

/* port belongs to a self powered device */
bool self_powered;

+ /*
+ * Honour psnkstdby after accept is received.
+ * However, in this case it has to be made sure that the tSnkStdby (15
+ * msec) is met.
+ *
+ * When not set, port requests PD_P_SNK_STDBY_5V upon entering SNK_DISCOVERY and
+ * the actual currrent limit after RX of PD_CTRL_PSRDY for PD link,
+ * SNK_READY for non-pd link.
+ *
+ * When set, port requests CC advertisement based current limit during
+ * SNK_DISCOVERY, current gets limited to PD_P_SNK_STDBY_5V during SNK_TRANSITION_SINK,
+ * PD based current limit gets set after RX of PD_CTRL_PSRDY.
+ */
+ bool psnkstdby_after_accept;
+
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct mutex logbuffer_lock; /* log buffer access lock */
@@ -1787,9 +1804,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
switch (port->state) {
case SNK_TRANSITION_SINK:
if (port->vbus_present) {
- tcpm_set_current_limit(port,
- port->current_limit,
- port->supply_voltage);
+ tcpm_set_current_limit(port, port->req_current_limit,
+ port->req_supply_voltage);
port->explicit_contract = true;
tcpm_set_state(port, SNK_READY, 0);
} else {
@@ -1861,8 +1877,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
break;
case SNK_NEGOTIATE_PPS_CAPABILITIES:
port->pps_data.active = true;
- port->supply_voltage = port->pps_data.out_volt;
- port->current_limit = port->pps_data.op_curr;
+ port->req_supply_voltage = port->pps_data.out_volt;
+ port->req_current_limit = port->pps_data.op_curr;
tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
break;
case SOFT_RESET_SEND:
@@ -2463,8 +2479,8 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
}

- port->current_limit = ma;
- port->supply_voltage = mv;
+ port->req_current_limit = ma;
+ port->req_supply_voltage = mv;

return 0;
}
@@ -3235,9 +3251,11 @@ static void run_state_machine(struct tcpm_port *port)
break;
case SNK_DISCOVERY:
if (port->vbus_present) {
- tcpm_set_current_limit(port,
- tcpm_get_current_limit(port),
- 5000);
+ if (port->psnkstdby_after_accept || tcpm_get_current_limit(port) <=
+ PD_P_SNK_STDBY_5V)
+ tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000);
+ else
+ tcpm_set_current_limit(port, PD_P_SNK_STDBY_5V, 5000);
tcpm_set_charge(port, true);
tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
break;
@@ -3318,6 +3336,10 @@ static void run_state_machine(struct tcpm_port *port)
}
break;
case SNK_TRANSITION_SINK:
+ if (port->psnkstdby_after_accept)
+ tcpm_set_current_limit(port, tcpm_get_current_limit(port) >
+ PD_P_SNK_STDBY_5V ? PD_P_SNK_STDBY_5V :
+ tcpm_get_current_limit(port), 5000);
case SNK_TRANSITION_SINK_VBUS:
tcpm_set_state(port, hard_reset_state(port),
PD_T_PS_TRANSITION);
@@ -3331,6 +3353,10 @@ static void run_state_machine(struct tcpm_port *port)
port->pwr_opmode = TYPEC_PWR_MODE_PD;
}

+ /* Set current limit for NON-PD link when psnkstdby_after_accept is not set*/
+ if (!port->pd_capable && !port->psnkstdby_after_accept)
+ tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000);
+
tcpm_swap_complete(port, 0);
tcpm_typec_connect(port);
tcpm_check_send_discover(port);
@@ -4513,6 +4539,8 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
port->typec_caps.type = ret;
port->port_type = port->typec_caps.type;

+ port->psnkstdby_after_accept = fwnode_property_read_bool(fwnode, "psnkstdby-after-accept");
+
if (port->port_type == TYPEC_PORT_SNK)
goto sink;

diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index b6c233e79bd4..6bd70f77045e 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -483,5 +483,8 @@ static inline unsigned int rdo_max_power(u32 rdo)
#define PD_N_CAPS_COUNT (PD_T_NO_RESPONSE / PD_T_SEND_SOURCE_CAP)
#define PD_N_HARD_RESET_COUNT 2

-#define PD_T_BIST_CONT_MODE 50 /* 30 - 60 ms */
+#define PD_T_BIST_CONT_MODE 50 /* 30 - 60 ms */
+
+#define PD_P_SNK_STDBY_5V 500 /* 2500 mw - 500mA @ 5V */
+
#endif /* __LINUX_USB_PD_H */
--
2.28.0.163.g6104cc2f0b6-goog