summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCaveh Jalali <caveh@chromium.org>2022-09-21 18:10:28 -0700
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2022-10-04 20:53:06 +0000
commit46e51dc78601066da3dfc91e8dc6fceb0e3ab88e (patch)
treef7f624950bddeb937ceef7e7bfc460d54483c8ba
parentf079a846def4856bb955d3b038171ff44d4887bb (diff)
downloadchrome-ec-46e51dc78601066da3dfc91e8dc6fceb0e3ab88e.tar.gz
usb_mux: Add support for idling USB muxes
This adds support for putting USB muxes in a non-active mode on chipset suspend (S0ix, S3). Some USB muxes consume a significant amount of power when the AP goes into suspend mode which has prompted some board support files to explicitly power down the USB3 portion of the Burnside Bridge. So, add a more general interface for notifying mux chip drivers about AP power state changes. This interface is optional and only kicks in for mux drivers that implement the new .set_idle_mode() method and the board's mux config specifies USB_MUX_FLAG_CAN_IDLE in usb_mux.flags. If a port is already considered to be in low power mode, no actions are taken on chipset suspend/resume. USB_MUX_FLAG_CAN_IDLE is intended to give boards full control over enabling this feature for each mux chip. This feature flag is disabled by default. BRANCH=none BUG=b:245753005 TEST='make buildall' passes './twister -v -T zephyr/test' passes Signed-off-by: Caveh Jalali <caveh@chromium.org> Change-Id: I93eda041e816398806eb29eb0625cca97c69c2ac Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3914992 Reviewed-by: Boris Mittelberg <bmbm@google.com> Code-Coverage: Zoss <zoss-cl-coverage@prod.google.com> Reviewed-by: Diana Z <dzigterman@chromium.org>
-rw-r--r--driver/usb_mux/usb_mux.c44
-rw-r--r--include/usb_mux.h17
-rw-r--r--zephyr/include/dt-bindings/usbc_mux.h1
-rw-r--r--zephyr/test/drivers/default/src/usb_mux.c46
4 files changed, 108 insertions, 0 deletions
diff --git a/driver/usb_mux/usb_mux.c b/driver/usb_mux/usb_mux.c
index 1edcf25179..edc9da5778 100644
--- a/driver/usb_mux/usb_mux.c
+++ b/driver/usb_mux/usb_mux.c
@@ -59,6 +59,8 @@ enum mux_config_type {
USB_MUX_LOW_POWER,
USB_MUX_SET_MODE,
USB_MUX_GET_MODE,
+ USB_MUX_CHIPSET_IDLE,
+ USB_MUX_CHIPSET_ACTIVE,
USB_MUX_CHIPSET_RESET,
USB_MUX_HPD_UPDATE,
};
@@ -306,6 +308,20 @@ static int configure_mux(int port, int index, enum mux_config_type config,
break;
+ case USB_MUX_CHIPSET_IDLE:
+ if ((mux_ptr->flags & USB_MUX_FLAG_CAN_IDLE) && drv &&
+ drv->set_idle_mode)
+ rv = drv->set_idle_mode(mux_ptr, true);
+
+ break;
+
+ case USB_MUX_CHIPSET_ACTIVE:
+ if ((mux_ptr->flags & USB_MUX_FLAG_CAN_IDLE) && drv &&
+ drv->set_idle_mode)
+ rv = drv->set_idle_mode(mux_ptr, false);
+
+ break;
+
case USB_MUX_CHIPSET_RESET:
if (drv && drv->chipset_reset)
rv = drv->chipset_reset(mux_ptr);
@@ -689,6 +705,34 @@ static void mux_chipset_reset(void)
}
DECLARE_HOOK(HOOK_CHIPSET_RESET, mux_chipset_reset, HOOK_PRIO_DEFAULT);
+static void mux_chipset_suspend(void)
+{
+ int port;
+
+ for (port = 0; port < board_get_usb_pd_port_count(); ++port) {
+ if (flags[port] & USB_MUX_FLAG_IN_LPM)
+ continue;
+
+ configure_mux(port, TYPEC_USB_MUX_SET_ALL_CHIPS,
+ USB_MUX_CHIPSET_IDLE, NULL);
+ }
+}
+DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, mux_chipset_suspend, HOOK_PRIO_DEFAULT);
+
+static void mux_chipset_resume(void)
+{
+ int port;
+
+ for (port = 0; port < board_get_usb_pd_port_count(); ++port) {
+ if (flags[port] & USB_MUX_FLAG_IN_LPM)
+ continue;
+
+ configure_mux(port, TYPEC_USB_MUX_SET_ALL_CHIPS,
+ USB_MUX_CHIPSET_ACTIVE, NULL);
+ }
+}
+DECLARE_HOOK(HOOK_CHIPSET_RESUME, mux_chipset_resume, HOOK_PRIO_DEFAULT);
+
/*
* For muxes which have powered off in G3, clear any cached INIT and LPM flags
* since the chip will need reset.
diff --git a/include/usb_mux.h b/include/usb_mux.h
index d510a347d4..bc89c0ea85 100644
--- a/include/usb_mux.h
+++ b/include/usb_mux.h
@@ -29,6 +29,7 @@
#define USB_MUX_FLAG_SET_WITHOUT_FLIP BIT(1) /* SET should not flip */
#define USB_MUX_FLAG_RESETS_IN_G3 BIT(2) /* Mux chip will reset in G3 */
#define USB_MUX_FLAG_POLARITY_INVERTED BIT(3) /* Mux polarity is inverted */
+#define USB_MUX_FLAG_CAN_IDLE BIT(4) /* MUX supports idle mode */
#endif /* CONFIG_ZEPHYR */
@@ -101,6 +102,22 @@ struct usb_mux_driver {
* @return EC_SUCCESS on success, non-zero error code on failure.
*/
int (*chipset_reset)(const struct usb_mux *me);
+
+ /**
+ * Optional method that is called on HOOK_CHIPSET_{SUSPEND,RESUME}.
+ *
+ * Note: This notifies the mux that the rest of the system
+ * entered (left) a low power state such as S0ix or S3. This
+ * enables the mux driver to make power optimization decisions
+ * such as powering down the USB3 retimer when not in use. If
+ * the associated port is in low power mode, idle mode is not
+ * used.
+ *
+ * @param me usb_mux
+ * @param idle bool
+ * @return EC_SUCCESS on success, non-zero error code on failure.
+ */
+ int (*set_idle_mode)(const struct usb_mux *me, bool idle);
};
/* Describes a USB mux present in the system */
diff --git a/zephyr/include/dt-bindings/usbc_mux.h b/zephyr/include/dt-bindings/usbc_mux.h
index 1d91542814..0c0a330fc9 100644
--- a/zephyr/include/dt-bindings/usbc_mux.h
+++ b/zephyr/include/dt-bindings/usbc_mux.h
@@ -15,5 +15,6 @@
#define USB_MUX_FLAG_SET_WITHOUT_FLIP BIT(1) /* SET should not flip */
#define USB_MUX_FLAG_RESETS_IN_G3 BIT(2) /* Mux chip will reset in G3 */
#define USB_MUX_FLAG_POLARITY_INVERTED BIT(3) /* Mux polarity is inverted */
+#define USB_MUX_FLAG_CAN_IDLE BIT(4) /* MUX supports idle mode */
#endif /* DT_BINDINGS_USBC_MUX_H_ */
diff --git a/zephyr/test/drivers/default/src/usb_mux.c b/zephyr/test/drivers/default/src/usb_mux.c
index 45b81d6ea5..abd27efbba 100644
--- a/zephyr/test/drivers/default/src/usb_mux.c
+++ b/zephyr/test/drivers/default/src/usb_mux.c
@@ -33,6 +33,8 @@ struct usb_mux_chain usb_mux_c1;
/** Number of usb mux proxies in chain */
#define NUM_OF_PROXY 3
+/** Number of usb mux proxies in chain that CAN_IDLE */
+#define NUM_OF_PROXY_CAN_IDLE 1
/** Pointers to original usb muxes chain of port c1 */
const struct usb_mux *org_mux[NUM_OF_PROXY];
@@ -177,6 +179,29 @@ static int proxy_chipset_reset_custom(const struct usb_mux *me)
return ec;
}
+/** Proxy function which check calls from usb_mux framework to driver */
+FAKE_VALUE_FUNC(int, proxy_set_idle_mode, const struct usb_mux *, bool);
+static int proxy_set_idle_mode_custom(const struct usb_mux *me, bool idle)
+{
+ int i = me->i2c_addr_flags;
+ int ec = EC_SUCCESS;
+
+ zassert_true(i < NUM_OF_PROXY, "Proxy called for non proxy usb_mux");
+
+ if (org_mux[i] != NULL && org_mux[i]->driver->set_idle_mode != NULL) {
+ ec = org_mux[i]->driver->set_idle_mode(org_mux[i], idle);
+ }
+
+ if (task_get_current() == TASK_ID_TEST_RUNNER) {
+ RETURN_FAKE_RESULT(proxy_set_idle_mode);
+ }
+
+ /* Discard this call if made from different thread */
+ proxy_set_idle_mode_fake.call_count--;
+
+ return ec;
+}
+
/** Proxy function for fw update capability */
static bool proxy_fw_update_cap(void)
{
@@ -211,6 +236,7 @@ const struct usb_mux_driver proxy_usb_mux = {
.get = &proxy_get,
.enter_low_power_mode = &proxy_enter_low_power_mode,
.chipset_reset = &proxy_chipset_reset,
+ .set_idle_mode = &proxy_set_idle_mode,
.is_retimer_fw_update_capable = &proxy_fw_update_cap,
};
@@ -254,6 +280,7 @@ static void reset_proxy_fakes(void)
RESET_FAKE(proxy_get);
RESET_FAKE(proxy_enter_low_power_mode);
RESET_FAKE(proxy_chipset_reset);
+ RESET_FAKE(proxy_set_idle_mode);
RESET_FAKE(proxy_hpd_update);
RESET_FAKE(mock_board_init);
RESET_FAKE(mock_board_set);
@@ -265,6 +292,7 @@ static void reset_proxy_fakes(void)
proxy_enter_low_power_mode_fake.custom_fake =
proxy_enter_low_power_mode_custom;
proxy_chipset_reset_fake.custom_fake = proxy_chipset_reset_custom;
+ proxy_set_idle_mode_fake.custom_fake = proxy_set_idle_mode_custom;
proxy_hpd_update_fake.custom_fake = proxy_hpd_update_custom;
mock_board_init_fake.custom_fake = mock_board_init_custom;
mock_board_set_fake.custom_fake = mock_board_set_custom;
@@ -275,6 +303,7 @@ static void reset_proxy_fakes(void)
proxy_get_fake.return_val = EC_SUCCESS;
proxy_enter_low_power_mode_fake.return_val = EC_SUCCESS;
proxy_chipset_reset_fake.return_val = EC_SUCCESS;
+ proxy_set_idle_mode_fake.return_val = EC_SUCCESS;
mock_board_init_fake.return_val = EC_SUCCESS;
mock_board_set_fake.return_val = EC_SUCCESS;
}
@@ -750,6 +779,23 @@ ZTEST(usb_init_mux, test_usb_mux_chipset_reset)
CHECK_PROXY_FAKE_CALL_CNT(proxy_chipset_reset, NUM_OF_PROXY);
}
+ZTEST(usb_init_mux, test_usb_mux_set_idle_mode)
+{
+ /* After the SUSPEND hook, set_idle_mode functions should be called */
+ proxy_mux_0.flags |= USB_MUX_FLAG_CAN_IDLE;
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+ CHECK_PROXY_FAKE_CALL_CNT(proxy_set_idle_mode, NUM_OF_PROXY_CAN_IDLE);
+
+ /*
+ * Other tests will fail if we leave the chipset in SUSPEND,
+ * so we test the RESUME case here.
+ */
+ proxy_set_idle_mode_fake.call_count = 0;
+ /* After the RESUME hook, set_idle_mode functions should be called */
+ hook_notify(HOOK_CHIPSET_RESUME);
+ CHECK_PROXY_FAKE_CALL_CNT(proxy_set_idle_mode, NUM_OF_PROXY_CAN_IDLE);
+}
+
/* Test host command get mux info */
ZTEST(usb_init_mux, test_usb_mux_hc_mux_info)
{