summaryrefslogtreecommitdiff
path: root/driver/usb_mux/usb_mux.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/usb_mux/usb_mux.c')
-rw-r--r--driver/usb_mux/usb_mux.c247
1 files changed, 229 insertions, 18 deletions
diff --git a/driver/usb_mux/usb_mux.c b/driver/usb_mux/usb_mux.c
index 155ba8fb3e..037757cd98 100644
--- a/driver/usb_mux/usb_mux.c
+++ b/driver/usb_mux/usb_mux.c
@@ -11,6 +11,7 @@
#include "chipset.h"
#include "hooks.h"
#include "host_command.h"
+#include "queue.h"
#include "task.h"
#include "timer.h"
#include "usb_mux.h"
@@ -46,6 +47,10 @@ static mutex_t mux_lock[CONFIG_USB_PD_PORT_MAX_COUNT];
static task_id_t ack_task[CONFIG_USB_PD_PORT_MAX_COUNT] = {
[0 ... CONFIG_USB_PD_PORT_MAX_COUNT - 1] = TASK_ID_INVALID };
+static void perform_mux_set(int port, mux_state_t mux_mode,
+ enum usb_switch usb_mode, int polarity);
+static void perform_mux_hpd_update(int port, mux_state_t hpd_state);
+
enum mux_config_type {
USB_MUX_INIT,
USB_MUX_LOW_POWER,
@@ -55,19 +60,174 @@ enum mux_config_type {
USB_MUX_HPD_UPDATE,
};
+/* Define a USB mux task ID for the purpose of linking */
+#ifndef HAS_TASK_USB_MUX
+#define TASK_ID_USB_MUX TASK_ID_INVALID
+#endif
+
+/*
+ * USB mux task
+ *
+ * Since USB mux sets can take extended periods of time (on the order of 100s of
+ * ms for some muxes), run a small task to complete those mux sets in order to
+ * not block the PD task. Run HPD sets from this task as well, since they
+ * should be sequenced behind setting up the mux pins for DP.
+ *
+ * Depth must be a power of 2, which is normally enforced by the queue init
+ * code, but must be manually enforced here.
+ */
+#define MUX_QUEUE_DEPTH 4
+BUILD_ASSERT(POWER_OF_TWO(MUX_QUEUE_DEPTH));
+
+struct mux_queue_entry {
+ enum mux_config_type type;
+ mux_state_t mux_mode; /* For both HPD and mux set */
+ enum usb_switch usb_config; /* Set only */
+ int polarity; /* Set only */
+};
+
+/*
+ * Note: test builds won't optimize out the mux task code and thereby require
+ * the queue to link
+ */
+#if defined(TEST_BUILD) || defined(HAS_TASK_USB_MUX)
+/*
+ * Note: QUEUE macros cannot be used to initialize this array, since they rely
+ * on anonymous data structs for allocation which results in all entries
+ * sharing the same state pointer and data buffers.
+ */
+static struct queue mux_queue[CONFIG_USB_PD_PORT_MAX_COUNT];
+__maybe_unused static struct queue_state
+ queue_states[CONFIG_USB_PD_PORT_MAX_COUNT];
+__maybe_unused static struct mux_queue_entry
+ queue_buffers[CONFIG_USB_PD_PORT_MAX_COUNT]
+ [MUX_QUEUE_DEPTH];
+static mutex_t queue_lock[CONFIG_USB_PD_PORT_MAX_COUNT];
+#else
+extern struct queue const mux_queue[];
+extern mutex_t queue_lock[];
+#endif
+
#ifdef CONFIG_ZEPHYR
static int init_mux_mutex(const struct device *dev)
{
int port;
ARG_UNUSED(dev);
- for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; port++)
+ for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; port++) {
k_mutex_init(&mux_lock[port]);
+
+ if (IS_ENABLED(HAS_TASK_USB_MUX))
+ k_mutex_init(&queue_lock[port]);
+ }
+
return 0;
}
SYS_INIT(init_mux_mutex, POST_KERNEL, 50);
#endif /* CONFIG_ZEPHYR */
+__maybe_unused static void mux_task_enqueue(int port, enum mux_config_type type,
+ mux_state_t mux_mode,
+ enum usb_switch usb_config,
+ int polarity)
+{
+ struct mux_queue_entry new_entry;
+
+ if (!IS_ENABLED(HAS_TASK_USB_MUX))
+ return;
+
+ new_entry.type = type;
+ new_entry.mux_mode = mux_mode;
+ new_entry.usb_config = usb_config;
+ new_entry.polarity = polarity;
+
+ mutex_lock(&queue_lock[port]);
+
+ if (queue_add_unit(&mux_queue[port], &new_entry) == 0)
+ CPRINTS("Error: Dropping port %d mux %d", port, type);
+ else
+ task_wake(TASK_ID_USB_MUX);
+
+ mutex_unlock(&queue_lock[port]);
+}
+
+#ifdef HAS_TASK_USB_MUX
+static void init_queue_structs(void)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) {
+ mux_queue[i].state = &queue_states[i];
+ mux_queue[i].policy = &queue_policy_null;
+ mux_queue[i].buffer_units = MUX_QUEUE_DEPTH;
+ mux_queue[i].buffer_units_mask = MUX_QUEUE_DEPTH - 1;
+ mux_queue[i].unit_bytes = sizeof(struct mux_queue_entry);
+ mux_queue[i].buffer = (uint8_t *) &queue_buffers[i][0];
+ }
+}
+DECLARE_HOOK(HOOK_INIT, init_queue_structs, HOOK_PRIO_FIRST);
+#endif
+
+__maybe_unused void usb_mux_task(void *u)
+{
+ bool items_waiting = true;
+
+ while (1) {
+ int port;
+
+ /* Wait if we had no queue items to service */
+ if (!items_waiting)
+ task_wait_event(-1);
+
+ items_waiting = false;
+
+ /*
+ * Round robin the ports, so no one port can monopolize the task
+ */
+ for (port = 0; port < board_get_usb_pd_port_count(); port++) {
+ if (queue_count(&mux_queue[port])) {
+ /*
+ * Process our first item. Leave it in the
+ * queue until we've completed its operation so
+ * the PD task can tell it is still pending.
+ * Note this should be safe to do unlocked, as
+ * this task is the only one which changes the
+ * queue head.
+ */
+ struct mux_queue_entry next;
+
+ queue_peek_units(&mux_queue[port], &next, 0, 1);
+
+ if (next.type == USB_MUX_SET_MODE)
+ perform_mux_set(port, next.mux_mode,
+ next.usb_config,
+ next.polarity);
+ else if (next.type == USB_MUX_HPD_UPDATE)
+ perform_mux_hpd_update(port,
+ next.mux_mode);
+ else
+ CPRINTS("Error: Unknown mux task type:"
+ "%d", next.type);
+
+ /*
+ * Lock since the tail is changing, which would
+ * disrupt any calls iterating the queue.
+ */
+ mutex_lock(&queue_lock[port]);
+ queue_advance_head(&mux_queue[port], 1);
+ mutex_unlock(&queue_lock[port]);
+
+ /*
+ * Force the task to run again if this queue has
+ * more items to process.
+ */
+ if (queue_count(&mux_queue[port]))
+ items_waiting = true;
+ }
+ }
+ }
+}
+
/* Configure the MUX */
static int configure_mux(int port,
enum mux_config_type config,
@@ -173,8 +333,16 @@ static int configure_mux(int port,
mutex_unlock(&mux_lock[port]);
if (ack_required) {
- /* This should only be called from the PD task */
- assert(port == TASK_ID_TO_PD_PORT(task_get_current()));
+ /*
+ * This should only be called from the PD task or usb
+ * mux task
+ */
+ if (IS_ENABLED(HAS_TASK_USB_MUX)) {
+ assert(task_get_current() == TASK_ID_USB_MUX);
+ } else {
+ assert(port ==
+ TASK_ID_TO_PD_PORT(task_get_current()));
+ }
/*
* Note: This task event could be generalized for more
@@ -254,22 +422,14 @@ void usb_mux_init(int port)
atomic_clear_bits(&flags[port], USB_MUX_FLAG_IN_LPM);
}
-/*
- * TODO(crbug.com/505480): Setting muxes often involves I2C transcations,
- * which can block. Consider implementing an asynchronous task.
- */
-void usb_mux_set(int port, mux_state_t mux_mode,
- enum usb_switch usb_mode, int polarity)
+static void perform_mux_set(int port, mux_state_t mux_mode,
+ enum usb_switch usb_mode, int polarity)
{
mux_state_t mux_state;
const int should_enter_low_power_mode =
(mux_mode == USB_PD_MUX_NONE &&
usb_mode == USB_SWITCH_DISCONNECT);
- if (port >= board_get_usb_pd_port_count()) {
- return;
- }
-
/* Perform initialization if not initialized yet */
if (!(flags[port] & USB_MUX_FLAG_INIT))
usb_mux_init(port);
@@ -310,6 +470,48 @@ void usb_mux_set(int port, mux_state_t mux_mode,
enter_low_power_mode(port);
}
+void usb_mux_set(int port, mux_state_t mux_mode,
+ enum usb_switch usb_mode, int polarity)
+{
+ if (port >= board_get_usb_pd_port_count())
+ return;
+
+ /* Block if we have no mux task, but otherwise queue it up and return */
+ if (IS_ENABLED(HAS_TASK_USB_MUX))
+ mux_task_enqueue(port, USB_MUX_SET_MODE, mux_mode,
+ usb_mode, polarity);
+ else
+ perform_mux_set(port, mux_mode, usb_mode, polarity);
+}
+
+bool usb_mux_set_completed(int port)
+{
+ bool sets_pending = false;
+ struct queue_iterator it;
+
+ /* No mux task, no items waiting to process */
+ if (!IS_ENABLED(HAS_TASK_USB_MUX))
+ return true;
+
+ /* Lock the queue so we can scroll through the items left to do */
+ mutex_lock(&queue_lock[port]);
+
+ for (queue_begin(&mux_queue[port], &it); it.ptr != NULL;
+ queue_next(&mux_queue[port], &it)) {
+ const struct mux_queue_entry *check =
+ (struct mux_queue_entry *) it.ptr;
+
+ if (check->type == USB_MUX_SET_MODE) {
+ sets_pending = true;
+ break;
+ }
+ }
+
+ mutex_unlock(&queue_lock[port]);
+
+ return !sets_pending;
+}
+
mux_state_t usb_mux_get(int port)
{
mux_state_t mux_state;
@@ -357,12 +559,8 @@ void usb_mux_flip(int port)
configure_mux(port, USB_MUX_SET_MODE, &mux_state);
}
-void usb_mux_hpd_update(int port, mux_state_t hpd_state)
+static void perform_mux_hpd_update(int port, mux_state_t hpd_state)
{
- if (port >= board_get_usb_pd_port_count()) {
- return;
- }
-
/* Perform initialization if not initialized yet */
if (!(flags[port] & USB_MUX_FLAG_INIT))
usb_mux_init(port);
@@ -373,6 +571,19 @@ void usb_mux_hpd_update(int port, mux_state_t hpd_state)
configure_mux(port, USB_MUX_HPD_UPDATE, &hpd_state);
}
+void usb_mux_hpd_update(int port, mux_state_t hpd_state)
+{
+ if (port >= board_get_usb_pd_port_count())
+ return;
+
+ /* Send to the mux task if present to maintain sequencing with sets */
+ if (IS_ENABLED(HAS_TASK_USB_MUX))
+ mux_task_enqueue(port, USB_MUX_HPD_UPDATE, hpd_state,
+ 0, 0);
+ else
+ perform_mux_hpd_update(port, hpd_state);
+}
+
int usb_mux_retimer_fw_update_port_info(void)
{
int i;