diff options
-rw-r--r-- | board/aleena/board.c | 6 | ||||
-rw-r--r-- | board/ampton/board.c | 2 | ||||
-rw-r--r-- | board/bip/board.c | 2 | ||||
-rw-r--r-- | board/careena/board.c | 6 | ||||
-rw-r--r-- | board/cheza/board.c | 4 | ||||
-rw-r--r-- | board/delan/board.c | 6 | ||||
-rw-r--r-- | board/grunt/board.c | 6 | ||||
-rw-r--r-- | board/liara/board.c | 6 | ||||
-rw-r--r-- | board/nocturne/board.c | 13 | ||||
-rw-r--r-- | common/usb_pd_protocol.c | 107 | ||||
-rw-r--r-- | common/usbc_ppc.c | 99 | ||||
-rw-r--r-- | driver/ppc/sn5s330.c | 5 | ||||
-rw-r--r-- | include/usb_pd.h | 9 | ||||
-rw-r--r-- | include/usbc_ppc.h | 74 |
14 files changed, 307 insertions, 38 deletions
diff --git a/board/aleena/board.c b/board/aleena/board.c index 5ba79d81fa..5c235b7cad 100644 --- a/board/aleena/board.c +++ b/board/aleena/board.c @@ -116,12 +116,14 @@ void board_update_sensor_config_from_sku(void) } } -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { enum gpio_signal signal = (port == 0) ? GPIO_USB_C0_OC_L : GPIO_USB_C1_OC_L; + /* Note that the levels are inverted because the pin is active low. */ + int lvl = is_overcurrented ? 0 : 1; - gpio_set_level(signal, 0); + gpio_set_level(signal, lvl); CPRINTS("p%d: overcurrent!", port); } diff --git a/board/ampton/board.c b/board/ampton/board.c index 5f9c2a41cb..941aab7f00 100644 --- a/board/ampton/board.c +++ b/board/ampton/board.c @@ -316,7 +316,7 @@ void board_hibernate_late(void) gpio_set_flags_by_mask(GPIO_KSI, 0xff, GPIO_INPUT); } -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { /* TODO(b/78344554): pass this signal upstream once hardware reworked */ cprints(CC_USBPD, "p%d: overcurrent!", port); diff --git a/board/bip/board.c b/board/bip/board.c index 318a5bc178..fb4ff5e39a 100644 --- a/board/bip/board.c +++ b/board/bip/board.c @@ -125,7 +125,7 @@ const struct spi_device_t spi_devices[] = { }; const unsigned int spi_devices_used = ARRAY_SIZE(spi_devices); -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { /* TODO(b/78344554): pass this signal upstream once hardware reworked */ cprints(CC_USBPD, "p%d: overcurrent!", port); diff --git a/board/careena/board.c b/board/careena/board.c index 380a936fd1..0774cd9adc 100644 --- a/board/careena/board.c +++ b/board/careena/board.c @@ -101,12 +101,14 @@ const struct pwm_t pwm_channels[] = { }; BUILD_ASSERT(ARRAY_SIZE(pwm_channels) == PWM_CH_COUNT); -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { enum gpio_signal signal = (port == 0) ? GPIO_USB_C0_OC_L : GPIO_USB_C1_OC_L; + /* Note that the levels are inverted because the pin is active low. */ + int lvl = is_overcurrented ? 0 : 1; - gpio_set_level(signal, 0); + gpio_set_level(signal, lvl); CPRINTS("p%d: overcurrent!", port); } diff --git a/board/cheza/board.c b/board/cheza/board.c index fffdedf707..66121fc3bb 100644 --- a/board/cheza/board.c +++ b/board/cheza/board.c @@ -129,7 +129,7 @@ static void ppc_interrupt(enum gpio_signal signal) static void usb1_oc_evt_deferred(void) { /* Only port-1 has overcurrent GPIO interrupt */ - board_overcurrent_event(0); + board_overcurrent_event(0, 1); } DECLARE_DEFERRED(usb1_oc_evt_deferred); @@ -509,7 +509,7 @@ int board_is_sourcing_vbus(int port) return EC_ERROR_INVAL; } -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { /* TODO(b/120231371): Notify AP */ CPRINTS("p%d: overcurrent!", port); diff --git a/board/delan/board.c b/board/delan/board.c index f5b5f5b2dd..4304ce25e4 100644 --- a/board/delan/board.c +++ b/board/delan/board.c @@ -114,12 +114,14 @@ const struct pwm_t pwm_channels[] = { }; BUILD_ASSERT(ARRAY_SIZE(pwm_channels) == PWM_CH_COUNT); -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { enum gpio_signal signal = (port == 0) ? GPIO_USB_C0_OC_L : GPIO_USB_C1_OC_L; + /* Note that the levels are inverted because the pin is active low. */ + int lvl = is_overcurrented ? 0 : 1; - gpio_set_level(signal, 0); + gpio_set_level(signal, lvl); CPRINTS("p%d: overcurrent!", port); } diff --git a/board/grunt/board.c b/board/grunt/board.c index f60e3be18a..13e062589a 100644 --- a/board/grunt/board.c +++ b/board/grunt/board.c @@ -124,12 +124,14 @@ void board_update_sensor_config_from_sku(void) gpio_enable_interrupt(GPIO_6AXIS_INT_L); } -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { enum gpio_signal signal = (port == 0) ? GPIO_USB_C0_OC_L : GPIO_USB_C1_OC_L; + /* Note that the levels are inverted because the pin is active low. */ + int lvl = is_overcurrented ? 0 : 1; - gpio_set_level(signal, 0); + gpio_set_level(signal, lvl); CPRINTS("p%d: overcurrent!", port); } diff --git a/board/liara/board.c b/board/liara/board.c index 0e8a9abb5e..67e79d76c1 100644 --- a/board/liara/board.c +++ b/board/liara/board.c @@ -114,12 +114,14 @@ const struct pwm_t pwm_channels[] = { }; BUILD_ASSERT(ARRAY_SIZE(pwm_channels) == PWM_CH_COUNT); -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { enum gpio_signal signal = (port == 0) ? GPIO_USB_C0_OC_L : GPIO_USB_C1_OC_L; + /* Note that the levels are inverted because the pin is active low. */ + int lvl = is_overcurrented ? 0 : 1; - gpio_set_level(signal, 0); + gpio_set_level(signal, lvl); CPRINTS("p%d: overcurrent!", port); } diff --git a/board/nocturne/board.c b/board/nocturne/board.c index ffa8c65920..20b4b3983b 100644 --- a/board/nocturne/board.c +++ b/board/nocturne/board.c @@ -572,28 +572,29 @@ static void board_quirks(void) } DECLARE_HOOK(HOOK_INIT, board_quirks, HOOK_PRIO_DEFAULT); -void board_overcurrent_event(int port) +void board_overcurrent_event(int port, int is_overcurrented) { + int lvl; + /* Sanity check the port. */ if ((port < 0) || (port >= CONFIG_USB_PD_PORT_COUNT)) return; /* Note that the levels are inverted because the pin is active low. */ + lvl = is_overcurrented ? 0 : 1; + switch (port) { case 0: - gpio_set_level(GPIO_USB_C0_OC_ODL, 0); + gpio_set_level(GPIO_USB_C0_OC_ODL, lvl); break; case 1: - gpio_set_level(GPIO_USB_C1_OC_ODL, 0); + gpio_set_level(GPIO_USB_C1_OC_ODL, lvl); break; default: return; }; - - /* TODO(b/69935262): Write a PD log entry for the OC event. */ - CPRINTS("C%d: overcurrent!", port); } static int read_gyro_sensor_temp(int idx, int *temp_ptr) diff --git a/common/usb_pd_protocol.c b/common/usb_pd_protocol.c index 542d388a3e..a22d22fd4f 100644 --- a/common/usb_pd_protocol.c +++ b/common/usb_pd_protocol.c @@ -630,7 +630,19 @@ static inline void set_state(int port, enum pd_states next_state) #ifdef CONFIG_USB_PD_TCPC_LOW_POWER if (next_state != PD_STATE_DRP_AUTO_TOGGLE) exit_low_power_mode(port); -#endif + +#ifdef CONFIG_USBC_PPC + /* If we're entering DRP_AUTO_TOGGLE, there is no sink connected. */ + if (next_state == PD_STATE_DRP_AUTO_TOGGLE) { + ppc_sink_is_connected(port, 0); + /* + * Clear the overcurrent event counter + * since we've detected a disconnect. + */ + ppc_clear_oc_event_counter(port); + } +#endif /* CONFIG_USBC_PPC */ +#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ #ifdef CONFIG_USB_PD_DUAL_ROLE #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE @@ -648,6 +660,23 @@ static inline void set_state(int port, enum pd_states next_state) if (next_state == PD_STATE_SRC_DISCONNECTED || next_state == PD_STATE_SNK_DISCONNECTED) { +#ifdef CONFIG_USBC_PPC + int cc1, cc2; + + tcpm_get_cc(port, &cc1, &cc2); + /* + * Neither a debug accessory nor UFP attached. + * Tell the PPC module that there is no sink connected. + */ + if (cc1 != TYPEC_CC_VOLT_RD && cc2 != TYPEC_CC_VOLT_RD) { + ppc_sink_is_connected(port, 0); + /* + * Clear the overcurrent event counter + * since we've detected a disconnect. + */ + ppc_clear_oc_event_counter(port); + } +#endif /* CONFIG_USBC_PPC */ /* Clear the input current limit */ pd_set_input_current_limit(port, 0, 0); #ifdef CONFIG_CHARGE_MANAGER @@ -2841,6 +2870,15 @@ void pd_task(void *u) } #endif +#ifdef CONFIG_USBC_PPC + /* + * TODO: Useful for non-PPC cases as well, but only needed + * for PPC cases right now. Revisit later. + */ + if (evt & PD_EVENT_SEND_HARD_RESET) + set_state(port, PD_STATE_HARD_RESET_SEND); +#endif /* defined(CONFIG_USBC_PPC) */ + /* process any potential incoming message */ incoming_packet = tcpm_has_pending_message(port); if (incoming_packet) { @@ -2985,9 +3023,23 @@ void pd_task(void *u) if (get_time().val < pd[port].cc_debounce) break; - /* Debounce complete. UFP is attached */ + /* Debounce complete */ +#ifdef CONFIG_USBC_PPC + /* + * If the port is latched off, just continue to + * monitor for a detach. + */ + if (ppc_is_port_latched_off(port)) + break; +#endif /* CONFIG_USBC_PPC */ + + /* UFP is attached */ if (new_cc_state == PD_CC_UFP_ATTACHED || new_cc_state == PD_CC_DEBUG_ACC) { +#ifdef CONFIG_USBC_PPC + /* Inform PPC that a sink is connected. */ + ppc_sink_is_connected(port, 1); +#endif /* CONFIG_USBC_PPC */ pd[port].polarity = (cc1 != TYPEC_CC_VOLT_RD); set_polarity(port, pd[port].polarity); @@ -4453,6 +4505,57 @@ void pd_update_contract(int port) #endif /* CONFIG_USB_PD_DUAL_ROLE */ +#ifdef CONFIG_USBC_PPC +static void pd_send_hard_reset(int port) +{ + task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SEND_HARD_RESET, 0); +} + +static uint32_t port_oc_reset_req; + +static void re_enable_ports(void) +{ + uint32_t ports = atomic_read_clear(&port_oc_reset_req); + + while (ports) { + int port = __fls(ports); + + ports &= ~(1 << port); + + /* + * Let the board know that the overcurrent is + * over since we're going to attempt re-enabling + * the port. + */ + board_overcurrent_event(port, 0); + + pd_send_hard_reset(port); + /* + * TODO(b/117854867): PD3.0 to send an alert message + * indicating OCP after explicit contract. + */ + } +} +DECLARE_DEFERRED(re_enable_ports); + +void pd_handle_overcurrent(int port) +{ + /* Keep track of the overcurrent events. */ + CPRINTS("C%d: overcurrent!", port); +#ifdef CONFIG_USB_PD_LOGGING + pd_log_event(PD_EVENT_PS_FAULT, PD_LOG_PORT_SIZE(port, 0), PS_FAULT_OCP, + NULL); +#endif /* defined(CONFIG_USB_PD_LOGGING) */ + ppc_add_oc_event(port); + /* Let the board specific code know about the OC event. */ + board_overcurrent_event(port, 1); + + /* Wait 1s before trying to re-enable the port. */ + atomic_or(&port_oc_reset_req, (1 << port)); + hook_call_deferred(&re_enable_ports_data, SECOND); +} +#endif /* defined(CONFIG_USBC_PPC) */ + static int command_pd(int argc, char **argv) { int port; diff --git a/common/usbc_ppc.c b/common/usbc_ppc.c index 0961ba6e83..9ec33a3ec1 100644 --- a/common/usbc_ppc.c +++ b/common/usbc_ppc.c @@ -5,15 +5,26 @@ /* USB-C Power Path Controller Common Code */ +#include "atomic.h" #include "common.h" #include "console.h" #include "hooks.h" +#include "timer.h" #include "usbc_ppc.h" #include "util.h" #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) +/* + * 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_COUNT]; + +static uint32_t connected_ports; + /* Simple wrappers to dispatch to the drivers. */ int ppc_init(int port) @@ -32,6 +43,59 @@ int ppc_init(int port) return rv; } +int ppc_add_oc_event(int port) +{ + if ((port < 0) || (port >= ppc_cnt)) + return EC_ERROR_INVAL; + + oc_event_cnt_tbl[port]++; + + /* The port overcurrented, so don't clear it's OC events. */ + atomic_clear(&connected_ports, 1 << port); + + if (oc_event_cnt_tbl[port] >= PPC_OC_CNT_THRESH) + CPRINTS("C%d: OC event limit reached! " + "Source path disabled until physical disconnect.", + port); + return EC_SUCCESS; +} + +static void clear_oc_tbl(void) +{ + int port; + + for (port = 0; port < ppc_cnt; port++) + /* + * Only clear the table if the port partner is no longer + * attached after debouncing. + */ + if ((!((1 << port) & 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 ppc_clear_oc_event_counter(int port) +{ + if ((port < 0) || (port >= ppc_cnt)) + 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, + PPC_OC_COOLDOWN_DELAY_US); + } + return EC_SUCCESS; +} + int ppc_is_sourcing_vbus(int port) { if ((port < 0) || (port >= ppc_cnt)) { @@ -68,16 +132,41 @@ int ppc_discharge_vbus(int port, int enable) return ppc_chips[port].drv->discharge_vbus(port, enable); } +int ppc_is_port_latched_off(int port) +{ + if ((port < 0) || (port >= ppc_cnt)) + return 0; + + return oc_event_cnt_tbl[port] >= PPC_OC_CNT_THRESH; +} + #ifdef CONFIG_USBC_PPC_VCONN int ppc_set_vconn(int port, int enable) { if ((port < 0) || (port >= ppc_cnt)) return EC_ERROR_INVAL; + /* + * Check our OC event counter. If we've exceeded our threshold, then + * let's latch our source path off to prevent continuous cycling. When + * the PD state machine detects a disconnection on the CC lines, we will + * reset our OC event counter. + */ + if (enable && ppc_is_port_latched_off(port)) + return EC_ERROR_ACCESS_DENIED; + return ppc_chips[port].drv->set_vconn(port, enable); } #endif +void ppc_sink_is_connected(int port, int is_connected) +{ + if (is_connected) + atomic_or(&connected_ports, 1 << port); + else + atomic_clear(&connected_ports, 1 << port); +} + int ppc_vbus_sink_enable(int port, int enable) { if ((port < 0) || (port >= ppc_cnt)) @@ -104,6 +193,15 @@ int ppc_vbus_source_enable(int port, int enable) if ((port < 0) || (port >= ppc_cnt)) return EC_ERROR_INVAL; + /* + * Check our OC event counter. If we've exceeded our threshold, then + * let's latch our source path off to prevent continuous cycling. When + * the PD state machine detects a disconnection on the CC lines, we will + * reset our OC event counter. + */ + if (enable && ppc_is_port_latched_off(port)) + return EC_ERROR_ACCESS_DENIED; + return ppc_chips[port].drv->vbus_source_enable(port, enable); } @@ -119,7 +217,6 @@ int ppc_is_vbus_present(int port) } #endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */ - #ifdef CONFIG_CMD_PPC_DUMP static int command_ppc_dump(int argc, char **argv) { diff --git a/driver/ppc/sn5s330.c b/driver/ppc/sn5s330.c index b90605a3e1..6b48c8650b 100644 --- a/driver/ppc/sn5s330.c +++ b/driver/ppc/sn5s330.c @@ -19,6 +19,7 @@ #include "timer.h" #include "usb_charge.h" #include "usb_pd_tcpm.h" +#include "usb_pd.h" #include "usbc_ppc.h" #include "util.h" @@ -641,9 +642,9 @@ static void sn5s330_handle_interrupt(int port) read_reg(port, SN5S330_INT_TRIP_RISE_REG1, &rise); read_reg(port, SN5S330_INT_TRIP_FALL_REG1, &fall); - /* Let the board know about the overcurrent event. */ + /* Notify the system about the overcurrent event. */ if (rise & SN5S330_ILIM_PP1_MASK) - board_overcurrent_event(port); + pd_handle_overcurrent(port); /* Clear the interrupt sources. */ write_reg(port, SN5S330_INT_TRIP_RISE_REG1, rise); diff --git a/include/usb_pd.h b/include/usb_pd.h index d87fcc8fa4..5ae89696dd 100644 --- a/include/usb_pd.h +++ b/include/usb_pd.h @@ -48,6 +48,7 @@ enum pd_rx_errors { */ #define PD_EVENT_DEVICE_ACCESSED (1<<7) #define PD_EVENT_POWER_STATE_CHANGE (1<<8) /* Chipset power state changed */ +#define PD_EVENT_SEND_HARD_RESET (1<<9) /* Issue a Hard Reset. */ /* Ensure TCPC is out of low power mode before handling these events. */ #define PD_EXIT_LOW_POWER_EVENT_MASK \ @@ -1769,6 +1770,14 @@ int pd_get_polarity(int port); int pd_get_partner_data_swap_capable(int port); /** + * Handle an overcurrent protection event. The port acting as a source has + * reported an overcurrent event. + * + * @param port: USB-C port number. + */ +void pd_handle_overcurrent(int port); + +/** * Request power swap command to be issued * * @param port USB-C port number diff --git a/include/usbc_ppc.h b/include/usbc_ppc.h index 06c2ff3f2f..a502ebe91e 100644 --- a/include/usbc_ppc.h +++ b/include/usbc_ppc.h @@ -11,6 +11,18 @@ /* Common APIs for USB Type-C Power Path Controllers (PPC) */ +/* + * Number of times a port may overcurrent before we latch off the port until a + * physical disconnect is detected. + */ +#define PPC_OC_CNT_THRESH 3 + +/* + * Number of seconds until a latched-off port is re-enabled for sourcing after + * detecting a physical disconnect. + */ +#define PPC_OC_COOLDOWN_DELAY_US (2 * SECOND) + struct ppc_drv { /** * Initialize the PPC. @@ -127,6 +139,31 @@ extern struct ppc_config_t ppc_chips[]; extern unsigned int ppc_cnt; /** + * Increment the overcurrent event counter. + * + * @param port: The Type-C port that has overcurrented. + * @return EC_SUCCESS on success, EC_ERROR_INVAL if non-existent port. + */ +int ppc_add_oc_event(int port); + +/** + * Clear the overcurrent event counter. + * + * @param port: The Type-C port's counter to clear. + * @return EC_SUCCESS on success, EC_ERROR_INVAL if non-existent port. + */ +int ppc_clear_oc_event_counter(int port); + +/** + * Discharge PD VBUS on src/sink disconnect & power role swap + * + * @param port: The Type-C port number. + * @param enable: 1 -> discharge vbus, 0 -> stop discharging vbus + * @return EC_SUCCESS on success, error otherwise. + */ +int ppc_discharge_vbus(int port, int enable); + +/** * Initializes the PPC for the specified port. * * @param port: The Type-C port number. @@ -135,12 +172,12 @@ extern unsigned int ppc_cnt; int ppc_init(int port); /** - * Determine if VBUS is present or not. + * Is the port latched off due to multiple overcurrent events in succession? * * @param port: The Type-C port number. - * @return 1 if VBUS is present, 0 if not. + * @return 1 if the port is latched off, 0 if it is not latched off. */ -int ppc_is_vbus_present(int port); +int ppc_is_port_latched_off(int port); /** * Is the port sourcing Vbus? @@ -151,6 +188,25 @@ int ppc_is_vbus_present(int port); int ppc_is_sourcing_vbus(int port); /** + * Determine if VBUS is present or not. + * + * @param port: The Type-C port number. + * @return 1 if VBUS is present, 0 if not. + */ +int ppc_is_vbus_present(int port); + +/** + * Inform the PPC module that a sink is connected. + * + * This is used such that it can determine when to clear the overcurrent events + * counter for a port. + * @param port: The Type-C port number. + * @param is_connected: 1: if sink is connected on this port, 0: if not + * connected. + */ +void ppc_sink_is_connected(int port, int is_connected); + +/** * Inform the PPC of the polarity of the CC pins. * * @param port: The Type-C port number. @@ -177,15 +233,6 @@ int ppc_set_vbus_source_current_limit(int port, enum tcpc_rp_value rp); int ppc_set_vconn(int port, int enable); /** - * Discharge PD VBUS on src/sink disconnect & power role swap - * - * @param port: The Type-C port number. - * @param enable: 1 -> discharge vbus, 0 -> stop discharging vbus - * @return EC_SUCCESS on success, error otherwise. - */ -int ppc_discharge_vbus(int port, int enable); - -/** * Turn on/off the charge path FET, such that current flows into the * system. * @@ -209,8 +256,9 @@ int ppc_vbus_source_enable(int port, int enable); * Board specific callback when a port overcurrents. * * @param port: The Type-C port which overcurrented. + * @param is_overcurrented: 1 if port overcurrented, 0 if the condition is gone. */ -void board_overcurrent_event(int port); +void board_overcurrent_event(int port, int is_overcurrented); /** * Put the PPC into its lowest power state. In this state it should still fire |