diff options
-rw-r--r-- | include/driver/tcpm/tcpci.h | 34 | ||||
-rw-r--r-- | zephyr/dts/bindings/emul/cros,tcpci-emul.yaml | 9 | ||||
-rw-r--r-- | zephyr/emul/CMakeLists.txt | 1 | ||||
-rw-r--r-- | zephyr/emul/Kconfig | 1 | ||||
-rw-r--r-- | zephyr/emul/Kconfig.tcpci | 24 | ||||
-rw-r--r-- | zephyr/emul/emul_tcpci.c | 1115 | ||||
-rw-r--r-- | zephyr/include/emul/emul_tcpci.h | 239 |
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 */ + |