Skip to content

drivers: usb: add Nordic USBHS wrapper and USBHS BC12 drivers#106759

Open
jfischer-no wants to merge 5 commits intozephyrproject-rtos:mainfrom
jfischer-no:pr-nrf54lm20a-wrapper-bc12
Open

drivers: usb: add Nordic USBHS wrapper and USBHS BC12 drivers#106759
jfischer-no wants to merge 5 commits intozephyrproject-rtos:mainfrom
jfischer-no:pr-nrf54lm20a-wrapper-bc12

Conversation

@jfischer-no
Copy link
Copy Markdown
Contributor

Add drivers for Nordic USBHS wrapper and USB PD Charging-Type detector.
Alternative to #105731

Copy link
Copy Markdown
Contributor

@tmon-nordic tmon-nordic left a comment

Choose a reason for hiding this comment

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

Significantly better than #105731 but there are errors in actual bit handling and the synchronization is currently only apparent. It should be possible to do proper synchronization with the overall structure.

Comment on lines +99 to +101
/* Additionally suspend PHY */
inputoverride = USBHS_PHY_INPUTOVERRIDE_ID_Msk |
USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk;
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.

This path is not forcing PHY SUSPENDM0 to suspend mode, the code in if (suspended) does it.

struct nrf_usbhs_wrapper_data *const data = dev->data;

wrapper_enable_phy_internal(dev, suspended);
atomic_inc(&data->refcount);
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 synchronization working? Reference counter is atomic, but the actual wrapper accesses are in no way protected. I think a spin lock would be necessary (can change the refcount to just normal variable then).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Synchronization between what? The UDC driver quirk does not access the wrapper until the BC12 driver finishes. The BC12 driver has preference over the UDC driver and always accesses wrapper registers from ISR context. UDC wrapper access seems to be doomed, but I could not observe it, perhaps because of the VREG hysteresis.

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.

BC12 role can be changed independently of UDC state. Input overrides are effectively controlled by both UDC and BC12 driver.

wrapper_enable_phy_internal() is not in any way synchronized by the use of atomic_inc() at all.

Comment on lines +123 to +124
/* Wait for PHY clock to start */
k_busy_wait(45);
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.

Who controls reference clock? PHY clock won't start without it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, and it is still called from the UDC driver only.

const void *bc12_cb_data;
regulator_callback_t phy_cb;
const void *phy_cb_data;
atomic_t refcount;
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 there should be separate flag for whether udc and bc12 are enabled. Refcount is not really suitable here because the actual override sequence does depend on enabled order (bc12 or udc first) and vregusb interrupt.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, I do not think so. Refcount is very well suited, otherwise it would not be there. It is also not about enabled order, when there is BC12 driver, it always takes the precedence.

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.

UDC can be enabled and be kept enabled all the time. BC12 needs to perform Battery Charging operations on every VBUS detection (if role is set to Portable Device) and then leave with the state suitable for UDC to continue its operation.

Comment thread drivers/usb/bc12/bc12_nrf_usbhs.c Outdated
const struct usbhs_bc_config *const config = dev->config;
NRF_USBHS_Type *const wrapper = config->base;

wrapper->PHY.OVERRIDEVALUES = USBHS_PHY_OVERRIDEVALUES_OPMODE0_Msk |
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.

OPMODE0 has to be binary 01, not 11. BIT(USBHS_PHY_OVERRIDEVALUES_OPMODE0_Pos) will produce correct value.

Comment thread drivers/usb/bc12/bc12_nrf_usbhs.c Outdated
NRF_USBHS_Type *const wrapper = config->base;

wrapper->PHY.OVERRIDEVALUES = USBHS_PHY_OVERRIDEVALUES_OPMODE0_Msk |
USBHS_PHY_OVERRIDEVALUES_XCVRSEL0_Msk;
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.

XCVRSEL0 has to be binary 01, not 11. BIT(USBHS_PHY_OVERRIDEVALUES_XCVRSEL0_Pos) will produce correct value.

Comment on lines +171 to +175
/*
* Enable VDP_SRC onto DP and keep it enabled until DWC2 driver
* is ready and enables D+ pull-up.
*/
wrapper->PHY.BATTCHRG = USBHS_PHY_BATTCHRG_VDATSRCENB0_Msk;
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.

This has to turned off in case DWC2 enables D+ pull-up.

jfischer-no and others added 4 commits April 8, 2026 15:50
Nordic USBHS wrapper is a set of registers used by both USB BC12 driver
and vendor specific part in UDC driver. The wrapper can be considered
to be MFD device. Both drivers depends on the VBUS regulator driver and
require some synchronisation when accessing wrapper register.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Use USBHS wrapper driver to register VREGUSB regulator callback and
enable USB peripheral.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Add driver for Nordic USB PD Charging-Type detector.
This driver depends on the USBHS wrapper driver.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Co-authored-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
Add and enable USBHS charging-type detector node.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
@jfischer-no jfischer-no force-pushed the pr-nrf54lm20a-wrapper-bc12 branch 2 times, most recently from c28e199 to e6ee67b Compare April 9, 2026 16:23
keith-zephyr
keith-zephyr previously approved these changes Apr 9, 2026
Copy link
Copy Markdown
Contributor

@keith-zephyr keith-zephyr left a comment

Choose a reason for hiding this comment

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

BC12 driver looks good to me

Add an example of how to use USB BC12 driver callback among the USB
device support.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
@sonarqubecloud
Copy link
Copy Markdown


wrapper->ENABLE = 0UL;

nrf_usbhs_wrapper_disable(parent);
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.

Because this effectively cancels out nrf_usbhs_wrapper_enable_udc(), this should be renamed to nrf_usbhs_wrapper_disable_udc().

{
const struct usbhs_bc_config *const config = dev->config;

nrf_usbhs_wrapper_disable(config->parent);
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.

Because this effectively cancels out nrf_usbhs_wrapper_enable_phy(), this should be renamed to nrf_usbhs_wrapper_disable_phy() to match.

However, I think the nrf_usbhs_wrapper_enable_phy() should be changed to nrf_usbhs_wrapper_enable_bc() because what we need is BC functionality. Technically we cannot just enable PHY alone without enabling CORE. If nrf_usbhs_wrapper_enable_phy() gets renamed to nrf_usbhs_wrapper_enable_bc() then nrf_usbhs_wrapper_disable_bc() would be the correct name to use here.

Comment on lines +319 to +328
if (role == BC12_DISCONNECTED) {
data->partner_state.type = BC12_TYPE_NONE;
data->partner_state.bc12_role = role;
err = nrf_usbhs_wrapper_vreg_disable(config->parent);
if (err) {
LOG_ERR("Failed to disable regulator");
}

return err;
}
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.

The logic will still trigger BC operations if UDC driver called nrf_usbhs_wrapper_vreg_enable().

There are multiple ways to solve the problem. Example would be to check bc12_role before starting BC operations. The assigned role would need synchronization via spin lock because the check would be done inside interrupt handler. If role is set to BC12_DISCONNECTED while BC detection is in progress, then I believe the detection should still finish normally and the change should only refer to VBUS detections that happens after role was set.

struct nrf_usbhs_wrapper_data *const data = dev->data;

wrapper_enable_phy_internal(dev, suspended);
atomic_inc(&data->refcount);
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.

BC12 role can be changed independently of UDC state. Input overrides are effectively controlled by both UDC and BC12 driver.

wrapper_enable_phy_internal() is not in any way synchronized by the use of atomic_inc() at all.

Comment on lines +150 to +151
LOG_INF("Enable %s, refcount %lu",
"USB peripheral", atomic_get(&data->refcount));
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.

This log is not atomic. The return value from atomic_inc() must be used instead of atomic_get() if you want to use the primitive correctly.

I believe just using the data->lock spinlock would be superior here. Using atomic API in non-atomic way is effectively making this rather pointless.

LOG_INF("BC12 detection finished, refcount %lu", atomic_get(&data->refcount));

k_event_post(&data->events, NRF_USBHS_PHY_READY);
if (atomic_get(&data->refcount) > 1) {
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.

Using atomic here sounds like atomic misuse. There really should be a bit indicating that nrf_usbhs_wrapper_enable_udc() was called. The bit should be protected with a spin lock.

*/
wrapper->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk;

if (IS_ENABLED(CONFIG_USB_BC12_NRF_USBHS)) {
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.

This shouldn't be only based on whether the driver is compiled, but also if BC12 operations were performed.

#include <nrf_usbhs_wrapper.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbhs, LOG_LEVEL_ERR);
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.

It would be nice it if this was not hardcoded.

const void *bc12_cb_data;
regulator_callback_t phy_cb;
const void *phy_cb_data;
atomic_t refcount;
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.

UDC can be enabled and be kept enabled all the time. BC12 needs to perform Battery Charging operations on every VBUS detection (if role is set to Portable Device) and then leave with the state suitable for UDC to continue its operation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Boards/SoCs area: Devicetree Binding PR modifies or adds a Device Tree binding area: Samples Samples area: USB Universal Serial Bus platform: nRF Nordic nRFx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants