/* * Copyright 2022 Google LLC * * SPDX-License-Identifier: Apache-2.0 * * Emulator for Diodes PI3USB9201 Dual-Role USB Charging-Type Detector. */ #include #include #include #include #include #include #include #include #include #include #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)