diff options
Diffstat (limited to 'zephyr/test/drivers/default/src/ppc_sn5s330.c')
-rw-r--r-- | zephyr/test/drivers/default/src/ppc_sn5s330.c | 689 |
1 files changed, 689 insertions, 0 deletions
diff --git a/zephyr/test/drivers/default/src/ppc_sn5s330.c b/zephyr/test/drivers/default/src/ppc_sn5s330.c new file mode 100644 index 0000000000..c9ba62cf20 --- /dev/null +++ b/zephyr/test/drivers/default/src/ppc_sn5s330.c @@ -0,0 +1,689 @@ +/* Copyright 2021 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/kernel.h> +#include <zephyr/device.h> +#include <zephyr/devicetree.h> +#include <zephyr/drivers/emul.h> +#include <zephyr/ztest.h> +#include <zephyr/fff.h> + +#include "driver/ppc/sn5s330.h" +#include "driver/ppc/sn5s330_public.h" +#include "emul/emul_common_i2c.h" +#include "emul/emul_sn5s330.h" +#include "usbc_ppc.h" +#include "test/drivers/test_mocks.h" +#include "test/drivers/test_state.h" + +/** This must match the index of the sn5s330 in ppc_chips[] */ +#define SN5S330_PORT 0 +#define EMUL EMUL_DT_GET(DT_NODELABEL(sn5s330_emul)) +#define COMMON_DATA emul_sn5s330_get_i2c_common_data(EMUL) +#define FUNC_SET1_ILIMPP1_MSK 0x1F +#define SN5S330_INTERRUPT_DELAYMS 15 + +FAKE_VOID_FUNC(sn5s330_emul_interrupt_set_stub); + +/* + * TODO(b/203364783): Exclude other threads from interacting with the emulator + * to avoid test flakiness + */ + +struct intercept_write_data { + int reg_to_intercept; + uint8_t val_intercepted; +}; + +struct intercept_read_data { + int reg_to_intercept; + bool replace_reg_val; + uint8_t replacement_val; +}; + +static int intercept_read_func(const struct emul *emul, int reg, uint8_t *val, + int bytes, void *data) +{ + struct intercept_read_data *test_data = data; + + if (test_data->reg_to_intercept && test_data->replace_reg_val) + *val = test_data->replacement_val; + + return EC_SUCCESS; +} + +static int intercept_write_func(const struct emul *emul, int reg, uint8_t val, + int bytes, void *data) +{ + struct intercept_write_data *test_data = data; + + if (test_data->reg_to_intercept == reg) + test_data->val_intercepted = val; + + return 1; +} + +static int fail_until_write_func(const struct emul *emul, int reg, uint8_t val, + int bytes, void *data) +{ + uint32_t *count = data; + + if (*count != 0) { + *count -= 1; + return -EIO; + } + return 1; +} + +ZTEST(ppc_sn5s330, test_fail_once_func_set1) +{ + const struct emul *emul = EMUL; + uint32_t count = 1; + uint8_t func_set1_value; + + i2c_common_emul_set_write_func(COMMON_DATA, fail_until_write_func, + &count); + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + zassert_equal(count, 0, NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET1, &func_set1_value); + zassert_true((func_set1_value & SN5S330_ILIM_1_62) != 0, NULL); + i2c_common_emul_set_write_func(COMMON_DATA, NULL, NULL); +} + +ZTEST(ppc_sn5s330, test_dead_battery_boot_force_pp2_fets_set) +{ + struct intercept_write_data test_write_data = { + .reg_to_intercept = SN5S330_FUNC_SET3, + .val_intercepted = 0, + }; + struct intercept_read_data test_read_data = { + .reg_to_intercept = SN5S330_INT_STATUS_REG4, + .replace_reg_val = true, + .replacement_val = SN5S330_DB_BOOT, + }; + + i2c_common_emul_set_write_func(COMMON_DATA, intercept_write_func, + &test_write_data); + i2c_common_emul_set_read_func(COMMON_DATA, intercept_read_func, + &test_read_data); + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + /* + * Although the device enables PP2_FET on dead battery boot by setting + * the PP2_EN bit, the driver also force sets this bit during dead + * battery boot by writing that bit to the FUNC_SET3 reg. + * + * TODO(b/207034759): Verify need or remove redundant PP2 set. + */ + zassert_true(test_write_data.val_intercepted & SN5S330_PP2_EN, NULL); + zassert_false(sn5s330_drv.is_sourcing_vbus(SN5S330_PORT), NULL); +} + +ZTEST(ppc_sn5s330, test_enter_low_power_mode) +{ + const struct emul *emul = EMUL; + + uint8_t func_set2_reg; + uint8_t func_set3_reg; + uint8_t func_set4_reg; + uint8_t func_set9_reg; + + /* + * Requirements were extracted from TI's recommended changes for octopus + * to lower power use during hibernate as well as the follow up changes + * we made to allow the device to wake up from hibernate. + * + * For Reference: b/111006203#comment35 + */ + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + zassert_ok(sn5s330_drv.enter_low_power_mode(SN5S330_PORT), NULL); + + /* 1) Verify VBUS power paths are off */ + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_equal(func_set3_reg & SN5S330_PP1_EN, 0, NULL); + zassert_equal(func_set3_reg & SN5S330_PP2_EN, 0, NULL); + + /* 2) Verify VCONN power path is off */ + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET4, &func_set4_reg); + zassert_not_equal(func_set4_reg & SN5S330_CC_EN, 0, NULL); + zassert_equal(func_set4_reg & SN5S330_VCONN_EN, 0, NULL); + + /* 3) Verify SBU FET is off */ + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET2, &func_set2_reg); + zassert_equal(func_set2_reg & SN5S330_SBU_EN, 0, NULL); + + /* 4) Verify VBUS and SBU OVP comparators are off */ + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET9, &func_set9_reg); + zassert_equal(func_set9_reg & SN5S330_FORCE_OVP_EN_SBU, 0, NULL); + zassert_equal(func_set9_reg & SN5S330_PWR_OVR_VBUS, 0, NULL); + zassert_not_equal(func_set9_reg & SN5S330_OVP_EN_CC, 0, NULL); + zassert_equal(func_set9_reg & SN5S330_FORCE_ON_VBUS_OVP, 0, NULL); + zassert_equal(func_set9_reg & SN5S330_FORCE_ON_VBUS_UVP, 0, NULL); +} + +ZTEST(ppc_sn5s330, test_vbus_source_sink_enable) +{ + const struct emul *emul = EMUL; + uint8_t func_set3_reg; + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + /* Test enable/disable VBUS source FET */ + zassert_ok(sn5s330_drv.vbus_source_enable(SN5S330_PORT, true), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_not_equal(func_set3_reg & SN5S330_PP1_EN, 0, NULL); + + zassert_ok(sn5s330_drv.vbus_source_enable(SN5S330_PORT, false), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_equal(func_set3_reg & SN5S330_PP1_EN, 0, NULL); + + /* Test enable/disable VBUS sink FET */ + zassert_ok(sn5s330_drv.vbus_sink_enable(SN5S330_PORT, true), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_not_equal(func_set3_reg & SN5S330_PP2_EN, 0, NULL); + + zassert_ok(sn5s330_drv.vbus_sink_enable(SN5S330_PORT, false), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_equal(func_set3_reg & SN5S330_PP2_EN, 0, NULL); +} + +/* This test depends on EC GIPO initialization happening before I2C */ +BUILD_ASSERT(CONFIG_PLATFORM_EC_GPIO_INIT_PRIORITY < CONFIG_I2C_INIT_PRIORITY, + "GPIO initialization must happen before I2C"); +ZTEST(ppc_sn5s330, test_vbus_discharge) +{ + const struct emul *emul = EMUL; + uint8_t func_set3_reg; + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + /* Test enable/disable VBUS discharging */ + zassert_ok(sn5s330_drv.discharge_vbus(SN5S330_PORT, true), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_not_equal(func_set3_reg & SN5S330_VBUS_DISCH_EN, 0, NULL); + + zassert_ok(sn5s330_drv.discharge_vbus(SN5S330_PORT, false), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET3, &func_set3_reg); + zassert_equal(func_set3_reg & SN5S330_VBUS_DISCH_EN, 0, NULL); +} + +ZTEST(ppc_sn5s330, test_set_vbus_source_current_limit) +{ + const struct emul *emul = EMUL; + uint8_t func_set1_reg; + + /* Test every TCPC Pull Resistance Value */ + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + /* USB */ + zassert_ok(sn5s330_drv.set_vbus_source_current_limit(SN5S330_PORT, + TYPEC_RP_USB), + NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET1, &func_set1_reg); + zassert_equal(func_set1_reg & FUNC_SET1_ILIMPP1_MSK, SN5S330_ILIM_0_63, + NULL); + + /* 1.5A */ + zassert_ok(sn5s330_drv.set_vbus_source_current_limit(SN5S330_PORT, + TYPEC_RP_1A5), + NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET1, &func_set1_reg); + zassert_equal(func_set1_reg & FUNC_SET1_ILIMPP1_MSK, SN5S330_ILIM_1_62, + NULL); + + /* 3.0A */ + zassert_ok(sn5s330_drv.set_vbus_source_current_limit(SN5S330_PORT, + TYPEC_RP_3A0), + NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET1, &func_set1_reg); + zassert_equal(func_set1_reg & FUNC_SET1_ILIMPP1_MSK, SN5S330_ILIM_3_06, + NULL); + + /* Unknown/Reserved - We set result as USB */ + zassert_ok(sn5s330_drv.set_vbus_source_current_limit(SN5S330_PORT, + TYPEC_RP_RESERVED), + NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET1, &func_set1_reg); + zassert_equal(func_set1_reg & FUNC_SET1_ILIMPP1_MSK, SN5S330_ILIM_0_63, + NULL); +} + +ZTEST(ppc_sn5s330, test_sn5s330_set_sbu) +#ifdef CONFIG_USBC_PPC_SBU +{ + const struct emul *emul = EMUL; + uint8_t func_set2_reg; + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + /* Verify driver enables SBU FET */ + zassert_ok(sn5s330_drv.set_sbu(SN5S330_PORT, true), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET2, &func_set2_reg); + zassert_not_equal(func_set2_reg & SN5S330_SBU_EN, 0, NULL); + + /* Verify driver disables SBU FET */ + zassert_ok(sn5s330_drv.set_sbu(SN5S330_PORT, false), NULL); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET2, &func_set2_reg); + zassert_equal(func_set2_reg & SN5S330_SBU_EN, 0, NULL); +} +#else +{ + ztest_test_skip(); +} +#endif /* CONFIG_USBC_PPC_SBU */ + +ZTEST(ppc_sn5s330, test_sn5s330_vbus_overcurrent) +{ + const struct emul *emul = EMUL; + uint8_t int_trip_rise_reg1; + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + sn5s330_emul_make_vbus_overcurrent(emul); + /* + * TODO(b/201420132): Replace arbitrary sleeps. + */ + /* Make sure interrupt happens first. */ + k_msleep(SN5S330_INTERRUPT_DELAYMS); + zassert_true(sn5s330_emul_interrupt_set_stub_fake.call_count > 0, NULL); + + /* + * Verify we cleared vbus overcurrent interrupt trip rise bit so the + * driver can detect future overcurrent clamping interrupts. + */ + sn5s330_emul_peek_reg(emul, SN5S330_INT_TRIP_RISE_REG1, + &int_trip_rise_reg1); + zassert_equal(int_trip_rise_reg1 & SN5S330_ILIM_PP1_MASK, 0, NULL); +} + +ZTEST(ppc_sn5s330, test_sn5s330_vbus_overcurrent_late_jump) +{ + const struct emul *emul = EMUL; + uint8_t int_trip_rise_reg1; + + /* Simulate the system jumping late. The second call to init() will + * skip certain interrupt setup work. Make sure the interrupt continues + * to function. + */ + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + system_jumped_late_fake.return_val = 1; + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + sn5s330_emul_make_vbus_overcurrent(emul); + /* + * TODO(b/201420132): Replace arbitrary sleeps. + */ + /* Make sure interrupt happens first. */ + k_msleep(SN5S330_INTERRUPT_DELAYMS); + zassert_true(sn5s330_emul_interrupt_set_stub_fake.call_count > 0, NULL); + + /* + * Verify we cleared vbus overcurrent interrupt trip rise bit so the + * driver can detect future overcurrent clamping interrupts. + */ + sn5s330_emul_peek_reg(emul, SN5S330_INT_TRIP_RISE_REG1, + &int_trip_rise_reg1); + zassert_equal(int_trip_rise_reg1 & SN5S330_ILIM_PP1_MASK, 0, NULL); +} + +ZTEST(ppc_sn5s330, test_sn5s330_disable_vbus_low_interrupt) +{ + const struct emul *emul = EMUL; + + /* Interrupt disabled here */ + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + /* Would normally cause a vbus low interrupt */ + sn5s330_emul_lower_vbus_below_minv(emul); + zassert_equal(sn5s330_emul_interrupt_set_stub_fake.call_count, 0, NULL); +} + +ZTEST(ppc_sn5s330, test_sn5s330_disable_vbus_low_interrupt_late_jump) +{ + const struct emul *emul = EMUL; + + /* Simulate the system jumping late. The second call to init() will + * skip certain interrupt setup work. Make sure the interrupt continues + * to function. + */ + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + system_jumped_late_fake.return_val = 1; + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + /* Would normally cause a vbus low interrupt */ + sn5s330_emul_lower_vbus_below_minv(emul); + zassert_equal(sn5s330_emul_interrupt_set_stub_fake.call_count, 0, NULL); +} + +ZTEST(ppc_sn5s330, test_sn5s330_set_vconn_fet) +{ + if (!IS_ENABLED(CONFIG_USBC_PPC_VCONN)) { + ztest_test_skip(); + return; + } + + const struct emul *emul = EMUL; + uint8_t func_set4_reg; + + zassert_ok(sn5s330_drv.init(SN5S330_PORT), NULL); + + sn5s330_drv.set_vconn(SN5S330_PORT, false); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET4, &func_set4_reg); + zassert_equal(func_set4_reg & SN5S330_VCONN_EN, 0, NULL); + + sn5s330_drv.set_vconn(SN5S330_PORT, true); + sn5s330_emul_peek_reg(emul, SN5S330_FUNC_SET4, &func_set4_reg); + zassert_not_equal(func_set4_reg & SN5S330_VCONN_EN, 0, NULL); +} + +/* Make an I2C emulator mock read func wrapped in FFF */ +FAKE_VALUE_FUNC(int, dump_read_fn, const struct emul *, int, uint8_t *, int, + void *); + +ZTEST(ppc_sn5s330, test_dump) +{ + int ret; + + /* Set up our fake read function to pass through to the real emul */ + RESET_FAKE(dump_read_fn); + dump_read_fn_fake.return_val = 1; + i2c_common_emul_set_read_func(COMMON_DATA, dump_read_fn, NULL); + + ret = sn5s330_drv.reg_dump(SN5S330_PORT); + + zassert_equal(EC_SUCCESS, ret, "Expected EC_SUCCESS, got %d", ret); + + /* Check that all the expected I2C reads were performed. */ + MOCK_ASSERT_I2C_READ(dump_read_fn, 0, SN5S330_FUNC_SET1); + MOCK_ASSERT_I2C_READ(dump_read_fn, 1, SN5S330_FUNC_SET2); + MOCK_ASSERT_I2C_READ(dump_read_fn, 2, SN5S330_FUNC_SET3); + MOCK_ASSERT_I2C_READ(dump_read_fn, 3, SN5S330_FUNC_SET4); + MOCK_ASSERT_I2C_READ(dump_read_fn, 4, SN5S330_FUNC_SET5); + MOCK_ASSERT_I2C_READ(dump_read_fn, 5, SN5S330_FUNC_SET6); + MOCK_ASSERT_I2C_READ(dump_read_fn, 6, SN5S330_FUNC_SET7); + MOCK_ASSERT_I2C_READ(dump_read_fn, 7, SN5S330_FUNC_SET8); + MOCK_ASSERT_I2C_READ(dump_read_fn, 8, SN5S330_FUNC_SET9); + MOCK_ASSERT_I2C_READ(dump_read_fn, 9, SN5S330_FUNC_SET10); + MOCK_ASSERT_I2C_READ(dump_read_fn, 10, SN5S330_FUNC_SET11); + MOCK_ASSERT_I2C_READ(dump_read_fn, 11, SN5S330_FUNC_SET12); + MOCK_ASSERT_I2C_READ(dump_read_fn, 12, SN5S330_INT_STATUS_REG1); + MOCK_ASSERT_I2C_READ(dump_read_fn, 13, SN5S330_INT_STATUS_REG2); + MOCK_ASSERT_I2C_READ(dump_read_fn, 14, SN5S330_INT_STATUS_REG3); + MOCK_ASSERT_I2C_READ(dump_read_fn, 15, SN5S330_INT_STATUS_REG4); + MOCK_ASSERT_I2C_READ(dump_read_fn, 16, SN5S330_INT_TRIP_RISE_REG1); + MOCK_ASSERT_I2C_READ(dump_read_fn, 17, SN5S330_INT_TRIP_RISE_REG2); + MOCK_ASSERT_I2C_READ(dump_read_fn, 18, SN5S330_INT_TRIP_RISE_REG3); + MOCK_ASSERT_I2C_READ(dump_read_fn, 19, SN5S330_INT_TRIP_FALL_REG1); + MOCK_ASSERT_I2C_READ(dump_read_fn, 20, SN5S330_INT_TRIP_FALL_REG2); + MOCK_ASSERT_I2C_READ(dump_read_fn, 21, SN5S330_INT_TRIP_FALL_REG3); + MOCK_ASSERT_I2C_READ(dump_read_fn, 22, SN5S330_INT_MASK_RISE_REG1); + MOCK_ASSERT_I2C_READ(dump_read_fn, 23, SN5S330_INT_MASK_RISE_REG2); + MOCK_ASSERT_I2C_READ(dump_read_fn, 24, SN5S330_INT_MASK_RISE_REG3); + MOCK_ASSERT_I2C_READ(dump_read_fn, 25, SN5S330_INT_MASK_FALL_REG1); + MOCK_ASSERT_I2C_READ(dump_read_fn, 26, SN5S330_INT_MASK_FALL_REG2); + MOCK_ASSERT_I2C_READ(dump_read_fn, 27, SN5S330_INT_MASK_FALL_REG3); +} + +enum i2c_operation { + I2C_WRITE, + I2C_READ, +}; + +#define INIT_I2C_FAIL_HELPER(COMMON_DATA, RW, REG) \ + do { \ + if ((RW) == I2C_READ) { \ + i2c_common_emul_set_read_fail_reg((COMMON_DATA), \ + (REG)); \ + i2c_common_emul_set_write_fail_reg( \ + (COMMON_DATA), I2C_COMMON_EMUL_NO_FAIL_REG); \ + } else if ((RW) == I2C_WRITE) { \ + i2c_common_emul_set_read_fail_reg( \ + (COMMON_DATA), I2C_COMMON_EMUL_NO_FAIL_REG); \ + i2c_common_emul_set_write_fail_reg((COMMON_DATA), \ + (REG)); \ + } else { \ + zassert_true(false, "Invalid I2C operation"); \ + } \ + zassert_equal( \ + EC_ERROR_INVAL, sn5s330_drv.init(SN5S330_PORT), \ + "Did not get EC_ERROR_INVAL when reg %s (0x%02x)" \ + "could not be %s", \ + #REG, (REG), ((RW) == I2C_READ) ? "read" : "written"); \ + } while (0) + +ZTEST(ppc_sn5s330, test_init_reg_fails) +{ + /* Fail on each of the I2C operations the init function does to ensure + * we get the correct return value. This includes operations made by + * clr_flags() and set_flags(). + */ + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET5); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_READ, SN5S330_FUNC_SET6); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET6); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET2); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET9); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET11); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_READ, SN5S330_FUNC_SET8); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET8); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_READ, SN5S330_FUNC_SET4); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET4); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_READ, SN5S330_FUNC_SET3); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET3); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_READ, SN5S330_FUNC_SET10); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_FUNC_SET10); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, SN5S330_INT_STATUS_REG4); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_MASK_RISE_REG1); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_MASK_FALL_REG1); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_MASK_RISE_REG2); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_MASK_FALL_REG2); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_MASK_RISE_REG3); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_MASK_FALL_REG3); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_READ, SN5S330_INT_STATUS_REG4); + + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_TRIP_RISE_REG1); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_TRIP_RISE_REG2); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_TRIP_RISE_REG3); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_TRIP_FALL_REG1); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_TRIP_FALL_REG2); + INIT_I2C_FAIL_HELPER(COMMON_DATA, I2C_WRITE, + SN5S330_INT_TRIP_FALL_REG3); +} + +static int pp_fet_test_mock_read_fn(const struct emul *emul, int reg, + uint8_t *val, int bytes, void *data) +{ + int *counter = data; + + zassert_true(counter, "data cannot be a NULL pointer"); + + /* Pretend to be in dead battery mode (needed for part 2 of the test) */ + if (reg == SN5S330_INT_STATUS_REG4) { + *val = SN5S330_DB_BOOT; + return 0; + } + + /* Fail if we try to read SN5S330_FUNC_SET3 after the counter hits 0 */ + if (reg == SN5S330_FUNC_SET3 && (*counter)-- <= 0) { + printf("Failing\n"); + return -EIO; + } + + return 1; +} + +ZTEST(ppc_sn5s330, test_pp_fet_enable_fail) +{ + /* We attempt to enable the PP (power path) FET at two points during + * the init function, constituting the second and third accesses to the + * FUNC_SET3 register. We need to allow the first N reads/writes to + * succeed to test failure handling of each call to + * sn5s330_pp_fet_enable(). The second call requires us to be in dead + * battery mode, which we take care of in the mock read function. + */ + + int counter; + int ret; + + i2c_common_emul_set_read_func(COMMON_DATA, pp_fet_test_mock_read_fn, + &counter); + + /* Allow only the first access to the reg to succeed. This tests the + * error handling of the first call to sn5s330_pp_fet_enable(). + */ + + counter = 1; + ret = sn5s330_drv.init(SN5S330_PORT); + + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); + + /* Allow only the first two accesses to succeed. This tests the error + * handling of the second call to sn5s330_pp_fet_enable(). + */ + + counter = 2; + ret = sn5s330_drv.init(SN5S330_PORT); + + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); +} + +ZTEST(ppc_sn5s330, test_set_polarity) +{ + int ret; + uint8_t reg_val; + + /* Ensure flags start cleared */ + sn5s330_emul_peek_reg(EMUL, SN5S330_FUNC_SET4, ®_val); + zassert_false(reg_val & SN5S330_CC_POLARITY, + "Polarity flags should not be set after reset."); + + /* Set polarity flags */ + ret = sn5s330_drv.set_polarity(SN5S330_PORT, 1); + zassert_equal(EC_SUCCESS, ret, "Expected EC_SUCCESS but got %d", ret); + + sn5s330_emul_peek_reg(EMUL, SN5S330_FUNC_SET4, ®_val); + zassert_true(reg_val & SN5S330_CC_POLARITY, + "Polarity flags should be set."); + + /* Clear polarity flags */ + ret = sn5s330_drv.set_polarity(SN5S330_PORT, 0); + zassert_equal(EC_SUCCESS, ret, "Expected EC_SUCCESS but got %d", ret); + + sn5s330_emul_peek_reg(EMUL, SN5S330_FUNC_SET4, ®_val); + zassert_false(reg_val & SN5S330_CC_POLARITY, + "Polarity flags should be cleared."); +} + +ZTEST(ppc_sn5s330, test_set_vbus_source_current_limit_fail) +{ + int ret; + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET1); + + ret = sn5s330_drv.set_vbus_source_current_limit(SN5S330_PORT, + TYPEC_RP_3A0); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); +} + +ZTEST(ppc_sn5s330, test_sn5s330_discharge_vbus_fail) +{ + int ret; + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET3); + + ret = sn5s330_drv.discharge_vbus(SN5S330_PORT, false); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); +} + +ZTEST(ppc_sn5s330, test_low_power_mode_fail) +{ + /* Test failed I2C operations in the enter low power mode function */ + + int ret; + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET3); + ret = sn5s330_drv.enter_low_power_mode(SN5S330_PORT); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET4); + ret = sn5s330_drv.enter_low_power_mode(SN5S330_PORT); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET2); + ret = sn5s330_drv.enter_low_power_mode(SN5S330_PORT); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET9); + ret = sn5s330_drv.enter_low_power_mode(SN5S330_PORT); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); +} + +ZTEST(ppc_sn5s330, test_sn5s330_set_vconn_fail) +{ + /* Test failed I2C operations in the set Vconn function */ + + int ret; + + i2c_common_emul_set_read_fail_reg(COMMON_DATA, SN5S330_FUNC_SET4); + ret = sn5s330_drv.set_vconn(SN5S330_PORT, 0); + zassert_equal(EC_ERROR_INVAL, ret, "Expected EC_ERROR_INVAL but got %d", + ret); +} + +static inline void reset_sn5s330_state(void) +{ + i2c_common_emul_set_write_func(COMMON_DATA, NULL, NULL); + i2c_common_emul_set_read_func(COMMON_DATA, NULL, NULL); + i2c_common_emul_set_write_fail_reg(COMMON_DATA, + I2C_COMMON_EMUL_NO_FAIL_REG); + i2c_common_emul_set_read_fail_reg(COMMON_DATA, + I2C_COMMON_EMUL_NO_FAIL_REG); + sn5s330_emul_reset(EMUL); + RESET_FAKE(sn5s330_emul_interrupt_set_stub); +} + +static void ppc_sn5s330_before(void *state) +{ + ARG_UNUSED(state); + reset_sn5s330_state(); +} + +static void ppc_sn5s330_after(void *state) +{ + ARG_UNUSED(state); + reset_sn5s330_state(); +} + +ZTEST_SUITE(ppc_sn5s330, drivers_predicate_post_main, NULL, ppc_sn5s330_before, + ppc_sn5s330_after, NULL); |