summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Herrmann <eherrmann@chromium.org>2020-02-14 09:33:56 -0800
committerCommit Bot <commit-bot@chromium.org>2020-05-28 08:03:13 +0000
commit32ab887033cc781ae4260cdaa0214a59d933f0ec (patch)
treee5fe83adf17d31b9522e9bac5e0421d92e0a76ef
parent9cb8e84dfc25c8a6ae58bc12311b2e1df16c32a6 (diff)
downloadchrome-ec-32ab887033cc781ae4260cdaa0214a59d933f0ec.tar.gz
syv682x: Create interrupt handler
Add interrupt handler to SYV682. All alerts are clear on read, so interrupts must be checked regardless of why the register is being read. It is also very spammy so flags are used to trigger only on rising edges of alerts. BUG=b:149531621 BRANCH=none TEST=make buildall TEST=On Volteer Connect a passive USB C-to-A adapter to the USB4 DB. Short the VBUS and ground pins; make sure the OC event is seen by the EC with the C1 overcurrent console message. Change-Id: I61c1a6b962e60d813adab92fcbd21fbdf838f188 Signed-off-by: Eric Herrmann <eherrmann@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2057380 Reviewed-by: Aseda Aboagye <aaboagye@chromium.org> Reviewed-by: Keith Short <keithshort@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2215642 Commit-Queue: Marco Chen <marcochen@chromium.org> Tested-by: Marco Chen <marcochen@chromium.org> Reviewed-by: Marco Chen <marcochen@chromium.org>
-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) */