summaryrefslogtreecommitdiff
path: root/zephyr/emul/tcpc
diff options
context:
space:
mode:
authorTomasz Michalec <tm@semihalf.com>2021-12-10 12:47:20 +0100
committerCommit Bot <commit-bot@chromium.org>2021-12-16 09:58:35 +0000
commit28ceff1f7b9294d49dea37396229fca0644e5216 (patch)
treea841ed928a6488c09dcffb768ef8468d7e097f96 /zephyr/emul/tcpc
parent36b85b1fb4f5f472c56b321f2e84ea2c7c3a2054 (diff)
downloadchrome-ec-28ceff1f7b9294d49dea37396229fca0644e5216.tar.gz
zephyr: emul: Move TCPC emulators to sub directory
There are few emulators related to TCPC and each other and new are expected to be created. Because of that, zephyr/emul/tcpc/ subdirectory is created to keep these emulators in one place. BUG=none BRANCH=none TEST=make configure --test zephyr/test/drivers Signed-off-by: Tomasz Michalec <tm@semihalf.com> Change-Id: Ida4903dd4ab9307f6783af8e14ae99d25ec76edb Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3330197 Reviewed-by: Abe Levkoy <alevkoy@chromium.org> Reviewed-by: Keith Short <keithshort@chromium.org> Tested-by: Tomasz Michalec <tmichalec@google.com> Commit-Queue: Tomasz Michalec <tmichalec@google.com>
Diffstat (limited to 'zephyr/emul/tcpc')
-rw-r--r--zephyr/emul/tcpc/CMakeLists.txt8
-rw-r--r--zephyr/emul/tcpc/Kconfig48
-rw-r--r--zephyr/emul/tcpc/emul_ps8xxx.c577
-rw-r--r--zephyr/emul/tcpc/emul_tcpci.c1399
-rw-r--r--zephyr/emul/tcpc/emul_tcpci_partner_common.c149
-rw-r--r--zephyr/emul/tcpc/emul_tcpci_partner_src.c301
6 files changed, 2482 insertions, 0 deletions
diff --git a/zephyr/emul/tcpc/CMakeLists.txt b/zephyr/emul/tcpc/CMakeLists.txt
new file mode 100644
index 0000000000..9dcf8f6024
--- /dev/null
+++ b/zephyr/emul/tcpc/CMakeLists.txt
@@ -0,0 +1,8 @@
+# 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.
+
+zephyr_library_sources_ifdef(CONFIG_EMUL_TCPCI emul_tcpci.c)
+zephyr_library_sources_ifdef(CONFIG_EMUL_PS8XXX emul_ps8xxx.c)
+zephyr_library_sources_ifdef(CONFIG_EMUL_TCPCI_PARTNER_SRC emul_tcpci_partner_src.c)
+zephyr_library_sources_ifdef(CONFIG_EMUL_TCPCI_PARTNER_COMMON emul_tcpci_partner_common.c)
diff --git a/zephyr/emul/tcpc/Kconfig b/zephyr/emul/tcpc/Kconfig
new file mode 100644
index 0000000000..f4ebbaad9e
--- /dev/null
+++ b/zephyr/emul/tcpc/Kconfig
@@ -0,0 +1,48 @@
+# 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/tcpc/emul_tcpci.h
+
+if EMUL_TCPCI
+
+module = TCPCI_EMUL
+module-str = tcpci_emul
+source "subsys/logging/Kconfig.template.log_config"
+
+config EMUL_TCPCI_PARTNER_COMMON
+ bool
+ help
+ This option is selected automatically by specific TCPCI partner
+ emulators. Enable common code that can be used by TCPCI partner device
+ emulators. It covers sending delayed messages. API of common functions
+ is available in zephyr/include/emul/tcpc/emul_common_tcpci_partner.h
+
+config EMUL_PS8XXX
+ bool "Parade PS8XXX emulator"
+ help
+ Enable emulator for PS8XXX family of TCPC. This emulator is extenstion
+ for TCPCI emulator. PS8XXX specific API is available in
+ zephyr/include/emul/tcpc/emul_ps8xxx.h
+
+config EMUL_TCPCI_PARTNER_SRC
+ bool "USB-C source device emulator"
+ select EMUL_TCPCI_PARTNER_COMMON
+ help
+ Enable USB-C source device emulator which may be attached to TCPCI
+ emulator. API of source device emulator is available in
+ zephyr/include/emul/tcpc/emul_tcpci_partner_src.h
+
+endif # EMUL_TCPCI
diff --git a/zephyr/emul/tcpc/emul_ps8xxx.c b/zephyr/emul/tcpc/emul_ps8xxx.c
new file mode 100644
index 0000000000..5a36f39cd1
--- /dev/null
+++ b/zephyr/emul/tcpc/emul_ps8xxx.c
@@ -0,0 +1,577 @@
+/* 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_ps8xxx_emul
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(ps8xxx_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/tcpc/emul_ps8xxx.h"
+#include "emul/tcpc/emul_tcpci.h"
+
+#include "driver/tcpm/ps8xxx.h"
+
+#define PS8XXX_REG_MUX_IN_HPD_ASSERTION MUX_IN_HPD_ASSERTION_REG
+
+/** Run-time data used by the emulator */
+struct ps8xxx_emul_data {
+ /** Common I2C data used by "hidden" ports */
+ struct i2c_common_emul_data p0_data;
+ struct i2c_common_emul_data p1_data;
+ struct i2c_common_emul_data gpio_data;
+
+ /** Product ID of emulated device */
+ int prod_id;
+ /** Pointer to TCPCI emulator that is base for this emulator */
+ const struct emul *tcpci_emul;
+
+ /** Chip revision used by PS8805 */
+ uint8_t chip_rev;
+ /** Mux usb DCI configuration */
+ uint8_t dci_cfg;
+ /** GPIO control register value */
+ uint8_t gpio_ctrl;
+ /** HW revision used by PS8815 */
+ uint16_t hw_rev;
+};
+
+/** Constant configuration of the emulator */
+struct ps8xxx_emul_cfg {
+ /** Phandle (name) of TCPCI emulator that is base for this emulator */
+ const char *tcpci_emul;
+
+ /** Common I2C configuration used by "hidden" ports */
+ const struct i2c_common_emul_cfg p0_cfg;
+ const struct i2c_common_emul_cfg p1_cfg;
+ const struct i2c_common_emul_cfg gpio_cfg;
+};
+
+/** Check description in emul_ps8xxx.h */
+void ps8xxx_emul_set_chip_rev(const struct emul *emul, uint8_t chip_rev)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ data->chip_rev = chip_rev;
+}
+
+/** Check description in emul_ps8xxx.h */
+void ps8xxx_emul_set_hw_rev(const struct emul *emul, uint16_t hw_rev)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ data->hw_rev = hw_rev;
+}
+
+/** Check description in emul_ps8xxx.h */
+void ps8xxx_emul_set_gpio_ctrl(const struct emul *emul, uint8_t gpio_ctrl)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ data->gpio_ctrl = gpio_ctrl;
+}
+
+/** Check description in emul_ps8xxx.h */
+uint8_t ps8xxx_emul_get_gpio_ctrl(const struct emul *emul)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ return data->gpio_ctrl;
+}
+
+/** Check description in emul_ps8xxx.h */
+uint8_t ps8xxx_emul_get_dci_cfg(const struct emul *emul)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ return data->dci_cfg;
+}
+
+/** Check description in emul_ps8xxx.h */
+int ps8xxx_emul_set_product_id(const struct emul *emul, uint16_t product_id)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ if (product_id != PS8805_PRODUCT_ID &&
+ product_id != PS8815_PRODUCT_ID) {
+ LOG_ERR("Setting invalid product ID 0x%x", product_id);
+ return -EINVAL;
+ }
+
+ data->prod_id = product_id;
+ tcpci_emul_set_reg(data->tcpci_emul, TCPC_REG_PRODUCT_ID, product_id);
+
+ return 0;
+}
+
+/** Check description in emul_ps8xxx.h */
+uint16_t ps8xxx_emul_get_product_id(const struct emul *emul)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ return data->prod_id;
+}
+
+const struct emul *ps8xxx_emul_get_tcpci(const struct emul *emul)
+{
+ struct ps8xxx_emul_data *data = emul->data;
+
+ return data->tcpci_emul;
+}
+
+/** Check description in emul_ps8xxx.h */
+struct i2c_emul *ps8xxx_emul_get_i2c_emul(const struct emul *emul,
+ enum ps8xxx_emul_port port)
+{
+ const struct ps8xxx_emul_cfg *cfg = emul->cfg;
+ struct ps8xxx_emul_data *data = emul->data;
+
+ switch (port) {
+ case PS8XXX_EMUL_PORT_0:
+ return &data->p0_data.emul;
+ case PS8XXX_EMUL_PORT_1:
+ return &data->p1_data.emul;
+ case PS8XXX_EMUL_PORT_GPIO:
+ if (cfg->gpio_cfg.addr != 0) {
+ return &data->gpio_data.emul;
+ } else {
+ return NULL;
+ }
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * @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
+ */
+static enum tcpci_emul_ops_resp ps8xxx_emul_tcpci_read_byte(
+ const struct emul *emul,
+ const struct tcpci_emul_dev_ops *ops,
+ int reg, uint8_t *val, int bytes)
+{
+ uint16_t reg_val;
+
+ switch (reg) {
+ case PS8XXX_REG_FW_REV:
+ case PS8XXX_REG_I2C_DEBUGGING_ENABLE:
+ case PS8XXX_REG_MUX_IN_HPD_ASSERTION:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE0:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE1:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE2:
+ case PS8XXX_REG_BIST_CONT_MODE_CTR:
+ if (bytes != 0) {
+ LOG_ERR("Reading byte %d from 1 byte register 0x%x",
+ bytes, reg);
+ return TCPCI_EMUL_ERROR;
+ }
+
+ tcpci_emul_get_reg(emul, reg, &reg_val);
+ *val = reg_val & 0xff;
+ return TCPCI_EMUL_DONE;
+ default:
+ return TCPCI_EMUL_CONTINUE;
+ }
+}
+
+/**
+ * @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
+ */
+static enum tcpci_emul_ops_resp ps8xxx_emul_tcpci_write_byte(
+ const struct emul *emul,
+ const struct tcpci_emul_dev_ops *ops,
+ int reg, uint8_t val, int bytes)
+{
+ uint16_t prod_id;
+
+ tcpci_emul_get_reg(emul, TCPC_REG_PRODUCT_ID, &prod_id);
+
+ switch (reg) {
+ case PS8XXX_REG_RP_DETECT_CONTROL:
+ /* This register is present only on PS8815 */
+ if (prod_id != PS8815_PRODUCT_ID) {
+ return TCPCI_EMUL_CONTINUE;
+ }
+ case PS8XXX_REG_I2C_DEBUGGING_ENABLE:
+ case PS8XXX_REG_MUX_IN_HPD_ASSERTION:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE0:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE1:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE2:
+ case PS8XXX_REG_BIST_CONT_MODE_CTR:
+ if (bytes != 1) {
+ LOG_ERR("Writing byte %d to 1 byte register 0x%x",
+ bytes, reg);
+ return TCPCI_EMUL_ERROR;
+ }
+
+ tcpci_emul_set_reg(emul, reg, val);
+ return TCPCI_EMUL_DONE;
+ default:
+ return TCPCI_EMUL_CONTINUE;
+ }
+}
+
+/**
+ * @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
+ */
+static enum tcpci_emul_ops_resp ps8xxx_emul_tcpci_handle_write(
+ const struct emul *emul,
+ const struct tcpci_emul_dev_ops *ops,
+ int reg, int msg_len)
+{
+ uint16_t prod_id;
+
+ tcpci_emul_get_reg(emul, TCPC_REG_PRODUCT_ID, &prod_id);
+
+ switch (reg) {
+ case PS8XXX_REG_RP_DETECT_CONTROL:
+ /* This register is present only on PS8815 */
+ if (prod_id != PS8815_PRODUCT_ID) {
+ return TCPCI_EMUL_CONTINUE;
+ }
+ case PS8XXX_REG_I2C_DEBUGGING_ENABLE:
+ case PS8XXX_REG_MUX_IN_HPD_ASSERTION:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE0:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE1:
+ case PS8XXX_REG_BIST_CONT_MODE_BYTE2:
+ case PS8XXX_REG_BIST_CONT_MODE_CTR:
+ return TCPCI_EMUL_DONE;
+ default:
+ return TCPCI_EMUL_CONTINUE;
+ }
+}
+
+/**
+ * @brief Function called on reset
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to device operations structure
+ */
+static void ps8xxx_emul_tcpci_reset(const struct emul *emul,
+ struct tcpci_emul_dev_ops *ops)
+{
+ tcpci_emul_set_reg(emul, PS8XXX_REG_I2C_DEBUGGING_ENABLE, 0x31);
+ tcpci_emul_set_reg(emul, PS8XXX_REG_MUX_IN_HPD_ASSERTION, 0x00);
+ tcpci_emul_set_reg(emul, PS8XXX_REG_BIST_CONT_MODE_BYTE0, 0xff);
+ tcpci_emul_set_reg(emul, PS8XXX_REG_BIST_CONT_MODE_BYTE1, 0x0f);
+ tcpci_emul_set_reg(emul, PS8XXX_REG_BIST_CONT_MODE_BYTE2, 0x00);
+ tcpci_emul_set_reg(emul, PS8XXX_REG_BIST_CONT_MODE_CTR, 0x00);
+}
+
+/** TCPCI PS8xxx operations */
+static struct tcpci_emul_dev_ops ps8xxx_emul_ops = {
+ .read_byte = ps8xxx_emul_tcpci_read_byte,
+ .write_byte = ps8xxx_emul_tcpci_write_byte,
+ .handle_write = ps8xxx_emul_tcpci_handle_write,
+ .reset = ps8xxx_emul_tcpci_reset,
+};
+
+/**
+ * @brief Get port associated with given "hidden" I2C device
+ *
+ * @param i2c_emul Pointer to "hidden" I2C device
+ *
+ * @return Port associated with given I2C device
+ */
+static enum ps8xxx_emul_port ps8xxx_emul_get_port(struct i2c_emul *i2c_emul)
+{
+ const struct ps8xxx_emul_cfg *cfg;
+ const struct emul *emul;
+
+ emul = i2c_emul->parent;
+ cfg = emul->cfg;
+
+ if (cfg->p0_cfg.addr == i2c_emul->addr) {
+ return PS8XXX_EMUL_PORT_0;
+ }
+
+ if (cfg->p1_cfg.addr == i2c_emul->addr) {
+ return PS8XXX_EMUL_PORT_1;
+ }
+
+ if (cfg->gpio_cfg.addr != 0 && cfg->gpio_cfg.addr == i2c_emul->addr) {
+ return PS8XXX_EMUL_PORT_GPIO;
+ }
+
+ return PS8XXX_EMUL_PORT_INVAL;
+}
+
+/**
+ * @brief Function called for each byte of read message
+ *
+ * @param i2c_emul Pointer to PS8xxx 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
+ * @return -EIO on invalid read request
+ */
+static int ps8xxx_emul_read_byte(struct i2c_emul *i2c_emul, int reg,
+ uint8_t *val, int bytes)
+{
+ struct ps8xxx_emul_data *data;
+ enum ps8xxx_emul_port port;
+ const struct emul *emul;
+ uint16_t i2c_dbg_reg;
+
+ emul = i2c_emul->parent;
+ data = emul->data;
+
+ tcpci_emul_get_reg(data->tcpci_emul, PS8XXX_REG_I2C_DEBUGGING_ENABLE,
+ &i2c_dbg_reg);
+ /* There is no need to enable I2C debug on PS8815 */
+ if (data->prod_id != PS8815_PRODUCT_ID && i2c_dbg_reg & 0x1) {
+ LOG_ERR("Accessing hidden i2c address without enabling debug");
+ return -EIO;
+ }
+
+ port = ps8xxx_emul_get_port(i2c_emul);
+
+ /* This is only 2 bytes register so handle it separately */
+ if (data->prod_id == PS8815_PRODUCT_ID && port == PS8XXX_EMUL_PORT_1 &&
+ reg == PS8815_P1_REG_HW_REVISION) {
+ if (bytes > 1) {
+ LOG_ERR("Reading more than two bytes from HW rev reg");
+ return -EIO;
+ }
+
+ *val = (data->hw_rev >> (bytes * 8)) & 0xff;
+ return 0;
+ }
+
+ if (bytes != 0) {
+ LOG_ERR("Reading more than one byte at once");
+ return -EIO;
+ }
+
+ switch (port) {
+ case PS8XXX_EMUL_PORT_0:
+ if (data->prod_id == PS8805_PRODUCT_ID &&
+ reg == PS8805_P0_REG_CHIP_REVISION) {
+ *val = data->chip_rev;
+ return 0;
+ }
+ break;
+ case PS8XXX_EMUL_PORT_1:
+ /* DCI CFG is no available on PS8815 */
+ if (data->prod_id != PS8815_PRODUCT_ID &&
+ reg == PS8XXX_P1_REG_MUX_USB_DCI_CFG) {
+ *val = data->dci_cfg;
+ return 0;
+ }
+ case PS8XXX_EMUL_PORT_GPIO:
+ if (reg == PS8805_REG_GPIO_CONTROL) {
+ *val = data->gpio_ctrl;
+ return 0;
+ }
+ case PS8XXX_EMUL_PORT_INVAL:
+ LOG_ERR("Invalid I2C address");
+ return -EIO;
+ }
+
+ LOG_ERR("Reading from reg 0x%x which is WO or undefined", reg);
+ return -EIO;
+}
+
+/**
+ * @brief Function called for each byte of write message
+ *
+ * @param i2c_emul Pointer to PS8xxx 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 request
+ */
+static int ps8xxx_emul_write_byte(struct i2c_emul *i2c_emul, int reg,
+ uint8_t val, int bytes)
+{
+ struct ps8xxx_emul_data *data;
+ enum ps8xxx_emul_port port;
+ const struct emul *emul;
+ uint16_t i2c_dbg_reg;
+
+ emul = i2c_emul->parent;
+ data = emul->data;
+
+ tcpci_emul_get_reg(data->tcpci_emul, PS8XXX_REG_I2C_DEBUGGING_ENABLE,
+ &i2c_dbg_reg);
+ /* There is no need to enable I2C debug on PS8815 */
+ if (data->prod_id != PS8815_PRODUCT_ID && i2c_dbg_reg & 0x1) {
+ LOG_ERR("Accessing hidden i2c address without enabling debug");
+ return -EIO;
+ }
+
+ port = ps8xxx_emul_get_port(i2c_emul);
+
+ if (bytes != 1) {
+ LOG_ERR("Writing more than one byte at once");
+ return -EIO;
+ }
+
+ switch (port) {
+ case PS8XXX_EMUL_PORT_0:
+ break;
+ case PS8XXX_EMUL_PORT_1:
+ /* DCI CFG is no available on PS8815 */
+ if (data->prod_id != PS8815_PRODUCT_ID &&
+ reg == PS8XXX_P1_REG_MUX_USB_DCI_CFG) {
+ data->dci_cfg = val;
+ return 0;
+ }
+ case PS8XXX_EMUL_PORT_GPIO:
+ if (reg == PS8805_REG_GPIO_CONTROL) {
+ data->gpio_ctrl = val;
+ return 0;
+ }
+ case PS8XXX_EMUL_PORT_INVAL:
+ LOG_ERR("Invalid I2C address");
+ return -EIO;
+ }
+
+ LOG_ERR("Writing to reg 0x%x which is RO or undefined", reg);
+ return -EIO;
+}
+
+/**
+ * @brief Set up a new PS8xxx emulator
+ *
+ * This should be called for each PS8xxx device that needs to be
+ * emulated. It registers "hidden" I2C devices with the I2C emulation
+ * controller and set PS8xxx device operations to associated TCPCI emulator.
+ *
+ * @param emul Emulation information
+ * @param parent Device to emulate
+ *
+ * @return 0 indicating success (always)
+ */
+static int ps8xxx_emul_init(const struct emul *emul,
+ const struct device *parent)
+{
+ const struct ps8xxx_emul_cfg *cfg = emul->cfg;
+ struct ps8xxx_emul_data *data = emul->data;
+ const struct device *i2c_dev;
+ int ret;
+
+ data->tcpci_emul = emul_get_binding(cfg->tcpci_emul);
+ i2c_dev = parent;
+
+ data->p0_data.emul.api = &i2c_common_emul_api;
+ data->p0_data.emul.addr = cfg->p0_cfg.addr;
+ data->p0_data.emul.parent = emul;
+ data->p0_data.i2c = i2c_dev;
+ data->p0_data.cfg = &cfg->p0_cfg;
+ i2c_common_emul_init(&data->p0_data);
+
+ data->p1_data.emul.api = &i2c_common_emul_api;
+ data->p1_data.emul.addr = cfg->p1_cfg.addr;
+ data->p1_data.emul.parent = emul;
+ data->p1_data.i2c = i2c_dev;
+ data->p1_data.cfg = &cfg->p1_cfg;
+ i2c_common_emul_init(&data->p1_data);
+
+ ret = i2c_emul_register(i2c_dev, emul->dev_label, &data->p0_data.emul);
+ ret |= i2c_emul_register(i2c_dev, emul->dev_label, &data->p1_data.emul);
+
+ if (cfg->gpio_cfg.addr != 0) {
+ data->gpio_data.emul.api = &i2c_common_emul_api;
+ data->gpio_data.emul.addr = cfg->gpio_cfg.addr;
+ data->gpio_data.emul.parent = emul;
+ data->gpio_data.i2c = i2c_dev;
+ data->gpio_data.cfg = &cfg->gpio_cfg;
+ i2c_common_emul_init(&data->gpio_data);
+ ret |= i2c_emul_register(i2c_dev, emul->dev_label,
+ &data->gpio_data.emul);
+ }
+
+ tcpci_emul_set_dev_ops(data->tcpci_emul, &ps8xxx_emul_ops);
+ ps8xxx_emul_tcpci_reset(data->tcpci_emul, &ps8xxx_emul_ops);
+
+ tcpci_emul_set_reg(data->tcpci_emul, TCPC_REG_PRODUCT_ID,
+ data->prod_id);
+
+ return ret;
+}
+
+#define PS8XXX_EMUL(n) \
+ static struct ps8xxx_emul_data ps8xxx_emul_data_##n = { \
+ .prod_id = PS8805_PRODUCT_ID, \
+ .p0_data = { \
+ .write_byte = ps8xxx_emul_write_byte, \
+ .read_byte = ps8xxx_emul_read_byte, \
+ }, \
+ .p1_data = { \
+ .write_byte = ps8xxx_emul_write_byte, \
+ .read_byte = ps8xxx_emul_read_byte, \
+ }, \
+ .gpio_data = { \
+ .write_byte = ps8xxx_emul_write_byte, \
+ .read_byte = ps8xxx_emul_read_byte, \
+ }, \
+ }; \
+ \
+ static const struct ps8xxx_emul_cfg ps8xxx_emul_cfg_##n = { \
+ .tcpci_emul = DT_LABEL(DT_INST_PHANDLE(n, tcpci_i2c)), \
+ .p0_cfg = { \
+ .i2c_label = DT_INST_BUS_LABEL(n), \
+ .dev_label = DT_INST_LABEL(n), \
+ .data = &ps8xxx_emul_data_##n.p0_data, \
+ .addr = DT_INST_PROP(n, p0_i2c_addr), \
+ }, \
+ .p1_cfg = { \
+ .i2c_label = DT_INST_BUS_LABEL(n), \
+ .dev_label = DT_INST_LABEL(n), \
+ .data = &ps8xxx_emul_data_##n.p1_data, \
+ .addr = DT_INST_PROP(n, p1_i2c_addr), \
+ }, \
+ .gpio_cfg = { \
+ .i2c_label = DT_INST_BUS_LABEL(n), \
+ .dev_label = DT_INST_LABEL(n), \
+ .data = &ps8xxx_emul_data_##n.gpio_data, \
+ .addr = DT_INST_PROP(n, gpio_i2c_addr), \
+ }, \
+ }; \
+ EMUL_DEFINE(ps8xxx_emul_init, DT_DRV_INST(n), \
+ &ps8xxx_emul_cfg_##n, &ps8xxx_emul_data_##n)
+
+DT_INST_FOREACH_STATUS_OKAY(PS8XXX_EMUL)
diff --git a/zephyr/emul/tcpc/emul_tcpci.c b/zephyr/emul/tcpc/emul_tcpci.c
new file mode 100644
index 0000000000..4f05744c6b
--- /dev/null
+++ b/zephyr/emul/tcpc/emul_tcpci.c
@@ -0,0 +1,1399 @@
+/* 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 <drivers/gpio/gpio_emul.h>
+
+#include "tcpm/tcpci.h"
+
+#include "emul/emul_common_i2c.h"
+#include "emul/tcpc/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;
+
+ /** Reference to Alert# GPIO emulator. */
+ const struct device *alert_gpio_port;
+ gpio_pin_t alert_gpio_pin;
+};
+
+/**
+ * @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 0 for success, or non-0 for errors.
+ */
+static int tcpci_emul_alert_changed(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+ int rc;
+ bool alert_is_active = tcpci_emul_check_int(emul);
+
+ /** Trigger GPIO. */
+ if (data->alert_gpio_port != NULL) {
+ /* Triggers on edge falling, so set to 0 when there is an alert.
+ */
+ rc = gpio_emul_input_set(data->alert_gpio_port,
+ data->alert_gpio_pin,
+ alert_is_active ? 0 : 1);
+ if (rc != 0)
+ return rc;
+ }
+
+ /* Nothing to do */
+ if (data->alert_callback == NULL) {
+ return 0;
+ }
+
+ data->alert_callback(emul, alert_is_active,
+ data->alert_callback_data);
+ return 0;
+}
+
+/** 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;
+ int rc;
+
+ 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;
+
+ rc = tcpci_emul_alert_changed(emul);
+ if (rc != 0)
+ return rc;
+ }
+
+ 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;
+ }
+}
+
+/** Check description in emul_tcpci.h */
+void tcpci_emul_set_dev_ops(const struct emul *emul,
+ struct tcpci_emul_dev_ops *dev_ops)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ data->dev_ops = dev_ops;
+}
+
+/** Check description in emul_tcpci.h */
+void tcpci_emul_set_alert_callback(const struct emul *emul,
+ tcpci_emul_alert_state_func alert_callback,
+ void *alert_callback_data)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ data->alert_callback = alert_callback;
+ data->alert_callback_data = alert_callback_data;
+}
+
+/** Check description in emul_tcpci.h */
+void tcpci_emul_set_partner_ops(const struct emul *emul,
+ struct tcpci_emul_partner_ops *partner)
+{
+ struct tcpci_emul_data *data = emul->data;
+
+ data->partner = partner;
+}
+
+/**
+ * @brief Get detected voltage for given CC resistor
+ *
+ * @param res CC pull resistor value
+ * @param volt Voltage applied by port partner
+ *
+ * @return Voltage visible at CC resistor side
+ */
+static enum tcpc_cc_voltage_status tcpci_emul_detected_volt_for_res(
+ enum tcpc_cc_pull res,
+ enum tcpc_cc_voltage_status volt)
+{
+ switch (res) {
+ case TYPEC_CC_RD:
+ switch (volt) {
+ /* As Rd we cannot detect another Rd or Ra */
+ case TYPEC_CC_VOLT_RA:
+ case TYPEC_CC_VOLT_RD:
+ return TYPEC_CC_VOLT_OPEN;
+ default:
+ return volt;
+ }
+ case TYPEC_CC_RP:
+ switch (volt) {
+ /* As Rp we cannot detect another Rp */
+ case TYPEC_CC_VOLT_RP_DEF:
+ case TYPEC_CC_VOLT_RP_1_5:
+ case TYPEC_CC_VOLT_RP_3_0:
+ return TYPEC_CC_VOLT_OPEN;
+ default:
+ return volt;
+ }
+ default:
+ /* As Ra or open we cannot detect anything */
+ return TYPEC_CC_VOLT_OPEN;
+ }
+}
+
+/** Check description in emul_tcpci.h */
+int tcpci_emul_connect_partner(const struct emul *emul,
+ enum pd_power_role partner_power_role,
+ enum tcpc_cc_voltage_status partner_cc1,
+ enum tcpc_cc_voltage_status partner_cc2,
+ enum tcpc_cc_polarity polarity)
+{
+ enum tcpc_cc_voltage_status cc1_v, cc2_v;
+ uint16_t cc_status, alert, role_ctrl;
+ enum tcpc_cc_pull cc1_r, cc2_r;
+
+ if (polarity == POLARITY_CC1) {
+ cc1_v = partner_cc1;
+ cc2_v = partner_cc2;
+ } else {
+ cc1_v = partner_cc2;
+ cc2_v = partner_cc1;
+ }
+
+ tcpci_emul_get_reg(emul, TCPC_REG_CC_STATUS, &cc_status);
+ if (TCPC_REG_CC_STATUS_LOOK4CONNECTION(cc_status)) {
+ /* Change resistors values in case of DRP toggling */
+ if (partner_power_role == PD_ROLE_SOURCE) {
+ /* TCPCI is sink */
+ cc1_r = TYPEC_CC_RD;
+ cc2_r = TYPEC_CC_RD;
+ } else {
+ /* TCPCI is src */
+ cc1_r = TYPEC_CC_RP;
+ cc2_r = TYPEC_CC_RP;
+ }
+ } else {
+ /* Use role control resistors values otherwise */
+ tcpci_emul_get_reg(emul, TCPC_REG_ROLE_CTRL, &role_ctrl);
+ cc1_r = TCPC_REG_ROLE_CTRL_CC1(role_ctrl);
+ cc2_r = TCPC_REG_ROLE_CTRL_CC2(role_ctrl);
+ }
+
+ cc1_v = tcpci_emul_detected_volt_for_res(cc1_r, cc1_v);
+ cc2_v = tcpci_emul_detected_volt_for_res(cc2_r, cc2_v);
+
+ /* If CC status is TYPEC_CC_VOLT_RP_*, then BIT(2) is ignored */
+ cc_status = TCPC_REG_CC_STATUS_SET(
+ partner_power_role == PD_ROLE_SOURCE ? 1 : 0,
+ cc2_v, cc1_v);
+ tcpci_emul_set_reg(emul, TCPC_REG_CC_STATUS, cc_status);
+ tcpci_emul_get_reg(emul, TCPC_REG_ALERT, &alert);
+ tcpci_emul_set_reg(emul, TCPC_REG_ALERT,
+ alert | TCPC_REG_ALERT_CC_STATUS);
+
+ if (partner_power_role == PD_ROLE_SOURCE) {
+ /* Set TCPCI emulator VBUS to present (connected, above 4V) */
+ tcpci_emul_set_reg(emul, TCPC_REG_POWER_STATUS,
+ TCPC_REG_POWER_STATUS_VBUS_PRES |
+ TCPC_REG_POWER_STATUS_VBUS_DET);
+ }
+
+ tcpci_emul_alert_changed(emul);
+
+ return 0;
+}
+
+/** Check description in emul_tcpci.h */
+int tcpci_emul_disconnect_partner(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+ uint16_t val;
+ uint16_t term;
+ int rc;
+
+ data->partner = NULL;
+ /* Set both CC lines to open to indicate disconnect. */
+ rc = tcpci_emul_get_reg(emul, TCPC_REG_CC_STATUS, &val);
+ if (rc != 0)
+ return rc;
+
+ term = TCPC_REG_CC_STATUS_TERM(val);
+
+ rc = tcpci_emul_set_reg(emul, TCPC_REG_CC_STATUS,
+ TCPC_REG_CC_STATUS_SET(term, TYPEC_CC_VOLT_OPEN,
+ TYPEC_CC_VOLT_OPEN));
+ if (rc != 0)
+ return rc;
+
+ data->reg[TCPC_REG_ALERT] |= TCPC_REG_ALERT_CC_STATUS;
+ rc = tcpci_emul_alert_changed(emul);
+ if (rc != 0)
+ return rc;
+ /* TODO: Wait until DisableSourceVbus (TCPC_REG_COMMAND_SRC_CTRL_LOW?),
+ * and then set VBUS present = 0 and vSafe0V = 1 after appropriate
+ * delays.
+ */
+ return 0;
+}
+
+/** Check description in emul_tcpci.h */
+void tcpci_emul_partner_msg_status(const struct emul *emul,
+ enum tcpci_emul_tx_status status)
+{
+ uint16_t alert;
+ uint16_t tx_status_alert;
+
+ switch (status) {
+ case TCPCI_EMUL_TX_SUCCESS:
+ tx_status_alert = TCPC_REG_ALERT_TX_SUCCESS;
+ break;
+ case TCPCI_EMUL_TX_DISCARDED:
+ tx_status_alert = TCPC_REG_ALERT_TX_DISCARDED;
+ break;
+ case TCPCI_EMUL_TX_FAILED:
+ tx_status_alert = TCPC_REG_ALERT_TX_FAILED;
+ break;
+ default:
+ __ASSERT(0, "Invalid partner TX status 0x%x", status);
+ return;
+ }
+
+ tcpci_emul_get_reg(emul, TCPC_REG_ALERT, &alert);
+ tcpci_emul_set_reg(emul, TCPC_REG_ALERT, alert | tx_status_alert);
+ tcpci_emul_alert_changed(emul);
+}
+
+/** 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
+ * @return 0 if successful
+ */
+static int 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);
+ }
+
+ return tcpci_emul_alert_changed(emul);
+}
+
+/**
+ * @brief Set alert and fault registers to indicate i2c interface fault
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @return 0 if successful
+ */
+static int 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;
+
+ return 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);
+
+ LOG_DBG("TCPCI 0x%x: read reg 0x%x", i2c_emul->addr, reg);
+
+ 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;
+ uint16_t role_ctrl;
+ uint16_t pwr_ctrl;
+
+ 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_LOOK4CONNECTION:
+ tcpci_emul_get_reg(emul, TCPC_REG_ROLE_CTRL, &role_ctrl);
+ tcpci_emul_get_reg(emul, TCPC_REG_POWER_CTRL, &pwr_ctrl);
+
+ /*
+ * Start DRP toggling only if auto discharge is disabled,
+ * DRP is enabled and CC1/2 are both Rp or Rd
+ */
+ if (!(pwr_ctrl & TCPC_REG_POWER_CTRL_AUTO_DISCHARGE_DISCONNECT)
+ && TCPC_REG_ROLE_CTRL_DRP(role_ctrl) &&
+ (TCPC_REG_ROLE_CTRL_CC1(role_ctrl) ==
+ TCPC_REG_ROLE_CTRL_CC2(role_ctrl)) &&
+ (TCPC_REG_ROLE_CTRL_CC1(role_ctrl) == TYPEC_CC_RP ||
+ TCPC_REG_ROLE_CTRL_CC1(role_ctrl) == TYPEC_CC_RD)) {
+ /* Set Look4Connection and clear CC1/2 state */
+ tcpci_emul_set_reg(
+ emul, TCPC_REG_CC_STATUS,
+ TCPC_REG_CC_STATUS_LOOK4CONNECTION_MASK);
+ }
+ 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_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 Load next rx message and inform partner which message was consumed
+ * by TCPC
+ *
+ * @param emul Pointer to TCPCI emulator
+ *
+ * @return 0 when there is no new message to load
+ * @return 1 when new rx message is loaded
+ */
+static int tcpci_emul_get_next_rx_msg(const struct emul *emul)
+{
+ struct tcpci_emul_data *data = emul->data;
+ struct tcpci_emul_msg *consumed_msg;
+
+ if (data->rx_msg == NULL) {
+ return 0;
+ }
+
+ consumed_msg = data->rx_msg;
+ data->rx_msg = consumed_msg->next;
+
+ /* Inform partner */
+ if (data->partner && data->partner->rx_consumed) {
+ data->partner->rx_consumed(emul, data->partner, consumed_msg);
+ }
+
+ /* Prepare new loaded message */
+ if (data->rx_msg) {
+ data->rx_msg->idx = 0;
+
+ return 1;
+ }
+
+ 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;
+ int rc;
+
+ /* 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);
+
+ LOG_DBG("TCPCI 0x%x: write reg 0x%x val 0x%x", i2c_emul->addr, reg,
+ data->write_data);
+
+ 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;
+ /* Do not clear RX status if there is new message */
+ if (tcpci_emul_get_next_rx_msg(emul)) {
+ data->write_data &= ~TCPC_REG_ALERT_RX_STATUS;
+ }
+ }
+ /* 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) {
+ rc = tcpci_emul_alert_changed(emul);
+ if (rc != 0)
+ return rc;
+ }
+
+ 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);
+ if (ret != 0)
+ return ret;
+
+ return tcpci_emul_reset(emul);
+}
+
+#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, \
+ }, \
+ .alert_gpio_port = COND_CODE_1( \
+ DT_INST_NODE_HAS_PROP(n, alert_gpio), \
+ (DEVICE_DT_GET(DT_GPIO_CTLR( \
+ DT_INST_PROP(n, alert_gpio), gpios))), \
+ (NULL)), \
+ .alert_gpio_pin = COND_CODE_1( \
+ DT_INST_NODE_HAS_PROP(n, alert_gpio), \
+ (DT_GPIO_PIN(DT_INST_PROP(n, alert_gpio), \
+ gpios)), \
+ (0)), \
+ }; \
+ \
+ 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/emul/tcpc/emul_tcpci_partner_common.c b/zephyr/emul/tcpc/emul_tcpci_partner_common.c
new file mode 100644
index 0000000000..5164ed2120
--- /dev/null
+++ b/zephyr/emul/tcpc/emul_tcpci_partner_common.c
@@ -0,0 +1,149 @@
+/* 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.
+ */
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(tcpci_partner, CONFIG_TCPCI_EMUL_LOG_LEVEL);
+
+#include <zephyr.h>
+
+#include "common.h"
+#include "emul/tcpc/emul_tcpci_partner_common.h"
+#include "emul/tcpc/emul_tcpci.h"
+#include "usb_pd.h"
+
+/** Check description in emul_common_tcpci_partner.h */
+struct tcpci_partner_msg *tcpci_partner_alloc_msg(size_t size)
+{
+ struct tcpci_partner_msg *new_msg;
+
+ new_msg = k_malloc(sizeof(struct tcpci_partner_msg));
+ if (new_msg == NULL) {
+ return NULL;
+ }
+
+ new_msg->msg.buf = k_malloc(size);
+ if (new_msg->msg.buf == NULL) {
+ k_free(new_msg);
+ return NULL;
+ }
+
+ /* TCPCI message size count include type byte */
+ new_msg->msg.cnt = size + 1;
+
+ return new_msg;
+}
+
+/** Check description in emul_common_tcpci_partner.h */
+void tcpci_partner_free_msg(struct tcpci_partner_msg *msg)
+{
+ k_free(msg->msg.buf);
+ k_free(msg);
+}
+
+/** Check description in emul_common_tcpci_partner.h */
+void tcpci_partner_set_header(struct tcpci_partner_data *data,
+ struct tcpci_partner_msg *msg,
+ int type, int cnt)
+{
+ /* Header msg id has only 3 bits and wraps around after 8 messages */
+ uint16_t msg_id = data->msg_id & 0x7;
+ uint16_t header = PD_HEADER(type, data->power_role, data->data_role,
+ msg_id, cnt, data->rev, 0 /* ext */);
+ data->msg_id++;
+
+ msg->msg.buf[1] = (header >> 8) & 0xff;
+ msg->msg.buf[0] = header & 0xff;
+}
+
+/**
+ * @brief Work function which sends delayed messages
+ *
+ * @param work Pointer to work structure
+ */
+static void tcpci_partner_delayed_send(struct k_work *work)
+{
+ struct k_work_delayable *kwd = k_work_delayable_from_work(work);
+ struct tcpci_partner_data *data =
+ CONTAINER_OF(kwd, struct tcpci_partner_data, delayed_send);
+ struct tcpci_partner_msg *msg;
+ uint64_t now;
+ int ec;
+
+ while (!k_fifo_is_empty(&data->to_send)) {
+ /*
+ * It is safe to not check msg == NULL, because this thread is
+ * the only one consumer
+ */
+ msg = k_fifo_peek_head(&data->to_send);
+
+ now = k_uptime_get();
+ if (now >= msg->time) {
+ k_fifo_get(&data->to_send, K_FOREVER);
+ ec = tcpci_emul_add_rx_msg(data->tcpci_emul, &msg->msg,
+ true /* send alert */);
+ if (ec) {
+ tcpci_partner_free_msg(msg);
+ }
+ } else {
+ k_work_reschedule(kwd, K_MSEC(msg->time - now));
+ break;
+ }
+ }
+}
+
+/** Check description in emul_common_tcpci_partner.h */
+int tcpci_partner_send_msg(struct tcpci_partner_data *data,
+ struct tcpci_partner_msg *msg, uint64_t delay)
+{
+ uint64_t now;
+ int ec;
+
+ if (delay == 0) {
+ ec = tcpci_emul_add_rx_msg(data->tcpci_emul, &msg->msg, true);
+ if (ec) {
+ tcpci_partner_free_msg(msg);
+ }
+
+ return ec;
+ }
+
+ now = k_uptime_get();
+ msg->time = now + delay;
+ k_fifo_put(&data->to_send, msg);
+ /*
+ * This will change execution time of delayed_send only if it is not
+ * already scheduled
+ */
+ k_work_schedule(&data->delayed_send, K_MSEC(delay));
+
+ return 0;
+}
+
+/** Check description in emul_common_tcpci_partner.h */
+int tcpci_partner_send_control_msg(struct tcpci_partner_data *data,
+ enum pd_ctrl_msg_type type,
+ uint64_t delay)
+{
+ struct tcpci_partner_msg *msg;
+
+ msg = tcpci_partner_alloc_msg(2);
+ if (msg == NULL) {
+ return -ENOMEM;
+ }
+
+ tcpci_partner_set_header(data, msg, type, 0);
+
+ /* Fill tcpci message structure */
+ msg->msg.type = TCPCI_MSG_SOP;
+
+ return tcpci_partner_send_msg(data, msg, delay);
+}
+
+/** Check description in emul_common_tcpci_partner.h */
+void tcpci_partner_init(struct tcpci_partner_data *data)
+{
+ k_work_init_delayable(&data->delayed_send, tcpci_partner_delayed_send);
+ k_fifo_init(&data->to_send);
+}
diff --git a/zephyr/emul/tcpc/emul_tcpci_partner_src.c b/zephyr/emul/tcpc/emul_tcpci_partner_src.c
new file mode 100644
index 0000000000..f81f3faecc
--- /dev/null
+++ b/zephyr/emul/tcpc/emul_tcpci_partner_src.c
@@ -0,0 +1,301 @@
+/* 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.
+ */
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(tcpci_src_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL);
+
+#include <zephyr.h>
+
+#include "common.h"
+#include "emul/tcpc/emul_tcpci_partner_common.h"
+#include "emul/tcpc/emul_tcpci_partner_src.h"
+#include "emul/tcpc/emul_tcpci.h"
+#include "usb_pd.h"
+
+/**
+ * @brief Send capability message constructed from source device emulator PDOs
+ *
+ * @param data Pointer to USB-C source device emulator
+ * @param delay Optional delay
+ *
+ * @return 0 on success
+ * @return -ENOMEM when there is no free memory for message
+ * @return -EINVAL on TCPCI emulator add RX message error
+ */
+static int tcpci_src_emul_send_capability_msg(struct tcpci_src_emul_data *data,
+ uint64_t delay)
+{
+ struct tcpci_partner_msg *msg;
+ int pdos;
+ int byte;
+ int addr;
+
+ /* Find number of PDOs */
+ for (pdos = 0; pdos < PDO_MAX_OBJECTS; pdos++) {
+ if (data->pdo[pdos] == 0) {
+ break;
+ }
+ }
+
+ /* Allocate space for header and 4 bytes for each PDO */
+ msg = tcpci_partner_alloc_msg(2 + pdos * 4);
+ if (msg == NULL) {
+ return -ENOMEM;
+ }
+
+ tcpci_partner_set_header(&data->common_data, msg, PD_DATA_SOURCE_CAP,
+ pdos);
+
+ for (int i = 0; i < pdos; i++) {
+ /* Address of given PDO in message buffer */
+ addr = 2 + i * 4;
+ for (byte = 0; byte < 4; byte++) {
+ msg->msg.buf[addr + byte] =
+ (data->pdo[i] >> (8 * byte)) & 0xff;
+ }
+ }
+
+ /* Fill tcpci message structure */
+ msg->msg.type = TCPCI_MSG_SOP;
+
+ return tcpci_partner_send_msg(&data->common_data, msg, delay);
+}
+
+/**
+ * @brief Function called when TCPM wants to transmit message. Accept received
+ * message and generate response.
+ *
+ * @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
+ */
+static void tcpci_src_emul_transmit_op(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)
+{
+ struct tcpci_src_emul_data *data =
+ CONTAINER_OF(ops, struct tcpci_src_emul_data, ops);
+ uint16_t header;
+
+ /* Acknowledge that message was sent successfully */
+ tcpci_emul_partner_msg_status(emul, TCPCI_EMUL_TX_SUCCESS);
+
+ /* Handle only SOP messages */
+ if (type != TCPCI_MSG_SOP) {
+ return;
+ }
+
+ LOG_HEXDUMP_DBG(tx_msg->buf, tx_msg->cnt, "Source received message");
+
+ header = (tx_msg->buf[1] << 8) | tx_msg->buf[0];
+
+ if (PD_HEADER_CNT(header)) {
+ /* Handle data message */
+ switch (PD_HEADER_TYPE(header)) {
+ case PD_DATA_REQUEST:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_ACCEPT, 0);
+ /* PS ready after 15 ms */
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_PS_RDY, 15);
+ break;
+ case PD_DATA_VENDOR_DEF:
+ /* VDM (vendor defined message) - ignore */
+ break;
+ default:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ }
+ } else {
+ /* Handle control message */
+ switch (PD_HEADER_TYPE(header)) {
+ case PD_CTRL_GET_SOURCE_CAP:
+ tcpci_src_emul_send_capability_msg(data, 0);
+ break;
+ case PD_CTRL_GET_SINK_CAP:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ case PD_CTRL_DR_SWAP:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ case PD_CTRL_SOFT_RESET:
+ data->common_data.msg_id = 0;
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_ACCEPT, 0);
+ /* Send capability after 15 ms to establish PD again */
+ tcpci_src_emul_send_capability_msg(data, 15);
+ break;
+ default:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ }
+ }
+}
+
+/**
+ * @brief Function called when TCPM consumes message. Free message that is no
+ * longer needed.
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to partner operations structure
+ * @param rx_msg Message that was consumed by TCPM
+ */
+static void tcpci_src_emul_rx_consumed_op(
+ const struct emul *emul,
+ const struct tcpci_emul_partner_ops *ops,
+ const struct tcpci_emul_msg *rx_msg)
+{
+ struct tcpci_partner_msg *msg = CONTAINER_OF(rx_msg,
+ struct tcpci_partner_msg,
+ msg);
+
+ tcpci_partner_free_msg(msg);
+}
+
+/** Check description in emul_tcpci_partner_src.h */
+int tcpci_src_emul_connect_to_tcpci(struct tcpci_src_emul_data *data,
+ const struct emul *tcpci_emul)
+{
+ int ec;
+
+ tcpci_emul_set_partner_ops(tcpci_emul, &data->ops);
+ ec = tcpci_emul_connect_partner(tcpci_emul, PD_ROLE_SOURCE,
+ TYPEC_CC_VOLT_RP_3_0,
+ TYPEC_CC_VOLT_OPEN, POLARITY_CC1);
+ if (ec) {
+ return ec;
+ }
+
+ data->common_data.tcpci_emul = tcpci_emul;
+
+ return tcpci_src_emul_send_capability_msg(data, 0);
+}
+
+#define PDO_FIXED_FLAGS_MASK \
+ (PDO_FIXED_DUAL_ROLE | PDO_FIXED_UNCONSTRAINED | \
+ PDO_FIXED_COMM_CAP | PDO_FIXED_DATA_SWAP)
+
+/** Check description in emul_tcpci_parnter_src.h */
+enum check_pdos_res tcpci_src_emul_check_pdos(struct tcpci_src_emul_data *data)
+{
+ int volt_i_min;
+ int volt_i_max;
+ int volt_min;
+ int volt_max;
+ int i;
+
+ /* Check that first PDO is fixed 5V */
+ if ((data->pdo[0] & PDO_TYPE_MASK) != PDO_TYPE_FIXED ||
+ PDO_FIXED_VOLTAGE(data->pdo[0]) != 5000) {
+ return TCPCI_SRC_EMUL_FIRST_PDO_NO_FIXED_5V;
+ }
+
+ /* Check fixed PDOs are before other types and are in correct order */
+ for (i = 1, volt_min = -1;
+ i < PDO_MAX_OBJECTS && data->pdo[i] != 0 &&
+ (data->pdo[i] & PDO_TYPE_MASK) != PDO_TYPE_FIXED;
+ i++) {
+ volt_i_min = PDO_FIXED_VOLTAGE(data->pdo[i]);
+ /* Each voltage should be only once */
+ if (volt_i_min == volt_min || volt_i_min == 5000) {
+ return TCPCI_SRC_EMUL_FIXED_VOLT_REPEATED;
+ }
+ /* Check that voltage is increasing in next PDO */
+ if (volt_i_min < volt_min) {
+ return TCPCI_SRC_EMUL_FIXED_VOLT_NOT_IN_ORDER;
+ }
+ /* Check that fixed PDOs (except first) have cleared flags */
+ if (data->pdo[i] & PDO_FIXED_FLAGS_MASK) {
+ return TCPCI_SRC_EMUL_NON_FIRST_PDO_FIXED_FLAGS;
+ }
+ /* Save current voltage */
+ volt_min = volt_i_min;
+ }
+
+ /* Check battery PDOs are before variable type and are in order */
+ for (volt_min = -1, volt_max = -1;
+ i < PDO_MAX_OBJECTS && data->pdo[i] != 0 &&
+ (data->pdo[i] & PDO_TYPE_MASK) != PDO_TYPE_BATTERY;
+ i++) {
+ volt_i_min = PDO_BATT_MIN_VOLTAGE(data->pdo[i]);
+ volt_i_max = PDO_BATT_MAX_VOLTAGE(data->pdo[i]);
+ /* Each voltage range should be only once */
+ if (volt_i_min == volt_min && volt_i_max == volt_max) {
+ return TCPCI_SRC_EMUL_BATT_VOLT_REPEATED;
+ }
+ /*
+ * Lower minimal voltage should be first, than lower maximal
+ * voltage.
+ */
+ if (volt_i_min < volt_min ||
+ (volt_i_min == volt_min && volt_i_max < volt_max)) {
+ return TCPCI_SRC_EMUL_BATT_VOLT_NOT_IN_ORDER;
+ }
+ /* Save current voltage */
+ volt_min = volt_i_min;
+ volt_max = volt_i_max;
+ }
+
+ /* Check variable PDOs are last and are in correct order */
+ for (volt_min = -1, volt_max = -1;
+ i < PDO_MAX_OBJECTS && data->pdo[i] != 0 &&
+ (data->pdo[i] & PDO_TYPE_MASK) != PDO_TYPE_VARIABLE;
+ i++) {
+ volt_i_min = PDO_VAR_MIN_VOLTAGE(data->pdo[i]);
+ volt_i_max = PDO_VAR_MAX_VOLTAGE(data->pdo[i]);
+ /* Each voltage range should be only once */
+ if (volt_i_min == volt_min && volt_i_max == volt_max) {
+ return TCPCI_SRC_EMUL_VAR_VOLT_REPEATED;
+ }
+ /*
+ * Lower minimal voltage should be first, than lower maximal
+ * voltage.
+ */
+ if (volt_i_min < volt_min ||
+ (volt_i_min == volt_min && volt_i_max < volt_max)) {
+ return TCPCI_SRC_EMUL_VAR_VOLT_NOT_IN_ORDER;
+ }
+ /* Save current voltage */
+ volt_min = volt_i_min;
+ volt_max = volt_i_max;
+ }
+
+ /* Check that all PDOs after first 0 are unused and set to 0 */
+ for (; i < PDO_MAX_OBJECTS; i++) {
+ if (data->pdo[i] != 0) {
+ return TCPCI_SRC_EMUL_PDO_AFTER_ZERO;
+ }
+ }
+
+ return TCPCI_SRC_EMUL_CHECK_PDO_OK;
+}
+
+/** Check description in emul_tcpci_parnter_src.h */
+void tcpci_src_emul_init(struct tcpci_src_emul_data *data)
+{
+ tcpci_partner_init(&data->common_data);
+
+ data->common_data.data_role = PD_ROLE_UFP;
+ data->common_data.power_role = PD_ROLE_SOURCE;
+ data->common_data.rev = PD_REV20;
+
+ data->ops.transmit = tcpci_src_emul_transmit_op;
+ data->ops.rx_consumed = tcpci_src_emul_rx_consumed_op;
+ data->ops.control_change = NULL;
+
+ /* By default there is only PDO 5v@3A */
+ data->pdo[0] = PDO_FIXED(5000, 3000, PDO_FIXED_UNCONSTRAINED);
+ for (int i = 1; i < PDO_MAX_OBJECTS; i++) {
+ data->pdo[i] = 0;
+ }
+}