summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Marheine <pmarheine@chromium.org>2023-02-22 16:33:40 +1100
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-04-19 06:38:27 +0000
commitfd2415cda6a261b92390aad04829f46f33ffc9e6 (patch)
tree7b14526e80487c3897d062b184e6c1bc10f75b76
parent1b5cbe58be80e428aced08ac0692019822ddf81a (diff)
downloadchrome-ec-fd2415cda6a261b92390aad04829f46f33ffc9e6.tar.gz
zephyr/tests: emulator and tests for sm5803
This implements an emulator and some tests for the SM5803 charger. The current tests do not exercise the driver as comprehensively as intended, but those will be added in a later change. One discovered bug in the driver is fixed, where some interrupts were disabled immediately after being enabled. BUG=b:242544165 TEST=twister -ciC -s drivers/drivers.sm5803 BRANCH=nissa Change-Id: I9fc6abd26cd1b2e0cbc05b8e45cf94fc83abd08c Signed-off-by: Peter Marheine <pmarheine@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/4290087 Reviewed-by: Tristan Honscheid <honscheid@google.com>
-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: