summaryrefslogtreecommitdiff
path: root/zephyr/emul
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/emul')
-rw-r--r--zephyr/emul/CMakeLists.txt5
-rw-r--r--zephyr/emul/Kconfig3
-rw-r--r--zephyr/emul/Kconfig.ln93101
-rw-r--r--zephyr/emul/Kconfig.sn5s3301
-rw-r--r--zephyr/emul/Kconfig.tcpci38
-rw-r--r--zephyr/emul/emul_charger.c355
-rw-r--r--zephyr/emul/emul_isl923x.c295
-rw-r--r--zephyr/emul/emul_lis2dw12.c158
-rw-r--r--zephyr/emul/emul_ln9310.c137
-rw-r--r--zephyr/emul/emul_sn5s330.c313
-rw-r--r--zephyr/emul/emul_syv682x.c97
-rw-r--r--zephyr/emul/tcpc/CMakeLists.txt9
-rw-r--r--zephyr/emul/tcpc/Kconfig56
-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.c247
-rw-r--r--zephyr/emul/tcpc/emul_tcpci_partner_snk.c433
-rw-r--r--zephyr/emul/tcpc/emul_tcpci_partner_src.c279
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;
+ }
+}