summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--driver/ppc/syv682x.c145
-rw-r--r--driver/ppc/syv682x.h10
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, &regval);
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) */