Skip to content

drivers: udc_dwc2: Add nRF54LM20A vendor quirks #92842

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
198 changes: 198 additions & 0 deletions drivers/usb/udc/udc_dwc2_vendor_quirks.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,204 @@ DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE)

#endif /*DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs) */

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l)

#define USBHS_DT_WRAPPER_REG_ADDR(n) UINT_TO_POINTER(DT_INST_REG_ADDR_BY_NAME(n, wrapper))

#include <nrf.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>

#define NRF_DEFAULT_IRQ_PRIORITY 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#define NRF_DEFAULT_IRQ_PRIORITY 1


/*
* On USBHS, we cannot access the DWC2 register until VBUS is detected and
* valid. If the user tries to force usbd_enable() and the corresponding
* udc_enable() without a "VBUS ready" notification, the event wait will block
* until a valid VBUS signal is detected or until the
* CONFIG_UDC_DWC2_USBHS_VBUS_READY_TIMEOUT timeout expires.
*/
static K_EVENT_DEFINE(usbhs_events);
#define USBHS_VBUS_READY BIT(0)

static struct onoff_manager *pclk24m_mgr;
static struct onoff_client pclk24m_cli;

static void vregusb_isr(const void *arg)
{
const struct device *dev = arg;

if (NRF_VREGUSB->EVENTS_VBUSDETECTED) {
NRF_VREGUSB->EVENTS_VBUSDETECTED = 0;
k_event_post(&usbhs_events, USBHS_VBUS_READY);
udc_submit_event(dev, UDC_EVT_VBUS_READY, 0);
}

if (NRF_VREGUSB->EVENTS_VBUSREMOVED) {
NRF_VREGUSB->EVENTS_VBUSREMOVED = 0;
k_event_set_masked(&usbhs_events, 0, USBHS_VBUS_READY);
udc_submit_event(dev, UDC_EVT_VBUS_REMOVED, 0);
}
}

static inline int usbhs_init_vreg_and_clock(const struct device *dev)
{
IRQ_CONNECT(VREGUSB_IRQn, NRF_DEFAULT_IRQ_PRIORITY,
vregusb_isr, DEVICE_DT_INST_GET(0), 0);
Comment on lines +358 to +359
Copy link
Contributor

Choose a reason for hiding this comment

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

Why use NRF_DEFAULT_IRQ_PRIORITY? Why not from devicetree (node) ?

Copy link
Contributor

Choose a reason for hiding this comment

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

There is no devicetree node for VREGUSB peripheral (there is devicetree for USBHS peripheral, but it is VREGUSB that is used here in vendor quirk).

Copy link
Member

@carlescufi carlescufi Aug 6, 2025

Choose a reason for hiding this comment

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

This is acceptable for the time being that there is no node as explained above. This will also be looked at again by @jfischer-no in the near future for some additional work.


NRF_VREGUSB->INTEN = VREGUSB_INTEN_VBUSDETECTED_Msk |
VREGUSB_INTEN_VBUSREMOVED_Msk;
NRF_VREGUSB->TASKS_START = 1;

/* TODO: Determine conditions when VBUSDETECTED is not generated */
if (sys_read32((mem_addr_t)NRF_VREGUSB + 0x400) & BIT(2)) {
k_event_post(&usbhs_events, USBHS_VBUS_READY);
udc_submit_event(dev, UDC_EVT_VBUS_READY, 0);
}

irq_enable(VREGUSB_IRQn);
pclk24m_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF24M);

return 0;
}

static inline int usbhs_enable_core(const struct device *dev)
{
LOG_MODULE_DECLARE(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL);
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);
k_timeout_t timeout = K_FOREVER;
int err;

if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_NO_WAIT)) {
LOG_WRN("VBUS is not ready, block udc_enable()");
if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, timeout)) {
return -ETIMEDOUT;
}
}

/* Request PCLK24M using clock control driver */
sys_notify_init_spinwait(&pclk24m_cli.notify);
err = onoff_request(pclk24m_mgr, &pclk24m_cli);
if (err < 0) {
LOG_ERR("Failed to start PCLK24M %d", err);
return err;
}

/* Power up peripheral */
wrapper->ENABLE = USBHS_ENABLE_CORE_Msk;

/* Set ID to Device and force D+ pull-up off for now */
wrapper->PHY.OVERRIDEVALUES = (1 << 31);
wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk;

/* Release PHY power-on reset */
wrapper->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk;

/* Wait for PHY clock to start */
k_busy_wait(45);

/* Release DWC2 reset */
wrapper->TASKS_START = 1UL;

/* Wait for clock to start to avoid hang on too early register read */
k_busy_wait(1);

/* DWC2 opmode is now guaranteed to be Non-Driving, allow D+ pull-up to
* become active once driver clears DCTL SftDiscon bit.
*/
wrapper->PHY.INPUTOVERRIDE = (1 << 31);

return 0;
}

static inline int usbhs_disable_core(const struct device *dev)
{
LOG_MODULE_DECLARE(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL);
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);
int err;

/* Set ID to Device and forcefully disable D+ pull-up */
wrapper->PHY.OVERRIDEVALUES = (1 << 31);
wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk;

wrapper->ENABLE = 0UL;

/* Release PCLK24M using clock control driver */
err = onoff_cancel_or_release(pclk24m_mgr, &pclk24m_cli);
if (err < 0) {
LOG_ERR("Failed to stop PCLK24M %d", err);
return err;
}

return 0;
}

static inline int usbhs_disable_vreg(const struct device *dev)
{
NRF_VREGUSB->INTEN = 0;
NRF_VREGUSB->TASKS_STOP = 1;

return 0;
}

static inline int usbhs_init_caps(const struct device *dev)
{
struct udc_data *data = dev->data;

data->caps.can_detect_vbus = true;
data->caps.hs = true;

return 0;
}

static inline int usbhs_is_phy_clk_off(const struct device *dev)
{
return !k_event_test(&usbhs_events, USBHS_VBUS_READY);
}

static inline int usbhs_post_hibernation_entry(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);

sys_set_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK);

wrapper->TASKS_STOP = 1;

return 0;
}

static inline int usbhs_pre_hibernation_exit(const struct device *dev)
{
const struct udc_dwc2_config *const config = dev->config;
struct usb_dwc2_reg *const base = config->base;
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);

sys_clear_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK);

wrapper->TASKS_START = 1;

return 0;
}

#define QUIRK_NRF_USBHS_DEFINE(n) \
struct dwc2_vendor_quirks dwc2_vendor_quirks_##n = { \
.init = usbhs_init_vreg_and_clock, \
.pre_enable = usbhs_enable_core, \
.disable = usbhs_disable_core, \
.shutdown = usbhs_disable_vreg, \
.caps = usbhs_init_caps, \
.is_phy_clk_off = usbhs_is_phy_clk_off, \
.post_hibernation_entry = usbhs_post_hibernation_entry, \
.pre_hibernation_exit = usbhs_pre_hibernation_exit, \
};

DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE)

#endif /*DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) */

/* Add next vendor quirks definition above this line */

#endif /* ZEPHYR_DRIVERS_USB_UDC_DWC2_VENDOR_QUIRKS_H */
Loading