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