/* * Copyright (c) 2018, Nordic Semiconductor ASA * Copyright (c) 2018 Sundar Subramaniyan * * SPDX-License-Identifier: Apache-2.0 */ /** * @file usb_dc_nrfx.c * @brief Nordic USB device controller driver * * The driver implements the interface between the USBD peripheral * driver from nrfx package and the operating system. */ #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL #include #include LOG_MODULE_REGISTER(usb_nrfx); /* USB device controller access from devicetree */ #define DT_DRV_COMPAT nordic_nrf_usbd /** * @brief nRF USBD peripheral states */ enum usbd_periph_state { USBD_DETACHED, USBD_ATTACHED, USBD_POWERED, USBD_SUSPENDED, USBD_RESUMED, USBD_DEFAULT, USBD_ADDRESS_SET, USBD_CONFIGURED, }; /** * @brief Endpoint event types. */ enum usbd_ep_event_type { EP_EVT_SETUP_RECV, EP_EVT_RECV_REQ, EP_EVT_RECV_COMPLETE, EP_EVT_WRITE_COMPLETE, }; /** * @brief USBD peripheral event types. */ enum usbd_event_type { USBD_EVT_POWER, USBD_EVT_EP, USBD_EVT_RESET, USBD_EVT_SOF, USBD_EVT_REINIT }; /** * @brief Endpoint configuration. * * @param cb Endpoint callback. * @param max_sz Max packet size supported by endpoint. * @param en Enable/Disable flag. * @param addr Endpoint address. * @param type Endpoint transfer type. */ struct nrf_usbd_ep_cfg { usb_dc_ep_callback cb; uint32_t max_sz; bool en; uint8_t addr; enum usb_dc_ep_transfer_type type; }; struct usbd_mem_block { void *data; }; /** * @brief Endpoint buffer * * @param len Remaining length to be read/written. * @param block Mempool block, for freeing up buffer after use. * @param data Pointer to the data buffer for the endpoint. * @param curr Pointer to the current offset in the endpoint buffer. */ struct nrf_usbd_ep_buf { uint32_t len; struct usbd_mem_block block; uint8_t *data; uint8_t *curr; }; /** * @brief Endpoint context * * @param cfg Endpoint configuration * @param buf Endpoint buffer * @param read_complete A flag indicating that DMA read operation * has been completed. * @param read_pending A flag indicating that the Host has requested * a data transfer. * @param write_in_progress A flag indicating that write operation has * been scheduled. * @param trans_zlp Flag required for Control IN Endpoint. It * indicates that ZLP is required to end data * stage of the control request. */ struct nrf_usbd_ep_ctx { struct nrf_usbd_ep_cfg cfg; struct nrf_usbd_ep_buf buf; volatile bool read_complete; volatile bool read_pending; volatile bool write_in_progress; bool trans_zlp; }; /** * @brief Endpoint event structure * * @param ep Endpoint control block pointer * @param evt_type Event type */ struct usbd_ep_event { struct nrf_usbd_ep_ctx *ep; enum usbd_ep_event_type evt_type; }; /** * @brief Power event structure * * @param state New USBD peripheral state. */ struct usbd_pwr_event { enum usbd_periph_state state; }; /** * @brief Endpoint USB event * Used by ISR to send events to work handler * * @param node Used by the kernel for FIFO management * @param block Mempool block pointer for freeing up after use * @param evt Event data field * @param evt_type Type of event that has occurred from the USBD peripheral */ struct usbd_event { sys_snode_t node; struct usbd_mem_block block; union { struct usbd_ep_event ep_evt; struct usbd_pwr_event pwr_evt; } evt; enum usbd_event_type evt_type; }; /** * @brief Fifo element slab * Used for allocating fifo elements to pass from ISR to work handler * TODO: The number of FIFO elements is an arbitrary number now but it should * be derived from the theoretical number of backlog events possible depending * on the number of endpoints configured. */ #define FIFO_ELEM_SZ sizeof(struct usbd_event) #define FIFO_ELEM_ALIGN sizeof(unsigned int) K_MEM_SLAB_DEFINE(fifo_elem_slab, FIFO_ELEM_SZ, CONFIG_USB_NRFX_EVT_QUEUE_SIZE, FIFO_ELEM_ALIGN); /** Number of IN Endpoints configured (including control) */ #define CFG_EPIN_CNT (DT_INST_PROP(0, num_in_endpoints) + \ DT_INST_PROP(0, num_bidir_endpoints)) /** Number of OUT Endpoints configured (including control) */ #define CFG_EPOUT_CNT (DT_INST_PROP(0, num_out_endpoints) + \ DT_INST_PROP(0, num_bidir_endpoints)) /** Number of ISO IN Endpoints */ #define CFG_EP_ISOIN_CNT DT_INST_PROP(0, num_isoin_endpoints) /** Number of ISO OUT Endpoints */ #define CFG_EP_ISOOUT_CNT DT_INST_PROP(0, num_isoout_endpoints) /** ISO endpoint index */ #define EP_ISOIN_INDEX CFG_EPIN_CNT #define EP_ISOOUT_INDEX (CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + CFG_EPOUT_CNT) #define EP_BUF_MAX_SZ 64UL #define ISO_EP_BUF_MAX_SZ 1024UL /** * @brief Output endpoint buffers * Used as buffers for the endpoints' data transfer * Max buffers size possible: 1536 Bytes (8 EP * 64B + 1 ISO * 1024B) */ static uint8_t ep_out_bufs[CFG_EPOUT_CNT][EP_BUF_MAX_SZ] __aligned(sizeof(uint32_t)); static uint8_t ep_isoout_bufs[CFG_EP_ISOOUT_CNT][ISO_EP_BUF_MAX_SZ] __aligned(sizeof(uint32_t)); /** Total endpoints configured */ #define CFG_EP_CNT (CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + \ CFG_EPOUT_CNT + CFG_EP_ISOOUT_CNT) /** * @brief USBD control structure * * @param status_cb Status callback for USB DC notifications * @param setup Setup packet for Control requests * @param hfxo_cli Onoff client used to control HFXO * @param hfxo_mgr Pointer to onoff manager associated with HFXO. * @param clk_requested Flag used to protect against double stop. * @param attached USBD Attached flag * @param ready USBD Ready flag set after pullup * @param usb_work USBD work item * @param drv_lock Mutex for thread-safe nrfx driver use * @param ep_ctx Endpoint contexts * @param ctrl_read_len State of control read operation (EP0). */ struct nrf_usbd_ctx { usb_dc_status_callback status_cb; struct usb_setup_packet setup; struct onoff_client hfxo_cli; struct onoff_manager *hfxo_mgr; atomic_t clk_requested; bool attached; bool ready; struct k_work usb_work; struct k_mutex drv_lock; struct nrf_usbd_ep_ctx ep_ctx[CFG_EP_CNT]; uint16_t ctrl_read_len; }; /* FIFO used for queuing up events from ISR. */ K_FIFO_DEFINE(usbd_evt_fifo); /* Work queue used for handling the ISR events (i.e. for notifying the USB * device stack, for executing the endpoints callbacks, etc.) out of the ISR * context. * The system work queue cannot be used for this purpose as it might be used in * applications for scheduling USB transfers and this could lead to a deadlock * when the USB device stack would not be notified about certain event because * of a system work queue item waiting for a USB transfer to be finished. */ static struct k_work_q usbd_work_queue; static K_KERNEL_STACK_DEFINE(usbd_work_queue_stack, CONFIG_USB_NRFX_WORK_QUEUE_STACK_SIZE); static struct nrf_usbd_ctx usbd_ctx = { .attached = false, .ready = false, }; static inline struct nrf_usbd_ctx *get_usbd_ctx(void) { return &usbd_ctx; } static inline bool dev_attached(void) { return get_usbd_ctx()->attached; } static inline bool dev_ready(void) { return get_usbd_ctx()->ready; } static inline nrf_usbd_common_ep_t ep_addr_to_nrfx(uint8_t ep) { return (nrf_usbd_common_ep_t)ep; } static inline uint8_t nrfx_addr_to_ep(nrf_usbd_common_ep_t ep) { return (uint8_t)ep; } static inline bool ep_is_valid(const uint8_t ep) { uint8_t ep_num = USB_EP_GET_IDX(ep); if (NRF_USBD_EPIN_CHECK(ep)) { if (unlikely(ep_num == NRF_USBD_EPISO_FIRST)) { if (CFG_EP_ISOIN_CNT == 0) { return false; } } else { if (ep_num >= CFG_EPIN_CNT) { return false; } } } else { if (unlikely(ep_num == NRF_USBD_EPISO_FIRST)) { if (CFG_EP_ISOOUT_CNT == 0) { return false; } } else { if (ep_num >= CFG_EPOUT_CNT) { return false; } } } return true; } static struct nrf_usbd_ep_ctx *endpoint_ctx(const uint8_t ep) { struct nrf_usbd_ctx *ctx; uint8_t ep_num; if (!ep_is_valid(ep)) { return NULL; } ctx = get_usbd_ctx(); ep_num = NRF_USBD_EP_NR_GET(ep); if (NRF_USBD_EPIN_CHECK(ep)) { if (unlikely(NRF_USBD_EPISO_CHECK(ep))) { return &ctx->ep_ctx[EP_ISOIN_INDEX]; } else { return &ctx->ep_ctx[ep_num]; } } else { if (unlikely(NRF_USBD_EPISO_CHECK(ep))) { return &ctx->ep_ctx[EP_ISOOUT_INDEX]; } else { return &ctx->ep_ctx[CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + ep_num]; } } return NULL; } static struct nrf_usbd_ep_ctx *in_endpoint_ctx(const uint8_t ep) { return endpoint_ctx(NRF_USBD_EPIN(ep)); } static struct nrf_usbd_ep_ctx *out_endpoint_ctx(const uint8_t ep) { return endpoint_ctx(NRF_USBD_EPOUT(ep)); } /** * @brief Schedule USBD event processing. * * Should be called after usbd_evt_put(). */ static inline void usbd_work_schedule(void) { k_work_submit_to_queue(&usbd_work_queue, &get_usbd_ctx()->usb_work); } /** * @brief Free previously allocated USBD event. * * Should be called after usbd_evt_get(). * * @param Pointer to the USBD event structure. */ static inline void usbd_evt_free(struct usbd_event *ev) { k_mem_slab_free(&fifo_elem_slab, (void *)ev->block.data); } /** * @brief Enqueue USBD event. * * @param Pointer to the previously allocated and filled event structure. */ static inline void usbd_evt_put(struct usbd_event *ev) { k_fifo_put(&usbd_evt_fifo, ev); } /** * @brief Get next enqueued USBD event if present. */ static inline struct usbd_event *usbd_evt_get(void) { return k_fifo_get(&usbd_evt_fifo, K_NO_WAIT); } /** * @brief Drop all enqueued events. */ static inline void usbd_evt_flush(void) { struct usbd_event *ev; do { ev = usbd_evt_get(); if (ev) { usbd_evt_free(ev); } } while (ev != NULL); } /** * @brief Allocate USBD event. * * This function should be called prior to usbd_evt_put(). * * @returns Pointer to the allocated event or NULL if there was no space left. */ static inline struct usbd_event *usbd_evt_alloc(void) { struct usbd_event *ev; struct usbd_mem_block block; if (k_mem_slab_alloc(&fifo_elem_slab, (void **)&block.data, K_NO_WAIT)) { LOG_ERR("USBD event allocation failed!"); /* * Allocation may fail if workqueue thread is starved or event * queue size is too small (CONFIG_USB_NRFX_EVT_QUEUE_SIZE). * Wipe all events, free the space and schedule * reinitialization. */ usbd_evt_flush(); if (k_mem_slab_alloc(&fifo_elem_slab, (void **)&block.data, K_NO_WAIT)) { LOG_ERR("USBD event memory corrupted"); __ASSERT_NO_MSG(0); return NULL; } ev = (struct usbd_event *)block.data; ev->block = block; ev->evt_type = USBD_EVT_REINIT; usbd_evt_put(ev); usbd_work_schedule(); return NULL; } ev = (struct usbd_event *)block.data; ev->block = block; return ev; } static void submit_dc_power_event(enum usbd_periph_state state) { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } ev->evt_type = USBD_EVT_POWER; ev->evt.pwr_evt.state = state; usbd_evt_put(ev); if (usbd_ctx.attached) { usbd_work_schedule(); } } #if CONFIG_USB_NRFX_ATTACHED_EVENT_DELAY static void attached_evt_delay_handler(struct k_timer *timer) { LOG_DBG("ATTACHED event delay done"); submit_dc_power_event(USBD_ATTACHED); } static K_TIMER_DEFINE(delay_timer, attached_evt_delay_handler, NULL); #endif static void usb_dc_power_event_handler(nrfx_power_usb_evt_t event) { enum usbd_periph_state new_state; switch (event) { case NRFX_POWER_USB_EVT_DETECTED: #if !CONFIG_USB_NRFX_ATTACHED_EVENT_DELAY new_state = USBD_ATTACHED; break; #else LOG_DBG("ATTACHED event delayed"); k_timer_start(&delay_timer, K_MSEC(CONFIG_USB_NRFX_ATTACHED_EVENT_DELAY), K_NO_WAIT); return; #endif case NRFX_POWER_USB_EVT_READY: new_state = USBD_POWERED; break; case NRFX_POWER_USB_EVT_REMOVED: new_state = USBD_DETACHED; break; default: LOG_ERR("Unknown USB power event %d", event); return; } submit_dc_power_event(new_state); } /* Stopping HFXO, algorithm supports case when stop comes before clock is * started. In that case, it is stopped from the callback context. */ static int hfxo_stop(struct nrf_usbd_ctx *ctx) { if (atomic_cas(&ctx->clk_requested, 1, 0)) { return onoff_cancel_or_release(ctx->hfxo_mgr, &ctx->hfxo_cli); } return 0; } static int hfxo_start(struct nrf_usbd_ctx *ctx) { if (atomic_cas(&ctx->clk_requested, 0, 1)) { sys_notify_init_spinwait(&ctx->hfxo_cli.notify); return onoff_request(ctx->hfxo_mgr, &ctx->hfxo_cli); } return 0; } static void usbd_enable_endpoints(struct nrf_usbd_ctx *ctx) { struct nrf_usbd_ep_ctx *ep_ctx; int i; for (i = 0; i < CFG_EPIN_CNT; i++) { ep_ctx = in_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); if (ep_ctx->cfg.en) { nrf_usbd_common_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } } if (CFG_EP_ISOIN_CNT) { ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); __ASSERT_NO_MSG(ep_ctx); if (ep_ctx->cfg.en) { nrf_usbd_common_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } } for (i = 0; i < CFG_EPOUT_CNT; i++) { ep_ctx = out_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); if (ep_ctx->cfg.en) { nrf_usbd_common_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } } if (CFG_EP_ISOOUT_CNT) { ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); __ASSERT_NO_MSG(ep_ctx); if (ep_ctx->cfg.en) { nrf_usbd_common_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } } } /** * @brief Reset endpoint state. * * Resets the internal logic state for a given endpoint. * * @param[in] ep_cts Endpoint structure control block */ static void ep_ctx_reset(struct nrf_usbd_ep_ctx *ep_ctx) { ep_ctx->buf.data = ep_ctx->buf.block.data; ep_ctx->buf.curr = ep_ctx->buf.data; ep_ctx->buf.len = 0U; /* Abort ongoing write operation. */ if (ep_ctx->write_in_progress) { nrf_usbd_common_ep_abort(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } ep_ctx->read_complete = true; ep_ctx->read_pending = false; ep_ctx->write_in_progress = false; ep_ctx->trans_zlp = false; } /** * @brief Initialize all endpoint structures. * * Endpoint buffers are allocated during the first call of this function. * This function may also be called again on every USB reset event * to reinitialize the state of all endpoints. */ static int eps_ctx_init(void) { struct nrf_usbd_ep_ctx *ep_ctx; uint32_t i; for (i = 0U; i < CFG_EPIN_CNT; i++) { ep_ctx = in_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); ep_ctx_reset(ep_ctx); } for (i = 0U; i < CFG_EPOUT_CNT; i++) { ep_ctx = out_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); if (!ep_ctx->buf.block.data) { ep_ctx->buf.block.data = ep_out_bufs[i]; } ep_ctx_reset(ep_ctx); } if (CFG_EP_ISOIN_CNT) { ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); __ASSERT_NO_MSG(ep_ctx); ep_ctx_reset(ep_ctx); } if (CFG_EP_ISOOUT_CNT) { BUILD_ASSERT(CFG_EP_ISOOUT_CNT <= 1); ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); __ASSERT_NO_MSG(ep_ctx); if (!ep_ctx->buf.block.data) { ep_ctx->buf.block.data = ep_isoout_bufs[0]; } ep_ctx_reset(ep_ctx); } return 0; } static inline void usbd_work_process_pwr_events(struct usbd_pwr_event *pwr_evt) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); int err; switch (pwr_evt->state) { case USBD_ATTACHED: if (!nrf_usbd_common_is_enabled()) { LOG_DBG("USB detected"); nrf_usbd_common_enable(); err = hfxo_start(ctx); __ASSERT_NO_MSG(err >= 0); } /* No callback here. * Stack will be notified when the peripheral is ready. */ break; case USBD_POWERED: usbd_enable_endpoints(ctx); nrf_usbd_common_start(IS_ENABLED(CONFIG_USB_DEVICE_SOF)); ctx->ready = true; LOG_DBG("USB Powered"); if (ctx->status_cb) { ctx->status_cb(USB_DC_CONNECTED, NULL); } break; case USBD_DETACHED: ctx->ready = false; nrf_usbd_common_disable(); err = hfxo_stop(ctx); __ASSERT_NO_MSG(err >= 0); LOG_DBG("USB Removed"); if (ctx->status_cb) { ctx->status_cb(USB_DC_DISCONNECTED, NULL); } break; case USBD_SUSPENDED: if (dev_ready()) { nrf_usbd_common_suspend(); LOG_DBG("USB Suspend state"); if (ctx->status_cb) { ctx->status_cb(USB_DC_SUSPEND, NULL); } } break; case USBD_RESUMED: if (ctx->status_cb && dev_ready()) { LOG_DBG("USB resume"); ctx->status_cb(USB_DC_RESUME, NULL); } break; default: break; } } static inline void usbd_work_process_setup(struct nrf_usbd_ep_ctx *ep_ctx) { __ASSERT_NO_MSG(ep_ctx); __ASSERT(ep_ctx->cfg.type == USB_DC_EP_CONTROL, "Invalid event on CTRL EP."); struct usb_setup_packet *usbd_setup; /* SETUP packets are handled by USBD hardware. * For compatibility with the USB stack, * SETUP packet must be reassembled. */ usbd_setup = (struct usb_setup_packet *)ep_ctx->buf.data; memset(usbd_setup, 0, sizeof(struct usb_setup_packet)); usbd_setup->bmRequestType = nrf_usbd_setup_bmrequesttype_get(NRF_USBD); usbd_setup->bRequest = nrf_usbd_setup_brequest_get(NRF_USBD); usbd_setup->wValue = nrf_usbd_setup_wvalue_get(NRF_USBD); usbd_setup->wIndex = nrf_usbd_setup_windex_get(NRF_USBD); usbd_setup->wLength = nrf_usbd_setup_wlength_get(NRF_USBD); ep_ctx->buf.len = sizeof(struct usb_setup_packet); /* Copy setup packet to driver internal structure */ memcpy(&usbd_ctx.setup, usbd_setup, sizeof(struct usb_setup_packet)); LOG_DBG("SETUP: bR:0x%02x bmRT:0x%02x wV:0x%04x wI:0x%04x wL:%d", (uint32_t)usbd_setup->bRequest, (uint32_t)usbd_setup->bmRequestType, (uint32_t)usbd_setup->wValue, (uint32_t)usbd_setup->wIndex, (uint32_t)usbd_setup->wLength); /* Inform the stack. */ ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_SETUP); struct nrf_usbd_ctx *ctx = get_usbd_ctx(); if (usb_reqtype_is_to_device(usbd_setup) && usbd_setup->wLength) { ctx->ctrl_read_len = usbd_setup->wLength; /* Allow data chunk on EP0 OUT */ nrf_usbd_common_setup_data_clear(); } else { ctx->ctrl_read_len = 0U; } } static inline void usbd_work_process_recvreq(struct nrf_usbd_ctx *ctx, struct nrf_usbd_ep_ctx *ep_ctx) { if (!ep_ctx->read_pending) { return; } if (!ep_ctx->read_complete) { return; } ep_ctx->read_pending = false; ep_ctx->read_complete = false; k_mutex_lock(&ctx->drv_lock, K_FOREVER); NRF_USBD_COMMON_TRANSFER_OUT(transfer, ep_ctx->buf.data, ep_ctx->cfg.max_sz); nrfx_err_t err = nrf_usbd_common_ep_transfer( ep_addr_to_nrfx(ep_ctx->cfg.addr), &transfer); if (err != NRFX_SUCCESS) { LOG_ERR("nRF USBD transfer error (OUT): 0x%02x", err); } k_mutex_unlock(&ctx->drv_lock); } static inline void usbd_work_process_ep_events(struct usbd_ep_event *ep_evt) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); struct nrf_usbd_ep_ctx *ep_ctx = ep_evt->ep; __ASSERT_NO_MSG(ep_ctx); switch (ep_evt->evt_type) { case EP_EVT_SETUP_RECV: usbd_work_process_setup(ep_ctx); break; case EP_EVT_RECV_REQ: usbd_work_process_recvreq(ctx, ep_ctx); break; case EP_EVT_RECV_COMPLETE: ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_OUT); break; case EP_EVT_WRITE_COMPLETE: if (ep_ctx->cfg.type == USB_DC_EP_CONTROL && !ep_ctx->trans_zlp) { /* Trigger the hardware to perform * status stage, but only if there is * no ZLP required. */ k_mutex_lock(&ctx->drv_lock, K_FOREVER); nrf_usbd_common_setup_clear(); k_mutex_unlock(&ctx->drv_lock); } ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_IN); break; default: break; } } static void usbd_event_transfer_ctrl(nrf_usbd_common_evt_t const *const p_event) { struct nrf_usbd_ep_ctx *ep_ctx = endpoint_ctx(p_event->data.eptransfer.ep); if (NRF_USBD_EPIN_CHECK(p_event->data.eptransfer.ep)) { switch (p_event->data.eptransfer.status) { case NRF_USBD_COMMON_EP_OK: { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } ep_ctx->write_in_progress = false; ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_WRITE_COMPLETE; ev->evt.ep_evt.ep = ep_ctx; LOG_DBG("ctrl write complete"); usbd_evt_put(ev); usbd_work_schedule(); } break; case NRF_USBD_COMMON_EP_ABORTED: { LOG_DBG("Endpoint 0x%02x write aborted", p_event->data.eptransfer.ep); } break; default: { LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); } break; } } else { switch (p_event->data.eptransfer.status) { case NRF_USBD_COMMON_EP_WAITING: { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } LOG_DBG("ctrl read request"); ep_ctx->read_pending = true; ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; ev->evt.ep_evt.ep = ep_ctx; usbd_evt_put(ev); usbd_work_schedule(); } break; case NRF_USBD_COMMON_EP_OK: { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } nrf_usbd_common_ep_status_t err_code; ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_RECV_COMPLETE; ev->evt.ep_evt.ep = ep_ctx; err_code = nrf_usbd_common_ep_status_get( p_event->data.eptransfer.ep, &ep_ctx->buf.len); if (err_code != NRF_USBD_COMMON_EP_OK) { LOG_ERR("_ep_status_get failed! Code: %d", err_code); __ASSERT_NO_MSG(0); } LOG_DBG("ctrl read done: %d", ep_ctx->buf.len); if (ctx->ctrl_read_len > ep_ctx->buf.len) { ctx->ctrl_read_len -= ep_ctx->buf.len; /* Allow next data chunk on EP0 OUT */ nrf_usbd_common_setup_data_clear(); } else { ctx->ctrl_read_len = 0U; } usbd_evt_put(ev); usbd_work_schedule(); } break; default: { LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); } break; } } } static void usbd_event_transfer_data(nrf_usbd_common_evt_t const *const p_event) { struct nrf_usbd_ep_ctx *ep_ctx = endpoint_ctx(p_event->data.eptransfer.ep); if (NRF_USBD_EPIN_CHECK(p_event->data.eptransfer.ep)) { switch (p_event->data.eptransfer.status) { case NRF_USBD_COMMON_EP_OK: { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } LOG_DBG("write complete, ep 0x%02x", (uint32_t)p_event->data.eptransfer.ep); ep_ctx->write_in_progress = false; ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_WRITE_COMPLETE; ev->evt.ep_evt.ep = ep_ctx; usbd_evt_put(ev); usbd_work_schedule(); } break; case NRF_USBD_COMMON_EP_ABORTED: { LOG_DBG("Endpoint 0x%02x write aborted", p_event->data.eptransfer.ep); } break; default: { LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); } break; } } else { switch (p_event->data.eptransfer.status) { case NRF_USBD_COMMON_EP_WAITING: { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } LOG_DBG("read request, ep 0x%02x", (uint32_t)p_event->data.eptransfer.ep); ep_ctx->read_pending = true; ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; ev->evt.ep_evt.ep = ep_ctx; usbd_evt_put(ev); usbd_work_schedule(); } break; case NRF_USBD_COMMON_EP_OK: { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { return; } ep_ctx->buf.len = nrf_usbd_ep_amount_get(NRF_USBD, p_event->data.eptransfer.ep); LOG_DBG("read complete, ep 0x%02x, len %d", (uint32_t)p_event->data.eptransfer.ep, ep_ctx->buf.len); ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_RECV_COMPLETE; ev->evt.ep_evt.ep = ep_ctx; usbd_evt_put(ev); usbd_work_schedule(); } break; default: { LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); } break; } } } /** * @brief nRFx USBD driver event handler function. */ static void usbd_event_handler(nrf_usbd_common_evt_t const *const p_event) { struct usbd_event evt = {0}; bool put_evt = false; switch (p_event->type) { case NRF_USBD_COMMON_EVT_SUSPEND: LOG_DBG("SUSPEND state detected"); evt.evt_type = USBD_EVT_POWER; evt.evt.pwr_evt.state = USBD_SUSPENDED; put_evt = true; break; case NRF_USBD_COMMON_EVT_RESUME: LOG_DBG("RESUMING from suspend"); evt.evt_type = USBD_EVT_POWER; evt.evt.pwr_evt.state = USBD_RESUMED; put_evt = true; break; case NRF_USBD_COMMON_EVT_WUREQ: LOG_DBG("RemoteWU initiated"); evt.evt_type = USBD_EVT_POWER; evt.evt.pwr_evt.state = USBD_RESUMED; put_evt = true; break; case NRF_USBD_COMMON_EVT_RESET: evt.evt_type = USBD_EVT_RESET; put_evt = true; break; case NRF_USBD_COMMON_EVT_SOF: if (IS_ENABLED(CONFIG_USB_DEVICE_SOF)) { evt.evt_type = USBD_EVT_SOF; put_evt = true; } break; case NRF_USBD_COMMON_EVT_EPTRANSFER: { struct nrf_usbd_ep_ctx *ep_ctx; ep_ctx = endpoint_ctx(p_event->data.eptransfer.ep); switch (ep_ctx->cfg.type) { case USB_DC_EP_CONTROL: usbd_event_transfer_ctrl(p_event); break; case USB_DC_EP_BULK: case USB_DC_EP_INTERRUPT: usbd_event_transfer_data(p_event); break; case USB_DC_EP_ISOCHRONOUS: usbd_event_transfer_data(p_event); break; default: break; } break; } case NRF_USBD_COMMON_EVT_SETUP: { nrf_usbd_common_setup_t drv_setup; nrf_usbd_common_setup_get(&drv_setup); if ((drv_setup.bRequest != USB_SREQ_SET_ADDRESS) || (USB_REQTYPE_GET_TYPE(drv_setup.bmRequestType) != USB_REQTYPE_TYPE_STANDARD)) { /* SetAddress is handled by USBD hardware. * No software action required. */ struct nrf_usbd_ep_ctx *ep_ctx = endpoint_ctx(NRF_USBD_EPOUT(0)); evt.evt_type = USBD_EVT_EP; evt.evt.ep_evt.ep = ep_ctx; evt.evt.ep_evt.evt_type = EP_EVT_SETUP_RECV; put_evt = true; } break; } default: break; } if (put_evt) { struct usbd_event *ev; ev = usbd_evt_alloc(); if (!ev) { return; } ev->evt_type = evt.evt_type; ev->evt = evt.evt; usbd_evt_put(ev); usbd_work_schedule(); } } static inline void usbd_reinit(void) { int ret; nrfx_err_t err; nrfx_power_usbevt_disable(); nrf_usbd_common_disable(); nrf_usbd_common_uninit(); usbd_evt_flush(); ret = eps_ctx_init(); __ASSERT_NO_MSG(ret == 0); nrfx_power_usbevt_enable(); err = nrf_usbd_common_init(usbd_event_handler); if (err != NRFX_SUCCESS) { LOG_DBG("nRF USBD driver reinit failed. Code: %d", err); __ASSERT_NO_MSG(0); } } /** * @brief function to generate fake receive request for * ISO OUT EP. * * ISO OUT endpoint does not generate irq by itself and reading * from ISO OUT ep is synchronized with SOF frame. For more details * refer to Nordic usbd specification. */ static void usbd_sof_trigger_iso_read(void) { struct usbd_event *ev; struct nrf_usbd_ep_ctx *ep_ctx; ep_ctx = endpoint_ctx(NRF_USBD_COMMON_EPOUT8); if (!ep_ctx) { LOG_ERR("There is no ISO ep"); return; } if (ep_ctx->cfg.en) { /* Dissect receive request * if the iso OUT ep is enabled */ ep_ctx->read_pending = true; ep_ctx->read_complete = true; ev = usbd_evt_alloc(); if (!ev) { LOG_ERR("Failed to alloc evt"); return; } ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; ev->evt.ep_evt.ep = ep_ctx; usbd_evt_put(ev); usbd_work_schedule(); } else { LOG_DBG("Endpoint is not enabled"); } } /* Work handler */ static void usbd_work_handler(struct k_work *item) { struct nrf_usbd_ctx *ctx; struct usbd_event *ev; ctx = CONTAINER_OF(item, struct nrf_usbd_ctx, usb_work); while ((ev = usbd_evt_get()) != NULL) { if (!dev_ready() && ev->evt_type != USBD_EVT_POWER) { /* Drop non-power events when cable is detached. */ usbd_evt_free(ev); continue; } switch (ev->evt_type) { case USBD_EVT_EP: if (!ctx->attached) { LOG_ERR("not attached, EP 0x%02x event dropped", (uint32_t)ev->evt.ep_evt.ep->cfg.addr); } usbd_work_process_ep_events(&ev->evt.ep_evt); break; case USBD_EVT_POWER: usbd_work_process_pwr_events(&ev->evt.pwr_evt); break; case USBD_EVT_RESET: LOG_DBG("USBD reset event"); k_mutex_lock(&ctx->drv_lock, K_FOREVER); eps_ctx_init(); k_mutex_unlock(&ctx->drv_lock); if (ctx->status_cb) { ctx->status_cb(USB_DC_RESET, NULL); } break; case USBD_EVT_SOF: usbd_sof_trigger_iso_read(); if (ctx->status_cb) { ctx->status_cb(USB_DC_SOF, NULL); } break; case USBD_EVT_REINIT: { /* * Reinitialize the peripheral after queue * overflow. */ LOG_ERR("USBD event queue full!"); usbd_reinit(); break; } default: LOG_ERR("Unknown USBD event: %"PRId16, ev->evt_type); break; } usbd_evt_free(ev); } } int usb_dc_attach(void) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); int ret; if (ctx->attached) { return 0; } k_mutex_init(&ctx->drv_lock); ctx->hfxo_mgr = z_nrf_clock_control_get_onoff( COND_CODE_1(NRF_CLOCK_HAS_HFCLK192M, (CLOCK_CONTROL_NRF_SUBSYS_HF192M), (CLOCK_CONTROL_NRF_SUBSYS_HF))); IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), nrfx_isr, nrf_usbd_common_irq_handler, 0); nrfx_power_usbevt_enable(); ret = eps_ctx_init(); if (ret == 0) { ctx->attached = true; } if (!k_fifo_is_empty(&usbd_evt_fifo)) { usbd_work_schedule(); } if (nrfx_power_usbstatus_get() != NRFX_POWER_USB_STATE_DISCONNECTED) { /* USBDETECTED event is be generated on cable attachment and * when cable is already attached during reset, but not when * the peripheral is re-enabled. * When USB-enabled bootloader is used, target application * will not receive this event and it needs to be generated * again here. */ usb_dc_power_event_handler(NRFX_POWER_USB_EVT_DETECTED); } return ret; } int usb_dc_detach(void) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); k_mutex_lock(&ctx->drv_lock, K_FOREVER); usbd_evt_flush(); if (nrf_usbd_common_is_enabled()) { nrf_usbd_common_disable(); } (void)hfxo_stop(ctx); nrfx_power_usbevt_disable(); ctx->attached = false; k_mutex_unlock(&ctx->drv_lock); return 0; } int usb_dc_reset(void) { int ret; if (!dev_attached() || !dev_ready()) { return -ENODEV; } LOG_DBG("USBD Reset"); ret = usb_dc_detach(); if (ret) { return ret; } ret = usb_dc_attach(); if (ret) { return ret; } return 0; } int usb_dc_set_address(const uint8_t addr) { struct nrf_usbd_ctx *ctx; if (!dev_attached() || !dev_ready()) { return -ENODEV; } /** * Nothing to do here. The USBD HW already takes care of initiating * STATUS stage. Just double check the address for sanity. */ __ASSERT(addr == (uint8_t)NRF_USBD->USBADDR, "USB Address incorrect!"); ctx = get_usbd_ctx(); LOG_DBG("Address set to: %d", addr); return 0; } int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data *const ep_cfg) { uint8_t ep_idx = NRF_USBD_EP_NR_GET(ep_cfg->ep_addr); LOG_DBG("ep 0x%02x, mps %d, type %d", ep_cfg->ep_addr, ep_cfg->ep_mps, ep_cfg->ep_type); if ((ep_cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { LOG_ERR("invalid endpoint configuration"); return -1; } if (!NRF_USBD_EP_VALIDATE(ep_cfg->ep_addr)) { LOG_ERR("invalid endpoint index/address"); return -1; } if ((ep_cfg->ep_type == USB_DC_EP_ISOCHRONOUS) && (!NRF_USBD_EPISO_CHECK(ep_cfg->ep_addr))) { LOG_WRN("invalid endpoint type"); return -1; } if ((ep_cfg->ep_type != USB_DC_EP_ISOCHRONOUS) && (NRF_USBD_EPISO_CHECK(ep_cfg->ep_addr))) { LOG_WRN("iso endpoint can only be iso"); return -1; } return 0; } int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const ep_cfg) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached()) { return -ENODEV; } /** * TODO: * For ISO endpoints, application has to use EPIN/OUT 8 * but right now there's no standard way of knowing the * ISOIN/ISOOUT endpoint number in advance to configure * accordingly. So either this needs to be chosen in the * menuconfig in application area or perhaps in device tree * at compile time or introduce a new API to read the endpoint * configuration at runtime before configuring them. */ ep_ctx = endpoint_ctx(ep_cfg->ep_addr); if (!ep_ctx) { return -EINVAL; } ep_ctx->cfg.addr = ep_cfg->ep_addr; ep_ctx->cfg.type = ep_cfg->ep_type; ep_ctx->cfg.max_sz = ep_cfg->ep_mps; if (!NRF_USBD_EPISO_CHECK(ep_cfg->ep_addr)) { if ((ep_cfg->ep_mps & (ep_cfg->ep_mps - 1)) != 0U) { LOG_ERR("EP max packet size must be a power of 2"); return -EINVAL; } } nrf_usbd_common_ep_max_packet_size_set(ep_addr_to_nrfx(ep_cfg->ep_addr), ep_cfg->ep_mps); return 0; } int usb_dc_ep_set_stall(const uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached() || !dev_ready()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } switch (ep_ctx->cfg.type) { case USB_DC_EP_CONTROL: nrf_usbd_common_setup_stall(); break; case USB_DC_EP_BULK: case USB_DC_EP_INTERRUPT: nrf_usbd_common_ep_stall(ep_addr_to_nrfx(ep)); break; case USB_DC_EP_ISOCHRONOUS: LOG_ERR("STALL unsupported on ISO endpoint"); return -EINVAL; } ep_ctx->buf.len = 0U; ep_ctx->buf.curr = ep_ctx->buf.data; LOG_DBG("STALL on EP 0x%02x", ep); return 0; } int usb_dc_ep_clear_stall(const uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached() || !dev_ready()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (NRF_USBD_EPISO_CHECK(ep)) { /* ISO transactions do not support a handshake phase. */ return -EINVAL; } nrf_usbd_common_ep_dtoggle_clear(ep_addr_to_nrfx(ep)); nrf_usbd_common_ep_stall_clear(ep_addr_to_nrfx(ep)); LOG_DBG("Unstall on EP 0x%02x", ep); return 0; } int usb_dc_ep_halt(const uint8_t ep) { return usb_dc_ep_set_stall(ep); } int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached() || !dev_ready()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (!stalled) { return -EINVAL; } *stalled = (uint8_t) nrf_usbd_common_ep_stall_check(ep_addr_to_nrfx(ep)); return 0; } int usb_dc_ep_enable(const uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (!NRF_USBD_EPISO_CHECK(ep)) { /* ISO transactions for full-speed device do not support * toggle sequencing and should only send DATA0 PID. */ nrf_usbd_common_ep_dtoggle_clear(ep_addr_to_nrfx(ep)); /** Endpoint is enabled on SetInterface request. * This should also clear EP's halt status. */ nrf_usbd_common_ep_stall_clear(ep_addr_to_nrfx(ep)); } if (ep_ctx->cfg.en) { return -EALREADY; } LOG_DBG("EP enable: 0x%02x", ep); ep_ctx->cfg.en = true; /* Defer the endpoint enable if USBD is not ready yet. */ if (dev_ready()) { nrf_usbd_common_ep_enable(ep_addr_to_nrfx(ep)); } return 0; } int usb_dc_ep_disable(const uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (!ep_ctx->cfg.en) { return -EALREADY; } LOG_DBG("EP disable: 0x%02x", ep); nrf_usbd_common_ep_disable(ep_addr_to_nrfx(ep)); /* Clear write_in_progress as nrf_usbd_common_ep_disable() * terminates endpoint transaction. */ ep_ctx->write_in_progress = false; ep_ctx_reset(ep_ctx); ep_ctx->cfg.en = false; return 0; } int usb_dc_ep_flush(const uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached() || !dev_ready()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } ep_ctx->buf.len = 0U; ep_ctx->buf.curr = ep_ctx->buf.data; nrf_usbd_common_transfer_out_drop(ep_addr_to_nrfx(ep)); return 0; } int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, const uint32_t data_len, uint32_t *const ret_bytes) { LOG_DBG("ep_write: ep 0x%02x, len %d", ep, data_len); struct nrf_usbd_ctx *ctx = get_usbd_ctx(); struct nrf_usbd_ep_ctx *ep_ctx; int result = 0; if (!dev_attached() || !dev_ready()) { return -ENODEV; } if (NRF_USBD_EPOUT_CHECK(ep)) { return -EINVAL; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (!ep_ctx->cfg.en) { LOG_ERR("Endpoint 0x%02x is not enabled", ep); return -EINVAL; } k_mutex_lock(&ctx->drv_lock, K_FOREVER); /* USBD driver does not allow scheduling multiple DMA transfers * for one EP at a time. Next USB transfer on this endpoint can be * triggered after the completion of previous one. */ if (ep_ctx->write_in_progress) { k_mutex_unlock(&ctx->drv_lock); return -EAGAIN; } /** Clear the ZLP flag if current write is ZLP. After the ZLP will be * send the driver will perform status stage. */ if (!data_len && ep_ctx->trans_zlp) { ep_ctx->trans_zlp = false; } /** If writing to a Control Endpoint there might be a need to transfer * ZLP. If the Hosts asks for more data that the device may return and * the last packet is wMaxPacketSize long. The driver must send ZLP. * For consistence with the Zephyr USB stack sending ZLP must be issued * from the stack level. Making trans_zlp flag true results in blocking * the driver from starting setup stage without required ZLP. */ if (ep_ctx->cfg.type == USB_DC_EP_CONTROL) { if (data_len && usbd_ctx.setup.wLength > data_len && !(data_len % ep_ctx->cfg.max_sz)) { ep_ctx->trans_zlp = true; } } /* Setup stage is handled by hardware. * Detect the setup stage initiated by the stack * and perform appropriate action. */ if ((ep_ctx->cfg.type == USB_DC_EP_CONTROL) && (nrf_usbd_common_last_setup_dir_get() != ep)) { nrf_usbd_common_setup_clear(); k_mutex_unlock(&ctx->drv_lock); return 0; } ep_ctx->write_in_progress = true; NRF_USBD_COMMON_TRANSFER_IN(transfer, data, data_len, 0); nrfx_err_t err = nrf_usbd_common_ep_transfer(ep_addr_to_nrfx(ep), &transfer); if (err != NRFX_SUCCESS) { ep_ctx->write_in_progress = false; if (ret_bytes) { *ret_bytes = 0; } result = -EIO; LOG_ERR("nRF USBD write error: %d", (uint32_t)err); } else { if (ret_bytes) { *ret_bytes = data_len; } } k_mutex_unlock(&ctx->drv_lock); return result; } int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *read_bytes) { struct nrf_usbd_ep_ctx *ep_ctx; struct nrf_usbd_ctx *ctx = get_usbd_ctx(); uint32_t bytes_to_copy; if (!dev_attached() || !dev_ready()) { return -ENODEV; } if (NRF_USBD_EPIN_CHECK(ep)) { return -EINVAL; } if (!data && max_data_len) { return -EINVAL; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (!ep_ctx->cfg.en) { LOG_ERR("Endpoint 0x%02x is not enabled", ep); return -EINVAL; } k_mutex_lock(&ctx->drv_lock, K_FOREVER); bytes_to_copy = MIN(max_data_len, ep_ctx->buf.len); if (!data && !max_data_len) { if (read_bytes) { *read_bytes = ep_ctx->buf.len; } k_mutex_unlock(&ctx->drv_lock); return 0; } memcpy(data, ep_ctx->buf.curr, bytes_to_copy); ep_ctx->buf.curr += bytes_to_copy; ep_ctx->buf.len -= bytes_to_copy; if (read_bytes) { *read_bytes = bytes_to_copy; } k_mutex_unlock(&ctx->drv_lock); return 0; } int usb_dc_ep_read_continue(uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; struct nrf_usbd_ctx *ctx = get_usbd_ctx(); if (!dev_attached() || !dev_ready()) { return -ENODEV; } if (NRF_USBD_EPIN_CHECK(ep)) { return -EINVAL; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } if (!ep_ctx->cfg.en) { LOG_ERR("Endpoint 0x%02x is not enabled", ep); return -EINVAL; } k_mutex_lock(&ctx->drv_lock, K_FOREVER); if (!ep_ctx->buf.len) { ep_ctx->buf.curr = ep_ctx->buf.data; ep_ctx->read_complete = true; if (ep_ctx->read_pending) { struct usbd_event *ev = usbd_evt_alloc(); if (!ev) { k_mutex_unlock(&ctx->drv_lock); return -ENOMEM; } ev->evt_type = USBD_EVT_EP; ev->evt.ep_evt.ep = ep_ctx; ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; usbd_evt_put(ev); usbd_work_schedule(); } } k_mutex_unlock(&ctx->drv_lock); return 0; } int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, const uint32_t max_data_len, uint32_t *const read_bytes) { LOG_DBG("ep_read: ep 0x%02x, maxlen %d", ep, max_data_len); int ret; ret = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes); if (ret) { return ret; } if (!data && !max_data_len) { return ret; } ret = usb_dc_ep_read_continue(ep); return ret; } int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } ep_ctx->cfg.cb = cb; return 0; } void usb_dc_set_status_callback(const usb_dc_status_callback cb) { get_usbd_ctx()->status_cb = cb; } int usb_dc_ep_mps(const uint8_t ep) { struct nrf_usbd_ep_ctx *ep_ctx; if (!dev_attached()) { return -ENODEV; } ep_ctx = endpoint_ctx(ep); if (!ep_ctx) { return -EINVAL; } return ep_ctx->cfg.max_sz; } int usb_dc_wakeup_request(void) { bool res = nrf_usbd_common_wakeup_req(); if (!res) { return -EAGAIN; } return 0; } static int usb_init(void) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); nrfx_err_t err; #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 static const nrfx_power_config_t power_config = { .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 }; static const nrfx_power_usbevt_config_t usbevt_config = { .handler = usb_dc_power_event_handler }; err = nrf_usbd_common_init(usbd_event_handler); if (err != NRFX_SUCCESS) { LOG_DBG("nRF USBD driver init failed. Code: %d", (uint32_t)err); return -EIO; } /* Ignore the return value, as NRFX_ERROR_ALREADY_INITIALIZED is not * a problem here. */ (void)nrfx_power_init(&power_config); nrfx_power_usbevt_init(&usbevt_config); k_work_queue_start(&usbd_work_queue, usbd_work_queue_stack, K_KERNEL_STACK_SIZEOF(usbd_work_queue_stack), CONFIG_SYSTEM_WORKQUEUE_PRIORITY, NULL); k_thread_name_set(&usbd_work_queue.thread, "usbd_workq"); k_work_init(&ctx->usb_work, usbd_work_handler); return 0; } SYS_INIT(usb_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);