summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Michalec <tm@semihalf.com>2021-10-05 18:55:03 +0200
committerCommit Bot <commit-bot@chromium.org>2021-10-12 12:26:51 +0000
commit8a68e5665016200c454a7019b0625e3ccc2a8515 (patch)
tree02b61ec1e21e39bac0a9fccd3e115d01e38108c8
parent63962c7e6cf8499780ce378d23d49702a033d537 (diff)
downloadchrome-ec-8a68e5665016200c454a7019b0625e3ccc2a8515.tar.gz
zephyr: add TCPCI emulator
Add TCPCI emulator. It follows TCPCI specification revision 2.0 version 1.2, but allows access to TX and RX buffer registers in revision 1.0 style if configured to do so. It can be extended by device specific operations (to implement specific TCPC device behaviour) and partner operations to emulate scenarios with different devices connected on other side of TCPC. BUG=b:184857030 BRANCH=none TEST=make configure --test zephyr/test/drivers Signed-off-by: Tomasz Michalec <tm@semihalf.com> Change-Id: I6305c437d820528de24439337211f32a015e83d1 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3181722 Tested-by: Tomasz Michalec <tmichalec@google.com> Commit-Queue: Tomasz Michalec <tmichalec@google.com> Reviewed-by: Yuval Peress <peress@google.com> Reviewed-by: Abe Levkoy <alevkoy@chromium.org>
-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 */
+