diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 1c64b831bbb533f1a38653ac132eb7d98f0bce28..cedc6cf82d61757fd8bbe487c8586d3e29f62666 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -258,7 +258,19 @@ struct tcpm_port { bool attached; bool connected; enum typec_port_type port_type; + + /* + * Set to true when vbus is greater than VSAFE5V min. + * Set to false when vbus falls below vSinkDisconnect max threshold. + */ bool vbus_present; + + /* + * Set to true when vbus is less than VSAFE0V max. + * Set to false when vbus is greater than VSAFE0V max. + */ + bool vbus_vsafe0v; + bool vbus_never_low; bool vbus_source; bool vbus_charge; @@ -3093,7 +3105,7 @@ static void run_state_machine(struct tcpm_port *port) else if (tcpm_port_is_audio(port)) tcpm_set_state(port, AUDIO_ACC_ATTACHED, PD_T_CC_DEBOUNCE); - else if (tcpm_port_is_source(port)) + else if (tcpm_port_is_source(port) && port->vbus_vsafe0v) tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED, @@ -4096,6 +4108,12 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) { tcpm_log_force(port, "VBUS on"); port->vbus_present = true; + /* + * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly + * states that vbus is not at VSAFE0V, hence clear the vbus_vsafe0v flag here. + */ + port->vbus_vsafe0v = false; + switch (port->state) { case SNK_TRANSITION_SINK_VBUS: port->explicit_contract = true; @@ -4185,16 +4203,8 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) case SNK_HARD_RESET_SINK_OFF: tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0); break; - case SRC_HARD_RESET_VBUS_OFF: - /* - * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait - * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V. - */ - tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); - break; case HARD_RESET_SEND: break; - case SNK_TRY: /* Do nothing, waiting for timeout */ break; @@ -4265,6 +4275,28 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) } } +static void _tcpm_pd_vbus_vsafe0v(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS VSAFE0V"); + port->vbus_vsafe0v = true; + switch (port->state) { + case SRC_HARD_RESET_VBUS_OFF: + /* + * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait + * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V. + */ + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_source(port)) + tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED, + PD_T_CC_DEBOUNCE); + break; + default: + break; + } +} + static void _tcpm_pd_hard_reset(struct tcpm_port *port) { tcpm_log_force(port, "Received hard reset"); @@ -4300,10 +4332,19 @@ static void tcpm_pd_event_handler(struct kthread_work *work) bool vbus; vbus = port->tcpc->get_vbus(port->tcpc); - if (vbus) + if (vbus) { _tcpm_pd_vbus_on(port); - else + } else { _tcpm_pd_vbus_off(port); + /* + * When TCPC does not support detecting vsafe0v voltage level, + * treat vbus absent as vsafe0v. Else invoke is_vbus_vsafe0v + * to see if vbus has discharge to VSAFE0V. + */ + if (!port->tcpc->is_vbus_vsafe0v || + port->tcpc->is_vbus_vsafe0v(port->tcpc)) + _tcpm_pd_vbus_vsafe0v(port); + } } if (events & TCPM_CC_EVENT) { enum typec_cc_status cc1, cc2; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index efaedd7e8a182d23822ee6d7bff8a3ec98bd341c..f4a18427f5c4cbccd24b13d13bbaa71d693694df 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -98,6 +98,12 @@ enum tcpm_transmit_type { * 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. + * @is_vbus_vsafe0v: + * Optional; TCPCI spec based TCPC implementations are expected to + * detect VSAFE0V voltage level at vbus. When detection of VSAFE0V + * is supported by TCPC, set this callback for TCPM to query + * whether vbus is at VSAFE0V when needed. + * Returns true when vbus is at VSAFE0V, false otherwise. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -128,6 +134,7 @@ struct tcpc_dev { int (*enable_auto_vbus_discharge)(struct tcpc_dev *dev, bool enable); int (*set_auto_vbus_discharge_threshold)(struct tcpc_dev *dev, enum typec_pwr_opmode mode, bool pps_active, u32 requested_vbus_voltage); + bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev); }; struct tcpm_port;