From 7cc4c6e351b346c650817d13b8fc9ef1d7f0b547 Mon Sep 17 00:00:00 2001 From: Rob Barnes Date: Tue, 26 Jan 2021 08:50:51 -0700 Subject: guybrush: Add FP6 USB mux driver FP6 USB mux driver is similar to the FP5 USB mux driver, but uses two addresses instead of an index to select the different USB ports. Also a low power bit has been added to the control register. Datasheet can be found in the bug. BUG=b:177057723 TEST=Build BRANCH=None Signed-off-by: Rob Barnes Change-Id: I94a3db5f7d643a17dfdf89d721dd7f14cb75e402 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2654387 Reviewed-by: Diana Z --- driver/usb_mux/amd_fp6.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++ driver/usb_mux/amd_fp6.h | 30 +++++++ 2 files changed, 246 insertions(+) create mode 100644 driver/usb_mux/amd_fp6.c create mode 100644 driver/usb_mux/amd_fp6.h (limited to 'driver/usb_mux') 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 */ -- cgit v1.2.1