summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Ji <xji@analogixsemi.com>2022-01-12 10:42:44 +0800
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-03-13 19:13:46 +0000
commit38d981d3a0d7298770f318c6c8c50e0ac5c39bf2 (patch)
tree87cf27cdbe0645f6c5937204e96f2c70de511198
parent89bd1915da0cf5b496b522063927091692d49b4d (diff)
downloadchrome-ec-38d981d3a0d7298770f318c6c8c50e0ac5c39bf2.tar.gz
tcpm: anx7406: add PD anx7406 support
Add ANX7406 tcpci driver, support external retimer control. PD 20V@3A Request, DP Alt mode. Disable low power mode. BUG=b:212509565 BRANCH=none TEST=1. Verify DP Alt mode. 2. Verify PD charging. Change-Id: I0d82349578eb6a6f9c938fb4b9e33b94853a4d04 Signed-off-by: Xin Ji <xji@analogix.corp-partner.google.com> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3460777 Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org> Commit-Queue: Daisuke Nojiri <dnojiri@chromium.org> Tested-by: Daisuke Nojiri <dnojiri@chromium.org>
-rw-r--r--driver/build.mk1
-rw-r--r--driver/tcpm/anx7406.c376
-rw-r--r--driver/tcpm/anx7406.h135
-rw-r--r--include/config.h1
-rw-r--r--zephyr/Kconfig.tcpm13
5 files changed, 526 insertions, 0 deletions
diff --git a/driver/build.mk b/driver/build.mk
index 622201e886..23e6dcd149 100644
--- a/driver/build.mk
+++ b/driver/build.mk
@@ -162,6 +162,7 @@ driver-y +=tcpm/ite_pd_intc.o
driver-$(CONFIG_USB_PD_TCPM_DRIVER_IT83XX)+=tcpm/it83xx.o
driver-$(CONFIG_USB_PD_TCPM_DRIVER_IT8XXX2)+=tcpm/it8xxx2.o
endif
+driver-$(CONFIG_USB_PD_TCPM_ANX7406)+=tcpm/anx7406.o
driver-$(CONFIG_USB_PD_TCPM_ANX74XX)+=tcpm/anx74xx.o
driver-$(CONFIG_USB_PD_TCPM_ANX7688)+=tcpm/anx7688.o
driver-$(CONFIG_USB_PD_TCPM_ANX7447)+=tcpm/anx7447.o
diff --git a/driver/tcpm/anx7406.c b/driver/tcpm/anx7406.c
new file mode 100644
index 0000000000..d270be0099
--- /dev/null
+++ b/driver/tcpm/anx7406.c
@@ -0,0 +1,376 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* ANX7406 port manager */
+
+#include "anx7406.h"
+#include "common.h"
+#include "console.h"
+#include "hooks.h"
+#include "tcpm/tcpci.h"
+#include "tcpm/tcpm.h"
+#include "timer.h"
+#include "usb_pd.h"
+#include "util.h"
+
+#define CPRINTS(format, args...) cprints(CC_USBPD, format, ##args)
+
+const struct anx7406_i2c_addr anx7406_i2c_addrs_flags[] = {
+ { ANX7406_TCPC0_I2C_ADDR_FLAGS, ANX7406_TOP0_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC1_I2C_ADDR_FLAGS, ANX7406_TOP1_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC2_I2C_ADDR_FLAGS, ANX7406_TOP2_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC3_I2C_ADDR_FLAGS, ANX7406_TOP3_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC4_I2C_ADDR_FLAGS, ANX7406_TOP4_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC5_I2C_ADDR_FLAGS, ANX7406_TOP5_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC6_I2C_ADDR_FLAGS, ANX7406_TOP6_I2C_ADDR_FLAGS },
+ { ANX7406_TCPC7_I2C_ADDR_FLAGS, ANX7406_TOP7_I2C_ADDR_FLAGS },
+};
+
+static struct anx7406_i2c_addr i2c_peripheral[CONFIG_USB_PD_PORT_MAX_COUNT];
+
+static int anx7406_set_hpd(int port, int hpd_lvl)
+{
+ int val;
+
+ if (hpd_lvl) {
+ CPRINTS("set hpd to HIGH");
+ val = ANX7406_REG_HPD_OEN | HPD_DEGLITCH_TIME;
+ } else {
+ CPRINTS("set hpd to LOW");
+ val = HPD_DEGLITCH_TIME;
+ }
+
+ return i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ ANX7406_REG_HPD_DEGLITCH_H, val);
+}
+
+int anx7406_hpd_reset(const int port)
+{
+ int rv;
+
+ CPRINTS("HPD reset");
+ rv = i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ ANX7406_REG_HPD_CTRL_0, 0);
+ if (rv) {
+ CPRINTS("Clear HPD_MODE failed: %d", rv);
+ return rv;
+ }
+
+ return anx7406_set_hpd(port, 0);
+}
+
+/*
+ * timestamp of the next possible toggle to ensure the 2-ms spacing
+ * between IRQ_HPD.
+ */
+static uint64_t hpd_timestamp[CONFIG_USB_PD_PORT_MAX_COUNT];
+void anx7406_update_hpd_status(const struct usb_mux *mux, mux_state_t mux_state)
+{
+ int port = mux->usb_port;
+ int hpd_lvl = (mux_state & USB_PD_MUX_HPD_LVL) ? 1 : 0;
+ int hpd_irq = (mux_state & USB_PD_MUX_HPD_IRQ) ? 1 : 0;
+ int rv;
+
+ /*
+ * All calls within this method need to update to a mux_read/write calls
+ * that use the secondary address. This is a non-trival change and no
+ * one is using the anx7406 as a mux only (and probably never will since
+ * it doesn't have a re-driver). If that changes, we need to update this
+ * code.
+ */
+ ASSERT(!(mux->flags & USB_MUX_FLAG_NOT_TCPC));
+
+ anx7406_set_hpd(port, hpd_lvl);
+
+ if (hpd_irq) {
+ uint64_t now = get_time().val;
+ /* wait for the minimum spacing between IRQ_HPD if needed */
+ if (now < hpd_timestamp[port])
+ usleep(hpd_timestamp[port] - now);
+
+ /*
+ * For generate hardware HPD IRQ, need set bit
+ * ANX7406_REG_HPD_IRQ0 first, then clear it. This bit is not
+ * write clear.
+ */
+ rv = i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ ANX7406_REG_HPD_CTRL_0, ANX7406_REG_HPD_IRQ0);
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ ANX7406_REG_HPD_CTRL_0, 0);
+ if (rv)
+ CPRINTS("Generate HPD IRQ failed: %d", rv);
+ }
+ /* enforce 2-ms delay between HPD pulses */
+ hpd_timestamp[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
+}
+
+static int anx7406_init(int port)
+{
+ int rv, i;
+
+ CPRINTS("C%d: %s", port, __func__);
+ /*
+ * find corresponding anx7406 TOP address according to
+ * specified TCPC address
+ */
+ for (i = 0; i < ARRAY_SIZE(anx7406_i2c_addrs_flags); i++) {
+ if (I2C_STRIP_FLAGS(tcpc_config[port].i2c_info.addr_flags) ==
+ I2C_STRIP_FLAGS(
+ anx7406_i2c_addrs_flags[i].tcpc_addr_flags)) {
+ i2c_peripheral[port].tcpc_addr_flags =
+ anx7406_i2c_addrs_flags[i].tcpc_addr_flags;
+ i2c_peripheral[port].top_addr_flags =
+ anx7406_i2c_addrs_flags[i].top_addr_flags;
+ break;
+ }
+ }
+ if (!I2C_STRIP_FLAGS(i2c_peripheral[port].top_addr_flags)) {
+ CPRINTS("C%d: 0x%x is invalid for anx7406", port,
+ i2c_peripheral[port].top_addr_flags);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ /* Set VBUS OCP */
+ rv = tcpc_write(port, ANX7406_REG_VBUS_OCP, OCP_THRESHOLD);
+ if (rv)
+ return rv;
+
+ /* Disable CAP write protect */
+ rv = tcpc_update8(port, ANX7406_REG_TCPCCTRL, ANX7406_REG_CAP_WP,
+ MASK_CLR);
+ /* Disable bleed discharge */
+ rv |= tcpc_update16(port, TCPC_REG_DEV_CAP_1,
+ TCPC_REG_DEV_CAP_1_BLEED_DISCHARGE, MASK_CLR);
+ CPRINTS("C%d: TCPC config disable bleed discharge", port);
+ /* Enable CAP write protect */
+ rv |= tcpc_update8(port, ANX7406_REG_TCPCCTRL, ANX7406_REG_CAP_WP,
+ MASK_SET);
+ if (rv)
+ return rv;
+
+ rv = tcpc_update8(port, TCPC_REG_POWER_STATUS,
+ TCPC_REG_POWER_STATUS_UNINIT, MASK_CLR);
+ if (rv)
+ return rv;
+
+ rv = tcpci_tcpm_init(port);
+ if (rv)
+ return rv;
+
+ /* Let sink_ctrl & source_ctrl GPIO pin controlled by TCPC */
+ tcpc_write(port, ANX7406_REG_VBUS_SOURCE_CTRL, SOURCE_GPIO_OEN);
+ tcpc_write(port, ANX7406_REG_VBUS_SINK_CTRL, SINK_GPIO_OEN);
+
+ /* Clear CABLE DETECT signale */
+ rv = tcpc_update8(port, ANX7406_REG_ANALOG_SETTING,
+ ANX7406_REG_CABLE_DET_DIG, MASK_CLR);
+ if (rv)
+ return rv;
+
+ /*
+ * Specifically disable voltage alarms, as VBUS_VOLTAGE_ALARM_HI may
+ * trigger repeatedly despite being masked (b/153989733)
+ */
+ rv = tcpc_update16(port, TCPC_REG_POWER_CTRL,
+ TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS, MASK_SET);
+ if (rv)
+ return rv;
+
+ /* TCPC Filter set to 512uS */
+ rv = tcpc_write(port, ANX7406_REG_TCPCFILTER, 0xFF);
+ rv |= tcpc_update8(port, ANX7406_REG_TCPCCTRL,
+ ANX7406_REG_TCPCFILTERBIT8, MASK_SET);
+ if (rv)
+ CPRINTS("C%d: TCPC filter set failed: %d", port, rv);
+
+ rv = anx7406_hpd_reset(port);
+
+ CPRINTS("C%d: TCPC anx7406 initial success", port);
+ return rv;
+}
+
+static int anx7406_release(int port)
+{
+ return EC_SUCCESS;
+}
+
+static int anx7406_set_polarity(int port, enum tcpc_cc_polarity polarity)
+{
+ int rv;
+
+ if (polarity_rm_dts(polarity))
+ rv = tcpc_write(port, ANX7406_REG_VCONN_CTRL,
+ VCONN_PWR_CTRL_SEL | VCONN_CC1_PWR_ENABLE);
+ else
+ rv = tcpc_write(port, ANX7406_REG_VCONN_CTRL,
+ VCONN_PWR_CTRL_SEL | VCONN_CC2_PWR_ENABLE);
+ if (rv)
+ CPRINTS("Update VCONN power failed: %d, polarity: %d", rv,
+ polarity);
+
+ return tcpci_tcpm_set_polarity(port, polarity);
+}
+
+static int anx7406_m1_config(int port, int slave, int offset)
+{
+ int rv;
+
+ /* Configure external I2C slave address */
+ rv = i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags, EXT_I2C1_ADDR,
+ slave);
+ /* Configure external I2C slave offset */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags, EXT_I2C1_OFFSET,
+ offset);
+ /* Configure external I2C transfer byte count */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ EXT_I2C1_ACCESS_DATA_BYTE_CNT, 1);
+ /* Clear DATA buffer */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ EXT_I2C1_ACCESS_CTRL, I2C1_DATA_CLR);
+ /* Clear release */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ EXT_I2C1_ACCESS_CTRL, 0);
+
+ return rv;
+}
+
+int anx7406_m1_read(int port, int slave, int offset)
+{
+ int rv, val;
+
+ rv = anx7406_m1_config(port, slave, offset);
+
+ /* Send I2C read command */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags, EXT_I2C1_CTRL,
+ I2C1_CMD_READ | I2C1_SPEED_100K);
+ if (rv) {
+ CPRINTS("initial cisco I2C master failed!");
+ return rv;
+ }
+
+ usleep(1000);
+
+ /* Read I2C data out */
+ rv = i2c_read8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ EXT_I2C1_ACCESS_DATA, &val);
+ if (rv) {
+ CPRINTS("read cisco register failed!");
+ return rv;
+ }
+
+ return val;
+}
+
+static int anx7406_m1_write(int port, int slave, int offset, int data)
+{
+ int rv;
+
+ /* Configure external I2C slave address */
+ rv = anx7406_m1_config(port, slave, offset);
+
+ /* Configure I2C data */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags,
+ EXT_I2C1_ACCESS_DATA, data);
+ /* Send I2C write command */
+ rv |= i2c_write8(tcpc_config[port].i2c_info.port,
+ i2c_peripheral[port].top_addr_flags, EXT_I2C1_CTRL,
+ I2C1_CMD_WRITE | I2C1_SPEED_100K);
+ if (rv) {
+ CPRINTS("write data to cisco register failed!");
+ return rv;
+ }
+
+ return 0;
+}
+
+int anx7406_set_aux(int port, int flip)
+{
+ int rv;
+
+ CPRINTS("Set SBU %s flip", flip ? "" : "un");
+ /* Configure SBU orientation */
+ rv = anx7406_m1_write(port, I2C1_CISCO_SLAVE, I2C1_CISCO_LOCAL_REG,
+ SELECT_SBU_1_2);
+ if (rv) {
+ CPRINTS("Config CISCO_LOCAL_REG register failed");
+ return rv;
+ }
+
+ /* Disable pull up/down */
+ rv = anx7406_m1_write(port, I2C1_CISCO_SLAVE, I2C1_CISCO_CTRL_3,
+ flip ? AUX_FLIP_EN : 0);
+ if (rv) {
+ CPRINTS("Config CISCO_CTRL_3 register failed");
+ return rv;
+ }
+
+ /* Disable pull up/down */
+ rv = anx7406_m1_write(port, I2C1_CISCO_SLAVE, I2C1_CISCO_CTRL_1,
+ VBUS_PROTECT_750MA | AUX_PULL_DISABLE);
+ if (rv) {
+ CPRINTS("Config CISCO_CTRL_1 register failed");
+ return rv;
+ }
+
+ return 0;
+}
+
+/*
+ * ANX7406 is a TCPCI compatible port controller, with some caveats.
+ * It seems to require both CC lines to be set always, instead of just
+ * one at a time, according to TCPCI spec. Thus, now that the TCPCI
+ * driver more closely follows the spec, this driver requires
+ * overrides for set_cc and set_polarity.
+ */
+const struct tcpm_drv anx7406_tcpm_drv = {
+ .init = &anx7406_init,
+ .release = &anx7406_release,
+ .get_cc = &tcpci_tcpm_get_cc,
+#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
+ .check_vbus_level = &tcpci_tcpm_check_vbus_level,
+#endif
+ .select_rp_value = &tcpci_tcpm_select_rp_value,
+ .set_cc = &tcpci_tcpm_set_cc,
+ .set_polarity = &anx7406_set_polarity,
+#ifdef CONFIG_USB_PD_DECODE_SOP
+ .sop_prime_enable = &tcpci_tcpm_sop_prime_enable,
+#endif
+ .set_vconn = &tcpci_tcpm_set_vconn,
+ .set_msg_header = &tcpci_tcpm_set_msg_header,
+ .set_rx_enable = &tcpci_tcpm_set_rx_enable,
+ .get_message_raw = &tcpci_tcpm_get_message_raw,
+ .transmit = &tcpci_tcpm_transmit,
+ .tcpc_alert = &tcpci_tcpc_alert,
+#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
+ .tcpc_discharge_vbus = &tcpci_tcpc_discharge_vbus,
+#endif
+#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
+ .drp_toggle = tcpci_tcpc_drp_toggle,
+#endif
+ .get_chip_info = &tcpci_get_chip_info,
+#ifdef CONFIG_USB_PD_PPC
+ .set_snk_ctrl = &tcpci_tcpm_set_snk_ctrl,
+ .set_src_ctrl = &tcpci_tcpm_set_src_ctrl,
+#endif
+#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
+ .enter_low_power_mode = &tcpci_enter_low_power_mode,
+#endif
+ .set_bist_test_mode = &tcpci_set_bist_test_mode,
+#ifdef CONFIG_CMD_TCPC_DUMP
+ .dump_registers = &tcpc_dump_std_registers,
+#endif
+};
diff --git a/driver/tcpm/anx7406.h b/driver/tcpm/anx7406.h
new file mode 100644
index 0000000000..83651b9573
--- /dev/null
+++ b/driver/tcpm/anx7406.h
@@ -0,0 +1,135 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef __CROS_EC_USB_PD_TCPM_ANX7406_H
+#define __CROS_EC_USB_PD_TCPM_ANX7406_H
+
+#include "usb_mux.h"
+
+struct anx7406_i2c_addr {
+ uint16_t tcpc_addr_flags;
+ uint16_t top_addr_flags;
+};
+
+#define ANX7406_TCPC0_I2C_ADDR_FLAGS (0x58 >> 1)
+#define ANX7406_TCPC1_I2C_ADDR_FLAGS (0x56 >> 1)
+#define ANX7406_TCPC2_I2C_ADDR_FLAGS (0x54 >> 1)
+#define ANX7406_TCPC3_I2C_ADDR_FLAGS (0x52 >> 1)
+#define ANX7406_TCPC4_I2C_ADDR_FLAGS (0x90 >> 1)
+#define ANX7406_TCPC5_I2C_ADDR_FLAGS (0x9A >> 1)
+#define ANX7406_TCPC6_I2C_ADDR_FLAGS (0xA4 >> 1)
+#define ANX7406_TCPC7_I2C_ADDR_FLAGS (0xAE >> 1)
+
+#define ANX7406_TOP0_I2C_ADDR_FLAGS (0x7E >> 1)
+#define ANX7406_TOP1_I2C_ADDR_FLAGS (0x6E >> 1)
+#define ANX7406_TOP2_I2C_ADDR_FLAGS (0x64 >> 1)
+#define ANX7406_TOP3_I2C_ADDR_FLAGS (0x62 >> 1)
+#define ANX7406_TOP4_I2C_ADDR_FLAGS (0x92 >> 1)
+#define ANX7406_TOP5_I2C_ADDR_FLAGS (0x9C >> 1)
+#define ANX7406_TOP6_I2C_ADDR_FLAGS (0xA6 >> 1)
+#define ANX7406_TOP7_I2C_ADDR_FLAGS (0xB0 >> 1)
+
+/* Registers: TCPC address used */
+#define ANX7406_REG_ANALOG_SETTING 0x0C
+#define ANX7406_REG_CABLE_DET_DIG BIT(6)
+#define ANX7406_REG_DIGITAL_RDY BIT(5)
+
+#define ANX7406_REG_TCPCFILTER 0x9F
+#define ANX7406_REG_TCPCCTRL 0xCD
+#define ANX7406_REG_TCPCFILTERBIT8 BIT(0)
+#define ANX7406_REG_CAP_WP BIT(2)
+
+#define ANX7406_REG_VBUS_SOURCE_CTRL 0xC2
+#define SOURCE_GPIO_OEN BIT(2)
+#define ANX7406_REG_VBUS_SINK_CTRL 0xC3
+#define SINK_GPIO_OEN BIT(2)
+
+#define ANX7406_REG_VBUS_OCP 0xD2
+#define OCP_THRESHOLD 0xFF
+
+#define ANX7406_REG_ADC_CTRL_1 0xE3
+#define ANX7406_REG_ADC_FSM_EN BIT(0)
+#define ANX7406_REG_ADC_MEASURE_VCONN BIT(1)
+#define ANX7406_REG_ADC_MEASURE_VBUS BIT(2)
+#define ANX7406_REG_ADC_MEASURE_OCP BIT(3)
+
+#define ANX7406_REG_VCONN_CTRL 0xEB
+#define VCONN_PWR_CTRL_SEL BIT(2)
+#define VCONN_CC2_PWR_ENABLE BIT(1)
+#define VCONN_CC1_PWR_ENABLE BIT(0)
+
+/* Registers: TOP address used */
+#define ANX7406_REG_HPD_CTRL_0 0x7E
+#define ANX7406_REG_HPD_IRQ0 BIT(2)
+
+#define ANX7406_REG_HPD_DEGLITCH_H 0x80
+#define ANX7406_REG_HPD_OEN BIT(6)
+#define HPD_DEGLITCH_TIME 0x0D
+
+#define EXT_I2C_OP_DELAY 1000
+/* Internal I2C0 master */
+/* External I2C0 address & offset */
+#define EXT_I2C0_ADDR 0x5E
+#define EXT_I2C0_OFFSET 0x5F
+/* External I2C0 master control bit */
+#define EXT_I2C0_CTRL 0x60
+#define I2C0_CMD_RESET BIT(6)
+#define I2C0_CMD_WRITE BIT(4)
+#define I2C0_CMD_READ 0
+#define I2C0_CMD_CISCO_READ (BIT(5) | BIT(6))
+#define I2C0_SPEED_100K (BIT(2) | BIT(3))
+#define I2C0_NO_STOP BIT(1)
+#define I2C0_NO_ACK BIT(0)
+
+#define EXT_I2C0_ACCESS_DATA_BYTE_CNT 0x61
+#define EXT_I2C0_ACCESS_DATA 0x65
+
+#define EXT_I2C0_ACCESS_CTRL 0x66
+#define I2C0_DATA_FULL BIT(7)
+#define I2C0_DATA_EMPTY BIT(6)
+#define I2C0_TIMING_SET_EN BIT(1)
+#define I2C0_DATA_CLR BIT(0)
+
+/* Internal I2C1 master */
+/* External I2C1 address & offset */
+#define EXT_I2C1_ADDR 0xCC
+#define EXT_I2C1_OFFSET 0xCD
+/* External I2C1 master control bit */
+#define EXT_I2C1_CTRL 0xCE
+#define I2C1_CMD_RESET BIT(6)
+#define I2C1_CMD_WRITE BIT(4)
+#define I2C1_CMD_READ 0
+#define I2C1_CMD_CISCO_READ (BIT(5) | BIT(6))
+#define I2C1_SPEED_100K (BIT(2) | BIT(3))
+#define I2C1_NO_STOP BIT(1)
+#define I2C1_NO_ACK BIT(0)
+
+#define EXT_I2C1_ACCESS_DATA_BYTE_CNT 0xCF
+#define EXT_I2C1_ACCESS_DATA 0xD3
+
+#define EXT_I2C1_ACCESS_CTRL 0xD4
+#define I2C1_DATA_FULL BIT(7)
+#define I2C1_DATA_EMPTY BIT(6)
+#define I2C1_TIMING_SET_EN BIT(1)
+#define I2C1_DATA_CLR BIT(0)
+
+#define I2C1_CISCO_SLAVE 0x80
+#define I2C1_CISCO_CTRL_1 0x01
+#define VBUS_PROTECT_750MA BIT(1)
+#define AUX_PULL_DISABLE BIT(3)
+
+#define I2C1_CISCO_CTRL_3 0x03
+#define AUX_FLIP_EN BIT(0)
+
+#define I2C1_CISCO_LOCAL_REG 0x06
+#define SELECT_SBU_1_2 BIT(6)
+
+extern const struct tcpm_drv anx7406_tcpm_drv;
+int anx7406_set_aux(int port, int flip);
+int anx7406_hpd_reset(const int port);
+void anx7406_update_hpd_status(const struct usb_mux *mux,
+ mux_state_t mux_state);
+
+#endif /* __CROS_EC_USB_PD_TCPM_ANX7406_H */
diff --git a/include/config.h b/include/config.h
index d08214c17a..af7ef8d94d 100644
--- a/include/config.h
+++ b/include/config.h
@@ -4898,6 +4898,7 @@
#undef CONFIG_USB_PD_TCPM_FUSB302
#undef CONFIG_USB_PD_TCPM_ITE_ON_CHIP
#undef CONFIG_USB_PD_TCPM_ANX3429
+#undef CONFIG_USB_PD_TCPM_ANX7406
#undef CONFIG_USB_PD_TCPM_ANX740X
#undef CONFIG_USB_PD_TCPM_ANX741X
#undef CONFIG_USB_PD_TCPM_ANX7447
diff --git a/zephyr/Kconfig.tcpm b/zephyr/Kconfig.tcpm
index 6651336954..e2f50cfb39 100644
--- a/zephyr/Kconfig.tcpm
+++ b/zephyr/Kconfig.tcpm
@@ -72,6 +72,19 @@ config PLATFORM_EC_USB_PD_TCPC_RUNTIME_CONFIG
factory. Without this, multiple EC images would need to be installed
depending on the board.
+config PLATFORM_EC_USB_PD_TCPM_ANX7406
+ bool "Analogix ANX7406 USB-C Gen 2 Type-C Port Controller"
+ select PLATFORM_EC_USB_PD_TCPM_MUX
+ help
+ The Analogix ANX7406 is a USB Type-C Port Controller (TCPC)
+ for USB Type-C v1.2 Host, USB3.1 Gen2 and DisplayPort applications.
+ It has an on-chip microcontroller (OCM) to manage the signal
+ switching. It supports Power Delivery Rev. 3.0 and the DisplayPort
+ Alt Mode version 1.4a HBR3.
+
+ Supported chips are:
+ ANX7406
+
config PLATFORM_EC_USB_PD_TCPM_ANX7447
bool "Analogix ANX7447 USB-C Gen 2 Type-C Port Controller"
select PLATFORM_EC_USB_PD_TCPM_MUX