diff options
-rw-r--r-- | driver/ppc/syv682x.c | 145 | ||||
-rw-r--r-- | driver/ppc/syv682x.h | 10 |
2 files changed, 154 insertions, 1 deletions
diff --git a/driver/ppc/syv682x.c b/driver/ppc/syv682x.c index 7754a7db7b..e8f7f4e243 100644 --- a/driver/ppc/syv682x.c +++ b/driver/ppc/syv682x.c @@ -7,22 +7,35 @@ #include "common.h" #include "console.h" #include "driver/ppc/syv682x.h" +#include "hooks.h" #include "i2c.h" +#include "stdbool.h" +#include "system.h" #include "timer.h" #include "usb_charge.h" #include "usb_pd_tcpm.h" #include "usbc_ppc.h" +#include "usb_pd.h" #include "util.h" #define SYV682X_FLAGS_SOURCE_ENABLED (1 << 0) /* 0 -> CC1, 1 -> CC2 */ #define SYV682X_FLAGS_CC_POLARITY (1 << 1) #define SYV682X_FLAGS_VBUS_PRESENT (1 << 2) +#define SYV682X_FLAGS_OCP BIT(3) +#define SYV682X_FLAGS_OVP BIT(4) +#define SYV682X_FLAGS_TSD BIT(5) +#define SYV682X_FLAGS_RVS BIT(6) +#define SYV682X_FLAGS_VCONN_OCP BIT(7) + +static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */ static uint8_t flags[CONFIG_USB_PD_PORT_MAX_COUNT]; #define SYV682X_VBUS_DET_THRESH_MV 4000 /* Longest time that can be programmed in DSG_TIME field */ #define SYV682X_MAX_VBUS_DISCHARGE_TIME_MS 400 +/* Delay between checks when polling the interrupt registers */ +#define INTERRUPT_DELAY_MS 10 #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) @@ -93,6 +106,69 @@ static int syv682x_discharge_vbus(int port, int enable) return EC_SUCCESS; } +/* Filter interrupts with rising edge trigger */ +static bool syv682x_interrupt_filter(int port, int regval, int regmask, + int flagmask) +{ + if (regval & regmask) { + if (!(flags[port] & flagmask)) { + flags[port] |= flagmask; + return true; + } + } else { + flags[port] &= ~flagmask; + } + return false; +} + +/* + * Two status registers can trigger the ALERT_L pin, STATUS and CONTROL_4 + * These registers are clear on read if the condition has been cleared. + * The ALERT_L pin will not de-assert if the alert condition has not been + * cleared. Since they are clear on read, we should check the alerts whenever we + * read these registers to avoid race conditions. + */ +static void syv682x_handle_status_interrupt(int port, int regval) +{ + /* These conditions automatically turn off VBUS sourcing */ + if (regval & + (SYV682X_STATUS_OC_5V | SYV682X_STATUS_OVP | SYV682X_STATUS_TSD)) { + flags[port] &= ~SYV682X_FLAGS_SOURCE_ENABLED; + } + + /* Handle OC and thermal shutdown the same */ + if (syv682x_interrupt_filter(port, regval, + (SYV682X_STATUS_OC_HV | + SYV682X_STATUS_OC_5V | + SYV682X_STATUS_TSD), + SYV682X_FLAGS_OCP)) { + pd_handle_overcurrent(port); + } + + /* No PD handler for VBUS OVP/RVS events */ + if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_OVP, + SYV682X_FLAGS_OVP)) { + CPRINTS("ppc p%d: VBUS OVP!", port); + } + if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_RVS, + SYV682X_FLAGS_RVS)) { + CPRINTS("ppc p%d: VBUS Reverse Voltage!", port); + } +} + +static void syv682x_handle_control_4_interrupt(int port, int regval) +{ + if (syv682x_interrupt_filter(port, regval, SYV682X_CONTROL_4_VCONN_OCP, + SYV682X_FLAGS_VCONN_OCP)) { + CPRINTS("ppc p%d: VCONN OC!", port); + } + + /* This should never happen unless something really bad happened */ + if (regval & SYV682X_CONTROL_4_VBAT_OVP) { + CPRINTS("ppc p%d: VBAT OVP!", port); + } +} + static int syv682x_vbus_sink_enable(int port, int enable) { int regval; @@ -138,6 +214,12 @@ static int syv682x_is_vbus_present(int port) if (read_reg(port, SYV682X_STATUS_REG, &val)) return vbus; + /* + * The status register interrupt bits are clear on read, check + * register value to see if there are interrupts to avoid race + * conditions with the interrupt handler + */ + syv682x_handle_status_interrupt(port, val); /* * VBUS is considered present if VSafe5V is detected or neither VSafe5V @@ -164,7 +246,6 @@ static int syv682x_vbus_source_enable(int port, int enable) { int regval; int rv; - /* * For source mode need to make sure 5V power path is connected * and source mode is selected. @@ -275,6 +356,12 @@ static int syv682x_set_vconn(int port, int enable) rv = read_reg(port, SYV682X_CONTROL_4_REG, ®val); if (rv) return rv; + /* + * The control4 register interrupt bits are clear on read, check + * register value to see if there are interrupts to avoid race + * conditions with the interrupt handler + */ + syv682x_handle_control_4_interrupt(port, regval); if (enable) regval |= flags[port] & SYV682X_FLAGS_CC_POLARITY ? @@ -313,6 +400,62 @@ static int syv682x_dump(int port) } #endif /* defined(CONFIG_CMD_PPC_DUMP) */ +static void syv682x_interrupt_delayed(int port, int delay); + +static void syv682x_handle_interrupt(int port) +{ + int control4; + int status; + + /* Both interrupt registers are clear on read */ + read_reg(port, SYV682X_CONTROL_4_REG, &control4); + syv682x_handle_control_4_interrupt(port, control4); + + read_reg(port, SYV682X_STATUS_REG, &status); + syv682x_handle_status_interrupt(port, status); + + /* + * Since ALERT_L is level-triggered, check the alert status and repeat + * until all interrupts are cleared. This will not spam indefinitely on + * OCP, but may on OVP, RVS, or TSD + */ + + if (IS_ENABLED(CONFIG_USBC_PPC_DEDICATED_INT) && + ppc_get_alert_status(port)) { + syv682x_interrupt_delayed(port, INTERRUPT_DELAY_MS); + } else { + read_reg(port, SYV682X_CONTROL_4_REG, &control4); + read_reg(port, SYV682X_STATUS_REG, &status); + if (status & SYV682X_STATUS_INT_MASK || + control4 & SYV682X_CONTROL_4_INT_MASK) { + syv682x_interrupt_delayed(port, INTERRUPT_DELAY_MS); + } + } +} + +static void syv682x_irq_deferred(void) +{ + int i; + uint32_t pending = atomic_read_clear(&irq_pending); + + for (i = 0; i < board_get_usb_pd_port_count(); i++) + if (BIT(i) & pending) + syv682x_handle_interrupt(i); +} +DECLARE_DEFERRED(syv682x_irq_deferred); + +static void syv682x_interrupt_delayed(int port, int delay) +{ + atomic_or(&irq_pending, BIT(port)); + hook_call_deferred(&syv682x_irq_deferred_data, delay * MSEC); +} + +void syv682x_interrupt(int port) +{ + /* FRS timings require <15ms response to an FRS event */ + syv682x_interrupt_delayed(port, 0); +} + static int syv682x_init(int port) { int rv; diff --git a/driver/ppc/syv682x.h b/driver/ppc/syv682x.h index fd6925ed97..ad57b814dc 100644 --- a/driver/ppc/syv682x.h +++ b/driver/ppc/syv682x.h @@ -22,8 +22,15 @@ #define SYV682X_CONTROL_4_REG 0x04 /* Status Register */ +#define SYV682X_STATUS_OC_HV BIT(7) +#define SYV682X_STATUS_RVS BIT(6) +#define SYV682X_STATUS_OC_5V BIT(5) +#define SYV682X_STATUS_OVP BIT(4) +#define SYV682X_STATUS_FRS BIT(3) +#define SYV682X_STATUS_TSD BIT(2) #define SYV682X_STATUS_VSAFE_5V (1 << 1) #define SYV682X_STATUS_VSAFE_0V (1 << 0) +#define SYV682X_STATUS_INT_MASK 0xfc /* Control Register 1 */ #define SYV682X_CONTROL_1_CH_SEL (1 << 1) @@ -63,8 +70,11 @@ #define SYV682X_CONTROL_4_VBAT_OVP (1 << 3) #define SYV682X_CONTROL_4_VCONN_OCP (1 << 2) #define SYV682X_CONTROL_4_CC_FRS (1 << 1) +#define SYV682X_CONTROL_4_INT_MASK 0x0c struct ppc_drv; extern const struct ppc_drv syv682x_drv; +void syv682x_interrupt(int port); + #endif /* defined(__CROS_EC_SYV682X_H) */ |