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)
|