summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Marheine <pmarheine@chromium.org>2023-03-31 10:35:01 +1100
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-04-06 01:19:48 +0000
commit6963113b6ae438a6a84ba3b19e548b401df3aae1 (patch)
tree16126e187cf795974f6157ce8b40560d997c4d77
parent76c0324c1b504f269fee877b0e5eb662092d2085 (diff)
downloadchrome-ec-6963113b6ae438a6a84ba3b19e548b401df3aae1.tar.gz
nissa: test project-level sub_board.c
This defines a Nissa-like board for unit testing and adds tests to exercise the project's sub_board.c, in particular that GPIO configuration is correct. A small change is made to make it possible for the tests to reset some global values that are set by board code on initialization; there is no functional change to board code. BUG=b:271118112 TEST=./twister -ciC -T zephyr/test/nissa BRANCH=nissa Change-Id: I8edfddbf663761f5c72c4d680cc056ee7d46d2f8 Signed-off-by: Peter Marheine <pmarheine@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/4402751 Reviewed-by: Tristan Honscheid <honscheid@google.com>
-rw-r--r--zephyr/program/nissa/src/sub_board.c23
-rw-r--r--zephyr/test/nissa/CMakeLists.txt17
-rw-r--r--zephyr/test/nissa/Kconfig9
-rw-r--r--zephyr/test/nissa/README.md3
-rw-r--r--zephyr/test/nissa/boards/generic_npcx.dts179
-rw-r--r--zephyr/test/nissa/prj.conf24
-rw-r--r--zephyr/test/nissa/src/stubs.c27
-rw-r--r--zephyr/test/nissa/src/sub_board.c245
-rw-r--r--zephyr/test/nissa/testcase.yaml10
9 files changed, 526 insertions, 11 deletions
diff --git a/zephyr/program/nissa/src/sub_board.c b/zephyr/program/nissa/src/sub_board.c
index 9c86c9422c..db6788de96 100644
--- a/zephyr/program/nissa/src/sub_board.c
+++ b/zephyr/program/nissa/src/sub_board.c
@@ -19,7 +19,6 @@
#include "usbc/usb_muxes.h"
#include <zephyr/drivers/gpio.h>
-#include <zephyr/drivers/pinctrl.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
@@ -39,55 +38,56 @@ __override uint8_t board_get_usb_pd_port_count(void)
return cached_usb_pd_port_count;
}
+test_export_static enum nissa_sub_board_type nissa_cached_sub_board =
+ NISSA_SB_UNKNOWN;
/*
* Retrieve sub-board type from FW_CONFIG.
*/
enum nissa_sub_board_type nissa_get_sb_type(void)
{
- static enum nissa_sub_board_type sb = NISSA_SB_UNKNOWN;
int ret;
uint32_t val;
/*
* Return cached value.
*/
- if (sb != NISSA_SB_UNKNOWN)
- return sb;
+ if (nissa_cached_sub_board != NISSA_SB_UNKNOWN)
+ return nissa_cached_sub_board;
- sb = NISSA_SB_NONE; /* Defaults to none */
+ nissa_cached_sub_board = NISSA_SB_NONE; /* Defaults to none */
ret = cros_cbi_get_fw_config(FW_SUB_BOARD, &val);
if (ret != 0) {
LOG_WRN("Error retrieving CBI FW_CONFIG field %d",
FW_SUB_BOARD);
- return sb;
+ return nissa_cached_sub_board;
}
switch (val) {
default:
LOG_WRN("No sub-board defined");
break;
case FW_SUB_BOARD_1:
- sb = NISSA_SB_C_A;
+ nissa_cached_sub_board = NISSA_SB_C_A;
LOG_INF("SB: USB type C, USB type A");
break;
case FW_SUB_BOARD_2:
- sb = NISSA_SB_C_LTE;
+ nissa_cached_sub_board = NISSA_SB_C_LTE;
LOG_INF("SB: USB type C, WWAN LTE");
break;
case FW_SUB_BOARD_3:
- sb = NISSA_SB_HDMI_A;
+ nissa_cached_sub_board = NISSA_SB_HDMI_A;
LOG_INF("SB: HDMI, USB type A");
break;
}
- return sb;
+ return nissa_cached_sub_board;
}
/*
* Initialise the USB PD port count, which
* depends on which sub-board is attached.
*/
-static void board_usb_pd_count_init(void)
+test_export_static void board_usb_pd_count_init(void)
{
switch (nissa_get_sb_type()) {
default:
@@ -181,6 +181,7 @@ __overridable void nissa_configure_hdmi_power_gpios(void)
*/
#define I2C4_NODE DT_NODELABEL(i2c4)
#if DT_NODE_EXISTS(I2C4_NODE)
+#include <zephyr/drivers/pinctrl.h>
PINCTRL_DT_DEFINE(I2C4_NODE);
/* disable i2c4 alternate function */
diff --git a/zephyr/test/nissa/CMakeLists.txt b/zephyr/test/nissa/CMakeLists.txt
new file mode 100644
index 0000000000..dc351aa3b6
--- /dev/null
+++ b/zephyr/test/nissa/CMakeLists.txt
@@ -0,0 +1,17 @@
+# 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.
+
+cmake_minimum_required(VERSION 3.13.1)
+find_package(Zephyr REQUIRED HINTS "${ZEPHYR_BASE}")
+project(nissa)
+
+add_subdirectory(${PLATFORM_EC}/zephyr/test/test_utils test_utils)
+
+zephyr_include_directories("${PLATFORM_EC_PROGRAM_DIR}/nissa/include")
+
+target_sources(app PRIVATE src/stubs.c src/sub_board.c)
+
+target_sources(app PRIVATE
+ ${PLATFORM_EC_PROGRAM_DIR}/nissa/src/sub_board.c
+ ${PLATFORM_EC_PROGRAM_DIR}/nissa/src/common.c)
diff --git a/zephyr/test/nissa/Kconfig b/zephyr/test/nissa/Kconfig
new file mode 100644
index 0000000000..f8215bbf37
--- /dev/null
+++ b/zephyr/test/nissa/Kconfig
@@ -0,0 +1,9 @@
+# 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.
+
+module = NISSA
+module-str = Nissa board-specific code (unit tests)
+source "subsys/logging/Kconfig.template.log_config"
+
+source "Kconfig.zephyr"
diff --git a/zephyr/test/nissa/README.md b/zephyr/test/nissa/README.md
new file mode 100644
index 0000000000..17737fcba5
--- /dev/null
+++ b/zephyr/test/nissa/README.md
@@ -0,0 +1,3 @@
+Tests for board specific code under `zephyr/program/nissa/`
+
+Run with ./twister -T zephyr/test/nissa
diff --git a/zephyr/test/nissa/boards/generic_npcx.dts b/zephyr/test/nissa/boards/generic_npcx.dts
new file mode 100644
index 0000000000..1639dc80c8
--- /dev/null
+++ b/zephyr/test/nissa/boards/generic_npcx.dts
@@ -0,0 +1,179 @@
+/* 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.
+ *
+ * Device tree for a generic Nissa device based on a NPCX EC, suitable for board
+ * unit tests.
+ */
+#include <npcx_emul.dts>
+#include "../program/nissa/cbi.dtsi"
+
+/ {
+ aliases {
+ /* type-C */
+ gpio-usb-c1-int-odl = &gpio_sb_1;
+ /* type-A */
+ gpio-en-usb-a1-vbus = &gpio_sb_2;
+ /* HDMI */
+ gpio-en-rails-odl = &gpio_sb_1;
+ gpio-hdmi-en-odl = &gpio_sb_3;
+ gpio-hpd-odl = &gpio_sb_4;
+ /* LTE */
+ gpio-en-sub-s5-rails = &gpio_sb_2;
+ };
+
+ gpio-interrupts {
+ compatible = "cros-ec,gpio-interrupts";
+
+ int_usb_c0: usb_c0 {
+ irq-pin = <&gpio_usb_c0_int_odl>;
+ flags = <GPIO_INT_EDGE_FALLING>;
+ handler = "usb_interrupt_c0";
+ };
+
+ int_usb_c1: usb_c1 {
+ irq-pin = <&gpio_sb_1>;
+ flags = <GPIO_INT_EDGE_FALLING>;
+ handler = "usb_interrupt_c1";
+ };
+ };
+
+ named-gpios {
+ compatible = "named-gpios";
+
+ gpio_ec_soc_dsw_pwrok: ec_soc_dsw_pwrok {
+ gpios = <&gpio6 1 GPIO_OUTPUT>;
+ };
+
+ gpio_ec_soc_hdmi_hpd: ec_soc_hdmi_hpd {
+ gpios = <&gpioe 4 GPIO_OUTPUT>;
+ };
+
+ entering_rw {
+ gpios = <&gpio0 3 GPIO_OUTPUT>;
+ enum-name = "GPIO_ENTERING_RW";
+ };
+
+ gpio_en_pp5000_pen_x: en_pp5000_pen_x {
+ gpios = <&gpioe 2 GPIO_OUTPUT>;
+ };
+
+ gpio_en_slp_z: en_slp_z {
+ gpios = <&gpioe 1 GPIO_OUTPUT>;
+ };
+
+ gpio_hdmi_sel: hdmi_sel {
+ gpios = <&gpioc 6 GPIO_OUTPUT>;
+ };
+
+ gpio_usb_a0_enable: usb_a0_enable {
+ gpios = <&gpio1 0 GPIO_OUTPUT>;
+ };
+
+ gpio_usb_a0_ilimit_sdp: usb_a0_ilimit_sdp {
+ gpios = <&gpio1 2 GPIO_OUTPUT>;
+ enum-name = "GPIO_USB1_ILIM_SEL";
+ };
+
+ gpio_sub_usb_a1_ilimit_sdp: usb_a1_ilimit_sdp {
+ gpios = <&gpio1 3 GPIO_OUTPUT>;
+ enum-name = "GPIO_USB2_ILIM_SEL";
+ };
+
+ gpio_usb_c0_int_odl: usb_c0_int_odl {
+ gpios = <&gpio0 1 GPIO_INPUT_PULL_UP>;
+ };
+
+ gpio_sb_1: sb-1 {
+ gpios = <&gpio0 2 GPIO_PULL_UP>;
+ no-auto-init;
+ };
+
+ gpio_sb_2: sb-2 {
+ gpios = <&gpiod 4 GPIO_OUTPUT>;
+ no-auto-init;
+ };
+
+ gpio_sb_3: sb-3 {
+ gpios = <&gpiof 4 0>;
+ no-auto-init;
+ };
+
+ gpio_sb_4: sb-4 {
+ gpios = <&gpiof 5 GPIO_INPUT>;
+ no-auto-init;
+ };
+ };
+
+ named-i2c-ports {
+ compatible = "named-i2c-ports";
+ i2c_ec_i2c_usb_c0: ec_i2c_usb_c0 {
+ i2c-port = <&i2c_ctrl0>;
+ enum-names = "I2C_PORT_USB_C0_TCPC";
+ };
+ i2c_ec_i2c_sub_usb_c1: ec_i2c_sub_usb_c1 {
+ i2c-port = <&i2c_ctrl1>;
+ enum-names = "I2C_PORT_USB_C1_TCPC";
+ };
+ };
+
+ usba {
+ compatible = "cros-ec,usba-port-enable-pins";
+ enable-pins = <&gpio_usb_a0_enable &gpio_sb_2>;
+ status = "okay";
+ };
+
+ usbc {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port0@0 {
+ compatible = "named-usbc-port";
+ reg = <0>;
+ tcpc = <&tcpci_emul_0>;
+ usb-mux-chain-0 {
+ compatible = "cros-ec,usb-mux-chain";
+ usb-muxes = <&virtual_mux_0>;
+ };
+ };
+ port1@1 {
+ compatible = "named-usbc-port";
+ reg = <1>;
+ tcpc = <&tcpci_emul_1>;
+ usb-mux-chain-1 {
+ compatible = "cros-ec,usb-mux-chain";
+ usb-muxes = <&virtual_mux_1>;
+ };
+ usb_mux_chain_1_no_mux: usb-mux-chain-1-alternate {
+ compatible = "cros-ec,usb-mux-chain";
+ alternative-chain;
+ usb-muxes = <&virtual_mux_1>;
+ };
+ };
+ port0-muxes {
+ virtual_mux_0: virtual-mux-0 {
+ compatible = "cros-ec,usbc-mux-virtual";
+ };
+ };
+ port1-muxes {
+ virtual_mux_1: virtual-mux-1 {
+ compatible = "cros-ec,usbc-mux-virtual";
+ };
+ };
+ };
+};
+
+&i2c_ctrl0 {
+ tcpci_emul_0: tcpci_emul@82 {
+ compatible = "cros,tcpci-generic-emul";
+ status = "okay";
+ reg = <0x82>;
+ };
+};
+
+&i2c_ctrl1 {
+ tcpci_emul_1: tcpci_emul@82 {
+ compatible = "cros,tcpci-generic-emul";
+ status = "okay";
+ reg = <0x82>;
+ };
+};
diff --git a/zephyr/test/nissa/prj.conf b/zephyr/test/nissa/prj.conf
new file mode 100644
index 0000000000..3d4f0e5f3d
--- /dev/null
+++ b/zephyr/test/nissa/prj.conf
@@ -0,0 +1,24 @@
+# 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_ZTEST=y
+CONFIG_ZTEST_ASSERT_VERBOSE=1
+CONFIG_ZTEST_NEW_API=y
+CONFIG_ASSERT=y
+
+CONFIG_CROS_EC=y
+CONFIG_PLATFORM_EC=y
+
+# Configuration used for Nissa boards, with some items that aren't relevant
+# disabled so they don't depend on us defining items they want.
+CONFIG_PLATFORM_EC_USBC=y
+CONFIG_PLATFORM_EC_USBC_PPC=n
+CONFIG_PLATFORM_EC_USB_MUX_RUNTIME_CONFIG=y
+CONFIG_PLATFORM_EC_USB_PD_DISCHARGE=n
+CONFIG_PLATFORM_EC_USB_CHARGER=n
+CONFIG_PLATFORM_EC_USB_PD_HOST_CMD=n
+CONFIG_PLATFORM_EC_USB_PORT_ENABLE_DYNAMIC=y
+
+# Allow the test fixture to use k_malloc
+CONFIG_HEAP_MEM_POOL_SIZE=1024 \ No newline at end of file
diff --git a/zephyr/test/nissa/src/stubs.c b/zephyr/test/nissa/src/stubs.c
new file mode 100644
index 0000000000..970232197e
--- /dev/null
+++ b/zephyr/test/nissa/src/stubs.c
@@ -0,0 +1,27 @@
+/* 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.
+ *
+ * Function stubs needed for building Nissa board code, but that aren't
+ * meaningful to testing.
+ */
+
+#include "common.h"
+
+__overridable void pd_power_supply_reset(int port)
+{
+}
+
+__overridable int pd_set_power_supply_ready(int port)
+{
+ return 0;
+}
+
+__overridable void pd_set_input_current_limit(int port, uint32_t max_ma,
+ uint32_t supply_voltage)
+{
+}
+
+__overridable void usb_interrupt_c0(enum gpio_signal signal)
+{
+}
diff --git a/zephyr/test/nissa/src/sub_board.c b/zephyr/test/nissa/src/sub_board.c
new file mode 100644
index 0000000000..78b5b19143
--- /dev/null
+++ b/zephyr/test/nissa/src/sub_board.c
@@ -0,0 +1,245 @@
+/* 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.
+ *
+ * Unit tests for program/nissa/src/sub_board.c.
+ */
+#include "cros_cbi.h"
+#include "hooks.h"
+#include "nissa_hdmi.h"
+#include "nissa_sub_board.h"
+#include "usb_pd.h"
+
+#include <zephyr/drivers/gpio/gpio_emul.h>
+#include <zephyr/fff.h>
+#include <zephyr/kernel.h>
+#include <zephyr/ztest.h>
+
+#include <ap_power/ap_power.h>
+#include <ap_power/ap_power_events.h>
+
+FAKE_VALUE_FUNC(int, cros_cbi_get_fw_config, enum cbi_fw_config_field_id,
+ uint32_t *);
+FAKE_VOID_FUNC(usb_interrupt_c1, enum gpio_signal);
+
+/*
+ * Private bits of board code that are visible for testing.
+ *
+ * The cached sub-board ID needs to be cleared by tests so we can run multiple
+ * tests per process, and usb_pd_count_init() needs to run following each update
+ * of reported sub-board.
+ */
+extern enum nissa_sub_board_type nissa_cached_sub_board;
+void board_usb_pd_count_init(void);
+
+/* Shim GPIO initialization from devicetree. */
+int init_gpios(const struct device *unused);
+
+static uint32_t fw_config_value;
+
+/** Set the value of the CBI fw_config field returned by the fake. */
+static void set_fw_config_value(uint32_t value)
+{
+ fw_config_value = value;
+ board_usb_pd_count_init();
+}
+
+/** Custom fake for cros_cgi_get_fw_config(). */
+static int get_fake_fw_config_field(enum cbi_fw_config_field_id field_id,
+ uint32_t *value)
+{
+ *value = fw_config_value;
+ return 0;
+}
+
+#define ASSERT_GPIO_FLAGS(spec, expected) \
+ do { \
+ gpio_flags_t flags; \
+ zassert_ok(gpio_emul_flags_get((spec)->port, (spec)->pin, \
+ &flags)); \
+ zassert_equal(flags, expected, \
+ "actual value was %#x; expected %#x", flags, \
+ expected); \
+ } while (0)
+
+static int get_gpio_output(const struct gpio_dt_spec *const spec)
+{
+ return gpio_emul_output_get(spec->port, spec->pin);
+}
+
+struct nissa_sub_board_fixture {
+ const struct gpio_dt_spec *sb_1;
+ const struct gpio_dt_spec *sb_2;
+ const struct gpio_dt_spec *sb_3;
+ const struct gpio_dt_spec *sb_4;
+};
+
+static void *suite_setup_fn()
+{
+ struct nissa_sub_board_fixture *fixture =
+ k_malloc(sizeof(struct nissa_sub_board_fixture));
+
+ zassume_not_null(fixture);
+ fixture->sb_1 = GPIO_DT_FROM_NODELABEL(gpio_sb_1);
+ fixture->sb_2 = GPIO_DT_FROM_NODELABEL(gpio_sb_2);
+ fixture->sb_3 = GPIO_DT_FROM_NODELABEL(gpio_sb_3);
+ fixture->sb_4 = GPIO_DT_FROM_NODELABEL(gpio_sb_4);
+
+ return fixture;
+}
+
+static void test_before_fn(void *fixture_)
+{
+ struct nissa_sub_board_fixture *fixture = fixture_;
+
+ /* Reset cached global state. */
+ nissa_cached_sub_board = NISSA_SB_UNKNOWN;
+ fw_config_value = -1;
+
+ /* Return the fake fw_config value. */
+ RESET_FAKE(cros_cbi_get_fw_config);
+ cros_cbi_get_fw_config_fake.custom_fake = get_fake_fw_config_field;
+
+ /* Unconfigure sub-board GPIOs. */
+ gpio_pin_configure_dt(fixture->sb_1, GPIO_DISCONNECTED);
+ gpio_pin_configure_dt(fixture->sb_2, GPIO_DISCONNECTED);
+ gpio_pin_configure_dt(fixture->sb_3, GPIO_DISCONNECTED);
+ gpio_pin_configure_dt(fixture->sb_4, GPIO_DISCONNECTED);
+ /* Reset C1 interrupt to deasserted. */
+ gpio_emul_input_set(fixture->sb_1->port, fixture->sb_1->pin, 1);
+
+ RESET_FAKE(usb_interrupt_c1);
+}
+
+ZTEST_SUITE(nissa_sub_board, NULL, suite_setup_fn, test_before_fn, NULL, NULL);
+
+ZTEST_F(nissa_sub_board, test_usb_c_a)
+{
+ /* Set the sub-board, reported configuration is correct. */
+ set_fw_config_value(FW_SUB_BOARD_1);
+ zassert_equal(nissa_get_sb_type(), NISSA_SB_C_A);
+ zassert_equal(board_get_usb_pd_port_count(), 2);
+
+ /* Should have fetched CBI exactly once, asking for the sub-board. */
+ zassert_equal(cros_cbi_get_fw_config_fake.call_count, 1);
+ zassert_equal(cros_cbi_get_fw_config_fake.arg0_history[0],
+ FW_SUB_BOARD);
+
+ /* Run IO configuration in init. */
+ init_gpios(NULL);
+ hook_notify(HOOK_INIT);
+
+ /* Check that the sub-board GPIOs are configured correctly. */
+ ASSERT_GPIO_FLAGS(fixture->sb_2 /* A1 VBUS enable */, GPIO_OUTPUT);
+ ASSERT_GPIO_FLAGS(fixture->sb_1 /* C1 interrupt */,
+ GPIO_INPUT | GPIO_PULL_UP | GPIO_INT_EDGE_FALLING);
+
+ /* USB-C1 interrupt is handled. */
+ RESET_FAKE(usb_interrupt_c1);
+ gpio_emul_input_set(fixture->sb_1->port, fixture->sb_1->pin, 0);
+ zassert_equal(usb_interrupt_c1_fake.call_count, 1,
+ "usb_interrupt was called %d times",
+ usb_interrupt_c1_fake.call_count);
+}
+
+ZTEST_F(nissa_sub_board, test_usb_c_lte)
+{
+ set_fw_config_value(FW_SUB_BOARD_2);
+ zassert_equal(nissa_get_sb_type(), NISSA_SB_C_LTE);
+ zassert_equal(board_get_usb_pd_port_count(), 2);
+
+ init_gpios(NULL);
+ hook_notify(HOOK_INIT);
+
+ /* GPIOs are configured as expected. */
+ ASSERT_GPIO_FLAGS(fixture->sb_2 /* Standby power enable */,
+ GPIO_OUTPUT | GPIO_OUTPUT_INIT_LOW);
+ ASSERT_GPIO_FLAGS(fixture->sb_1 /* C1 interrupt */,
+ GPIO_INPUT | GPIO_PULL_UP | GPIO_INT_EDGE_FALLING);
+
+ /* USB interrupt is handled. */
+ RESET_FAKE(usb_interrupt_c1);
+ gpio_emul_input_set(fixture->sb_1->port, fixture->sb_1->pin, 0);
+ zassert_equal(usb_interrupt_c1_fake.call_count, 1,
+ "usb_interrupt was called %d times",
+ usb_interrupt_c1_fake.call_count);
+
+ /* LTE power gets enabled on S5. */
+ ap_power_ev_send_callbacks(AP_POWER_PRE_INIT);
+ zassert_equal(get_gpio_output(fixture->sb_2), 1);
+ /* And disabled on G3. */
+ ap_power_ev_send_callbacks(AP_POWER_HARD_OFF);
+ zassert_equal(get_gpio_output(fixture->sb_2), 0);
+}
+
+ZTEST_F(nissa_sub_board, test_usb_a_hdmi)
+{
+ set_fw_config_value(FW_SUB_BOARD_3);
+ zassert_equal(nissa_get_sb_type(), NISSA_SB_HDMI_A);
+ zassert_equal(board_get_usb_pd_port_count(), 1);
+
+ init_gpios(NULL);
+ hook_notify(HOOK_INIT);
+
+ /* USB-A controls are enabled. */
+ ASSERT_GPIO_FLAGS(fixture->sb_2 /* A1 VBUS enable */, GPIO_OUTPUT);
+
+ /*
+ * HDMI IOs configured as expected. The HDMI power enable and DDC select
+ * pins are impossible to test because emulated GPIOs don't support
+ * open-drain mode, so this only checks HPD.
+ */
+ ASSERT_GPIO_FLAGS(fixture->sb_4,
+ GPIO_INPUT | GPIO_ACTIVE_LOW | GPIO_INT_EDGE_BOTH);
+
+ /* Power events adjust HDMI port power as expected. */
+ ap_power_ev_send_callbacks(AP_POWER_PRE_INIT);
+ zassert_equal(get_gpio_output(GPIO_DT_FROM_NODELABEL(gpio_hdmi_sel)),
+ 1);
+ ap_power_ev_send_callbacks(AP_POWER_STARTUP);
+ ap_power_ev_send_callbacks(AP_POWER_SHUTDOWN);
+ ap_power_ev_send_callbacks(AP_POWER_HARD_OFF);
+ zassert_equal(get_gpio_output(GPIO_DT_FROM_NODELABEL(gpio_hdmi_sel)),
+ 0);
+
+ /* HPD input gets copied through to the output, and inverted. */
+ gpio_emul_input_set(fixture->sb_4->port, fixture->sb_4->pin, 1);
+ zassert_equal(
+ get_gpio_output(GPIO_DT_FROM_NODELABEL(gpio_ec_soc_hdmi_hpd)),
+ 0);
+ gpio_emul_input_set(fixture->sb_4->port, fixture->sb_4->pin, 0);
+ zassert_equal(
+ get_gpio_output(GPIO_DT_FROM_NODELABEL(gpio_ec_soc_hdmi_hpd)),
+ 1);
+}
+
+ZTEST(nissa_sub_board, test_unset_board)
+{
+ /* fw_config with an unset sub-board means none is present. */
+ set_fw_config_value(0);
+ zassert_equal(nissa_get_sb_type(), NISSA_SB_NONE);
+ zassert_equal(board_get_usb_pd_port_count(), 1);
+}
+
+static int get_fw_config_error(enum cbi_fw_config_field_id field,
+ uint32_t *value)
+{
+ return EC_ERROR_UNKNOWN;
+}
+
+ZTEST(nissa_sub_board, test_cbi_error)
+{
+ /*
+ * Reading fw_config from CBI returns an error, so sub-board is treated
+ * as absent.
+ */
+ cros_cbi_get_fw_config_fake.custom_fake = get_fw_config_error;
+ zassert_equal(nissa_get_sb_type(), NISSA_SB_NONE);
+}
+
+/** Override default HDMI configuration to exercise the power enable as well. */
+__override void nissa_configure_hdmi_power_gpios(void)
+{
+ nissa_configure_hdmi_rails();
+ nissa_configure_hdmi_vcc();
+}
diff --git a/zephyr/test/nissa/testcase.yaml b/zephyr/test/nissa/testcase.yaml
new file mode 100644
index 0000000000..c9b568e326
--- /dev/null
+++ b/zephyr/test/nissa/testcase.yaml
@@ -0,0 +1,10 @@
+# Copyright 2022 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+common:
+ platform_allow: native_posix
+tests:
+ nissa.sub_board:
+ extra_dtc_overlay_files:
+ - "boards/generic_npcx.dts"