Skip to content

Commit 520bbac

Browse files
author
Josuah Demangeon
committed
driver: usb: uhc: dwc2: integrate nRF54LM20 vendor quirks
nRF54LM20's DWC2 core is powered through the PHY, which requires to power-up the PHY before making any register access. Import and modify the registers from UDC DWC2 driver taking this in consideration. Signed-off-by: Josuah Demangeon <[email protected]>
1 parent 426ed18 commit 520bbac

File tree

3 files changed

+247
-2
lines changed

3 files changed

+247
-2
lines changed

drivers/usb/uhc/uhc_dwc2.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ struct uhc_dwc2_vendor_quirks {
4747
int (*pre_hibernation_exit)(const struct device *dev);
4848
};
4949

50-
#include "uhc_dwc2_vendor_quirks.h"
51-
5250
/* Driver configuration per instance */
5351
struct uhc_dwc2_config {
5452
/* Pointer to base address of DWC_OTG registers */
@@ -64,6 +62,8 @@ struct uhc_dwc2_config {
6462
/* TODO: Peripheral driver public parameters? */
6563
};
6664

65+
#include "uhc_dwc2_vendor_quirks.h"
66+
6767
#define UHC_DWC2_VENDOR_QUIRK_GET(n) \
6868
COND_CODE_1(DT_NODE_VENDOR_HAS_IDX(DT_DRV_INST(n), 1), \
6969
(&uhc_dwc2_vendor_quirks_##n), \

drivers/usb/uhc/uhc_dwc2_vendor_quirks.h

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd.
3+
* Copyright (c) 2025 Nordic Semiconductor ASA
34
*
45
* SPDX-License-Identifier: Apache-2.0
56
*/
@@ -13,6 +14,8 @@
1314
#include <zephyr/device.h>
1415
#include <zephyr/drivers/usb/uhc.h>
1516

17+
static void uhc_dwc2_isr_handler(const struct device *dev);
18+
1619
#if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg)
1720

1821
#include <zephyr/logging/log.h>
@@ -213,6 +216,240 @@ DT_INST_FOREACH_STATUS_OKAY(QUIRK_ESP32_USB_OTG_DEFINE)
213216

214217
#endif /*DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) */
215218

219+
#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l)
220+
221+
#define USBHS_DT_WRAPPER_REG_ADDR(n) UINT_TO_POINTER(DT_INST_REG_ADDR_BY_NAME(n, wrapper))
222+
223+
#include <nrf.h>
224+
#include <zephyr/logging/log.h>
225+
#include <zephyr/drivers/clock_control.h>
226+
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
227+
228+
#define NRF_DEFAULT_IRQ_PRIORITY 1
229+
230+
/*
231+
* On USBHS, we cannot access the DWC2 register until VBUS is detected and
232+
* valid. If the user tries to force usbd_enable() and the corresponding
233+
* uhc_enable() without a "VBUS ready" notification, the event wait will block
234+
* until a valid VBUS signal is detected or until the
235+
* CONFIG_UHC_DWC2_USBHS_VBUS_READY_TIMEOUT timeout expires.
236+
*/
237+
static K_EVENT_DEFINE(usbhs_events);
238+
#define USBHS_VBUS_READY BIT(0)
239+
#define USBHS_VBUS_REMOVED BIT(1)
240+
241+
static struct onoff_manager *pclk24m_mgr;
242+
static struct onoff_client pclk24m_cli;
243+
244+
static void vregusb_isr(const void *arg)
245+
{
246+
//const struct device *dev = arg;
247+
248+
if (NRF_VREGUSB->EVENTS_VBUSDETECTED) {
249+
NRF_VREGUSB->EVENTS_VBUSDETECTED = 0;
250+
k_event_post(&usbhs_events, USBHS_VBUS_READY);
251+
//uhc_submit_event(dev, UHC_EVT_VBUS_READY, 0);
252+
}
253+
254+
if (NRF_VREGUSB->EVENTS_VBUSREMOVED) {
255+
NRF_VREGUSB->EVENTS_VBUSREMOVED = 0;
256+
k_event_set_masked(&usbhs_events, 0, USBHS_VBUS_REMOVED);
257+
//uhc_submit_event(dev, UHC_EVT_VBUS_REMOVED, 0);
258+
}
259+
}
260+
261+
static inline int usbhs_enable_core(const struct device *dev)
262+
{
263+
LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL);
264+
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);
265+
k_timeout_t timeout = K_FOREVER;
266+
int err;
267+
268+
if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_NO_WAIT)) {
269+
LOG_WRN("VBUS is not ready, block uhc_enable()");
270+
if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, timeout)) {
271+
return -ETIMEDOUT;
272+
}
273+
}
274+
275+
/* Request PCLK24M using clock control driver */
276+
sys_notify_init_spinwait(&pclk24m_cli.notify);
277+
err = onoff_request(pclk24m_mgr, &pclk24m_cli);
278+
if (err < 0) {
279+
LOG_ERR("Failed to start PCLK24M %d", err);
280+
return err;
281+
}
282+
283+
/* Power up peripheral */
284+
wrapper->ENABLE = USBHS_ENABLE_CORE_Msk;
285+
286+
/* Set ID to Host and disable D+ pull-up */
287+
wrapper->PHY.OVERRIDEVALUES = (1 << 24) | (1 << 23);
288+
wrapper->PHY.INPUTOVERRIDE = (1 << 31) | (1 << 30) | (1 << 24) | (1 << 23);
289+
290+
/* Release PHY power-on reset */
291+
wrapper->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk;
292+
293+
/* Wait for PHY clock to start */
294+
k_busy_wait(45);
295+
296+
/* Release DWC2 reset */
297+
wrapper->TASKS_START = 1UL;
298+
299+
/* Wait for clock to start to avoid hang on too early register read */
300+
k_busy_wait(1);
301+
302+
return 0;
303+
}
304+
305+
static inline int usbhs_disable_core(const struct device *dev)
306+
{
307+
LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL);
308+
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);
309+
int err;
310+
311+
/* Set ID to Device and forcefully disable D+ pull-up */
312+
wrapper->PHY.OVERRIDEVALUES = (1 << 31);
313+
wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk;
314+
315+
wrapper->ENABLE = 0UL;
316+
317+
/* Release PCLK24M using clock control driver */
318+
err = onoff_cancel_or_release(pclk24m_mgr, &pclk24m_cli);
319+
if (err < 0) {
320+
LOG_ERR("Failed to stop PCLK24M %d", err);
321+
return err;
322+
}
323+
324+
return 0;
325+
}
326+
327+
static inline int usbhs_init_vreg_and_clock_and_core(const struct device *dev)
328+
{
329+
/* Init VREG */
330+
331+
IRQ_CONNECT(VREGUSB_IRQn, NRF_DEFAULT_IRQ_PRIORITY,
332+
vregusb_isr, DEVICE_DT_INST_GET(0), 0);
333+
334+
NRF_VREGUSB->INTEN = VREGUSB_INTEN_VBUSDETECTED_Msk |
335+
VREGUSB_INTEN_VBUSREMOVED_Msk;
336+
NRF_VREGUSB->TASKS_START = 1;
337+
338+
/* TODO: Determine conditions when VBUSDETECTED is not generated */
339+
if (sys_read32((mem_addr_t)NRF_VREGUSB + 0x400) & BIT(2)) {
340+
k_event_post(&usbhs_events, USBHS_VBUS_READY);
341+
//uhc_submit_event(dev, UHC_EVT_VBUS_READY, 0);
342+
}
343+
344+
irq_enable(VREGUSB_IRQn);
345+
346+
/* Init the clock */
347+
348+
pclk24m_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF24M);
349+
350+
/* Enable the core */
351+
352+
return usbhs_enable_core(dev);
353+
354+
/* It is now possible to access the configuration registers */
355+
}
356+
357+
static inline int usbhs_disable_vreg(const struct device *dev)
358+
{
359+
NRF_VREGUSB->INTEN = 0;
360+
NRF_VREGUSB->TASKS_STOP = 1;
361+
362+
return 0;
363+
}
364+
365+
static inline int usbhs_init_caps(const struct device *dev)
366+
{
367+
struct uhc_data *data = dev->data;
368+
369+
data->caps.hs = true;
370+
371+
return 0;
372+
}
373+
374+
static inline int usbhs_is_phy_clk_off(const struct device *dev)
375+
{
376+
return !k_event_test(&usbhs_events, USBHS_VBUS_READY);
377+
}
378+
379+
static inline int usbhs_post_hibernation_entry(const struct device *dev)
380+
{
381+
const struct uhc_dwc2_config *const config = dev->config;
382+
struct usb_dwc2_reg *const base = config->base;
383+
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);
384+
385+
sys_set_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK);
386+
387+
wrapper->TASKS_STOP = 1;
388+
389+
return 0;
390+
}
391+
392+
static inline int usbhs_pre_hibernation_exit(const struct device *dev)
393+
{
394+
const struct uhc_dwc2_config *const config = dev->config;
395+
struct usb_dwc2_reg *const base = config->base;
396+
NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0);
397+
398+
sys_clear_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK);
399+
400+
wrapper->TASKS_START = 1;
401+
402+
return 0;
403+
}
404+
405+
#define UHC_DWC2_IRQ_FLAGS_TYPE0(n) 0
406+
#define UHC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type)
407+
408+
#define UHC_DWC2_IRQ_FLAGS(n) \
409+
_CONCAT(UHC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n)
410+
411+
#define QUIRK_NRF_USBHS_DEFINE(n) \
412+
\
413+
static int usbhs_irq_enable_func_##n(const struct device *dev) \
414+
{ \
415+
IRQ_CONNECT(DT_INST_IRQN(n), \
416+
DT_INST_IRQ(n, priority), \
417+
uhc_dwc2_isr_handler, \
418+
DEVICE_DT_INST_GET(n), \
419+
UHC_DWC2_IRQ_FLAGS(n)); \
420+
\
421+
irq_enable(DT_INST_IRQN(n)); \
422+
\
423+
return 0; \
424+
} \
425+
\
426+
static int usbhs_irq_disable_func_##n(const struct device *dev) \
427+
{ \
428+
irq_disable(DT_INST_IRQN(n)); \
429+
\
430+
return 0; \
431+
} \
432+
\
433+
struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \
434+
.init = usbhs_init_vreg_and_clock_and_core, \
435+
.pre_enable = usbhs_enable_core, \
436+
.disable = usbhs_disable_core, \
437+
.shutdown = usbhs_disable_vreg, \
438+
.caps = usbhs_init_caps, \
439+
.is_phy_clk_off = usbhs_is_phy_clk_off, \
440+
.post_hibernation_entry = usbhs_post_hibernation_entry, \
441+
.pre_hibernation_exit = usbhs_pre_hibernation_exit, \
442+
.irq_enable_func = usbhs_irq_enable_func_##n, \
443+
.irq_disable_func = usbhs_irq_disable_func_##n, \
444+
};
445+
446+
DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE)
447+
448+
/* TODO remove from uhc_dwc2.c */
449+
#define IRAM_ATTR
450+
451+
#endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) */
452+
216453
/* Add next vendor quirks definition above this line */
217454

218455
#endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H */
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
zephyr_uhc0: &usbhs {
7+
status = "okay";
8+
};

0 commit comments

Comments
 (0)