Skip to content
Open
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
1 change: 1 addition & 0 deletions drivers/usb/common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: Apache-2.0

add_subdirectory_ifdef(CONFIG_HAS_NRFX nrf_usbd_common)
add_subdirectory_ifdef(CONFIG_SOC_FAMILY_STM32 stm32)
1 change: 1 addition & 0 deletions drivers/usb/common/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
menu "USB common"

rsource "nrf_usbd_common/Kconfig"
rsource "stm32/Kconfig"

endmenu
10 changes: 10 additions & 0 deletions drivers/usb/common/stm32/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0

if(CONFIG_STM32_USB_COMMON)
zephyr_library()

zephyr_include_directories(.)

zephyr_library_sources(stm32_usb_pwr.c)
endif()
17 changes: 17 additions & 0 deletions drivers/usb/common/stm32/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0

if SOC_FAMILY_STM32

config STM32_USB_COMMON
bool
help
Enable compilation of STM32 USB common code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this empty line.

This option is selected by the USB drivers when appropriate.

module = STM32_USB_COMMON
module-str = STM32 USB common code
source "subsys/logging/Kconfig.template.log_config"

endif # SOC_FAMILY_STM32
23 changes: 23 additions & 0 deletions drivers/usb/common/stm32/stm32_usb_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_DRIVERS_USB_COMMON_STM32_STM32_USB_COMMON_H_
#define ZEPHYR_DRIVERS_USB_COMMON_STM32_STM32_USB_COMMON_H_

/**
* @brief Configures the Power Controller as necessary
* for proper operation of the USB controllers
*/
int stm32_usb_pwr_enable(void);

/**
* @brief Configures the Power Controller to disable
* USB-related regulators/etc if no controller is
* still active (refcounted).
*/
int stm32_usb_pwr_disable(void);

#endif /* ZEPHYR_DRIVERS_USB_COMMON_STM32_STM32_USB_COMMON_H_ */
171 changes: 171 additions & 0 deletions drivers/usb/common/stm32/stm32_usb_pwr.c
Copy link
Contributor

Choose a reason for hiding this comment

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

Create infrastructure for shared USB common code on STM32 family, and move
the Power Controller configuration logic to common code.

But it is not shared. You just moved the code from UDC driver. It is not worth it to touch legacy driver at this point. What is the reason to move the code?

If there is stm32_usb_pwr_enable(void), then you need also stm32_usb_pwr_disable(). stm32_usb_pwr.c actually looks like regulator driver. Why not use regulator API and implement a regulator driver? What you would need to implement is just

static DEVICE_API(regulator, api) = {
	.enable = stm32_usbreg_enable,
	.disable = stm32_usbreg_disable,
};

Copy link
Contributor Author

@mathieuchopstm mathieuchopstm Dec 10, 2025

Choose a reason for hiding this comment

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

But it is not shared.

It is not shared for now, but would become shared when/if an STM32 UHC driver is implemented.

It is not worth it to touch legacy driver

What do you mean by legacy driver? This is the mainline UDC driver, usb_dc_stm32.c is left untouched.

If there is stm32_usb_pwr_enable(void), then you need also stm32_usb_pwr_disable().

I did not implement disable() because it requires refcounting, but...

stm32_usb_pwr.c actually looks like regulator driver.

...good remark - and this solves refcounting issues. I'll try implementing as a regulator driver.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After a quick and dirty implementation, I don't think a regulator driver will be an acceptable solution. Turning this code into a regulator driver (and thus enabling the regulator subsystem!) adds an unjustified ~1K to the ROM footprint (on our smallest product with USB support, that's 2% of ROM size!)

Would you be OK if the code was in soc/st/common instead of drivers/usb/common?
(note that next PR in patchlist will place things in drivers/usb/common either way - unless that directory is pure legacy, in which case what's there should be migrated to a Nordic SoC-specific folder 🙂)

Copy link
Contributor

Choose a reason for hiding this comment

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

It is not shared for now, but would become shared when/if an STM32 UHC driver is implemented.

Just FYI, UHC shim driver for DWC2 controller would not be acceptable.

It is not worth it to touch legacy driver

What do you mean by legacy driver? This is the mainline UDC driver, usb_dc_stm32.c is left untouched.

From the commit message it is not clear where it would be shared, so I have to guess.

If there is stm32_usb_pwr_enable(void), then you need also stm32_usb_pwr_disable().

I did not implement disable() because it requires refcounting, but...

But why move still "not yet" shared code and keep it half-backed?

After a quick and dirty implementation, I don't think a regulator driver will be an acceptable solution. Turning this code into a regulator driver (and thus enabling the regulator subsystem!) adds an unjustified ~1K to the ROM footprint (on our smallest product with USB support, that's 2% of ROM size!)

AFAIK there is no regulator subsystem and the increased footprint sounds acceptable to me, we should stimulate the market, but I do not insist on it. Please add stm32_usb_pwr_disable().

Copy link
Contributor Author

@mathieuchopstm mathieuchopstm Dec 11, 2025

Choose a reason for hiding this comment

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

It is not shared for now, but would become shared when/if an STM32 UHC driver is implemented.

Just FYI, UHC shim driver for DWC2 controller would not be acceptable.

ST's USB controller also supports Host mode. (not that this matters anyways - regardless of which driver controls the IP, it will have to enable power the same way, hence the move to common code)

But why move still "not yet" shared code and keep it half-backed?

I want to avoid functional changes as much as possible while implementing multi-instance support.

Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <soc.h>
#include <stm32_ll_bus.h>
#include <stm32_ll_pwr.h>
#include <stm32_ll_rcc.h>
#include <stm32_ll_system.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(stm32_usb_pwr, CONFIG_STM32_USB_COMMON_LOG_LEVEL);

/*
* Keep track of whether power is already
* enabled here to simplify the USB drivers.
*/
static atomic_t usb_pwr_refcount = ATOMIC_INIT(0);

int stm32_usb_pwr_enable(void)
{
atomic_val_t old = atomic_inc(&usb_pwr_refcount);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be incremented if the function exits with -EIO?


if (old > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need old? Can it be if (atomic_get(&usb_pwr_refcount)) { here and atomic_inc(&usb_pwr_refcount); at the end of the function?

/* Already enabled - nothing to do */
return 0;
}

#if defined(CONFIG_SOC_SERIES_STM32H7X)
LL_PWR_EnableUSBVoltageDetector();

/* Per AN2606: USBREGEN not supported when running in FS mode. */
LL_PWR_DisableUSBReg();
while (!LL_PWR_IsActiveFlag_USB()) {
LOG_INF("PWR not active yet");
k_msleep(100);
}
#elif defined(CONFIG_SOC_SERIES_STM32U5X)
/* Sequence to enable the power of the OTG HS on a stm32U5 serie : Enable VDDUSB */
Copy link
Contributor

Choose a reason for hiding this comment

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

on a STM32U5 ?

__ASSERT_NO_MSG(LL_AHB3_GRP1_IsEnabledClock(LL_AHB3_GRP1_PERIPH_PWR));

/* Check that power range is 1 or 2 */
if (LL_PWR_GetRegulVoltageScaling() < LL_PWR_REGU_VOLTAGE_SCALE2) {
LOG_ERR("Wrong Power range to use USB OTG HS");
return -EIO;
}

LL_PWR_EnableVddUSB();

#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
/* Configure VOSR register of USB HSTransceiverSupply(); */
LL_PWR_EnableUSBPowerSupply();
LL_PWR_EnableUSBEPODBooster();
while (LL_PWR_IsActiveFlag_USBBOOST() != 1) {
/* Wait for USB EPOD BOOST ready */
}
#endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) */
Comment on lines +56 to +63
Copy link
Contributor

Choose a reason for hiding this comment

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

We usually do not indent blocks inside #if/#endif.

#elif defined(CONFIG_SOC_SERIES_STM32N6X)
/* Enable Vdd33USB voltage monitoring */
LL_PWR_EnableVddUSBMonitoring();
while (!LL_PWR_IsActiveFlag_USB33RDY()) {
/* Wait for Vdd33USB ready */
}

/* Enable VDDUSB */
LL_PWR_EnableVddUSB();
#elif defined(CONFIG_SOC_SERIES_STM32WBAX)
/* Remove VDDUSB power isolation */
LL_PWR_EnableVddUSB();

/* Make sure that voltage scaling is Range 1 */
__ASSERT_NO_MSG(LL_PWR_GetRegulCurrentVOS() == LL_PWR_REGU_VOLTAGE_SCALE1);

/* Enable VDD11USB */
LL_PWR_EnableVdd11USB();

/* Enable USB OTG internal power */
LL_PWR_EnableUSBPWR();

while (!LL_PWR_IsActiveFlag_VDD11USBRDY()) {
/* Wait for VDD11USB supply to be ready */
}

/* Enable USB OTG booster */
LL_PWR_EnableUSBBooster();

while (!LL_PWR_IsActiveFlag_USBBOOSTRDY()) {
/* Wait for USB OTG booster to be ready */
}
#elif defined(PWR_USBSCR_USB33SV) || defined(PWR_SVMCR_USV)
/*
* VDDUSB independent USB supply (PWR clock is on)
* with LL_PWR_EnableVDDUSB function (higher case)
*/
LL_PWR_EnableVDDUSB();
#elif defined(PWR_CR2_USV)
/*cd
Copy link
Contributor

Choose a reason for hiding this comment

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

/*cd
----------^ ?

* Required for at least STM32L4 devices as they electrically
* isolate USB features from VDDUSB. It must be enabled before
* USB can function. Refer to section 5.1.3 in DM00083560 or
* DM00310109.
*/
LL_PWR_EnableVddUSB();
#endif
return 0;
}

int stm32_usb_pwr_disable(void)
{
atomic_val_t old = atomic_dec(&usb_pwr_refcount);

if (old > 1) {
/* There are other users - don't disable now */
return 0;
}

#if defined(CONFIG_SOC_SERIES_STM32H7X)
LL_PWR_DisableUSBVoltageDetector();
#elif defined(CONFIG_SOC_SERIES_STM32U5X)
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
LL_PWR_DisableUSBEPODBooster();
while (LL_PWR_IsActiveFlag_USBBOOST() != 0) {
/* Wait for USB EPOD BOOST off */
}

LL_PWR_DisableUSBPowerSupply();
#endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) */

LL_PWR_DisableVddUSB();
#elif defined(CONFIG_SOC_SERIES_STM32N6X)
/* Disable Vdd33USB voltage monitoring */
LL_PWR_DisableVddUSBMonitoring();

/* Disable VDDUSB */
LL_PWR_DisableVddUSB();
#elif defined(CONFIG_SOC_SERIES_STM32WBAX)
/* Disable USB OTG booster */
LL_PWR_DisableUSBBooster();

while (LL_PWR_IsActiveFlag_USBBOOSTRDY()) {
/* Wait until USB OTG booster is off */
}

/* Disable USB OTG internal power */
LL_PWR_DisableUSBPWR();

/* Disable VDD11USB */
LL_PWR_DisableVdd11USB();

while (LL_PWR_IsActiveFlag_VDD11USBRDY()) {
/* Wait until VDD11USB supply is off */
}

/* Enable VDDUSB power isolation */
LL_PWR_DisableVddUSB();
#elif defined(PWR_USBSCR_USB33SV) || defined(PWR_SVMCR_USV)
/* Enable VDDUSB power isolation */
LL_PWR_DisableVDDUSB();
#elif defined(PWR_CR2_USV)
/* Enable VDDUSB power isolation */
LL_PWR_DisableVddUSB();
#endif

return 0;
}
1 change: 1 addition & 0 deletions drivers/usb/udc/Kconfig.stm32
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ config UDC_STM32
select USE_STM32_HAL_PCD
select USE_STM32_HAL_PCD_EX
select UDC_DRIVER_HAS_HIGH_SPEED_SUPPORT
select STM32_USB_COMMON
select PINCTRL
default y
help
Expand Down
105 changes: 15 additions & 90 deletions drivers/usb/udc/udc_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <zephyr/sys/util.h>

#include "udc_common.h"
#include <stm32_usb_common.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(udc_stm32, CONFIG_UDC_DRIVER_LOG_LEVEL);
Expand Down Expand Up @@ -720,6 +721,13 @@ int udc_stm32_init(const struct device *dev)
struct udc_stm32_data *priv = udc_get_private(dev);
const struct udc_stm32_config *cfg = dev->config;
HAL_StatusTypeDef status;
int err;

err = stm32_usb_pwr_enable();
if (err < 0) {
LOG_ERR("Error enabling USB power: %d", err);
return err;
}

if (udc_stm32_clock_enable(dev) < 0) {
LOG_ERR("Error enabling clock(s)");
Expand Down Expand Up @@ -946,6 +954,7 @@ static int udc_stm32_shutdown(const struct device *dev)
struct udc_stm32_data *priv = udc_get_private(dev);
const struct udc_stm32_config *cfg = dev->config;
HAL_StatusTypeDef status;
int err;

status = HAL_PCD_DeInit(&priv->pcd);
if (status != HAL_OK) {
Expand All @@ -958,6 +967,12 @@ static int udc_stm32_shutdown(const struct device *dev)
/* continue anyway */
}

err = stm32_usb_pwr_disable();
if (err < 0) {
LOG_ERR("Error disabling USB power: %d", err);
/* continue anyway */
}

if (irq_is_enabled(cfg->irqn)) {
irq_disable(cfg->irqn);
}
Expand Down Expand Up @@ -1296,76 +1311,6 @@ static int udc_stm32_clock_enable(const struct device *dev)
return -ENODEV;
}

/* Power configuration */
#if defined(CONFIG_SOC_SERIES_STM32H7X)
LL_PWR_EnableUSBVoltageDetector();

/* Per AN2606: USBREGEN not supported when running in FS mode. */
LL_PWR_DisableUSBReg();
while (!LL_PWR_IsActiveFlag_USB()) {
LOG_INF("PWR not active yet");
k_msleep(100);
}
#elif defined(CONFIG_SOC_SERIES_STM32U5X)
/* Sequence to enable the power of the OTG HS on a stm32U5 serie : Enable VDDUSB */
__ASSERT_NO_MSG(LL_AHB3_GRP1_IsEnabledClock(LL_AHB3_GRP1_PERIPH_PWR));

/* Check that power range is 1 or 2 */
if (LL_PWR_GetRegulVoltageScaling() < LL_PWR_REGU_VOLTAGE_SCALE2) {
LOG_ERR("Wrong Power range to use USB OTG HS");
return -EIO;
}

LL_PWR_EnableVddUSB();

#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
/* Configure VOSR register of USB HSTransceiverSupply(); */
LL_PWR_EnableUSBPowerSupply();
LL_PWR_EnableUSBEPODBooster();
while (LL_PWR_IsActiveFlag_USBBOOST() != 1) {
/* Wait for USB EPOD BOOST ready */
}
#endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) */
#elif defined(CONFIG_SOC_SERIES_STM32N6X)
/* Enable Vdd33USB voltage monitoring */
LL_PWR_EnableVddUSBMonitoring();
while (!LL_PWR_IsActiveFlag_USB33RDY()) {
/* Wait for Vdd33USB ready */
}

/* Enable VDDUSB */
LL_PWR_EnableVddUSB();
#elif defined(CONFIG_SOC_SERIES_STM32WBAX)
/* Remove VDDUSB power isolation */
LL_PWR_EnableVddUSB();

/* Make sure that voltage scaling is Range 1 */
__ASSERT_NO_MSG(LL_PWR_GetRegulCurrentVOS() == LL_PWR_REGU_VOLTAGE_SCALE1);

/* Enable VDD11USB */
LL_PWR_EnableVdd11USB();

/* Enable USB OTG internal power */
LL_PWR_EnableUSBPWR();

while (!LL_PWR_IsActiveFlag_VDD11USBRDY()) {
/* Wait for VDD11USB supply to be ready */
}

/* Enable USB OTG booster */
LL_PWR_EnableUSBBooster();

while (!LL_PWR_IsActiveFlag_USBBOOSTRDY()) {
/* Wait for USB OTG booster to be ready */
}
#elif defined(PWR_USBSCR_USB33SV) || defined(PWR_SVMCR_USV)
/*
* VDDUSB independent USB supply (PWR clock is on)
* with LL_PWR_EnableVDDUSB function (higher case)
*/
LL_PWR_EnableVDDUSB();
#endif

if (cfg->num_clocks > 1) {
if (clock_control_configure(clk, &cfg->pclken[1], NULL) != 0) {
LOG_ERR("Could not select USB domain clock");
Expand Down Expand Up @@ -1618,26 +1563,6 @@ static int udc_stm32_driver_init0(const struct device *dev)
}
}

/*cd
* Required for at least STM32L4 devices as they electrically
* isolate USB features from VDDUSB. It must be enabled before
* USB can function. Refer to section 5.1.3 in DM00083560 or
* DM00310109.
*/
#ifdef PWR_CR2_USV
#if defined(LL_APB1_GRP1_PERIPH_PWR)
if (LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_PWR)) {
LL_PWR_EnableVddUSB();
} else {
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
LL_PWR_EnableVddUSB();
LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_PWR);
}
#else
LL_PWR_EnableVddUSB();
#endif /* defined(LL_APB1_GRP1_PERIPH_PWR) */
#endif /* PWR_CR2_USV */

return 0;
}

Expand Down