diff options
Diffstat (limited to 'zephyr/emul')
-rw-r--r-- | zephyr/emul/CMakeLists.txt | 5 | ||||
-rw-r--r-- | zephyr/emul/Kconfig | 3 | ||||
-rw-r--r-- | zephyr/emul/Kconfig.ln9310 | 1 | ||||
-rw-r--r-- | zephyr/emul/Kconfig.sn5s330 | 1 | ||||
-rw-r--r-- | zephyr/emul/Kconfig.tcpci | 38 | ||||
-rw-r--r-- | zephyr/emul/emul_charger.c | 355 | ||||
-rw-r--r-- | zephyr/emul/emul_isl923x.c | 295 | ||||
-rw-r--r-- | zephyr/emul/emul_lis2dw12.c | 158 | ||||
-rw-r--r-- | zephyr/emul/emul_ln9310.c | 137 | ||||
-rw-r--r-- | zephyr/emul/emul_sn5s330.c | 313 | ||||
-rw-r--r-- | zephyr/emul/emul_syv682x.c | 97 | ||||
-rw-r--r-- | zephyr/emul/tcpc/CMakeLists.txt | 9 | ||||
-rw-r--r-- | zephyr/emul/tcpc/Kconfig | 56 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_ps8xxx.c (renamed from zephyr/emul/emul_ps8xxx.c) | 4 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci.c (renamed from zephyr/emul/emul_tcpci.c) | 58 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci_partner_common.c | 247 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci_partner_snk.c | 433 | ||||
-rw-r--r-- | zephyr/emul/tcpc/emul_tcpci_partner_src.c | 279 |
18 files changed, 1697 insertions, 792 deletions
diff --git a/zephyr/emul/CMakeLists.txt b/zephyr/emul/CMakeLists.txt index 030ae7cc51..8d96d46029 100644 --- a/zephyr/emul/CMakeLists.txt +++ b/zephyr/emul/CMakeLists.txt @@ -2,6 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +add_subdirectory("tcpc") + zephyr_library_sources_ifdef(CONFIG_EMUL_COMMON_I2C emul_common_i2c.c) zephyr_library_sources_ifdef(CONFIG_EMUL_SMART_BATTERY emul_smart_battery.c) zephyr_library_sources_ifdef(CONFIG_EMUL_BMA255 emul_bma255.c) @@ -17,7 +19,4 @@ zephyr_library_sources_ifdef(CONFIG_EMUL_LIS2DW12 emul_lis2dw12.c) zephyr_library_sources_ifdef(CONFIG_I2C_MOCK i2c_mock.c) zephyr_library_sources_ifdef(CONFIG_EMUL_ISL923X emul_isl923x.c) zephyr_library_sources_ifdef(CONFIG_EMUL_CLOCK_CONTROL emul_clock_control.c) -zephyr_library_sources_ifdef(CONFIG_EMUL_TCPCI emul_tcpci.c) zephyr_library_sources_ifdef(CONFIG_EMUL_SN5S330 emul_sn5s330.c) -zephyr_library_sources_ifdef(CONFIG_EMUL_PS8XXX emul_ps8xxx.c) -zephyr_library_sources_ifdef(CONFIG_EMUL_CHARGER emul_charger.c) diff --git a/zephyr/emul/Kconfig b/zephyr/emul/Kconfig index a6b39405c6..7e1e869b39 100644 --- a/zephyr/emul/Kconfig +++ b/zephyr/emul/Kconfig @@ -2,6 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +rsource "tcpc/Kconfig" + config EMUL_COMMON_I2C bool "Common handler for I2C emulator messages" help @@ -72,5 +74,4 @@ rsource "Kconfig.lis2dw12" rsource "Kconfig.i2c_mock" rsource "Kconfig.isl923x" rsource "Kconfig.clock_control" -rsource "Kconfig.tcpci" rsource "Kconfig.sn5s330" diff --git a/zephyr/emul/Kconfig.ln9310 b/zephyr/emul/Kconfig.ln9310 index 5773cf3721..c9e3e6fbc9 100644 --- a/zephyr/emul/Kconfig.ln9310 +++ b/zephyr/emul/Kconfig.ln9310 @@ -8,6 +8,7 @@ menuconfig EMUL_LN9310 bool "LN9310 switchcap emulator" default $(dt_compat_enabled,$(DT_COMPAT_LN9310_EMUL)) depends on I2C_EMUL + depends on ASSERT help Enable the LN9310 emulator. This driver uses the emulated I2C bus. It is used to test the ln9310 driver. Emulator API is available in diff --git a/zephyr/emul/Kconfig.sn5s330 b/zephyr/emul/Kconfig.sn5s330 index aba3bb9028..bb3e5eeea8 100644 --- a/zephyr/emul/Kconfig.sn5s330 +++ b/zephyr/emul/Kconfig.sn5s330 @@ -8,6 +8,7 @@ menuconfig EMUL_SN5S330 bool "sn5s330 emulator" default $(dt_compat_enabled,$(DT_COMPAT_SN5S330_EMUL)) depends on I2C_EMUL + depends on ASSERT help Enable the sn5s330 emulator. This driver uses the emulated I2C bus. It is used to test the sn5s330 driver. Emulator API is available in diff --git a/zephyr/emul/Kconfig.tcpci b/zephyr/emul/Kconfig.tcpci deleted file mode 100644 index fc4b1bcda4..0000000000 --- a/zephyr/emul/Kconfig.tcpci +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2021 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -DT_COMPAT_TCPCI_EMUL := cros,tcpci-emul - -menuconfig EMUL_TCPCI - bool "TCPCI emulator" - default $(dt_compat_enabled,$(DT_COMPAT_TCPCI_EMUL)) - depends on I2C_EMUL - help - Enable the TCPCI emulator. This driver uses the emulated I2C bus. - It is used to test tcpci code. It supports reads and writes to all - emulator registers. Generic TCPCI emulator can be used as the base - for specific TCPC device emulator that follow TCPCI specification. - TCPCI emulator API is available in zephyr/include/emul/emul_tcpci.h - -if EMUL_TCPCI - -module = TCPCI_EMUL -module-str = tcpci_emul -source "subsys/logging/Kconfig.template.log_config" - -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/emul_tcpci.h - -config EMUL_CHARGER - bool "USB-C charger emulator" - help - Enable USB-C charger emulator which may be attached to TCPCI emulator. - API of charger emulator is available in - zephyr/include/emul/emul_charger.h - -endif # EMUL_TCPCI diff --git a/zephyr/emul/emul_charger.c b/zephyr/emul/emul_charger.c deleted file mode 100644 index d584ab882b..0000000000 --- a/zephyr/emul/emul_charger.c +++ /dev/null @@ -1,355 +0,0 @@ -/* 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(charger_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL); - -#include <zephyr.h> - -#include "common.h" -#include "emul/emul_charger.h" -#include "emul/emul_tcpci.h" -#include "usb_pd.h" - -/** Structure of message used by USB-C charger emulator */ -struct charger_emul_msg { - /** Reserved for k_fifo_* usage */ - void *fifo_reserved; - /** TCPCI emulator message */ - struct tcpci_emul_msg msg; - /** Time when message should be sent if message is delayed */ - uint64_t time; -}; - -/** - * @brief Allocate message - * - * @param size Size of message buffer - * - * @return Pointer to new message on success - * @return NULL on error - */ -static struct charger_emul_msg *charger_emul_alloc_msg(size_t size) -{ - struct charger_emul_msg *new_msg; - - new_msg = k_malloc(sizeof(struct charger_emul_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; -} - -/** - * @brief Free message's memory - * - * @param msg Pointer to message - */ -static void charger_emul_free_msg(struct charger_emul_msg *msg) -{ - k_free(msg->msg.buf); - k_free(msg); -} - -/** - * @brief Set header of the message - * - * @param data Pointer to USB-C charger emulator - * @param msg Pointer to message - * @param type Type of message - * @param cnt Number of data objects - */ -static void charger_emul_set_header(struct charger_emul_data *data, - struct charger_emul_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, PD_ROLE_SOURCE, PD_ROLE_UFP, msg_id, - cnt, PD_REV20, 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 charger_emul_delayed_send(struct k_work *work) -{ - struct k_work_delayable *kwd = k_work_delayable_from_work(work); - struct charger_emul_data *data = CONTAINER_OF(kwd, - struct charger_emul_data, - delayed_send); - struct charger_emul_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) { - charger_emul_free_msg(msg); - } - } else { - k_work_reschedule(kwd, K_MSEC(msg->time - now)); - break; - } - } -} - -/** - * @brief Send message to TCPCI emulator or schedule message - * - * @param data Pointer to USB-C charger emulator - * @param msg Pointer to message to send - * @param delay Optional delay - * - * @return 0 on success - * @return -EINVAL on TCPCI emulator add RX message error - */ -static int charger_emul_send_msg(struct charger_emul_data *data, - struct charger_emul_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) { - charger_emul_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; -} - -/** - * @brief Send capability message which for now is hardcoded - * - * @param data Pointer to USB-C charger 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 charger_emul_send_capability_msg(struct charger_emul_data *data, - uint64_t delay) -{ - struct charger_emul_msg *msg; - - msg = charger_emul_alloc_msg(6); - if (msg == NULL) { - return -ENOMEM; - } - - /* Capability with 5v@3A */ - charger_emul_set_header(data, msg, PD_DATA_SOURCE_CAP, 1); - - /* Fixed supply (type of supply) 0xc0 */ - msg->msg.buf[5] = 0x00; - /* Dual role capable 0x20 */ - msg->msg.buf[5] |= 0x00; - /* Unconstrained power 0x08 */ - msg->msg.buf[5] |= 0x08; - - /* 5V on bits 19-10 */ - msg->msg.buf[4] = 0x1; - msg->msg.buf[3] = 0x90; - /* 3A on bits 9-0 */ - msg->msg.buf[3] |= 0x1; - msg->msg.buf[2] = 0x2c; - - /* Fill tcpci message structure */ - msg->msg.type = TCPCI_MSG_SOP; - - return charger_emul_send_msg(data, msg, delay); -} - -/** - * @brief Send control message with optional delay - * - * @param data Pointer to USB-C charger emulator - * @param type Type of message - * @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 charger_emul_send_control_msg(struct charger_emul_data *data, - enum pd_ctrl_msg_type type, - uint64_t delay) -{ - struct charger_emul_msg *msg; - - msg = charger_emul_alloc_msg(2); - if (msg == NULL) { - return -ENOMEM; - } - - charger_emul_set_header(data, msg, type, 0); - - /* Fill tcpci message structure */ - msg->msg.type = TCPCI_MSG_SOP; - - return charger_emul_send_msg(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 charger_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 charger_emul_data *data = CONTAINER_OF(ops, - struct charger_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, "Charger 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: - charger_emul_send_control_msg(data, PD_CTRL_ACCEPT, 0); - /* PS ready after 15 ms */ - charger_emul_send_control_msg(data, PD_CTRL_PS_RDY, 15); - break; - case PD_DATA_VENDOR_DEF: - /* VDM (vendor defined message) - ignore */ - break; - default: - charger_emul_send_control_msg(data, PD_CTRL_REJECT, 0); - break; - } - } else { - /* Handle control message */ - switch (PD_HEADER_TYPE(header)) { - case PD_CTRL_GET_SOURCE_CAP: - charger_emul_send_capability_msg(data, 0); - break; - case PD_CTRL_GET_SINK_CAP: - charger_emul_send_control_msg(data, PD_CTRL_REJECT, 0); - break; - case PD_CTRL_DR_SWAP: - charger_emul_send_control_msg(data, PD_CTRL_REJECT, 0); - break; - case PD_CTRL_SOFT_RESET: - data->msg_id = 0; - charger_emul_send_control_msg(data, PD_CTRL_ACCEPT, 0); - /* Send capability after 15 ms to establish PD again */ - charger_emul_send_capability_msg(data, 15); - break; - default: - charger_emul_send_control_msg(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 charger_emul_rx_consumed_op( - const struct emul *emul, - const struct tcpci_emul_partner_ops *ops, - const struct tcpci_emul_msg *rx_msg) -{ - struct charger_emul_msg *msg = CONTAINER_OF(rx_msg, - struct charger_emul_msg, - msg); - - charger_emul_free_msg(msg); -} - -/** Check description in emul_charger.h */ -int charger_emul_connect_to_tcpci(struct charger_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->tcpci_emul = tcpci_emul; - - return charger_emul_send_capability_msg(data, 0); -} - -/** Check description in emul_charger.h */ -void charger_emul_init(struct charger_emul_data *data) -{ - k_work_init_delayable(&data->delayed_send, charger_emul_delayed_send); - k_fifo_init(&data->to_send); - - data->ops.transmit = charger_emul_transmit_op; - data->ops.rx_consumed = charger_emul_rx_consumed_op; -} diff --git a/zephyr/emul/emul_isl923x.c b/zephyr/emul/emul_isl923x.c index e7a286e6fb..05ec6bdc52 100644 --- a/zephyr/emul/emul_isl923x.c +++ b/zephyr/emul/emul_isl923x.c @@ -16,6 +16,7 @@ #include "driver/charger/isl923x_public.h" #include "emul/emul_common_i2c.h" #include "emul/emul_isl923x.h" +#include "emul/emul_smart_battery.h" #include "i2c.h" #include <logging/log.h> @@ -28,6 +29,9 @@ LOG_MODULE_REGISTER(isl923x_emul, CONFIG_ISL923X_EMUL_LOG_LEVEL); /** Mask used for the charge current register */ #define REG_CHG_CURRENT_MASK GENMASK(12, 2) +/** Mask used for the system voltage min register */ +#define REG_SYS_VOLTAGE_MIN_MASK GENMASK(13, 8) + /** Mask used for the system voltage max register */ #define REG_SYS_VOLTAGE_MAX_MASK GENMASK(14, 3) @@ -49,12 +53,22 @@ LOG_MODULE_REGISTER(isl923x_emul, CONFIG_ISL923X_EMUL_LOG_LEVEL); /** Mask used for the control 3 register */ #define REG_CONTROL3_MASK GENMASK(15, 0) +/** Mask used for the control 4 register */ +#define REG_CONTROL4_MASK GENMASK(15, 0) + +/** Mask used for the control 8 register */ +#define REG_CONTROL8_MASK GENMASK(15, 0) + /** Mask used for the AC PROCHOT register */ #define REG_PROCHOT_AC_MASK GENMASK(12, 7) /** Mask used for the DC PROCHOT register */ #define REG_PROCHOT_DC_MASK GENMASK(13, 8) +#define DEFAULT_R_SNS 10 +#define R_SNS CONFIG_CHARGER_SENSE_RESISTOR +#define REG_TO_CURRENT(REG) ((REG) * DEFAULT_R_SNS / R_SNS) + struct isl923x_emul_data { /** Common I2C data */ struct i2c_common_emul_data common; @@ -64,6 +78,8 @@ struct isl923x_emul_data { uint16_t adapter_current_limit1_reg; /** Emulated adapter current limit 2 register */ uint16_t adapter_current_limit2_reg; + /** Emulated min voltage register */ + uint16_t min_volt_reg; /** Emulated max voltage register */ uint16_t max_volt_reg; /** Emulated manufacturer ID register */ @@ -78,12 +94,20 @@ struct isl923x_emul_data { uint16_t control_2_reg; /** Emulated control 3 register */ uint16_t control_3_reg; + /** Emulated control 4 register */ + uint16_t control_4_reg; + /** Emulated control 8 register (RAA489000-only) */ + uint16_t control_8_reg; + /** Emulated info 2 reg */ + uint16_t info_2_reg; /** Emulated AC PROCHOT register */ uint16_t ac_prochot_reg; /** Emulated DC PROCHOT register */ uint16_t dc_prochot_reg; /** Emulated ADC vbus register */ uint16_t adc_vbus_reg; + /** Pointer to battery emulator. */ + int battery_ord; }; struct isl923x_emul_cfg { @@ -109,9 +133,11 @@ void isl923x_emul_reset(const struct emul *emulator) { struct isl923x_emul_data *data = emulator->data; struct i2c_common_emul_data common_backup = data->common; + int battery_ord = data->battery_ord; memset(data, 0, sizeof(struct isl923x_emul_data)); data->common = common_backup; + data->battery_ord = battery_ord; } void isl923x_emul_set_manufacturer_id(const struct emul *emulator, @@ -157,6 +183,27 @@ void isl923x_emul_set_adc_vbus(const struct emul *emulator, data->adc_vbus_reg = value & GENMASK(13, 6); } +void raa489000_emul_set_acok_pin(const struct emul *emulator, uint16_t value) +{ + struct isl923x_emul_data *data = emulator->data; + + if (value) + data->info_2_reg |= RAA489000_INFO2_ACOK; + else + data->info_2_reg &= ~RAA489000_INFO2_ACOK; +} + +/** Convenience macro for reading 16-bit registers */ +#define READ_REG_16(REG, BYTES, OUT) \ + do { \ + __ASSERT_NO_MSG((BYTES) == 0 || (BYTES) == 1); \ + if ((BYTES) == 0) \ + *(OUT) = (uint8_t)((REG)&0xff); \ + else \ + *(OUT) = (uint8_t)(((REG) >> 8) & 0xff); \ + break; \ + } while (0) + static int isl923x_emul_read_byte(struct i2c_emul *emul, int reg, uint8_t *val, int bytes) { @@ -164,109 +211,84 @@ static int isl923x_emul_read_byte(struct i2c_emul *emul, int reg, uint8_t *val, switch (reg) { case ISL923X_REG_CHG_CURRENT: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->current_limit_reg & 0xff); - else - *val = (uint8_t)((data->current_limit_reg >> 8) & 0xff); + READ_REG_16(data->current_limit_reg, bytes, val); + break; + case ISL923X_REG_SYS_VOLTAGE_MIN: + READ_REG_16(data->min_volt_reg, bytes, val); break; case ISL923X_REG_SYS_VOLTAGE_MAX: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->max_volt_reg & 0xff); - else - *val = (uint8_t)((data->max_volt_reg >> 8) & 0xff); + READ_REG_16(data->max_volt_reg, bytes, val); break; case ISL923X_REG_ADAPTER_CURRENT_LIMIT1: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->adapter_current_limit1_reg & - 0xff); - else - *val = (uint8_t)((data->adapter_current_limit1_reg >> - 8) & - 0xff); + READ_REG_16(data->adapter_current_limit1_reg, bytes, val); break; case ISL923X_REG_ADAPTER_CURRENT_LIMIT2: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->adapter_current_limit2_reg & - 0xff); - else - *val = (uint8_t)((data->adapter_current_limit2_reg >> - 8) & - 0xff); + READ_REG_16(data->adapter_current_limit2_reg, bytes, val); break; case ISL923X_REG_MANUFACTURER_ID: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->manufacturer_id_reg & 0xff); - else - *val = (uint8_t)((data->manufacturer_id_reg >> 8) & - 0xff); + READ_REG_16(data->manufacturer_id_reg, bytes, val); break; case ISL923X_REG_DEVICE_ID: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->device_id_reg & 0xff); - else - *val = (uint8_t)((data->device_id_reg >> 8) & 0xff); + READ_REG_16(data->device_id_reg, bytes, val); break; case ISL923X_REG_CONTROL0: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->control_0_reg & 0xff); - else - *val = (uint8_t)((data->control_0_reg >> 8) & 0xff); + READ_REG_16(data->control_0_reg, bytes, val); break; case ISL923X_REG_CONTROL1: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->control_1_reg & 0xff); - else - *val = (uint8_t)((data->control_1_reg >> 8) & 0xff); + READ_REG_16(data->control_1_reg, bytes, val); break; case ISL923X_REG_CONTROL2: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->control_2_reg & 0xff); - else - *val = (uint8_t)((data->control_2_reg >> 8) & 0xff); + READ_REG_16(data->control_2_reg, bytes, val); break; case ISL9238_REG_CONTROL3: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->control_3_reg & 0xff); - else - *val = (uint8_t)((data->control_3_reg >> 8) & 0xff); + READ_REG_16(data->control_3_reg, bytes, val); + break; + case ISL9238_REG_CONTROL4: + READ_REG_16(data->control_4_reg, bytes, val); + break; + case RAA489000_REG_CONTROL8: + READ_REG_16(data->control_8_reg, bytes, val); + break; + case ISL9238_REG_INFO2: + READ_REG_16(data->info_2_reg, bytes, val); break; case ISL923X_REG_PROCHOT_AC: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->ac_prochot_reg & 0xff); - else - *val = (uint8_t)((data->ac_prochot_reg >> 8) & 0xff); + READ_REG_16(data->ac_prochot_reg, bytes, val); break; case ISL923X_REG_PROCHOT_DC: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->dc_prochot_reg & 0xff); - else - *val = (uint8_t)((data->dc_prochot_reg >> 8) & 0xff); + READ_REG_16(data->dc_prochot_reg, bytes, val); break; case RAA489000_REG_ADC_VBUS: - __ASSERT_NO_MSG(bytes == 0 || bytes == 1); - if (bytes == 0) - *val = (uint8_t)(data->adc_vbus_reg & 0xff); - else - *val = (uint8_t)((data->adc_vbus_reg >> 8) & 0xff); + READ_REG_16(data->adc_vbus_reg, bytes, val); break; default: + __ASSERT(false, "Attempt to read unimplemented reg 0x%02x", + reg); return -EINVAL; } return 0; } +uint16_t isl923x_emul_peek_reg(struct i2c_emul *i2c_emul, int reg) +{ + uint8_t bytes[2]; + + isl923x_emul_read_byte(i2c_emul, reg, &bytes[0], 0); + isl923x_emul_read_byte(i2c_emul, reg, &bytes[1], 1); + + return bytes[1] << 8 | bytes[0]; +} + +/** Convenience macro for writing 16-bit registers */ +#define WRITE_REG_16(REG, BYTES, VAL, MASK) \ + do { \ + __ASSERT_NO_MSG((BYTES) == 1 || (BYTES) == 2); \ + if ((BYTES) == 1) \ + (REG) = (VAL) & (MASK); \ + else \ + (REG) |= ((VAL) << 8) & (MASK); \ + } while (0) + static int isl923x_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, int bytes) { @@ -274,89 +296,97 @@ static int isl923x_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, switch (reg) { case ISL923X_REG_CHG_CURRENT: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->current_limit_reg = val & REG_CHG_CURRENT_MASK; - else - data->current_limit_reg |= (val << 8) & - REG_CHG_CURRENT_MASK; + WRITE_REG_16(data->current_limit_reg, bytes, val, + REG_CHG_CURRENT_MASK); + break; + case ISL923X_REG_SYS_VOLTAGE_MIN: + WRITE_REG_16(data->min_volt_reg, bytes, val, + REG_SYS_VOLTAGE_MIN_MASK); break; case ISL923X_REG_SYS_VOLTAGE_MAX: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->max_volt_reg = val & REG_SYS_VOLTAGE_MAX_MASK; - else - data->max_volt_reg |= (val << 8) & - REG_SYS_VOLTAGE_MAX_MASK; + WRITE_REG_16(data->max_volt_reg, bytes, val, + REG_SYS_VOLTAGE_MAX_MASK); break; case ISL923X_REG_ADAPTER_CURRENT_LIMIT1: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->adapter_current_limit1_reg = - val & REG_ADAPTER_CURRENT_LIMIT1_MASK; - else - data->adapter_current_limit1_reg |= - (val << 8) & REG_ADAPTER_CURRENT_LIMIT1_MASK; + WRITE_REG_16(data->adapter_current_limit1_reg, bytes, val, + REG_ADAPTER_CURRENT_LIMIT1_MASK); break; case ISL923X_REG_ADAPTER_CURRENT_LIMIT2: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->adapter_current_limit2_reg = - val & REG_ADAPTER_CURRENT_LIMIT2_MASK; - else - data->adapter_current_limit2_reg |= - (val << 8) & REG_ADAPTER_CURRENT_LIMIT2_MASK; + WRITE_REG_16(data->adapter_current_limit2_reg, bytes, val, + REG_ADAPTER_CURRENT_LIMIT2_MASK); break; case ISL923X_REG_CONTROL0: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->control_0_reg = val & REG_CONTROL0_MASK; - else - data->control_0_reg |= (val << 8) & REG_CONTROL0_MASK; + WRITE_REG_16(data->control_0_reg, bytes, val, + REG_CONTROL0_MASK); break; case ISL923X_REG_CONTROL1: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->control_1_reg = val & REG_CONTROL1_MASK; - else - data->control_1_reg |= (val << 8) & REG_CONTROL1_MASK; + WRITE_REG_16(data->control_1_reg, bytes, val, + REG_CONTROL1_MASK); break; case ISL923X_REG_CONTROL2: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->control_2_reg = val & REG_CONTROL2_MASK; - else - data->control_2_reg |= (val << 8) & REG_CONTROL2_MASK; + WRITE_REG_16(data->control_2_reg, bytes, val, + REG_CONTROL2_MASK); break; case ISL9238_REG_CONTROL3: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->control_3_reg = val & REG_CONTROL3_MASK; - else - data->control_3_reg |= (val << 8) & REG_CONTROL3_MASK; + WRITE_REG_16(data->control_3_reg, bytes, val, + REG_CONTROL3_MASK); + break; + case ISL9238_REG_CONTROL4: + WRITE_REG_16(data->control_4_reg, bytes, val, + REG_CONTROL4_MASK); + break; + case RAA489000_REG_CONTROL8: + WRITE_REG_16(data->control_8_reg, bytes, val, + REG_CONTROL8_MASK); + break; + case ISL9238_REG_INFO2: + __ASSERT(false, "Write to read-only reg ISL9238_REG_INFO2"); break; case ISL923X_REG_PROCHOT_AC: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->ac_prochot_reg = val & REG_PROCHOT_AC_MASK; - else - data->ac_prochot_reg |= (val << 8) & - REG_PROCHOT_AC_MASK; + WRITE_REG_16(data->ac_prochot_reg, bytes, val, + REG_PROCHOT_AC_MASK); break; case ISL923X_REG_PROCHOT_DC: - __ASSERT_NO_MSG(bytes == 1 || bytes == 2); - if (bytes == 1) - data->dc_prochot_reg = val & REG_PROCHOT_DC_MASK; - else - data->dc_prochot_reg |= (val << 8) & - REG_PROCHOT_DC_MASK; + WRITE_REG_16(data->dc_prochot_reg, bytes, val, + REG_PROCHOT_DC_MASK); break; default: + __ASSERT(false, "Attempt to write unimplemented reg 0x%02x", + reg); return -EINVAL; } return 0; } +static int isl923x_emul_finish_write(struct i2c_emul *emul, int reg, int bytes) +{ + struct isl923x_emul_data *data = ISL923X_DATA_FROM_I2C_EMUL(emul); + struct i2c_emul *battery_i2c_emul; + struct sbat_emul_bat_data *bat; + int16_t current; + + switch (reg) { + case ISL923X_REG_CHG_CURRENT: + /* Write current to battery. */ + if (data->battery_ord >= 0) { + battery_i2c_emul = sbat_emul_get_ptr(data->battery_ord); + if (battery_i2c_emul != NULL) { + bat = sbat_emul_get_bat_data(battery_i2c_emul); + if (bat != NULL) { + current = REG_TO_CURRENT( + data->current_limit_reg); + if (current > 0) + bat->cur = current; + else + bat->cur = -5; + } + } + } + break; + } + return 0; +} + static int emul_isl923x_init(const struct emul *emul, const struct device *parent) { @@ -378,7 +408,12 @@ static int emul_isl923x_init(const struct emul *emul, .common = { \ .write_byte = isl923x_emul_write_byte, \ .read_byte = isl923x_emul_read_byte, \ + .finish_write = isl923x_emul_finish_write, \ }, \ + .battery_ord = COND_CODE_1( \ + DT_INST_NODE_HAS_PROP(n, battery), \ + (DT_DEP_ORD(DT_INST_PROP(n, battery))), \ + (-1)), \ }; \ static struct isl923x_emul_cfg isl923x_emul_cfg_##n = { \ .common = { \ diff --git a/zephyr/emul/emul_lis2dw12.c b/zephyr/emul/emul_lis2dw12.c index 07fba6091f..e14c184504 100644 --- a/zephyr/emul/emul_lis2dw12.c +++ b/zephyr/emul/emul_lis2dw12.c @@ -29,10 +29,20 @@ struct lis2dw12_emul_data { struct i2c_common_emul_data common; /** Emulated who-am-i register */ uint8_t who_am_i_reg; + /** Emulated ctrl1 register */ + uint8_t ctrl1_reg; /** Emulated ctrl2 register */ uint8_t ctrl2_reg; + /** Emulated ctrl3 register */ + uint8_t ctrl3_reg; + /** Emulated ctrl6 register */ + uint8_t ctrl6_reg; + /** Emulated status register */ + uint8_t status_reg; /** Soft reset count */ uint32_t soft_reset_count; + /** Current X, Y, and Z output data registers */ + int16_t accel_data[3]; }; struct lis2dw12_emul_cfg { @@ -59,8 +69,14 @@ void lis2dw12_emul_reset(const struct emul *emul) i2c_common_emul_set_read_func(i2c_emul, NULL, NULL); i2c_common_emul_set_write_func(i2c_emul, NULL, NULL); data->who_am_i_reg = LIS2DW12_WHO_AM_I; + data->ctrl1_reg = 0; data->ctrl2_reg = 0; + data->ctrl3_reg = 0; + data->ctrl6_reg = 0; + data->status_reg = 0; data->soft_reset_count = 0; + + memset(data->accel_data, 0, sizeof(data->accel_data)); } void lis2dw12_emul_set_who_am_i(const struct emul *emul, uint8_t who_am_i) @@ -87,16 +103,103 @@ static int lis2dw12_emul_read_byte(struct i2c_emul *emul, int reg, uint8_t *val, __ASSERT_NO_MSG(bytes == 0); *val = data->who_am_i_reg; break; + case LIS2DW12_CTRL1_ADDR: + __ASSERT_NO_MSG(bytes == 0); + *val = data->ctrl1_reg; + break; case LIS2DW12_CTRL2_ADDR: __ASSERT_NO_MSG(bytes == 0); *val = data->ctrl2_reg; break; + case LIS2DW12_CTRL3_ADDR: + __ASSERT_NO_MSG(bytes == 0); + *val = data->ctrl3_reg; + break; + case LIS2DW12_CTRL6_ADDR: + __ASSERT_NO_MSG(bytes == 0); + *val = data->ctrl6_reg; + break; + case LIS2DW12_STATUS_REG: + __ASSERT_NO_MSG(bytes == 0); + *val = data->status_reg; + break; + case LIS2DW12_OUT_X_L_ADDR: + case LIS2DW12_OUT_X_H_ADDR: + case LIS2DW12_OUT_Y_L_ADDR: + case LIS2DW12_OUT_Y_H_ADDR: + case LIS2DW12_OUT_Z_L_ADDR: + case LIS2DW12_OUT_Z_H_ADDR: + /* Allow multi-byte reads within this range of registers. + * `bytes` is actually an offset past the starting register + * `reg`. + */ + + __ASSERT_NO_MSG(LIS2DW12_OUT_X_L_ADDR + bytes <= + LIS2DW12_OUT_Z_H_ADDR); + + /* 0 is OUT_X_L_ADDR .. 5 is OUT_Z_H_ADDR */ + int offset_into_odrs = reg - LIS2DW12_OUT_X_L_ADDR + bytes; + + /* Which of the 3 channels we're reading. 0 = X, 1 = Y, 2 = Z */ + int channel = offset_into_odrs / 2; + + if (offset_into_odrs % 2 == 0) { + /* Get the LSB (L reg) */ + *val = data->accel_data[channel] & 0xFF; + } else { + /* Get the MSB (H reg) */ + *val = (data->accel_data[channel] >> 8) & 0xFF; + } + break; default: + __ASSERT(false, "No read handler for register 0x%02x", reg); return -EINVAL; } return 0; } +uint8_t lis2dw12_emul_peek_reg(struct i2c_emul *emul, int reg) +{ + __ASSERT(emul, "emul is NULL"); + + uint8_t val; + int rv; + + rv = lis2dw12_emul_read_byte(emul, reg, &val, 0); + __ASSERT(rv == 0, "Read function returned non-zero: %d", rv); + + return val; +} + +uint8_t lis2dw12_emul_peek_odr(struct i2c_emul *emul) +{ + __ASSERT(emul, "emul is NULL"); + + uint8_t reg = lis2dw12_emul_peek_reg(emul, LIS2DW12_ACC_ODR_ADDR); + + return (reg & LIS2DW12_ACC_ODR_MASK) >> + __builtin_ctz(LIS2DW12_ACC_ODR_MASK); +} + +uint8_t lis2dw12_emul_peek_mode(struct i2c_emul *emul) +{ + __ASSERT(emul, "emul is NULL"); + + uint8_t reg = lis2dw12_emul_peek_reg(emul, LIS2DW12_ACC_MODE_ADDR); + + return (reg & LIS2DW12_ACC_MODE_MASK) >> + __builtin_ctz(LIS2DW12_ACC_MODE_MASK); +} + +uint8_t lis2dw12_emul_peek_lpmode(struct i2c_emul *emul) +{ + __ASSERT(emul, "emul is NULL"); + + uint8_t reg = lis2dw12_emul_peek_reg(emul, LIS2DW12_ACC_LPMODE_ADDR); + + return (reg & LIS2DW12_ACC_LPMODE_MASK); +} + static int lis2dw12_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, int bytes) { @@ -106,6 +209,9 @@ static int lis2dw12_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, case LIS2DW12_WHO_AM_I_REG: LOG_ERR("Can't write to who-am-i register"); return -EINVAL; + case LIS2DW12_CTRL1_ADDR: + data->ctrl1_reg = val; + break; case LIS2DW12_CTRL2_ADDR: __ASSERT_NO_MSG(bytes == 1); if ((val & LIS2DW12_SOFT_RESET_MASK) != 0) { @@ -114,7 +220,28 @@ static int lis2dw12_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, } data->ctrl2_reg = val & ~LIS2DW12_SOFT_RESET_MASK; break; + case LIS2DW12_CTRL3_ADDR: + data->ctrl3_reg = val; + break; + case LIS2DW12_CTRL6_ADDR: + data->ctrl6_reg = val; + break; + case LIS2DW12_STATUS_REG: + __ASSERT(false, + "Attempt to write to read-only status register"); + return -EINVAL; + case LIS2DW12_OUT_X_L_ADDR: + case LIS2DW12_OUT_X_H_ADDR: + case LIS2DW12_OUT_Y_L_ADDR: + case LIS2DW12_OUT_Y_H_ADDR: + case LIS2DW12_OUT_Z_L_ADDR: + case LIS2DW12_OUT_Z_H_ADDR: + __ASSERT(false, + "Attempt to write to data output register 0x%02x", + reg); + return -EINVAL; default: + __ASSERT(false, "No write handler for register 0x%02x", reg); return -EINVAL; } return 0; @@ -137,6 +264,37 @@ static int emul_lis2dw12_init(const struct emul *emul, return i2c_emul_register(parent, emul->dev_label, &data->common.emul); } +int lis2dw12_emul_set_accel_reading(const struct emul *emul, intv3_t reading) +{ + __ASSERT(emul, "emul is NULL"); + struct lis2dw12_emul_data *data = emul->data; + + for (int i = X; i <= Z; i++) { + /* Ensure we fit in a 14-bit signed integer */ + if (reading[i] < LIS2DW12_SAMPLE_MIN || + reading[i] > LIS2DW12_SAMPLE_MAX) { + return -EINVAL; + } + /* Readings are left-aligned, so shift over by 2 */ + data->accel_data[i] = reading[i] * 4; + } + + /* Set the DRDY (data ready) bit */ + data->status_reg |= LIS2DW12_STS_DRDY_UP; + + return 0; +} + +void lis2dw12_emul_clear_accel_reading(const struct emul *emul) +{ + __ASSERT(emul, "emul is NULL"); + struct lis2dw12_emul_data *data = emul->data; + + /* Zero out the registers and reset DRDY bit */ + memset(data->accel_data, 0, sizeof(data->accel_data)); + data->status_reg &= ~LIS2DW12_STS_DRDY_UP; +} + #define INIT_LIS2DW12(n) \ static struct lis2dw12_emul_data lis2dw12_emul_data_##n = { \ .common = { \ diff --git a/zephyr/emul/emul_ln9310.c b/zephyr/emul/emul_ln9310.c index 3e6be65591..4b74c537cf 100644 --- a/zephyr/emul/emul_ln9310.c +++ b/zephyr/emul/emul_ln9310.c @@ -6,6 +6,8 @@ #define DT_DRV_COMPAT cros_ln9310_emul #include <device.h> +#include <devicetree/gpio.h> +#include <drivers/gpio/gpio_emul.h> #include <drivers/i2c.h> #include <drivers/i2c_emul.h> #include <emul.h> @@ -37,6 +39,10 @@ enum functional_mode { struct ln9310_emul_data { /** Common I2C data */ struct i2c_common_emul_data common; + /** Emulated int_gpio port */ + const struct device *gpio_int_port; + /** Emulated int_gpio pin */ + gpio_pin_t gpio_int_pin; /** The current emulated battery cell type */ enum battery_cell_type battery_cell_type; /** Current Functional Mode */ @@ -100,22 +106,28 @@ struct i2c_emul *ln9310_emul_get_i2c_emul(const struct emul *emulator) return &(data->common.emul); } -static void do_ln9310_interrupt(struct ln9310_emul_data *data) +static void ln9310_emul_set_int_pin(struct ln9310_emul_data *data, bool val) { - /* - * TODO(b/201437348): Use gpio interrupt pins properly instead of - * making direct interrupt call as part of this or system test - */ + int res = gpio_emul_input_set(data->gpio_int_port, data->gpio_int_pin, + val); + __ASSERT_NO_MSG(res == 0); +} +static void ln9310_emul_assert_interrupt(struct ln9310_emul_data *data) +{ data->int1_reg |= LN9310_INT1_MODE; - ln9310_interrupt(0); + ln9310_emul_set_int_pin(data, false); } -static void mode_change(struct ln9310_emul_data *data) +static void ln9310_emul_deassert_interrupt(struct ln9310_emul_data *data) { + ln9310_emul_set_int_pin(data, true); +} +static void mode_change(struct ln9310_emul_data *data) +{ bool new_mode_in_standby = data->startup_ctrl_reg & - LN9310_STARTUP_STANDBY_EN; + LN9310_STARTUP_STANDBY_EN; bool new_mode_in_switching_21 = ((data->power_ctrl_reg & LN9310_PWR_OP_MODE_MASK) == LN9310_PWR_OP_MODE_SWITCH21) && @@ -131,41 +143,35 @@ static void mode_change(struct ln9310_emul_data *data) switch (data->current_mode) { case FUNCTIONAL_MODE_STANDBY: if (new_mode_in_switching_21) { - data->current_mode = - FUNCTIONAL_MODE_SWITCHING_21; + data->current_mode = FUNCTIONAL_MODE_SWITCHING_21; data->sys_sts_reg = data->current_mode; - do_ln9310_interrupt(data); + ln9310_emul_assert_interrupt(data); } else if (new_mode_in_switching_31) { - data->current_mode = - FUNCTIONAL_MODE_SWITCHING_31; + data->current_mode = FUNCTIONAL_MODE_SWITCHING_31; data->sys_sts_reg = data->current_mode; - do_ln9310_interrupt(data); + ln9310_emul_assert_interrupt(data); } break; case FUNCTIONAL_MODE_SWITCHING_21: if (new_mode_in_standby) { - data->current_mode = - FUNCTIONAL_MODE_STANDBY; + data->current_mode = FUNCTIONAL_MODE_STANDBY; data->sys_sts_reg = data->current_mode; - do_ln9310_interrupt(data); + ln9310_emul_assert_interrupt(data); } else if (new_mode_in_switching_31) { - data->current_mode = - FUNCTIONAL_MODE_SWITCHING_31; + data->current_mode = FUNCTIONAL_MODE_SWITCHING_31; data->sys_sts_reg = data->current_mode; - do_ln9310_interrupt(data); + ln9310_emul_assert_interrupt(data); } break; case FUNCTIONAL_MODE_SWITCHING_31: if (new_mode_in_standby) { - data->current_mode = - FUNCTIONAL_MODE_STANDBY; + data->current_mode = FUNCTIONAL_MODE_STANDBY; data->sys_sts_reg = data->current_mode; - do_ln9310_interrupt(data); + ln9310_emul_assert_interrupt(data); } else if (new_mode_in_switching_21) { - data->current_mode = - FUNCTIONAL_MODE_SWITCHING_21; + data->current_mode = FUNCTIONAL_MODE_SWITCHING_21; data->sys_sts_reg = data->current_mode; - do_ln9310_interrupt(data); + ln9310_emul_assert_interrupt(data); } break; default: @@ -183,10 +189,17 @@ void ln9310_emul_reset(const struct emul *emulator) struct ln9310_emul_data *data = emulator->data; struct i2c_common_emul_data common = data->common; + gpio_pin_t gpio_int_pin = data->gpio_int_pin; + const struct device *gpio_int_port = data->gpio_int_port; + /* Only Reset the LN9310 Register Data */ memset(data, 0, sizeof(struct ln9310_emul_data)); data->common = common; data->current_mode = LN9310_SYS_STANDBY; + data->gpio_int_pin = gpio_int_pin; + data->gpio_int_port = gpio_int_port; + + ln9310_emul_deassert_interrupt(data); } void ln9310_emul_set_battery_cell_type(const struct emul *emulator, @@ -253,104 +266,87 @@ static int ln9310_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, { struct ln9310_emul_data *data = LN9310_DATA_FROM_I2C_EMUL(emul); + __ASSERT(bytes == 1, "bytes 0x%x != 0x1 on reg 0x%x", bytes, reg); + switch (reg) { case LN9310_REG_INT1: - __ASSERT_NO_MSG(bytes == 1); data->int1_reg = val; break; case LN9310_REG_SYS_STS: - __ASSERT_NO_MSG(bytes == 1); data->sys_sts_reg = val; break; case LN9310_REG_INT1_MSK: - __ASSERT_NO_MSG(bytes == 1); data->int1_msk_reg = val; break; case LN9310_REG_STARTUP_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->startup_ctrl_reg = val; break; case LN9310_REG_LION_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->lion_ctrl_reg = val; break; case LN9310_REG_BC_STS_B: - __ASSERT_NO_MSG(bytes == 1); data->bc_sts_b_reg = val; break; case LN9310_REG_BC_STS_C: - LOG_ERR("Can't write to BC STS C register"); - return -EINVAL; + __ASSERT(false, + "Write to an unverified as safe " + "read-only register on 0x%x", + reg); + break; case LN9310_REG_CFG_0: - __ASSERT_NO_MSG(bytes == 1); data->cfg_0_reg = val; break; case LN9310_REG_CFG_4: - __ASSERT_NO_MSG(bytes == 1); data->cfg_4_reg = val; break; case LN9310_REG_CFG_5: - __ASSERT_NO_MSG(bytes == 1); data->cfg_5_reg = val; break; case LN9310_REG_PWR_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->power_ctrl_reg = val; break; case LN9310_REG_TIMER_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->timer_ctrl_reg = val; break; case LN9310_REG_LB_CTRL: - __ASSERT_NO_MSG(bytes = 1); data->lower_bound_ctrl_reg = val; break; case LN9310_REG_SPARE_0: - __ASSERT_NO_MSG(bytes == 1); data->spare_0_reg = val; break; case LN9310_REG_SWAP_CTRL_0: - __ASSERT_NO_MSG(bytes == 1); data->swap_ctrl_0_reg = val; break; case LN9310_REG_SWAP_CTRL_1: - __ASSERT_NO_MSG(bytes == 1); data->swap_ctrl_1_reg = val; break; case LN9310_REG_SWAP_CTRL_2: - __ASSERT_NO_MSG(bytes == 1); data->swap_ctrl_2_reg = val; break; case LN9310_REG_SWAP_CTRL_3: - __ASSERT_NO_MSG(bytes == 1); data->swap_ctrl_3_reg = val; break; case LN9310_REG_TRACK_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->track_ctrl_reg = val; break; case LN9310_REG_MODE_CHANGE_CFG: - __ASSERT_NO_MSG(bytes == 1); data->mode_change_cfg_reg = val; break; case LN9310_REG_SYS_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->sys_ctrl_reg = val; break; case LN9310_REG_FORCE_SC21_CTRL_1: - __ASSERT_NO_MSG(bytes == 1); data->force_sc21_ctrl_1_reg = val; break; case LN9310_REG_FORCE_SC21_CTRL_2: - __ASSERT_NO_MSG(bytes == 1); data->force_sc21_ctrl_2_reg = val; break; case LN9310_REG_TEST_MODE_CTRL: - __ASSERT_NO_MSG(bytes == 1); data->test_mode_ctrl_reg = val; break; default: - return -EINVAL; + __ASSERT(false, "Unimplemented Register Access Error on 0x%x", + reg); } mode_change(data); return 0; @@ -379,101 +375,82 @@ static int ln9310_emul_read_byte(struct i2c_emul *emul, int reg, uint8_t *val, { struct ln9310_emul_data *data = LN9310_DATA_FROM_I2C_EMUL(emul); + __ASSERT(bytes == 0, "bytes 0x%x != 0x0 on reg 0x%x", bytes, reg); + switch (reg) { case LN9310_REG_INT1: - __ASSERT_NO_MSG(bytes == 0); *val = data->int1_reg; + /* Reading clears interrupts */ + data->int1_reg = 0; + ln9310_emul_deassert_interrupt(data); break; case LN9310_REG_SYS_STS: - __ASSERT_NO_MSG(bytes == 0); *val = data->sys_sts_reg; break; case LN9310_REG_INT1_MSK: - __ASSERT_NO_MSG(bytes == 0); *val = data->int1_msk_reg; break; case LN9310_REG_STARTUP_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->startup_ctrl_reg; break; case LN9310_REG_LION_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->lion_ctrl_reg; break; case LN9310_REG_BC_STS_B: - __ASSERT_NO_MSG(bytes == 0); *val = data->bc_sts_b_reg; break; case LN9310_REG_BC_STS_C: - __ASSERT_NO_MSG(bytes == 0); *val = data->bc_sts_c_reg; break; case LN9310_REG_CFG_0: - __ASSERT_NO_MSG(bytes == 0); *val = data->cfg_0_reg; break; case LN9310_REG_CFG_4: - __ASSERT_NO_MSG(bytes == 0); *val = data->cfg_4_reg; break; case LN9310_REG_CFG_5: - __ASSERT_NO_MSG(bytes == 0); *val = data->cfg_5_reg; break; case LN9310_REG_PWR_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->power_ctrl_reg; break; case LN9310_REG_TIMER_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->timer_ctrl_reg; break; case LN9310_REG_LB_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->lower_bound_ctrl_reg; break; case LN9310_REG_SPARE_0: - __ASSERT_NO_MSG(bytes == 0); *val = data->spare_0_reg; break; case LN9310_REG_SWAP_CTRL_0: - __ASSERT_NO_MSG(bytes == 0); *val = data->swap_ctrl_0_reg; break; case LN9310_REG_SWAP_CTRL_1: - __ASSERT_NO_MSG(bytes == 0); *val = data->swap_ctrl_1_reg; break; case LN9310_REG_SWAP_CTRL_2: - __ASSERT_NO_MSG(bytes == 0); *val = data->swap_ctrl_2_reg; break; case LN9310_REG_SWAP_CTRL_3: - __ASSERT_NO_MSG(bytes == 0); *val = data->swap_ctrl_3_reg; break; case LN9310_REG_TRACK_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->track_ctrl_reg; break; case LN9310_REG_MODE_CHANGE_CFG: - __ASSERT_NO_MSG(bytes == 0); *val = data->mode_change_cfg_reg; break; case LN9310_REG_SYS_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->sys_ctrl_reg; break; case LN9310_REG_FORCE_SC21_CTRL_1: - __ASSERT_NO_MSG(bytes == 0); *val = data->force_sc21_ctrl_1_reg; break; case LN9310_REG_FORCE_SC21_CTRL_2: - __ASSERT_NO_MSG(bytes == 0); *val = data->force_sc21_ctrl_2_reg; break; case LN9310_REG_TEST_MODE_CTRL: - __ASSERT_NO_MSG(bytes == 0); *val = data->test_mode_ctrl_reg; break; default: @@ -506,6 +483,12 @@ static int emul_ln9310_init(const struct emul *emul, return i2c_emul_register(parent, emul->dev_label, &data->common.emul); } +#define LN9310_GET_GPIO_INT_PORT(n) \ + DEVICE_DT_GET(DT_GPIO_CTLR(DT_INST_PROP(n, pg_int_gpio), gpios)) + +#define LN9310_GET_GPIO_INT_PIN(n) \ + DT_GPIO_PIN(DT_INST_PROP(n, pg_int_gpio), gpios) + #define INIT_LN9310(n) \ const struct ln9310_config_t ln9310_config = { \ .i2c_port = NAMED_I2C(power), \ @@ -521,6 +504,8 @@ static int emul_ln9310_init(const struct emul *emul, .finish_read = ln9310_emul_finish_read, \ .access_reg = ln9310_emul_access_reg, \ }, \ + .gpio_int_port = LN9310_GET_GPIO_INT_PORT(n), \ + .gpio_int_pin = LN9310_GET_GPIO_INT_PIN(n), \ }; \ static const struct i2c_common_emul_cfg ln9310_emul_cfg_##n = { \ .i2c_label = DT_INST_BUS_LABEL(n), \ diff --git a/zephyr/emul/emul_sn5s330.c b/zephyr/emul/emul_sn5s330.c index f6b3217372..238815b11c 100644 --- a/zephyr/emul/emul_sn5s330.c +++ b/zephyr/emul/emul_sn5s330.c @@ -11,6 +11,8 @@ #include <emul.h> #include <errno.h> #include <sys/__assert.h> +#include <devicetree/gpio.h> +#include <drivers/gpio/gpio_emul.h> #include "driver/ppc/sn5s330.h" #include "driver/ppc/sn5s330_public.h" @@ -28,6 +30,10 @@ LOG_MODULE_REGISTER(sn5s330_emul, CONFIG_SN5S330_EMUL_LOG_LEVEL); struct sn5s330_emul_data { /** Common I2C data */ struct i2c_common_emul_data common; + /** Emulated int_gpio port */ + const struct device *gpio_int_port; + /** Emulated int_gpio pin */ + gpio_pin_t gpio_int_pin; /** Emulated FUNC_SET1 register */ uint8_t func_set1_reg; /** Emulated FUNC_SET2 register */ @@ -60,9 +66,14 @@ struct sn5s330_emul_data { uint8_t int_status_reg3; /** Emulated INT_STATUS_REG4 register */ /* - * TODO(b:205754232): Register name discrepancy + * TODO(b/205754232): Register name discrepancy */ uint8_t int_status_reg4; + /* + * TODO(b/203364783): For all falling edge registers, implement + * interrupt and bit change to correspond to change in interrupt status + * registers. + */ /** Emulated INT_MASK_RISE_REG1 register */ uint8_t int_mask_rise_reg1; /** Emulated INT_MASK_RISE_REG2 register */ @@ -94,123 +105,122 @@ struct sn5s330_emul_cfg { const struct i2c_common_emul_cfg common; }; -struct i2c_emul *sn5s330_emul_to_i2c_emul(const struct emul *emul) +test_mockable_static void sn5s330_emul_interrupt_set_stub(void) { - struct sn5s330_emul_data *data = emul->data; - - return &(data->common.emul); + /* Stub to be used by fff fakes during test */ } -int sn5s330_emul_peek_reg(const struct emul *emul, uint32_t reg, uint32_t *val) +struct i2c_emul *sn5s330_emul_to_i2c_emul(const struct emul *emul) { struct sn5s330_emul_data *data = emul->data; - switch (reg) { - case SN5S330_FUNC_SET1: - *val = data->func_set1_reg; - break; - default: - return -EINVAL; - } - return 0; + return &(data->common.emul); } -static int sn5s330_emul_read_byte(struct i2c_emul *emul, int reg, uint8_t *val, - int bytes) +/* Workhorse for mapping i2c reg to internal emulator data access */ +static uint8_t *sn5s330_emul_get_reg_ptr(struct sn5s330_emul_data *data, + int reg) { - struct sn5s330_emul_data *data = SN5S330_DATA_FROM_I2C_EMUL(emul); - - __ASSERT(bytes == 0, "bytes 0x%x != 0x0 on reg 0x%x", bytes, reg); - switch (reg) { case SN5S330_FUNC_SET1: - *val = data->func_set1_reg; - break; + return &(data->func_set1_reg); case SN5S330_FUNC_SET2: - *val = data->func_set2_reg; - break; + return &(data->func_set2_reg); case SN5S330_FUNC_SET3: - *val = data->func_set3_reg; - break; + return &(data->func_set3_reg); case SN5S330_FUNC_SET4: - *val = data->func_set4_reg; - break; + return &(data->func_set4_reg); case SN5S330_FUNC_SET5: - *val = data->func_set5_reg; - break; + return &(data->func_set5_reg); case SN5S330_FUNC_SET6: - *val = data->func_set6_reg; - break; + return &(data->func_set6_reg); case SN5S330_FUNC_SET7: - *val = data->func_set7_reg; - break; + return &(data->func_set7_reg); case SN5S330_FUNC_SET8: - *val = data->func_set8_reg; - break; + return &(data->func_set8_reg); case SN5S330_FUNC_SET9: - *val = data->func_set9_reg; - break; + return &(data->func_set9_reg); case SN5S330_FUNC_SET10: - *val = data->func_set10_reg; - break; + return &(data->func_set10_reg); case SN5S330_FUNC_SET11: - *val = data->func_set11_reg; - break; + return &(data->func_set11_reg); case SN5S330_FUNC_SET12: - *val = data->func_set12_reg; - break; + return &(data->func_set12_reg); case SN5S330_INT_STATUS_REG1: - *val = data->int_status_reg1; - break; + return &(data->int_status_reg1); case SN5S330_INT_STATUS_REG2: - *val = data->int_status_reg2; - break; + return &(data->int_status_reg2); case SN5S330_INT_STATUS_REG3: - *val = data->int_status_reg3; - break; + return &(data->int_status_reg3); case SN5S330_INT_STATUS_REG4: - *val = data->int_status_reg4; - break; + return &(data->int_status_reg4); case SN5S330_INT_MASK_RISE_REG1: - *val = data->int_mask_rise_reg1; - break; + return &(data->int_mask_rise_reg1); case SN5S330_INT_MASK_RISE_REG2: - *val = data->int_mask_rise_reg2; - break; + return &(data->int_mask_rise_reg2); case SN5S330_INT_MASK_RISE_REG3: - *val = data->int_mask_rise_reg3; - break; + return &(data->int_mask_rise_reg3); case SN5S330_INT_MASK_FALL_REG1: - *val = data->int_mask_fall_reg1; - break; + return &(data->int_mask_fall_reg1); case SN5S330_INT_MASK_FALL_REG2: - *val = data->int_mask_fall_reg2; - break; + return &(data->int_mask_fall_reg2); case SN5S330_INT_MASK_FALL_REG3: - *val = data->int_mask_fall_reg3; - break; + return &(data->int_mask_fall_reg3); case SN5S330_INT_TRIP_RISE_REG1: - *val = data->int_trip_rise_reg1; - break; + return &(data->int_trip_rise_reg1); case SN5S330_INT_TRIP_RISE_REG2: - *val = data->int_trip_rise_reg2; - break; + return &(data->int_trip_rise_reg2); case SN5S330_INT_TRIP_RISE_REG3: - *val = data->int_trip_rise_reg3; - break; + return &(data->int_trip_rise_reg3); case SN5S330_INT_TRIP_FALL_REG1: - *val = data->int_trip_fall_reg1; - break; + return &(data->int_trip_fall_reg1); case SN5S330_INT_TRIP_FALL_REG2: - *val = data->int_trip_fall_reg2; - break; + return &(data->int_trip_fall_reg2); case SN5S330_INT_TRIP_FALL_REG3: - *val = data->int_trip_fall_reg3; - break; + return &(data->int_trip_fall_reg3); default: __ASSERT(false, "Unimplemented Register Access Error on 0x%x", reg); + /* Statement never reached, required for compiler warnings */ + return NULL; } +} + +void sn5s330_emul_peek_reg(const struct emul *emul, uint32_t reg, uint8_t *val) +{ + struct sn5s330_emul_data *data = emul->data; + uint8_t *data_reg = sn5s330_emul_get_reg_ptr(data, reg); + + *val = *data_reg; +} + +static void sn5s330_emul_set_int_pin(struct i2c_emul *emul, bool val) +{ + struct sn5s330_emul_data *data = SN5S330_DATA_FROM_I2C_EMUL(emul); + int res = gpio_emul_input_set(data->gpio_int_port, data->gpio_int_pin, + val); + __ASSERT_NO_MSG(res == 0); +} + +static void sn5s330_emul_assert_interrupt(struct i2c_emul *emul) +{ + sn5s330_emul_interrupt_set_stub(); + sn5s330_emul_set_int_pin(emul, false); +} + +static void sn5s330_emul_deassert_interrupt(struct i2c_emul *emul) +{ + sn5s330_emul_set_int_pin(emul, true); +} + +static int sn5s330_emul_read_byte(struct i2c_emul *emul, int reg, uint8_t *val, + int bytes) +{ + struct sn5s330_emul_data *data = SN5S330_DATA_FROM_I2C_EMUL(emul); + uint8_t *reg_to_read = sn5s330_emul_get_reg_ptr(data, reg); + + __ASSERT(bytes == 0, "bytes 0x%x != 0x0 on reg 0x%x", bytes, reg); + *val = *reg_to_read; return 0; } @@ -219,45 +229,30 @@ static int sn5s330_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, int bytes) { struct sn5s330_emul_data *data = SN5S330_DATA_FROM_I2C_EMUL(emul); + uint8_t *reg_to_write; + bool deassert_int = false; __ASSERT(bytes == 1, "bytes 0x%x != 0x1 on reg 0x%x", bytes, reg); + /* Specially check for read-only reg */ switch (reg) { - case SN5S330_FUNC_SET1: - data->func_set1_reg = val; - break; - case SN5S330_FUNC_SET2: - data->func_set2_reg = val; - break; - case SN5S330_FUNC_SET3: - data->func_set3_reg = val; - break; - case SN5S330_FUNC_SET4: - data->func_set4_reg = val; - break; - case SN5S330_FUNC_SET5: - data->func_set5_reg = val; - break; - case SN5S330_FUNC_SET6: - data->func_set6_reg = val; - break; - case SN5S330_FUNC_SET7: - data->func_set7_reg = val; - break; - case SN5S330_FUNC_SET8: - data->func_set8_reg = val; - break; - case SN5S330_FUNC_SET9: - data->func_set9_reg = val; - break; - case SN5S330_FUNC_SET10: - data->func_set10_reg = val; - break; - case SN5S330_FUNC_SET11: - data->func_set11_reg = val; - break; - case SN5S330_FUNC_SET12: - data->func_set12_reg = val; + case SN5S330_INT_TRIP_RISE_REG1: + /* fallthrough */ + case SN5S330_INT_TRIP_RISE_REG2: + /* fallthrough */ + case SN5S330_INT_TRIP_RISE_REG3: + /* fallthrough */ + case SN5S330_INT_TRIP_FALL_REG1: + /* fallthrough */ + case SN5S330_INT_TRIP_FALL_REG2: + /* fallthrough */ + case SN5S330_INT_TRIP_FALL_REG3: + reg_to_write = sn5s330_emul_get_reg_ptr(data, reg); + /* Clearing any bit deasserts /INT interrupt signal */ + deassert_int = (*reg_to_write & val) != 0; + /* Write 0 is noop and 1 clears the bit. */ + val = (~val & *reg_to_write); + *reg_to_write = val; break; case SN5S330_INT_STATUS_REG1: /* fallthrough */ @@ -269,61 +264,65 @@ static int sn5s330_emul_write_byte(struct i2c_emul *emul, int reg, uint8_t val, "0x%x", reg); /* fallthrough for checkpath */ - case SN5S330_INT_STATUS_REG4: - data->int_status_reg4 = val; - break; - case SN5S330_INT_MASK_RISE_REG1: - data->int_mask_rise_reg1 = val; - break; - case SN5S330_INT_MASK_RISE_REG2: - data->int_mask_rise_reg2 = val; - break; - case SN5S330_INT_MASK_RISE_REG3: - data->int_mask_rise_reg3 = val; - break; - case SN5S330_INT_MASK_FALL_REG1: - data->int_mask_fall_reg1 = val; - break; - case SN5S330_INT_MASK_FALL_REG2: - data->int_mask_fall_reg2 = val; - break; - case SN5S330_INT_MASK_FALL_REG3: - data->int_mask_fall_reg3 = val; - break; - case SN5S330_INT_TRIP_RISE_REG1: - data->int_trip_rise_reg1 = val; - break; - case SN5S330_INT_TRIP_RISE_REG2: - data->int_trip_rise_reg2 = val; - break; - case SN5S330_INT_TRIP_RISE_REG3: - data->int_trip_rise_reg3 = val; - break; - case SN5S330_INT_TRIP_FALL_REG1: - data->int_trip_fall_reg1 = val; - break; - case SN5S330_INT_TRIP_FALL_REG2: - data->int_trip_fall_reg2 = val; - break; - case SN5S330_INT_TRIP_FALL_REG3: - data->int_trip_fall_reg3 = val; - break; default: - __ASSERT(false, "Unimplemented Register Access Error on 0x%x", - reg); + reg_to_write = sn5s330_emul_get_reg_ptr(data, reg); + *reg_to_write = val; } + if (deassert_int) + sn5s330_emul_deassert_interrupt(emul); + return 0; } +void sn5s330_emul_make_vbus_overcurrent(const struct emul *emul) +{ + struct sn5s330_emul_data *data = emul->data; + struct i2c_emul *i2c_emul = &data->common.emul; + + data->int_status_reg1 |= SN5S330_ILIM_PP1_MASK; + data->int_trip_rise_reg1 |= SN5S330_ILIM_PP1_MASK; + + /* driver disabled this interrupt trigger */ + if (data->int_mask_rise_reg1 & SN5S330_ILIM_PP1_MASK) + return; + + sn5s330_emul_assert_interrupt(i2c_emul); +} + +void sn5s330_emul_lower_vbus_below_minv(const struct emul *emul) +{ + struct sn5s330_emul_data *data = emul->data; + struct i2c_emul *i2c_emul = &data->common.emul; + + data->int_status_reg4 |= SN5S330_VSAFE0V_STAT; + + /* driver disabled this interrupt trigger */ + if (data->int_status_reg4 & SN5S330_VSAFE0V_MASK) + return; + + sn5s330_emul_assert_interrupt(i2c_emul); +} + void sn5s330_emul_reset(const struct emul *emul) { struct sn5s330_emul_data *data = emul->data; struct i2c_common_emul_data common = data->common; + const struct device *gpio_int_port = data->gpio_int_port; + gpio_pin_t gpio_int_pin = data->gpio_int_pin; + + sn5s330_emul_deassert_interrupt(&data->common.emul); + + /* + * TODO(b/203364783): Some registers reset with set bits; this should be + * reflected in the emul_reset function. + */ /* Only Reset the sn5s330 Register Data */ memset(data, 0, sizeof(struct sn5s330_emul_data)); data->common = common; + data->gpio_int_port = gpio_int_port; + data->gpio_int_pin = gpio_int_pin; } static int emul_sn5s330_init(const struct emul *emul, @@ -332,6 +331,8 @@ static int emul_sn5s330_init(const struct emul *emul, const struct sn5s330_emul_cfg *cfg = emul->cfg; struct sn5s330_emul_data *data = emul->data; + sn5s330_emul_deassert_interrupt(&data->common.emul); + data->common.emul.api = &i2c_common_emul_api; data->common.emul.addr = cfg->common.addr; data->common.emul.parent = emul; @@ -342,12 +343,20 @@ static int emul_sn5s330_init(const struct emul *emul, return i2c_emul_register(parent, emul->dev_label, &data->common.emul); } +#define SN5S330_GET_GPIO_INT_PORT(n) \ + DEVICE_DT_GET(DT_GPIO_CTLR(DT_INST_PROP(n, int_gpio), gpios)) + +#define SN5S330_GET_GPIO_INT_PIN(n) \ + DT_GPIO_PIN(DT_INST_PROP(n, int_gpio), gpios) + #define INIT_SN5S330(n) \ static struct sn5s330_emul_data sn5s330_emul_data_##n = { \ .common = { \ .write_byte = sn5s330_emul_write_byte, \ .read_byte = sn5s330_emul_read_byte, \ }, \ + .gpio_int_port = SN5S330_GET_GPIO_INT_PORT(n), \ + .gpio_int_pin = SN5S330_GET_GPIO_INT_PIN(n), \ }; \ static struct sn5s330_emul_cfg sn5s330_emul_cfg_##n = { \ .common = { \ diff --git a/zephyr/emul/emul_syv682x.c b/zephyr/emul/emul_syv682x.c index b9d2dc57c7..51a61aaf0e 100644 --- a/zephyr/emul/emul_syv682x.c +++ b/zephyr/emul/emul_syv682x.c @@ -29,6 +29,8 @@ struct syv682x_emul_data { const struct device *i2c; const struct device *frs_en_gpio_port; gpio_pin_t frs_en_gpio_pin; + const struct device *alert_gpio_port; + gpio_pin_t alert_gpio_pin; /** Configuration information */ const struct syv682x_emul_cfg *cfg; /** Current state of all emulated SYV682x registers */ @@ -39,6 +41,11 @@ struct syv682x_emul_data { */ uint8_t status_cond; uint8_t control_4_cond; + /** + * How many CONTROL_3 reads the busy bit should stay set. 0 means not + * busy. + */ + int busy_read_count; }; /** Static configuration for the emulator */ @@ -51,6 +58,15 @@ struct syv682x_emul_cfg { struct syv682x_emul_data *data; }; +/* Asserts or deasserts the interrupt signal to the EC. */ +static void syv682x_emul_set_alert(struct syv682x_emul_data *data, bool alert) +{ + int res = gpio_emul_input_set(data->alert_gpio_port, + /* The signal is inverted. */ + data->alert_gpio_pin, !alert); + __ASSERT_NO_MSG(res == 0); +} + int syv682x_emul_set_reg(struct i2c_emul *emul, int reg, uint8_t val) { struct syv682x_emul_data *data; @@ -64,48 +80,36 @@ int syv682x_emul_set_reg(struct i2c_emul *emul, int reg, uint8_t val) return 0; } -void syv682x_emul_set_status(struct i2c_emul *emul, uint8_t val) +void syv682x_emul_set_condition(struct i2c_emul *emul, uint8_t status, + uint8_t control_4) { + uint8_t control_4_interrupt = control_4 & SYV682X_CONTROL_4_INT_MASK; struct syv682x_emul_data *data = CONTAINER_OF(emul, struct syv682x_emul_data, emul); int frs_en_gpio = gpio_emul_output_get(data->frs_en_gpio_port, data->frs_en_gpio_pin); - /* Only assert FRS status if FRS is enabled. */ __ASSERT_NO_MSG(frs_en_gpio >= 0); + + /* Only assert FRS status if FRS is enabled. */ if (!frs_en_gpio) - val &= ~SYV682X_STATUS_FRS; + status &= ~SYV682X_STATUS_FRS; - data->status_cond = val; - data->reg[SYV682X_STATUS_REG] |= val; + data->status_cond = status; + data->reg[SYV682X_STATUS_REG] |= status; - if (val & (SYV682X_STATUS_TSD | SYV682X_STATUS_OVP | + data->control_4_cond = control_4_interrupt; + /* Only update the interrupting bits of CONTROL_4. */ + data->reg[SYV682X_CONTROL_4_REG] &= ~SYV682X_CONTROL_4_INT_MASK; + data->reg[SYV682X_CONTROL_4_REG] |= control_4_interrupt; + + /* These conditions disable the power path. */ + if (status & (SYV682X_STATUS_TSD | SYV682X_STATUS_OVP | SYV682X_STATUS_OC_HV)) { data->reg[SYV682X_CONTROL_1_REG] |= SYV682X_CONTROL_1_PWR_ENB; } /* - * TODO(b/190519131): Make this emulator trigger GPIO-based interrupts - * by itself based on the status. In real life, the device should turn - * the power path off when either of these conditions occurs, and they - * should quickly dissipate. If they somehow stay set, the device should - * interrupt continuously. Relatedly, the emulator should only generate - * an interrupt based on FRS status if the FRS enable pin was asserted. - */ -} - -void syv682x_emul_set_control_4(struct i2c_emul *emul, uint8_t val) -{ - struct syv682x_emul_data *data; - uint8_t val_interrupt = val & SYV682X_CONTROL_4_INT_MASK; - - data = CONTAINER_OF(emul, struct syv682x_emul_data, emul); - data->control_4_cond = val_interrupt; - /* Only update the interrupting bits. */ - data->reg[SYV682X_CONTROL_4_REG] &= ~SYV682X_CONTROL_4_INT_MASK; - data->reg[SYV682X_CONTROL_4_REG] |= val_interrupt; - - /* * Note: The description of CONTROL_4 suggests that setting VCONN_OC * will turn off the VCONN channel. The "VCONN Channel Over Current * Response" plot shows that VCONN the device will merely throttle VCONN @@ -114,12 +118,25 @@ void syv682x_emul_set_control_4(struct i2c_emul *emul, uint8_t val) */ /* VBAT_OVP disconnects CC and VCONN. */ - if (val_interrupt & SYV682X_CONTROL_4_VBAT_OVP) { + if (control_4_interrupt & SYV682X_CONTROL_4_VBAT_OVP) { data->reg[SYV682X_CONTROL_4_REG] &= ~(SYV682X_CONTROL_4_CC1_BPS | SYV682X_CONTROL_4_CC2_BPS | SYV682X_CONTROL_4_VCONN1 | SYV682X_CONTROL_4_VCONN2); } + + syv682x_emul_set_alert(data, status | control_4_interrupt); +} + +void syv682x_emul_set_busy_reads(struct i2c_emul *emul, int reads) +{ + struct syv682x_emul_data *data = + CONTAINER_OF(emul, struct syv682x_emul_data, emul); + data->busy_read_count = reads; + if (reads) + data->reg[SYV682X_CONTROL_3_REG] |= SYV682X_BUSY; + else + data->reg[SYV682X_CONTROL_3_REG] &= ~SYV682X_BUSY; } int syv682x_emul_get_reg(struct i2c_emul *emul, int reg, uint8_t *val) @@ -217,6 +234,15 @@ static int syv682x_emul_transfer(struct i2c_emul *emul, struct i2c_msg *msgs, case SYV682X_STATUS_REG: syv682x_emul_set_reg(emul, reg, data->status_cond); break; + case SYV682X_CONTROL_3_REG: + /* Update CONTROL_3[BUSY] based on the busy count. */ + if (data->busy_read_count > 0) { + if (--data->busy_read_count == 0) { + data->reg[SYV682X_CONTROL_3_REG] &= + ~SYV682X_BUSY; + } + } + break; case SYV682X_CONTROL_4_REG: syv682x_emul_set_reg(emul, reg, (*buf & ~SYV682X_CONTROL_4_INT_MASK) | @@ -248,7 +274,7 @@ static struct i2c_emul_api syv682x_emul_api = { * @param emul Emulation information * @param parent Device to emulate * - * @return 0 indicating success (always) + * @return 0 on success or an error code on failure */ static int syv682x_emul_init(const struct emul *emul, const struct device *parent) @@ -264,6 +290,15 @@ static int syv682x_emul_init(const struct emul *emul, memset(data->reg, 0, sizeof(data->reg)); ret = i2c_emul_register(parent, emul->dev_label, &data->emul); + if (ret) + return ret; + + syv682x_emul_set_alert(data, false); + data->reg[SYV682X_CONTROL_1_REG] = + (SYV682X_HV_ILIM_3_30 << SYV682X_HV_ILIM_BIT_SHIFT) | + (SYV682X_5V_ILIM_3_30 << SYV682X_5V_ILIM_BIT_SHIFT) | + /* HV_DR = 0 */ + SYV682X_CONTROL_1_CH_SEL; return ret; } @@ -274,6 +309,10 @@ static int syv682x_emul_init(const struct emul *emul, DT_INST_PROP(n, frs_en_gpio), gpios)), \ .frs_en_gpio_pin = DT_GPIO_PIN( \ DT_INST_PROP(n, frs_en_gpio), gpios), \ + .alert_gpio_port = DEVICE_DT_GET(DT_GPIO_CTLR( \ + DT_INST_PROP(n, alert_gpio), gpios)), \ + .alert_gpio_pin = DT_GPIO_PIN( \ + DT_INST_PROP(n, alert_gpio), gpios), \ }; \ static const struct syv682x_emul_cfg syv682x_emul_cfg_##n = { \ .i2c_label = DT_INST_BUS_LABEL(n), \ diff --git a/zephyr/emul/tcpc/CMakeLists.txt b/zephyr/emul/tcpc/CMakeLists.txt new file mode 100644 index 0000000000..ba00d766e2 --- /dev/null +++ b/zephyr/emul/tcpc/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2021 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +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) +zephyr_library_sources_ifdef(CONFIG_EMUL_TCPCI_PARTNER_SNK emul_tcpci_partner_snk.c) diff --git a/zephyr/emul/tcpc/Kconfig b/zephyr/emul/tcpc/Kconfig new file mode 100644 index 0000000000..424773be3f --- /dev/null +++ b/zephyr/emul/tcpc/Kconfig @@ -0,0 +1,56 @@ +# 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 + +config EMUL_TCPCI_PARTNER_SNK + bool "USB-C sink device emulator" + select EMUL_TCPCI_PARTNER_COMMON + help + Enable USB-C sink device emulator which may be attached to TCPCI + emulator. API of source device emulator is available in + zephyr/include/emul/tcpc/emul_tcpci_partner_snk.h + +endif # EMUL_TCPCI diff --git a/zephyr/emul/emul_ps8xxx.c b/zephyr/emul/tcpc/emul_ps8xxx.c index 0a8cc61fba..5a36f39cd1 100644 --- a/zephyr/emul/emul_ps8xxx.c +++ b/zephyr/emul/tcpc/emul_ps8xxx.c @@ -16,8 +16,8 @@ LOG_MODULE_REGISTER(ps8xxx_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL); #include "tcpm/tcpci.h" #include "emul/emul_common_i2c.h" -#include "emul/emul_ps8xxx.h" -#include "emul/emul_tcpci.h" +#include "emul/tcpc/emul_ps8xxx.h" +#include "emul/tcpc/emul_tcpci.h" #include "driver/tcpm/ps8xxx.h" diff --git a/zephyr/emul/emul_tcpci.c b/zephyr/emul/tcpc/emul_tcpci.c index b2c21c2ec4..660f62ead0 100644 --- a/zephyr/emul/emul_tcpci.c +++ b/zephyr/emul/tcpc/emul_tcpci.c @@ -17,7 +17,7 @@ LOG_MODULE_REGISTER(tcpci_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL); #include "tcpm/tcpci.h" #include "emul/emul_common_i2c.h" -#include "emul/emul_tcpci.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), \ @@ -380,8 +380,8 @@ int tcpci_emul_connect_partner(const struct emul *emul, enum tcpc_cc_voltage_status partner_cc2, enum tcpc_cc_polarity polarity) { + uint16_t cc_status, alert, role_ctrl, power_status; 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) { @@ -424,10 +424,16 @@ int tcpci_emul_connect_partner(const struct emul *emul, 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_get_reg(emul, TCPC_REG_POWER_STATUS, &power_status); + if (power_status & TCPC_REG_POWER_STATUS_VBUS_DET) { + /* + * Set TCPCI emulator VBUS to present (connected, + * above 4V) only if VBUS detection is enabled + */ + tcpci_emul_set_reg(emul, TCPC_REG_POWER_STATUS, + TCPC_REG_POWER_STATUS_VBUS_PRES | + power_status); + } } tcpci_emul_alert_changed(emul); @@ -436,6 +442,46 @@ int tcpci_emul_connect_partner(const struct emul *emul, } /** Check description in emul_tcpci.h */ +int tcpci_emul_disconnect_partner(const struct emul *emul) +{ + struct tcpci_emul_data *data = emul->data; + uint16_t power_status; + 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. + */ + + /* Clear VBUS present in case if source partner is disconnected */ + tcpci_emul_get_reg(emul, TCPC_REG_POWER_STATUS, &power_status); + tcpci_emul_set_reg(emul, TCPC_REG_POWER_STATUS, + power_status & ~TCPC_REG_POWER_STATUS_VBUS_PRES); + + return 0; +} + +/** Check description in emul_tcpci.h */ void tcpci_emul_partner_msg_status(const struct emul *emul, enum tcpci_emul_tx_status status) { 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..f6bff888af --- /dev/null +++ b/zephyr/emul/tcpc/emul_tcpci_partner_common.c @@ -0,0 +1,247 @@ +/* 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 <sys/byteorder.h> +#include <zephyr.h> + +#include "common.h" +#include "emul/tcpc/emul_tcpci_partner_common.h" +#include "emul/tcpc/emul_tcpci.h" +#include "usb_pd.h" + +/** Length of PDO, RDO and BIST request object in SOP message in bytes */ +#define TCPCI_MSG_DO_LEN 4 +/** Length of header in SOP message in bytes */ +#define TCPCI_MSG_HEADER_LEN 2 + +/** Check description in emul_common_tcpci_partner.h */ +struct tcpci_partner_msg *tcpci_partner_alloc_msg(int data_objects) +{ + struct tcpci_partner_msg *new_msg; + size_t size = TCPCI_MSG_HEADER_LEN + TCPCI_MSG_DO_LEN * data_objects; + + 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; + } + + /* Set default message type to SOP */ + new_msg->msg.type = TCPCI_MSG_SOP; + /* TCPCI message size count include type byte */ + new_msg->msg.cnt = size + 1; + new_msg->data_objects = data_objects; + + 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) +{ + /* 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(msg->type, data->power_role, + data->data_role, msg_id, msg->data_objects, + 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 ret; + + do { + ret = k_mutex_lock(&data->to_send_mutex, K_FOREVER); + } while (ret); + + while (!sys_slist_is_empty(&data->to_send)) { + msg = SYS_SLIST_PEEK_HEAD_CONTAINER(&data->to_send, msg, node); + + now = k_uptime_get(); + if (now >= msg->time) { + sys_slist_get_not_empty(&data->to_send); + k_mutex_unlock(&data->to_send_mutex); + + tcpci_partner_set_header(data, msg); + ret = tcpci_emul_add_rx_msg(data->tcpci_emul, &msg->msg, + true /* send alert */); + if (ret) { + tcpci_partner_free_msg(msg); + } + + do { + ret = k_mutex_lock(&data->to_send_mutex, + K_FOREVER); + } while (ret); + } else { + k_work_reschedule(kwd, K_MSEC(msg->time - now)); + break; + } + } + + k_mutex_unlock(&data->to_send_mutex); +} + +/** 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) +{ + struct tcpci_partner_msg *next_msg; + struct tcpci_partner_msg *prev_msg; + uint64_t now; + int ret; + + if (delay == 0) { + tcpci_partner_set_header(data, msg); + ret = tcpci_emul_add_rx_msg(data->tcpci_emul, &msg->msg, true); + if (ret) { + tcpci_partner_free_msg(msg); + } + + return ret; + } + + now = k_uptime_get(); + msg->time = now + delay; + + ret = k_mutex_lock(&data->to_send_mutex, K_FOREVER); + if (ret) { + tcpci_partner_free_msg(msg); + + return ret; + } + + prev_msg = SYS_SLIST_PEEK_HEAD_CONTAINER(&data->to_send, prev_msg, + node); + /* Current message should be sent first */ + if (prev_msg == NULL || prev_msg->time > msg->time) { + sys_slist_prepend(&data->to_send, &msg->node); + k_work_reschedule(&data->delayed_send, K_MSEC(delay)); + k_mutex_unlock(&data->to_send_mutex); + return 0; + } + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&data->to_send, prev_msg, next_msg, + node) { + /* + * If we reach tail or next message should be sent after new + * message, insert new message to the list. + */ + if (next_msg == NULL || next_msg->time > msg->time) { + sys_slist_insert(&data->to_send, &prev_msg->node, + &msg->node); + k_mutex_unlock(&data->to_send_mutex); + return 0; + } + } + + __ASSERT(0, "Message should be always inserted to the list"); + + return -1; +} + +/** 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(0); + if (msg == NULL) { + return -ENOMEM; + } + + msg->type = type; + + return tcpci_partner_send_msg(data, msg, delay); +} + +/** Check description in emul_common_tcpci_partner.h */ +int tcpci_partner_send_data_msg(struct tcpci_partner_data *data, + enum pd_data_msg_type type, + uint32_t *data_obj, int data_obj_num, + uint64_t delay) +{ + struct tcpci_partner_msg *msg; + int addr; + + msg = tcpci_partner_alloc_msg(data_obj_num); + if (msg == NULL) { + return -ENOMEM; + } + + for (int i = 0; i < data_obj_num; i++) { + /* Address of given data object in message buffer */ + addr = TCPCI_MSG_HEADER_LEN + i * TCPCI_MSG_DO_LEN; + sys_put_le32(data_obj[i], msg->msg.buf + addr); + } + + msg->type = type; + + return tcpci_partner_send_msg(data, msg, delay); +} + +/** Check description in emul_common_tcpci_partner.h */ +int tcpci_partner_clear_msg_queue(struct tcpci_partner_data *data) +{ + struct tcpci_partner_msg *msg; + int ret; + + k_work_cancel_delayable(&data->delayed_send); + + ret = k_mutex_lock(&data->to_send_mutex, K_FOREVER); + if (ret) { + return ret; + } + + while (!sys_slist_is_empty(&data->to_send)) { + msg = SYS_SLIST_CONTAINER( + sys_slist_get_not_empty(&data->to_send), + msg, node); + tcpci_partner_free_msg(msg); + } + + k_mutex_unlock(&data->to_send_mutex); + + return 0; +} + +/** 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); + sys_slist_init(&data->to_send); + k_mutex_init(&data->to_send_mutex); +} diff --git a/zephyr/emul/tcpc/emul_tcpci_partner_snk.c b/zephyr/emul/tcpc/emul_tcpci_partner_snk.c new file mode 100644 index 0000000000..2f4d8628b5 --- /dev/null +++ b/zephyr/emul/tcpc/emul_tcpci_partner_snk.c @@ -0,0 +1,433 @@ +/* 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_snk_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL); + +#include <sys/byteorder.h> +#include <zephyr.h> + +#include "common.h" +#include "emul/tcpc/emul_tcpci.h" +#include "emul/tcpc/emul_tcpci_partner_common.h" +#include "emul/tcpc/emul_tcpci_partner_snk.h" +#include "usb_pd.h" + +/** Length of PDO, RDO and BIST request object in SOP message in bytes */ +#define TCPCI_MSG_DO_LEN 4 +/** Length of header in SOP message in bytes */ +#define TCPCI_MSG_HEADER_LEN 2 + +/** + * @brief Get number of PDOs that will be present in sink capability message + * + * @param data Pointer to USB-C sink emulator + * + * @return Number of PDOs that will be present in sink capability message + */ +static int tcpci_snk_emul_num_of_pdos(struct tcpci_snk_emul_data *data) +{ + for (int pdos = 0; pdos < PDO_MAX_OBJECTS; pdos++) { + if (data->pdo[pdos] == 0) { + return pdos; + } + } + + return PDO_MAX_OBJECTS; +} + +/** + * @brief Send capability message constructed from USB-C sink emulator PDOs + * + * @param data Pointer to USB-C sink 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_snk_emul_send_capability_msg(struct tcpci_snk_emul_data *data, + uint64_t delay) +{ + int pdos; + + /* Find number of PDOs */ + pdos = tcpci_snk_emul_num_of_pdos(data); + + return tcpci_partner_send_data_msg(&data->common_data, + PD_DATA_SINK_CAP, + data->pdo, pdos, delay); +} + +/** + * @brief Check if given source PDO satisfy given sink PDO + * + * @param src_pdo PDO presented in source capabilities + * @param snk_pdo PDO presented in sink capabilities + * + * @return 0 on success + * @return -1 if PDOs are different types, PDOs type is unknown or source + * voltage not satisfy sink + * @return Positive value when voltage is OK, but source cannot provide enough + * current for sink. Amount of missing current is returned value in + * 10mA units. + */ +static int tcpci_snk_emul_are_pdos_complementary(uint32_t src_pdo, + uint32_t snk_pdo) +{ + uint32_t pdo_type = src_pdo & PDO_TYPE_MASK; + int missing_current; + + if ((snk_pdo & PDO_TYPE_MASK) != pdo_type) { + return -1; + } + + switch (pdo_type) { + case PDO_TYPE_FIXED: + if (PDO_FIXED_VOLTAGE(snk_pdo) != PDO_FIXED_VOLTAGE(src_pdo)) { + /* Voltage doesn't match */ + return -1; + } + missing_current = PDO_FIXED_CURRENT(snk_pdo) - + PDO_FIXED_CURRENT(src_pdo); + break; + case PDO_TYPE_BATTERY: + if ((PDO_BATT_MIN_VOLTAGE(snk_pdo) < + PDO_BATT_MIN_VOLTAGE(src_pdo)) || + (PDO_BATT_MAX_VOLTAGE(snk_pdo) > + PDO_BATT_MAX_VOLTAGE(src_pdo))) { + /* Voltage not in range */ + return -1; + } + /* + * Convert to current I * 10[mA] = P * 250[mW] / V * 50[mV] + * = P / V * 5 [A] = P / V * 500 * 10[mA] + */ + missing_current = (PDO_BATT_MAX_POWER(snk_pdo) - + PDO_BATT_MAX_POWER(src_pdo)) * 500 / + PDO_BATT_MAX_VOLTAGE(src_pdo); + break; + case PDO_TYPE_VARIABLE: + if ((PDO_VAR_MIN_VOLTAGE(snk_pdo) < + PDO_VAR_MIN_VOLTAGE(src_pdo)) || + (PDO_VAR_MAX_VOLTAGE(snk_pdo) > + PDO_VAR_MAX_VOLTAGE(src_pdo))) { + /* Voltage not in range */ + return -1; + } + missing_current = PDO_VAR_MAX_CURRENT(snk_pdo) - + PDO_VAR_MAX_CURRENT(src_pdo); + break; + default: + /* Unknown PDO type */ + return -1; + } + + if (missing_current > 0) { + /* Voltage is correct, but src doesn't offer enough current */ + return missing_current; + } + + return 0; +} + +/** + * @brief Get given PDO from source capability message + * + * @param msg Source capability message + * @param pdo_num Number of PDO to get. First PDO is 0. + * + * @return PDO on success + * @return 0 when there is no PDO of given index in message + */ +static uint32_t tcpci_snk_emul_get_pdo_from_cap( + const struct tcpci_emul_msg *msg, int pdo_num) +{ + int addr; + + /* Get address of PDO in message */ + addr = TCPCI_MSG_HEADER_LEN + pdo_num * TCPCI_MSG_DO_LEN; + + if (addr >= msg->cnt) { + return 0; + } + + return sys_get_le32(msg->buf + addr); +} + +/** + * @brief Create RDO for given sink and source PDOs + * + * @param src_pdo Selected source PDO + * @param snk_pdo Matching sink PDO + * @param src_pdo_num Index of source PDO in capability message. First PDO is 1. + * + * @return RDO on success + * @return 0 When type of PDOs doesn't match + */ +static uint32_t tcpci_snk_emul_create_rdo(uint32_t src_pdo, uint32_t snk_pdo, + int src_pdo_num) +{ + uint32_t pdo_type = src_pdo & PDO_TYPE_MASK; + int flags; + int pow; + int cur; + + if ((snk_pdo & PDO_TYPE_MASK) != pdo_type) { + return 0; + } + + switch (pdo_type) { + case PDO_TYPE_FIXED: + if (PDO_FIXED_CURRENT(snk_pdo) > PDO_FIXED_CURRENT(src_pdo)) { + flags = RDO_CAP_MISMATCH; + cur = PDO_FIXED_CURRENT(src_pdo); + } else { + flags = 0; + cur = PDO_FIXED_CURRENT(snk_pdo); + } + + /* + * Force mismatch flag if higher capability bit is set. Flags + * should be set only in the first PDO (vSafe5V). This statment + * will only be true for sink which requries higher voltage than + * 5V and doesn't found it in source capabilities. + */ + if (snk_pdo & PDO_FIXED_SNK_HIGHER_CAP) { + flags = RDO_CAP_MISMATCH; + } + + return RDO_FIXED(src_pdo_num, cur, PDO_FIXED_CURRENT(snk_pdo), + flags); + case PDO_TYPE_BATTERY: + if (PDO_BATT_MAX_POWER(snk_pdo) > PDO_BATT_MAX_POWER(src_pdo)) { + flags = RDO_CAP_MISMATCH; + pow = PDO_BATT_MAX_POWER(src_pdo); + } else { + flags = 0; + pow = PDO_BATT_MAX_POWER(snk_pdo); + } + + return RDO_BATT(src_pdo_num, pow, PDO_BATT_MAX_POWER(snk_pdo), + flags); + case PDO_TYPE_VARIABLE: + if (PDO_VAR_MAX_CURRENT(snk_pdo) > + PDO_VAR_MAX_CURRENT(src_pdo)) { + flags = RDO_CAP_MISMATCH; + cur = PDO_VAR_MAX_CURRENT(src_pdo); + } else { + flags = 0; + cur = PDO_VAR_MAX_CURRENT(snk_pdo); + } + return RDO_FIXED(src_pdo_num, cur, PDO_VAR_MAX_CURRENT(snk_pdo), + flags); + } + + return 0; +} + +/** + * @brief Respond to source capability message + * + * @param data Pointer to USB-C sink emulator + * @param msg Source capability message + */ +static void tcpci_snk_emul_handle_source_cap(struct tcpci_snk_emul_data *data, + const struct tcpci_emul_msg *msg) +{ + uint32_t rdo = 0; + uint32_t pdo; + int missing_current; + int skip_first_pdo; + int snk_pdos; + int src_pdos; + + /* If higher capability bit is set, skip matching to first (5V) PDO */ + if (data->pdo[0] & PDO_FIXED_SNK_HIGHER_CAP) { + skip_first_pdo = 1; + } else { + skip_first_pdo = 0; + } + + /* Find number of PDOs */ + snk_pdos = tcpci_snk_emul_num_of_pdos(data); + src_pdos = (msg->cnt - TCPCI_MSG_HEADER_LEN) / TCPCI_MSG_DO_LEN; + + /* Find if any source PDO satisfy any sink PDO */ + for (int pdo_num = 0; pdo_num < src_pdos; pdo_num++) { + pdo = tcpci_snk_emul_get_pdo_from_cap(msg, pdo_num); + + for (int i = skip_first_pdo; i < snk_pdos; i++) { + missing_current = tcpci_snk_emul_are_pdos_complementary( + pdo, data->pdo[i]); + if (missing_current == 0) { + rdo = tcpci_snk_emul_create_rdo(pdo, + data->pdo[i], + pdo_num + 1); + break; + } + } + + /* Correct PDO already found */ + if (rdo != 0) { + break; + } + } + + if (rdo == 0) { + /* Correct PDO wasn't found, let's use 5V */ + pdo = tcpci_snk_emul_get_pdo_from_cap(msg, 0); + rdo = tcpci_snk_emul_create_rdo(pdo, data->pdo[0], 1); + } + + tcpci_partner_send_data_msg(&data->common_data, PD_DATA_REQUEST, &rdo, + 1 /* = data_obj_num */, 0 /* = 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_snk_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_snk_emul_data *data = + CONTAINER_OF(ops, struct tcpci_snk_emul_data, ops); + uint16_t header; + + /* Acknowledge that message was sent successfully */ + tcpci_emul_partner_msg_status(emul, TCPCI_EMUL_TX_SUCCESS); + + /* Handle hard reset */ + if (type == TCPCI_MSG_TX_HARD_RESET) { + tcpci_partner_clear_msg_queue(&data->common_data); + data->common_data.msg_id = 0; + return; + } + + /* Handle only SOP messages */ + if (type != TCPCI_MSG_SOP) { + return; + } + + LOG_HEXDUMP_INF(tx_msg->buf, tx_msg->cnt, + "USB-C sink received message"); + + header = sys_get_le16(tx_msg->buf); + + if (PD_HEADER_CNT(header)) { + /* Handle data message */ + switch (PD_HEADER_TYPE(header)) { + case PD_DATA_SOURCE_CAP: + tcpci_snk_emul_handle_source_cap(data, tx_msg); + 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_partner_send_control_msg(&data->common_data, + PD_CTRL_REJECT, 0); + break; + case PD_CTRL_GET_SINK_CAP: + tcpci_snk_emul_send_capability_msg(data, 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); + break; + case PD_CTRL_ACCEPT: + break; + case PD_CTRL_REJECT: + break; + case PD_CTRL_PING: + break; + case PD_CTRL_PS_RDY: + 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_snk_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_snk.h */ +int tcpci_snk_emul_connect_to_tcpci(struct tcpci_snk_emul_data *data, + const struct emul *tcpci_emul) +{ + int ret; + + tcpci_emul_set_partner_ops(tcpci_emul, &data->ops); + ret = tcpci_emul_connect_partner(tcpci_emul, PD_ROLE_SINK, + TYPEC_CC_VOLT_RD, + TYPEC_CC_VOLT_OPEN, POLARITY_CC1); + if (!ret) { + data->common_data.tcpci_emul = tcpci_emul; + } + + return ret; +} + +/** Check description in emul_tcpci_snk.h */ +void tcpci_snk_emul_init(struct tcpci_snk_emul_data *data) +{ + tcpci_partner_init(&data->common_data); + + data->common_data.data_role = PD_ROLE_DFP; + data->common_data.power_role = PD_ROLE_SINK; + data->common_data.rev = PD_REV20; + + data->ops.transmit = tcpci_snk_emul_transmit_op; + data->ops.rx_consumed = tcpci_snk_emul_rx_consumed_op; + data->ops.control_change = NULL; + + /* By default there is only PDO 5v@500mA */ + data->pdo[0] = PDO_FIXED(5000, 500, 0); + for (int i = 1; i < PDO_MAX_OBJECTS; i++) { + data->pdo[i] = 0; + } +} 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..8dad1c9c5e --- /dev/null +++ b/zephyr/emul/tcpc/emul_tcpci_partner_src.c @@ -0,0 +1,279 @@ +/* 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) +{ + int pdos; + + /* Find number of PDOs */ + for (pdos = 0; pdos < PDO_MAX_OBJECTS; pdos++) { + if (data->pdo[pdos] == 0) { + break; + } + } + + return tcpci_partner_send_data_msg(&data->common_data, + PD_DATA_SOURCE_CAP, + data->pdo, pdos, 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; + } +} |