Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: typec: tcpm: Add callbacks to mitigate wakeups due to contaminant

On some of the TCPC implementations, when the Type-C port is exposed
to contaminants, such as water, TCPC stops toggling while reporting OPEN
either by the time TCPM reads CC pin status or during CC debounce
window. This causes TCPM to be stuck in TOGGLING state. If TCPM is made
to restart toggling, the behavior recurs causing redundant CPU wakeups
till the USB-C port is free of contaminant.

[206199.287817] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]
[206199.640337] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]
[206199.985789] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]

(or)

[ 7853.867577] Start toggling
[ 7853.889921] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]
[ 7855.698765] CC1: 0 -> 0, CC2: 0 -> 5 [state TOGGLING, polarity 0, connected]
[ 7855.698790] state change TOGGLING -> SNK_ATTACH_WAIT [rev3 NONE_AMS]
[ 7855.698826] pending state change SNK_ATTACH_WAIT -> SNK_DEBOUNCED @ 170 ms [rev3 NONE_AMS]
[ 7855.703559] CC1: 0 -> 0, CC2: 5 -> 5 [state SNK_ATTACH_WAIT, polarity 0, connected]
[ 7855.856555] CC1: 0 -> 0, CC2: 5 -> 0 [state SNK_ATTACH_WAIT, polarity 0, disconnected]
[ 7855.856581] state change SNK_ATTACH_WAIT -> SNK_ATTACH_WAIT [rev3 NONE_AMS]
[ 7855.856613] pending state change SNK_ATTACH_WAIT -> SNK_UNATTACHED @ 170 ms [rev3 NONE_AMS]
[ 7856.027744] state change SNK_ATTACH_WAIT -> SNK_UNATTACHED [delayed 170 ms]
[ 7856.181949] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]
[ 7856.187896] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]
[ 7857.645630] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected]
[ 7857.647291] CC1: 0 -> 0, CC2: 0 -> 5 [state TOGGLING, polarity 0, connected]
[ 7857.647298] state change TOGGLING -> SNK_ATTACH_WAIT [rev3 NONE_AMS]
[ 7857.647310] pending state change SNK_ATTACH_WAIT -> SNK_DEBOUNCED @ 170 ms [rev3 NONE_AMS]
[ 7857.808106] CC1: 0 -> 0, CC2: 5 -> 0 [state SNK_ATTACH_WAIT, polarity 0, disconnected]
[ 7857.808123] state change SNK_ATTACH_WAIT -> SNK_ATTACH_WAIT [rev3 NONE_AMS]
[ 7857.808150] pending state change SNK_ATTACH_WAIT -> SNK_UNATTACHED @ 170 ms [rev3 NONE_AMS]
[ 7857.978727] state change SNK_ATTACH_WAIT -> SNK_UNATTACHED [delayed 170 ms]

To mitigate redundant TCPM wakeups, TCPCs which do have the needed hardware
can implement the check_contaminant callback which is invoked by TCPM
to evaluate for presence of contaminant. Lower level TCPC driver can
restart toggling through TCPM_PORT_CLEAN event when the driver detects
that USB-C port is free of contaminant. check_contaminant callback also
passes the disconnect_while_debounce flag which when true denotes that
the CC pins transitioned to OPEN state during the CC debounce window.

Signed-off-by: Badhri Jagan Sridharan <badhri@google.com>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://lore.kernel.org/r/20230114093246.1933321-1-badhri@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Badhri Jagan Sridharan and committed by
Greg Kroah-Hartman
599f008c 6681e43f

+62 -1
+54 -1
drivers/usb/typec/tcpm/tcpm.c
··· 36 36 #define FOREACH_STATE(S) \ 37 37 S(INVALID_STATE), \ 38 38 S(TOGGLING), \ 39 + S(CHECK_CONTAMINANT), \ 39 40 S(SRC_UNATTACHED), \ 40 41 S(SRC_ATTACH_WAIT), \ 41 42 S(SRC_ATTACHED), \ ··· 250 249 #define TCPM_RESET_EVENT BIT(2) 251 250 #define TCPM_FRS_EVENT BIT(3) 252 251 #define TCPM_SOURCING_VBUS BIT(4) 252 + #define TCPM_PORT_CLEAN BIT(5) 253 253 254 254 #define LOG_BUFFER_ENTRIES 1024 255 255 #define LOG_BUFFER_ENTRY_SIZE 128 ··· 485 483 * SNK_READY for non-pd link. 486 484 */ 487 485 bool slow_charger_loop; 486 + 487 + /* 488 + * When true indicates that the lower level drivers indicate potential presence 489 + * of contaminant in the connector pins based on the tcpm state machine 490 + * transitions. 491 + */ 492 + bool potential_contaminant; 488 493 #ifdef CONFIG_DEBUG_FS 489 494 struct dentry *dentry; 490 495 struct mutex logbuffer_lock; /* log buffer access lock */ ··· 656 647 /* Do not log while disconnected and unattached */ 657 648 if (tcpm_port_is_disconnected(port) && 658 649 (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED || 659 - port->state == TOGGLING)) 650 + port->state == TOGGLING || port->state == CHECK_CONTAMINANT)) 660 651 return; 661 652 662 653 va_start(args, fmt); ··· 3913 3904 unsigned int msecs; 3914 3905 enum tcpm_state upcoming_state; 3915 3906 3907 + if (port->tcpc->check_contaminant && port->state != CHECK_CONTAMINANT) 3908 + port->potential_contaminant = ((port->enter_state == SRC_ATTACH_WAIT && 3909 + port->state == SRC_UNATTACHED) || 3910 + (port->enter_state == SNK_ATTACH_WAIT && 3911 + port->state == SNK_UNATTACHED)); 3912 + 3916 3913 port->enter_state = port->state; 3917 3914 switch (port->state) { 3918 3915 case TOGGLING: 3916 + break; 3917 + case CHECK_CONTAMINANT: 3918 + port->tcpc->check_contaminant(port->tcpc); 3919 3919 break; 3920 3920 /* SRC states */ 3921 3921 case SRC_UNATTACHED: 3922 3922 if (!port->non_pd_role_swap) 3923 3923 tcpm_swap_complete(port, -ENOTCONN); 3924 3924 tcpm_src_detach(port); 3925 + if (port->potential_contaminant) { 3926 + tcpm_set_state(port, CHECK_CONTAMINANT, 0); 3927 + break; 3928 + } 3925 3929 if (tcpm_start_toggling(port, tcpm_rp_cc(port))) { 3926 3930 tcpm_set_state(port, TOGGLING, 0); 3927 3931 break; ··· 4172 4150 tcpm_swap_complete(port, -ENOTCONN); 4173 4151 tcpm_pps_complete(port, -ENOTCONN); 4174 4152 tcpm_snk_detach(port); 4153 + if (port->potential_contaminant) { 4154 + tcpm_set_state(port, CHECK_CONTAMINANT, 0); 4155 + break; 4156 + } 4175 4157 if (tcpm_start_toggling(port, TYPEC_CC_RD)) { 4176 4158 tcpm_set_state(port, TOGGLING, 0); 4177 4159 break; ··· 4952 4926 else if (tcpm_port_is_sink(port)) 4953 4927 tcpm_set_state(port, SNK_ATTACH_WAIT, 0); 4954 4928 break; 4929 + case CHECK_CONTAMINANT: 4930 + /* Wait for Toggling to be resumed */ 4931 + break; 4955 4932 case SRC_UNATTACHED: 4956 4933 case ACC_UNATTACHED: 4957 4934 if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || ··· 5454 5425 port->vbus_source = true; 5455 5426 _tcpm_pd_vbus_on(port); 5456 5427 } 5428 + if (events & TCPM_PORT_CLEAN) { 5429 + tcpm_log(port, "port clean"); 5430 + if (port->state == CHECK_CONTAMINANT) { 5431 + if (tcpm_start_toggling(port, tcpm_rp_cc(port))) 5432 + tcpm_set_state(port, TOGGLING, 0); 5433 + else 5434 + tcpm_set_state(port, tcpm_default_state(port), 0); 5435 + } 5436 + } 5457 5437 5458 5438 spin_lock(&port->pd_event_lock); 5459 5439 } ··· 5514 5476 kthread_queue_work(port->wq, &port->event_work); 5515 5477 } 5516 5478 EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus); 5479 + 5480 + void tcpm_port_clean(struct tcpm_port *port) 5481 + { 5482 + spin_lock(&port->pd_event_lock); 5483 + port->pd_events |= TCPM_PORT_CLEAN; 5484 + spin_unlock(&port->pd_event_lock); 5485 + kthread_queue_work(port->wq, &port->event_work); 5486 + } 5487 + EXPORT_SYMBOL_GPL(tcpm_port_clean); 5488 + 5489 + bool tcpm_port_is_toggling(struct tcpm_port *port) 5490 + { 5491 + return port->port_type == TYPEC_PORT_DRP && port->state == TOGGLING; 5492 + } 5493 + EXPORT_SYMBOL_GPL(tcpm_port_is_toggling); 5517 5494 5518 5495 static void tcpm_enable_frs_work(struct kthread_work *work) 5519 5496 {
+8
include/linux/usb/tcpm.h
··· 114 114 * Optional; The USB Communications Capable bit indicates if port 115 115 * partner is capable of communication over the USB data lines 116 116 * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. 117 + * @check_contaminant: 118 + * Optional; The callback is called when CC pins report open status 119 + * at the end of the deboumce period or when the port is still 120 + * toggling. Chip level drivers are expected to check for contaminant 121 + * and call tcpm_clean_port when the port is clean. 117 122 */ 118 123 struct tcpc_dev { 119 124 struct fwnode_handle *fwnode; ··· 153 148 bool pps_active, u32 requested_vbus_voltage); 154 149 bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev); 155 150 void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable); 151 + void (*check_contaminant)(struct tcpc_dev *dev); 156 152 }; 157 153 158 154 struct tcpm_port; ··· 171 165 enum tcpm_transmit_status status); 172 166 void tcpm_pd_hard_reset(struct tcpm_port *port); 173 167 void tcpm_tcpc_reset(struct tcpm_port *port); 168 + void tcpm_port_clean(struct tcpm_port *port); 169 + bool tcpm_port_is_toggling(struct tcpm_port *port); 174 170 175 171 #endif /* __LINUX_USB_TCPM_H */