summaryrefslogtreecommitdiff
path: root/driver
diff options
context:
space:
mode:
Diffstat (limited to 'driver')
-rw-r--r--driver/ppc/syv682x.c120
1 files changed, 85 insertions, 35 deletions
diff --git a/driver/ppc/syv682x.c b/driver/ppc/syv682x.c
index 3d2142e76f..31f6530212 100644
--- a/driver/ppc/syv682x.c
+++ b/driver/ppc/syv682x.c
@@ -4,6 +4,7 @@
*/
/* Silergy SYV682x USB-C Power Path Controller */
+#include "atomic.h"
#include "common.h"
#include "config.h"
#include "console.h"
@@ -19,17 +20,20 @@
#include "util.h"
#define SYV682X_FLAGS_SOURCE_ENABLED BIT(0)
+#define SYV682X_FLAGS_SINK_ENABLED BIT(1)
/* 0 -> CC1, 1 -> CC2 */
-#define SYV682X_FLAGS_CC_POLARITY BIT(1)
-#define SYV682X_FLAGS_VBUS_PRESENT BIT(2)
-#define SYV682X_FLAGS_OCP BIT(3)
-#define SYV682X_FLAGS_OVP BIT(4)
-#define SYV682X_FLAGS_5V_OC BIT(5)
-#define SYV682X_FLAGS_RVS BIT(6)
-#define SYV682X_FLAGS_VCONN_OCP BIT(7)
+#define SYV682X_FLAGS_CC_POLARITY BIT(2)
+#define SYV682X_FLAGS_VBUS_PRESENT BIT(3)
+#define SYV682X_FLAGS_TSD BIT(4)
+#define SYV682X_FLAGS_OVP BIT(5)
+#define SYV682X_FLAGS_5V_OC BIT(6)
+#define SYV682X_FLAGS_RVS BIT(7)
+#define SYV682X_FLAGS_VCONN_OCP BIT(8)
static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */
-static uint8_t flags[CONFIG_USB_PD_PORT_MAX_COUNT];
+static uint32_t flags[CONFIG_USB_PD_PORT_MAX_COUNT];
+/* Running count of sink ocp events */
+static uint32_t sink_ocp_count[CONFIG_USB_PD_PORT_MAX_COUNT];
static timestamp_t vbus_oc_timer[CONFIG_USB_PD_PORT_MAX_COUNT];
static timestamp_t vconn_oc_timer[CONFIG_USB_PD_PORT_MAX_COUNT];
@@ -41,6 +45,8 @@ static timestamp_t vconn_oc_timer[CONFIG_USB_PD_PORT_MAX_COUNT];
/* Deglitch in ms of sourcing overcurrent detection */
#define SOURCE_OC_DEGLITCH_MS 100
#define VCONN_OC_DEGLITCH_MS 100
+/* Max. number of OC events allowed before disabling port */
+#define OCP_COUNT_LIMIT 3
#if SOURCE_OC_DEGLITCH_MS < INTERRUPT_DELAY_MS
#error "SOURCE_OC_DEGLITCH_MS should be at least INTERRUPT_DELAY_MS"
@@ -55,6 +61,8 @@ static timestamp_t vconn_oc_timer[CONFIG_USB_PD_PORT_MAX_COUNT];
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
+static int syv682x_vbus_sink_enable(int port, int enable);
+
static int syv682x_init(int port);
static void syv682x_interrupt_delayed(int port, int delay);
@@ -172,10 +180,12 @@ static int syv682x_vbus_source_enable(int port, int enable)
if (rv)
return rv;
- if (enable)
- flags[port] |= SYV682X_FLAGS_SOURCE_ENABLED;
- else
- flags[port] &= ~SYV682X_FLAGS_SOURCE_ENABLED;
+ if (enable) {
+ atomic_or(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED);
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_SINK_ENABLED);
+ } else {
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED);
+ }
#if defined(CONFIG_USB_CHARGER) && defined(CONFIG_USB_PD_VBUS_DETECT_PPC)
/*
@@ -194,11 +204,11 @@ static bool syv682x_interrupt_filter(int port, int regval, int regmask,
{
if (regval & regmask) {
if (!(flags[port] & flagmask)) {
- flags[port] |= flagmask;
+ atomic_or(&flags[port], flagmask);
return true;
}
} else {
- flags[port] &= ~flagmask;
+ atomic_clear_bits(&flags[port], flagmask);
}
return false;
}
@@ -215,7 +225,8 @@ static void syv682x_handle_status_interrupt(int port, int regval)
/* An FRS will automatically enable the source path */
if (IS_ENABLED(CONFIG_USB_PD_FRS_PPC) &&
(regval & SYV682X_STATUS_FRS)) {
- flags[port] |= SYV682X_FLAGS_SOURCE_ENABLED;
+ atomic_or(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED);
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_SINK_ENABLED);
/*
* Workaround for bug in SYV692.
*
@@ -232,10 +243,6 @@ static void syv682x_handle_status_interrupt(int port, int regval)
pd_got_frs_signal(port);
}
- /* These conditions automatically turn off VBUS sourcing */
- if (regval & (SYV682X_STATUS_OVP | SYV682X_STATUS_TSD))
- flags[port] &= ~SYV682X_FLAGS_SOURCE_ENABLED;
-
/*
* 5V OC is actually notifying that it is current limiting
* to 3.3A. If this happens for a long time, we will trip TSD
@@ -252,22 +259,53 @@ static void syv682x_handle_status_interrupt(int port, int regval)
} else if ((regval & SYV682X_STATUS_OC_5V) &&
(get_time().val > vbus_oc_timer[port].val)) {
vbus_oc_timer[port].val = UINT64_MAX;
- flags[port] &= ~SYV682X_FLAGS_5V_OC;
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_5V_OC);
syv682x_vbus_source_enable(port, 0);
pd_handle_overcurrent(port);
}
- if (syv682x_interrupt_filter(port, regval,
- SYV682X_STATUS_OC_HV | SYV682X_STATUS_TSD,
- SYV682X_FLAGS_OCP))
- pd_handle_overcurrent(port);
-
- /* No PD handler for VBUS OVP/RVS events */
-
+ /*
+ * No PD handling for VBUS OVP or TSD events.
+ * For TSD, this means we are in danger of burning the device so turn
+ * everything off and leave it off. The power paths will be
+ * automatically disabled.
+ * In the case of OVP, the channels will be
+ * disabled but don't unset the sink flag, since a sink OCP can
+ * inadvertently cause an OVP, and we'd want to re-enable the sink
+ * path in that situation.
+ */
+ if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_TSD,
+ SYV682X_FLAGS_TSD)) {
+ ppc_prints("TSD!", port);
+ atomic_clear_bits(&flags[port],
+ SYV682X_FLAGS_SOURCE_ENABLED |
+ SYV682X_FLAGS_SINK_ENABLED);
+ }
if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_OVP,
SYV682X_FLAGS_OVP)) {
ppc_prints("VBUS OVP!", port);
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED);
+ }
+
+ /*
+ * HV OC is a hard limit that will disable the sink path (automatically
+ * removing this alert condition), so try re-enabling if we hit an OCP.
+ * If we get multiple OCPs, don't re-enable. The OCP counter is reset on
+ * the sink path being explicitly disabled or on a PPC init.
+ */
+ if (regval & SYV682X_STATUS_OC_HV) {
+ ppc_prints("Sink OCP!", port);
+ atomic_add(&sink_ocp_count[port], 1);
+ if ((sink_ocp_count[port] < OCP_COUNT_LIMIT) &&
+ (flags[port] & SYV682X_FLAGS_SINK_ENABLED)) {
+ syv682x_vbus_sink_enable(port, 1);
+ } else {
+ ppc_prints("Disable sink", port);
+ atomic_clear_bits(&flags[port],
+ SYV682X_FLAGS_SINK_ENABLED);
+ }
}
+
if (syv682x_interrupt_filter(port, regval, SYV682X_STATUS_RVS,
SYV682X_FLAGS_RVS)) {
ppc_prints("VBUS Reverse Voltage!", port);
@@ -292,7 +330,8 @@ static int syv682x_handle_control_4_interrupt(int port, int regval)
} else if ((regval & SYV682X_CONTROL_4_VCONN_OCP) &&
(get_time().val > vconn_oc_timer[port].val)) {
vconn_oc_timer[port].val = UINT64_MAX;
- flags[port] &= ~SYV682X_FLAGS_VCONN_OCP;
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_VCONN_OCP);
+
/* Disable VCONN */
regval &=
~(SYV682X_CONTROL_4_VCONN2 | SYV682X_CONTROL_4_VCONN1);
@@ -322,11 +361,20 @@ static int syv682x_vbus_sink_enable(int port, int enable)
int regval;
int rv;
- if (!enable && syv682x_is_sourcing_vbus(port)) {
+ if (!enable) {
+ atomic_clear(&sink_ocp_count[port]);
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_SINK_ENABLED);
/*
* We're currently a source, so nothing more to do
*/
- return EC_SUCCESS;
+ if (syv682x_is_sourcing_vbus(port))
+ return EC_SUCCESS;
+ } else if (sink_ocp_count[port] > OCP_COUNT_LIMIT) {
+ /*
+ * Don't re-enable the channel until an explicit sink disable
+ * resets the ocp counter.
+ */
+ return EC_ERROR_UNKNOWN;
}
/*
@@ -345,7 +393,8 @@ static int syv682x_vbus_sink_enable(int port, int enable)
SYV682X_CONTROL_1_PWR_ENB);
/* Set sink current limit to the configured value */
regval |= CONFIG_SYV682X_HV_ILIM << SYV682X_HV_ILIM_BIT_SHIFT;
- flags[port] &= ~SYV682X_FLAGS_SOURCE_ENABLED;
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_SOURCE_ENABLED);
+ atomic_or(&flags[port], SYV682X_FLAGS_SINK_ENABLED);
} else {
/*
* No need to change the voltage path or channel direction. But,
@@ -384,9 +433,9 @@ static int syv682x_is_vbus_present(int port)
usb_charger_vbus_change(port, vbus);
if (vbus)
- flags[port] |= SYV682X_FLAGS_VBUS_PRESENT;
+ atomic_or(&flags[port], SYV682X_FLAGS_VBUS_PRESENT);
else
- flags[port] &= ~SYV682X_FLAGS_VBUS_PRESENT;
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_VBUS_PRESENT);
#endif
return vbus;
@@ -438,9 +487,9 @@ static int syv682x_set_polarity(int port, int polarity)
* polarity = CC1.
*/
if (polarity)
- flags[port] |= SYV682X_FLAGS_CC_POLARITY;
+ atomic_or(&flags[port], SYV682X_FLAGS_CC_POLARITY);
else
- flags[port] &= ~SYV682X_FLAGS_CC_POLARITY;
+ atomic_clear_bits(&flags[port], SYV682X_FLAGS_CC_POLARITY);
return EC_SUCCESS;
}
@@ -670,6 +719,7 @@ static int syv682x_init(int port)
rv = read_reg(port, SYV682X_CONTROL_1_REG, &control_1);
if (rv)
return rv;
+ atomic_clear(&sink_ocp_count[port]);
if (!syv682x_is_sink(control_1)
|| (status & SYV682X_STATUS_VSAFE_0V)) {