usb: add support for board control over usb requests

Add support for controlling the board function through USB vendor
request, add an initial system for instantiating callbacks, implement
the basic power reset and boot control codes and a sample script to
flash npcx chips.

BUG=none
TEST=./flash_npcx.py

Change-Id: I4bfe0b4546afc4d777db379f09e3ec3ff061891b
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec-aic-tester/+/6182268
Reviewed-by: Keith Short <keithshort@chromium.org>
Tested-by: Fabio Baltieri <fabiobaltieri@google.com>
Commit-Queue: Keith Short <keithshort@chromium.org>
diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt
index bdc64d7..cd6e142 100644
--- a/firmware/CMakeLists.txt
+++ b/firmware/CMakeLists.txt
@@ -22,3 +22,5 @@
 target_sources_ifdef(CONFIG_DT_HAS_CROS_NPCX_BOOT_ENABLED app PRIVATE src/npcx_boot.c)
 target_sources_ifdef(CONFIG_I2C_EEPROM_TARGET app PRIVATE src/i2c_target.c)
 target_sources_ifdef(CONFIG_DT_HAS_CROS_I2C_MUX_ENABLED app PRIVATE src/i2c_mux.c)
+
+zephyr_linker_sources(ROM_SECTIONS src/usb_request.ld)
diff --git a/firmware/src/ec.c b/firmware/src/ec.c
index fd537d7..07285e4 100644
--- a/firmware/src/ec.c
+++ b/firmware/src/ec.c
@@ -6,6 +6,7 @@
 #define DT_DRV_COMPAT cros_aic_pins
 
 #include "ec.h"
+#include "usb_request.h"
 
 #include <zephyr/drivers/gpio.h>
 #include <zephyr/drivers/regulator.h>
@@ -203,6 +204,22 @@
 
 SHELL_CMD_REGISTER(ec, &ec_cmds, "EC control commands", NULL);
 
+static void ec_cb(uint16_t index, uint16_t value)
+{
+	switch (index) {
+	case USB_REQ_EC_RESET:
+		LOG_INF("usb ec reset");
+		ec_reset();
+		break;
+	case USB_REQ_POWER:
+		LOG_INF("usb ec power %d", value);
+		ec_power(value);
+		break;
+	}
+}
+
+USB_REQUEST_CALLBACK_DEFINE(ec_cb);
+
 static int ec_init(void)
 {
 	int ret;
diff --git a/firmware/src/npcx_boot.c b/firmware/src/npcx_boot.c
index fdad34e..c65b952 100644
--- a/firmware/src/npcx_boot.c
+++ b/firmware/src/npcx_boot.c
@@ -6,12 +6,14 @@
 #define DT_DRV_COMPAT cros_npcx_boot
 
 #include "ec.h"
+#include "usb_request.h"
 
 #include <zephyr/drivers/gpio.h>
 #include <zephyr/drivers/pinctrl.h>
 #include <zephyr/kernel.h>
 #include <zephyr/logging/log.h>
 #include <zephyr/shell/shell.h>
+#include <zephyr/shell/shell_uart.h>
 
 LOG_MODULE_REGISTER(npcx_boot, LOG_LEVEL_INF);
 
@@ -62,3 +64,18 @@
 }
 
 SHELL_CMD_REGISTER(npcx_boot, NULL, "Enter NPCX bootloader", cmd_npcx_boot);
+
+static void npcx_boot_cb(uint16_t index, uint16_t value)
+{
+	const struct shell *sh = shell_backend_uart_get_ptr();
+
+	if (index != USB_REQ_NPCX_BOOT) {
+		return;
+	}
+
+	LOG_INF("npcx boot");
+
+	cmd_npcx_boot(sh, 0, NULL);
+}
+
+USB_REQUEST_CALLBACK_DEFINE(npcx_boot_cb);
diff --git a/firmware/src/usb_request.h b/firmware/src/usb_request.h
new file mode 100644
index 0000000..f281fea
--- /dev/null
+++ b/firmware/src/usb_request.h
@@ -0,0 +1,20 @@
+/* Copyright 2025 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdint.h>
+
+#define USB_REQ_EC_RESET 0
+#define USB_REQ_POWER 1
+#define USB_REQ_NPCX_BOOT 2
+
+struct usb_request_callback {
+	void (*callback)(uint16_t index, uint16_t value);
+};
+
+#define USB_REQUEST_CALLBACK_DEFINE(_callback)                                \
+	static const STRUCT_SECTION_ITERABLE(                                 \
+		usb_request_callback, _usb_request_callback__##_callback) = { \
+		.callback = _callback,                                        \
+	}
diff --git a/firmware/src/usb_request.ld b/firmware/src/usb_request.ld
new file mode 100644
index 0000000..d99a755
--- /dev/null
+++ b/firmware/src/usb_request.ld
@@ -0,0 +1,8 @@
+/* Copyright 2025 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/linker/iterable_sections.h>
+
+ITERABLE_SECTION_ROM(usb_request_callback, Z_LINK_ITERABLE_SUBALIGN)
diff --git a/firmware/src/usbd.c b/firmware/src/usbd.c
index 96a4f1a..4a486de 100644
--- a/firmware/src/usbd.c
+++ b/firmware/src/usbd.c
@@ -4,6 +4,7 @@
  */
 
 #include "uart-bridge.h"
+#include "usb_request.h"
 
 #include <zephyr/device.h>
 #include <zephyr/input/input.h>
@@ -35,6 +36,30 @@
 USBD_CONFIGURATION_DEFINE(app_usb_hs_config, attributes, USB_MAX_POWER,
 			  &hs_cfg_desc);
 
+static int to_host_cb(const struct usbd_context *const ctx,
+		      const struct usb_setup_packet *const setup,
+		      struct net_buf *const buf)
+{
+	LOG_INF("%d: %d %d %d %d", setup->RequestType.type, setup->bRequest,
+		setup->wLength, setup->wIndex, setup->wValue);
+
+	return 0;
+}
+
+static int to_dev_cb(const struct usbd_context *const ctx,
+		     const struct usb_setup_packet *const setup,
+		     const struct net_buf *const buf)
+{
+	STRUCT_SECTION_FOREACH(usb_request_callback, callback)
+	{
+		callback->callback(setup->wIndex, setup->wValue);
+	}
+
+	return 0;
+}
+
+USBD_VREQUEST_DEFINE(vnd_vreq, 0, to_host_cb, to_dev_cb);
+
 static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
 			const struct usbd_msg *const msg)
 {
@@ -143,6 +168,12 @@
 		return NULL;
 	}
 
+	err = usbd_device_register_vreq(&app_usbd, &vnd_vreq);
+	if (err) {
+		LOG_ERR("Failed to register vreq");
+		return NULL;
+	}
+
 	err = usbd_init(&app_usbd);
 	if (err) {
 		LOG_ERR("Failed to initialize device support");
diff --git a/flash_npcx.py b/flash_npcx.py
new file mode 100755
index 0000000..c2af335
--- /dev/null
+++ b/flash_npcx.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+# Copyright 2025 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import os
+import sys
+import time
+
+import usb
+
+
+args = None
+target = None
+
+
+def find_usb_device():
+    dev = usb.core.find(manufacturer="Google", product="EC AIC Tester")
+    if dev is None:
+        raise ValueError("Device not found")
+
+    return dev
+
+
+# bRequest values:
+# bit 7 = 0: host to device
+# bit 6..5 = vendor
+# bit 4..0 = device
+TO_DEV_REQ_TYPE = 0x40
+# bit 7 = 0: device to host
+# bit 6..5 = vendor
+# bit 4..0 = device
+TO_HOST_REQ_TYPE = 0xC0
+
+# Must match the definitions in firmware/src/usb_request.h
+USB_REQ_EC_RESET = 0
+USB_REQ_POWER = 1
+USB_REQ_NPCX_BOOT = 2
+
+
+def main(argv):
+    global args
+    global target
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-p", "--port", type=str, default="ttyACM2")
+    args = parser.parse_args(argv)
+
+    dev = find_usb_device()
+
+    dev.ctrl_transfer(TO_DEV_REQ_TYPE, 0, 1, USB_REQ_POWER, 0)
+
+    dev.ctrl_transfer(TO_DEV_REQ_TYPE, 0, 0, USB_REQ_NPCX_BOOT, 0)
+
+    os.system(
+        f"uartupdatetool --port={args.port} --baudrate=115200 --opr=wr --addr=0x200C3020 --file build/zephyr/npcx-aic/output/npcx_monitor.bin"
+    )
+    os.system(
+        f"uartupdatetool --port={args.port} --baudrate=115200 --opr=wr --auto --offset=0 --file build/zephyr/npcx-aic/output/ec.bin"
+    )
+
+    dev.ctrl_transfer(TO_DEV_REQ_TYPE, 0, 0, USB_REQ_EC_RESET, 0)
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))