@@ -1706,6 +1706,25 @@ static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload,
}
}
+static int tcpm_set_auto_vbus_discharge_threshold(struct tcpm_port *port, enum typec_role port_role,
+ enum typec_pwr_opmode mode, bool pps_active,
+ u32 requested_vbus_voltage)
+{
+ int ret;
+
+ if (!port->tcpc->set_auto_vbus_discharge_threshold)
+ return 0;
+
+ ret = port->tcpc->set_auto_vbus_discharge_threshold(port->tcpc, port_role, mode, pps_active,
+ requested_vbus_voltage);
+ tcpm_log_force(port,
+ "set_auto_vbus_discharge_threshold pwr_role:%s mode:%d pps_active:%c vbus:%u ret:%d",
+ port_role == TYPEC_SINK ? "sink" : "source", mode, pps_active ? 'y' : 'n',
+ requested_vbus_voltage, ret);
+
+ return ret;
+}
+
static void tcpm_pd_data_request(struct tcpm_port *port,
const struct pd_message *msg)
{
@@ -1875,6 +1894,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
port->current_limit,
port->supply_voltage);
port->explicit_contract = true;
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SINK,
+ TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
tcpm_set_state(port, SNK_READY, 0);
} else {
/*
@@ -2789,8 +2812,14 @@ static int tcpm_src_attach(struct tcpm_port *port)
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SOURCE,
- tcpm_data_role_for_source(port));
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SOURCE, TYPEC_PWR_MODE_USB,
+ false, VSAFE5V);
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true);
+ tcpm_log_force(port, "enable vbus discharge ret:%d", ret);
+ }
+
+ ret = tcpm_set_roles(port, true, TYPEC_SOURCE, tcpm_data_role_for_source(port));
if (ret < 0)
return ret;
@@ -2857,6 +2886,12 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port)
static void tcpm_reset_port(struct tcpm_port *port)
{
+ int ret;
+
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, false);
+ tcpm_log_force(port, "Disable vbus discharge ret:%d", ret);
+ }
tcpm_unregister_altmodes(port);
tcpm_typec_disconnect(port);
port->attached = false;
@@ -2921,8 +2956,14 @@ static int tcpm_snk_attach(struct tcpm_port *port)
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SINK,
- tcpm_data_role_for_sink(port));
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SINK, TYPEC_PWR_MODE_USB, false,
+ VSAFE5V);
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true);
+ tcpm_log_force(port, "enable vbus discharge ret:%d", ret);
+ }
+
+ ret = tcpm_set_roles(port, true, TYPEC_SINK, tcpm_data_role_for_sink(port));
if (ret < 0)
return ret;
@@ -3482,6 +3523,8 @@ static void run_state_machine(struct tcpm_port *port)
* drive VBUS to vSafe0V as shown in Figure 7-9.
*/
tcpm_set_vconn(port, false);
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SOURCE, TYPEC_PWR_MODE_USB,
+ false, 0);
tcpm_set_vbus(port, false);
tcpm_set_roles(port, port->self_powered, TYPEC_SOURCE,
tcpm_data_role_for_source(port));
@@ -3503,9 +3546,13 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_vbus(port, true);
port->tcpc->set_pd_rx(port->tcpc, true);
tcpm_set_attached_state(port, true);
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SOURCE, TYPEC_PWR_MODE_USB,
+ false, VSAFE5V);
tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
break;
case SNK_HARD_RESET_SINK_OFF:
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SINK, TYPEC_PWR_MODE_USB, false,
+ 0);
memset(&port->pps_data, 0, sizeof(port->pps_data));
tcpm_set_vconn(port, false);
if (port->pd_capable)
@@ -3548,6 +3595,8 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_charge(port, true);
}
tcpm_set_attached_state(port, true);
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SINK, TYPEC_PWR_MODE_USB, false,
+ VSAFE5V);
tcpm_set_state(port, SNK_STARTUP, 0);
break;
@@ -3649,6 +3698,12 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0);
break;
case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+ /*
+ * Prevent vbus discharge circuit from turning on during PR_SWAP
+ * as this is not a disconnect.
+ */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SOURCE, TYPEC_PWR_MODE_USB,
+ port->pps_data.active, 0);
tcpm_set_vbus(port, false);
port->explicit_contract = false;
/* allow time for Vbus discharge, must be < tSrcSwapStdby */
@@ -3677,9 +3732,18 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
break;
case PR_SWAP_SRC_SNK_SINK_ON:
+ /* Set the vbus disconnect threshold for implicit contract */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SINK, TYPEC_PWR_MODE_USB, false,
+ VSAFE5V);
tcpm_set_state(port, SNK_STARTUP, 0);
break;
case PR_SWAP_SNK_SRC_SINK_OFF:
+ /*
+ * Prevent vbus discharge circuit from turning on during PR_SWAP
+ * as this is not a disconnect.
+ */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SINK, TYPEC_PWR_MODE_USB,
+ port->pps_data.active, 0);
tcpm_set_charge(port, false);
tcpm_set_state(port, hard_reset_state(port),
PD_T_PS_SOURCE_OFF);
@@ -3705,6 +3769,8 @@ static void run_state_machine(struct tcpm_port *port)
*/
tcpm_set_pwr_role(port, TYPEC_SOURCE);
tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_SOURCE, TYPEC_PWR_MODE_USB,
+ false, VSAFE5V);
tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START);
break;
@@ -83,6 +83,18 @@ enum tcpm_transmit_type {
* Optional; Called to enable/disable PD 3.0 fast role swap.
* Enabling frs is accessory dependent as not all PD3.0
* accessories support fast role swap.
+ * @enable_auto_vbus_discharge:
+ * Optional; TCPCI spec based TCPC implementations can optionally
+ * support hardware to autonomously dischrge vbus upon disconnecting
+ * as sink or source. TCPM signals TCPC to enable the mechanism upon
+ * entering connected state and signals disabling upon disconnect.
+ * @set_auto_vbus_discharge_threshold:
+ * Mandatory when enable_auto_vbus_discharge is implemented. TCPM
+ * calls this function to allow lower levels drivers to program the
+ * vbus threshold voltage below which the vbus discharge circuit
+ * will be turned on. requested_vbus_voltage is set to 0 when vbus
+ * is going to disappear knowingly i.e. during PR_SWAP and
+ * HARD_RESET etc.
*/
struct tcpc_dev {
struct fwnode_handle *fwnode;
@@ -109,6 +121,10 @@ struct tcpc_dev {
const struct pd_message *msg);
int (*set_bist_data)(struct tcpc_dev *dev, bool on);
int (*enable_frs)(struct tcpc_dev *dev, bool enable);
+ int (*enable_auto_vbus_discharge)(struct tcpc_dev *dev, bool enable);
+ int (*set_auto_vbus_discharge_threshold)(struct tcpc_dev *dev, enum typec_role port_role,
+ enum typec_pwr_opmode mode, bool pps_active,
+ u32 requested_vbus_voltage);
};
struct tcpm_port;
TCPCI spec allows TCPC hardware to autonomously discharge the vbus capacitance upon disconnect. The expectation is that the TCPM enables AutoDischargeDisconnect while entering SNK/SRC_ATTACHED states. Hardware then automously discharges vbus when the vbus falls below a certain threshold i.e. VBUS_SINK_DISCONNECT_THRESHOLD. Apart from enabling the vbus discharge circuit, AutoDischargeDisconnect is also used a flag to move TCPCI based TCPC implementations into Attached.Snk/Attached.Src state as mentioned in Figure 4-15. TCPC State Diagram before a Connection of the USB Type-C Port Controller Interface Specification. In such TCPC implementations, setting AutoDischargeDisconnect would prevent TCPC into entering "Connection_Invalid" state as well. Signed-off-by: Badhri Jagan Sridharan <badhri@google.com> --- Changes since v1: - Changing patch version to v6 to fix version number confusion. Changes since v6: - Fixed incorrect data_role error that I introduced by mistake in the previous version. Changes since v7: - Rebase on usb-next --- drivers/usb/typec/tcpm/tcpm.c | 74 +++++++++++++++++++++++++++++++++-- include/linux/usb/tcpm.h | 16 ++++++++ 2 files changed, 86 insertions(+), 4 deletions(-)