blob: f798d4d9ac0cd620b7d37bf7c22b8dbefd56ca35 [file] [log] [blame]
/*
* 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)