summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/driver/tcpm/tcpci.h34
-rw-r--r--zephyr/dts/bindings/emul/cros,tcpci-emul.yaml9
-rw-r--r--zephyr/emul/CMakeLists.txt1
-rw-r--r--zephyr/emul/Kconfig1
-rw-r--r--zephyr/emul/Kconfig.tcpci24
-rw-r--r--zephyr/emul/emul_tcpci.c1115
-rw-r--r--zephyr/include/emul/emul_tcpci.h239
7 files changed, 1422 insertions, 1 deletions
diff --git a/include/driver/tcpm/tcpci.h b/include/driver/tcpm/tcpci.h
index 53a6a4e65e..a2831d4e06 100644
--- a/include/driver/tcpm/tcpci.h
+++ b/include/driver/tcpm/tcpci.h
@@ -21,12 +21,24 @@
#define TCPC_REG_PD_REV 0x8
#define TCPC_REG_PD_INT_REV 0xa
+#define TCPC_REG_PD_INT_REV_REV_MASK 0xff00
+#define TCPC_REG_PD_INT_REV_REV_1_0 0x10
+#define TCPC_REG_PD_INT_REV_REV_2_0 0x20
+#define TCPC_REG_PD_INT_REV_VER_MASK 0x00ff
+#define TCPC_REG_PD_INT_REV_VER_1_0 0x10
+#define TCPC_REG_PD_INT_REV_VER_1_1 0x11
+#define TCPC_REG_PD_INT_REV_REV(reg) \
+ ((reg & TCOC_REG_PD_INT_REV_REV_MASK) >> 8)
+#define TCPC_REG_PD_INT_REV_VER(reg) \
+ (reg & TCOC_REG_PD_INT_REV_VER_MASK)
+
#define TCPC_REG_ALERT 0x10
#define TCPC_REG_ALERT_NONE 0x0000
#define TCPC_REG_ALERT_MASK_ALL 0xffff
#define TCPC_REG_ALERT_VENDOR_DEF BIT(15)
#define TCPC_REG_ALERT_ALERT_EXT BIT(14)
#define TCPC_REG_ALERT_EXT_STATUS BIT(13)
+#define TCPC_REG_ALERT_RX_BEGINNING BIT(12)
#define TCPC_REG_ALERT_VBUS_DISCNCT BIT(11)
#define TCPC_REG_ALERT_RX_BUF_OVF BIT(10)
#define TCPC_REG_ALERT_FAULT BIT(9)
@@ -153,6 +165,8 @@
#define TCPC_REG_COMMAND_SRC_CTRL_LOW 0x66
#define TCPC_REG_COMMAND_SRC_CTRL_HIGH 0x77
#define TCPC_REG_COMMAND_LOOK4CONNECTION 0x99
+#define TCPC_REG_COMMAND_RESET_TRANSMIT_BUF 0xDD
+#define TCPC_REG_COMMAND_RESET_RECEIVE_BUF 0xEE
#define TCPC_REG_COMMAND_I2CIDLE 0xFF
#define TCPC_REG_DEV_CAP_1 0x24
@@ -181,14 +195,30 @@
#define TCPC_REG_DEV_CAP_1_SOURCE_VBUS BIT(0)
#define TCPC_REG_DEV_CAP_2 0x26
-#define TCPC_REG_DEV_CAP_2_SNK_FR_SWAP BIT(9)
+#define TCPC_REG_DEV_CAP_2_LONG_MSG BIT(12)
+#define TCPC_REG_DEV_CAP_2_SNK_FR_SWAP BIT(9)
#define TCPC_REG_STD_INPUT_CAP 0x28
+#define TCPC_REG_STD_INPUT_CAP_SRC_FR_SWAP (BIT(4)|BIT(3))
+#define TCPC_REG_STD_INPUT_CAP_EXT_OVR_V_F BIT(2)
+#define TCPC_REG_STD_INPUT_CAP_EXT_OVR_C_F BIT(1)
+#define TCPC_REG_STD_INPUT_CAP_FORCE_OFF_VBUS BIT(0)
+
#define TCPC_REG_STD_OUTPUT_CAP 0x29
+#define TCPC_REG_STD_OUTPUT_CAP_SNK_DISC_DET BIT(7)
+#define TCPC_REG_STD_OUTPUT_CAP_DBG_ACCESSORY BIT(6)
+#define TCPC_REG_STD_OUTPUT_CAP_VBUS_PRESENT_MON BIT(5)
+#define TCPC_REG_STD_OUTPUT_CAP_AUDIO_ACCESSORY BIT(4)
+#define TCPC_REG_STD_OUTPUT_CAP_ACTIVE_CABLE BIT(3)
+#define TCPC_REG_STD_OUTPUT_CAP_MUX_CONF_CTRL BIT(2)
+#define TCPC_REG_STD_OUTPUT_CAP_CONN_PRESENT BIT(1)
+#define TCPC_REG_STD_OUTPUT_CAP_CONN_ORIENTATION BIT(0)
#define TCPC_REG_CONFIG_EXT_1 0x2A
#define TCPC_REG_CONFIG_EXT_1_FR_SWAP_SNK_DIR BIT(1)
+#define TCPC_REG_GENERIC_TIMER 0x2c
+
#define TCPC_REG_MSG_HDR_INFO 0x2e
#define TCPC_REG_MSG_HDR_INFO_SET(drole, prole) \
((drole) << 3 | (PD_REV20 << 1) | (prole))
@@ -241,6 +271,8 @@
#define TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG 0x76
#define TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG 0x78
+#define TCPC_REG_VBUS_NONDEFAULT_TARGET 0x7a
+
extern const struct tcpm_drv tcpci_tcpm_drv;
extern const struct usb_mux_driver tcpci_tcpm_usb_mux_driver;
diff --git a/zephyr/dts/bindings/emul/cros,tcpci-emul.yaml b/zephyr/dts/bindings/emul/cros,tcpci-emul.yaml
new file mode 100644
index 0000000000..11be7cf4d8
--- /dev/null
+++ b/zephyr/dts/bindings/emul/cros,tcpci-emul.yaml
@@ -0,0 +1,9 @@
+# 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.
+
+description: Zephyr TCPCI Emulator
+
+compatible: "cros,tcpci-emul"
+
+include: base.yaml
diff --git a/zephyr/emul/CMakeLists.txt b/zephyr/emul/CMakeLists.txt
index 1d0693fa57..19dcc84d30 100644
--- a/zephyr/emul/CMakeLists.txt
+++ b/zephyr/emul/CMakeLists.txt
@@ -17,3 +17,4 @@ zephyr_library_sources_ifdef(CONFIG_EMUL_LIS2DW12 emul_lis2dw12.c)
zephyr_library_sources_ifdef(CONFIG_I2C_MOCK i2c_mock.c)
zephyr_library_sources_ifdef(CONFIG_EMUL_ISL923X emul_isl923x.c)
zephyr_library_sources_ifdef(CONFIG_EMUL_CLOCK_CONTROL emul_clock_control.c)
+zephyr_library_sources_ifdef(CONFIG_EMUL_TCPCI emul_tcpci.c)
diff --git a/zephyr/emul/Kconfig b/zephyr/emul/Kconfig
index aa0d14da44..9a0b434bc9 100644
--- a/zephyr/emul/Kconfig
+++ b/zephyr/emul/Kconfig
@@ -72,3 +72,4 @@ rsource "Kconfig.lis2dw12"
rsource "Kconfig.i2c_mock"
rsource "Kconfig.isl923x"
rsource "Kconfig.clock_control"
+rsource "Kconfig.tcpci"
diff --git a/zephyr/emul/Kconfig.tcpci b/zephyr/emul/Kconfig.tcpci
new file mode 100644
index 0000000000..2892458676
--- /dev/null
+++ b/zephyr/emul/Kconfig.tcpci
@@ -0,0 +1,24 @@
+# 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.
+
+DT_COMPAT_TCPCI_EMUL := cros,tcpci-emul
+
+menuconfig EMUL_TCPCI
+ bool "TCPCI emulator"
+ default $(dt_compat_enabled,$(DT_COMPAT_TCPCI_EMUL))
+ depends on I2C_EMUL
+ help
+ Enable the TCPCI emulator. This driver uses the emulated I2C bus.
+ It is used to test tcpci code. It supports reads and writes to all
+ emulator registers. Generic TCPCI emulator can be used as the base
+ for specific TCPC device emulator that follow TCPCI specification.
+ TCPCI emulator API is available in zephyr/include/emul/emul_tcpci.h
+
+if EMUL_TCPCI
+
+module = TCPCI_EMUL
+module-str = tcpci_emul
+source "subsys/logging/Kconfig.template.log_config"
+
+endif # EMUL_TCPCI
diff --git a/zephyr/emul/emul_tcpci.c b/zephyr/emul/emul_tcpci.c
new file mode 100644
index 0000000000..c4d21ace78
--- /dev/null
+++ b/zephyr/emul/emul_tcpci.c
@@ -0,0 +1,1115 @@
+/* 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.
+ */
+
+#define DT_DRV_COMPAT cros_tcpci_emul
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(tcpci_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL);
+
+#include <device.h>
+#include <emul.h>
+#include <drivers/i2c.h>
+#include <drivers/i2c_emul.h>
+
+#include "tcpm/tcpci.h"
+
+#include "emul/emul_common_i2c.h"
+#include "emul/emul_tcpci.h"
+
+#define TCPCI_DATA_FROM_I2C_EMUL(_emul) \
+ CONTAINER_OF(CONTAINER_OF(_emul, struct i2c_common_emul_data, emul), \
+ struct tcpci_emul_data, common)
+
+/**
+ * Number of emulated register. This include vendor registers defined in TCPCI
+ * specification
+ */
+#define TCPCI_EMUL_REG_COUNT 0x100
+
+
+/** Run-time data used by the emulator */
+struct tcpci_emul_data {
+ /** Common I2C data */
+ struct i2c_common_emul_data common;
+
+ /** Current state of all emulated TCPCI registers */
+ uint8_t reg[TCPCI_EMUL_REG_COUNT];
+
+ /** Structures representing TX and RX buffers */
+ struct tcpci_emul_msg *rx_msg;
+ struct tcpci_emul_msg *tx_msg;
+
+ /** Data that should be written to register (except TX_BUFFER) */
+ uint16_t write_data;
+
+ /** Return error when trying to write to RO register */
+ bool error_on_ro_write;
+ /** Return error when trying to write 1 to reserved bit */
+ bool error_on_rsvd_write;
+
+ /** User function called when alert line could change */
+ tcpci_emul_alert_state_func alert_callback;
+ /** Data passed to alert_callback */
+ void *alert_callback_data;
+
+ /** Callbacks for specific TCPCI device emulator */
+ struct tcpci_emul_dev_ops *dev_ops;
+ /** Callbacks for TCPCI partner */
+ struct tcpci_emul_partner_ops *partner;
+};
+
+/**
+ * @brief Returns number of bytes in specific register
+ *
+ * @param reg Register address
+ *
+ * @return Number of bytes
+ */
+static int tcpci_emul_reg_bytes(int reg)
+{
+
+ switch (reg) {
+ case TCPC_REG_VENDOR_ID:
+ case TCPC_REG_PRODUCT_ID:
+ case TCPC_REG_BCD_DEV:
+ case TCPC_REG_TC_REV:
+ case TCPC_REG_PD_REV:
+ case TCPC_REG_PD_INT_REV:
+ case TCPC_REG_ALERT:
+ case TCPC_REG_ALERT_MASK:
+ case TCPC_REG_DEV_CAP_1:
+ case TCPC_REG_DEV_CAP_2:
+ case TCPC_REG_GENERIC_TIMER:
+ case TCPC_REG_VBUS_VOLTAGE:
+ case TCPC_REG_VBUS_SINK_DISCONNECT_THRESH:
+ case TCPC_REG_VBUS_STOP_DISCHARGE_THRESH:
+ case TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG:
+ case TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG:
+ case TCPC_REG_VBUS_NONDEFAULT_TARGET:
+ return 2;
+ }
+
+ /* Assume that other registers are one byte */
+ return 1;
+}
+
+/** Check description in emul_tcpci.h */
+int tcpci_emul_set_reg(const struct emul *emul, int reg, uint16_t val)
+{
+ struct tcpci_emul_data *data = emul->data;
+ int byte;
+
+ if (reg < 0 || reg > TCPCI_EMUL_REG_COUNT) {
+ return -EINVAL;
+ }
+
+ for (byte = tcpci_emul_reg_bytes(reg); byte > 0; byte--) {
+ data->reg[reg] = val & 0xff;
+ val >>= 8;
+ reg++;
+ }
+
+ return 0;
+}
+
+/** Check description in emul_tcpci.h */
+int tcpci_emul_get_reg(const struct emul *emul, int reg, uint16_t *val)
+{
+ struct tcpci_emul_data *data = emul->data;
+ int byte;
+
+ if (reg < 0 || reg > TCPCI_EMUL_REG_COUNT || val == NULL) {
+ return -EINVAL;
+ }
+
+ *val = 0;
+
+ byte = tcpci_emul_reg_bytes(reg);
+ for (byte -= 1; byte >= 0; byte--) {
+ *val <<= 8;
+ *val |= data->reg[reg + byte];
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Check if alert line should be active based on alert registers and
+ * masks
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return State of alert line
+ */
+static bool tcpci_emul_check_int(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+ uint16_t alert_mask;
+ uint16_t alert;
+
+ tcpci_emul_get_reg(emul, TCPC_REG_ALERT, &alert);
+ tcpci_emul_get_reg(emul, TCPC_REG_ALERT_MASK, &alert_mask);
+
+ /*
+ * For nested interrupts alert group bit and alert register bit has to
+ * be unmasked
+ */
+ if (alert & alert_mask & TCPC_REG_ALERT_ALERT_EXT &&
+ data->reg[TCPC_REG_ALERT_EXT] &
+ data->reg[TCPC_REG_ALERT_EXTENDED_MASK]) {
+ return true;
+ }
+
+ if (alert & alert_mask & TCPC_REG_ALERT_EXT_STATUS &&
+ data->reg[TCPC_REG_EXT_STATUS] &
+ data->reg[TCPC_REG_EXT_STATUS_MASK]) {
+ return true;
+ }
+
+ if (alert & alert_mask & TCPC_REG_ALERT_FAULT &&
+ data->reg[TCPC_REG_FAULT_STATUS] &
+ data->reg[TCPC_REG_FAULT_STATUS_MASK]) {
+ return true;
+ }
+
+ if (alert & alert_mask & TCPC_REG_POWER_STATUS &&
+ data->reg[TCPC_REG_POWER_STATUS] &
+ data->reg[TCPC_REG_POWER_STATUS_MASK]) {
+ return true;
+ }
+
+ /* Nested alerts are handled above */
+ alert &= ~(TCPC_REG_ALERT_ALERT_EXT | TCPC_REG_ALERT_EXT_STATUS |
+ TCPC_REG_ALERT_FAULT | TCPC_REG_ALERT_POWER_STATUS);
+ if (alert & alert_mask) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * @brief If alert callback is provided, call it with current alert line state
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return State of alert line
+ */
+static void tcpci_emul_alert_changed(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ /* Nothing to do */
+ if (data->alert_callback == NULL) {
+ return;
+ }
+
+ data->alert_callback(emul, tcpci_emul_check_int(emul),
+ data->alert_callback_data);
+}
+
+/** Check description in emul_tcpci.h */
+int tcpci_emul_add_rx_msg(const struct emul *emul,
+ struct tcpci_emul_msg *rx_msg, bool alert)
+{
+ struct tcpci_emul_data *data = emul->data;
+ uint16_t dev_cap_2;
+
+ if (data->rx_msg == NULL) {
+ tcpci_emul_get_reg(emul, TCPC_REG_DEV_CAP_2, &dev_cap_2);
+ if ((!(dev_cap_2 & TCPC_REG_DEV_CAP_2_LONG_MSG) &&
+ rx_msg->cnt > 31) || rx_msg->cnt > 265) {
+ LOG_ERR("Too long first message (%d)", rx_msg->cnt);
+ return -EINVAL;
+ }
+
+ data->rx_msg = rx_msg;
+ } else if (data->rx_msg->next == NULL) {
+ if (rx_msg->cnt > 31) {
+ LOG_ERR("Too long second message (%d)", rx_msg->cnt);
+ return -EINVAL;
+ }
+
+ data->rx_msg->next = rx_msg;
+ if (alert) {
+ data->reg[TCPC_REG_ALERT + 1] |=
+ TCPC_REG_ALERT_RX_BUF_OVF >> 8;
+ }
+ } else {
+ LOG_ERR("Cannot setup third message");
+ return -EINVAL;
+ }
+
+ if (alert) {
+ if (rx_msg->cnt > 133) {
+ data->reg[TCPC_REG_ALERT + 1] |=
+ TCPC_REG_ALERT_RX_BEGINNING >> 8;
+ }
+
+ data->reg[TCPC_REG_ALERT] |= TCPC_REG_ALERT_RX_STATUS;
+
+ tcpci_emul_alert_changed(emul);
+ }
+
+ rx_msg->next = NULL;
+ rx_msg->idx = 0;
+
+ return 0;
+}
+
+/** Check description in emul_tcpci.h */
+struct tcpci_emul_msg *tcpci_emul_get_tx_msg(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ return data->tx_msg;
+}
+
+/** Check description in emul_tcpci.h */
+void tcpci_emul_set_rev(const struct emul *emul, enum tcpci_emul_rev rev)
+{
+ switch (rev) {
+ case TCPCI_EMUL_REV1_0_VER1_0:
+ tcpci_emul_set_reg(emul, TCPC_REG_PD_INT_REV,
+ (TCPC_REG_PD_INT_REV_REV_1_0 << 8) |
+ TCPC_REG_PD_INT_REV_VER_1_0);
+ return;
+ case TCPCI_EMUL_REV2_0_VER1_1:
+ tcpci_emul_set_reg(emul, TCPC_REG_PD_INT_REV,
+ (TCPC_REG_PD_INT_REV_REV_2_0 << 8) |
+ TCPC_REG_PD_INT_REV_VER_1_1);
+ return;
+ }
+}
+
+/** Mask reserved bits in each register of TCPCI */
+static const uint8_t tcpci_emul_rsvd_mask[] = {
+ [TCPC_REG_VENDOR_ID] = 0x00,
+ [TCPC_REG_VENDOR_ID + 1] = 0x00,
+ [TCPC_REG_PRODUCT_ID] = 0x00,
+ [TCPC_REG_PRODUCT_ID + 1] = 0x00,
+ [TCPC_REG_BCD_DEV] = 0x00,
+ [TCPC_REG_BCD_DEV + 1] = 0xff,
+ [TCPC_REG_TC_REV] = 0x00,
+ [TCPC_REG_TC_REV + 1] = 0x00,
+ [TCPC_REG_PD_REV] = 0x00,
+ [TCPC_REG_PD_REV + 1] = 0x00,
+ [TCPC_REG_PD_INT_REV] = 0x00,
+ [TCPC_REG_PD_INT_REV + 1] = 0x00,
+ [0x0c ... 0x0f] = 0xff, /* Reserved */
+ [TCPC_REG_ALERT] = 0x00,
+ [TCPC_REG_ALERT + 1] = 0x00,
+ [TCPC_REG_ALERT_MASK] = 0x00,
+ [TCPC_REG_ALERT_MASK + 1] = 0x00,
+ [TCPC_REG_POWER_STATUS_MASK] = 0x00,
+ [TCPC_REG_FAULT_STATUS_MASK] = 0x00,
+ [TCPC_REG_EXT_STATUS_MASK] = 0xfe,
+ [TCPC_REG_ALERT_EXTENDED_MASK] = 0xf8,
+ [TCPC_REG_CONFIG_STD_OUTPUT] = 0x00,
+ [TCPC_REG_TCPC_CTRL] = 0x00,
+ [TCPC_REG_ROLE_CTRL] = 0x80,
+ [TCPC_REG_FAULT_CTRL] = 0x80,
+ [TCPC_REG_POWER_CTRL] = 0x00,
+ [TCPC_REG_CC_STATUS] = 0xc0,
+ [TCPC_REG_POWER_STATUS] = 0x00,
+ [TCPC_REG_FAULT_STATUS] = 0x00,
+ [TCPC_REG_EXT_STATUS] = 0xfe,
+ [TCPC_REG_ALERT_EXT] = 0xf8,
+ [0x22] = 0xff, /* Reserved */
+ [TCPC_REG_COMMAND] = 0x00,
+ [TCPC_REG_DEV_CAP_1] = 0x00,
+ [TCPC_REG_DEV_CAP_1 + 1] = 0x00,
+ [TCPC_REG_DEV_CAP_2] = 0x80,
+ [TCPC_REG_DEV_CAP_2 + 1] = 0x00,
+ [TCPC_REG_STD_INPUT_CAP] = 0xe0,
+ [TCPC_REG_STD_OUTPUT_CAP] = 0x00,
+ [TCPC_REG_CONFIG_EXT_1] = 0xfc,
+ [0x2b] = 0xff, /* Reserved */
+ [TCPC_REG_GENERIC_TIMER] = 0x00,
+ [TCPC_REG_GENERIC_TIMER + 1] = 0x00,
+ [TCPC_REG_MSG_HDR_INFO] = 0xe0,
+ [TCPC_REG_RX_DETECT] = 0x00,
+ [TCPC_REG_RX_BUFFER ... 0x4f] = 0x00,
+ [TCPC_REG_TRANSMIT ... 0x69] = 0x00,
+ [TCPC_REG_VBUS_VOLTAGE] = 0xf0,
+ [TCPC_REG_VBUS_VOLTAGE + 1] = 0x00,
+ [TCPC_REG_VBUS_SINK_DISCONNECT_THRESH] = 0x00,
+ [TCPC_REG_VBUS_SINK_DISCONNECT_THRESH + 1] = 0xfc,
+ [TCPC_REG_VBUS_STOP_DISCHARGE_THRESH] = 0x00,
+ [TCPC_REG_VBUS_STOP_DISCHARGE_THRESH + 1] = 0xfc,
+ [TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG] = 0x00,
+ [TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG + 1] = 0xfc,
+ [TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG] = 0x00,
+ [TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG + 1] = 0xfc,
+ [TCPC_REG_VBUS_NONDEFAULT_TARGET] = 0x00,
+ [TCPC_REG_VBUS_NONDEFAULT_TARGET + 1] = 0x00,
+ [0x7c ... 0x7f] = 0xff, /* Reserved */
+ [0x80 ... TCPCI_EMUL_REG_COUNT - 1] = 0x00,
+};
+
+
+/**
+ * @brief Reset role control and header info registers to default values.
+ *
+ * @param emul Pointer to TCPCI emulator
+ */
+static void tcpci_emul_reset_role_ctrl(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+ uint16_t dev_cap_1;
+
+ tcpci_emul_get_reg(emul, TCPC_REG_DEV_CAP_1, &dev_cap_1);
+ switch (dev_cap_1 & TCPC_REG_DEV_CAP_1_PWRROLE_MASK) {
+ case TCPC_REG_DEV_CAP_1_PWRROLE_SRC_OR_SNK:
+ case TCPC_REG_DEV_CAP_1_PWRROLE_SNK:
+ case TCPC_REG_DEV_CAP_1_PWRROLE_SNK_ACC:
+ data->reg[TCPC_REG_ROLE_CTRL] = 0x0a;
+ data->reg[TCPC_REG_MSG_HDR_INFO] = 0x04;
+ break;
+ case TCPC_REG_DEV_CAP_1_PWRROLE_SRC:
+ /* Dead batter */
+ data->reg[TCPC_REG_ROLE_CTRL] = 0x05;
+ data->reg[TCPC_REG_MSG_HDR_INFO] = 0x0d;
+ break;
+ case TCPC_REG_DEV_CAP_1_PWRROLE_DRP:
+ /* Dead batter and dbg acc ind */
+ data->reg[TCPC_REG_ROLE_CTRL] = 0x4a;
+ data->reg[TCPC_REG_MSG_HDR_INFO] = 0x04;
+ break;
+ case TCPC_REG_DEV_CAP_1_PWRROLE_SRC_SNK_DRP_ADPT_CBL:
+ case TCPC_REG_DEV_CAP_1_PWRROLE_SRC_SNK_DRP:
+ /* Dead batter and dbg acc ind */
+ data->reg[TCPC_REG_ROLE_CTRL] = 0x4a;
+ data->reg[TCPC_REG_MSG_HDR_INFO] = 0x04;
+ break;
+ }
+}
+
+/**
+ * @brief Reset registers to default values. Vendor and reserved registers
+ * are not changed.
+ *
+ * @param emul Pointer to TCPCI emulator
+ */
+static void tcpci_emul_reset(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ data->reg[TCPC_REG_ALERT] = 0x00;
+ data->reg[TCPC_REG_ALERT + 1] = 0x00;
+ data->reg[TCPC_REG_ALERT_MASK] = 0xff;
+ data->reg[TCPC_REG_ALERT_MASK + 1] = 0x7f;
+ data->reg[TCPC_REG_POWER_STATUS_MASK] = 0xff;
+ data->reg[TCPC_REG_FAULT_STATUS_MASK] = 0xff;
+ data->reg[TCPC_REG_EXT_STATUS_MASK] = 0x01;
+ data->reg[TCPC_REG_ALERT_EXTENDED_MASK] = 0x07;
+ data->reg[TCPC_REG_CONFIG_STD_OUTPUT] = 0x60;
+ data->reg[TCPC_REG_TCPC_CTRL] = 0x00;
+ data->reg[TCPC_REG_FAULT_CTRL] = 0x00;
+ data->reg[TCPC_REG_POWER_CTRL] = 0x60;
+ data->reg[TCPC_REG_CC_STATUS] = 0x00;
+ data->reg[TCPC_REG_POWER_STATUS] = 0x08;
+ data->reg[TCPC_REG_FAULT_STATUS] = 0x80;
+ data->reg[TCPC_REG_EXT_STATUS] = 0x00;
+ data->reg[TCPC_REG_ALERT_EXT] = 0x00;
+ data->reg[TCPC_REG_COMMAND] = 0x00;
+ data->reg[TCPC_REG_CONFIG_EXT_1] = 0x00;
+ data->reg[TCPC_REG_GENERIC_TIMER] = 0x00;
+ data->reg[TCPC_REG_GENERIC_TIMER + 1] = 0x00;
+ data->reg[TCPC_REG_RX_DETECT] = 0x00;
+ data->reg[TCPC_REG_VBUS_VOLTAGE] = 0x00;
+ data->reg[TCPC_REG_VBUS_VOLTAGE + 1] = 0x00;
+ data->reg[TCPC_REG_VBUS_SINK_DISCONNECT_THRESH] = 0x8c;
+ data->reg[TCPC_REG_VBUS_SINK_DISCONNECT_THRESH + 1] = 0x00;
+ data->reg[TCPC_REG_VBUS_STOP_DISCHARGE_THRESH] = 0x20;
+ data->reg[TCPC_REG_VBUS_STOP_DISCHARGE_THRESH + 1] = 0x00;
+ data->reg[TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG] = 0x00;
+ data->reg[TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG + 1] = 0x00;
+ data->reg[TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG] = 0x00;
+ data->reg[TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG + 1] = 0x00;
+ data->reg[TCPC_REG_VBUS_NONDEFAULT_TARGET] = 0x00;
+ data->reg[TCPC_REG_VBUS_NONDEFAULT_TARGET + 1] = 0x00;
+
+ tcpci_emul_reset_role_ctrl(emul);
+
+ if (data->dev_ops && data->dev_ops->reset) {
+ data->dev_ops->reset(emul, data->dev_ops);
+ }
+
+ tcpci_emul_alert_changed(emul);
+}
+
+/**
+ * @brief Set alert and fault registers to indicate i2c interface fault
+ *
+ * @param emul Pointer to TCPCI emulator
+ */
+static void tcpci_emul_set_i2c_interface_err(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ data->reg[TCPC_REG_FAULT_STATUS] |=
+ TCPC_REG_FAULT_STATUS_I2C_INTERFACE_ERR;
+ data->reg[TCPC_REG_ALERT + 1] |= TCPC_REG_ALERT_FAULT >> 8;
+
+ tcpci_emul_alert_changed(emul);
+}
+
+/**
+ * @brief Handle read from RX buffer registers for TCPCI rev 1.0 and rev 2.0
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param reg First byte of last i2c write message
+ * @param val Pointer where byte to read should be stored
+ * @param bytes Number of bytes already readded
+ *
+ * @return 0 on success
+ * @return -EIO invalid read request
+ */
+static int tcpci_emul_handle_rx_buf(const struct emul *emul, int reg,
+ uint8_t *val, int bytes)
+{
+ struct tcpci_emul_data *data = emul->data;
+ int is_rev1;
+
+ is_rev1 = data->reg[TCPC_REG_PD_INT_REV] == TCPC_REG_PD_INT_REV_REV_1_0;
+
+ if (!is_rev1 && reg != TCPC_REG_RX_BUFFER) {
+ LOG_ERR("Register 0x%x defined only for revision 1.0", reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+
+ switch (reg) {
+ case TCPC_REG_RX_BUFFER:
+ if (data->rx_msg == NULL) {
+ if (bytes < 2) {
+ *val = 0;
+ } else {
+ LOG_ERR("Accessing RX buffer with no msg");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ return 0;
+ }
+ if (bytes == 0) {
+ *val = data->rx_msg->cnt;
+ } else if (is_rev1) {
+ LOG_ERR("Revision 1.0 has only byte count at 0x30");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ } else if (bytes == 1) {
+ *val = data->rx_msg->type;
+ } else if (data->rx_msg->idx < data->rx_msg->cnt) {
+ *val = data->rx_msg->buf[data->rx_msg->idx];
+ data->rx_msg->idx++;
+ } else {
+ LOG_ERR("Reading past RX buffer");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ break;
+
+ case TCPC_REG_RX_BUF_FRAME_TYPE:
+ if (bytes != 0) {
+ LOG_ERR("Reading byte %d from 1 byte register 0x%x",
+ bytes, reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ if (data->rx_msg == NULL) {
+ *val = 0;
+ } else {
+ *val = data->rx_msg->type;
+ }
+ break;
+
+ case TCPC_REG_RX_HDR:
+ if (bytes > 1) {
+ LOG_ERR("Reading byte %d from 2 byte register 0x%x",
+ bytes, reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ if (data->rx_msg == NULL) {
+ LOG_ERR("Accessing RX buffer with no msg");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ *val = data->rx_msg->buf[bytes];
+ break;
+
+ case TCPC_REG_RX_DATA:
+ if (data->rx_msg == NULL) {
+ LOG_ERR("Accessing RX buffer with no msg");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ if (bytes < data->rx_msg->cnt - 2) {
+ /* rx_msg cnt include two bytes of header */
+ *val = data->rx_msg->buf[bytes + 2];
+ data->rx_msg->idx++;
+ } else {
+ LOG_ERR("Reading past RX buffer");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Function called for each byte of read message
+ *
+ * @param i2c_emul Pointer to TCPCI emulator
+ * @param reg First byte of last write message
+ * @param val Pointer where byte to read should be stored
+ * @param bytes Number of bytes already readded
+ *
+ * @return 0 on success
+ */
+static int tcpci_emul_read_byte(struct i2c_emul *i2c_emul, int reg,
+ uint8_t *val, int bytes)
+{
+ struct tcpci_emul_data *data;
+ const struct emul *emul;
+
+ emul = i2c_emul->parent;
+ data = TCPCI_DATA_FROM_I2C_EMUL(i2c_emul);
+
+ if (data->dev_ops && data->dev_ops->read_byte) {
+ switch (data->dev_ops->read_byte(emul, data->dev_ops, reg, val,
+ bytes)) {
+ case TCPCI_EMUL_CONTINUE:
+ break;
+ case TCPCI_EMUL_DONE:
+ return 0;
+ case TCPCI_EMUL_ERROR:
+ default:
+ return -EIO;
+ }
+ }
+
+ switch (reg) {
+ /* 16 bits values */
+ case TCPC_REG_VENDOR_ID:
+ case TCPC_REG_PRODUCT_ID:
+ case TCPC_REG_BCD_DEV:
+ case TCPC_REG_TC_REV:
+ case TCPC_REG_PD_REV:
+ case TCPC_REG_PD_INT_REV:
+ case TCPC_REG_ALERT:
+ case TCPC_REG_ALERT_MASK:
+ case TCPC_REG_DEV_CAP_1:
+ case TCPC_REG_DEV_CAP_2:
+ case TCPC_REG_VBUS_VOLTAGE:
+ case TCPC_REG_VBUS_SINK_DISCONNECT_THRESH:
+ case TCPC_REG_VBUS_STOP_DISCHARGE_THRESH:
+ case TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG:
+ case TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG:
+ case TCPC_REG_VBUS_NONDEFAULT_TARGET:
+ if (bytes > 1) {
+ LOG_ERR("Reading byte %d from 2 byte register 0x%x",
+ bytes, reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ *val = data->reg[reg + bytes];
+ break;
+
+ /* 8 bits values */
+ case TCPC_REG_POWER_STATUS_MASK:
+ case TCPC_REG_FAULT_STATUS_MASK:
+ case TCPC_REG_EXT_STATUS_MASK:
+ case TCPC_REG_ALERT_EXTENDED_MASK:
+ case TCPC_REG_CONFIG_STD_OUTPUT:
+ case TCPC_REG_TCPC_CTRL:
+ case TCPC_REG_ROLE_CTRL:
+ case TCPC_REG_FAULT_CTRL:
+ case TCPC_REG_POWER_CTRL:
+ case TCPC_REG_CC_STATUS:
+ case TCPC_REG_POWER_STATUS:
+ case TCPC_REG_FAULT_STATUS:
+ case TCPC_REG_EXT_STATUS:
+ case TCPC_REG_ALERT_EXT:
+ case TCPC_REG_STD_INPUT_CAP:
+ case TCPC_REG_STD_OUTPUT_CAP:
+ case TCPC_REG_CONFIG_EXT_1:
+ case TCPC_REG_MSG_HDR_INFO:
+ case TCPC_REG_RX_DETECT:
+ if (bytes != 0) {
+ LOG_ERR("Reading byte %d from 1 byte register 0x%x",
+ bytes, reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ *val = data->reg[reg];
+ break;
+
+ case TCPC_REG_RX_BUFFER:
+ case TCPC_REG_RX_BUF_FRAME_TYPE:
+ case TCPC_REG_RX_HDR:
+ case TCPC_REG_RX_DATA:
+ return tcpci_emul_handle_rx_buf(emul, reg, val, bytes);
+
+ default:
+ LOG_ERR("Reading from reg 0x%x which is WO or undefined", reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Function called for each byte of write message. Data are stored
+ * in write_data field of tcpci_emul_data or in tx_msg in case of
+ * writing to TX buffer.
+ *
+ * @param i2c_emul Pointer to TCPCI emulator
+ * @param reg First byte of write message
+ * @param val Received byte of write message
+ * @param bytes Number of bytes already received
+ *
+ * @return 0 on success
+ * @return -EIO on invalid write to TX buffer
+ */
+static int tcpci_emul_write_byte(struct i2c_emul *i2c_emul, int reg,
+ uint8_t val, int bytes)
+{
+ struct tcpci_emul_data *data;
+ const struct emul *emul;
+ int is_rev1;
+
+ emul = i2c_emul->parent;
+ data = TCPCI_DATA_FROM_I2C_EMUL(i2c_emul);
+
+ if (data->dev_ops && data->dev_ops->write_byte) {
+ switch (data->dev_ops->write_byte(emul, data->dev_ops, reg, val,
+ bytes)) {
+ case TCPCI_EMUL_CONTINUE:
+ break;
+ case TCPCI_EMUL_DONE:
+ return 0;
+ case TCPCI_EMUL_ERROR:
+ default:
+ return -EIO;
+ }
+ }
+
+ is_rev1 = data->reg[TCPC_REG_PD_INT_REV] == TCPC_REG_PD_INT_REV_REV_1_0;
+ switch (reg) {
+ case TCPC_REG_TX_BUFFER:
+ if (is_rev1) {
+ if (bytes > 1) {
+ LOG_ERR("Rev 1.0 has only byte count at 0x51");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ data->tx_msg->idx = val;
+ }
+
+ if (bytes == 1) {
+ data->tx_msg->cnt = val;
+ } else {
+ if (data->tx_msg->cnt > 0) {
+ data->tx_msg->cnt--;
+ data->tx_msg->buf[data->tx_msg->idx] = val;
+ data->tx_msg->idx++;
+ } else {
+ LOG_ERR("Writing past TX buffer");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ }
+
+ return 0;
+
+ case TCPC_REG_TX_DATA:
+ if (!is_rev1) {
+ LOG_ERR("Register 0x%x defined only for revision 1.0",
+ reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+
+ /* Skip header and account reg byte */
+ bytes += 2 - 1;
+
+ if (bytes > 29) {
+ LOG_ERR("Writing past TX buffer");
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ data->tx_msg->buf[bytes] = val;
+ return 0;
+
+ case TCPC_REG_TX_HDR:
+ if (!is_rev1) {
+ LOG_ERR("Register 0x%x defined only for revision 1.0",
+ reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+
+ /* Account reg byte */
+ bytes -= 1;
+
+ if (bytes > 1) {
+ LOG_ERR("Writing byte %d to 2 byte register 0x%x",
+ bytes, reg);
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+ data->tx_msg->buf[bytes] = val;
+ return 0;
+ }
+
+ if (bytes == 1) {
+ data->write_data = val;
+ } else if (bytes == 2) {
+ data->write_data |= (uint16_t)val << 8;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Handle writes to command register
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return 0 on success
+ * @return -EIO on unknown command value
+ */
+static int tcpci_emul_handle_command(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ switch (data->write_data & 0xff) {
+ case TCPC_REG_COMMAND_RESET_TRANSMIT_BUF:
+ data->tx_msg->idx = 0;
+ break;
+ case TCPC_REG_COMMAND_RESET_RECEIVE_BUF:
+ if (data->rx_msg) {
+ data->rx_msg->idx = 0;
+ }
+ break;
+ case TCPC_REG_COMMAND_ENABLE_VBUS_DETECT:
+ case TCPC_REG_COMMAND_SNK_CTRL_LOW:
+ case TCPC_REG_COMMAND_SNK_CTRL_HIGH:
+ case TCPC_REG_COMMAND_SRC_CTRL_LOW:
+ case TCPC_REG_COMMAND_SRC_CTRL_HIGH:
+ case TCPC_REG_COMMAND_LOOK4CONNECTION:
+ case TCPC_REG_COMMAND_I2CIDLE:
+ break;
+ default:
+ tcpci_emul_set_i2c_interface_err(emul);
+ return -EIO;
+ }
+
+ /*
+ * Set command register to allow easier inspection of last
+ * command sent
+ */
+ tcpci_emul_set_reg(emul, TCPC_REG_COMMAND, data->write_data & 0xff);
+ return 0;
+}
+
+/**
+ * @brief Handle write to transmit register
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return 0 on success
+ */
+static int tcpci_emul_handle_transmit(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ data->tx_msg->cnt = data->tx_msg->idx;
+ data->tx_msg->type = TCPC_REG_TRANSMIT_TYPE(data->write_data);
+ data->tx_msg->idx = 0;
+
+ if (data->partner && data->partner->transmit) {
+ data->partner->transmit(emul, data->partner, data->tx_msg,
+ TCPC_REG_TRANSMIT_TYPE(data->write_data),
+ TCPC_REG_TRANSMIT_RETRY(data->write_data));
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Handle I2C write message. It is checked if accessed register isn't RO
+ * and reserved bits are set to 0.
+ *
+ * @param i2c_emul Pointer to TCPCI emulator
+ * @param reg Register which is written
+ * @param msg_len Length of handled I2C message
+ *
+ * @return 0 on success
+ * @return -EIO on error
+ */
+static int tcpci_emul_handle_write(struct i2c_emul *i2c_emul, int reg,
+ int msg_len)
+{
+ struct tcpci_emul_data *data;
+ const struct emul *emul;
+ uint16_t rsvd_mask = 0;
+ uint16_t alert_val;
+ bool inform_partner = false;
+ bool alert_changed = false;
+ int reg_bytes;
+
+ /* This write message was setting register before read */
+ if (msg_len == 1) {
+ return 0;
+ }
+
+ /* Exclude register address byte from message length */
+ msg_len--;
+
+ emul = i2c_emul->parent;
+ data = TCPCI_DATA_FROM_I2C_EMUL(i2c_emul);
+
+ if (data->dev_ops && data->dev_ops->handle_write) {
+ switch (data->dev_ops->handle_write(emul, data->dev_ops, reg,
+ msg_len)) {
+ case TCPCI_EMUL_CONTINUE:
+ break;
+ case TCPCI_EMUL_DONE:
+ return 0;
+ case TCPCI_EMUL_ERROR:
+ default:
+ return -EIO;
+ }
+ }
+
+ switch (reg) {
+ /* Alert registers */
+ case TCPC_REG_ALERT:
+ /* Overflow is cleared by Receive SOP message status */
+ data->write_data &= ~TCPC_REG_ALERT_RX_BUF_OVF;
+ if (data->write_data & TCPC_REG_ALERT_RX_STATUS) {
+ data->write_data |= TCPC_REG_ALERT_RX_BUF_OVF;
+ /* Load next message if possible */
+ if (data->rx_msg && data->rx_msg->next) {
+ data->write_data &= ~TCPC_REG_ALERT_RX_STATUS;
+ data->rx_msg = data->rx_msg->next;
+ data->rx_msg->idx = 0;
+ } else {
+ data->rx_msg = NULL;
+ }
+ }
+ /* fallthrough */
+ case TCPC_REG_FAULT_STATUS:
+ case TCPC_REG_ALERT_EXT:
+ /* Clear bits where TCPM set 1 */
+ tcpci_emul_get_reg(emul, reg, &alert_val);
+ data->write_data = alert_val & (~data->write_data);
+ /* fallthrough */
+ case TCPC_REG_ALERT_MASK:
+ case TCPC_REG_POWER_STATUS_MASK:
+ case TCPC_REG_FAULT_STATUS_MASK:
+ case TCPC_REG_EXT_STATUS_MASK:
+ case TCPC_REG_ALERT_EXTENDED_MASK:
+ alert_changed = true;
+ break;
+
+ /* Control registers */
+ case TCPC_REG_TCPC_CTRL:
+ case TCPC_REG_ROLE_CTRL:
+ case TCPC_REG_FAULT_CTRL:
+ case TCPC_REG_POWER_CTRL:
+ inform_partner = true;
+ break;
+
+ /* Simple write registers */
+ case TCPC_REG_VBUS_SINK_DISCONNECT_THRESH:
+ case TCPC_REG_VBUS_STOP_DISCHARGE_THRESH:
+ case TCPC_REG_VBUS_VOLTAGE_ALARM_HI_CFG:
+ case TCPC_REG_VBUS_VOLTAGE_ALARM_LO_CFG:
+ case TCPC_REG_VBUS_NONDEFAULT_TARGET:
+ case TCPC_REG_CONFIG_STD_OUTPUT:
+ case TCPC_REG_MSG_HDR_INFO:
+ case TCPC_REG_RX_DETECT:
+ break;
+
+ case TCPC_REG_CONFIG_EXT_1:
+ if (data->write_data & TCPC_REG_CONFIG_EXT_1_FR_SWAP_SNK_DIR &&
+ ((data->reg[TCPC_REG_STD_INPUT_CAP] &
+ TCPC_REG_STD_INPUT_CAP_SRC_FR_SWAP) == BIT(4)) &&
+ data->reg[TCPC_REG_STD_OUTPUT_CAP] &
+ TCPC_REG_STD_OUTPUT_CAP_SNK_DISC_DET) {
+ tcpci_emul_set_i2c_interface_err(emul);
+ return 0;
+ }
+ break;
+
+ case TCPC_REG_COMMAND:
+ if (msg_len != 1) {
+ tcpci_emul_set_i2c_interface_err(emul);
+ LOG_ERR("Writing byte %d to 1 byte register 0x%x",
+ msg_len, reg);
+ return -EIO;
+ }
+ return tcpci_emul_handle_command(emul);
+
+ case TCPC_REG_TRANSMIT:
+ if (msg_len != 1) {
+ tcpci_emul_set_i2c_interface_err(emul);
+ LOG_ERR("Writing byte %d to 1 byte register 0x%x",
+ msg_len, reg);
+ return -EIO;
+ }
+ return tcpci_emul_handle_transmit(emul);
+
+ case TCPC_REG_GENERIC_TIMER:
+ /* TODO: Add timer */
+ return 0;
+
+ /* Already handled in tcpci_emul_write_byte() */
+ case TCPC_REG_TX_BUFFER:
+ case TCPC_REG_TX_DATA:
+ case TCPC_REG_TX_HDR:
+ return 0;
+ default:
+ tcpci_emul_set_i2c_interface_err(emul);
+ LOG_ERR("Write to reg 0x%x which is RO, undefined or unaligned",
+ reg);
+ return -EIO;
+ }
+
+ reg_bytes = tcpci_emul_reg_bytes(reg);
+
+ /* Compute reserved bits mask */
+ switch (reg_bytes) {
+ case 2:
+ rsvd_mask = tcpci_emul_rsvd_mask[reg + 1];
+ case 1:
+ rsvd_mask <<= 8;
+ rsvd_mask |= tcpci_emul_rsvd_mask[reg];
+ break;
+ }
+
+ /* Check reserved bits */
+ if (data->error_on_rsvd_write && rsvd_mask & data->write_data) {
+ tcpci_emul_set_i2c_interface_err(emul);
+ LOG_ERR("Writing 0x%x to reg 0x%x with rsvd bits mask 0x%x",
+ data->write_data, reg, rsvd_mask);
+ return -EIO;
+ }
+
+ /* Check if I2C write message has correct length */
+ if (msg_len != reg_bytes) {
+ tcpci_emul_set_i2c_interface_err(emul);
+ LOG_ERR("Writing byte %d to %d byte register 0x%x",
+ msg_len, reg_bytes, reg);
+ return -EIO;
+ }
+
+ /* Set new value of register */
+ tcpci_emul_set_reg(emul, reg, data->write_data);
+
+ if (alert_changed) {
+ tcpci_emul_alert_changed(emul);
+ }
+
+ if (inform_partner && data->partner && data->partner->control_change) {
+ data->partner->control_change(emul, data->partner);
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Get currently accessed register, which always equals to selected
+ * register.
+ *
+ * @param i2c_emul Pointer to TCPCI emulator
+ * @param reg First byte of last write message
+ * @param bytes Number of bytes already handled from current message
+ * @param read If currently handled is read message
+ *
+ * @return Currently accessed register
+ */
+static int tcpci_emul_access_reg(struct i2c_emul *i2c_emul, int reg, int bytes,
+ bool read)
+{
+ return reg;
+}
+
+/* Device instantiation */
+
+/** Check description in emul_tcpci.h */
+struct i2c_emul *tcpci_emul_get_i2c_emul(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ return &data->common.emul;
+}
+
+/**
+ * @brief Set up a new TCPCI emulator
+ *
+ * This should be called for each TCPCI device that needs to be
+ * emulated. It registers it with the I2C emulation controller.
+ *
+ * @param emul Emulation information
+ * @param parent Device to emulate
+ *
+ * @return 0 indicating success (always)
+ */
+static int tcpci_emul_init(const struct emul *emul, const struct device *parent)
+{
+ const struct i2c_common_emul_cfg *cfg = emul->cfg;
+ struct tcpci_emul_data *data = emul->data;
+ int ret;
+
+ data->common.emul.api = &i2c_common_emul_api;
+ data->common.emul.addr = cfg->addr;
+ data->common.emul.parent = emul;
+ data->common.i2c = parent;
+ data->common.cfg = cfg;
+ i2c_common_emul_init(&data->common);
+
+ ret = i2c_emul_register(parent, emul->dev_label, &data->common.emul);
+
+ tcpci_emul_reset(emul);
+
+ return ret;
+}
+
+#define TCPCI_EMUL(n) \
+ uint8_t tcpci_emul_tx_buf_##n[128]; \
+ static struct tcpci_emul_msg tcpci_emul_tx_msg_##n = { \
+ .buf = tcpci_emul_tx_buf_##n, \
+ }; \
+ \
+ static struct tcpci_emul_data tcpci_emul_data_##n = { \
+ .tx_msg = &tcpci_emul_tx_msg_##n, \
+ .error_on_ro_write = true, \
+ .error_on_rsvd_write = true, \
+ .common = { \
+ .write_byte = tcpci_emul_write_byte, \
+ .finish_write = tcpci_emul_handle_write, \
+ .read_byte = tcpci_emul_read_byte, \
+ .access_reg = tcpci_emul_access_reg, \
+ }, \
+ }; \
+ \
+ static const struct i2c_common_emul_cfg tcpci_emul_cfg_##n = { \
+ .i2c_label = DT_INST_BUS_LABEL(n), \
+ .dev_label = DT_INST_LABEL(n), \
+ .data = &tcpci_emul_data_##n.common, \
+ .addr = DT_INST_REG_ADDR(n), \
+ }; \
+ EMUL_DEFINE(tcpci_emul_init, DT_DRV_INST(n), \
+ &tcpci_emul_cfg_##n, &tcpci_emul_data_##n)
+
+DT_INST_FOREACH_STATUS_OKAY(TCPCI_EMUL)
diff --git a/zephyr/include/emul/emul_tcpci.h b/zephyr/include/emul/emul_tcpci.h
new file mode 100644
index 0000000000..98a3495b02
--- /dev/null
+++ b/zephyr/include/emul/emul_tcpci.h
@@ -0,0 +1,239 @@
+/* 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.
+ */
+
+/**
+ * @file
+ *
+ * @brief Backend API for TCPCI emulator
+ */
+
+#ifndef __EMUL_TCPCI_H
+#define __EMUL_TCPCI_H
+
+#include <emul.h>
+#include <drivers/i2c.h>
+#include <drivers/i2c_emul.h>
+#include <usb_pd_tcpm.h>
+
+/**
+ * @brief TCPCI emulator backend API
+ * @defgroup tcpci_emul TCPCI emulator
+ * @{
+ *
+ * TCPCI emulator supports access to its registers using I2C messages.
+ * It follows Type-C Port Controller Interface Specification. It is possible
+ * to use this emulator as base for implementation of specific TCPC emulator
+ * which follows TCPCI specification. Emulator allows to set callbacks
+ * on change of CC status or transmitting message to implement partner emulator.
+ * There is also callback used to inform about alert line state change.
+ * Application may alter emulator state:
+ *
+ * - call @ref tcpci_emul_set_reg and @ref tcpci_emul_get_reg to set and get
+ * value of TCPCI registers
+ * - call functions from emul_common_i2c.h to setup custom handlers for I2C
+ * messages
+ * - call @ref tcpci_emul_add_rx_msg to setup received SOP messages
+ * - call @ref tcpci_emul_get_tx_msg to examine sended message
+ * - call @ref tcpci_emul_set_rev to set revision of emulated TCPCI
+ */
+
+/** SOP message structure */
+struct tcpci_emul_msg {
+ /** Pointer to buffer for header and message */
+ uint8_t *buf;
+ /** Number of bytes in buf */
+ int cnt;
+ /** Type of message (SOP, SOP', etc) */
+ uint8_t type;
+ /** Index used to mark accessed byte */
+ int idx;
+ /** Pointer to optional second message */
+ struct tcpci_emul_msg *next;
+};
+
+/**
+ * @brief Function type that is used by TCPCI emulator to provide information
+ * about alert line state
+ *
+ * @param emul Pointer to emulator
+ * @param alert State of alert line (false - low, true - high)
+ * @param data Pointer to custom function data
+ */
+typedef void (*tcpci_emul_alert_state_func)(const struct emul *emul, bool alert,
+ void *data);
+
+/** Response from TCPCI specific device operations */
+enum tcpci_emul_ops_resp {
+ TCPCI_EMUL_CONTINUE = 0,
+ TCPCI_EMUL_DONE,
+ TCPCI_EMUL_ERROR
+};
+
+/** Revisions supported by TCPCI emaluator */
+enum tcpci_emul_rev {
+ TCPCI_EMUL_REV1_0_VER1_0 = 0,
+ TCPCI_EMUL_REV2_0_VER1_1
+};
+
+/** TCPCI specific device operations. Not all of them need to be implemented. */
+struct tcpci_emul_dev_ops {
+ /**
+ * @brief Function called for each byte of read message
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to device operations structure
+ * @param reg First byte of last write message
+ * @param val Pointer where byte to read should be stored
+ * @param bytes Number of bytes already readded
+ *
+ * @return TCPCI_EMUL_CONTINUE to continue with default handler
+ * @return TCPCI_EMUL_DONE to immedietly return success
+ * @return TCPCI_EMUL_ERROR to immedietly return error
+ */
+ enum tcpci_emul_ops_resp (*read_byte)(const struct emul *emul,
+ const struct tcpci_emul_dev_ops *ops,
+ int reg, uint8_t *val, int bytes);
+
+ /**
+ * @brief Function called for each byte of write message
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to device operations structure
+ * @param reg First byte of write message
+ * @param val Received byte of write message
+ * @param bytes Number of bytes already received
+ *
+ * @return TCPCI_EMUL_CONTINUE to continue with default handler
+ * @return TCPCI_EMUL_DONE to immedietly return success
+ * @return TCPCI_EMUL_ERROR to immedietly return error
+ */
+ enum tcpci_emul_ops_resp (*write_byte)(const struct emul *emul,
+ const struct tcpci_emul_dev_ops *ops,
+ int reg, uint8_t val, int bytes);
+
+ /**
+ * @brief Function called on the end of write message
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to device operations structure
+ * @param reg Register which is written
+ * @param msg_len Length of handled I2C message
+ *
+ * @return TCPCI_EMUL_CONTINUE to continue with default handler
+ * @return TCPCI_EMUL_DONE to immedietly return success
+ * @return TCPCI_EMUL_ERROR to immedietly return error
+ */
+ enum tcpci_emul_ops_resp (*handle_write)(const struct emul *emul,
+ const struct tcpci_emul_dev_ops *ops,
+ int reg, int msg_len);
+
+ /**
+ * @brief Function called on reset
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to device operations structure
+ */
+ void (*reset)(const struct emul *emul, struct tcpci_emul_dev_ops *ops);
+};
+
+/** TCPCI partner operations. Not all of them need to be implemented. */
+struct tcpci_emul_partner_ops {
+ /**
+ * @brief Function called when TCPM wants to transmit message to partner
+ * connected to TCPCI
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to partner operations structure
+ * @param tx_msg Pointer to TX message buffer
+ * @param type Type of message
+ * @param retry Count of retries
+ */
+ void (*transmit)(const struct emul *emul,
+ const struct tcpci_emul_partner_ops *ops,
+ const struct tcpci_emul_msg *tx_msg,
+ enum tcpci_msg_type type,
+ int retry);
+
+ /**
+ * @brief Function called when control settings change to allow partner
+ * to react
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to partner operations structure
+ */
+ void (*control_change)(const struct emul *emul,
+ const struct tcpci_emul_partner_ops *ops);
+};
+
+/**
+ * @brief Get i2c_emul for TCPCI emulator
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return Pointer to I2C TCPCI emulator
+ */
+struct i2c_emul *tcpci_emul_get_i2c_emul(const struct emul *emul);
+
+/**
+ * @brief Set value of given register of TCPCI
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param reg Register address which value will be changed
+ * @param val New value of the register
+ *
+ * @return 0 on success
+ * @return -EINVAL when register is out of range defined in TCPCI specification
+ */
+int tcpci_emul_set_reg(const struct emul *emul, int reg, uint16_t val);
+
+/**
+ * @brief Get value of given register of TCPCI
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param reg Register address
+ * @param val Pointer where value should be stored
+ *
+ * @return 0 on success
+ * @return -EINVAL when register is out of range defined in TCPCI specification
+ * or val is NULL
+ */
+int tcpci_emul_get_reg(const struct emul *emul, int reg, uint16_t *val);
+
+/**
+ * @brief Add up to two SOP RX messages
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param rx_msg Pointer to message that is added
+ * @param alert Select if alert register should be updated
+ *
+ * @return 0 on success
+ * @return -EINVAL on error (too long message or adding third message)
+ */
+int tcpci_emul_add_rx_msg(const struct emul *emul,
+ struct tcpci_emul_msg *rx_msg, bool alert);
+
+/**
+ * @brief Get SOP TX message to examine what was sended by TCPM
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return Pointer to TX message
+ */
+struct tcpci_emul_msg *tcpci_emul_get_tx_msg(const struct emul *emul);
+
+/**
+ * @brief Set TCPCI revision in PD_INT_REV register
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param rev Requested revision
+ */
+void tcpci_emul_set_rev(const struct emul *emul, enum tcpci_emul_rev rev);
+
+/**
+ * @}
+ */
+
+#endif /* __EMUL_TCPCI */
+