summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--driver/charger/sm5803.c12
-rw-r--r--zephyr/dts/bindings/emul/cros,sm5803-emul.yaml34
-rw-r--r--zephyr/emul/CMakeLists.txt1
-rw-r--r--zephyr/emul/Kconfig1
-rw-r--r--zephyr/emul/Kconfig.sm580318
-rw-r--r--zephyr/emul/emul_sm5803.c722
-rw-r--r--zephyr/include/emul/emul_sm5803.h77
-rw-r--r--zephyr/test/drivers/CMakeLists.txt1
-rw-r--r--zephyr/test/drivers/Kconfig3
-rw-r--r--zephyr/test/drivers/sm5803/CMakeLists.txt5
-rw-r--r--zephyr/test/drivers/sm5803/prj.conf6
-rw-r--r--zephyr/test/drivers/sm5803/sm5803.dts32
-rw-r--r--zephyr/test/drivers/sm5803/src/sm5803.c709
-rw-r--r--zephyr/test/drivers/sm5803/src/usbc.c42
-rw-r--r--zephyr/test/drivers/testcase.yaml9
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);
- 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: