4aac1756a9
This commit changes the parameter of i2c_dump_msgs function from string name to pointer to the device structure. It allows for comparison of device pointers and allow to use the printed device name in i2c shell commands. Signed-off-by: Michał Barnaś <mb@semihalf.com>
327 lines
9.2 KiB
C
327 lines
9.2 KiB
C
/*
|
|
* Copyright 2022 Google LLC
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Emulator for Diodes PI3USB9201 Dual-Role USB Charging-Type Detector.
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/emul.h>
|
|
#include <zephyr/drivers/gpio/gpio_emul.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/i2c_emul.h>
|
|
#include <zephyr/drivers/usb/emul_bc12.h>
|
|
#include <zephyr/drivers/usb/usb_bc12.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/ztest.h>
|
|
|
|
#include <bc12_pi3usb9201.h>
|
|
|
|
#define DT_DRV_COMPAT diodes_pi3usb9201
|
|
|
|
LOG_MODULE_REGISTER(emul_pi3usb9201, LOG_LEVEL_DBG);
|
|
|
|
#define IS_I2C_MSG_WRITE(flags) ((flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE)
|
|
#define IS_I2C_MSG_READ(flags) ((flags & I2C_MSG_RW_MASK) == I2C_MSG_READ)
|
|
|
|
#define EMUL_REG_COUNT (PI3USB9201_REG_HOST_STS + 1)
|
|
#define EMUL_REG_IS_VALID(reg) (reg >= 0 && reg < EMUL_REG_COUNT)
|
|
|
|
#define DCP_DETECTED BIT(7)
|
|
#define SDP_DETECTED BIT(6)
|
|
#define CDP_DETECTED BIT(5)
|
|
#define PROPRIETARY_1A_DETECTED BIT(3)
|
|
#define PROPRIETARY_2A_DETECTED BIT(2)
|
|
#define PROPRIETARY_2_4A_DETECTED BIT(1)
|
|
|
|
/** Run-time data used by the emulator */
|
|
struct pi3usb9201_emul_data {
|
|
/** pi3usb9201 device being emulated */
|
|
const struct device *i2c;
|
|
/** Configuration information */
|
|
const struct pi3usb9201_emul_cfg *cfg;
|
|
/** Current state of all emulated pi3usb9201 registers */
|
|
uint8_t reg[EMUL_REG_COUNT];
|
|
/** The current charging partner type requested by the test */
|
|
uint8_t test_client_status;
|
|
};
|
|
|
|
/** Static configuration for the emulator */
|
|
struct pi3usb9201_emul_cfg {
|
|
/** Pointer to run-time data */
|
|
struct pi3usb9201_emul_data *data;
|
|
/** Address of pi3usb9201 on i2c bus */
|
|
uint16_t addr;
|
|
/** GPIO connected to the INTB signal */
|
|
struct gpio_dt_spec intb_gpio;
|
|
};
|
|
|
|
static bool pi3usb9201_emul_interrupt_is_pending(const struct emul *target)
|
|
{
|
|
struct pi3usb9201_emul_data *data = target->data;
|
|
|
|
if (data->reg[PI3USB9201_REG_CTRL_1] & PI3USB9201_REG_CTRL_1_INT_MASK) {
|
|
/* Interrupt is masked */
|
|
return false;
|
|
}
|
|
|
|
if ((data->reg[PI3USB9201_REG_CTRL_2] & PI3USB9201_REG_CTRL_2_START_DET) &&
|
|
data->reg[PI3USB9201_REG_CLIENT_STS]) {
|
|
/* Cient detection is enabled and there are bits set in the
|
|
* client status register
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
if (data->reg[PI3USB9201_REG_HOST_STS]) {
|
|
/* Any bits set in the host status register generate an interrupt */
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int pi3usb9201_emul_set_reg(const struct emul *target, int reg, uint8_t val)
|
|
{
|
|
struct pi3usb9201_emul_data *data = target->data;
|
|
const struct pi3usb9201_emul_cfg *cfg = target->cfg;
|
|
|
|
if (!EMUL_REG_IS_VALID(reg)) {
|
|
return -EIO;
|
|
}
|
|
|
|
data->reg[reg] = val;
|
|
|
|
/* Once the driver sets the operating mode to client, update the
|
|
* client status register with tests requested charging partner type
|
|
*/
|
|
if (reg == PI3USB9201_REG_CTRL_1) {
|
|
enum pi3usb9201_mode mode = val >> PI3USB9201_REG_CTRL_1_MODE_SHIFT;
|
|
|
|
mode &= PI3USB9201_REG_CTRL_1_MODE_MASK;
|
|
if (mode == PI3USB9201_CLIENT_MODE) {
|
|
data->reg[PI3USB9201_REG_CLIENT_STS] = data->test_client_status;
|
|
}
|
|
}
|
|
|
|
/* Check if an interrupt should be asserted */
|
|
if (pi3usb9201_emul_interrupt_is_pending(target)) {
|
|
gpio_emul_input_set(cfg->intb_gpio.port, cfg->intb_gpio.pin, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pi3usb9201_emul_get_reg(const struct emul *target, int reg, uint8_t *val)
|
|
{
|
|
struct pi3usb9201_emul_data *data = target->data;
|
|
const struct pi3usb9201_emul_cfg *cfg = target->cfg;
|
|
|
|
if (!EMUL_REG_IS_VALID(reg)) {
|
|
return -EIO;
|
|
}
|
|
|
|
*val = data->reg[reg];
|
|
|
|
/* Client/host status register clear on I2C read */
|
|
if ((reg == PI3USB9201_REG_CLIENT_STS) || (reg == PI3USB9201_REG_HOST_STS)) {
|
|
data->reg[reg] = 0;
|
|
|
|
/* Deassert the interrupt line when both client and host status are clear */
|
|
if (data->reg[PI3USB9201_REG_CLIENT_STS] == 0 &&
|
|
data->reg[PI3USB9201_REG_HOST_STS] == 0) {
|
|
gpio_emul_input_set(cfg->intb_gpio.port, cfg->intb_gpio.pin, 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pi3usb9201_emul_reset(const struct emul *target)
|
|
{
|
|
struct pi3usb9201_emul_data *data = target->data;
|
|
const struct pi3usb9201_emul_cfg *cfg = target->cfg;
|
|
|
|
data->reg[PI3USB9201_REG_CTRL_1] = 0;
|
|
data->reg[PI3USB9201_REG_CTRL_2] = 0;
|
|
data->reg[PI3USB9201_REG_CLIENT_STS] = 0;
|
|
data->reg[PI3USB9201_REG_HOST_STS] = 0;
|
|
|
|
data->test_client_status = 0;
|
|
|
|
gpio_emul_input_set(cfg->intb_gpio.port, cfg->intb_gpio.pin, 1);
|
|
}
|
|
|
|
#define PI3USB9201_EMUL_RESET_RULE_BEFORE(inst) \
|
|
pi3usb9201_emul_reset(&EMUL_DT_NAME_GET(DT_DRV_INST(inst)));
|
|
|
|
static void emul_pi3usb9201_reset_before(const struct ztest_unit_test *test, void *data)
|
|
{
|
|
ARG_UNUSED(test);
|
|
ARG_UNUSED(data);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(PI3USB9201_EMUL_RESET_RULE_BEFORE)
|
|
}
|
|
ZTEST_RULE(emul_isl923x_reset, emul_pi3usb9201_reset_before, NULL);
|
|
|
|
/**
|
|
* Emulate an I2C transfer to a pi3usb9201
|
|
*
|
|
* This handles simple reads and writes
|
|
*
|
|
* @param target I2C emulation information
|
|
* @param msgs List of messages to process
|
|
* @param num_msgs Number of messages to process
|
|
* @param addr Address of the I2C target device
|
|
*
|
|
* @retval 0 If successful
|
|
* @retval -EIO General input / output error
|
|
*/
|
|
static int pi3usb9201_emul_transfer(const struct emul *target, struct i2c_msg *msgs, int num_msgs,
|
|
int addr)
|
|
{
|
|
const struct pi3usb9201_emul_cfg *cfg;
|
|
struct pi3usb9201_emul_data *data;
|
|
|
|
data = target->data;
|
|
cfg = data->cfg;
|
|
|
|
if (cfg->addr != addr) {
|
|
LOG_ERR("Address mismatch, expected %02x, got %02x", cfg->addr, addr);
|
|
return -EIO;
|
|
}
|
|
|
|
i2c_dump_msgs(target->dev, msgs, num_msgs, addr);
|
|
|
|
/* Only single byte register access permitted. Write operations must
|
|
* consist of 2 write bytes: register offset, register data. Read
|
|
* operations must consist 1 write byte (register offset) and 1 read
|
|
* byte (register data).
|
|
*/
|
|
if (num_msgs == 1) {
|
|
if (!(IS_I2C_MSG_WRITE(msgs[0].flags) && (msgs[0].len == 2))) {
|
|
LOG_ERR("Unexpected write msgs");
|
|
return -EIO;
|
|
}
|
|
return pi3usb9201_emul_set_reg(target, msgs[0].buf[0], msgs[0].buf[1]);
|
|
} else if (num_msgs == 2) {
|
|
if (!(IS_I2C_MSG_WRITE(msgs[0].flags) && (msgs[0].len == 1) &&
|
|
IS_I2C_MSG_READ(msgs[1].flags) && (msgs[1].len == 1))) {
|
|
LOG_ERR("Unexpected read msgs");
|
|
return -EIO;
|
|
}
|
|
return pi3usb9201_emul_get_reg(target, msgs[0].buf[0], &(msgs[1].buf[0]));
|
|
}
|
|
|
|
LOG_ERR("Unexpected num_msgs");
|
|
return -EIO;
|
|
}
|
|
|
|
int pi3usb9201_emul_set_charging_partner(const struct emul *target, enum bc12_type partner_type)
|
|
{
|
|
struct pi3usb9201_emul_data *data = target->data;
|
|
|
|
/* Set the client status matching the requested partner type */
|
|
switch (partner_type) {
|
|
case BC12_TYPE_NONE:
|
|
data->test_client_status = 0;
|
|
break;
|
|
case BC12_TYPE_SDP:
|
|
data->test_client_status = SDP_DETECTED;
|
|
break;
|
|
case BC12_TYPE_DCP:
|
|
data->test_client_status = DCP_DETECTED;
|
|
break;
|
|
case BC12_TYPE_CDP:
|
|
data->test_client_status = CDP_DETECTED;
|
|
break;
|
|
case BC12_TYPE_PROPRIETARY:
|
|
data->test_client_status = PROPRIETARY_1A_DETECTED;
|
|
break;
|
|
default:
|
|
LOG_ERR("Unsupported partner mode");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pi3usb9201_emul_set_pd_partner_state(const struct emul *target, bool connected)
|
|
{
|
|
struct pi3usb9201_emul_data *data = target->data;
|
|
const struct pi3usb9201_emul_cfg *cfg = target->cfg;
|
|
uint8_t mode;
|
|
|
|
mode = data->reg[PI3USB9201_REG_CTRL_1];
|
|
mode >>= PI3USB9201_REG_CTRL_1_MODE_SHIFT;
|
|
mode &= PI3USB9201_REG_CTRL_1_MODE_MASK;
|
|
|
|
/* Driver must be configured for host mode SDP/CDP */
|
|
if (mode != PI3USB9201_SDP_HOST_MODE && mode != PI3USB9201_CDP_HOST_MODE) {
|
|
return -EACCES;
|
|
}
|
|
|
|
if (connected) {
|
|
data->reg[PI3USB9201_REG_HOST_STS] |= PI3USB9201_REG_HOST_STS_DEV_PLUG;
|
|
} else {
|
|
data->reg[PI3USB9201_REG_HOST_STS] |= PI3USB9201_REG_HOST_STS_DEV_UNPLUG;
|
|
}
|
|
|
|
/* Interrupt are enabled - assert the interrupt */
|
|
if (!(data->reg[PI3USB9201_REG_CTRL_1] & PI3USB9201_REG_CTRL_1_INT_MASK)) {
|
|
gpio_emul_input_set(cfg->intb_gpio.port, cfg->intb_gpio.pin, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Device instantiation */
|
|
|
|
static struct i2c_emul_api pi3usb9201_emul_bus_api = {
|
|
.transfer = pi3usb9201_emul_transfer,
|
|
};
|
|
|
|
static const struct bc12_emul_driver_api pi3usb9201_emul_backend_api = {
|
|
.set_charging_partner = pi3usb9201_emul_set_charging_partner,
|
|
.set_pd_partner = pi3usb9201_emul_set_pd_partner_state,
|
|
};
|
|
|
|
/**
|
|
* @brief Set up a new pi3usb9201 emulator
|
|
*
|
|
* This should be called for each pi3usb9201 device that needs to be
|
|
* emulated. It registers it with the I2C emulation controller.
|
|
*
|
|
* @param target Emulation information
|
|
* @param parent Device to emulate
|
|
*
|
|
* @return 0 indicating success (always)
|
|
*/
|
|
static int pi3usb9201_emul_init(const struct emul *target, const struct device *parent)
|
|
{
|
|
const struct pi3usb9201_emul_cfg *cfg = target->cfg;
|
|
struct pi3usb9201_emul_data *data = cfg->data;
|
|
|
|
data->i2c = parent;
|
|
data->cfg = cfg;
|
|
|
|
LOG_INF("init");
|
|
pi3usb9201_emul_reset(target);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PI3USB9201_EMUL(n) \
|
|
static struct pi3usb9201_emul_data pi3usb9201_emul_data_##n = {}; \
|
|
static const struct pi3usb9201_emul_cfg pi3usb9201_emul_cfg_##n = { \
|
|
.data = &pi3usb9201_emul_data_##n, \
|
|
.addr = DT_INST_REG_ADDR(n), \
|
|
.intb_gpio = GPIO_DT_SPEC_INST_GET_OR(n, intb_gpios, {0}), \
|
|
}; \
|
|
EMUL_DT_INST_DEFINE(n, pi3usb9201_emul_init, &pi3usb9201_emul_data_##n, \
|
|
&pi3usb9201_emul_cfg_##n, &pi3usb9201_emul_bus_api, \
|
|
&pi3usb9201_emul_backend_api)
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(PI3USB9201_EMUL)
|