summaryrefslogtreecommitdiff
path: root/driver/usb_mux
diff options
context:
space:
mode:
Diffstat (limited to 'driver/usb_mux')
-rw-r--r--driver/usb_mux/amd_fp6.c216
-rw-r--r--driver/usb_mux/amd_fp6.h30
2 files changed, 246 insertions, 0 deletions
diff --git a/driver/usb_mux/amd_fp6.c b/driver/usb_mux/amd_fp6.c
new file mode 100644
index 0000000000..a2d6311816
--- /dev/null
+++ b/driver/usb_mux/amd_fp6.c
@@ -0,0 +1,216 @@
+/* Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * AMD FP6 USB/DP Mux.
+ */
+
+#include "amd_fp6.h"
+#include "chipset.h"
+#include "common.h"
+#include "hooks.h"
+#include "i2c.h"
+#include "queue.h"
+#include "timer.h"
+#include "usb_mux.h"
+
+/*
+ * This may be shorter than the internal MUX timeout.
+ * Making it any longer could cause the PD task to miss messages.
+ */
+#define WRITE_CMD_TIMEOUT_MS 100
+
+/*
+ * Local data structure for saving mux state so it can be restored after
+ * an AP reset.
+ */
+static struct {
+ const struct usb_mux *mux;
+ mux_state_t state;
+} saved_mux_state[USBC_PORT_COUNT];
+
+static int amd_fp6_mux_port0_read(const struct usb_mux *me, uint8_t *val)
+{
+ uint8_t payload[3] = { 0 };
+ int rv;
+ bool mux_ready;
+
+ if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
+ return EC_ERROR_NOT_POWERED;
+
+ rv = i2c_xfer(me->i2c_port, me->i2c_addr_flags, NULL, 0, payload, 3);
+ if (rv)
+ return rv;
+
+ /*
+ * payload[0]: Status/ID
+ * payload[1]: Port 0 Control/Status
+ * payload[2]: Port 1 Control/Status (unused on FP6)
+ */
+ mux_ready = !!(payload[0] & AMD_FP6_MUX_PD_STATUS_READY);
+ if (!mux_ready)
+ return EC_ERROR_BUSY;
+ *val = payload[1];
+
+ return EC_SUCCESS;
+}
+
+static int amd_fp6_mux_port0_write(const struct usb_mux *me, uint8_t write_val)
+{
+ int rv;
+ uint8_t read_val;
+ uint8_t port_status;
+ timestamp_t start;
+
+ /* Check if mux is ready */
+ rv = amd_fp6_mux_port0_read(me, &read_val);
+ if (rv)
+ return rv;
+
+ /* Write control register */
+ rv = i2c_write8(me->i2c_port, me->i2c_addr_flags, 0, write_val);
+ if (rv)
+ return rv;
+
+ /*
+ * Read status until write command finishes or times out.
+ * The mux has an internal opaque timeout, which we wrap with our own
+ * timeout to be safe.
+ */
+ start = get_time();
+ while (time_since32(start) < WRITE_CMD_TIMEOUT_MS * MSEC) {
+ rv = amd_fp6_mux_port0_read(me, &read_val);
+ if (rv)
+ return rv;
+
+ port_status = read_val >> AMD_FP6_MUX_PORT_STATUS_OFFSET;
+
+ if (port_status == AMD_FP6_MUX_PORT_CMD_COMPLETE)
+ return EC_SUCCESS;
+ else if (port_status == AMD_FP6_MUX_PORT_CMD_TIMEOUT)
+ return EC_ERROR_TIMEOUT;
+ else if (port_status == AMD_FP6_MUX_PORT_CMD_BUSY)
+ msleep(WRITE_CMD_TIMEOUT_MS / 5);
+ else
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_ERROR_TIMEOUT;
+}
+
+static int amd_fp6_init(const struct usb_mux *me)
+{
+ return EC_SUCCESS;
+}
+
+static int amd_fp6_set_mux(const struct usb_mux *me, mux_state_t mux_state)
+{
+ uint8_t val;
+ int rv;
+
+ saved_mux_state[me->usb_port].mux = me;
+ saved_mux_state[me->usb_port].state = mux_state;
+
+ if (mux_state == USB_PD_MUX_NONE)
+ /*
+ * LOW_POWER must be set when connection mode is
+ * set to 00b (safe state)
+ */
+ val = AMD_FP6_MUX_MODE_SAFE | AMD_FP6_MUX_LOW_POWER;
+ else if ((mux_state & USB_PD_MUX_USB_ENABLED) &&
+ (mux_state & USB_PD_MUX_DP_ENABLED))
+ val = AMD_FP6_MUX_MODE_DOCK;
+ else if (mux_state & USB_PD_MUX_USB_ENABLED)
+ val = AMD_FP6_MUX_MODE_USB;
+ else if (mux_state & USB_PD_MUX_DP_ENABLED)
+ val = AMD_FP6_MUX_MODE_DP;
+ else {
+ ccprintf("Unhandled mux_state %x\n", mux_state);
+ return EC_ERROR_INVAL;
+ }
+
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ val |= AMD_FP6_MUX_ORIENTATION;
+
+ rv = amd_fp6_mux_port0_write(me, val);
+ /*
+ * This MUX is on the FP6 SoC. If that device is not powered then
+ * we either have to complain that it is not powered or if we were
+ * setting the state to OFF, then go ahead and report that we did
+ * it because a powered down MUX is off.
+ */
+ if (rv == EC_ERROR_NOT_POWERED && mux_state == USB_PD_MUX_NONE)
+ rv = EC_SUCCESS;
+ return rv;
+}
+
+static int amd_fp6_get_mux(const struct usb_mux *me, mux_state_t *mux_state)
+{
+ uint8_t val;
+ bool inverted;
+ uint8_t mode;
+ int rv;
+
+ rv = amd_fp6_mux_port0_read(me, &val);
+ /*
+ * This MUX is on the FP6 SoC. If that device is not powered then claim
+ * thestate to be NONE, which is SAFE.
+ */
+ if (rv == EC_ERROR_NOT_POWERED)
+ val = 0;
+ else if (rv)
+ return rv;
+
+ mode = (val & AMD_FP6_MUX_MODE_MASK);
+ inverted = !!(val & AMD_FP6_MUX_ORIENTATION);
+
+ if (mode == AMD_FP6_MUX_MODE_USB)
+ *mux_state = USB_PD_MUX_USB_ENABLED;
+ else if (mode == AMD_FP6_MUX_MODE_DP)
+ *mux_state = USB_PD_MUX_DP_ENABLED;
+ else if (mode == AMD_FP6_MUX_MODE_DOCK)
+ *mux_state = USB_PD_MUX_USB_ENABLED | USB_PD_MUX_DP_ENABLED;
+ else /* AMD_FP6_MUX_MODE_SAFE */
+ *mux_state = USB_PD_MUX_NONE;
+
+ if (inverted)
+ *mux_state |= USB_PD_MUX_POLARITY_INVERTED;
+
+ return EC_SUCCESS;
+}
+
+static void amd_fp6_chipset_reset_delay(void)
+{
+ int rv;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(saved_mux_state); i++) {
+ /* Check if saved_mux_state has been initialized */
+ if (saved_mux_state[i].mux == NULL)
+ continue;
+ rv = amd_fp6_set_mux(saved_mux_state[i].mux,
+ saved_mux_state[i].state);
+ if (rv)
+ ccprints("C%d restore mux rv:%d", i, rv);
+ }
+
+}
+DECLARE_DEFERRED(amd_fp6_chipset_reset_delay);
+
+/*
+ * The AP's internal USB-C mux is reset when AP resets, so wait for
+ * it to be ready and then restore the previous setting.
+ */
+static int amd_fp6_chipset_reset(const struct usb_mux *mux)
+{
+ /* TODO: Tune 200ms delay for FP6 */
+ hook_call_deferred(&amd_fp6_chipset_reset_delay_data, 200 * MSEC);
+ return EC_SUCCESS;
+}
+
+const struct usb_mux_driver amd_fp6_usb_mux_driver = {
+ .init = &amd_fp6_init,
+ .set = &amd_fp6_set_mux,
+ .get = &amd_fp6_get_mux,
+ .chipset_reset = &amd_fp6_chipset_reset,
+};
diff --git a/driver/usb_mux/amd_fp6.h b/driver/usb_mux/amd_fp6.h
new file mode 100644
index 0000000000..b5fb661353
--- /dev/null
+++ b/driver/usb_mux/amd_fp6.h
@@ -0,0 +1,30 @@
+/* Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * AMD FP6 USB/DP Mux.
+ */
+
+#ifndef __CROS_EC_USB_MUX_AMD_FP6_H
+#define __CROS_EC_USB_MUX_AMD_FP6_H
+
+#define AMD_FP6_C0_MUX_I2C_ADDR 0x5C
+#define AMD_FP6_C4_MUX_I2C_ADDR 0x52
+
+#define AMD_FP6_MUX_MODE_SAFE 0x0
+#define AMD_FP6_MUX_MODE_USB 0x1
+#define AMD_FP6_MUX_MODE_DP 0x2
+#define AMD_FP6_MUX_MODE_DOCK 0x3
+#define AMD_FP6_MUX_MODE_MASK GENMASK(1, 0)
+
+#define AMD_FP6_MUX_ORIENTATION BIT(4)
+#define AMD_FP6_MUX_LOW_POWER BIT(5)
+
+#define AMD_FP6_MUX_PORT_STATUS_OFFSET 6
+#define AMD_FP6_MUX_PORT_CMD_BUSY 0x0
+#define AMD_FP6_MUX_PORT_CMD_COMPLETE 0x1
+#define AMD_FP6_MUX_PORT_CMD_TIMEOUT 0x2
+
+#define AMD_FP6_MUX_PD_STATUS_READY BIT(5)
+
+#endif /* __CROS_EC_USB_MUX_AMD_FP6_H */