diff options
-rw-r--r-- | driver/charger/sm5803.c | 12 | ||||
-rw-r--r-- | zephyr/dts/bindings/emul/cros,sm5803-emul.yaml | 34 | ||||
-rw-r--r-- | zephyr/emul/CMakeLists.txt | 1 | ||||
-rw-r--r-- | zephyr/emul/Kconfig | 1 | ||||
-rw-r--r-- | zephyr/emul/Kconfig.sm5803 | 18 | ||||
-rw-r--r-- | zephyr/emul/emul_sm5803.c | 722 | ||||
-rw-r--r-- | zephyr/include/emul/emul_sm5803.h | 77 | ||||
-rw-r--r-- | zephyr/test/drivers/CMakeLists.txt | 1 | ||||
-rw-r--r-- | zephyr/test/drivers/Kconfig | 3 | ||||
-rw-r--r-- | zephyr/test/drivers/sm5803/CMakeLists.txt | 5 | ||||
-rw-r--r-- | zephyr/test/drivers/sm5803/prj.conf | 6 | ||||
-rw-r--r-- | zephyr/test/drivers/sm5803/sm5803.dts | 32 | ||||
-rw-r--r-- | zephyr/test/drivers/sm5803/src/sm5803.c | 709 | ||||
-rw-r--r-- | zephyr/test/drivers/sm5803/src/usbc.c | 42 | ||||
-rw-r--r-- | zephyr/test/drivers/testcase.yaml | 9 |
15 files changed, 1665 insertions, 7 deletions
diff --git a/driver/charger/sm5803.c b/driver/charger/sm5803.c index 57b1a0330f..ec7132fb35 100644 --- a/driver/charger/sm5803.c +++ b/driver/charger/sm5803.c @@ -44,7 +44,7 @@ #define CPRINTS(format, args...) cprints(CC_CHARGER, format, ##args) #define UNKNOWN_DEV_ID -1 -static int dev_id = UNKNOWN_DEV_ID; +test_export_static int dev_id = UNKNOWN_DEV_ID; static const struct charger_info sm5803_charger_info = { .name = CHARGER_NAME, @@ -435,7 +435,7 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) * Track and store whether we've initialized the charger chips already on this * boot. This should prevent us from re-running inits after sysjumps. */ -static bool chip_inited[CHARGER_NUM]; +test_export_static bool chip_inited[CHARGER_NUM]; #define SM5803_SYSJUMP_TAG 0x534D /* SM */ #define SM5803_HOOK_VERSION 1 @@ -734,15 +734,13 @@ static void sm5803_init(int chgnum) SM5803_TINT_MIN_LEVEL); /* - * Configure VBAT_SNSP high interrupt to fire after thresholds are set. + * Configure VBAT_SNSP high and TINT interrupts to fire after + * thresholds are set. */ rv |= main_read8(chgnum, SM5803_REG_INT2_EN, ®); - reg |= SM5803_INT2_VBATSNSP; + reg |= SM5803_INT2_VBATSNSP | SM5803_INT2_TINT; rv |= main_write8(chgnum, SM5803_REG_INT2_EN, reg); - /* Configure TINT interrupts to fire after thresholds are set */ - rv |= main_write8(chgnum, SM5803_REG_INT2_EN, SM5803_INT2_TINT); - /* * Configure CHG_ENABLE to only be set through I2C by setting * HOST_MODE_EN bit (all other register bits are 0 by default) diff --git a/zephyr/dts/bindings/emul/cros,sm5803-emul.yaml b/zephyr/dts/bindings/emul/cros,sm5803-emul.yaml new file mode 100644 index 0000000000..a3e42682e6 --- /dev/null +++ b/zephyr/dts/bindings/emul/cros,sm5803-emul.yaml @@ -0,0 +1,34 @@ +# Copyright 2023 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +description: SM5803 charger emulator + +compatible: "cros,sm5803-emul" + +include: base.yaml + +properties: + main-addr: + type: int + default: 0x30 + description: | + I2C address of the charger base registers. This is always 0x30 on + hardware. + meas-addr: + type: int + default: 0x31 + description: | + I2C address of the charger measurement registers. This is always 0x31 on + hardware. + test-addr: + type: int + default: 0x37 + description: | + I2C address of the charger test registers. This is always 0x37 on + hardware. + interrupt-gpios: + type: phandle-array + description: | + Emulated GPIO pin acting as the active-low interrupt input from the + charger. diff --git a/zephyr/emul/CMakeLists.txt b/zephyr/emul/CMakeLists.txt index 58eef44627..65daeafbde 100644 --- a/zephyr/emul/CMakeLists.txt +++ b/zephyr/emul/CMakeLists.txt @@ -30,6 +30,7 @@ zephyr_library_sources_ifdef(CONFIG_EMUL_PS8743 emul_ps8743.c) zephyr_library_sources_ifdef(CONFIG_EMUL_RT9490 emul_rt9490.c) zephyr_library_sources_ifdef(CONFIG_EMUL_RTC emul_rtc.c) zephyr_library_sources_ifdef(CONFIG_EMUL_SMART_BATTERY emul_smart_battery.c) +zephyr_library_sources_ifdef(CONFIG_EMUL_SM5803 emul_sm5803.c) zephyr_library_sources_ifdef(CONFIG_EMUL_SN5S330 emul_sn5s330.c) zephyr_library_sources_ifdef(CONFIG_EMUL_TCS3400 emul_tcs3400.c) zephyr_library_sources_ifdef(CONFIG_EMUL_TUSB1064 emul_tusb1064.c) diff --git a/zephyr/emul/Kconfig b/zephyr/emul/Kconfig index ecdbfb426c..ecd2d25055 100644 --- a/zephyr/emul/Kconfig +++ b/zephyr/emul/Kconfig @@ -215,4 +215,5 @@ rsource "Kconfig.lis2dw12" rsource "Kconfig.i2c_mock" rsource "Kconfig.isl923x" rsource "Kconfig.clock_control" +rsource "Kconfig.sm5803" rsource "Kconfig.sn5s330" diff --git a/zephyr/emul/Kconfig.sm5803 b/zephyr/emul/Kconfig.sm5803 new file mode 100644 index 0000000000..b1ee48a392 --- /dev/null +++ b/zephyr/emul/Kconfig.sm5803 @@ -0,0 +1,18 @@ +# Copyright 2023 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +menuconfig EMUL_SM5803 + bool "SM5803 charger emulator" + default y + depends on DT_HAS_CROS_SM5803_EMUL_ENABLED + depends on I2C_EMUL + help + Enable the SM5803 emulator, used to test the sm5803 driver. + The emulator API is defined in zephyr/include/emul/emul_sm5803.h + +if EMUL_SM5803 +module = SM5803_EMUL +module-str = sm5803_emul +source "subsys/logging/Kconfig.template.log_config" +endif diff --git a/zephyr/emul/emul_sm5803.c b/zephyr/emul/emul_sm5803.c new file mode 100644 index 0000000000..01038765ef --- /dev/null +++ b/zephyr/emul/emul_sm5803.c @@ -0,0 +1,722 @@ +/* Copyright 2023 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "driver/charger/sm5803.h" +#include "emul/emul_common_i2c.h" +#include "emul/emul_sm5803.h" +#include "emul/emul_stub_device.h" + +#include <zephyr/drivers/gpio/gpio_emul.h> +#include <zephyr/logging/log.h> +#include <zephyr/ztest.h> + +#define DT_DRV_COMPAT cros_sm5803_emul + +LOG_MODULE_REGISTER(sm5803_emul, CONFIG_SM5803_EMUL_LOG_LEVEL); + +#define VBUS_GPADC_LSB_MV 23.4 +#define ADC_CURRENT_LSB_MA 7.32 +#define ICL_LSB_MA 100 +#define CHG_DET_THRESHOLD_MV 4000 + +struct sm5803_emul_data { + struct i2c_common_emul_data i2c_main; + struct i2c_common_emul_data i2c_chg; + struct i2c_common_emul_data i2c_meas; + struct i2c_common_emul_data i2c_test; + + /** Device ID register value */ + uint8_t device_id; + /** PLATFORM register value */ + uint8_t pmode; + /** Raw value of ISO_CL_REG1 */ + uint8_t input_current_limit; + uint8_t gpadc_conf1, gpadc_conf2; + /** Raw values of INT_EN{1..4} */ + uint8_t int_en[4]; + /** Raw values of INT_REQ_{1..4} */ + uint8_t irq1, irq2, irq3, irq4; + uint16_t vbus; + uint16_t ibus; + uint16_t ibat_avg; + bool clock_slowed; + uint8_t cc_conf1; + /** Raw values of FLOW_REG* */ + uint8_t flow1, flow2, flow3; + /** Raw value of SWITCHER_CONF register */ + uint8_t switcher_conf; + /** Bit 0 (PSYS_DAC_EN) of PSYS_REG1 */ + bool psys_dac_enabled; + /** PHOT_REG1 raw value. */ + uint8_t phot1; + /** Raw values of DISCH_CONF_REG* */ + uint8_t disch_conf5; + /** Raw values of PRE_FAST_CONF_REG{1..6} */ + uint8_t pre_fast_conf[6]; + /** Raw value of GPIO0_CTRL register */ + uint8_t gpio_ctrl; +}; + +struct sm5803_emul_cfg { + const struct i2c_common_emul_cfg i2c_main; + const struct i2c_common_emul_cfg i2c_chg; + const struct i2c_common_emul_cfg i2c_meas; + const struct i2c_common_emul_cfg i2c_test; + struct gpio_dt_spec interrupt_gpio; +}; + +const struct gpio_dt_spec * +sm5803_emul_get_interrupt_gpio(const struct emul *emul) +{ + const struct sm5803_emul_cfg *cfg = emul->cfg; + + return &cfg->interrupt_gpio; +} + +struct i2c_common_emul_data *sm5803_emul_get_i2c_main(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return &data->i2c_main; +} + +struct i2c_common_emul_data *sm5803_emul_get_i2c_chg(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return &data->i2c_chg; +} + +struct i2c_common_emul_data *sm5803_emul_get_i2c_meas(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return &data->i2c_meas; +} + +struct i2c_common_emul_data *sm5803_emul_get_i2c_test(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return &data->i2c_test; +} + +int sm5803_emul_read_chg_reg(const struct emul *emul, uint8_t reg) +{ + struct sm5803_emul_data *data = emul->data; + + switch (reg) { + case SM5803_REG_CHG_ILIM: + return data->input_current_limit; + } + return -ENOTSUP; +} + +int sm5803_emul_get_fast_charge_current_limit(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return data->pre_fast_conf[3] & GENMASK(5, 0); +} + +void sm5803_emul_set_vbus_voltage(const struct emul *emul, uint16_t mv) +{ + struct sm5803_emul_data *data = emul->data; + uint16_t old = (float)data->vbus * VBUS_GPADC_LSB_MV; + + data->vbus = (uint16_t)((float)mv / VBUS_GPADC_LSB_MV); + + if (MIN(mv, old) <= CHG_DET_THRESHOLD_MV && + MAX(mv, old) > CHG_DET_THRESHOLD_MV) { + /* CHG_DET changes state; trigger an interrupt. */ + sm5803_emul_set_irqs(emul, SM5803_INT1_CHG, 0, 0, 0); + } +} + +void sm5803_emul_set_input_current(const struct emul *emul, uint16_t mv) +{ + struct sm5803_emul_data *data = emul->data; + + data->ibus = (uint16_t)((float)mv / ADC_CURRENT_LSB_MA); +} + +void sm5803_emul_set_battery_current(const struct emul *emul, uint16_t ma) +{ + struct sm5803_emul_data *data = emul->data; + + data->ibat_avg = (uint16_t)((float)ma / ADC_CURRENT_LSB_MA); +} + +static void update_interrupt_pin(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + const struct sm5803_emul_cfg *cfg = emul->cfg; + + bool pending = data->irq1 || data->irq2 || data->irq3 || data->irq4; + + /* Pin goes low if any IRQ is pending. */ + gpio_emul_input_set(cfg->interrupt_gpio.port, cfg->interrupt_gpio.pin, + !pending); +} + +void sm5803_emul_set_irqs(const struct emul *emul, uint8_t irq1, uint8_t irq2, + uint8_t irq3, uint8_t irq4) +{ + struct sm5803_emul_data *data = emul->data; + + data->irq1 |= irq1; + data->irq2 |= irq2; + data->irq3 |= irq3; + data->irq4 |= irq4; + update_interrupt_pin(emul); +} + +static bool is_chg_det(struct sm5803_emul_data *data) +{ + /* Assume charger presence is cut off at 4V VBUS. */ + return data->vbus * VBUS_GPADC_LSB_MV > CHG_DET_THRESHOLD_MV; +} + +void sm5803_emul_set_gpadc_conf(const struct emul *emul, uint8_t conf1, + uint8_t conf2) +{ + struct sm5803_emul_data *data = emul->data; + + data->gpadc_conf1 = conf1; + data->gpadc_conf2 = conf2; +} + +void sm5803_emul_get_gpadc_conf(const struct emul *emul, uint8_t *conf1, + uint8_t *conf2) +{ + struct sm5803_emul_data *data = emul->data; + + *conf1 = data->gpadc_conf1; + *conf2 = data->gpadc_conf2; +} + +bool sm5803_emul_is_clock_slowed(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return data->clock_slowed; +} + +uint8_t sm5803_emul_get_cc_config(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return data->cc_conf1; +} + +void sm5803_emul_get_flow_regs(const struct emul *emul, uint8_t *flow1, + uint8_t *flow2, uint8_t *flow3) +{ + struct sm5803_emul_data *data = emul->data; + + *flow1 = data->flow1; + *flow2 = data->flow2; + *flow3 = data->flow3; +} + +void sm5803_emul_set_pmode(const struct emul *emul, uint8_t pmode) +{ + struct sm5803_emul_data *data = emul->data; + + data->pmode = pmode & GENMASK(4, 0); +} + +void sm5803_emul_set_device_id(const struct emul *emul, uint8_t id) +{ + struct sm5803_emul_data *data = emul->data; + + data->device_id = id; +} + +uint8_t sm5803_emul_get_gpio_ctrl(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + + return data->gpio_ctrl; +} + +static void sm5803_emul_reset(const struct emul *emul) +{ + struct sm5803_emul_data *data = emul->data; + const struct sm5803_emul_cfg *cfg = emul->cfg; + +#define RESET_I2C(page) \ + do { \ + struct i2c_common_emul_data *common = &data->i2c_##page; \ + \ + i2c_common_emul_set_read_func(common, NULL, NULL); \ + i2c_common_emul_set_write_func(common, NULL, NULL); \ + i2c_common_emul_set_read_fail_reg( \ + common, I2C_COMMON_EMUL_NO_FAIL_REG); \ + i2c_common_emul_set_write_fail_reg( \ + common, I2C_COMMON_EMUL_NO_FAIL_REG); \ + } while (0) + + RESET_I2C(main); + RESET_I2C(chg); + RESET_I2C(meas); + RESET_I2C(test); + + /* Registers set to chip reset values */ + data->device_id = 3; + data->pmode = 0x0b; + data->input_current_limit = 4; + data->gpadc_conf1 = 0xf3; + data->gpadc_conf2 = 0x01; + memset(data->int_en, 0, sizeof(data->int_en)); + data->irq1 = data->irq2 = data->irq3 = data->irq4 = 0; + data->vbus = 0; + data->ibus = 0; + data->ibat_avg = 0; + data->clock_slowed = false; + data->cc_conf1 = 0x09; + data->flow1 = 0x01; + data->flow2 = 0; + data->flow3 = 0; + data->switcher_conf = 1; + data->psys_dac_enabled = true; + data->phot1 = 0x20; + data->disch_conf5 = 0; + memset(data->pre_fast_conf, 0, sizeof(data->pre_fast_conf)); + data->gpio_ctrl = 0x04; + + /* Interrupt pin deasserted */ + gpio_emul_input_set(cfg->interrupt_gpio.port, cfg->interrupt_gpio.pin, + 1); +} + +static int sm5803_main_read_byte(const struct emul *target, int reg, + uint8_t *val, int bytes) +{ + struct sm5803_emul_data *data = target->data; + + switch (reg) { + case SM5803_REG_CHIP_ID: + *val = data->device_id; + return 0; + case SM5803_REG_STATUS1: + *val = is_chg_det(data) ? SM5803_STATUS1_CHG_DET : 0; + return 0; + case SM5803_REG_INT1_REQ: + *val = data->irq1; + /* register clears on read */ + data->irq1 = 0; + update_interrupt_pin(target); + return 0; + case SM5803_REG_INT2_REQ: + *val = data->irq2; + /* register clears on read */ + data->irq2 = 0; + update_interrupt_pin(target); + return 0; + case SM5803_REG_INT3_REQ: + *val = data->irq3; + /* register clears on read */ + data->irq3 = 0; + update_interrupt_pin(target); + return 0; + case SM5803_REG_INT4_REQ: + *val = data->irq4; + /* register clears on read */ + data->irq4 = 0; + update_interrupt_pin(target); + return 0; + case SM5803_REG_INT1_EN: + case SM5803_REG_INT2_EN: + case SM5803_REG_INT3_EN: + case SM5803_REG_INT4_EN: + *val = data->int_en[reg - SM5803_REG_INT1_EN]; + return 0; + case SM5803_REG_PLATFORM: + *val = data->pmode; + return 0; + case SM5803_REG_REFERENCE: + /* Driver never actually uses LDO PGOOD bits. */ + *val = 0; + return 0; + case SM5803_REG_CLOCK_SEL: + *val = data->clock_slowed ? 1 : 0; + return 0; + case SM5803_REG_GPIO0_CTRL: + *val = data->gpio_ctrl; + return 0; + } + LOG_INF("SM5803 main page read of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_main_write_byte(const struct emul *target, int reg, + uint8_t val, int bytes) +{ + struct sm5803_emul_data *data = target->data; + + switch (reg) { + case SM5803_REG_CLOCK_SEL: + data->clock_slowed = (val & 1) == 1; + return 0; + case SM5803_REG_GPIO0_CTRL: + data->gpio_ctrl = val & (GENMASK(7, 6) | GENMASK(2, 0)); + return 0; + } + LOG_INF("SM5803 main page write of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_chg_read_byte(const struct emul *target, int reg, + uint8_t *val, int bytes) +{ + struct sm5803_emul_data *data = target->data; + + switch (reg) { + case SM5803_REG_CC_CONFIG1: + *val = data->cc_conf1; + return 0; + case SM5803_REG_INT1_EN: + case SM5803_REG_INT2_EN: + case SM5803_REG_INT3_EN: + case SM5803_REG_INT4_EN: + *val = data->int_en[reg - SM5803_REG_INT1_EN]; + return 0; + case SM5803_REG_FLOW1: + *val = data->flow1; + return 0; + case SM5803_REG_FLOW2: + *val = data->flow2; + return 0; + case SM5803_REG_FLOW3: + *val = data->flow3; + return 0; + case SM5803_REG_SWITCHER_CONF: + *val = data->switcher_conf; + return 0; + case SM5803_REG_CHG_ILIM: + *val = data->input_current_limit; + return 0; + case SM5803_REG_DISCH_CONF5: + *val = data->disch_conf5; + return 0; + case SM5803_REG_PRE_FAST_CONF_REG1: + case SM5803_REG_PRE_FAST_CONF_REG1 + 1: + case SM5803_REG_PRE_FAST_CONF_REG1 + 2: + case SM5803_REG_PRE_FAST_CONF_REG1 + 3: + case SM5803_REG_PRE_FAST_CONF_REG1 + 4: + case SM5803_REG_PRE_FAST_CONF_REG1 + 5: + *val = data->pre_fast_conf[reg - SM5803_REG_PRE_FAST_CONF_REG1]; + return 0; + case SM5803_REG_LOG2: + *val = ((data->ibus * ADC_CURRENT_LSB_MA) > + (data->input_current_limit * ICL_LSB_MA)) + << 1; + return 0; + case SM5803_REG_PHOT1: + *val = data->phot1; + return 0; + } + LOG_INF("SM5803 charger page read of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_chg_write_byte(const struct emul *target, int reg, + uint8_t val, int bytes) +{ + struct sm5803_emul_data *data = target->data; + + switch (reg) { + case SM5803_REG_CC_CONFIG1: + data->cc_conf1 = val; + return 0; + case SM5803_REG_FLOW1: + data->flow1 = val & 0x8f; + return 0; + case SM5803_REG_FLOW2: + data->flow2 = val; + return 0; + case SM5803_REG_FLOW3: + data->flow3 = val & GENMASK(3, 0); + return 0; + case SM5803_REG_SWITCHER_CONF: + data->switcher_conf = val & 0xc1; + return 0; + case SM5803_REG_CHG_ILIM: + data->input_current_limit = val & GENMASK(4, 0); + return 0; + case SM5803_REG_PRE_FAST_CONF_REG1: + case SM5803_REG_PRE_FAST_CONF_REG1 + 1: + case SM5803_REG_PRE_FAST_CONF_REG1 + 2: + case SM5803_REG_PRE_FAST_CONF_REG1 + 3: + case SM5803_REG_PRE_FAST_CONF_REG1 + 4: + case SM5803_REG_PRE_FAST_CONF_REG1 + 5: + data->pre_fast_conf[reg - SM5803_REG_PRE_FAST_CONF_REG1] = val; + return 0; + case SM5803_REG_PHOT1: + data->phot1 = val; + return 0; + case SM5803_REG_DISCH_CONF5: + data->disch_conf5 = val; + return 0; + } + LOG_INF("SM5803 charger page write of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_meas_read_byte(const struct emul *target, int reg, + uint8_t *val, int bytes) +{ + struct sm5803_emul_data *data = target->data; + + switch (reg) { + case SM5803_REG_GPADC_CONFIG1: + *val = data->gpadc_conf1; + return 0; + case SM5803_REG_GPADC_CONFIG2: + *val = data->gpadc_conf2; + return 0; + case SM5803_REG_PSYS1: + *val = 0x04 | data->psys_dac_enabled; + return 0; + case SM5803_REG_IBUS_CHG_MEAS_MSB: + *val = (data->ibus & GENMASK(9, 2)) >> 2; + return 0; + case SM5803_REG_IBUS_CHG_MEAS_LSB: + *val = data->ibus & GENMASK(1, 0); + return 0; + case SM5803_REG_VBUS_MEAS_MSB: + *val = (data->vbus & GENMASK(9, 2)) >> 2; + return 0; + case SM5803_REG_VBUS_MEAS_LSB: + *val = (is_chg_det(data) ? SM5803_VBUS_MEAS_CHG_DET : 0) | + (data->vbus & GENMASK(1, 0)); + return 0; + case SM5803_REG_IBAT_CHG_AVG_MEAS_MSB: + *val = (data->ibat_avg & GENMASK(9, 2)) >> 2; + return 0; + case SM5803_REG_IBAT_CHG_AVG_MEAS_LSB: + *val = data->ibat_avg & GENMASK(1, 0); + return 0; + } + LOG_INF("SM5803 meas page read of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_meas_write_byte(const struct emul *target, int reg, + uint8_t val, int bytes) +{ + struct sm5803_emul_data *data = target->data; + + switch (reg) { + case SM5803_REG_GPADC_CONFIG1: + data->gpadc_conf1 = val; + return 0; + case SM5803_REG_GPADC_CONFIG2: + data->gpadc_conf2 = val; + return 0; + } + + LOG_INF("SM5803 meas page write of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_test_read_byte(const struct emul *target, int reg, + uint8_t *val, int bytes) +{ + switch (reg) { + case 0x8e: /* Mystery register used for init on chip ID 2 */ + *val = 0; + return 0; + } + LOG_INF("SM5803 test page read of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_test_write_byte(const struct emul *target, int reg, + uint8_t val, int bytes) +{ + switch (reg) { + case 0x8e: /* Mystery register used for init on chip ID 2 */ + return 0; + } + LOG_INF("SM5803 test page write of register %#x unhandled", reg); + return -ENOTSUP; +} + +static int sm5803_emul_i2c_transfer(const struct emul *target, + struct i2c_msg *msgs, int num_msgs, + int addr) +{ + struct sm5803_emul_data *data = target->data; + const struct sm5803_emul_cfg *cfg = target->cfg; + + if (addr == cfg->i2c_main.addr) { + return i2c_common_emul_transfer_workhorse(target, + &data->i2c_main, + &cfg->i2c_main, msgs, + num_msgs, addr); + } else if (addr == cfg->i2c_chg.addr) { + return i2c_common_emul_transfer_workhorse(target, + &data->i2c_chg, + &cfg->i2c_chg, msgs, + num_msgs, addr); + } else if (addr == cfg->i2c_meas.addr) { + return i2c_common_emul_transfer_workhorse(target, + &data->i2c_meas, + &cfg->i2c_meas, msgs, + num_msgs, addr); + } else if (addr == cfg->i2c_test.addr) { + return i2c_common_emul_transfer_workhorse(target, + &data->i2c_test, + &cfg->i2c_test, msgs, + num_msgs, addr); + } + LOG_ERR("I2C transaction for address %#x not supported by SM5803", + addr); + return -ENOTSUP; +} + +const static struct i2c_emul_api sm5803_emul_api = { + .transfer = sm5803_emul_i2c_transfer, +}; + +static int sm5803_emul_init(const struct emul *emul, + const struct device *parent) +{ + struct sm5803_emul_data *data = emul->data; + struct i2c_common_emul_data *const i2c_pages[] = { + &data->i2c_chg, + &data->i2c_main, + &data->i2c_meas, + &data->i2c_test, + }; + + for (int i = 0; i < ARRAY_SIZE(i2c_pages); i++) { + int rv = i2c_emul_register(parent, &i2c_pages[i]->emul); + + if (rv != 0) { + k_oops(); + } + i2c_common_emul_init(i2c_pages[i]); + } + + sm5803_emul_reset(emul); + + return 0; +} + +#define INIT_SM5803(n) \ + const static struct sm5803_emul_cfg sm5803_emul_cfg_##n; \ + static struct sm5803_emul_data sm5803_emul_data_##n = { \ + .i2c_main = { \ + .i2c = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .emul = \ + (struct i2c_emul){ \ + .target = EMUL_DT_GET( \ + DT_DRV_INST(n)), \ + .api = &sm5803_emul_api, \ + .addr = DT_INST_PROP( \ + n, main_addr), \ + }, \ + .cfg = &sm5803_emul_cfg_##n.i2c_main, \ + .read_byte = &sm5803_main_read_byte, \ + .write_byte = &sm5803_main_write_byte, \ + }, \ + .i2c_chg = { \ + .i2c = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .emul = \ + (struct i2c_emul){ \ + .target = EMUL_DT_GET( \ + DT_DRV_INST(n)), \ + .api = &sm5803_emul_api, \ + .addr = DT_INST_REG_ADDR(n), \ + }, \ + .cfg = &sm5803_emul_cfg_##n.i2c_chg, \ + .read_byte = &sm5803_chg_read_byte, \ + .write_byte = &sm5803_chg_write_byte, \ + }, \ + .i2c_meas = { \ + .i2c = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .emul = \ + (struct i2c_emul){ \ + .target = EMUL_DT_GET( \ + DT_DRV_INST(n)), \ + .api = &sm5803_emul_api, \ + .addr = DT_INST_PROP( \ + n, meas_addr), \ + }, \ + .cfg = &sm5803_emul_cfg_##n.i2c_meas, \ + .read_byte = &sm5803_meas_read_byte, \ + .write_byte = &sm5803_meas_write_byte, \ + }, \ + .i2c_test = { \ + .i2c = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .emul = \ + (struct i2c_emul){ \ + .target = EMUL_DT_GET( \ + DT_DRV_INST(n)), \ + .api = &sm5803_emul_api, \ + .addr = DT_INST_PROP( \ + n, test_addr), \ + }, \ + .cfg = &sm5803_emul_cfg_##n.i2c_test, \ + .read_byte = &sm5803_test_read_byte, \ + .write_byte = &sm5803_test_write_byte, \ + }, \ + }; \ + const static struct sm5803_emul_cfg sm5803_emul_cfg_##n = { \ + .i2c_main = \ + (struct i2c_common_emul_cfg){ \ + .dev_label = \ + DT_NODE_FULL_NAME(DT_DRV_INST(n)), \ + .addr = DT_INST_PROP(n, main_addr), \ + .data = &sm5803_emul_data_##n.i2c_main, \ + }, \ + .i2c_chg = \ + (struct i2c_common_emul_cfg){ \ + .dev_label = \ + DT_NODE_FULL_NAME(DT_DRV_INST(n)), \ + .addr = DT_INST_REG_ADDR(n), \ + .data = &sm5803_emul_data_##n.i2c_chg, \ + }, \ + .i2c_meas = \ + (struct i2c_common_emul_cfg){ \ + .dev_label = \ + DT_NODE_FULL_NAME(DT_DRV_INST(n)), \ + .addr = DT_INST_PROP(n, meas_addr), \ + .data = &sm5803_emul_data_##n.i2c_meas, \ + }, \ + .i2c_test = \ + (struct i2c_common_emul_cfg){ \ + .dev_label = \ + DT_NODE_FULL_NAME(DT_DRV_INST(n)), \ + .addr = DT_INST_PROP(n, test_addr), \ + .data = &sm5803_emul_data_##n.i2c_test, \ + }, \ + .interrupt_gpio = { \ + .port = DEVICE_DT_GET(DT_GPIO_CTLR(DT_DRV_INST(n), \ + interrupt_gpios)), \ + .pin = DT_INST_GPIO_PIN(n, interrupt_gpios), \ + .dt_flags = DT_INST_GPIO_FLAGS(n, interrupt_gpios), \ + }, \ + }; \ + EMUL_DT_INST_DEFINE(n, sm5803_emul_init, &sm5803_emul_data_##n, \ + &sm5803_emul_cfg_##n, &sm5803_emul_api, NULL); + +DT_INST_FOREACH_STATUS_OKAY(INIT_SM5803) +DT_INST_FOREACH_STATUS_OKAY(EMUL_STUB_DEVICE) + +static void sm5803_emul_reset_before(const struct ztest_unit_test *test, + void *data) +{ + ARG_UNUSED(test); + ARG_UNUSED(data); + +#define SM5803_EMUL_RESET_RULE_BEFORE(n) \ + sm5803_emul_reset(EMUL_DT_GET(DT_DRV_INST(n))); + + DT_INST_FOREACH_STATUS_OKAY(SM5803_EMUL_RESET_RULE_BEFORE); +} +ZTEST_RULE(sm5803_emul_reset, sm5803_emul_reset_before, NULL); diff --git a/zephyr/include/emul/emul_sm5803.h b/zephyr/include/emul/emul_sm5803.h new file mode 100644 index 0000000000..006fe1d0c7 --- /dev/null +++ b/zephyr/include/emul/emul_sm5803.h @@ -0,0 +1,77 @@ +/* Copyright 2023 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <zephyr/drivers/emul.h> +#include <zephyr/drivers/gpio.h> + +const struct gpio_dt_spec * +sm5803_emul_get_interrupt_gpio(const struct emul *emul); +struct i2c_common_emul_data *sm5803_emul_get_i2c_main(const struct emul *emul); +struct i2c_common_emul_data *sm5803_emul_get_i2c_chg(const struct emul *emul); +struct i2c_common_emul_data *sm5803_emul_get_i2c_meas(const struct emul *emul); +struct i2c_common_emul_data *sm5803_emul_get_i2c_test(const struct emul *emul); + +/** + * Read the value of a charger page register, by address. + * + * This is useful to verify that a user has written an expected value to a + * register, without depending on the user's corresponding getter function. + * + * @return negative value on error, otherwise 8-bit register value. + */ +int sm5803_emul_read_chg_reg(const struct emul *emul, uint8_t reg); + +/** + * Set the reported VBUS voltage, in mV. + * + * If the VBUS voltage crosses the charger detection threshold as a result, + * a CHG_DET interrupt will automatically be triggered. + */ +void sm5803_emul_set_vbus_voltage(const struct emul *emul, uint16_t mv); + +/** Set the reported input current (from VBUS), in mA. */ +void sm5803_emul_set_input_current(const struct emul *emul, uint16_t mv); + +/** Set the reported battery charge current, in mA. */ +void sm5803_emul_set_battery_current(const struct emul *emul, uint16_t ma); + +/** Set the reported device ID (default 3). */ +void sm5803_emul_set_device_id(const struct emul *emul, uint8_t id); + +/** Set the platform ID as configured in hardware by the PMODE resistor. */ +void sm5803_emul_set_pmode(const struct emul *emul, uint8_t pmode); + +/** Get the register value of ICHG_FAST_SET; the fast charge current limit. */ +int sm5803_emul_get_fast_charge_current_limit(const struct emul *emul); + +/** Get the values of the GPADC_CONFIG_1 and GPADC_CONFIG_2 registers. */ +void sm5803_emul_get_gpadc_conf(const struct emul *emul, uint8_t *conf1, + uint8_t *conf2); + +/** Set the GPADC enable bits in GPADC_CONFIG_1 and GPADC_CONFIG_2 registers. */ +void sm5803_emul_set_gpadc_conf(const struct emul *emul, uint8_t conf1, + uint8_t conf2); + +/** Return whether the main clock is slowed (CLOCK_SEL:LOW_POWER_CLOCK_EN). */ +bool sm5803_emul_is_clock_slowed(const struct emul *emul); + +/** Get the value of the CC_CONFIG_1 register. */ +uint8_t sm5803_emul_get_cc_config(const struct emul *emul); + +/** Get the values of the FLOW1..FLOW3 registers. */ +void sm5803_emul_get_flow_regs(const struct emul *emul, uint8_t *flow1, + uint8_t *flow2, uint8_t *flow3); + +/** + * Set the INT_REQ_* registers to indicate pending interrupts. + * + * This does not clear pending IRQs; it only asserts them. IRQs are cleared only + * when the interrupt status registers are read. + */ +void sm5803_emul_set_irqs(const struct emul *emul, uint8_t irq1, uint8_t irq2, + uint8_t irq3, uint8_t irq4); + +/** Get the value of the GPIO_CTRL_1 register, which controls GPIO0. */ +uint8_t sm5803_emul_get_gpio_ctrl(const struct emul *emul); diff --git a/zephyr/test/drivers/CMakeLists.txt b/zephyr/test/drivers/CMakeLists.txt index 4768a7567b..c2e521585e 100644 --- a/zephyr/test/drivers/CMakeLists.txt +++ b/zephyr/test/drivers/CMakeLists.txt @@ -75,6 +75,7 @@ add_subdirectory_ifdef(CONFIG_LINK_TEST_SUITE_PS8XXX ps8xxx) add_subdirectory_ifdef(CONFIG_LINK_TEST_SUITE_GPIO_UNHOOK gpio_unhook) add_subdirectory_ifdef(CONFIG_LINK_TEST_SUITE_HOST_COMMAND_MEMORY_DUMP host_command_memory_dump) add_subdirectory_ifdef(CONFIG_LINK_TEST_SUITE_ANX7452 anx7452) +add_subdirectory_ifdef(CONFIG_LINK_TEST_SUITE_SM5803 sm5803) get_target_property(TEST_SOURCES_NEW app SOURCES) diff --git a/zephyr/test/drivers/Kconfig b/zephyr/test/drivers/Kconfig index 652b36110f..0a39a54ec6 100644 --- a/zephyr/test/drivers/Kconfig +++ b/zephyr/test/drivers/Kconfig @@ -247,4 +247,7 @@ config LINK_TEST_SUITE_ANX7452 Include the test suite of ANX7452 retimer in the binary. The tests use I2C emulation. +config LINK_TEST_SUITE_SM5803 + bool "Link and test the SM5803 charger tests" + source "Kconfig.zephyr" diff --git a/zephyr/test/drivers/sm5803/CMakeLists.txt b/zephyr/test/drivers/sm5803/CMakeLists.txt new file mode 100644 index 0000000000..f7dfa41b4c --- /dev/null +++ b/zephyr/test/drivers/sm5803/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright 2023 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +target_sources(app PRIVATE src/sm5803.c src/usbc.c) diff --git a/zephyr/test/drivers/sm5803/prj.conf b/zephyr/test/drivers/sm5803/prj.conf new file mode 100644 index 0000000000..67e8d4287e --- /dev/null +++ b/zephyr/test/drivers/sm5803/prj.conf @@ -0,0 +1,6 @@ +# Copyright 2023 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +CONFIG_PLATFORM_EC_CHARGER_SM5803=y +CONFIG_PLATFORM_EC_PD_MAX_VOLTAGE_MV=15000 diff --git a/zephyr/test/drivers/sm5803/sm5803.dts b/zephyr/test/drivers/sm5803/sm5803.dts new file mode 100644 index 0000000000..4e0fab39e3 --- /dev/null +++ b/zephyr/test/drivers/sm5803/sm5803.dts @@ -0,0 +1,32 @@ +/* Copyright 2023 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/ { + usbc { + port0@0 { + compatible = "named-usbc-port"; + chg = <&sm5803_emul>; + }; + }; + + gpio2: gpio@2000 { + status = "okay"; + compatible = "zephyr,gpio-emul"; + reg = <0x2000 0x4>; + falling-edge; + gpio-controller; + #gpio-cells = <2>; + ngpios = <1>; + }; +}; + +&i2c0 { + sm5803_emul: sm5803@32 { + compatible = "cros,sm5803-emul", "siliconmitus,sm5803"; + status = "okay"; + reg = <0x32>; + interrupt-gpios = <&gpio2 0 (GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/zephyr/test/drivers/sm5803/src/sm5803.c b/zephyr/test/drivers/sm5803/src/sm5803.c new file mode 100644 index 0000000000..b9d4e17505 --- /dev/null +++ b/zephyr/test/drivers/sm5803/src/sm5803.c @@ -0,0 +1,709 @@ +/* Copyright 2023 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "battery_smart.h" +#include "charger.h" +#include "driver/charger/sm5803.h" +#include "emul/emul_common_i2c.h" +#include "emul/emul_sm5803.h" +#include "emul/tcpc/emul_tcpci_partner_src.h" +#include "test/drivers/charger_utils.h" +#include "test/drivers/test_state.h" +#include "test/drivers/utils.h" + +#include <zephyr/drivers/emul.h> +#include <zephyr/ztest.h> + +#define CHARGER_NUM get_charger_num(&sm5803_drv) +#define SM5803_EMUL EMUL_DT_GET(DT_NODELABEL(sm5803_emul)) + +ZTEST_SUITE(sm5803, drivers_predicate_post_main, NULL, NULL, NULL, NULL); + +ZTEST(sm5803, test_chip_id) +{ + int id; + + /* Emulator only implements chip revision 3. */ + zassert_ok(sm5803_drv.device_id(CHARGER_NUM, &id)); + zassert_equal(id, 3); + + /* After a successful read, the value is cached. */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_main(SM5803_EMUL), + SM5803_REG_CHIP_ID); + zassert_ok(sm5803_drv.device_id(CHARGER_NUM, &id)); +} + +struct i2c_log_entry { + bool write; + uint8_t i2c_addr; + uint8_t reg_addr; + uint8_t value; +}; + +struct i2c_log { + struct i2c_log_entry entries[128]; + size_t entries_used; + size_t entries_asserted; +}; + +#define LOG_ASSERT(_write, _i2c_addr, _reg_addr, _value) \ + do { \ + zassert_true(log_ptr->entries_asserted < \ + log_ptr->entries_used, \ + "No more I2C transactions to verify " \ + "(logged %d)", \ + log_ptr->entries_used); \ + size_t i = log_ptr->entries_asserted++; \ + struct i2c_log_entry *entry = &log_ptr->entries[i]; \ + \ + zassert(entry->write == _write && \ + entry->i2c_addr == _i2c_addr && \ + entry->reg_addr == _reg_addr && \ + (!_write || entry->value == _value), \ + "I2C log mismatch", \ + "Transaction %d did not match expectations:\n" \ + "expected %5s of address %#04x register" \ + " %#04x with value %#04x\n" \ + " found %s of address %#04x register" \ + " %#04x with value %#04x", \ + i, _write ? "write" : "read", _i2c_addr, _reg_addr, \ + _value, entry->write ? "write" : "read", \ + entry->i2c_addr, entry->reg_addr, entry->value); \ + } while (0) + +#define LOG_ASSERT_R(_i2c_addr, _reg_addr) \ + LOG_ASSERT(false, _i2c_addr, _reg_addr, 0) +#define LOG_ASSERT_W(_i2c_addr, _reg_addr, _value) \ + LOG_ASSERT(true, _i2c_addr, _reg_addr, _value) +#define LOG_ASSERT_RW(_i2c_addr, _reg_addr, _value) \ + do { \ + LOG_ASSERT_R(_i2c_addr, _reg_addr); \ + LOG_ASSERT_W(_i2c_addr, _reg_addr, _value); \ + } while (0) + +/* + * Generate a function for each I2C address to log the correct address, because + * the target pointer is the same for each I2C address and there's no way to + * determine from parameters what address we're meant to be. + */ +#define DEFINE_LOG_FNS(name, addr) \ + static int i2c_log_write_##name(const struct emul *target, int reg, \ + uint8_t val, int bytes, void *ctx) \ + { \ + struct i2c_log *log = ctx; \ + \ + if (log->entries_used >= ARRAY_SIZE(log->entries)) { \ + return -ENOSPC; \ + } \ + \ + zassert_equal(target->bus_type, EMUL_BUS_TYPE_I2C); \ + log->entries[log->entries_used++] = (struct i2c_log_entry){ \ + .write = true, \ + .i2c_addr = addr, \ + .reg_addr = reg, \ + .value = val, \ + }; \ + \ + /* Discard write. */ \ + return 0; \ + } \ + \ + static int i2c_log_read_##name(const struct emul *target, int reg, \ + uint8_t *val, int bytes, void *ctx) \ + { \ + struct i2c_log *log = ctx; \ + \ + if (log->entries_used >= ARRAY_SIZE(log->entries)) { \ + return -ENOSPC; \ + } \ + \ + zassert_equal(target->bus_type, EMUL_BUS_TYPE_I2C); \ + log->entries[log->entries_used++] = (struct i2c_log_entry){ \ + .write = false, \ + .i2c_addr = addr, \ + .reg_addr = reg, \ + }; \ + \ + /* Fall through to emulator read. */ \ + return 1; \ + } + +DEFINE_LOG_FNS(main, SM5803_ADDR_MAIN_FLAGS); +DEFINE_LOG_FNS(meas, SM5803_ADDR_MEAS_FLAGS); +DEFINE_LOG_FNS(chg, SM5803_ADDR_CHARGER_FLAGS); +DEFINE_LOG_FNS(test, SM5803_ADDR_TEST_FLAGS); + +static void configure_i2c_log(const struct emul *emul, struct i2c_log *log) +{ + i2c_common_emul_set_read_func(sm5803_emul_get_i2c_main(emul), + i2c_log_read_main, log); + i2c_common_emul_set_write_func(sm5803_emul_get_i2c_main(emul), + i2c_log_write_main, log); + i2c_common_emul_set_read_func(sm5803_emul_get_i2c_meas(emul), + i2c_log_read_meas, log); + i2c_common_emul_set_write_func(sm5803_emul_get_i2c_meas(emul), + i2c_log_write_meas, log); + i2c_common_emul_set_read_func(sm5803_emul_get_i2c_chg(emul), + i2c_log_read_chg, log); + i2c_common_emul_set_write_func(sm5803_emul_get_i2c_chg(emul), + i2c_log_write_chg, log); + i2c_common_emul_set_read_func(sm5803_emul_get_i2c_test(emul), + i2c_log_read_test, log); + i2c_common_emul_set_write_func(sm5803_emul_get_i2c_test(emul), + i2c_log_write_test, log); +} + +static void verify_init_common(struct i2c_log *const log_ptr) +{ + /* Enable LDOs */ + LOG_ASSERT_RW(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_REFERENCE, 0); + /* Psys DAC */ + LOG_ASSERT_RW(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_PSYS1, 0x05); + /* ADC sigma delta */ + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_CC_CONFIG1, 0x09); + /* PROCHOT comparators */ + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_PHOT1, 0x2d); + /* DPM voltage */ + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_DPM_VL_SET_MSB, + 0x12); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_DPM_VL_SET_LSB, + 0x04); + /* Default input current limit */ + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_CHG_ILIM, 0x05); + /* Interrupts */ + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_INT1_EN, 0x04); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_INT4_EN, 0x13); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_TINT_HIGH_TH, 0xd1); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_TINT_LOW_TH, 0); + LOG_ASSERT_RW(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_INT2_EN, 0x81); + /* Charging is exclusively EC-controlled */ + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_FLOW2, 0x40); + /* Battery parameters */ + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_FAST_CONF5, 0x02); + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_PRE_FAST_CONF_REG1, + 0); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_PRECHG, 0x02); + /* BFET limits */ + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_BFET_PWR_MAX_TH, 0x33); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_BFET_PWR_HWSAFE_MAX_TH, + 0xcd); + LOG_ASSERT_RW(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_INT3_EN, 0x06); + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_FLOW3, 0); + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_SWITCHER_CONF, + 0x01); +} + +/** + * Driver internal "init completed" flag needs to be cleared to actually run + * init. + */ +extern bool chip_inited[1]; +/** Driver internal cached value of chip device ID. */ +extern int dev_id; + +ZTEST(sm5803, test_init_2s) +{ + struct i2c_log log = {}; + struct i2c_log *const log_ptr = &log; + + /* Hook up logging functions for each I2C address. */ + configure_i2c_log(SM5803_EMUL, &log); + + /* Emulator defaults to 2S PMODE so we don't need to set it. */ + chip_inited[0] = false; + sm5803_drv.init(CHARGER_NUM); + + /* Ensures we're in a safe state for operation. */ + LOG_ASSERT_R(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_CLOCK_SEL); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_GPADC_CONFIG1, 0xf7); + /* Checks VBUS presence and disables charger. */ + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_GPADC_CONFIG1); + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_VBUS_MEAS_MSB); + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_VBUS_MEAS_LSB); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_FLOW1, 0); + /* Gets chip ID (already cached) and PMODE. */ + LOG_ASSERT_R(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_PLATFORM); + /* Writes a lot of registers for presumably important reasons. */ + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, 0x26, 0xdc); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x21, 0x9b); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x30, 0xc0); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x80, 0x01); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1a, 0x08); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, 0x08, 0xc2); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x1d, 0x40); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x22, 0xb3); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x3e, 0x3c); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4f, 0xbf); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x52, 0x77); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x53, 0xD2); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x54, 0x02); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x55, 0xD1); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x56, 0x7F); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x57, 0x01); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x58, 0x50); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x59, 0x7F); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5A, 0x13); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5B, 0x52); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5D, 0xD0); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x60, 0x44); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x65, 0x35); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x66, 0x29); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x7D, 0x97); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x7E, 0x07); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x33, 0x3C); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5C, 0x7A); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x73, 0x22); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x50, 0x88); + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, 0x34, 0x80); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1f, 0x01); + LOG_ASSERT_W(SM5803_ADDR_TEST_FLAGS, 0x43, 0x10); + LOG_ASSERT_W(SM5803_ADDR_TEST_FLAGS, 0x47, 0x10); + LOG_ASSERT_W(SM5803_ADDR_TEST_FLAGS, 0x48, 0x04); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1f, 0); + verify_init_common(log_ptr); + + zassert_equal(log.entries_asserted, log.entries_used, + "recorded %d transactions but only verified %d", + log.entries_used, log.entries_asserted); + + /* + * Running init again should check and update VBUS presence but not + * re-run complete initialization. Doing more than that probably means + * the first init failed. + */ + log.entries_used = 0; + sm5803_drv.init(CHARGER_NUM); + zassert_equal(log.entries_used, 6); +} + +ZTEST(sm5803, test_init_3s) +{ + struct i2c_log log = {}; + struct i2c_log *const log_ptr = &log; + + /* Hook up logging functions for each I2C address. */ + configure_i2c_log(SM5803_EMUL, &log); + + /* Set 3S PMODE and run init */ + chip_inited[0] = false; + sm5803_emul_set_pmode(SM5803_EMUL, 0x14); + sm5803_drv.init(CHARGER_NUM); + + /* Ensures we're in a safe state for operation. */ + LOG_ASSERT_R(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_CLOCK_SEL); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_GPADC_CONFIG1, 0xf7); + /* Checks VBUS presence and disables charger. */ + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_GPADC_CONFIG1); + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_VBUS_MEAS_MSB); + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_VBUS_MEAS_LSB); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_FLOW1, 0); + /* Gets chip ID (already cached) and PMODE. */ + LOG_ASSERT_R(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_PLATFORM); + /* Writes a lot of registers for presumably important reasons. */ + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, 0x26, 0xd8); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x21, 0x9b); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x30, 0xc0); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x80, 0x01); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1a, 0x08); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, 0x08, 0xc2); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x1d, 0x40); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x22, 0xb3); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x3e, 0x3c); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4b, 0xa6); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4f, 0xbf); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x52, 0x77); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x53, 0xD2); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x54, 0x02); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x55, 0xD1); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x56, 0x7F); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x57, 0x01); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x58, 0x50); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x59, 0x7F); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5A, 0x13); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5B, 0x50); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5D, 0xB0); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x60, 0x44); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x65, 0x35); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x66, 0x29); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x7D, 0x67); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x7E, 0x04); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x33, 0x3C); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5C, 0x7A); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x73, 0x22); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x50, 0x88); + LOG_ASSERT_RW(SM5803_ADDR_CHARGER_FLAGS, 0x34, 0x80); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1f, 0x01); + LOG_ASSERT_W(SM5803_ADDR_TEST_FLAGS, 0x43, 0x10); + LOG_ASSERT_W(SM5803_ADDR_TEST_FLAGS, 0x47, 0x10); + LOG_ASSERT_W(SM5803_ADDR_TEST_FLAGS, 0x48, 0x04); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1f, 0); + verify_init_common(log_ptr); + + zassert_equal(log.entries_asserted, log.entries_used, + "recorded %d transactions but only verified %d", + log.entries_used, log.entries_asserted); +} + +ZTEST(sm5803, test_init_rev2) +{ + struct i2c_log log = {}; + struct i2c_log *const log_ptr = &log; + + /* Hook up logging functions for each I2C address. */ + configure_i2c_log(SM5803_EMUL, &log); + + chip_inited[0] = false; + dev_id = -1; + sm5803_emul_set_device_id(SM5803_EMUL, 2); + sm5803_drv.init(CHARGER_NUM); + + /* Ensures we're in a safe state for operation. */ + LOG_ASSERT_R(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_CLOCK_SEL); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_GPADC_CONFIG1, 0xf7); + /* Checks VBUS presence and disables charger. */ + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_GPADC_CONFIG1); + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_VBUS_MEAS_MSB); + LOG_ASSERT_R(SM5803_ADDR_MEAS_FLAGS, SM5803_REG_VBUS_MEAS_LSB); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, SM5803_REG_FLOW1, 0); + /* Gets chip ID */ + LOG_ASSERT_R(SM5803_ADDR_MAIN_FLAGS, SM5803_REG_CHIP_ID); + /* Writes a lot of registers for presumably important reasons. */ + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x20, 0x08); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x30, 0xc0); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x80, 0x01); + LOG_ASSERT_W(SM5803_ADDR_MEAS_FLAGS, 0x08, 0xc2); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x1d, 0x40); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x1f, 0x09); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x22, 0xb3); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x23, 0x81); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x28, 0xb7); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4a, 0x82); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4b, 0xa3); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4c, 0xa8); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4d, 0xca); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4e, 0x07); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x4f, 0xff); + + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x50, 0x98); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x51, 0); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x52, 0x77); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x53, 0xd2); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x54, 0x02); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x55, 0xd1); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x56, 0x7f); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x57, 0x02); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x58, 0xd1); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x59, 0x7f); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5a, 0x13); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5b, 0x50); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5c, 0x5b); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5d, 0xb0); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5e, 0x3c); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x5f, 0x3c); + + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x60, 0x44); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x61, 0x20); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x65, 0x35); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x66, 0x29); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x67, 0x64); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x68, 0x88); + LOG_ASSERT_W(SM5803_ADDR_CHARGER_FLAGS, 0x69, 0xc7); + + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1f, 1); + LOG_ASSERT_RW(SM5803_ADDR_TEST_FLAGS, 0x8e, 0x20); + LOG_ASSERT_W(SM5803_ADDR_MAIN_FLAGS, 0x1f, 0); + + verify_init_common(log_ptr); + + zassert_equal(log.entries_asserted, log.entries_used, + "recorded %d transactions but only verified %d", + log.entries_used, log.entries_asserted); +} + +ZTEST(sm5803, test_fast_charge_current) +{ + int ma; + + /* + * Can set and read back charge current limit, + * which is adjusted when 0. + */ + zassert_ok(charger_set_current(CHARGER_NUM, 0)); + zassert_equal(1, sm5803_emul_get_fast_charge_current_limit(SM5803_EMUL), + "Zero current limit should be converted to nonzero"); + zassert_ok(charger_get_current(CHARGER_NUM, &ma)); + zassert_equal(ma, 100, + "Actual current should be 100 mA times register value"); + + /* Errors are propagated. */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_chg(SM5803_EMUL), + SM5803_REG_FAST_CONF4); + zassert_not_equal( + 0, charger_set_current(CHARGER_NUM, 1000), + "set_current should fail if FAST_CONF4 is unreadable"); + zassert_not_equal( + 0, charger_get_current(CHARGER_NUM, &ma), + "get_current should fail if FAST_CONF4 is unreadable"); +} + +ZTEST(sm5803, test_measure_input_current) +{ + int ma; + + sm5803_emul_set_input_current(SM5803_EMUL, 852); + zassert_ok(charger_get_input_current(CHARGER_NUM, &ma)); + zassert_equal(ma, 849, "actual returned input current was %d", ma); + + /* Communication errors bubble up */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_meas(SM5803_EMUL), + SM5803_REG_IBUS_CHG_MEAS_LSB); + zassert_not_equal(0, charger_get_input_current(CHARGER_NUM, &ma)); + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_meas(SM5803_EMUL), + SM5803_REG_IBUS_CHG_MEAS_MSB); + zassert_not_equal(0, charger_get_input_current(CHARGER_NUM, &ma)); +} + +ZTEST(sm5803, test_input_current_limit) +{ + int icl; + bool reached; + + /* Can set and read back the input current limit. */ + zassert_ok(charger_set_input_current_limit(CHARGER_NUM, 2150)); + zassert_equal(21, sm5803_emul_read_chg_reg(SM5803_EMUL, + SM5803_REG_CHG_ILIM)); + zassert_ok(charger_get_input_current_limit(CHARGER_NUM, &icl)); + zassert_equal(2100, icl, + "expected 2100 mA input current limit, but was %d", icl); + + /* Can also check whether input current is limited. */ + zassert_ok(charger_is_icl_reached(CHARGER_NUM, &reached)); + zassert_false(reached); + sm5803_emul_set_input_current(SM5803_EMUL, 2400); + zassert_ok(charger_is_icl_reached(CHARGER_NUM, &reached)); + zassert_true(reached); + + /* Communication errors bubble up. */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_chg(SM5803_EMUL), + SM5803_REG_CHG_ILIM); + zassert_not_equal(0, + charger_get_input_current_limit(CHARGER_NUM, &icl)); + i2c_common_emul_set_write_fail_reg(sm5803_emul_get_i2c_chg(SM5803_EMUL), + SM5803_REG_CHG_ILIM); + zassert_not_equal(0, + charger_set_input_current_limit(CHARGER_NUM, 1400)); +} + +/* Analog measurement of VBUS. */ +ZTEST(sm5803, test_get_vbus_voltage) +{ + int mv; + + /* Regular measurement with VBUS ADC enabled works. */ + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 5032); + zassert_ok(charger_get_vbus_voltage(CHARGER_NUM, &mv)); + /* 5.031 is the nearest value representable by the VBUS ADC. */ + zassert_equal(mv, 5031, "driver reported %d mV VBUS", mv); + + /* Communication errors for ADC value bubble up. */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_meas(SM5803_EMUL), + SM5803_REG_VBUS_MEAS_LSB); + zassert_not_equal(0, charger_get_vbus_voltage(CHARGER_NUM, &mv)); + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_meas(SM5803_EMUL), + SM5803_REG_VBUS_MEAS_MSB); + zassert_not_equal(0, charger_get_vbus_voltage(CHARGER_NUM, &mv)); + + /* Returns a NOT_POWERED error if the VBUS ADC is disabled. */ + sm5803_emul_set_gpadc_conf(SM5803_EMUL, + (uint8_t)~SM5803_GPADCC1_VBUS_EN, 0); + zassert_equal(EC_ERROR_NOT_POWERED, + charger_get_vbus_voltage(CHARGER_NUM, &mv)); +} + +ZTEST(sm5803, test_get_battery_current) +{ + int ma; + + sm5803_emul_set_battery_current(SM5803_EMUL, 1234); + zassert_ok(charger_get_actual_current(CHARGER_NUM, &ma)); + /* 1229 mA is nearest value representable at ADC resolution */ + zassert_equal(ma, 1229, "read value was %d", ma); + + /* Communication errors bubble up. */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_meas(SM5803_EMUL), + SM5803_REG_IBAT_CHG_AVG_MEAS_LSB); + zassert_not_equal(0, charger_get_actual_current(CHARGER_NUM, &ma)); + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_meas(SM5803_EMUL), + SM5803_REG_IBAT_CHG_AVG_MEAS_MSB); + zassert_not_equal(0, charger_get_actual_current(CHARGER_NUM, &ma)); +} + +/* Digital VBUS presence detection derived from DHG_DET. */ +ZTEST(sm5803, test_digital_vbus_presence_detect) +{ + /* + * CHG_DET going high (from VBUS presence) triggers an interrupt and + * presence update. + */ + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 5000); + k_sleep(K_SECONDS(1)); /* Allow interrupt to be serviced. */ + zassert_true(sm5803_is_vbus_present(CHARGER_NUM)); + + /* VBUS going away triggers another interrupt and update. */ + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 0); + k_sleep(K_SECONDS(1)); /* Allow interrupt to be serviced. */ + zassert_false(sm5803_is_vbus_present(CHARGER_NUM)); +} + +/* VBUS detection for PD, analog or digital depending on chip state. */ +ZTEST(sm5803, test_check_vbus_level) +{ + /* Default state with VBUS ADC enabled: uses analog value */ + zassert_true(sm5803_check_vbus_level(CHARGER_NUM, VBUS_REMOVED)); + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 5000); + zassert_true(sm5803_check_vbus_level(CHARGER_NUM, VBUS_PRESENT)); + + /* 4.6V is less than vSafe5V */ + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 4600); + k_sleep(K_SECONDS(1)); + zassert_false(sm5803_check_vbus_level(CHARGER_NUM, VBUS_PRESENT)); + + /* + * With ADC disabled, uses digital presence only. 4.6V is high enough + * to trip CHG_DET but wasn't enough to count as present with the analog + * reading. + */ + sm5803_emul_set_gpadc_conf(SM5803_EMUL, 0, 0); + zassert_true(sm5803_check_vbus_level(CHARGER_NUM, VBUS_PRESENT)); + + /* 0.4V is !CHG_DET */ + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 400); + k_sleep(K_SECONDS(1)); + zassert_true(sm5803_check_vbus_level(CHARGER_NUM, VBUS_REMOVED)); +} + +ZTEST(sm5803, test_lpm) +{ + const struct emul *tcpci_emul = EMUL_GET_USBC_BINDING(0, tcpc); + struct tcpci_partner_data partner; + struct tcpci_src_emul_data partner_src; + uint8_t gpadc1, gpadc2; + uint8_t cc_conf1; + uint8_t flow1, flow2, flow3; + + tcpci_partner_init(&partner, PD_REV30); + partner.extensions = tcpci_src_emul_init(&partner_src, &partner, NULL); + + /* Connect 5V source. */ + zassert_ok(tcpci_partner_connect_to_tcpci(&partner, tcpci_emul)); + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 5000); + k_sleep(K_SECONDS(4)); + + /* Charger should now have exited runtime LPM. */ + zassert_false(sm5803_emul_is_clock_slowed(SM5803_EMUL)); + sm5803_emul_get_gpadc_conf(SM5803_EMUL, &gpadc1, &gpadc2); + /* All except IBAT_DISCHG enabled. */ + zassert_equal(gpadc1, 0xf7, "actual value was %#x", gpadc1); + /* Default value. */ + zassert_equal(gpadc2, 1, "actual value was %#x", gpadc2); + /* Sigma-delta for Coulomb Counter is enabled. */ + cc_conf1 = sm5803_emul_get_cc_config(SM5803_EMUL); + zassert_equal(cc_conf1, 0x09, "actual value was %#x", cc_conf1); + /* Charger is sinking. */ + sm5803_emul_get_flow_regs(SM5803_EMUL, &flow1, &flow2, &flow3); + zassert_equal(flow1, 0x01, "FLOW1 should be set for sinking, was %#x", + flow1); + + /* Disconnect source, causing charger to go to runtime LPM. */ + zassert_ok(tcpci_emul_disconnect_partner(tcpci_emul)); + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 24); + k_sleep(K_SECONDS(4)); + + zassert_true(sm5803_emul_is_clock_slowed(SM5803_EMUL)); + /* Sigma delta was disabled. */ + cc_conf1 = sm5803_emul_get_cc_config(SM5803_EMUL); + zassert_equal(sm5803_emul_get_cc_config(SM5803_EMUL), 0x01, + "actual value was %#x", cc_conf1); + /* + * Runtime LPM hook runs before the charge manager updates, so we expect + * the GPADCs to be left on because the charger is still set for sinking + * when it goes to runtime LPM. + */ + sm5803_emul_get_gpadc_conf(SM5803_EMUL, &gpadc1, &gpadc2); + zassert_equal(gpadc1, 0xf7, "actual value was %#x", gpadc1); + zassert_equal(gpadc2, 1, "actual value was %#x", gpadc2); + + /* + * Reconnect the source and inhibit charging, so GPADCs can be disabled + * when we disconnect it. + */ + zassert_ok(tcpci_partner_connect_to_tcpci(&partner, tcpci_emul)); + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 5010); + k_sleep(K_SECONDS(4)); + zassert_ok(charger_set_mode(CHARGE_FLAG_INHIBIT_CHARGE)); + zassert_ok(tcpci_emul_disconnect_partner(tcpci_emul)); + sm5803_emul_set_vbus_voltage(SM5803_EMUL, 0); + k_sleep(K_SECONDS(4)); + + /* This time LPM actually did disable the GPADCs. */ + sm5803_emul_get_gpadc_conf(SM5803_EMUL, &gpadc1, &gpadc2); + zassert_equal(gpadc1, 0, "actual value was %#x", gpadc1); + zassert_equal(gpadc2, 0, "actual value was %#x", gpadc2); +} + +ZTEST(sm5803, test_get_battery_cells) +{ + int cells; + + /* Default PMODE reports 2s */ + zassert_ok(sm5803_drv.get_battery_cells(CHARGER_NUM, &cells)); + zassert_equal(cells, 2); + + /* 3s PMODE is 3s */ + sm5803_emul_set_pmode(SM5803_EMUL, 0x14); + zassert_ok(sm5803_drv.get_battery_cells(CHARGER_NUM, &cells)); + zassert_equal(cells, 3); + + /* Unrecognized PMODE is an error */ + sm5803_emul_set_pmode(SM5803_EMUL, 0x1f); + zassert_not_equal(sm5803_drv.get_battery_cells(CHARGER_NUM, &cells), 0); + zassert_equal(cells, -1); + + /* Communication error bubbles up */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_main(SM5803_EMUL), + SM5803_REG_PLATFORM); + zassert_not_equal(sm5803_drv.get_battery_cells(CHARGER_NUM, &cells), 0); +} + +ZTEST(sm5803, test_gpio) +{ + /* Open drain output */ + zassert_ok(sm5803_configure_gpio0(CHARGER_NUM, GPIO0_MODE_OUTPUT, 1)); + zassert_equal(sm5803_emul_get_gpio_ctrl(SM5803_EMUL), 0x42); + /* Set output high, from default of low. */ + zassert_ok(sm5803_set_gpio0_level(CHARGER_NUM, 1)); + zassert_equal(sm5803_emul_get_gpio_ctrl(SM5803_EMUL), 0x43); + /* Set it low again. */ + zassert_ok(sm5803_set_gpio0_level(CHARGER_NUM, 0)); + zassert_equal(sm5803_emul_get_gpio_ctrl(SM5803_EMUL), 0x42); + + /* Push-pull prochot. */ + zassert_ok(sm5803_configure_gpio0(CHARGER_NUM, GPIO0_MODE_PROCHOT, 0)); + zassert_equal(sm5803_emul_get_gpio_ctrl(SM5803_EMUL), 0x00); + + /* CHG_DET output enable lives in this register too */ + zassert_ok(sm5803_configure_chg_det_od(CHARGER_NUM, 1)); + zassert_equal(sm5803_emul_get_gpio_ctrl(SM5803_EMUL), 0x80); + zassert_ok(sm5803_configure_chg_det_od(CHARGER_NUM, 0)); + zassert_equal(sm5803_emul_get_gpio_ctrl(SM5803_EMUL), 0x00); + + /* Communication errors bubble up */ + i2c_common_emul_set_read_fail_reg(sm5803_emul_get_i2c_main(SM5803_EMUL), + SM5803_REG_GPIO0_CTRL); + zassert_not_equal( + sm5803_configure_gpio0(CHARGER_NUM, GPIO0_MODE_INPUT, 0), 0); + zassert_not_equal(sm5803_set_gpio0_level(CHARGER_NUM, 0), 0); + zassert_not_equal(sm5803_configure_chg_det_od(CHARGER_NUM, 1), 0); +} diff --git a/zephyr/test/drivers/sm5803/src/usbc.c b/zephyr/test/drivers/sm5803/src/usbc.c new file mode 100644 index 0000000000..8028842a04 --- /dev/null +++ b/zephyr/test/drivers/sm5803/src/usbc.c @@ -0,0 +1,42 @@ +/* Copyright 2023 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "driver/charger/sm5803.h" +#include "driver/tcpm/tcpci.h" +#include "emul/emul_sm5803.h" +#include "test/drivers/charger_utils.h" + +#include <zephyr/drivers/emul.h> +#include <zephyr/drivers/gpio/gpio_emul.h> + +__override bool pd_check_vbus_level(int port, enum vbus_level level) +{ + return sm5803_check_vbus_level(port, level); +} + +static void pin_interrupt_handler(const struct device *gpio, + struct gpio_callback *const cb, + gpio_port_pins_t pins) +{ + sm5803_interrupt(get_charger_num(&sm5803_drv)); +} + +static int configure_charger_interrupt(void) +{ + const struct gpio_dt_spec *gpio = sm5803_emul_get_interrupt_gpio( + EMUL_DT_GET(DT_NODELABEL(sm5803_emul))); + static struct gpio_callback callback; + + if (!device_is_ready(gpio->port)) + k_oops(); + + gpio_emul_input_set(gpio->port, gpio->pin, 1); + gpio_pin_configure_dt(gpio, GPIO_INPUT | GPIO_ACTIVE_LOW); + gpio_init_callback(&callback, pin_interrupt_handler, BIT(gpio->pin)); + gpio_add_callback(gpio->port, &callback); + gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_EDGE_TO_ACTIVE); + + return 0; +} +SYS_INIT(configure_charger_interrupt, APPLICATION, 10); diff --git a/zephyr/test/drivers/testcase.yaml b/zephyr/test/drivers/testcase.yaml index fb8d9b8fdc..cb63d66fc8 100644 --- a/zephyr/test/drivers/testcase.yaml +++ b/zephyr/test/drivers/testcase.yaml @@ -338,6 +338,15 @@ tests: - CONFIG_PLATFORM_EC_RTC=y - CONFIG_PLATFORM_EC_HOSTCMD=y - CONFIG_PLATFORM_EC_HOSTCMD_RTC=y + drivers.sm5803: + extra_conf_files: + - prj.conf + - sm5803/prj.conf + extra_configs: + - CONFIG_LINK_TEST_SUITE_SM5803=y + extra_dtc_overlay_files: + - boards/native_posix.overlay + - sm5803/sm5803.dts drivers.system: tags: common system extra_configs: |