diff --git a/boards/st/disco_l475_iot1/disco_l475_iot1.dts b/boards/st/disco_l475_iot1/disco_l475_iot1.dts index 7db29844c7e65..6e0eab11505e3 100644 --- a/boards/st/disco_l475_iot1/disco_l475_iot1.dts +++ b/boards/st/disco_l475_iot1/disco_l475_iot1.dts @@ -69,6 +69,13 @@ }; }; + vbus_detect: vbus-detect { + compatible = "zephyr,usb-vbus-detect-gpio"; + vbus-gpios = <&gpioa 9 GPIO_ACTIVE_HIGH>; + udc = <&zephyr_udc0>; + debounce-ms = <20>; + }; + aliases { led0 = &green_led_2; led1 = &green_led_1; diff --git a/drivers/usb/CMakeLists.txt b/drivers/usb/CMakeLists.txt index e0d9f196b2920..5948fcdad234f 100644 --- a/drivers/usb/CMakeLists.txt +++ b/drivers/usb/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory_ifdef(CONFIG_UVB uvb) add_subdirectory_ifdef(CONFIG_USB_BC12 bc12) add_subdirectory_ifdef(CONFIG_USB_DEVICE_DRIVER device) add_subdirectory(common) +add_subdirectory_ifdef(CONFIG_USB_VBUS_DETECT vbus_detect) diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index bcccdf6b65789..a2cd6ddd8fba1 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -9,3 +9,4 @@ source "drivers/usb/uhc/Kconfig" source "drivers/usb/uvb/Kconfig" source "drivers/usb/device/Kconfig" source "drivers/usb/common/Kconfig" +source "drivers/usb/vbus_detect/Kconfig" diff --git a/drivers/usb/vbus_detect/CMakeLists.txt b/drivers/usb/vbus_detect/CMakeLists.txt new file mode 100644 index 0000000000000..1b0c1fab0415c --- /dev/null +++ b/drivers/usb/vbus_detect/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_USB_VBUS_DETECT_GPIO usb_vbus_detect_gpio.c) diff --git a/drivers/usb/vbus_detect/Kconfig b/drivers/usb/vbus_detect/Kconfig new file mode 100644 index 0000000000000..1760126f6a221 --- /dev/null +++ b/drivers/usb/vbus_detect/Kconfig @@ -0,0 +1,32 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +menuconfig USB_VBUS_DETECT + bool "USB VBUS Detection Drivers" + default y if DT_HAS_ZEPHYR_USB_VBUS_DETECT_GPIO_ENABLED + depends on UDC_DRIVER + help + Enable USB VBUS detection driver support. Automatically enabled + when a VBUS detect node is present in the devicetree. + +if USB_VBUS_DETECT + +module = USB_VBUS_DETECT +module-str = usb_vbus_detect +source "subsys/logging/Kconfig.template.log_config" + +config USB_VBUS_DETECT_INIT_PRIORITY + int "USB VBUS detect driver init priority" + default 80 + help + USB VBUS detection driver initialization priority. + +config USB_VBUS_DETECT_GPIO + bool "GPIO-based VBUS detector" + default y + depends on DT_HAS_ZEPHYR_USB_VBUS_DETECT_GPIO_ENABLED + depends on GPIO + help + Enable GPIO-based USB VBUS detection driver. + +endif # USB_VBUS_DETECT diff --git a/drivers/usb/vbus_detect/usb_vbus_detect_gpio.c b/drivers/usb/vbus_detect/usb_vbus_detect_gpio.c new file mode 100644 index 0000000000000..b69e6eb463a02 --- /dev/null +++ b/drivers/usb/vbus_detect/usb_vbus_detect_gpio.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2026 Leica Geosystems AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_usb_vbus_detect_gpio + +#include +#include +#include +#include +#include +#include + +#include "../udc/udc_common.h" + +LOG_MODULE_REGISTER(usb_vbus_detect_gpio, CONFIG_USB_VBUS_DETECT_LOG_LEVEL); + +struct usb_vbus_detect_gpio_config { + struct gpio_dt_spec vbus_gpio; + const struct device *const *udc_list; + size_t udc_count; + uint32_t debounce_ms; +}; + +struct usb_vbus_detect_gpio_data { + const struct device *dev; + struct gpio_callback gpio_cb; + struct k_work_delayable debounce_work; + bool last_state; + bool initialized; +}; + +static int usb_vbus_notify_device(const struct device *udc_dev, + enum udc_event_type event) +{ + if (!udc_is_initialized(udc_dev)) { + LOG_DBG("Device %s not initialized, skipping", udc_dev->name); + return -EPERM; + } + + return udc_submit_event(udc_dev, event, 0); +} + +static void usb_vbus_notify_all(const struct usb_vbus_detect_gpio_config *cfg, + bool vbus_ready) +{ + enum udc_event_type event = vbus_ready ? UDC_EVT_VBUS_READY + : UDC_EVT_VBUS_REMOVED; + const char *event_name = vbus_ready ? "VBUS_READY" : "VBUS_REMOVED"; + int ret; + + for (size_t i = 0; i < cfg->udc_count; i++) { + const struct device *udc_dev = cfg->udc_list[i]; + + ret = usb_vbus_notify_device(udc_dev, event); + if (ret < 0 && ret != -EPERM) { + LOG_ERR("Failed to signal %s to %s: %d", + event_name, udc_dev->name, ret); + } else if (ret == 0) { + LOG_INF("%s sent to %s", event_name, udc_dev->name); + } + } +} + +static void usb_vbus_detect_gpio_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct usb_vbus_detect_gpio_data *data = + CONTAINER_OF(dwork, struct usb_vbus_detect_gpio_data, + debounce_work); + const struct device *dev = data->dev; + const struct usb_vbus_detect_gpio_config *cfg = dev->config; + int state; + + state = gpio_pin_get_dt(&cfg->vbus_gpio); + if (state < 0) { + LOG_ERR("Failed to read VBUS GPIO: %d", state); + return; + } + + LOG_DBG("VBUS state: %d (last: %d, initialized: %d)", + state, data->last_state, data->initialized); + + if (!data->initialized) { + data->last_state = state; + data->initialized = true; + usb_vbus_notify_all(cfg, state != 0); + return; + } + + if (state != data->last_state) { + data->last_state = state; + usb_vbus_notify_all(cfg, state != 0); + } +} + +static void usb_vbus_detect_gpio_callback(const struct device *port, + struct gpio_callback *cb, + gpio_port_pins_t pins) +{ + struct usb_vbus_detect_gpio_data *data = + CONTAINER_OF(cb, struct usb_vbus_detect_gpio_data, gpio_cb); + const struct device *dev = data->dev; + const struct usb_vbus_detect_gpio_config *cfg = dev->config; + + ARG_UNUSED(port); + ARG_UNUSED(pins); + + k_work_reschedule(&data->debounce_work, K_MSEC(cfg->debounce_ms)); +} + +static int usb_vbus_detect_gpio_init(const struct device *dev) +{ + const struct usb_vbus_detect_gpio_config *cfg = dev->config; + struct usb_vbus_detect_gpio_data *data = dev->data; + int ret; + + data->dev = dev; + data->initialized = false; + + if (!gpio_is_ready_dt(&cfg->vbus_gpio)) { + LOG_ERR("VBUS GPIO device not ready"); + return -ENODEV; + } + + /* Check all UDC devices are ready and inform them about VBUS detect */ + for (size_t i = 0; i < cfg->udc_count; i++) { + const struct device *udc_dev = cfg->udc_list[i]; + + if (!device_is_ready(udc_dev)) { + LOG_ERR("UDC device %s not ready", udc_dev->name); + return -ENODEV; + } + + /* Inform UDC that external VBUS detection is available */ + udc_set_can_detect_vbus(udc_dev); + LOG_DBG("Registered VBUS detection for %s", udc_dev->name); + } + + ret = gpio_pin_configure_dt(&cfg->vbus_gpio, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to configure VBUS GPIO: %d", ret); + return ret; + } + + k_work_init_delayable(&data->debounce_work, + usb_vbus_detect_gpio_work_handler); + + gpio_init_callback(&data->gpio_cb, usb_vbus_detect_gpio_callback, + BIT(cfg->vbus_gpio.pin)); + + ret = gpio_add_callback(cfg->vbus_gpio.port, &data->gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to add GPIO callback: %d", ret); + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&cfg->vbus_gpio, + GPIO_INT_EDGE_BOTH); + if (ret < 0) { + LOG_ERR("Failed to configure GPIO interrupt: %d", ret); + return ret; + } + + /* Schedule initial state check */ + k_work_reschedule(&data->debounce_work, K_MSEC(cfg->debounce_ms)); + + LOG_INF("USB VBUS detect initialized with %zu UDC device(s)", + cfg->udc_count); + + return 0; +} + +/* Helper macro to get device pointer for each phandle in udc list */ +#define USB_VBUS_UDC_DEVICE_GET(node_id, prop, idx) \ + DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), + +#define USB_VBUS_DETECT_GPIO_DEFINE(inst) \ + static struct usb_vbus_detect_gpio_data \ + usb_vbus_detect_gpio_data_##inst; \ + \ + static const struct device *const \ + usb_vbus_udc_list_##inst[] = { \ + DT_INST_FOREACH_PROP_ELEM(inst, udc, USB_VBUS_UDC_DEVICE_GET) \ + }; \ + \ + static const struct usb_vbus_detect_gpio_config \ + usb_vbus_detect_gpio_config_##inst = { \ + .vbus_gpio = GPIO_DT_SPEC_INST_GET(inst, vbus_gpios), \ + .udc_list = usb_vbus_udc_list_##inst, \ + .udc_count = ARRAY_SIZE(usb_vbus_udc_list_##inst), \ + .debounce_ms = DT_INST_PROP(inst, debounce_ms), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, usb_vbus_detect_gpio_init, NULL, \ + &usb_vbus_detect_gpio_data_##inst, \ + &usb_vbus_detect_gpio_config_##inst, \ + POST_KERNEL, \ + CONFIG_USB_VBUS_DETECT_INIT_PRIORITY, \ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(USB_VBUS_DETECT_GPIO_DEFINE) diff --git a/dts/bindings/usb/zephyr,usb-vbus-detect-gpio.yaml b/dts/bindings/usb/zephyr,usb-vbus-detect-gpio.yaml new file mode 100644 index 0000000000000..6c050a57ce5e2 --- /dev/null +++ b/dts/bindings/usb/zephyr,usb-vbus-detect-gpio.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +description: GPIO-based USB VBUS detection + +compatible: "zephyr,usb-vbus-detect-gpio" + +include: base.yaml + +properties: + vbus-gpios: + type: phandle-array + required: true + description: GPIO pin for VBUS sensing + + udc: + type: phandles + required: true + description: | + List of USB device controllers to receive VBUS events. + All devices in this list will be notified when VBUS state changes. + + debounce-ms: + type: int + default: 20 + description: Debounce interval in milliseconds diff --git a/include/zephyr/drivers/usb/udc.h b/include/zephyr/drivers/usb/udc.h index 9b1f25e78e093..e69801f23857c 100644 --- a/include/zephyr/drivers/usb/udc.h +++ b/include/zephyr/drivers/usb/udc.h @@ -420,6 +420,21 @@ static inline struct udc_device_caps udc_caps(const struct device *dev) return data->caps; } +/** + * @brief Set external VBUS detection capability + * + * Called by external VBUS detection drivers to indicate that VBUS + * detection is available for this controller. + * + * @param[in] dev Pointer to device struct of the driver instance + */ +static inline void udc_set_can_detect_vbus(const struct device *dev) +{ + struct udc_data *data = (struct udc_data *)dev->data; + + data->caps.can_detect_vbus = true; +} + /** * @brief Get actual USB device speed * diff --git a/include/zephyr/drivers/usb/usb_vbus_detect.h b/include/zephyr/drivers/usb/usb_vbus_detect.h new file mode 100644 index 0000000000000..b0bb9cf571416 --- /dev/null +++ b/include/zephyr/drivers/usb/usb_vbus_detect.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2026 Leica Geosystems AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB VBUS detection API + * + * This API provides functions for VBUS detection drivers to notify + * the USB device controller of VBUS state changes. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_USB_USB_VBUS_DETECT_H_ +#define ZEPHYR_INCLUDE_DRIVERS_USB_USB_VBUS_DETECT_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Notify USB stack that VBUS is ready. + * + * Called by VBUS detection drivers when VBUS voltage reaches + * a stable valid level. + * + * @param udc_dev Pointer to USB device controller device. + * @return 0 on success, negative errno on failure. + * @retval -EPERM if controller is not initialized. + */ +int usb_vbus_ready(const struct device *udc_dev); + +/** + * @brief Notify USB stack that VBUS was removed. + * + * Called by VBUS detection drivers when VBUS voltage drops + * below the valid threshold. + * + * @param udc_dev Pointer to USB device controller device. + * @return 0 on success, negative errno on failure. + * @retval -EPERM if controller is not initialized. + */ +int usb_vbus_removed(const struct device *udc_dev); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_USB_USB_VBUS_DETECT_H_ */ diff --git a/samples/drivers/usb/vbus_detect/CMakeLists.txt b/samples/drivers/usb/vbus_detect/CMakeLists.txt new file mode 100644 index 0000000000000..d5943bc585ed2 --- /dev/null +++ b/samples/drivers/usb/vbus_detect/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(usb_vbus_detect_sample) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/usb/vbus_detect/prj.conf b/samples/drivers/usb/vbus_detect/prj.conf new file mode 100644 index 0000000000000..fc3389b8890d4 --- /dev/null +++ b/samples/drivers/usb/vbus_detect/prj.conf @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USB_VBUS_DETECT=y +CONFIG_USB_VBUS_DETECT_GPIO=y +CONFIG_USB_VBUS_DETECT_LOG_LEVEL_DBG=y +CONFIG_UDC_DRIVER_LOG_LEVEL_DBG=y diff --git a/samples/drivers/usb/vbus_detect/sample.yaml b/samples/drivers/usb/vbus_detect/sample.yaml new file mode 100644 index 0000000000000..bcd2705b48c61 --- /dev/null +++ b/samples/drivers/usb/vbus_detect/sample.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +sample: + name: USB VBUS Detection Sample + description: Demonstrates USB VBUS detection using GPIO + +common: + tags: + - usb + - vbus + - gpio + +tests: + sample.drivers.usb.vbus_detect: + platform_allow: + - disco_l475_iot1 + build_only: true diff --git a/samples/drivers/usb/vbus_detect/src/main.c b/samples/drivers/usb/vbus_detect/src/main.c new file mode 100644 index 0000000000000..da4ad7f3ffbc7 --- /dev/null +++ b/samples/drivers/usb/vbus_detect/src/main.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026 Leica Geosystems AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB VBUS Detection Sample + * + * This sample demonstrates the USB VBUS detection driver. It initializes + * the USB device controller and monitors VBUS state changes via GPIO. + * + * Connect a VBUS sense signal to the configured GPIO pin (PA9 on + * disco_l475_iot1) to see VBUS ready/removed events in the log output. + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); + +static const struct device *udc_dev = DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)); + +static int udc_event_handler(const struct device *dev, + const struct udc_event *const event) +{ + switch (event->type) { + case UDC_EVT_VBUS_READY: + LOG_INF("VBUS ready - USB cable connected"); + break; + case UDC_EVT_VBUS_REMOVED: + LOG_INF("VBUS removed - USB cable disconnected"); + break; + case UDC_EVT_RESET: + LOG_INF("USB bus reset"); + break; + case UDC_EVT_SUSPEND: + LOG_INF("USB suspended"); + break; + case UDC_EVT_RESUME: + LOG_INF("USB resumed"); + break; + default: + LOG_DBG("UDC event: %d", event->type); + break; + } + + return 0; +} + +int main(void) +{ + int ret; + + LOG_INF("USB VBUS Detection Sample"); + + if (!device_is_ready(udc_dev)) { + LOG_ERR("UDC device not ready"); + return -ENODEV; + } + + ret = udc_init(udc_dev, udc_event_handler, NULL); + if (ret) { + LOG_ERR("Failed to initialize UDC: %d", ret); + return ret; + } + + LOG_INF("UDC initialized, waiting for VBUS events..."); + LOG_INF("Connect/disconnect USB cable to see VBUS state changes"); + + /* The VBUS detect driver runs automatically in the background */ + while (1) { + k_sleep(K_FOREVER); + } + + return 0; +} diff --git a/tests/drivers/usb/vbus_detect/CMakeLists.txt b/tests/drivers/usb/vbus_detect/CMakeLists.txt new file mode 100644 index 0000000000000..f9fc49792de88 --- /dev/null +++ b/tests/drivers/usb/vbus_detect/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(usb_vbus_detect_test) + +target_sources(app PRIVATE + src/main.c +) diff --git a/tests/drivers/usb/vbus_detect/boards/native_sim.conf b/tests/drivers/usb/vbus_detect/boards/native_sim.conf new file mode 100644 index 0000000000000..8b21a55043250 --- /dev/null +++ b/tests/drivers/usb/vbus_detect/boards/native_sim.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000 +CONFIG_UHC_DRIVER=y +CONFIG_USB_HOST_STACK=y diff --git a/tests/drivers/usb/vbus_detect/boards/native_sim.overlay b/tests/drivers/usb/vbus_detect/boards/native_sim.overlay new file mode 100644 index 0000000000000..1b443a784a47f --- /dev/null +++ b/tests/drivers/usb/vbus_detect/boards/native_sim.overlay @@ -0,0 +1,24 @@ +/* Copyright (c) 2026 Leica Geosystems AG + * SPDX-License-Identifier: Apache-2.0 + */ + +/delete-node/ &zephyr_udc0; + +/ { + zephyr_uhc0: uhc_vrt0 { + compatible = "zephyr,uhc-virtual"; + + zephyr_udc0: udc_vrt0 { + compatible = "zephyr,udc-virtual"; + num-bidir-endpoints = <8>; + maximum-speed = "high-speed"; + }; + }; + + vbus_detect: vbus-detect { + compatible = "zephyr,usb-vbus-detect-gpio"; + vbus-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; + udc = <&zephyr_udc0>; + debounce-ms = <5>; + }; +}; diff --git a/tests/drivers/usb/vbus_detect/prj.conf b/tests/drivers/usb/vbus_detect/prj.conf new file mode 100644 index 0000000000000..e65dc4e6ded07 --- /dev/null +++ b/tests/drivers/usb/vbus_detect/prj.conf @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_GPIO=y +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USB_VBUS_DETECT=y +CONFIG_USB_VBUS_DETECT_GPIO=y +CONFIG_USB_VBUS_DETECT_LOG_LEVEL_DBG=y +CONFIG_LOG=y diff --git a/tests/drivers/usb/vbus_detect/src/main.c b/tests/drivers/usb/vbus_detect/src/main.c new file mode 100644 index 0000000000000..761c0c243f199 --- /dev/null +++ b/tests/drivers/usb/vbus_detect/src/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026 Leica Geosystems AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(test_vbus_detect, LOG_LEVEL_DBG); + +#define VBUS_GPIO_NODE DT_NODELABEL(vbus_detect) +#define VBUS_GPIO_CTLR DT_GPIO_CTLR(VBUS_GPIO_NODE, vbus_gpios) +#define VBUS_GPIO_PIN DT_GPIO_PIN(VBUS_GPIO_NODE, vbus_gpios) + +static const struct device *gpio_dev = DEVICE_DT_GET(VBUS_GPIO_CTLR); +static const struct device *vbus_dev = DEVICE_DT_GET(VBUS_GPIO_NODE); + +static void *vbus_detect_setup(void) +{ + zassert_true(device_is_ready(gpio_dev), "GPIO device not ready"); + zassert_true(device_is_ready(vbus_dev), "VBUS detect device not ready"); + + return NULL; +} + +static void vbus_detect_before(void *fixture) +{ + ARG_UNUSED(fixture); + + /* Start with VBUS low */ + gpio_emul_input_set(gpio_dev, VBUS_GPIO_PIN, 0); + k_msleep(10); +} + +ZTEST(vbus_detect, test_device_ready) +{ + /* Verify the VBUS detect device is ready */ + zassert_true(device_is_ready(vbus_dev), + "VBUS detect device not ready"); +} + +ZTEST(vbus_detect, test_gpio_interrupt_configured) +{ + gpio_flags_t flags; + int ret; + + /* Verify GPIO is configured with interrupt */ + ret = gpio_emul_flags_get(gpio_dev, VBUS_GPIO_PIN, &flags); + zassert_ok(ret, "Failed to get GPIO flags"); + + /* Check that input mode is set */ + zassert_true(flags & GPIO_INPUT, + "GPIO not configured as input"); +} + +ZTEST(vbus_detect, test_gpio_state_change_detected) +{ + int state; + + /* Set VBUS low */ + gpio_emul_input_set(gpio_dev, VBUS_GPIO_PIN, 0); + k_msleep(10); + + state = gpio_pin_get(gpio_dev, VBUS_GPIO_PIN); + zassert_equal(state, 0, "Expected GPIO low"); + + /* Set VBUS high */ + gpio_emul_input_set(gpio_dev, VBUS_GPIO_PIN, 1); + k_msleep(10); + + state = gpio_pin_get(gpio_dev, VBUS_GPIO_PIN); + zassert_equal(state, 1, "Expected GPIO high"); +} + +ZTEST(vbus_detect, test_debounce_delay) +{ + /* + * Test that rapid GPIO changes are handled. + * The driver uses debounce so rapid toggles should not cause issues. + */ + for (int i = 0; i < 10; i++) { + gpio_emul_input_set(gpio_dev, VBUS_GPIO_PIN, i % 2); + k_msleep(1); + } + + /* Let debounce settle */ + k_msleep(20); + + /* If we got here without crashing, debounce is working */ + zassert_true(true, "Debounce handling works"); +} + +ZTEST_SUITE(vbus_detect, NULL, vbus_detect_setup, vbus_detect_before, NULL, + NULL); diff --git a/tests/drivers/usb/vbus_detect/testcase.yaml b/tests/drivers/usb/vbus_detect/testcase.yaml new file mode 100644 index 0000000000000..307972dc20e9f --- /dev/null +++ b/tests/drivers/usb/vbus_detect/testcase.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Leica Geosystems AG +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.usb.vbus_detect: + tags: + - drivers + - usb + - vbus + platform_allow: native_sim