| /* Copyright 2020 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* USB-C Overcurrent Protection Common Code */ |
| |
| #include "atomic.h" |
| #include "common.h" |
| #include "console.h" |
| #include "hooks.h" |
| #include "timer.h" |
| #include "usb_pd.h" |
| #include "usbc_ocp.h" |
| #include "util.h" |
| |
| #ifndef TEST_BUILD |
| #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) |
| #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) |
| #else |
| #define CPRINTF(args...) |
| #define CPRINTS(args...) |
| #endif |
| |
| /* |
| * Number of times a port may overcurrent before we latch off the port until a |
| * physical disconnect is detected. |
| */ |
| #define OCP_CNT_THRESH 3 |
| |
| /* |
| * Number of seconds until a latched-off port is re-enabled for sourcing after |
| * detecting a physical disconnect. |
| */ |
| #define OCP_COOLDOWN_DELAY_US (2 * SECOND) |
| |
| /* |
| * A per-port table that indicates how many VBUS overcurrent events have |
| * occurred. This table is cleared after detecting a physical disconnect of the |
| * sink. |
| */ |
| static uint8_t oc_event_cnt_tbl[CONFIG_USB_PD_PORT_MAX_COUNT]; |
| |
| /* A flag for ports with sink device connected. */ |
| static uint32_t snk_connected_ports; |
| |
| static void clear_oc_tbl(void) |
| { |
| int port; |
| |
| for (port = 0; port < board_get_usb_pd_port_count(); port++) |
| /* |
| * Only clear the table if the port partner is no longer |
| * attached after debouncing. |
| */ |
| if ((!(BIT(port) & snk_connected_ports)) && |
| oc_event_cnt_tbl[port]) { |
| oc_event_cnt_tbl[port] = 0; |
| CPRINTS("C%d: OC events cleared", port); |
| } |
| } |
| DECLARE_DEFERRED(clear_oc_tbl); |
| |
| int usbc_ocp_add_event(int port) |
| { |
| if ((port < 0) || (port >= board_get_usb_pd_port_count())) { |
| CPRINTS("%s(%d) Invalid port!", __func__, port); |
| return EC_ERROR_INVAL; |
| } |
| |
| oc_event_cnt_tbl[port]++; |
| |
| /* The port overcurrented, so don't clear it's OC events. */ |
| atomic_clear_bits(&snk_connected_ports, 1 << port); |
| |
| if (oc_event_cnt_tbl[port] >= OCP_CNT_THRESH) |
| CPRINTS("C%d: OC event limit reached! " |
| "Source path disabled until physical disconnect.", |
| port); |
| return EC_SUCCESS; |
| } |
| |
| |
| int usbc_ocp_clear_event_counter(int port) |
| { |
| if ((port < 0) || (port >= board_get_usb_pd_port_count())) { |
| CPRINTS("%s(%d) Invalid port!", __func__, port); |
| return EC_ERROR_INVAL; |
| } |
| |
| /* |
| * If we are clearing our event table in quick succession, we may be in |
| * an overcurrent loop where we are also detecting a disconnect on the |
| * CC pins. Therefore, let's not clear it just yet and the let the |
| * limit be reached. This way, we won't send the hard reset and |
| * actually detect the physical disconnect. |
| */ |
| if (oc_event_cnt_tbl[port]) { |
| hook_call_deferred(&clear_oc_tbl_data, |
| OCP_COOLDOWN_DELAY_US); |
| } |
| return EC_SUCCESS; |
| } |
| |
| int usbc_ocp_is_port_latched_off(int port) |
| { |
| if ((port < 0) || (port >= board_get_usb_pd_port_count())) { |
| CPRINTS("%s(%d) Invalid port!", __func__, port); |
| return 0; |
| } |
| |
| return oc_event_cnt_tbl[port] >= OCP_CNT_THRESH; |
| } |
| |
| void usbc_ocp_snk_is_connected(int port, bool connected) |
| { |
| if ((port < 0) || (port >= board_get_usb_pd_port_count())) { |
| CPRINTS("%s(%d) Invalid port!", __func__, port); |
| return; |
| } |
| |
| if (connected) |
| atomic_or(&snk_connected_ports, 1 << port); |
| else |
| atomic_clear_bits(&snk_connected_ports, 1 << port); |
| } |
| |
| __overridable void board_overcurrent_event(int port, int is_overcurrented) |
| { |
| /* Do nothing by default. Boards with overcurrent GPIOs may override */ |
| } |