summaryrefslogtreecommitdiff
path: root/common/usbc/usb_retimer_fw_update.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/usbc/usb_retimer_fw_update.c')
-rw-r--r--common/usbc/usb_retimer_fw_update.c188
1 files changed, 164 insertions, 24 deletions
diff --git a/common/usbc/usb_retimer_fw_update.c b/common/usbc/usb_retimer_fw_update.c
index 1c3023db9b..3f9b1b4c72 100644
--- a/common/usbc/usb_retimer_fw_update.c
+++ b/common/usbc/usb_retimer_fw_update.c
@@ -1,10 +1,12 @@
-/* Copyright 2021 The Chromium OS Authors. All rights reserved.
+/* Copyright 2021 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <stdbool.h>
#include <stdint.h>
+
+#include "builtin/assert.h"
#include "compile_time_macros.h"
#include "console.h"
#include "hooks.h"
@@ -14,14 +16,20 @@
#include "usb_tc_sm.h"
#ifdef CONFIG_COMMON_RUNTIME
-#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
-#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_USBPD, format, ##args)
+#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ##args)
#else
#define CPRINTS(format, args...)
#define CPRINTF(format, args...)
#endif
/*
+ * Update retimer firmware of no device attached (NDA) ports
+ *
+ * https://docs.kernel.org/admin-guide/thunderbolt.html#
+ * upgrading-on-board-retimer-nvm-when-there-is-no-cable-connected
+ *
+ * On EC side:
* Retimer firmware update is initiated by AP.
* The operations requested by AP are:
* 0 - USB_RETIMER_FW_UPDATE_QUERY_PORT
@@ -43,10 +51,52 @@
* If 4/5/6/7 is received, TC_FLAGS_USB_RETIMER_FW_UPDATE_LTD_RUN is
* set, PD task should be in suspended mode and process it.
*
+ * On host side:
+ * 1. Put NDA ports into offline mode.
+ * This forces retimer to power on, and requests EC to suspend
+ * PD port, set USB mux to USB, Safe then TBT.
+ * 2. Scan for retimers
+ * 3. Update retimer NVM firmware.
+ * 4. Authenticate.
+ * 5. Wait 5 or more seconds for retimer to come back.
+ * 6. Put NDA ports into online mode -- the functional state.
+ * This requestes EC to disconnect(set USB mux to 0), resume PD port.
+ *
+ * Error recovery:
+ * As mentioned above, to put port online, host sends two requests to EC
+ * 1. Disconnect USB MUX: USB_RETIMER_FW_UPDATE_DISCONNECT
+ * if step 1 is successful, then
+ * 2. Resume PD port: USB_RETIMER_FW_UPDATE_RESUME_PD
+ *
+ * If step 1 fails, host will not send step 2. This means no
+ * resume request from host. PD port stays in suspended state.
+ * EC needs an error recovery to resume PD port by itself.
+ *
+ * Below is how error recovery works:
+ * PD port state is set to RETIMER_ONLINE_REQUESTED when receives
+ * "Disconnect USB MUX"; a deferred call is set up too. When EC resumes
+ * port upon host's request, port state will be set to RETIMER_ONLINE;
+ * or port state stays RETIMER_ONLINE_REQUESTED if host doesn't request.
+ * By the time the deferrred call is fired, it will check if any port is
+ * still in RETIMER_ONLINE_REQUESTED state. If true, EC will put the
+ * port online by itself. That is, retry disconnect and unconditionally
+ * resume the port.
*/
#define SUSPEND 1
-#define RESUME 0
+#define RESUME 0
+
+enum retimer_port_state {
+ RETIMER_ONLINE,
+ RETIMER_OFFLINE,
+ RETIMER_ONLINE_REQUESTED
+};
+
+/*
+ * Two seconds buffer is added on top of required 5 seconds;
+ * to cover the time to disconnect and resume.
+ */
+#define RETIMTER_ONLINE_DELAY (7 * SECOND)
/* Track current port AP requested to update retimer firmware */
static int cur_port;
@@ -54,7 +104,7 @@ static int last_op; /* Operation received from AP via ACPI_WRITE */
/* Operation result returned to ACPI_READ */
static int last_result;
/* Track port state: SUSPEND or RESUME */
-static int port_state[CONFIG_USB_PD_PORT_MAX_COUNT];
+static enum retimer_port_state port_state[CONFIG_USB_PD_PORT_MAX_COUNT];
int usb_retimer_fw_update_get_result(void)
{
@@ -87,12 +137,13 @@ int usb_retimer_fw_update_get_result(void)
return result;
}
-static void retimer_fw_update_set_port_state(int port, int state)
+static void retimer_fw_update_set_port_state(int port,
+ enum retimer_port_state state)
{
port_state[port] = state;
}
-static int retimer_fw_update_get_port_state(int port)
+static enum retimer_port_state retimer_fw_update_get_port_state(int port)
{
return port_state[port];
}
@@ -101,16 +152,16 @@ static int retimer_fw_update_get_port_state(int port)
* @brief Suspend or resume PD task and update the state of the port.
*
* @param port PD port
- * @param state
- * SUSPEND: suspend PD task for firmware update; and set state to SUSPEND
- * RESUME: resume PD task after firmware update is done; and set state
- * to RESUME.
+ * @param suspend
+ * SUSPEND: suspend PD task; set state to RETIMER_OFFLINE
+ * RESUME: resume PD task; set state to RETIMER_ONLINE.
*
*/
-static void retimer_fw_update_port_handler(int port, int state)
+static void retimer_fw_update_port_handler(int port, bool suspend)
{
- pd_set_suspend(port, state);
- retimer_fw_update_set_port_state(port, state);
+ pd_set_suspend(port, suspend);
+ retimer_fw_update_set_port_state(
+ port, suspend == SUSPEND ? RETIMER_OFFLINE : RETIMER_ONLINE);
}
static void deferred_pd_suspend(void)
@@ -124,14 +175,74 @@ static inline mux_state_t retimer_fw_update_usb_mux_get(int port)
return usb_mux_get(port) & USB_RETIMER_FW_UPDATE_MUX_MASK;
}
+/*
+ * Host will wait maximum 300ms for result; otherwise it's error.
+ * so the polling takes 300ms too.
+ */
+#define POLLING_CYCLE 15
+#define POLLING_TIME_MS 20
+
+static bool query_usb_mux_set_completed_timeout(int port)
+{
+ int i;
+
+ for (i = 0; i < POLLING_CYCLE; i++) {
+ if (!usb_mux_set_completed(port))
+ msleep(POLLING_TIME_MS);
+ else
+ return false;
+ }
+
+ return true;
+}
+
+static void retry_online(int port)
+{
+ usb_mux_set(port, USB_PD_MUX_NONE, USB_SWITCH_DISCONNECT,
+ pd_get_polarity(port));
+ /* Wait maximum 300 ms for USB mux to be set */
+ query_usb_mux_set_completed_timeout(port);
+ /* Resume the port unconditionally */
+ retimer_fw_update_port_handler(port, RESUME);
+}
+
+/*
+ * After NVM update, if AP skips step 5, not wait 5+ seconds for retimer
+ * to come back; then do step 6 immediately, requesting EC to put
+ * retimer online. Step 6 will fail; port is still offline afterwards.
+ *
+ * This deferred function monitors if any port has this problem and retry
+ * online one more time.
+ */
+static void retimer_check_online(void)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) {
+ if (retimer_fw_update_get_port_state(i) ==
+ RETIMER_ONLINE_REQUESTED) {
+ /*
+ * Now the time has passed RETIMTER_ONLINE_DELAY;
+ * retry online.
+ * The port is suspended; if the port is not
+ * suspended, DISCONNECT request won't go through,
+ * we couldn't be here.
+ */
+ retry_online(i);
+ /* PD port is resumed */
+ }
+ }
+}
+DECLARE_DEFERRED(retimer_check_online);
+
/* Allow mux results to be filled in during HOOKS if needed */
static void last_result_mux_get(void);
DECLARE_DEFERRED(last_result_mux_get);
static void last_result_mux_get(void)
{
- if (!usb_mux_set_completed(cur_port)) {
- hook_call_deferred(&last_result_mux_get_data, 20 * MSEC);
+ if (query_usb_mux_set_completed_timeout(cur_port)) {
+ last_result = USB_RETIMER_FW_UPDATE_ERR;
return;
}
@@ -175,8 +286,8 @@ void usb_retimer_fw_update_process_op_cb(int port)
result_mux_get = true;
break;
case USB_RETIMER_FW_UPDATE_SET_USB:
- usb_mux_set(port, USB_PD_MUX_USB_ENABLED,
- USB_SWITCH_CONNECT, pd_get_polarity(port));
+ usb_mux_set(port, USB_PD_MUX_USB_ENABLED, USB_SWITCH_CONNECT,
+ pd_get_polarity(port));
result_mux_get = true;
break;
case USB_RETIMER_FW_UPDATE_SET_SAFE:
@@ -185,13 +296,21 @@ void usb_retimer_fw_update_process_op_cb(int port)
break;
case USB_RETIMER_FW_UPDATE_SET_TBT:
usb_mux_set(port, USB_PD_MUX_TBT_COMPAT_ENABLED,
- USB_SWITCH_CONNECT, pd_get_polarity(port));
+ USB_SWITCH_CONNECT, pd_get_polarity(port));
result_mux_get = true;
break;
case USB_RETIMER_FW_UPDATE_DISCONNECT:
- usb_mux_set(port, USB_PD_MUX_NONE,
- USB_SWITCH_DISCONNECT, pd_get_polarity(port));
+ usb_mux_set(port, USB_PD_MUX_NONE, USB_SWITCH_DISCONNECT,
+ pd_get_polarity(port));
result_mux_get = true;
+ /*
+ * Host decides to put retimer online; now disconnects USB MUX
+ * and sets port state to "RETIMER_ONLINE_REQUESTED".
+ */
+ retimer_fw_update_set_port_state(port,
+ RETIMER_ONLINE_REQUESTED);
+ hook_call_deferred(&retimer_check_online_data,
+ RETIMTER_ONLINE_DELAY);
break;
default:
break;
@@ -210,12 +329,32 @@ void usb_retimer_fw_update_process_op(int port, int op)
ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_MAX_COUNT);
/*
- * TODO(b/179220036): check not overlapping requests;
- * not change cur_port if retimer scan is in progress
+ * The order of requests from host are:
+ *
+ * Port 0 offline
+ * Port 0 rescan retimers
+ * Port 1 offline
+ * Port 1 rescan retimers
+ * ...
+ * Port 0 online
+ * Port 1 online
+ * ...
*/
last_op = op;
cur_port = port;
+ /*
+ * Host has requested to put this port back online, and haven't
+ * finished online process. During this period, don't accept any
+ * requests, except USB_RETIMER_FW_UPDATE_RESUME_PD.
+ */
+ if (port_state[port] == RETIMER_ONLINE_REQUESTED) {
+ if (op != USB_RETIMER_FW_UPDATE_RESUME_PD) {
+ last_result = USB_RETIMER_FW_UPDATE_ERR;
+ return;
+ }
+ }
+
switch (op) {
case USB_RETIMER_FW_UPDATE_QUERY_PORT:
break;
@@ -253,9 +392,10 @@ static void restore_port(void)
{
int port;
- for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; port++) {
+ for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; port++) {
if (retimer_fw_update_get_port_state(port))
retimer_fw_update_port_handler(port, RESUME);
}
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, restore_port, HOOK_PRIO_DEFAULT);
+DECLARE_HOOK(HOOK_CHIPSET_RESET, restore_port, HOOK_PRIO_DEFAULT);