| /* |
| * Copyright (c) 2021 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file udc_nrf.c |
| * @brief Nordic USB device controller (UDC) driver |
| * |
| * The driver implements the interface between the nRF USBD peripheral |
| * driver from nrfx package and UDC API. |
| */ |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <soc.h> |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/usb/udc.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/nrf_clock_control.h> |
| |
| #include <nrf_usbd_common.h> |
| #include <hal/nrf_usbd.h> |
| #include <nrfx_power.h> |
| #include "udc_common.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(udc_nrf, CONFIG_UDC_DRIVER_LOG_LEVEL); |
| |
| /* |
| * There is no real advantage to change control endpoint size |
| * but we can use it for testing UDC driver API and higher layers. |
| */ |
| #define UDC_NRF_MPS0 UDC_MPS0_64 |
| #define UDC_NRF_EP0_SIZE 64 |
| |
| enum udc_nrf_event_type { |
| /* An event generated by the HAL driver */ |
| UDC_NRF_EVT_HAL, |
| /* Shim driver event to trigger next transfer */ |
| UDC_NRF_EVT_XFER, |
| /* Let controller perform status stage */ |
| UDC_NRF_EVT_STATUS_IN, |
| }; |
| |
| struct udc_nrf_evt { |
| enum udc_nrf_event_type type; |
| union { |
| nrf_usbd_common_evt_t hal_evt; |
| uint8_t ep; |
| }; |
| }; |
| |
| K_MSGQ_DEFINE(drv_msgq, sizeof(struct udc_nrf_evt), |
| CONFIG_UDC_NRF_MAX_QMESSAGES, sizeof(uint32_t)); |
| |
| static K_KERNEL_STACK_DEFINE(drv_stack, CONFIG_UDC_NRF_THREAD_STACK_SIZE); |
| static struct k_thread drv_stack_data; |
| |
| /* USB device controller access from devicetree */ |
| #define DT_DRV_COMPAT nordic_nrf_usbd |
| |
| #define CFG_EPIN_CNT DT_INST_PROP(0, num_in_endpoints) |
| #define CFG_EPOUT_CNT DT_INST_PROP(0, num_out_endpoints) |
| #define CFG_EP_ISOIN_CNT DT_INST_PROP(0, num_isoin_endpoints) |
| #define CFG_EP_ISOOUT_CNT DT_INST_PROP(0, num_isoout_endpoints) |
| |
| static struct udc_ep_config ep_cfg_out[CFG_EPOUT_CNT + CFG_EP_ISOOUT_CNT + 1]; |
| static struct udc_ep_config ep_cfg_in[CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + 1]; |
| static bool udc_nrf_setup_rcvd; |
| const static struct device *udc_nrf_dev; |
| |
| struct udc_nrf_config { |
| clock_control_subsys_t clock; |
| nrfx_power_config_t pwr; |
| nrfx_power_usbevt_config_t evt; |
| }; |
| |
| static struct onoff_manager *hfxo_mgr; |
| static struct onoff_client hfxo_cli; |
| |
| static void udc_nrf_clear_control_out(const struct device *dev) |
| { |
| if (nrf_usbd_common_last_setup_dir_get() == USB_CONTROL_EP_OUT && |
| udc_nrf_setup_rcvd) { |
| /* Allow data chunk on EP0 OUT */ |
| nrf_usbd_common_setup_data_clear(); |
| udc_nrf_setup_rcvd = false; |
| LOG_INF("Allow data OUT"); |
| } |
| } |
| |
| static void udc_event_xfer_in_next(const struct device *dev, const uint8_t ep) |
| { |
| struct net_buf *buf; |
| |
| if (udc_ep_is_busy(dev, ep)) { |
| return; |
| } |
| |
| buf = udc_buf_peek(dev, ep); |
| if (buf != NULL) { |
| nrf_usbd_common_transfer_t xfer = { |
| .p_data = {.tx = buf->data}, |
| .size = buf->len, |
| .flags = udc_ep_buf_has_zlp(buf) ? |
| NRF_USBD_COMMON_TRANSFER_ZLP_FLAG : 0, |
| }; |
| nrfx_err_t err; |
| |
| err = nrf_usbd_common_ep_transfer(ep, &xfer); |
| if (err != NRFX_SUCCESS) { |
| LOG_ERR("ep 0x%02x nrfx error: %x", ep, err); |
| /* REVISE: remove from endpoint queue? ASSERT? */ |
| udc_submit_ep_event(dev, buf, -ECONNREFUSED); |
| } else { |
| udc_ep_set_busy(dev, ep, true); |
| } |
| } |
| } |
| |
| static void udc_event_xfer_ctrl_in(const struct device *dev, |
| struct net_buf *const buf) |
| { |
| if (udc_ctrl_stage_is_status_in(dev) || |
| udc_ctrl_stage_is_no_data(dev)) { |
| /* Status stage finished, notify upper layer */ |
| udc_ctrl_submit_status(dev, buf); |
| } |
| |
| if (udc_ctrl_stage_is_data_in(dev)) { |
| /* |
| * s-in-[status] finished, release buffer. |
| * Since the controller supports auto-status we cannot use |
| * if (udc_ctrl_stage_is_status_out()) after state update. |
| */ |
| net_buf_unref(buf); |
| } |
| |
| /* Update to next stage of control transfer */ |
| udc_ctrl_update_stage(dev, buf); |
| |
| nrf_usbd_common_setup_clear(); |
| } |
| |
| static void udc_event_fake_status_in(const struct device *dev) |
| { |
| struct net_buf *buf; |
| |
| buf = udc_buf_get(dev, USB_CONTROL_EP_IN); |
| if (unlikely(buf == NULL)) { |
| LOG_DBG("ep 0x%02x queue is empty", USB_CONTROL_EP_IN); |
| return; |
| } |
| |
| LOG_DBG("Fake status IN %p", buf); |
| udc_event_xfer_ctrl_in(dev, buf); |
| } |
| |
| static void udc_event_xfer_in(const struct device *dev, |
| nrf_usbd_common_evt_t const *const event) |
| { |
| uint8_t ep = event->data.eptransfer.ep; |
| struct net_buf *buf; |
| |
| switch (event->data.eptransfer.status) { |
| case NRF_USBD_COMMON_EP_OK: |
| buf = udc_buf_get(dev, ep); |
| if (buf == NULL) { |
| LOG_ERR("ep 0x%02x queue is empty", ep); |
| __ASSERT_NO_MSG(false); |
| return; |
| } |
| |
| udc_ep_set_busy(dev, ep, false); |
| if (ep == USB_CONTROL_EP_IN) { |
| return udc_event_xfer_ctrl_in(dev, buf); |
| } |
| |
| udc_submit_ep_event(dev, buf, 0); |
| break; |
| |
| case NRF_USBD_COMMON_EP_ABORTED: |
| LOG_WRN("aborted IN ep 0x%02x", ep); |
| buf = udc_buf_get_all(dev, ep); |
| |
| if (buf == NULL) { |
| LOG_DBG("ep 0x%02x queue is empty", ep); |
| return; |
| } |
| |
| udc_ep_set_busy(dev, ep, false); |
| udc_submit_ep_event(dev, buf, -ECONNABORTED); |
| break; |
| |
| default: |
| LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", |
| event->data.eptransfer.status, ep); |
| udc_submit_event(dev, UDC_EVT_ERROR, -EIO); |
| break; |
| } |
| } |
| |
| static void udc_event_xfer_ctrl_out(const struct device *dev, |
| struct net_buf *const buf) |
| { |
| /* |
| * In case s-in-status, controller supports auto-status therefore we |
| * do not have to call udc_ctrl_stage_is_status_out(). |
| */ |
| |
| /* Update to next stage of control transfer */ |
| udc_ctrl_update_stage(dev, buf); |
| |
| if (udc_ctrl_stage_is_status_in(dev)) { |
| udc_ctrl_submit_s_out_status(dev, buf); |
| } |
| } |
| |
| static void udc_event_xfer_out_next(const struct device *dev, const uint8_t ep) |
| { |
| struct net_buf *buf; |
| |
| if (udc_ep_is_busy(dev, ep)) { |
| return; |
| } |
| |
| buf = udc_buf_peek(dev, ep); |
| if (buf != NULL) { |
| nrf_usbd_common_transfer_t xfer = { |
| .p_data = {.rx = buf->data}, |
| .size = buf->size, |
| .flags = 0, |
| }; |
| nrfx_err_t err; |
| |
| err = nrf_usbd_common_ep_transfer(ep, &xfer); |
| if (err != NRFX_SUCCESS) { |
| LOG_ERR("ep 0x%02x nrfx error: %x", ep, err); |
| /* REVISE: remove from endpoint queue? ASSERT? */ |
| udc_submit_ep_event(dev, buf, -ECONNREFUSED); |
| } else { |
| udc_ep_set_busy(dev, ep, true); |
| } |
| } else { |
| LOG_DBG("ep 0x%02x waiting, queue is empty", ep); |
| } |
| } |
| |
| static void udc_event_xfer_out(const struct device *dev, |
| nrf_usbd_common_evt_t const *const event) |
| { |
| uint8_t ep = event->data.eptransfer.ep; |
| nrf_usbd_common_ep_status_t err_code; |
| struct net_buf *buf; |
| size_t len; |
| |
| switch (event->data.eptransfer.status) { |
| case NRF_USBD_COMMON_EP_WAITING: |
| /* |
| * There is nothing to do here, new transfer |
| * will be tried in both cases later. |
| */ |
| break; |
| |
| case NRF_USBD_COMMON_EP_OK: |
| err_code = nrf_usbd_common_ep_status_get(ep, &len); |
| if (err_code != NRF_USBD_COMMON_EP_OK) { |
| LOG_ERR("OUT transfer failed %d", err_code); |
| } |
| |
| buf = udc_buf_get(dev, ep); |
| if (buf == NULL) { |
| LOG_ERR("ep 0x%02x ok, queue is empty", ep); |
| return; |
| } |
| |
| net_buf_add(buf, len); |
| udc_ep_set_busy(dev, ep, false); |
| if (ep == USB_CONTROL_EP_OUT) { |
| udc_event_xfer_ctrl_out(dev, buf); |
| } else { |
| udc_submit_ep_event(dev, buf, 0); |
| } |
| |
| break; |
| |
| default: |
| LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", |
| event->data.eptransfer.status, ep); |
| udc_submit_event(dev, UDC_EVT_ERROR, -EIO); |
| break; |
| } |
| } |
| |
| static int usbd_ctrl_feed_dout(const struct device *dev, |
| const size_t length) |
| { |
| |
| struct udc_ep_config *cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); |
| struct net_buf *buf; |
| |
| buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); |
| if (buf == NULL) { |
| return -ENOMEM; |
| } |
| |
| net_buf_put(&cfg->fifo, buf); |
| udc_nrf_clear_control_out(dev); |
| |
| return 0; |
| } |
| |
| static int udc_event_xfer_setup(const struct device *dev) |
| { |
| struct net_buf *buf; |
| int err; |
| |
| buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, |
| sizeof(struct usb_setup_packet)); |
| if (buf == NULL) { |
| LOG_ERR("Failed to allocate for setup"); |
| return -ENOMEM; |
| } |
| |
| udc_ep_buf_set_setup(buf); |
| nrf_usbd_common_setup_get((nrf_usbd_common_setup_t *)buf->data); |
| net_buf_add(buf, sizeof(nrf_usbd_common_setup_t)); |
| udc_nrf_setup_rcvd = true; |
| |
| /* Update to next stage of control transfer */ |
| udc_ctrl_update_stage(dev, buf); |
| |
| if (udc_ctrl_stage_is_data_out(dev)) { |
| /* Allocate and feed buffer for data OUT stage */ |
| LOG_DBG("s:%p|feed for -out-", buf); |
| err = usbd_ctrl_feed_dout(dev, udc_data_stage_length(buf)); |
| if (err == -ENOMEM) { |
| err = udc_submit_ep_event(dev, buf, err); |
| } |
| } else if (udc_ctrl_stage_is_data_in(dev)) { |
| err = udc_ctrl_submit_s_in_status(dev); |
| } else { |
| err = udc_ctrl_submit_s_status(dev); |
| } |
| |
| return err; |
| } |
| |
| static void udc_nrf_thread(void *p1, void *p2, void *p3) |
| { |
| ARG_UNUSED(p2); |
| ARG_UNUSED(p3); |
| |
| const struct device *dev = p1; |
| |
| while (true) { |
| bool start_xfer = false; |
| struct udc_nrf_evt evt; |
| uint8_t ep; |
| |
| k_msgq_get(&drv_msgq, &evt, K_FOREVER); |
| |
| switch (evt.type) { |
| case UDC_NRF_EVT_HAL: |
| ep = evt.hal_evt.data.eptransfer.ep; |
| switch (evt.hal_evt.type) { |
| case NRF_USBD_COMMON_EVT_EPTRANSFER: |
| start_xfer = true; |
| if (USB_EP_DIR_IS_IN(ep)) { |
| udc_event_xfer_in(dev, &evt.hal_evt); |
| } else { |
| udc_event_xfer_out(dev, &evt.hal_evt); |
| } |
| break; |
| case NRF_USBD_COMMON_EVT_SETUP: |
| udc_event_xfer_setup(dev); |
| break; |
| default: |
| break; |
| } |
| break; |
| case UDC_NRF_EVT_XFER: |
| start_xfer = true; |
| ep = evt.ep; |
| break; |
| case UDC_NRF_EVT_STATUS_IN: |
| udc_event_fake_status_in(dev); |
| break; |
| } |
| |
| if (start_xfer) { |
| if (USB_EP_DIR_IS_IN(ep)) { |
| udc_event_xfer_in_next(dev, ep); |
| } else { |
| udc_event_xfer_out_next(dev, ep); |
| } |
| } |
| } |
| } |
| |
| static void udc_sof_check_iso_out(const struct device *dev) |
| { |
| const uint8_t iso_out_addr = 0x08; |
| struct udc_nrf_evt evt = { |
| .type = UDC_NRF_EVT_XFER, |
| .ep = iso_out_addr, |
| }; |
| struct udc_ep_config *ep_cfg; |
| |
| ep_cfg = udc_get_ep_cfg(dev, iso_out_addr); |
| if (ep_cfg == NULL) { |
| return; |
| } |
| |
| if (ep_cfg->stat.enabled && !k_fifo_is_empty(&ep_cfg->fifo)) { |
| k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); |
| } |
| } |
| |
| static void usbd_event_handler(nrf_usbd_common_evt_t const *const hal_evt) |
| { |
| switch (hal_evt->type) { |
| case NRF_USBD_COMMON_EVT_SUSPEND: |
| LOG_INF("SUSPEND state detected"); |
| nrf_usbd_common_suspend(); |
| udc_set_suspended(udc_nrf_dev, true); |
| udc_submit_event(udc_nrf_dev, UDC_EVT_SUSPEND, 0); |
| break; |
| case NRF_USBD_COMMON_EVT_RESUME: |
| LOG_INF("RESUMING from suspend"); |
| udc_set_suspended(udc_nrf_dev, false); |
| udc_submit_event(udc_nrf_dev, UDC_EVT_RESUME, 0); |
| break; |
| case NRF_USBD_COMMON_EVT_WUREQ: |
| LOG_INF("Remote wakeup initiated"); |
| udc_set_suspended(udc_nrf_dev, false); |
| udc_submit_event(udc_nrf_dev, UDC_EVT_RESUME, 0); |
| break; |
| case NRF_USBD_COMMON_EVT_RESET: |
| LOG_INF("Reset"); |
| udc_submit_event(udc_nrf_dev, UDC_EVT_RESET, 0); |
| break; |
| case NRF_USBD_COMMON_EVT_SOF: |
| udc_submit_event(udc_nrf_dev, UDC_EVT_SOF, 0); |
| udc_sof_check_iso_out(udc_nrf_dev); |
| break; |
| case NRF_USBD_COMMON_EVT_EPTRANSFER: |
| case NRF_USBD_COMMON_EVT_SETUP: { |
| struct udc_nrf_evt evt = { |
| .type = UDC_NRF_EVT_HAL, |
| .hal_evt = *hal_evt, |
| }; |
| |
| /* Forward these two to the thread since mutually exclusive |
| * access to the controller is necessary. |
| */ |
| k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| static void udc_nrf_power_handler(nrfx_power_usb_evt_t pwr_evt) |
| { |
| switch (pwr_evt) { |
| case NRFX_POWER_USB_EVT_DETECTED: |
| LOG_DBG("POWER event detected"); |
| break; |
| case NRFX_POWER_USB_EVT_READY: |
| LOG_INF("POWER event ready"); |
| udc_submit_event(udc_nrf_dev, UDC_EVT_VBUS_READY, 0); |
| nrf_usbd_common_start(true); |
| break; |
| case NRFX_POWER_USB_EVT_REMOVED: |
| LOG_INF("POWER event removed"); |
| udc_submit_event(udc_nrf_dev, UDC_EVT_VBUS_REMOVED, 0); |
| break; |
| default: |
| LOG_ERR("Unknown power event %d", pwr_evt); |
| } |
| } |
| |
| static void udc_nrf_fake_status_in(const struct device *dev) |
| { |
| struct udc_nrf_evt evt = { |
| .type = UDC_NRF_EVT_STATUS_IN, |
| .ep = USB_CONTROL_EP_IN, |
| }; |
| |
| if (nrf_usbd_common_last_setup_dir_get() == USB_CONTROL_EP_OUT) { |
| /* Let controller perform status IN stage */ |
| k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); |
| } |
| } |
| |
| static int udc_nrf_ep_enqueue(const struct device *dev, |
| struct udc_ep_config *cfg, |
| struct net_buf *buf) |
| { |
| struct udc_nrf_evt evt = { |
| .type = UDC_NRF_EVT_XFER, |
| .ep = cfg->addr, |
| }; |
| |
| udc_buf_put(cfg, buf); |
| |
| if (cfg->addr == USB_CONTROL_EP_IN && buf->len == 0) { |
| udc_nrf_fake_status_in(dev); |
| return 0; |
| } |
| |
| k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); |
| |
| return 0; |
| } |
| |
| static int udc_nrf_ep_dequeue(const struct device *dev, |
| struct udc_ep_config *cfg) |
| { |
| bool busy = nrf_usbd_common_ep_is_busy(cfg->addr); |
| |
| nrf_usbd_common_ep_abort(cfg->addr); |
| if (USB_EP_DIR_IS_OUT(cfg->addr) || !busy) { |
| struct net_buf *buf; |
| |
| /* |
| * HAL driver does not generate event for an OUT endpoint |
| * or when IN endpoint is not busy. |
| */ |
| buf = udc_buf_get_all(dev, cfg->addr); |
| if (buf) { |
| udc_submit_ep_event(dev, buf, -ECONNABORTED); |
| } else { |
| LOG_INF("ep 0x%02x queue is empty", cfg->addr); |
| } |
| |
| } |
| |
| udc_ep_set_busy(dev, cfg->addr, false); |
| |
| return 0; |
| } |
| |
| static int udc_nrf_ep_enable(const struct device *dev, |
| struct udc_ep_config *cfg) |
| { |
| uint16_t mps; |
| |
| __ASSERT_NO_MSG(cfg); |
| mps = (cfg->mps == 0) ? cfg->caps.mps : cfg->mps; |
| nrf_usbd_common_ep_max_packet_size_set(cfg->addr, mps); |
| nrf_usbd_common_ep_enable(cfg->addr); |
| if (!NRF_USBD_EPISO_CHECK(cfg->addr)) { |
| /* ISO transactions for full-speed device do not support |
| * toggle sequencing and should only send DATA0 PID. |
| */ |
| nrf_usbd_common_ep_dtoggle_clear(cfg->addr); |
| nrf_usbd_common_ep_stall_clear(cfg->addr); |
| } |
| |
| LOG_DBG("Enable ep 0x%02x", cfg->addr); |
| |
| return 0; |
| } |
| |
| static int udc_nrf_ep_disable(const struct device *dev, |
| struct udc_ep_config *cfg) |
| { |
| __ASSERT_NO_MSG(cfg); |
| nrf_usbd_common_ep_disable(cfg->addr); |
| LOG_DBG("Disable ep 0x%02x", cfg->addr); |
| |
| return 0; |
| } |
| |
| static int udc_nrf_ep_set_halt(const struct device *dev, |
| struct udc_ep_config *cfg) |
| { |
| LOG_DBG("Halt ep 0x%02x", cfg->addr); |
| |
| if (cfg->addr == USB_CONTROL_EP_OUT || |
| cfg->addr == USB_CONTROL_EP_IN) { |
| nrf_usbd_common_setup_stall(); |
| } else { |
| nrf_usbd_common_ep_stall(cfg->addr); |
| } |
| |
| return 0; |
| } |
| |
| static int udc_nrf_ep_clear_halt(const struct device *dev, |
| struct udc_ep_config *cfg) |
| { |
| LOG_DBG("Clear halt ep 0x%02x", cfg->addr); |
| |
| nrf_usbd_common_ep_dtoggle_clear(cfg->addr); |
| nrf_usbd_common_ep_stall_clear(cfg->addr); |
| |
| return 0; |
| } |
| |
| static int udc_nrf_set_address(const struct device *dev, const uint8_t addr) |
| { |
| /** |
| * Nothing to do here. The USBD HW already takes care of initiating |
| * STATUS stage. Just double check the address for sanity. |
| */ |
| if (addr != (uint8_t)NRF_USBD->USBADDR) { |
| LOG_WRN("USB Address incorrect 0x%02x", addr); |
| } |
| |
| return 0; |
| } |
| |
| static int udc_nrf_host_wakeup(const struct device *dev) |
| { |
| bool res = nrf_usbd_common_wakeup_req(); |
| |
| LOG_DBG("Host wakeup request"); |
| if (!res) { |
| return -EAGAIN; |
| } |
| |
| return 0; |
| } |
| |
| static int udc_nrf_enable(const struct device *dev) |
| { |
| int ret; |
| |
| nrf_usbd_common_enable(); |
| |
| sys_notify_init_spinwait(&hfxo_cli.notify); |
| ret = onoff_request(hfxo_mgr, &hfxo_cli); |
| if (ret < 0) { |
| LOG_ERR("Failed to start HFXO %d", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int udc_nrf_disable(const struct device *dev) |
| { |
| int ret; |
| |
| nrf_usbd_common_disable(); |
| |
| ret = onoff_cancel_or_release(hfxo_mgr, &hfxo_cli); |
| if (ret < 0) { |
| LOG_ERR("Failed to stop HFXO %d", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int udc_nrf_init(const struct device *dev) |
| { |
| const struct udc_nrf_config *cfg = dev->config; |
| int ret; |
| |
| hfxo_mgr = z_nrf_clock_control_get_onoff(cfg->clock); |
| |
| #ifdef CONFIG_HAS_HW_NRF_USBREG |
| /* Use CLOCK/POWER priority for compatibility with other series where |
| * USB events are handled by CLOCK interrupt handler. |
| */ |
| IRQ_CONNECT(USBREGULATOR_IRQn, |
| DT_IRQ(DT_INST(0, nordic_nrf_clock), priority), |
| nrfx_isr, nrfx_usbreg_irq_handler, 0); |
| irq_enable(USBREGULATOR_IRQn); |
| #endif |
| |
| IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), |
| nrfx_isr, nrf_usbd_common_irq_handler, 0); |
| |
| (void)nrfx_power_init(&cfg->pwr); |
| nrfx_power_usbevt_init(&cfg->evt); |
| |
| ret = nrf_usbd_common_init(usbd_event_handler); |
| if (ret != NRFX_SUCCESS) { |
| LOG_ERR("nRF USBD driver initialization failed"); |
| return -EIO; |
| } |
| |
| nrfx_power_usbevt_enable(); |
| if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, |
| USB_EP_TYPE_CONTROL, UDC_NRF_EP0_SIZE, 0)) { |
| LOG_ERR("Failed to enable control endpoint"); |
| return -EIO; |
| } |
| |
| if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, |
| USB_EP_TYPE_CONTROL, UDC_NRF_EP0_SIZE, 0)) { |
| LOG_ERR("Failed to enable control endpoint"); |
| return -EIO; |
| } |
| |
| LOG_INF("Initialized"); |
| |
| return 0; |
| } |
| |
| static int udc_nrf_shutdown(const struct device *dev) |
| { |
| LOG_INF("shutdown"); |
| |
| if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { |
| LOG_ERR("Failed to disable control endpoint"); |
| return -EIO; |
| } |
| |
| if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { |
| LOG_ERR("Failed to disable control endpoint"); |
| return -EIO; |
| } |
| |
| nrfx_power_usbevt_disable(); |
| nrf_usbd_common_uninit(); |
| nrfx_power_usbevt_uninit(); |
| #ifdef CONFIG_HAS_HW_NRF_USBREG |
| irq_disable(USBREGULATOR_IRQn); |
| #endif |
| |
| return 0; |
| } |
| |
| static int udc_nrf_driver_init(const struct device *dev) |
| { |
| struct udc_data *data = dev->data; |
| int err; |
| |
| LOG_INF("Preinit"); |
| udc_nrf_dev = dev; |
| k_mutex_init(&data->mutex); |
| k_thread_create(&drv_stack_data, drv_stack, |
| K_KERNEL_STACK_SIZEOF(drv_stack), |
| udc_nrf_thread, |
| (void *)dev, NULL, NULL, |
| K_PRIO_COOP(8), 0, K_NO_WAIT); |
| |
| k_thread_name_set(&drv_stack_data, "udc_nrfx"); |
| |
| for (int i = 0; i < ARRAY_SIZE(ep_cfg_out); i++) { |
| ep_cfg_out[i].caps.out = 1; |
| if (i == 0) { |
| ep_cfg_out[i].caps.control = 1; |
| ep_cfg_out[i].caps.mps = NRF_USBD_COMMON_EPSIZE; |
| } else if (i < (CFG_EPOUT_CNT + 1)) { |
| ep_cfg_out[i].caps.bulk = 1; |
| ep_cfg_out[i].caps.interrupt = 1; |
| ep_cfg_out[i].caps.mps = NRF_USBD_COMMON_EPSIZE; |
| } else { |
| ep_cfg_out[i].caps.iso = 1; |
| ep_cfg_out[i].caps.mps = NRF_USBD_COMMON_ISOSIZE / 2; |
| } |
| |
| ep_cfg_out[i].addr = USB_EP_DIR_OUT | i; |
| err = udc_register_ep(dev, &ep_cfg_out[i]); |
| if (err != 0) { |
| LOG_ERR("Failed to register endpoint"); |
| return err; |
| } |
| } |
| |
| for (int i = 0; i < ARRAY_SIZE(ep_cfg_in); i++) { |
| ep_cfg_in[i].caps.in = 1; |
| if (i == 0) { |
| ep_cfg_in[i].caps.control = 1; |
| ep_cfg_in[i].caps.mps = NRF_USBD_COMMON_EPSIZE; |
| } else if (i < (CFG_EPIN_CNT + 1)) { |
| ep_cfg_in[i].caps.bulk = 1; |
| ep_cfg_in[i].caps.interrupt = 1; |
| ep_cfg_in[i].caps.mps = NRF_USBD_COMMON_EPSIZE; |
| } else { |
| ep_cfg_in[i].caps.iso = 1; |
| ep_cfg_in[i].caps.mps = NRF_USBD_COMMON_ISOSIZE / 2; |
| } |
| |
| ep_cfg_in[i].addr = USB_EP_DIR_IN | i; |
| err = udc_register_ep(dev, &ep_cfg_in[i]); |
| if (err != 0) { |
| LOG_ERR("Failed to register endpoint"); |
| return err; |
| } |
| } |
| |
| data->caps.rwup = true; |
| data->caps.out_ack = true; |
| data->caps.mps0 = UDC_NRF_MPS0; |
| |
| return 0; |
| } |
| |
| static int udc_nrf_lock(const struct device *dev) |
| { |
| return udc_lock_internal(dev, K_FOREVER); |
| } |
| |
| static int udc_nrf_unlock(const struct device *dev) |
| { |
| return udc_unlock_internal(dev); |
| } |
| |
| static const struct udc_nrf_config udc_nrf_cfg = { |
| .clock = COND_CODE_1(NRF_CLOCK_HAS_HFCLK192M, |
| (CLOCK_CONTROL_NRF_SUBSYS_HF192M), |
| (CLOCK_CONTROL_NRF_SUBSYS_HF)), |
| .pwr = { |
| .dcdcen = IS_ENABLED(CONFIG_SOC_DCDC_NRF52X) || |
| IS_ENABLED(CONFIG_SOC_DCDC_NRF53X_APP), |
| #if NRFX_POWER_SUPPORTS_DCDCEN_VDDH |
| .dcdcenhv = IS_ENABLED(CONFIG_SOC_DCDC_NRF52X_HV) || |
| IS_ENABLED(CONFIG_SOC_DCDC_NRF53X_HV), |
| #endif |
| }, |
| |
| .evt = { |
| .handler = udc_nrf_power_handler |
| }, |
| }; |
| |
| static struct udc_data udc_nrf_data = { |
| .mutex = Z_MUTEX_INITIALIZER(udc_nrf_data.mutex), |
| .priv = NULL, |
| }; |
| |
| static const struct udc_api udc_nrf_api = { |
| .lock = udc_nrf_lock, |
| .unlock = udc_nrf_unlock, |
| .init = udc_nrf_init, |
| .enable = udc_nrf_enable, |
| .disable = udc_nrf_disable, |
| .shutdown = udc_nrf_shutdown, |
| .set_address = udc_nrf_set_address, |
| .host_wakeup = udc_nrf_host_wakeup, |
| .ep_try_config = NULL, |
| .ep_enable = udc_nrf_ep_enable, |
| .ep_disable = udc_nrf_ep_disable, |
| .ep_set_halt = udc_nrf_ep_set_halt, |
| .ep_clear_halt = udc_nrf_ep_clear_halt, |
| .ep_enqueue = udc_nrf_ep_enqueue, |
| .ep_dequeue = udc_nrf_ep_dequeue, |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, udc_nrf_driver_init, NULL, |
| &udc_nrf_data, &udc_nrf_cfg, |
| POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| &udc_nrf_api); |