From patchwork Thu Dec 8 02:00:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Badhri Jagan Sridharan X-Patchwork-Id: 13067819 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5F32FC4708E for ; Thu, 8 Dec 2022 02:00:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229835AbiLHCAt (ORCPT ); Wed, 7 Dec 2022 21:00:49 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37688 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229668AbiLHCAs (ORCPT ); Wed, 7 Dec 2022 21:00:48 -0500 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 400247E403 for ; Wed, 7 Dec 2022 18:00:47 -0800 (PST) Received: by mail-yb1-xb4a.google.com with SMTP id a4-20020a5b0004000000b006fdc6aaec4fso16063952ybp.20 for ; Wed, 07 Dec 2022 18:00:47 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=cc:to:from:subject:message-id:mime-version:date:from:to:cc:subject :date:message-id:reply-to; bh=gg7uvAOdpyEub2UHnmW2qmJ2HAntmcgxkViRZYZxbUQ=; b=GNpQFhnCw6y3yKFv8Ilxk4/JuAMfWFAWt+0mSXxEFihjqfVatwbk8Y1tcfjLEL3UvI ooLUDx+1NUVpeTLrWnDZQyfmPdmybC66Kv6ZvSXGrRAI15bT2/jjOhfIaQkGfe+P2YTi +Q2nNXOnQM4f+joBKvLBRt6nD/3rBKvdFVRpmFKkxiajhfUUYU2eYP7lssBtDnQpbxzJ PWUnKnfTbWWFiRIxnmj6e8J3QP3won/FoI1HzBpAmBVV55c25BJ1KnCEKXLYGQwcdbEx 5OqZUji3gcaABrvR3ZKPuOofwKqkrBMv5m6MahroXFUMLOQMQjXm6MbygdxBRCAldZQM iAig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:from:subject:message-id:mime-version:date:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=gg7uvAOdpyEub2UHnmW2qmJ2HAntmcgxkViRZYZxbUQ=; b=K2voqBEH2WlvZpWS+Vwnz39dKPp6U+OCf8CZWt+a7R6OqLOklXBOtTa6rJj6dU4FaG TZ2HewEmLqhFllwr6JJoD96ANq8bnm2Rm5o9HdcWUb2y+qdBHDioWGmtdIlMDKPhwRov 7nQj1wYEb332S+lmMNGvsIzZWv+yNGfKHSAnJPiJ083LZU45NXDI5WJ801Pe7BnXvbOA Orm9wdmHZYoDw3QwqfrlQY6aR+cQ+x0pd5uQPxDwxUzxTwvnnSWSLKWI+QdH7GFvF61o qGsZKawatjpVWqrGnSqGc8PhHbzLjC2nS2JSsdAXj2+ZyeD5xKYr7KeSbwQWkZuidhGo zQtQ== X-Gm-Message-State: ANoB5pk3cAVi5EVjfTIpvzGttJ0wEdXRIUmEUwNemUfhJAOe+ITOPzWC 057sZ9pf9i4EtLgBCgwCyLUFrFm05as= X-Google-Smtp-Source: AA0mqf7HXln7vjChU0YbV/kVPArNiFPeaq+v2VbRd5PC8MlGMTlV8728W4ecdXuqKhHfuopHL1SZD+AuPT8= X-Received: from badhri.mtv.corp.google.com ([2620:15c:211:201:592e:36a9:5604:2233]) (user=badhri job=sendgmr) by 2002:a25:e7c7:0:b0:6f2:eff3:a311 with SMTP id e190-20020a25e7c7000000b006f2eff3a311mr58478829ybh.258.1670464846523; Wed, 07 Dec 2022 18:00:46 -0800 (PST) Date: Wed, 7 Dec 2022 18:00:41 -0800 Mime-Version: 1.0 X-Mailer: git-send-email 2.39.0.rc0.267.gcb52ba06e7-goog Message-ID: <20221208020043.374033-1-badhri@google.com> Subject: [PATCH v5 1/3] usb: typec: tcpm: Add callbacks to mitigate wakeups due to contaminant From: Badhri Jagan Sridharan To: Guenter Roeck , Heikki Krogerus , Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Kyle Tso , Badhri Jagan Sridharan Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org 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] ... TCPM invokes is_potential_contaminant callback to allow low level chip drivers to monitor TCPM state machine transitions and notify TCPM when the Type-C port needs to be checked for potential contaminant presence. 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. Signed-off-by: Badhri Jagan Sridharan Change-Id: I136d788bb46d871d12e64bd73fd50a77b4659dbf --- Changes since v4: * None Changes since v3: * None Changes since V2: * Offloaded tcpm from maintaining disconnect_while_debouncing logic * to lower level maxim tcpc driver based on feedback. --- drivers/usb/typec/tcpm/tcpm.c | 162 +++++++++------------------------- include/linux/usb/tcpm.h | 133 ++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 118 deletions(-) base-commit: 1524ceb14dd5ebd6f724d993c5ec1a9a8d445d8e diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 904c7b4ce2f0..a138cea49612 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -33,119 +33,6 @@ #include -#define FOREACH_STATE(S) \ - S(INVALID_STATE), \ - S(TOGGLING), \ - S(SRC_UNATTACHED), \ - S(SRC_ATTACH_WAIT), \ - S(SRC_ATTACHED), \ - S(SRC_STARTUP), \ - S(SRC_SEND_CAPABILITIES), \ - S(SRC_SEND_CAPABILITIES_TIMEOUT), \ - S(SRC_NEGOTIATE_CAPABILITIES), \ - S(SRC_TRANSITION_SUPPLY), \ - S(SRC_READY), \ - S(SRC_WAIT_NEW_CAPABILITIES), \ - \ - S(SNK_UNATTACHED), \ - S(SNK_ATTACH_WAIT), \ - S(SNK_DEBOUNCED), \ - S(SNK_ATTACHED), \ - S(SNK_STARTUP), \ - S(SNK_DISCOVERY), \ - S(SNK_DISCOVERY_DEBOUNCE), \ - S(SNK_DISCOVERY_DEBOUNCE_DONE), \ - S(SNK_WAIT_CAPABILITIES), \ - S(SNK_NEGOTIATE_CAPABILITIES), \ - S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ - S(SNK_TRANSITION_SINK), \ - S(SNK_TRANSITION_SINK_VBUS), \ - S(SNK_READY), \ - \ - S(ACC_UNATTACHED), \ - S(DEBUG_ACC_ATTACHED), \ - S(AUDIO_ACC_ATTACHED), \ - S(AUDIO_ACC_DEBOUNCE), \ - \ - S(HARD_RESET_SEND), \ - S(HARD_RESET_START), \ - S(SRC_HARD_RESET_VBUS_OFF), \ - S(SRC_HARD_RESET_VBUS_ON), \ - S(SNK_HARD_RESET_SINK_OFF), \ - S(SNK_HARD_RESET_WAIT_VBUS), \ - S(SNK_HARD_RESET_SINK_ON), \ - \ - S(SOFT_RESET), \ - S(SRC_SOFT_RESET_WAIT_SNK_TX), \ - S(SNK_SOFT_RESET), \ - S(SOFT_RESET_SEND), \ - \ - S(DR_SWAP_ACCEPT), \ - S(DR_SWAP_SEND), \ - S(DR_SWAP_SEND_TIMEOUT), \ - S(DR_SWAP_CANCEL), \ - S(DR_SWAP_CHANGE_DR), \ - \ - S(PR_SWAP_ACCEPT), \ - S(PR_SWAP_SEND), \ - S(PR_SWAP_SEND_TIMEOUT), \ - S(PR_SWAP_CANCEL), \ - S(PR_SWAP_START), \ - S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ - S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ - S(PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ - S(PR_SWAP_SRC_SNK_SINK_ON), \ - S(PR_SWAP_SNK_SRC_SINK_OFF), \ - S(PR_SWAP_SNK_SRC_SOURCE_ON), \ - S(PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ - \ - S(VCONN_SWAP_ACCEPT), \ - S(VCONN_SWAP_SEND), \ - S(VCONN_SWAP_SEND_TIMEOUT), \ - S(VCONN_SWAP_CANCEL), \ - S(VCONN_SWAP_START), \ - S(VCONN_SWAP_WAIT_FOR_VCONN), \ - S(VCONN_SWAP_TURN_ON_VCONN), \ - S(VCONN_SWAP_TURN_OFF_VCONN), \ - \ - S(FR_SWAP_SEND), \ - S(FR_SWAP_SEND_TIMEOUT), \ - S(FR_SWAP_SNK_SRC_TRANSITION_TO_OFF), \ - S(FR_SWAP_SNK_SRC_NEW_SINK_READY), \ - S(FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED), \ - S(FR_SWAP_CANCEL), \ - \ - S(SNK_TRY), \ - S(SNK_TRY_WAIT), \ - S(SNK_TRY_WAIT_DEBOUNCE), \ - S(SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ - S(SRC_TRYWAIT), \ - S(SRC_TRYWAIT_DEBOUNCE), \ - S(SRC_TRYWAIT_UNATTACHED), \ - \ - S(SRC_TRY), \ - S(SRC_TRY_WAIT), \ - S(SRC_TRY_DEBOUNCE), \ - S(SNK_TRYWAIT), \ - S(SNK_TRYWAIT_DEBOUNCE), \ - S(SNK_TRYWAIT_VBUS), \ - S(BIST_RX), \ - \ - S(GET_STATUS_SEND), \ - S(GET_STATUS_SEND_TIMEOUT), \ - S(GET_PPS_STATUS_SEND), \ - S(GET_PPS_STATUS_SEND_TIMEOUT), \ - \ - S(GET_SINK_CAP), \ - S(GET_SINK_CAP_TIMEOUT), \ - \ - S(ERROR_RECOVERY), \ - S(PORT_RESET), \ - S(PORT_RESET_WAIT_OFF), \ - \ - S(AMS_START), \ - S(CHUNK_NOT_SUPP) - #define FOREACH_AMS(S) \ S(NONE_AMS), \ S(POWER_NEGOTIATION), \ @@ -182,13 +69,8 @@ S(COUNTRY_INFO), \ S(COUNTRY_CODES) -#define GENERATE_ENUM(e) e #define GENERATE_STRING(s) #s -enum tcpm_state { - FOREACH_STATE(GENERATE_ENUM) -}; - static const char * const tcpm_states[] = { FOREACH_STATE(GENERATE_STRING) }; @@ -249,6 +131,7 @@ enum frs_typec_current { #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 @@ -483,6 +366,14 @@ struct tcpm_port { * 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 */ @@ -3904,15 +3795,26 @@ static void run_state_machine(struct tcpm_port *port) unsigned int msecs; enum tcpm_state upcoming_state; + if (port->tcpc->is_potential_contaminant) + port->potential_contaminant = + port->tcpc->is_potential_contaminant(port->tcpc, port->state); + 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 && port->tcpc->check_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; @@ -4150,6 +4052,10 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, -ENOTCONN); tcpm_pps_complete(port, -ENOTCONN); tcpm_snk_detach(port); + if (port->potential_contaminant && port->tcpc->check_contaminant) { + tcpm_set_state(port, CHECK_CONTAMINANT, 0); + break; + } if (tcpm_start_toggling(port, TYPEC_CC_RD)) { tcpm_set_state(port, TOGGLING, 0); break; @@ -4926,6 +4832,9 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, 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) || @@ -5425,6 +5334,10 @@ static void tcpm_pd_event_handler(struct kthread_work *work) port->vbus_source = true; _tcpm_pd_vbus_on(port); } + if (events & TCPM_PORT_CLEAN) { + tcpm_log(port, "port clean"); + tcpm_set_state(port, TOGGLING, 0); + } spin_lock(&port->pd_event_lock); } @@ -5477,6 +5390,19 @@ void tcpm_sourcing_vbus(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus); +/* + * Low level tcpc drivers invoke this once the port is deemed clean to return + * the port to TOGGLING state. + */ +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); + static void tcpm_enable_frs_work(struct kthread_work *work) { struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs); diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index bffc8d3e14ad..9cf16372a6e4 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -10,6 +10,126 @@ #include #include "pd.h" +#define FOREACH_STATE(S) \ + S(INVALID_STATE), \ + S(TOGGLING), \ + S(CHECK_CONTAMINANT), \ + S(SRC_UNATTACHED), \ + S(SRC_ATTACH_WAIT), \ + S(SRC_ATTACHED), \ + S(SRC_STARTUP), \ + S(SRC_SEND_CAPABILITIES), \ + S(SRC_SEND_CAPABILITIES_TIMEOUT), \ + S(SRC_NEGOTIATE_CAPABILITIES), \ + S(SRC_TRANSITION_SUPPLY), \ + S(SRC_READY), \ + S(SRC_WAIT_NEW_CAPABILITIES), \ + \ + S(SNK_UNATTACHED), \ + S(SNK_ATTACH_WAIT), \ + S(SNK_DEBOUNCED), \ + S(SNK_ATTACHED), \ + S(SNK_STARTUP), \ + S(SNK_DISCOVERY), \ + S(SNK_DISCOVERY_DEBOUNCE), \ + S(SNK_DISCOVERY_DEBOUNCE_DONE), \ + S(SNK_WAIT_CAPABILITIES), \ + S(SNK_NEGOTIATE_CAPABILITIES), \ + S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ + S(SNK_TRANSITION_SINK), \ + S(SNK_TRANSITION_SINK_VBUS), \ + S(SNK_READY), \ + \ + S(ACC_UNATTACHED), \ + S(DEBUG_ACC_ATTACHED), \ + S(AUDIO_ACC_ATTACHED), \ + S(AUDIO_ACC_DEBOUNCE), \ + \ + S(HARD_RESET_SEND), \ + S(HARD_RESET_START), \ + S(SRC_HARD_RESET_VBUS_OFF), \ + S(SRC_HARD_RESET_VBUS_ON), \ + S(SNK_HARD_RESET_SINK_OFF), \ + S(SNK_HARD_RESET_WAIT_VBUS), \ + S(SNK_HARD_RESET_SINK_ON), \ + \ + S(SOFT_RESET), \ + S(SRC_SOFT_RESET_WAIT_SNK_TX), \ + S(SNK_SOFT_RESET), \ + S(SOFT_RESET_SEND), \ + \ + S(DR_SWAP_ACCEPT), \ + S(DR_SWAP_SEND), \ + S(DR_SWAP_SEND_TIMEOUT), \ + S(DR_SWAP_CANCEL), \ + S(DR_SWAP_CHANGE_DR), \ + \ + S(PR_SWAP_ACCEPT), \ + S(PR_SWAP_SEND), \ + S(PR_SWAP_SEND_TIMEOUT), \ + S(PR_SWAP_CANCEL), \ + S(PR_SWAP_START), \ + S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ + S(PR_SWAP_SRC_SNK_SINK_ON), \ + S(PR_SWAP_SNK_SRC_SINK_OFF), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ + \ + S(VCONN_SWAP_ACCEPT), \ + S(VCONN_SWAP_SEND), \ + S(VCONN_SWAP_SEND_TIMEOUT), \ + S(VCONN_SWAP_CANCEL), \ + S(VCONN_SWAP_START), \ + S(VCONN_SWAP_WAIT_FOR_VCONN), \ + S(VCONN_SWAP_TURN_ON_VCONN), \ + S(VCONN_SWAP_TURN_OFF_VCONN), \ + \ + S(FR_SWAP_SEND), \ + S(FR_SWAP_SEND_TIMEOUT), \ + S(FR_SWAP_SNK_SRC_TRANSITION_TO_OFF), \ + S(FR_SWAP_SNK_SRC_NEW_SINK_READY), \ + S(FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED), \ + S(FR_SWAP_CANCEL), \ + \ + S(SNK_TRY), \ + S(SNK_TRY_WAIT), \ + S(SNK_TRY_WAIT_DEBOUNCE), \ + S(SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ + S(SRC_TRYWAIT), \ + S(SRC_TRYWAIT_DEBOUNCE), \ + S(SRC_TRYWAIT_UNATTACHED), \ + \ + S(SRC_TRY), \ + S(SRC_TRY_WAIT), \ + S(SRC_TRY_DEBOUNCE), \ + S(SNK_TRYWAIT), \ + S(SNK_TRYWAIT_DEBOUNCE), \ + S(SNK_TRYWAIT_VBUS), \ + S(BIST_RX), \ + \ + S(GET_STATUS_SEND), \ + S(GET_STATUS_SEND_TIMEOUT), \ + S(GET_PPS_STATUS_SEND), \ + S(GET_PPS_STATUS_SEND_TIMEOUT), \ + \ + S(GET_SINK_CAP), \ + S(GET_SINK_CAP_TIMEOUT), \ + \ + S(ERROR_RECOVERY), \ + S(PORT_RESET), \ + S(PORT_RESET_WAIT_OFF), \ + \ + S(AMS_START), \ + S(CHUNK_NOT_SUPP) + +#define GENERATE_ENUM(e) e + +enum tcpm_state { + FOREACH_STATE(GENERATE_ENUM) +}; + enum typec_cc_status { TYPEC_CC_OPEN, TYPEC_CC_RA, @@ -114,6 +234,16 @@ enum tcpm_transmit_type { * 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 invoked when chiplevel drivers indicated + * that the USB port needs to be checked for contaminant presence. + * Chip level drivers are expected to check for contaminant and call + * tcpm_clean_port when the port is clean to put the port back into + * toggling state. + * @is_potential_contaminant: + * Optional; TCPM invokes the callback for every TCPM state machine + * transition. Chiplevel drivers can monitor the state machine + * transitions to flag for potential contaminant presence. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -148,6 +278,8 @@ struct tcpc_dev { 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); + bool (*is_potential_contaminant)(struct tcpc_dev *dev, enum tcpm_state current_state); }; struct tcpm_port; @@ -165,5 +297,6 @@ void tcpm_pd_transmit_complete(struct tcpm_port *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); #endif /* __LINUX_USB_TCPM_H */ From patchwork Thu Dec 8 02:00:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Badhri Jagan Sridharan X-Patchwork-Id: 13067820 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3176BC4708D for ; Thu, 8 Dec 2022 02:00:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229562AbiLHCAw (ORCPT ); Wed, 7 Dec 2022 21:00:52 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37702 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229838AbiLHCAt (ORCPT ); Wed, 7 Dec 2022 21:00:49 -0500 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 19B9D7E403 for ; Wed, 7 Dec 2022 18:00:49 -0800 (PST) Received: by mail-yb1-xb49.google.com with SMTP id c188-20020a25c0c5000000b006d8eba07513so21267002ybf.17 for ; Wed, 07 Dec 2022 18:00:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=SwslB3VPbnLWhTauQf3ujZ3C7cLlwRSqUDF8mp/fjWo=; b=tkE30ABPYDwrpXmTy+HkUzUMJXERWU6ZQFz3gFMwl8yr+YiAv6P3OhUsjDjPAbPfNV WDlE0t7mfl1cD+fjclyjXe/0oTcnX+fFBkYpMlybwN1Lf6ng/eoReAZeThptNZR0RlcQ Wf4jvzjgVD41pGChutjTpiyIVNECJEeijGGdYMDUEg+O/vIoNXmwW1PbF8e5gjaWqA5b +MKmVQdM0wWgrreDYUtHhek/MC4hisCXYkIRKE4S16gtBQA5ud1rOfyeXi3Avc5FzeUV n2Qj/gYZ01vVH/YzvB52SfJp+Vk+bdsodbfAio0huiO/R5YFGNm7TKVZ3ktyTYB5Qzzj ZT8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=SwslB3VPbnLWhTauQf3ujZ3C7cLlwRSqUDF8mp/fjWo=; b=DnU6w/mmR2pTJKmpn8gNN98JodWIC7ipBfRqV/yO2waIZ3ezY7/g1WuoFPRH+r8syZ Um/icv28bW59hltTxvgi9p/RVsflmuswbJhggBtqEDUnqioIsb5/Z0fRJI0DMdbhE+QW kRynbG75Cc4dR9PHqJNTCLcSKVEcHfDzmeyeXOGO27Xd6GEmxJh7+J0pu7jEvWvXqAYq 3dlGeWLAztICvNl29CV+u6sBs2JvLH6/+ZaMWrpcV7+dajNT53EhTaFltHcPJAF5JNex NOL8W40M7rbCriXP5TMm9vDfR1Hr+Sp9Rfp60Aic6crcekS/FT9Pz7yhrrqsqYMSrEb7 +JNA== X-Gm-Message-State: ANoB5pnJL9oUKeaXwUL/1NI5LKfqxXmgWYNH1pASphdFrhU8iN120xNT Nm6fYwQsZC2HQV+fFxj7i/g1tm46SBw= X-Google-Smtp-Source: AA0mqf5F15VSh/2kF1dLtjM19BXfiSiQ5kbOOxztHwJIFNqumcUllbvNP3hZVYUYGolKZNgXlCT2QOgJNLc= X-Received: from badhri.mtv.corp.google.com ([2620:15c:211:201:592e:36a9:5604:2233]) (user=badhri job=sendgmr) by 2002:a0d:e6c4:0:b0:3bc:7270:cb70 with SMTP id p187-20020a0de6c4000000b003bc7270cb70mr55637971ywe.83.1670464848383; Wed, 07 Dec 2022 18:00:48 -0800 (PST) Date: Wed, 7 Dec 2022 18:00:42 -0800 In-Reply-To: <20221208020043.374033-1-badhri@google.com> Mime-Version: 1.0 References: <20221208020043.374033-1-badhri@google.com> X-Mailer: git-send-email 2.39.0.rc0.267.gcb52ba06e7-goog Message-ID: <20221208020043.374033-2-badhri@google.com> Subject: [PATCH v5 2/3] usb: typec: tcpci: Add callback for evaluating contaminant presence From: Badhri Jagan Sridharan To: Guenter Roeck , Heikki Krogerus , Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Kyle Tso , Badhri Jagan Sridharan Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org This change adds callback to evaluate presence of contaminant in the TCPCI layer. Signed-off-by: Badhri Jagan Sridharan --- Changes since v4: * None Changes since v3: * None Changes since v2: * Added tcpci_is_potential_contaminant to offload * disconnect_while_debounce logic --- drivers/usb/typec/tcpm/tcpci.c | 20 ++++++++++++++++++++ include/linux/usb/tcpci.h | 13 +++++++++++++ 2 files changed, 33 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index fe781a38dc82..2aadf785970f 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -403,6 +403,24 @@ static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev) tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data); } +static bool tcpci_is_potential_contaminant(struct tcpc_dev *dev, enum tcpm_state current_state) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + + if (tcpci->data->is_potential_contaminant) + return tcpci->data->is_potential_contaminant(tcpci, tcpci->data, current_state); + + return false; +} + +static void tcpci_check_contaminant(struct tcpc_dev *dev) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + + if (tcpci->data->check_contaminant) + tcpci->data->check_contaminant(tcpci, tcpci->data); +} + static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -777,6 +795,8 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.enable_frs = tcpci_enable_frs; tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable; + tcpci->tcpc.is_potential_contaminant = tcpci_is_potential_contaminant; + tcpci->tcpc.check_contaminant = tcpci_check_contaminant; if (tcpci->data->auto_discharge_disconnect) { tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge; diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h index 17657451c762..548e85fdc7fa 100644 --- a/include/linux/usb/tcpci.h +++ b/include/linux/usb/tcpci.h @@ -188,6 +188,16 @@ struct tcpci; * 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 invoked when chiplevel drivers indicated + * that the USB port needs to be checked for contaminant presence. + * Chip level drivers are expected to check for contaminant and call + * tcpm_clean_port when the port is clean to put the port back into + * toggling state. + * @is_potential_contaminant: + * Optional; TCPM invokes the callback for every TCPM state machine + * transition. Chiplevel drivers can monitor the state machine + * transitions to flag for potential contaminant presence. */ struct tcpci_data { struct regmap *regmap; @@ -204,6 +214,9 @@ struct tcpci_data { void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data); void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data, bool capable); + void (*check_contaminant)(struct tcpci *tcpci, struct tcpci_data *data); + bool (*is_potential_contaminant)(struct tcpci *tcpci, struct tcpci_data *data, + enum tcpm_state current_state); }; struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); From patchwork Thu Dec 8 02:00:43 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Badhri Jagan Sridharan X-Patchwork-Id: 13067821 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 95F1CC4708E for ; Thu, 8 Dec 2022 02:00:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229863AbiLHCAz (ORCPT ); Wed, 7 Dec 2022 21:00:55 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37726 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229847AbiLHCAx (ORCPT ); Wed, 7 Dec 2022 21:00:53 -0500 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5211692A1C for ; Wed, 7 Dec 2022 18:00:51 -0800 (PST) Received: by mail-yb1-xb4a.google.com with SMTP id 187-20020a250dc4000000b006f8cd26bfcfso19415334ybn.13 for ; Wed, 07 Dec 2022 18:00:51 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=w2THepeDG/JyGD+2rypA8VneHB00aPrRoiz+Aa0V+jA=; b=iwAyerdaKzVlspYapa+hp+YNhglJ5A8jUXwrFW+PZctEhJA6iMAz2CsnKANjJzpjiF I0xxSLTkQUBC0yo5EUyyGvm4LH8rTAAi2eASdyBIXJuZQdkNDPbcjQyqx7+vIhOulOYl dmcUzECyvMjoQ8V9Ss7wJyv2dR4EWD+JKfNghUzTincjYl65uQWhNKDXrXTCkU3pIp50 ST4EJcS1m9AISpK8KjVpa93pHuG/UI4kVOODgtkEZA5v4AW4OB8mceafmA49dzPDZALx n89OklObI0FEEtQ+tg9Z9ykuzhJMuwTVTRa9GHwclnLGEeainby22VryS7YPHm1iDIdk xZaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=w2THepeDG/JyGD+2rypA8VneHB00aPrRoiz+Aa0V+jA=; b=4hfcjw0jLIe3L/8wOuuadTY7aA1mwU8CdImKiUGlagsvUKZP5sYJLifyquujc3gDXY Cq2Rs3Tuk+fHwqrS3dDHVqW9nnl0/mceSdqrCWV94RWv+whxkcLeDY9NYoZ79mVUPrut aQeU8s+1gw/Cg9JEGitDJUxFJRsK+ck1rbVkDdsE2J59krE8AD1kNPy++ulXWIk9gpeP HEIc4ds2DQTw45PEl6z3TOO0Lik7/KwnLj0jTJIqNEzW3YyFOIKq+N/PV2E+mu2BKU1P nxNPs4r64XvzrDeFSQUux7FqVJn7UasfhfpyhRUU2AAhCJHgUdlbdPsJmStWYOFq+Iqd XJ0g== X-Gm-Message-State: ANoB5pmtLPw8eEhvIb9fdjyYf7EPkShebEhlcSebLLwj81MfmJM84bgD cqDc8EgJsvtB6/shzRCO5Y3oYTUPtFM= X-Google-Smtp-Source: AA0mqf7OrjYbLWYH0J3lsmvGVagtI8Nbxzbrw5IAs933iPh98qlMkab8OyXHpw4nNC+72o9BZSQR/B9gGaI= X-Received: from badhri.mtv.corp.google.com ([2620:15c:211:201:592e:36a9:5604:2233]) (user=badhri job=sendgmr) by 2002:a5b:348:0:b0:6d3:a720:992 with SMTP id q8-20020a5b0348000000b006d3a7200992mr89089397ybp.53.1670464850562; Wed, 07 Dec 2022 18:00:50 -0800 (PST) Date: Wed, 7 Dec 2022 18:00:43 -0800 In-Reply-To: <20221208020043.374033-1-badhri@google.com> Mime-Version: 1.0 References: <20221208020043.374033-1-badhri@google.com> X-Mailer: git-send-email 2.39.0.rc0.267.gcb52ba06e7-goog Message-ID: <20221208020043.374033-3-badhri@google.com> Subject: [PATCH v5 3/3] usb: typec: maxim_contaminant: Implement check_contaminant callback From: Badhri Jagan Sridharan To: Guenter Roeck , Heikki Krogerus , Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Kyle Tso , Badhri Jagan Sridharan Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Maxim TCPC has additional ADCs and low current(1ua) current source to measure the impedance of CC and SBU pins. When tcpm invokes the check_contaminant callback, Maxim TCPC measures the impedance of the CC & SBU pins and when the impedance measured is less than 1MOhm, it is assumed that USB-C port is contaminated. CC comparators are also checked to differentiate between presence of sink and contaminant. Once USB-C is deemed to be contaminated, MAXIM TCPC has additional hardware to disable normal DRP toggling cycle and enable 1ua on CC pins once every 2.4secs/4.8secs. Maxim TCPC interrupts AP once the impedance on the CC pin is above the 1MOhm threshold. The Maxim tcpc driver then signals TCPM_PORT_CLEAN to restart toggling. Renaming tcpci_maxim.c to tcpci_maxim_core.c and moving reg read/write helper functions to the tcpci_maxim.h header file. Signed-off-by: Badhri Jagan Sridharan --- Changes since v4: * Missed committing local changes to fix ktest robot warnings. Updated the patch in v5. Changes since v3: * Renamed functions and removed the unecessary EXPORT. * Fixed ktest robot warning. Changes since v2: * Implemented is_potential_contaminant to offload * disconnect_while_debouncing Changes since v1: * Renamed tcpci_maxim.c to tcpci_maxim_core.c and compiling tcpci_maxim_core.o with maxim_contaminant.o as a single module as suggested by Guenter Roeck * Got rid of exporting symbols for reg read/write helper functions and moved them to header as suggested by Heikki Krogerus * Sqashed the commit which exposed the max_tcpci_read helper functions into this one. --- drivers/usb/typec/tcpm/Makefile | 1 + drivers/usb/typec/tcpm/maxim_contaminant.c | 337 ++++++++++++++++++ drivers/usb/typec/tcpm/tcpci_maxim.h | 91 +++++ .../{tcpci_maxim.c => tcpci_maxim_core.c} | 78 ++-- 4 files changed, 475 insertions(+), 32 deletions(-) create mode 100644 drivers/usb/typec/tcpm/maxim_contaminant.c create mode 100644 drivers/usb/typec/tcpm/tcpci_maxim.h rename drivers/usb/typec/tcpm/{tcpci_maxim.c => tcpci_maxim_core.c} (89%) diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile index 906d9dced8e7..08e57bb499cb 100644 --- a/drivers/usb/typec/tcpm/Makefile +++ b/drivers/usb/typec/tcpm/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o obj-$(CONFIG_TYPEC_MT6360) += tcpci_mt6360.o obj-$(CONFIG_TYPEC_TCPCI_MT6370) += tcpci_mt6370.o obj-$(CONFIG_TYPEC_TCPCI_MAXIM) += tcpci_maxim.o +tcpci_maxim-y += tcpci_maxim_core.o maxim_contaminant.o diff --git a/drivers/usb/typec/tcpm/maxim_contaminant.c b/drivers/usb/typec/tcpm/maxim_contaminant.c new file mode 100644 index 000000000000..23b5ed65cba8 --- /dev/null +++ b/drivers/usb/typec/tcpm/maxim_contaminant.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 Google, Inc + * + * USB-C module to reduce wakeups due to contaminants. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "tcpci_maxim.h" + +enum fladc_select { + CC1_SCALE1 = 1, + CC1_SCALE2, + CC2_SCALE1, + CC2_SCALE2, + SBU1, + SBU2, +}; + +#define FLADC_1uA_LSB_MV 25 +/* High range CC */ +#define FLADC_CC_HIGH_RANGE_LSB_MV 208 +/* Low range CC */ +#define FLADC_CC_LOW_RANGE_LSB_MV 126 + +/* 1uA current source */ +#define FLADC_CC_SCALE1 1 +/* 5 uA current source */ +#define FLADC_CC_SCALE2 5 + +#define FLADC_1uA_CC_OFFSET_MV 300 +#define FLADC_CC_HIGH_RANGE_OFFSET_MV 624 +#define FLADC_CC_LOW_RANGE_OFFSET_MV 378 + +#define CONTAMINANT_THRESHOLD_SBU_K 1000 +#define CONTAMINANT_THRESHOLD_CC_K 1000 + +#define READ1_SLEEP_MS 10 +#define READ2_SLEEP_MS 5 + +#define STATUS_CHECK(reg, mask, val) (((reg) & (mask)) == (val)) + +#define IS_CC_OPEN(cc_status) \ + (STATUS_CHECK((cc_status), TCPC_CC_STATUS_CC1_MASK << TCPC_CC_STATUS_CC1_SHIFT, \ + TCPC_CC_STATE_SRC_OPEN) && STATUS_CHECK((cc_status), \ + TCPC_CC_STATUS_CC2_MASK << \ + TCPC_CC_STATUS_CC2_SHIFT, \ + TCPC_CC_STATE_SRC_OPEN)) + +static int max_contaminant_adc_to_mv(struct max_tcpci_chip *chip, enum fladc_select channel, + bool ua_src, u8 fladc) +{ + /* SBU channels only have 1 scale with 1uA. */ + if ((ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2 || channel == SBU1 || + channel == SBU2))) + /* Mean of range */ + return FLADC_1uA_CC_OFFSET_MV + (fladc * FLADC_1uA_LSB_MV); + else if (!ua_src && (channel == CC1_SCALE1 || channel == CC2_SCALE1)) + return FLADC_CC_HIGH_RANGE_OFFSET_MV + (fladc * FLADC_CC_HIGH_RANGE_LSB_MV); + else if (!ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2)) + return FLADC_CC_LOW_RANGE_OFFSET_MV + (fladc * FLADC_CC_LOW_RANGE_LSB_MV); + + dev_err(chip->dev, "ADC ERROR: SCALE UNKNOWN"); + + return -EINVAL; +} + +static int max_contaminant_read_adc_mv(struct max_tcpci_chip *chip, enum fladc_select channel, + int sleep_msec, bool raw, bool ua_src) +{ + struct regmap *regmap = chip->data.regmap; + u8 fladc; + int ret; + + /* Channel & scale select */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, + channel << ADC_CHANNEL_OFFSET); + if (ret < 0) + return ret; + + /* Enable ADC */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, ADCEN); + if (ret < 0) + return ret; + + usleep_range(sleep_msec * 1000, (sleep_msec + 1) * 1000); + ret = max_tcpci_read8(chip, TCPC_VENDOR_FLADC_STATUS, &fladc); + if (ret < 0) + return ret; + + /* Disable ADC */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, 0); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, 0); + if (ret < 0) + return ret; + + if (!raw) + return max_contaminant_adc_to_mv(chip, channel, ua_src, fladc); + else + return fladc; +} + +static int max_contaminant_read_resistance_kohm(struct max_tcpci_chip *chip, + enum fladc_select channel, int sleep_msec, bool raw) +{ + struct regmap *regmap = chip->data.regmap; + int mv; + int ret; + + if (channel == CC1_SCALE1 || channel == CC2_SCALE1 || channel == CC1_SCALE2 || + channel == CC2_SCALE2) { + /* Enable 1uA current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK, + ULTRA_LOW_POWER_MODE); + if (ret < 0) + return ret; + + /* Enable 1uA current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_1_SRC); + if (ret < 0) + return ret; + + /* OVP disable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, CCOVPDIS); + if (ret < 0) + return ret; + + mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true); + /* OVP enable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, 0); + if (ret < 0) + return ret; + /* returns KOhm as 1uA source is used. */ + return mv; + } + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, SBUOVPDIS); + if (ret < 0) + return ret; + + /* SBU switches auto configure when channel is selected. */ + /* Enable 1ua current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, SBURPCTRL); + if (ret < 0) + return ret; + + mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true); + /* Disable current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, 0); + if (ret < 0) + return ret; + + /* OVP disable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, 0); + if (ret < 0) + return ret; + + return mv; +} + +static void max_contaminant_read_comparators(struct max_tcpci_chip *chip, u8 *vendor_cc_status2_cc1, + u8 *vendor_cc_status2_cc2) +{ + struct regmap *regmap = chip->data.regmap; + int ret; + + /* Enable 80uA source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_80_SRC); + if (ret < 0) + return; + + /* Enable comparators */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, CCCOMPEN); + if (ret < 0) + return; + + /* Sleep to allow comparators settle */ + usleep_range(5000, 6000); + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC1); + if (ret < 0) + return; + + usleep_range(5000, 6000); + ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc1); + if (ret < 0) + return; + + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC2); + if (ret < 0) + return; + + usleep_range(5000, 6000); + ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc2); + if (ret < 0) + return; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, 0); + if (ret < 0) + return; + regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, 0); +} + +static int max_contaminant_detect_contaminant(struct max_tcpci_chip *chip) +{ + int cc1_k, cc2_k, sbu1_k, sbu2_k; + u8 vendor_cc_status2_cc1 = 0xff, vendor_cc_status2_cc2 = 0xff; + u8 role_ctrl = 0, role_ctrl_backup = 0; + int inferred_state = NOT_DETECTED; + + max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); + role_ctrl_backup = role_ctrl; + role_ctrl = 0x0F; + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); + + cc1_k = max_contaminant_read_resistance_kohm(chip, CC1_SCALE2, READ1_SLEEP_MS, false); + cc2_k = max_contaminant_read_resistance_kohm(chip, CC2_SCALE2, READ2_SLEEP_MS, false); + + sbu1_k = max_contaminant_read_resistance_kohm(chip, SBU1, READ1_SLEEP_MS, false); + sbu2_k = max_contaminant_read_resistance_kohm(chip, SBU2, READ2_SLEEP_MS, false); + max_contaminant_read_comparators(chip, &vendor_cc_status2_cc1, &vendor_cc_status2_cc2); + + if ((!(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1) || + !(CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) && + !(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1 && CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) + inferred_state = SINK; + else if ((cc1_k < CONTAMINANT_THRESHOLD_CC_K || cc2_k < CONTAMINANT_THRESHOLD_CC_K) && + (sbu1_k < CONTAMINANT_THRESHOLD_SBU_K || sbu2_k < CONTAMINANT_THRESHOLD_SBU_K)) + inferred_state = DETECTED; + + if (inferred_state == NOT_DETECTED) + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + else + max_tcpci_write8(chip, TCPC_ROLE_CTRL, (TCPC_ROLE_CTRL_DRP | 0xA)); + + return inferred_state; +} + +static int max_contaminant_enable_dry_detection(struct max_tcpci_chip *chip) +{ + struct regmap *regmap = chip->data.regmap; + u8 temp; + int ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL3, CCWTRDEB_MASK | CCWTRSEL_MASK + | WTRCYCLE_MASK, CCWTRDEB_1MS << CCWTRDEB_SHIFT | + CCWTRSEL_1V << CCWTRSEL_SHIFT | WTRCYCLE_4_8_S << + WTRCYCLE_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP, TCPC_ROLE_CTRL_DRP); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, CCCONNDRY); + if (ret < 0) + return ret; + ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL1, &temp); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK, + ULTRA_LOW_POWER_MODE); + if (ret < 0) + return ret; + ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL2, &temp); + if (ret < 0) + return ret; + + /* Enable Look4Connection before sending the command */ + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_EN_LK4CONN_ALRT, + TCPC_TCPC_CTRL_EN_LK4CONN_ALRT); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION); + if (ret < 0) + return ret; + return 0; +} + +bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce) +{ + u8 cc_status, pwr_cntl; + + max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); + max_tcpci_read8(chip, TCPC_POWER_CTRL, &pwr_cntl); + + if (chip->contaminant_state == NOT_DETECTED || chip->contaminant_state == SINK) { + if (!disconnect_while_debounce) + msleep(100); + + max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); + if (IS_CC_OPEN(cc_status)) { + u8 role_ctrl, role_ctrl_backup; + + max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); + role_ctrl_backup = role_ctrl; + role_ctrl |= 0x0F; + role_ctrl &= ~(TCPC_ROLE_CTRL_DRP); + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); + + chip->contaminant_state = max_contaminant_detect_contaminant(chip); + + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + if (chip->contaminant_state == DETECTED) { + max_contaminant_enable_dry_detection(chip); + return true; + } + } + return false; + } else if (chip->contaminant_state == DETECTED) { + if (STATUS_CHECK(cc_status, TCPC_CC_STATUS_TOGGLING, 0)) { + chip->contaminant_state = max_contaminant_detect_contaminant(chip); + if (chip->contaminant_state == DETECTED) { + max_contaminant_enable_dry_detection(chip); + return true; + } + } + } + + return false; +} + +MODULE_DESCRIPTION("MAXIM TCPC CONTAMINANT Module"); +MODULE_AUTHOR("Badhri Jagan Sridharan "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h new file mode 100644 index 000000000000..1a78e1ac3820 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2022 Google, Inc + * + * MAXIM TCPC header file. + */ +#ifndef TCPCI_MAXIM_H_ +#define TCPCI_MAXIM_H_ + +#define VENDOR_CC_STATUS2 0x85 +#define CC1_VUFP_RD0P5 BIT(1) +#define CC2_VUFP_RD0P5 BIT(5) +#define TCPC_VENDOR_FLADC_STATUS 0x89 + +#define TCPC_VENDOR_CC_CTRL1 0x8c +#define CCCONNDRY BIT(7) +#define CCCOMPEN BIT(5) + +#define TCPC_VENDOR_CC_CTRL2 0x8d +#define SBUOVPDIS BIT(7) +#define CCOVPDIS BIT(6) +#define SBURPCTRL BIT(5) +#define CCLPMODESEL_MASK GENMASK(4, 3) +#define ULTRA_LOW_POWER_MODE BIT(3) +#define CCRPCTRL_MASK GENMASK(2, 0) +#define UA_1_SRC 1 +#define UA_80_SRC 3 + +#define TCPC_VENDOR_CC_CTRL3 0x8e +#define CCWTRDEB_MASK GENMASK(7, 6) +#define CCWTRDEB_SHIFT 6 +#define CCWTRDEB_1MS 1 +#define CCWTRSEL_MASK GENMASK(5, 3) +#define CCWTRSEL_SHIFT 3 +#define CCWTRSEL_1V 0x4 +#define CCLADDERDIS BIT(2) +#define WTRCYCLE_MASK BIT(0) +#define WTRCYCLE_SHIFT 0 +#define WTRCYCLE_2_4_S 0 +#define WTRCYCLE_4_8_S 1 + +#define TCPC_VENDOR_ADC_CTRL1 0x91 +#define ADCINSEL_MASK GENMASK(7, 5) +#define ADC_CHANNEL_OFFSET 5 +#define ADCEN BIT(0) + +enum contamiant_state { + NOT_DETECTED, + DETECTED, + SINK, +}; + +/* + * @potential_contaminant: + * Last returned result to tcpm indicating whether the TCPM port + * has potential contaminant. + */ +struct max_tcpci_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; + struct i2c_client *client; + struct tcpm_port *port; + enum contamiant_state contaminant_state; + enum tcpm_state tcpm_current_state; + bool potential_contaminant; +}; + +static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static inline int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static inline int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static inline int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce); + +#endif // TCPCI_MAXIM_H_ diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c similarity index 89% rename from drivers/usb/typec/tcpm/tcpci_maxim.c rename to drivers/usb/typec/tcpm/tcpci_maxim_core.c index 83e140ffcc3e..8e6770ac2953 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -1,6 +1,6 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2020, Google LLC + * Copyright (C) 2020 - 2022, Google LLC * * MAXIM TCPCI based TCPC driver */ @@ -15,6 +15,8 @@ #include #include +#include "tcpci_maxim.h" + #define PD_ACTIVITY_TIMEOUT_MS 10000 #define TCPC_VENDOR_ALERT 0x80 @@ -39,14 +41,6 @@ #define MAX_BUCK_BOOST_SOURCE 0xa #define MAX_BUCK_BOOST_SINK 0x5 -struct max_tcpci_chip { - struct tcpci_data data; - struct tcpci *tcpci; - struct device *dev; - struct i2c_client *client; - struct tcpm_port *port; -}; - static const struct regmap_range max_tcpci_tcpci_range[] = { regmap_reg_range(0x00, 0x95) }; @@ -68,26 +62,6 @@ static struct max_tcpci_chip *tdata_to_max_tcpci(struct tcpci_data *tdata) return container_of(tdata, struct max_tcpci_chip, data); } -static int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) -{ - return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); -} - -static int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val) -{ - return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); -} - -static int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val) -{ - return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); -} - -static int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val) -{ - return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); -} - static void max_tcpci_init_regs(struct max_tcpci_chip *chip) { u16 alert_mask = 0; @@ -348,8 +322,14 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) if (status & TCPC_ALERT_VBUS_DISCNCT) tcpm_vbus_change(chip->port); - if (status & TCPC_ALERT_CC_STATUS) - tcpm_cc_change(chip->port); + if (status & TCPC_ALERT_CC_STATUS) { + if (chip->contaminant_state == DETECTED) { + if (!max_contaminant_is_contaminant(chip, false)) + tcpm_port_clean(chip->port); + } else { + tcpm_cc_change(chip->port); + } + } if (status & TCPC_ALERT_POWER_STATUS) process_power_status(chip); @@ -438,6 +418,38 @@ static int tcpci_init(struct tcpci *tcpci, struct tcpci_data *data) return -1; } +static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + if (!max_contaminant_is_contaminant(chip, chip->tcpm_current_state != TOGGLING)) + tcpm_port_clean(chip->port); +} + +static bool max_tcpci_is_potential_contaminant(struct tcpci *tcpci, struct tcpci_data *tdata, + enum tcpm_state current_state) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + enum tcpm_state tcpm_prev_state = chip->tcpm_current_state; + + /* + * Return previous determinted contaminant status when tcpm is in CHECK_CONTAMINANT + * state. Do not update the tcpm current state either. + */ + if (current_state == CHECK_CONTAMINANT) + return chip->potential_contaminant; + + chip->tcpm_current_state = current_state; + + if ((tcpm_prev_state == SRC_ATTACH_WAIT && current_state == SRC_UNATTACHED) || + (tcpm_prev_state == SNK_ATTACH_WAIT && current_state == SNK_UNATTACHED)) + chip->potential_contaminant = true; + else + chip->potential_contaminant = false; + + return chip->potential_contaminant; +} + static int max_tcpci_probe(struct i2c_client *client) { int ret; @@ -471,6 +483,8 @@ static int max_tcpci_probe(struct i2c_client *client) chip->data.auto_discharge_disconnect = true; chip->data.vbus_vsafe0v = true; chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; + chip->data.check_contaminant = max_tcpci_check_contaminant; + chip->data.is_potential_contaminant = max_tcpci_is_potential_contaminant; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data);