|
1 | 1 | /* |
2 | 2 | * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. |
| 3 | + * Copyright (c) 2025 Nordic Semiconductor ASA |
3 | 4 | * |
4 | 5 | * SPDX-License-Identifier: Apache-2.0 |
5 | 6 | */ |
|
13 | 14 | #include <zephyr/device.h> |
14 | 15 | #include <zephyr/drivers/usb/uhc.h> |
15 | 16 |
|
| 17 | +static void uhc_dwc2_isr_handler(const struct device *dev); |
| 18 | + |
16 | 19 | #if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) |
17 | 20 |
|
18 | 21 | #include <zephyr/logging/log.h> |
@@ -213,6 +216,240 @@ DT_INST_FOREACH_STATUS_OKAY(QUIRK_ESP32_USB_OTG_DEFINE) |
213 | 216 |
|
214 | 217 | #endif /*DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) */ |
215 | 218 |
|
| 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 | + |
216 | 453 | /* Add next vendor quirks definition above this line */ |
217 | 454 |
|
218 | 455 | #endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H */ |
0 commit comments