#define FOREACH_STATE(S)                       \
        S(INVALID_STATE),                       \
        S(TOGGLING),                    \
+       S(CHECK_CONTAMINANT),                   \
        S(SRC_UNATTACHED),                      \
        S(SRC_ATTACH_WAIT),                     \
        S(SRC_ATTACHED),                        \
 #define TCPM_RESET_EVENT       BIT(2)
 #define TCPM_FRS_EVENT         BIT(3)
 #define TCPM_SOURCING_VBUS     BIT(4)
+#define TCPM_PORT_CLEAN                BIT(5)
 
 #define LOG_BUFFER_ENTRIES     1024
 #define LOG_BUFFER_ENTRY_SIZE  128
         * SNK_READY for non-pd link.
         */
        bool slow_charger_loop;
+
+       /*
+        * When true indicates that the lower level drivers indicate potential presence
+        * of contaminant in the connector pins based on the tcpm state machine
+        * transitions.
+        */
+       bool potential_contaminant;
 #ifdef CONFIG_DEBUG_FS
        struct dentry *dentry;
        struct mutex logbuffer_lock;    /* log buffer access lock */
        /* Do not log while disconnected and unattached */
        if (tcpm_port_is_disconnected(port) &&
            (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED ||
-            port->state == TOGGLING))
+            port->state == TOGGLING || port->state == CHECK_CONTAMINANT))
                return;
 
        va_start(args, fmt);
        unsigned int msecs;
        enum tcpm_state upcoming_state;
 
+       if (port->tcpc->check_contaminant && port->state != CHECK_CONTAMINANT)
+               port->potential_contaminant = ((port->enter_state == SRC_ATTACH_WAIT &&
+                                               port->state == SRC_UNATTACHED) ||
+                                              (port->enter_state == SNK_ATTACH_WAIT &&
+                                               port->state == SNK_UNATTACHED));
+
        port->enter_state = port->state;
        switch (port->state) {
        case TOGGLING:
                break;
+       case CHECK_CONTAMINANT:
+               port->tcpc->check_contaminant(port->tcpc);
+               break;
        /* SRC states */
        case SRC_UNATTACHED:
                if (!port->non_pd_role_swap)
                        tcpm_swap_complete(port, -ENOTCONN);
                tcpm_src_detach(port);
+               if (port->potential_contaminant) {
+                       tcpm_set_state(port, CHECK_CONTAMINANT, 0);
+                       break;
+               }
                if (tcpm_start_toggling(port, tcpm_rp_cc(port))) {
                        tcpm_set_state(port, TOGGLING, 0);
                        break;
                        tcpm_swap_complete(port, -ENOTCONN);
                tcpm_pps_complete(port, -ENOTCONN);
                tcpm_snk_detach(port);
+               if (port->potential_contaminant) {
+                       tcpm_set_state(port, CHECK_CONTAMINANT, 0);
+                       break;
+               }
                if (tcpm_start_toggling(port, TYPEC_CC_RD)) {
                        tcpm_set_state(port, TOGGLING, 0);
                        break;
                else if (tcpm_port_is_sink(port))
                        tcpm_set_state(port, SNK_ATTACH_WAIT, 0);
                break;
+       case CHECK_CONTAMINANT:
+               /* Wait for Toggling to be resumed */
+               break;
        case SRC_UNATTACHED:
        case ACC_UNATTACHED:
                if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) ||
                        port->vbus_source = true;
                        _tcpm_pd_vbus_on(port);
                }
+               if (events & TCPM_PORT_CLEAN) {
+                       tcpm_log(port, "port clean");
+                       if (port->state == CHECK_CONTAMINANT) {
+                               if (tcpm_start_toggling(port, tcpm_rp_cc(port)))
+                                       tcpm_set_state(port, TOGGLING, 0);
+                               else
+                                       tcpm_set_state(port, tcpm_default_state(port), 0);
+                       }
+               }
 
                spin_lock(&port->pd_event_lock);
        }
 }
 EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus);
 
+void tcpm_port_clean(struct tcpm_port *port)
+{
+       spin_lock(&port->pd_event_lock);
+       port->pd_events |= TCPM_PORT_CLEAN;
+       spin_unlock(&port->pd_event_lock);
+       kthread_queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_port_clean);
+
+bool tcpm_port_is_toggling(struct tcpm_port *port)
+{
+       return port->port_type == TYPEC_PORT_DRP && port->state == TOGGLING;
+}
+EXPORT_SYMBOL_GPL(tcpm_port_is_toggling);
+
 static void tcpm_enable_frs_work(struct kthread_work *work)
 {
        struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs);
 
  *              Optional; The USB Communications Capable bit indicates if port
  *              partner is capable of communication over the USB data lines
  *              (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit.
+ * @check_contaminant:
+ *             Optional; The callback is called when CC pins report open status
+ *             at the end of the deboumce period or when the port is still
+ *             toggling. Chip level drivers are expected to check for contaminant
+ *             and call tcpm_clean_port when the port is clean.
  */
 struct tcpc_dev {
        struct fwnode_handle *fwnode;
                                                 bool pps_active, u32 requested_vbus_voltage);
        bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev);
        void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable);
+       void (*check_contaminant)(struct tcpc_dev *dev);
 };
 
 struct tcpm_port;
                               enum tcpm_transmit_status status);
 void tcpm_pd_hard_reset(struct tcpm_port *port);
 void tcpm_tcpc_reset(struct tcpm_port *port);
+void tcpm_port_clean(struct tcpm_port *port);
+bool tcpm_port_is_toggling(struct tcpm_port *port);
 
 #endif /* __LINUX_USB_TCPM_H */