Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions boards/st/disco_l475_iot1/disco_l475_iot1.dts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
1 change: 1 addition & 0 deletions drivers/usb/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
6 changes: 6 additions & 0 deletions drivers/usb/vbus_detect/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
32 changes: 32 additions & 0 deletions drivers/usb/vbus_detect/Kconfig
Original file line number Diff line number Diff line change
@@ -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
204 changes: 204 additions & 0 deletions drivers/usb/vbus_detect/usb_vbus_detect_gpio.c
Original file line number Diff line number Diff line change
@@ -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 <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/drivers/usb/usb_vbus_detect.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#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)
26 changes: 26 additions & 0 deletions dts/bindings/usb/zephyr,usb-vbus-detect-gpio.yaml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions include/zephyr/drivers/usb/udc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment on lines +431 to +436
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this synchronized against UDC init and USB stack using the value?


/**
* @brief Get actual USB device speed
*
Expand Down
52 changes: 52 additions & 0 deletions include/zephyr/drivers/usb/usb_vbus_detect.h
Original file line number Diff line number Diff line change
@@ -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 <zephyr/device.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Notify USB stack that VBUS is ready.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will need some versioning.

*
* 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_ */
8 changes: 8 additions & 0 deletions samples/drivers/usb/vbus_detect/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions samples/drivers/usb/vbus_detect/prj.conf
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading