/* * 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 #define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL #include LOG_MODULE_REGISTER(usb_nrfx); #define USB_BMREQUEST_SETADDRESS 0x05 #define USB_BMREQUESTTYPE_POS 7uL #define USB_BMREQUESTTYPE_MASK (1uL << USB_BMREQUESTTYPE_POS) #define USB_BMREQUESTTYPE_HOSTTODEVICE_MASK 0uL #define USB_BMREQUESTTYPE_DEVICETOHOST_MASK (1uL << USB_BMREQUESTTYPE_POS) #define MAX_EP_BUF_SZ 64UL #define MAX_ISO_EP_BUF_SZ 1024UL #define USBD_EPSTATUS_EPIN_MASK (0x1FF << USBD_EPSTATUS_EPIN0_Pos) #define USBD_EPSTATUS_EPOUT_MASK (0x1FF << USBD_EPSTATUS_EPOUT0_Pos) #define USBD_EPDATASTATUS_EPIN_MASK (0x7F << USBD_EPDATASTATUS_EPIN1_Pos) #define USBD_EPDATASTATUS_EPOUT_MASK (0x7F << USBD_EPDATASTATUS_EPOUT1_Pos) /** USB Work flags */ #define NRF_USB_STATE_CHANGE 0 #define NRF_USB_STATUS_CHANGE 1 /** * @brief nRF USBD peripheral states */ enum usbd_periph_state { USBD_DETACHED, USBD_ATTACHED, USBD_POWERED, USBD_SUSPENDED, USBD_DEFAULT, USBD_ADDRESS_SET, USBD_CONFIGURED, }; /** * @brief Endpoint event types. */ enum ep_event_type { EP_EVT_SETUP_RECV, EP_EVT_RECV_REQ, EP_EVT_RECV_COMPLETE, EP_EVT_WRITE_COMPLETE, }; /** * @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 type. */ struct nrf_usbd_ep_cfg { usb_dc_ep_callback cb; u32_t max_sz; bool en; u8_t addr; enum usb_dc_ep_type type; }; /** * @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 { u32_t len; struct k_mem_block block; u8_t *data; u8_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. */ 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; }; /** * @brief Endpoint USB event * Used by ISR to send events to work handler * * @param node Used by the kernel for FIFO management * @param ep Endpoint context pointer that needs service * @param evt Event that has occurred from the USBD peripheral * @param block Mempool block pointer for freeing up after use * @param misc_u Miscellaneous information passed as flags */ struct usbd_ep_event { sys_snode_t node; struct nrf_usbd_ep_ctx *ep; enum ep_event_type evt; struct k_mem_block block; union { u32_t flags; u32_t frame_counter; } misc_u; }; /** * @brief Fifo element pool * 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_MIN_SZ sizeof(struct usbd_ep_event) #define FIFO_ELEM_MAX_SZ sizeof(struct usbd_ep_event) #define FIFO_ELEM_COUNT 32 #define FIFO_ELEM_ALIGN sizeof(unsigned int) K_MEM_POOL_DEFINE(fifo_elem_pool, FIFO_ELEM_MIN_SZ, FIFO_ELEM_MAX_SZ, FIFO_ELEM_COUNT, FIFO_ELEM_ALIGN); /** * @brief Endpoint buffer pool * Used for allocating buffers for the endpoints' data transfer * Max pool size possible: 3072 Bytes (16 EP * 64B + 2 ISO * 1024B) */ /** Number of IN Endpoints configured (including control) */ #define CFG_EPIN_CNT (CONFIG_USBD_NRF_NUM_IN_EP + \ CONFIG_USBD_NRF_NUM_BIDIR_EP) /** Number of OUT Endpoints configured (including control) */ #define CFG_EPOUT_CNT (CONFIG_USBD_NRF_NUM_OUT_EP + \ CONFIG_USBD_NRF_NUM_BIDIR_EP) /** Number of ISO IN Endpoints */ #define CFG_EP_ISOIN_CNT CONFIG_USBD_NRF_NUM_ISOIN_EP /** Number of ISO OUT Endpoints */ #define CFG_EP_ISOOUT_CNT CONFIG_USBD_NRF_NUM_ISOOUT_EP /** ISO endpoint index */ #define EP_ISOIN_INDEX CFG_EPIN_CNT #define EP_ISOOUT_INDEX (CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + CFG_EPOUT_CNT) /** Minimum endpoint buffer size */ #define EP_BUF_MIN_SZ MAX_EP_BUF_SZ /** Maximum endpoint buffer size */ #if (CFG_EP_ISOIN_CNT || CFG_EP_ISOOUT_CNT) #define EP_BUF_MAX_SZ MAX_ISO_EP_BUF_SZ #else #define EP_BUF_MAX_SZ MAX_EP_BUF_SZ #endif /** Total endpoints configured */ #define CFG_EP_CNT (CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + \ CFG_EPOUT_CNT + CFG_EP_ISOOUT_CNT) /** Total buffer size for all endpoints */ #define EP_BUF_TOTAL ((CFG_EPIN_CNT * MAX_EP_BUF_SZ) + \ (CFG_EPOUT_CNT * MAX_EP_BUF_SZ) + \ (CFG_EP_ISOIN_CNT * MAX_ISO_EP_BUF_SZ) + \ (CFG_EP_ISOOUT_CNT * MAX_ISO_EP_BUF_SZ)) /** Total number of maximum sized buffers needed */ #define EP_BUF_COUNT ((EP_BUF_TOTAL / EP_BUF_MAX_SZ) + \ ((EP_BUF_TOTAL % EP_BUF_MAX_SZ) ? 1 : 0)) /** 4 Byte Buffer alignment required by hardware */ #define EP_BUF_ALIGN sizeof(unsigned int) K_MEM_POOL_DEFINE(ep_buf_pool, EP_BUF_MIN_SZ, EP_BUF_MAX_SZ, EP_BUF_COUNT, EP_BUF_ALIGN); /** * @brief USBD private structure * * @param status_cb Status callback for USB DC notifications * @param attached USBD Attached flag * @param ready USBD Ready flag set after pullup * @param state USBD state * @param status_code Device Status code * @param flags Flags used in work context * @param usb_work USBD work item * @param work_queue FIFO used for queuing up events from ISR * @param dma_in_use Semaphore to restrict access to DMA one at a time * @param ep_ctx Endpoint contexts */ struct nrf_usbd_ctx { usb_dc_status_callback status_cb; bool attached; bool ready; enum usbd_periph_state state; enum usb_dc_status_code status_code; u32_t flags; struct k_work usb_work; struct k_fifo work_queue; struct k_sem dma_in_use; struct nrf_usbd_ep_ctx ep_ctx[CFG_EP_CNT]; }; static struct nrf_usbd_ctx usbd_ctx; static inline struct nrf_usbd_ctx *get_usbd_ctx(void) { return &usbd_ctx; } static inline nrfx_usbd_ep_t ep_addr_to_nrfx(uint8_t ep) { return (nrfx_usbd_ep_t)ep; } static inline uint8_t nrfx_addr_to_ep(nrfx_usbd_ep_t ep) { return (uint8_t)ep; } static inline bool ep_is_valid(const u8_t ep) { u8_t ep_num = NRF_USBD_EP_NR_GET(ep); if (NRF_USBD_EPIN_CHECK(ep)) { if (unlikely(NRF_USBD_EPISO_CHECK(ep))) { if (CFG_EP_ISOIN_CNT == 0) { return false; } } else { if (ep_num >= CFG_EPIN_CNT) { return false; } } } else { if (unlikely(NRF_USBD_EPISO_CHECK(ep))) { 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 u8_t ep) { struct nrf_usbd_ctx *ctx; u8_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 u8_t ep) { return endpoint_ctx(NRF_USBD_EPIN(ep)); } static struct nrf_usbd_ep_ctx *out_endpoint_ctx(const u8_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(&get_usbd_ctx()->usb_work); } /** * @brief Update USB DC status code. * * @param status New status code. */ static inline void usbd_status_code_update(enum usb_dc_status_code status) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); ctx->status_code = status; ctx->flags |= BIT(NRF_USB_STATUS_CHANGE); usbd_work_schedule(); } /** * @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_ep_event *usbd_evt_alloc(void) { int ret; struct usbd_ep_event *ev; struct k_mem_block block; ret = k_mem_pool_alloc(&fifo_elem_pool, &block, sizeof(struct usbd_ep_event), K_NO_WAIT); if (ret < 0) { LOG_DBG("USBD event alloc failed!"); __ASSERT_NO_MSG(0); return NULL; } ev = (struct usbd_ep_event *)block.data; ev->block = block; ev->misc_u.flags = 0; return ev; } /** * @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_ep_event *ev) { k_mem_pool_free(&ev->block); } /** * @brief Enqueue USBD event. * * @param Pointer to the previously allocated and filled event structure. */ static inline void usbd_evt_put(struct usbd_ep_event *ev) { k_fifo_put(&get_usbd_ctx()->work_queue, ev); } /** * @brief Get next enqueued USBD event if present. */ static inline struct usbd_ep_event *usbd_evt_get(void) { return k_fifo_get(&get_usbd_ctx()->work_queue, K_NO_WAIT); } /** * @brief Drop all enqueued events. */ static inline void usbd_evt_flush(void) { struct usbd_ep_event *ev; do { ev = usbd_evt_get(); if (ev) { usbd_evt_free(ev); } } while (ev != NULL); } void usb_dc_nrfx_power_event_callback(nrf_power_event_t event) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); switch (event) { case NRF_POWER_EVENT_USBDETECTED: ctx->state = USBD_ATTACHED; break; case NRF_POWER_EVENT_USBPWRRDY: ctx->state = USBD_POWERED; break; case NRF_POWER_EVENT_USBREMOVED: ctx->state = USBD_DETACHED; break; default: LOG_DBG("Unknown USB power event"); return; } ctx->flags |= BIT(NRF_USB_STATE_CHANGE); k_work_submit(&ctx->usb_work); } /** * @brief Enable/Disable the HF clock * * Toggle the HF clock. It needs to be enabled for USBD data exchange * * @param on Set true to enable the HF clock, false to disable. * @param blocking Set true to block wait till HF clock stabilizes. * * @return 0 on success, error number otherwise */ static int hf_clock_enable(bool on, bool blocking) { int ret = -ENODEV; struct device *clock; clock = device_get_binding(CONFIG_CLOCK_CONTROL_NRF5_M16SRC_DRV_NAME); if (!clock) { LOG_ERR("NRF HF Clock device not found!"); return ret; } if (on) { ret = clock_control_on(clock, (void *)blocking); } else { ret = clock_control_off(clock, (void *)blocking); } if (ret && (blocking || (ret != -EINPROGRESS))) { LOG_ERR("HF clock %s fail: %d", on ? "start" : "stop", ret); return ret; } LOG_DBG("HF clock %s success (%d)", on ? "start" : "stop", ret); return ret; } static void usbd_enable_endpoints(struct nrf_usbd_ctx *ctx) { struct nrf_usbd_ep_ctx *ep_ctx; int i; for (i = 0; i < NRF_USBD_EPIN_CNT; i++) { ep_ctx = in_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); if (ep_ctx->cfg.en) { nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } } for (i = 0; i < NRF_USBD_EPOUT_CNT; i++) { ep_ctx = out_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); if (ep_ctx->cfg.en) { nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); } } } static void usbd_handle_state_change(struct nrf_usbd_ctx *ctx) { switch (ctx->state) { case USBD_ATTACHED: LOG_DBG("USB detected"); nrfx_usbd_enable(); break; case USBD_POWERED: LOG_DBG("USB Powered"); ctx->status_code = USB_DC_CONNECTED; ctx->flags |= BIT(NRF_USB_STATUS_CHANGE); usbd_enable_endpoints(ctx); nrfx_usbd_start(true); ctx->ready = true; break; case USBD_DETACHED: LOG_DBG("USB Removed"); ctx->ready = false; nrfx_usbd_disable(); ctx->status_code = USB_DC_DISCONNECTED; ctx->flags |= BIT(NRF_USB_STATUS_CHANGE); break; default: break; } if (ctx->flags) { k_work_submit(&ctx->usb_work); } } static void usbd_handle_status_change(struct nrf_usbd_ctx *ctx) { if (ctx->status_cb) { ctx->status_cb(ctx->status_code, NULL); } } 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(); usbd_setup->bRequest = nrf_usbd_setup_brequest_get(); usbd_setup->wValue = nrf_usbd_setup_wvalue_get(); usbd_setup->wIndex = nrf_usbd_setup_windex_get(); usbd_setup->wLength = nrf_usbd_setup_wlength_get(); ep_ctx->buf.len = sizeof(struct usb_setup_packet); LOG_DBG("SETUP: r:%d rt:%d v:%d i:%d l:%d", (u32_t)usbd_setup->bRequest, (u32_t)usbd_setup->bmRequestType, (u32_t)usbd_setup->wValue, (u32_t)usbd_setup->wIndex, (u32_t)usbd_setup->wLength); /* Inform the stack. */ ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_SETUP); if (((usbd_setup->bmRequestType & USB_BMREQUESTTYPE_MASK) == USB_BMREQUESTTYPE_HOSTTODEVICE_MASK) && (usbd_setup->wLength)) { nrfx_usbd_setup_data_clear(); } } 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_sem_take(&ctx->dma_in_use, K_FOREVER); NRFX_USBD_TRANSFER_OUT(transfer, ep_ctx->buf.data, ep_ctx->cfg.max_sz); nrfx_err_t err = nrfx_usbd_ep_transfer( ep_addr_to_nrfx(ep_ctx->cfg.addr), &transfer); if (err != NRFX_SUCCESS) { LOG_ERR("nRF USBD transfer error (OUT): %d.", err); k_sem_give(&ctx->dma_in_use); } } /* Work handler */ static void usbd_work_handler(struct k_work *item) { struct nrf_usbd_ctx *ctx; struct usbd_ep_event *ev; ctx = CONTAINER_OF(item, struct nrf_usbd_ctx, usb_work); if (ctx->flags) { if (ctx->flags & BIT(NRF_USB_STATE_CHANGE)) { usbd_handle_state_change(ctx); ctx->flags &= ~BIT(NRF_USB_STATE_CHANGE); } if (ctx->flags & BIT(NRF_USB_STATUS_CHANGE)) { usbd_handle_status_change(ctx); ctx->flags &= ~BIT(NRF_USB_STATUS_CHANGE); } } while ((ev = usbd_evt_get()) != NULL) { if (!ctx->attached) { LOG_ERR("USBD event dropped (not attached): %d.", (uint32_t)ev->evt); } else { struct nrf_usbd_ep_ctx *ep_ctx = ev->ep; switch (ev->evt) { 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) { k_sem_take(&ctx->dma_in_use, K_FOREVER); nrfx_usbd_setup_clear(); k_sem_give(&ctx->dma_in_use); } ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_IN); break; default: break; } } usbd_evt_free(ev); } } static inline bool dev_attached(void) { return get_usbd_ctx()->attached; } static inline bool dev_ready(void) { return get_usbd_ctx()->ready; } static void endpoint_ctx_init(void) { struct nrf_usbd_ep_ctx *ep_ctx; int ret; u32_t i; for (i = 0; i < CFG_EPIN_CNT; i++) { ep_ctx = in_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); ret = k_mem_pool_alloc(&ep_buf_pool, &ep_ctx->buf.block, MAX_EP_BUF_SZ, K_NO_WAIT); if (ret < 0) { LOG_ERR("EP buffer alloc failed for EPIN%d", i); __ASSERT_NO_MSG(0); } ep_ctx->buf.data = ep_ctx->buf.block.data; ep_ctx->buf.curr = ep_ctx->buf.data; ep_ctx->read_complete = true; ep_ctx->read_pending = false; } for (i = 0; i < CFG_EPOUT_CNT; i++) { ep_ctx = out_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); ret = k_mem_pool_alloc(&ep_buf_pool, &ep_ctx->buf.block, MAX_EP_BUF_SZ, K_NO_WAIT); if (ret < 0) { LOG_ERR("EP buffer alloc failed for EPOUT%d", i); __ASSERT_NO_MSG(0); } ep_ctx->buf.data = ep_ctx->buf.block.data; ep_ctx->buf.curr = ep_ctx->buf.data; ep_ctx->read_complete = true; ep_ctx->read_pending = false; } if (CFG_EP_ISOIN_CNT) { ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); __ASSERT_NO_MSG(ep_ctx); ret = k_mem_pool_alloc(&ep_buf_pool, &ep_ctx->buf.block, MAX_ISO_EP_BUF_SZ, K_NO_WAIT); if (ret < 0) { LOG_ERR("EP buffer alloc failed for ISOIN"); __ASSERT_NO_MSG(0); } ep_ctx->buf.data = ep_ctx->buf.block.data; ep_ctx->buf.curr = ep_ctx->buf.data; ep_ctx->read_complete = true; ep_ctx->read_pending = false; } if (CFG_EP_ISOOUT_CNT) { ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); __ASSERT_NO_MSG(ep_ctx); ret = k_mem_pool_alloc(&ep_buf_pool, &ep_ctx->buf.block, MAX_ISO_EP_BUF_SZ, K_NO_WAIT); if (ret < 0) { LOG_ERR("EP buffer alloc failed for ISOOUT"); __ASSERT_NO_MSG(0); } ep_ctx->buf.data = ep_ctx->buf.block.data; ep_ctx->buf.curr = ep_ctx->buf.data; ep_ctx->read_complete = true; ep_ctx->read_pending = false; } } static void endpoint_ctx_deinit(void) { struct nrf_usbd_ep_ctx *ep_ctx; u32_t i; for (i = 0; i < CFG_EPIN_CNT; i++) { ep_ctx = in_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); k_mem_pool_free(&ep_ctx->buf.block); memset(ep_ctx, 0, sizeof(*ep_ctx)); } for (i = 0; i < CFG_EPOUT_CNT; i++) { ep_ctx = out_endpoint_ctx(i); __ASSERT_NO_MSG(ep_ctx); k_mem_pool_free(&ep_ctx->buf.block); memset(ep_ctx, 0, sizeof(*ep_ctx)); } if (CFG_EP_ISOIN_CNT) { ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); __ASSERT_NO_MSG(ep_ctx); k_mem_pool_free(&ep_ctx->buf.block); memset(ep_ctx, 0, sizeof(*ep_ctx)); } if (CFG_EP_ISOOUT_CNT) { ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); __ASSERT_NO_MSG(ep_ctx); k_mem_pool_free(&ep_ctx->buf.block); memset(ep_ctx, 0, sizeof(*ep_ctx)); } } static void usbd_event_transfer_ctrl(nrfx_usbd_evt_t const *const p_event) { struct nrf_usbd_ep_ctx *ep_ctx = endpoint_ctx(p_event->data.eptransfer.ep); struct nrf_usbd_ctx *ctx = get_usbd_ctx(); if (NRF_USBD_EPIN_CHECK(p_event->data.eptransfer.ep)) { switch (p_event->data.eptransfer.status) { case NRFX_USBD_EP_OK: { struct usbd_ep_event *ev = usbd_evt_alloc(); ev->ep = ep_ctx; ev->evt = EP_EVT_WRITE_COMPLETE; k_sem_give(&ctx->dma_in_use); LOG_DBG("ctrl write complete"); usbd_evt_put(ev); usbd_work_schedule(); } break; default: { LOG_ERR( "Unexpected event (nrfx_usbd): %d, ep %d", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); k_sem_give(&ctx->dma_in_use); } break; } } else { switch (p_event->data.eptransfer.status) { case NRFX_USBD_EP_WAITING: { struct usbd_ep_event *ev = usbd_evt_alloc(); LOG_DBG("ctrl read request"); ep_ctx->read_pending = true; ev->ep = ep_ctx; ev->evt = EP_EVT_RECV_REQ; usbd_evt_put(ev); usbd_work_schedule(); } break; case NRFX_USBD_EP_OK: { struct usbd_ep_event *ev = usbd_evt_alloc(); nrfx_err_t err_code; ev->ep = ep_ctx; ev->evt = EP_EVT_RECV_COMPLETE; err_code = nrfx_usbd_ep_status_get( p_event->data.eptransfer.ep, &ep_ctx->buf.len); if ((err_code != NRFX_SUCCESS) && (err_code != (nrfx_err_t)NRFX_USBD_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); k_sem_give(&ctx->dma_in_use); usbd_evt_put(ev); usbd_work_schedule(); } break; default: { LOG_ERR("Unexpected event from nrfx_usbd: %d, ep %d", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); k_sem_give(&ctx->dma_in_use); } break; } } } static void usbd_event_transfer_data(nrfx_usbd_evt_t const *const p_event) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); 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 NRFX_USBD_EP_OK: { struct usbd_ep_event *ev = usbd_evt_alloc(); ev->ep = ep_ctx; ev->evt = EP_EVT_WRITE_COMPLETE; LOG_DBG("write complete, ep %d", (u32_t)p_event->data.eptransfer.ep); k_sem_give(&ctx->dma_in_use); usbd_evt_put(ev); usbd_work_schedule(); } break; default: { LOG_ERR("Unexpected event from nrfx_usbd: %d, ep %d", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); k_sem_give(&ctx->dma_in_use); } break; } } else { switch (p_event->data.eptransfer.status) { case NRFX_USBD_EP_WAITING: { struct usbd_ep_event *ev = usbd_evt_alloc(); LOG_DBG("read request, ep %d", (u32_t)p_event->data.eptransfer.ep); ev->ep = ep_ctx; ev->evt = EP_EVT_RECV_REQ; usbd_evt_put(ev); ep_ctx->read_pending = true; usbd_work_schedule(); } break; case NRFX_USBD_EP_OK: { struct usbd_ep_event *ev = usbd_evt_alloc(); ev->ep = ep_ctx; ev->evt = EP_EVT_RECV_COMPLETE; ep_ctx->buf.len = nrf_usbd_ep_amount_get( p_event->data.eptransfer.ep); LOG_DBG("read complete, ep %d, len %d", (u32_t)p_event->data.eptransfer.ep, ep_ctx->buf.len); k_sem_give(&ctx->dma_in_use); usbd_evt_put(ev); usbd_work_schedule(); } break; default: { LOG_ERR("Unexpected event from nrfx_usbd: %d, ep %d", p_event->data.eptransfer.status, p_event->data.eptransfer.ep); k_sem_give(&ctx->dma_in_use); } break; } } } /** * @brief nRFx USBD driver event handler function. */ static void usbd_event_handler(nrfx_usbd_evt_t const *const p_event) { struct nrf_usbd_ep_ctx *ep_ctx; struct usbd_ep_event *ev; switch (p_event->type) { case NRFX_USBD_EVT_SUSPEND: LOG_DBG("SUSPEND state detected."); break; case NRFX_USBD_EVT_RESUME: LOG_DBG("RESUMING from suspend."); break; case NRFX_USBD_EVT_WUREQ: LOG_DBG("RemoteWU initiated."); break; case NRFX_USBD_EVT_RESET: LOG_DBG("USBD Reset."); usbd_status_code_update(USB_DC_RESET); break; case NRFX_USBD_EVT_SOF: break; case NRFX_USBD_EVT_EPTRANSFER: 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 NRFX_USBD_EVT_SETUP: { nrfx_usbd_setup_t drv_setup; nrfx_usbd_setup_get(&drv_setup); if (drv_setup.bmRequest != USB_BMREQUEST_SETADDRESS) { /* SetAddress is habdled by USBD hardware. * No software action required. */ struct nrf_usbd_ep_ctx *ep_ctx = endpoint_ctx(NRF_USBD_EPOUT(0)); ev = usbd_evt_alloc(); ev->ep = ep_ctx; ev->evt = EP_EVT_SETUP_RECV; usbd_evt_put(ev); usbd_work_schedule(); } break; } default: break; } } int usb_dc_attach(void) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); nrfx_err_t err; int ret; if (ctx->attached) { return 0; } k_work_init(&ctx->usb_work, usbd_work_handler); k_fifo_init(&ctx->work_queue); k_sem_init(&ctx->dma_in_use, 1, 1); IRQ_CONNECT(CONFIG_USBD_NRF_IRQ, CONFIG_USBD_NRF_IRQ_PRI, nrfx_isr, nrfx_usbd_irq_handler, 0); /* NOTE: Non-blocking HF clock enable can return -EINPROGRESS * if HF clock start was already requested. */ ret = hf_clock_enable(true, false); if (ret && ret != -EINPROGRESS) { return ret; } err = nrfx_usbd_init(usbd_event_handler); if (err != NRFX_SUCCESS) { LOG_DBG("nRF USBD driver init failed. Code: %d.", (u32_t)err); return -EIO; } nrf5_power_usb_power_int_enable(true); endpoint_ctx_init(); ctx->attached = true; return 0; } int usb_dc_detach(void) { struct nrf_usbd_ctx *ctx = get_usbd_ctx(); int ret; ctx->flags = 0; ctx->state = USBD_DETACHED; ctx->status_code = USB_DC_UNKNOWN; usbd_evt_flush(); k_sem_reset(&ctx->dma_in_use); endpoint_ctx_deinit(); nrfx_usbd_disable(); nrfx_usbd_uninit(); ret = hf_clock_enable(false, false); if (ret) { return ret; } nrf5_power_usb_power_int_enable(false); if (ret) { return ret; } ctx->attached = false; return ret; } 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 u8_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 == (u8_t)NRF_USBD->USBADDR, "USB Address incorrect!"); ctx = get_usbd_ctx(); ctx->state = USBD_ADDRESS_SET; 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) { u8_t ep_idx = NRF_USBD_EP_NR_GET(ep_cfg->ep_addr); LOG_DBG("ep %x, 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; } 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 ((ep_cfg->ep_mps & (ep_cfg->ep_mps - 1)) != 0) { LOG_ERR("EP max packet size must be a power of 2."); return -EINVAL; } nrfx_usbd_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 u8_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: nrfx_usbd_setup_stall(); break; case USB_DC_EP_BULK: case USB_DC_EP_INTERRUPT: nrfx_usbd_ep_stall(ep_addr_to_nrfx(ep)); break; case USB_DC_EP_ISOCHRONOUS: LOG_ERR("STALL unsupported on ISO endpoint.s"); return -EINVAL; } ep_ctx->buf.len = 0; ep_ctx->buf.curr = ep_ctx->buf.data; LOG_DBG("STALL on EP %d.", ep); return 0; } int usb_dc_ep_clear_stall(const u8_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; } nrfx_usbd_ep_stall_clear(ep_addr_to_nrfx(ep)); LOG_DBG("Unstall on EP %d", ep); return 0; } int usb_dc_ep_halt(const u8_t ep) { return usb_dc_ep_set_stall(ep); } int usb_dc_ep_is_stalled(const u8_t ep, u8_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; } *stalled = (u8_t) nrfx_usbd_ep_stall_check(ep_addr_to_nrfx(ep)); return 0; } int usb_dc_ep_enable(const u8_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 (ep_ctx->cfg.en) { return -EALREADY; } LOG_DBG("EP enable: %d.", ep); ep_ctx->cfg.en = true; /* Defer the endpoint enable if USBD is not ready yet. */ if (dev_ready()) { nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep)); } return 0; } int usb_dc_ep_disable(const u8_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 (!ep_ctx->cfg.en) { return -EALREADY; } LOG_DBG("EP disable: %d.", ep); nrfx_usbd_ep_disable(ep_addr_to_nrfx(ep)); ep_ctx->cfg.en = false; return 0; } int usb_dc_ep_flush(const u8_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 = 0; ep_ctx->buf.curr = ep_ctx->buf.data; nrfx_usbd_transfer_out_drop(ep_addr_to_nrfx(ep)); return 0; } int usb_dc_ep_write(const u8_t ep, const u8_t *const data, const u32_t data_len, u32_t *const ret_bytes) { LOG_DBG("ep_write: ep %d, len %d", ep, data_len); struct nrf_usbd_ctx *ctx = get_usbd_ctx(); struct nrf_usbd_ep_ctx *ep_ctx; u32_t bytes_to_copy; 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; } int sem_status = k_sem_take(&ctx->dma_in_use, K_NO_WAIT); /* USBD hardware does not allow scheduling multiple DMA transfers * at a time. Next USB transfer can be triggered after all DMA * operations are complete. */ if (sem_status != 0) { return -EAGAIN; } /* Data length longer than ep_ctx->cfg.max_sz is allowed. * NRFX driver performs the fragmentation. */ bytes_to_copy = data_len; memcpy(ep_ctx->buf.data, data, bytes_to_copy); ep_ctx->buf.len = bytes_to_copy; if (ret_bytes) { *ret_bytes = bytes_to_copy; } /* 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) && (nrfx_usbd_last_setup_dir_get() != ep)) { k_sem_give(&ctx->dma_in_use); nrfx_usbd_setup_clear(); return 0; } NRFX_USBD_TRANSFER_IN(transfer, ep_ctx->buf.data, ep_ctx->buf.len, 0); nrfx_err_t err = nrfx_usbd_ep_transfer(ep_addr_to_nrfx(ep), &transfer); if (err != NRFX_SUCCESS) { k_sem_give(&ctx->dma_in_use); LOG_ERR("nRF USBD write error: %d.", (u32_t)err); __ASSERT_NO_MSG(0); } return 0; } int usb_dc_ep_read_wait(u8_t ep, u8_t *data, u32_t max_data_len, u32_t *read_bytes) { struct nrf_usbd_ep_ctx *ep_ctx; struct nrf_usbd_ctx *ctx = get_usbd_ctx(); u32_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; } k_sem_take(&ctx->dma_in_use, 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_sem_give(&ctx->dma_in_use); 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_sem_give(&ctx->dma_in_use); return 0; } int usb_dc_ep_read_continue(u8_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; } k_sem_take(&ctx->dma_in_use, 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_ep_event *ev = usbd_evt_alloc(); ev->ep = ep_ctx; ev->evt = EP_EVT_RECV_REQ; usbd_evt_put(ev); usbd_work_schedule(); } } k_sem_give(&ctx->dma_in_use); return 0; } int usb_dc_ep_read(const u8_t ep, u8_t *const data, const u32_t max_data_len, u32_t *const read_bytes) { LOG_DBG("ep_read: ep %d, 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 u8_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; } int usb_dc_set_status_callback(const usb_dc_status_callback cb) { get_usbd_ctx()->status_cb = cb; return 0; } int usb_dc_ep_mps(const u8_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; }