diff options
author | Tomasz Michalec <tm@semihalf.com> | 2021-12-10 12:47:20 +0100 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-12-16 09:58:35 +0000 |
commit | 28ceff1f7b9294d49dea37396229fca0644e5216 (patch) | |
tree | a841ed928a6488c09dcffb768ef8468d7e097f96 /zephyr/emul/tcpc | |
parent | 36b85b1fb4f5f472c56b321f2e84ea2c7c3a2054 (diff) | |
download | chrome-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.txt | 8 | ||||
-rw-r--r-- | zephyr/emul/tcpc/Kconfig | 48 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_ps8xxx.c | 577 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci.c | 1399 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci_partner_common.c | 149 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci_partner_src.c | 301 |
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, ®_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; + } +} |