summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCaveh Jalali <caveh@chromium.org>2021-02-17 03:17:55 -0800
committerCommit Bot <commit-bot@chromium.org>2021-02-25 11:49:42 +0000
commit2b470623642407eae6a35a89dad145adb004b3ef (patch)
tree0ff3acea1d05eac5e51533a41e785fe46578ad50
parente3b28ee8e17d4292785e01f2b920a877e5add1fa (diff)
downloadchrome-ec-2b470623642407eae6a35a89dad145adb004b3ef.tar.gz
common: Add new shared PD interrupt handler
This adds a new TCPC interrupt handler task that knows how to deal with TCPCs that share a single interrupt line. ec.tasklist for this handler takes a port mask instead of a port number as a task startup argument. The main quirk of shared interrupts is that interrupts must be serviced on multiple devcies in order to clear the interrupt into the EC. BRANCH=none BUG=b:173575131 TEST=buildall passes Change-Id: I6f9f0ce471092741274d8a1542510d92100f6698 Signed-off-by: Caveh Jalali <caveh@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2700314
-rw-r--r--common/usbc_intr_task.c173
1 files changed, 130 insertions, 43 deletions
diff --git a/common/usbc_intr_task.c b/common/usbc_intr_task.c
index cb59882d7a..7470696c18 100644
--- a/common/usbc_intr_task.c
+++ b/common/usbc_intr_task.c
@@ -38,6 +38,38 @@ void schedule_deferred_pd_interrupt(const int port)
task_set_event(pd_int_task_id[port], PD_PROCESS_INTERRUPT);
}
+static struct {
+ int count;
+ timestamp_t time;
+} storm_tracker[CONFIG_USB_PD_PORT_MAX_COUNT];
+
+static void service_one_port(int port)
+{
+ timestamp_t now;
+
+ tcpc_alert(port);
+
+ now = get_time();
+ if (timestamp_expired(storm_tracker[port].time,
+ &now)) {
+ /* Reset timer into future */
+ storm_tracker[port].time.val = now.val + ALERT_STORM_INTERVAL;
+
+ /*
+ * Start at 1 since we are processing an interrupt right
+ * now
+ */
+ storm_tracker[port].count = 1;
+ } else if (++storm_tracker[port].count > ALERT_STORM_MAX_COUNT) {
+ CPRINTS("C%d: Interrupt storm detected."
+ " Disabling port temporarily",
+ port);
+
+ pd_set_suspend(port, 1);
+ pd_deferred_resume(port);
+ }
+}
+
/*
* Main task entry point that handles PD interrupts for a single port
*
@@ -48,10 +80,6 @@ void pd_interrupt_handler_task(void *p)
{
const int port = (int) ((intptr_t) p);
const int port_mask = (PD_STATUS_TCPC_ALERT_0 << port);
- struct {
- int count;
- timestamp_t time;
- } storm_tracker[CONFIG_USB_PD_PORT_MAX_COUNT] = {};
ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_MAX_COUNT);
@@ -66,46 +94,105 @@ void pd_interrupt_handler_task(void *p)
while (1) {
const int evt = task_wait_event(-1);
- if (evt & PD_PROCESS_INTERRUPT) {
- /*
- * While the interrupt signal is asserted; we have more
- * work to do. This effectively makes the interrupt a
- * level-interrupt instead of an edge-interrupt without
- * having to enable/disable a real level-interrupt in
- * multiple locations.
- *
- * Also, if the port is disabled do not process
- * interrupts. Upon existing suspend, we schedule a
- * PD_PROCESS_INTERRUPT to check if we missed anything.
- */
- while ((tcpc_get_alert_status() & port_mask) &&
- pd_is_port_enabled(port)) {
- timestamp_t now;
-
- tcpc_alert(port);
-
- now = get_time();
- if (timestamp_expired(storm_tracker[port].time,
- &now)) {
- /* Reset timer into future */
- storm_tracker[port].time.val =
- now.val + ALERT_STORM_INTERVAL;
-
- /*
- * Start at 1 since we are processing an
- * interrupt right now
- */
- storm_tracker[port].count = 1;
- } else if (++storm_tracker[port].count >
- ALERT_STORM_MAX_COUNT) {
- CPRINTS("C%d: Interrupt storm detected."
- " Disabling port temporarily",
- port);
-
- pd_set_suspend(port, 1);
- pd_deferred_resume(port);
+ if ((evt & PD_PROCESS_INTERRUPT) == 0)
+ continue;
+ /*
+ * While the interrupt signal is asserted; we have more
+ * work to do. This effectively makes the interrupt a
+ * level-interrupt instead of an edge-interrupt without
+ * having to enable/disable a real level-interrupt in
+ * multiple locations.
+ *
+ * Also, if the port is disabled do not process
+ * interrupts. Upon existing suspend, we schedule a
+ * PD_PROCESS_INTERRUPT to check if we missed anything.
+ */
+ while ((tcpc_get_alert_status() & port_mask) &&
+ pd_is_port_enabled(port)) {
+
+ service_one_port(port);
+ }
+ }
+}
+
+/*
+ * This code assumes port alert masks are adjacent to each other.
+ */
+BUILD_ASSERT(PD_STATUS_TCPC_ALERT_3 == (PD_STATUS_TCPC_ALERT_0 << 3));
+
+/*
+ * Shared TCPC interrupt handler. The function argument in ec.tasklist
+ * is the mask of ports to handle. For example:
+ *
+ * BIT(USBC_PORT_C2) | BIT(USBC_PORT_C0)
+ *
+ * Note that this bitmask is 0-based while PD_STATUS_TCPC_ALERT_<port>
+ * is not.
+ */
+
+void pd_shared_alert_task(void *p)
+{
+ const int sources_mask = (int) ((intptr_t) p);
+ int want_alerts = 0;
+ int port;
+ int port_mask;
+
+ CPRINTS("%s: port mask 0x%02x", __func__, sources_mask);
+
+ for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; ++port) {
+ if ((sources_mask & BIT(port)) == 0)
+ continue;
+ if (!board_is_usb_pd_port_present(port))
+ continue;
+
+ port_mask = PD_STATUS_TCPC_ALERT_0 << port;
+ want_alerts |= port_mask;
+ pd_int_task_id[port] = task_get_current();
+ }
+
+ if (want_alerts == 0) {
+ /*
+ * None of the configured alert sources are available.
+ */
+ return;
+ }
+
+ while (1) {
+ const int evt = task_wait_event(-1);
+ int have_alerts;
+
+ if ((evt & PD_PROCESS_INTERRUPT) == 0)
+ continue;
+
+ /*
+ * While the interrupt signal is asserted; we have more
+ * work to do. This effectively makes the interrupt a
+ * level-interrupt instead of an edge-interrupt without
+ * having to enable/disable a real level-interrupt in
+ * multiple locations.
+ *
+ * Also, if the port is disabled do not process
+ * interrupts. Upon existing suspend, we schedule a
+ * PD_PROCESS_INTERRUPT to check if we missed anything.
+ */
+ do {
+ have_alerts = tcpc_get_alert_status();
+ have_alerts &= want_alerts;
+
+ for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT;
+ ++port) {
+ port_mask = PD_STATUS_TCPC_ALERT_0 << port;
+ if ((have_alerts & port_mask) == 0) {
+ /* skip quiet port */
+ continue;
+ }
+ if (!pd_is_port_enabled(port)) {
+ /* filter out disabled port */
+ have_alerts &= ~port_mask;
+ continue;
}
+ service_one_port(port);
}
- }
+ } while (have_alerts != 0);
}
}