blob: 9a0cc4acbff96df0a7715afa658434b35f6879b6 [file] [log] [blame]
/*
* Copyright (c) 2018 Sundar Subramaniyan <sundar.subramaniyan@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file usb_dc_nrf5.c
* @brief nRF52840 USB device controller driver
*
* The driver implements the low level control routines to deal directly
* with the nRF52840 USBD peripheral.
*/
#include <soc.h>
#include <string.h>
#include <stdio.h>
#include <kernel.h>
#include <usb/usb_dc.h>
#include <usb/usb_device.h>
#include <clock_control.h>
#include <nrf_power.h>
#include <drivers/clock_control/nrf5_clock_control.h>
/* TODO: Find alternative for ASSERT in nrf_usbd.h */
#ifndef ASSERT
#define ASSERT(__x) __ASSERT_NO_MSG((__x))
#endif
#include <nrf_usbd.h>
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_USB_DRIVER_LEVEL
#define SYS_LOG_DOMAIN "usb/dc"
#include <logging/sys_log.h>
#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 NRF5_USB_STATE_CHANGE 0
#define NRF5_USB_STATUS_CHANGE 1
/**
* @brief USBD states
*/
enum nrf5_usbd_state {
USBD_DETACHED,
USBD_ATTACHED,
USBD_POWERED,
USBD_SUSPENDED,
USBD_DEFAULT,
USBD_ADDRESS_SET,
USBD_CONFIGURED,
};
/**
* @brief Endpoint state
*/
enum ep_state {
EP_IDLE,
EP_SETUP,
EP_DATA,
EP_STATUS,
};
/**
* @brief Endpoint event
*/
enum ep_event {
EP_SETUP_RECV,
EP_DATA_RECV,
EP_DMA_START,
EP_DMA_END,
EP_WRITE_COMPLETE,
EP_SOF,
};
/**
* @brief Miscellaneous endpoint event flags
*/
#define EP_CONTROL_READ 0
#define EP_CONTROL_WRITE 1
#define EP_CONTROL_WRITE_NO_DATA 2
#define EP_OUT_DATA_RCVD 3
#define EP_WRITE_PENDING 4
/**
* @brief Endpoint configuration
*
* @param en Enable/Disable flag
* @param addr Endpoint address
* @param max_sz Max packet size supported by endpoint
* @param type Endpoint type
* @param cb Endpoint callback
*/
struct nrf5_usbd_ep_cfg {
bool en;
u8_t addr;
u32_t max_sz;
enum usb_dc_ep_type type;
usb_dc_ep_callback cb;
};
/**
* @brief Endpoint buffer
*
* @param data Pointer to the data buffer
* for the endpoint
* @param curr Pointer to the current
* offset in the endpoint buffer
* @param len Remaining length to be read/written
* @param block Mempool block, for freeing up buffer after use
*/
struct nrf5_usbd_ep_buf {
u8_t *data;
u8_t *curr;
u32_t len;
struct k_mem_block block;
};
/**
* @brief Endpoint context
*
* @param cfg Endpoint configuration
* @param buf Endpoint buffer
* @param state Endpoint's current state
* @param flags Endpoint's flags
*/
struct nrf5_usbd_ep_ctx {
struct nrf5_usbd_ep_cfg cfg;
struct nrf5_usbd_ep_buf buf;
enum ep_state state;
u32_t flags;
};
/**
* @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 ep_usb_event {
sys_snode_t node;
struct nrf5_usbd_ep_ctx *ep;
enum ep_event 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 ep_usb_event)
#define FIFO_ELEM_MAX_SZ sizeof(struct ep_usb_event)
#define FIFO_ELEM_COUNT 16
#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_NRF5_NUM_IN_EP + \
CONFIG_USBD_NRF5_NUM_BIDIR_EP)
/** Number of OUT Endpoints configured (including control) */
#define CFG_EPOUT_CNT (CONFIG_USBD_NRF5_NUM_OUT_EP + \
CONFIG_USBD_NRF5_NUM_BIDIR_EP)
/** Number of ISO IN Endpoints */
#define CFG_EP_ISOIN_CNT CONFIG_USBD_NRF5_NUM_ISOIN_EP
/** Number of ISO OUT Endpoints */
#define CFG_EP_ISOOUT_CNT CONFIG_USBD_NRF5_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 enabled USBD Enable/Disable flag
* @param attached USBD Attached flag
* @param ready USBD Ready flag set after pullup
* @param address_set USB Address set or unset
* @param state USBD state
* @param status_code Device Status code
* @param flags Flags used in work context
* @param enable_mask Enabled interrupts mask
* @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 status_cb Status callback for USB DC notifications
* @param ep_ctx Endpoint contexts
* @param buf Buffer for the endpoints, aligned to 4 byte boundary
*/
struct nrf5_usbd_ctx {
bool enabled;
bool attached;
bool ready;
bool address_set;
enum nrf5_usbd_state state;
enum usb_dc_status_code status_code;
u32_t flags;
u32_t enable_mask;
struct k_work usb_work;
struct k_fifo work_queue;
struct k_sem dma_in_use;
usb_dc_status_callback status_cb;
struct nrf5_usbd_ep_ctx ep_ctx[CFG_EP_CNT];
};
static struct nrf5_usbd_ctx usbd_ctx;
static inline struct nrf5_usbd_ctx *get_usbd_ctx(void)
{
return &usbd_ctx;
}
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 nrf5_usbd_ep_ctx *endpoint_ctx(const u8_t ep)
{
struct nrf5_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 nrf5_usbd_ep_ctx *in_endpoint_ctx(const u8_t ep)
{
return endpoint_ctx(NRF_USBD_EPIN(ep));
}
static struct nrf5_usbd_ep_ctx *out_endpoint_ctx(const u8_t ep)
{
return endpoint_ctx(NRF_USBD_EPOUT(ep));
}
static void cfg_ep_interrupt(const u8_t ep, bool set)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
u8_t ep_num = NRF_USBD_EP_NR_GET(ep);
u32_t mask = 0;
if (NRF_USBD_EPIN_CHECK(ep)) {
if (NRF_USBD_EPISO_CHECK(ep)) {
mask |= (NRF_USBD_INT_ENDISOIN0_MASK |
NRF_USBD_INT_SOF_MASK);
} else {
mask |= BIT(ep_num + USBD_INTEN_ENDEPIN0_Pos);
if (ep_num == 0) {
mask |= NRF_USBD_INT_EP0DATADONE_MASK;
mask |= NRF_USBD_INT_EP0SETUP_MASK;
} else {
mask |= NRF_USBD_INT_DATAEP_MASK;
}
}
} else {
if (NRF_USBD_EPISO_CHECK(ep)) {
mask |= (NRF_USBD_INT_ENDISOOUT0_MASK |
NRF_USBD_INT_SOF_MASK);
} else {
mask |= BIT(ep_num + USBD_INTEN_ENDEPOUT0_Pos);
if (ep_num == 0) {
mask |= NRF_USBD_INT_EP0DATADONE_MASK;
mask |= NRF_USBD_INT_EP0SETUP_MASK;
} else {
mask |= NRF_USBD_INT_DATAEP_MASK;
}
}
}
irq_disable(CONFIG_USBD_NRF5_IRQ);
if (set) {
mask |= NRF_USBD_INT_STARTED_MASK;
nrf_usbd_int_enable(mask);
} else {
nrf_usbd_int_disable(mask);
}
ctx->enable_mask = nrf_usbd_int_enable_get();
irq_enable(CONFIG_USBD_NRF5_IRQ);
}
static struct nrf5_usbd_ep_ctx *epstatus_to_ep_ctx(u32_t epstatus)
{
int pos;
u8_t ep = 0;
/**
* TODO: Is it possible for multiple bits to be set?
* When debug logs are enabled, they cause delays in
* handling the ISR and possibly multiple EP status
* or EPDATA status were set. If this is common, we
* may have to handle them all here by allocating
* multiple events and queue them up to the FIFO.
*/
if (popcount(epstatus) > 1) {
SYS_LOG_ERR("%d bits set in epstatus!!",
popcount(epstatus));
__ASSERT_NO_MSG(0);
}
if (epstatus & USBD_EPSTATUS_EPIN_MASK) {
pos = USBD_EPSTATUS_EPIN0_Pos;
while (pos <= USBD_EPSTATUS_EPIN8_Pos) {
if (epstatus & BIT(pos)) {
ep = NRF_USBD_EPIN(pos);
return endpoint_ctx(ep);
}
pos++;
}
}
if (epstatus & USBD_EPSTATUS_EPOUT_MASK) {
pos = USBD_EPSTATUS_EPOUT0_Pos;
while (pos <= USBD_EPSTATUS_EPOUT8_Pos) {
if (epstatus & BIT(pos)) {
ep = NRF_USBD_EPOUT(pos -
USBD_EPSTATUS_EPOUT0_Pos);
return endpoint_ctx(ep);
}
pos++;
}
}
SYS_LOG_ERR("invalid epstatus 0x%08x", epstatus);
__ASSERT_NO_MSG(0);
return NULL;
}
static struct nrf5_usbd_ep_ctx *epdatastatus_to_ep_ctx(u32_t epdatastatus)
{
int pos;
u8_t ep = 0;
/**
* TODO: Is it possible for multiple bits to be set?
* When debug logs are enabled, they cause delays in
* handling the ISR and possibly multiple EP status
* or EPDATA status were set. If this is common, we
* may have to handle them all here by allocating
* multiple events and queue them up to the FIFO.
*/
if (popcount(epdatastatus) > 1) {
SYS_LOG_ERR("%d bits set in epdatastatus!!",
popcount(epdatastatus));
__ASSERT_NO_MSG(0);
}
if (epdatastatus & USBD_EPDATASTATUS_EPIN_MASK) {
pos = USBD_EPDATASTATUS_EPIN1_Pos;
while (pos <= USBD_EPDATASTATUS_EPIN7_Pos) {
if (epdatastatus & BIT(pos)) {
ep = NRF_USBD_EPIN(pos);
return endpoint_ctx(ep);
}
pos++;
}
}
if (epdatastatus & USBD_EPDATASTATUS_EPOUT_MASK) {
pos = USBD_EPDATASTATUS_EPOUT1_Pos;
while (pos <= USBD_EPDATASTATUS_EPOUT7_Pos) {
if (epdatastatus & BIT(pos)) {
ep = NRF_USBD_EPOUT(pos -
USBD_EPSTATUS_EPOUT0_Pos);
return endpoint_ctx(ep);
}
pos++;
}
}
SYS_LOG_ERR("invalid epdatastatus 0x%08x", epdatastatus);
__ASSERT_NO_MSG(0);
return NULL;
}
static void start_epin_task(u8_t ep)
{
u8_t epnum = NRF_USBD_EP_NR_GET(ep);
nrf_usbd_task_t task;
if (NRF_USBD_EPOUT_CHECK(ep)) {
SYS_LOG_ERR("invalid endpoint!");
return;
}
if (epnum > NRF_USBD_EPIN_CNT) {
SYS_LOG_ERR("invalid endpoint %d", epnum);
return;
}
task = NRF_USBD_TASK_STARTEPIN0 + (epnum * sizeof(u32_t));
nrf_usbd_task_trigger(task);
}
static void start_epout_task(u8_t ep)
{
u8_t epnum = NRF_USBD_EP_NR_GET(ep);
nrf_usbd_task_t task;
if (NRF_USBD_EPIN_CHECK(ep)) {
SYS_LOG_ERR("invalid endpoint!");
return;
}
if (epnum > NRF_USBD_EPOUT_CNT) {
SYS_LOG_ERR("invalid endpoint %d", epnum);
return;
}
task = NRF_USBD_TASK_STARTEPOUT0 + (epnum * sizeof(u32_t));
nrf_usbd_task_trigger(task);
}
static void start_ep0rcvout_task(void)
{
nrf_usbd_task_trigger(NRF_USBD_TASK_EP0RCVOUT);
}
static void start_ep0status_task(void)
{
nrf_usbd_task_trigger(NRF_USBD_TASK_EP0STATUS);
}
static void start_ep0stall_task(void)
{
nrf_usbd_task_trigger(NRF_USBD_TASK_EP0STALL);
}
static inline struct ep_usb_event *alloc_ep_usb_event(void)
{
int ret;
struct ep_usb_event *ev;
struct k_mem_block block;
ret = k_mem_pool_alloc(&fifo_elem_pool, &block,
sizeof(struct ep_usb_event),
K_NO_WAIT);
if (ret < 0) {
SYS_LOG_DBG("ep usb event alloc failed!");
__ASSERT_NO_MSG(0);
return NULL;
}
ev = (struct ep_usb_event *)block.data;
memcpy(&ev->block, &block, sizeof(block));
ev->misc_u.flags = 0;
return ev;
}
static inline void free_ep_usb_event(struct ep_usb_event *ev)
{
k_mem_pool_free(&ev->block);
}
static inline void enqueue_ep_usb_event(struct ep_usb_event *ev)
{
k_fifo_put(&get_usbd_ctx()->work_queue, ev);
}
static inline struct ep_usb_event *dequeue_ep_usb_event(void)
{
return k_fifo_get(&get_usbd_ctx()->work_queue, K_NO_WAIT);
}
static inline void flush_ep_usb_events(void)
{
struct ep_usb_event *ev;
do {
ev = dequeue_ep_usb_event();
if (ev) {
free_ep_usb_event(ev);
}
} while (ev != NULL);
}
/** Interrupt mask to event register map */
#define USBD_INT_CNT 25
static const u32_t event_map[USBD_INT_CNT] = {
NRF_USBD_EVENT_USBRESET,
NRF_USBD_EVENT_STARTED,
NRF_USBD_EVENT_ENDEPIN0,
NRF_USBD_EVENT_ENDEPIN1,
NRF_USBD_EVENT_ENDEPIN2,
NRF_USBD_EVENT_ENDEPIN3,
NRF_USBD_EVENT_ENDEPIN4,
NRF_USBD_EVENT_ENDEPIN5,
NRF_USBD_EVENT_ENDEPIN6,
NRF_USBD_EVENT_ENDEPIN7,
NRF_USBD_EVENT_EP0DATADONE,
NRF_USBD_EVENT_ENDISOIN0,
NRF_USBD_EVENT_ENDEPOUT0,
NRF_USBD_EVENT_ENDEPOUT1,
NRF_USBD_EVENT_ENDEPOUT2,
NRF_USBD_EVENT_ENDEPOUT3,
NRF_USBD_EVENT_ENDEPOUT4,
NRF_USBD_EVENT_ENDEPOUT5,
NRF_USBD_EVENT_ENDEPOUT6,
NRF_USBD_EVENT_ENDEPOUT7,
NRF_USBD_EVENT_ENDISOOUT0,
NRF_USBD_EVENT_SOF,
NRF_USBD_EVENT_USBEVENT,
NRF_USBD_EVENT_EP0SETUP,
NRF_USBD_EVENT_DATAEP,
};
static void usb_reset_handler(u32_t pos)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
ARG_UNUSED(pos);
ctx->status_code = USB_DC_RESET;
ctx->flags |= BIT(NRF5_USB_STATUS_CHANGE);
}
static void usb_started_handler(u32_t pos)
{
u32_t epstatus = nrf_usbd_epstatus_get_and_clear();
struct ep_usb_event *ev;
ARG_UNUSED(pos);
ev = alloc_ep_usb_event();
ev->ep = epstatus_to_ep_ctx(epstatus);
ev->evt = EP_DMA_START;
enqueue_ep_usb_event(ev);
}
static void end_epin_handler(u32_t pos)
{
struct ep_usb_event *ev;
u8_t ep;
ep = NRF_USBD_EPIN(pos - (u8_t)USBD_INTEN_ENDEPIN0_Pos);
ev = alloc_ep_usb_event();
ev->ep = endpoint_ctx(ep);
ev->evt = EP_DMA_END;
enqueue_ep_usb_event(ev);
}
static void ep0_datadone_handler(u32_t pos)
{
struct ep_usb_event *ev;
u8_t ep = NRF_USBD_EPIN(0);
ARG_UNUSED(pos);
ev = alloc_ep_usb_event();
ev->ep = endpoint_ctx(ep);
/* See if this is IN or OUT event */
if (ev->ep->state == EP_DATA) {
ev->evt = EP_WRITE_COMPLETE;
} else {
ep = NRF_USBD_EPOUT(0);
ev->ep = endpoint_ctx(ep);
/**
* This is just an indication of OUT data received into
* local buffer. We need to trigger DMA in work handler.
*/
ev->evt = EP_DATA_RECV;
}
enqueue_ep_usb_event(ev);
}
static void end_isoin_handler(u32_t pos)
{
struct ep_usb_event *ev;
u8_t ep;
ARG_UNUSED(pos);
ep = NRF_USBD_EPIN(NRF_USBD_EPISO_FIRST);
ev = alloc_ep_usb_event();
ev->ep = endpoint_ctx(ep);
ev->evt = EP_DMA_END;
enqueue_ep_usb_event(ev);
}
static void end_epout_handler(u32_t pos)
{
struct ep_usb_event *ev;
u8_t ep;
ep = NRF_USBD_EPOUT(pos - (u8_t)USBD_INTEN_ENDEPOUT0_Pos);
ev = alloc_ep_usb_event();
ev->ep = endpoint_ctx(ep);
ev->ep->buf.len = nrf_usbd_ep_amount_get(ep);
ev->evt = EP_DMA_END;
ev->misc_u.flags |= BIT(EP_OUT_DATA_RCVD);
enqueue_ep_usb_event(ev);
}
static void end_isoout_handler(u32_t pos)
{
struct ep_usb_event *ev;
u8_t ep;
ARG_UNUSED(pos);
/* TODO: Handle the CRC error case here */
ep = NRF_USBD_EPOUT(NRF_USBD_EPISO_FIRST);
ev = alloc_ep_usb_event();
ev->ep = endpoint_ctx(ep);
ev->ep->buf.len = nrf_usbd_ep_amount_get(ep);
ev->evt = EP_DMA_END;
enqueue_ep_usb_event(ev);
}
static void sof_handler(u32_t pos)
{
struct ep_usb_event *ev;
ARG_UNUSED(pos);
ev = alloc_ep_usb_event();
/* TODO: How to know if this is for ISOIN or ISOOUT? */
ev->ep = NULL;
ev->evt = EP_SOF;
ev->misc_u.frame_counter = nrf_usbd_framecntr_get();
enqueue_ep_usb_event(ev);
}
static void usb_event_handler(u32_t pos)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
u32_t eventcause;
ARG_UNUSED(pos);
eventcause = nrf_usbd_eventcause_get_and_clear();
if (eventcause & NRF_USBD_EVENTCAUSE_READY_MASK) {
/* TODO: Should we mark this somewhere? */
} else if (eventcause & NRF_USBD_EVENTCAUSE_ISOOUTCRC_MASK) {
/* TODO: Handle the CRC error case properly */
} else if (eventcause & NRF_USBD_EVENTCAUSE_SUSPEND_MASK) {
/* TODO: Add protection to discard write during suspend */
ctx->status_code = USB_DC_SUSPEND;
ctx->flags |= BIT(NRF5_USB_STATUS_CHANGE);
} else if (eventcause & NRF_USBD_EVENTCAUSE_RESUME_MASK) {
/* TODO: Take care of resume properly */
ctx->status_code = USB_DC_RESUME;
ctx->flags |= BIT(NRF5_USB_STATUS_CHANGE);
}
}
static void ep0setup_handler(u32_t pos)
{
struct nrf5_usbd_ep_ctx *ep_ctx;
struct usb_setup_packet *setup;
struct ep_usb_event *ev;
u8_t ep = NRF_USBD_EPOUT(0);
ARG_UNUSED(pos);
ep_ctx = endpoint_ctx(ep);
if (ep_ctx->buf.len) {
/**
* Pending device stack read
* This is happening during SET ADDRESS.
* From what it seems, the HW completes the STATUS stage for
* SET ADDR transaction even before software processes it.
* TODO: Find out if we can avoid this.
*/
return;
}
setup = (struct usb_setup_packet *)ep_ctx->buf.data;
setup->bmRequestType = nrf_usbd_setup_bmrequesttype_get();
setup->bRequest = nrf_usbd_setup_brequest_get();
setup->wValue = nrf_usbd_setup_wvalue_get();
setup->wIndex = nrf_usbd_setup_windex_get();
setup->wLength = nrf_usbd_setup_wlength_get();
ep_ctx->buf.len = sizeof(*setup);
ev = alloc_ep_usb_event();
ev->ep = ep_ctx;
ev->evt = EP_SETUP_RECV;
if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_DEVICE) {
if (setup->wLength) {
ev->misc_u.flags |= BIT(EP_CONTROL_WRITE);
} else {
ev->misc_u.flags |= BIT(EP_CONTROL_WRITE_NO_DATA);
}
} else {
ev->misc_u.flags |= BIT(EP_CONTROL_READ);
}
enqueue_ep_usb_event(ev);
}
static void epdata_handler(u32_t pos)
{
struct ep_usb_event *ev;
u32_t epdatastatus;
ARG_UNUSED(pos);
epdatastatus = nrf_usbd_epdatastatus_get_and_clear();
ev = alloc_ep_usb_event();
ev->ep = epdatastatus_to_ep_ctx(epdatastatus);
if (NRF_USBD_EPIN_CHECK(ev->ep->cfg.addr)) {
ev->evt = EP_WRITE_COMPLETE;
} else {
ev->evt = EP_DATA_RECV;
}
ev->misc_u.flags = 0;
enqueue_ep_usb_event(ev);
}
typedef void (*isr_event_handler_t)(u32_t pos);
static const isr_event_handler_t isr_event_handlers[USBD_INT_CNT] = {
usb_reset_handler,
usb_started_handler,
end_epin_handler,
end_epin_handler,
end_epin_handler,
end_epin_handler,
end_epin_handler,
end_epin_handler,
end_epin_handler,
end_epin_handler,
ep0_datadone_handler,
end_isoin_handler,
end_epout_handler,
end_epout_handler,
end_epout_handler,
end_epout_handler,
end_epout_handler,
end_epout_handler,
end_epout_handler,
end_epout_handler,
end_isoout_handler,
sof_handler,
usb_event_handler,
ep0setup_handler,
epdata_handler,
};
/** Process interrupts that are enabled */
static u32_t process_interrupts(u32_t mask)
{
u32_t processed = 0, pos = 0;
isr_event_handler_t evt_hdlr;
while (pos < USBD_INT_CNT) {
if (!(mask & BIT(pos))) {
pos++;
continue;
}
if (nrf_usbd_event_check(event_map[pos])) {
nrf_usbd_event_clear(event_map[pos]);
evt_hdlr = isr_event_handlers[pos];
if (evt_hdlr) {
evt_hdlr(pos);
processed++;
}
}
pos++;
}
return processed;
}
/**
* @brief Interrupt service routine.
*
* This handles the USBD interrupt
*
* @param arg Argument to ISR.
*
* @return N/A
*/
static void usbd_isr_handler(void *arg)
{
struct nrf5_usbd_ctx *ctx = arg;
if (!ctx->enabled) {
return;
}
irq_disable(CONFIG_USBD_NRF5_IRQ);
if (process_interrupts(ctx->enable_mask)) {
if (!k_work_pending(&ctx->usb_work)) {
k_work_submit(&ctx->usb_work);
}
}
irq_enable(CONFIG_USBD_NRF5_IRQ);
}
/**
* @brief Install the Interrupt service routine.
*
* This installs the interrupt service routine and enables the USBD interrupt.
*
* @return N/A
*/
static void usbd_install_isr(void)
{
IRQ_CONNECT(CONFIG_USBD_NRF5_IRQ,
CONFIG_USBD_NRF5_IRQ_PRI,
usbd_isr_handler, &usbd_ctx, 0);
}
static void usbd_enable_interrupts(void)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
ctx->enable_mask = NRF_USBD_INT_USBRESET_MASK |
NRF_USBD_INT_USBEVENT_MASK |
NRF_USBD_INT_DATAEP_MASK;
nrf_usbd_int_enable(ctx->enable_mask);
ctx->enabled = true;
irq_enable(CONFIG_USBD_NRF5_IRQ);
}
static void usbd_disable_interrupts(void)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
nrf_usbd_int_disable(~0);
ctx->enabled = false;
irq_disable(CONFIG_USBD_NRF5_IRQ);
}
void nrf5_usbd_power_event_callback(nrf_power_event_t event)
{
struct nrf5_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:
SYS_LOG_DBG("Unknown USB event");
return;
}
ctx->flags |= BIT(NRF5_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) {
SYS_LOG_ERR("NRF5 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) {
SYS_LOG_ERR("NRF5 HF clock %s fail: %d",
on ? "start" : "stop", ret);
return ret;
}
SYS_LOG_DBG("HF clock %s success", on ? "start" : "stop");
return ret;
}
static void usbd_enable_endpoints(struct nrf5_usbd_ctx *ctx)
{
struct nrf5_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) {
nrf_usbd_ep_enable(ep_ctx->cfg.addr);
cfg_ep_interrupt(ep_ctx->cfg.addr, true);
}
}
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) {
nrf_usbd_ep_enable(ep_ctx->cfg.addr);
cfg_ep_interrupt(ep_ctx->cfg.addr, true);
nrf_usbd_epout_clear(ep_ctx->cfg.addr);
}
}
}
static void usbd_handle_state_change(struct nrf5_usbd_ctx *ctx)
{
switch (ctx->state) {
case USBD_ATTACHED:
SYS_LOG_DBG("USB detected");
nrf_usbd_enable();
break;
case USBD_POWERED:
SYS_LOG_DBG("USB Powered");
ctx->status_code = USB_DC_CONNECTED;
ctx->flags |= BIT(NRF5_USB_STATUS_CHANGE);
usbd_enable_endpoints(ctx);
nrf_usbd_pullup_enable();
ctx->ready = true;
break;
case USBD_DETACHED:
SYS_LOG_DBG("USB Removed");
if (nrf_usbd_pullup_check()) {
nrf_usbd_pullup_disable();
ctx->ready = false;
}
nrf_usbd_disable();
ctx->status_code = USB_DC_DISCONNECTED;
ctx->flags |= BIT(NRF5_USB_STATUS_CHANGE);
break;
default:
SYS_LOG_ERR("Unknown USB state");
}
if (ctx->flags) {
k_work_submit(&ctx->usb_work);
}
}
static void usbd_handle_status_change(struct nrf5_usbd_ctx *ctx)
{
if (ctx->status_cb) {
ctx->status_cb(ctx->status_code, NULL);
}
}
/* Control read/write: Handle the HW events during IDLE state */
static inline void handle_ctrl_ep_idle_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_SETUP_RECV:
if (ev->misc_u.flags & BIT(EP_CONTROL_READ)) {
struct nrf5_usbd_ep_ctx *epin0;
/**
* SETUP packet comes through CTRL EPOUT0
* and control read data stage happens in CTRL
* EPIN0. So, go back to IDLE state in EPOUT0.
*/
ep_ctx->state = EP_IDLE;
/* Prepare EPIN0 for DATA stage */
epin0 = endpoint_ctx(NRF_USBD_EPIN(0));
epin0->state = EP_DATA;
} else if (ev->misc_u.flags & BIT(EP_CONTROL_WRITE)) {
ep_ctx->state = EP_SETUP;
/**
* Initiate reception of EP0 OUT DATA to USBD
* local buffer
*/
start_ep0rcvout_task();
} else if (ev->misc_u.flags & BIT(EP_CONTROL_WRITE_NO_DATA)) {
/* No DATA stage. Be in IDLE state. Redundant. */
ep_ctx->state = EP_IDLE;
}
/* Inform the stack */
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_SETUP);
break;
case EP_DATA_RECV:
case EP_DMA_START:
case EP_DMA_END:
case EP_WRITE_COMPLETE:
case EP_SOF:
SYS_LOG_ERR("invalid event %d in data state for EP %d",
ev->evt, ep_ctx->cfg.addr);
__ASSERT_NO_MSG(0);
break;
}
}
/* Control read/write: Handle the HW events during SETUP stage */
static inline void handle_ctrl_ep_setup_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_DATA_RECV:
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
/**
* We have received OUT data on EPOUT0 which is in USBD's local
* buffer. Grab it into endpoint's buffer with EasyDMA.
*/
nrf_usbd_ep_easydma_set(ep_ctx->cfg.addr,
(u32_t)ep_ctx->buf.data,
ep_ctx->buf.len);
/* Only one DMA operation can happen at a time */
k_sem_take(&ctx->dma_in_use, K_FOREVER);
start_epout_task(ep_ctx->cfg.addr);
/* Move to DATA stage */
ep_ctx->state = EP_DATA;
}
break;
case EP_SETUP_RECV:
case EP_DMA_START:
case EP_DMA_END:
case EP_WRITE_COMPLETE:
case EP_SOF:
SYS_LOG_ERR("invalid event %d in setup state", ev->evt);
__ASSERT_NO_MSG(0);
break;
}
}
/* Control read/write: Handle the HW events during data stage */
static inline void handle_ctrl_ep_data_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_DMA_START:
/* Nothing much to do here */
break;
case EP_DMA_END:
/**
* EasyDMA can now be used by other threads
* possibly waiting for DMA time.
*/
k_sem_give(&ctx->dma_in_use);
/**
* If this is an OUT data indication, indicate the device stack
* to read from the endpoint buffer.
*/
if (ev->misc_u.flags & BIT(EP_OUT_DATA_RCVD)) {
if (ep_ctx->buf.len < ep_ctx->cfg.max_sz) {
/**
* ZLP or short packet? Initiate STATUS stage
* TODO: Should we do this here or in read()?
*/
start_ep0status_task();
ep_ctx->state = EP_IDLE;
} else {
/* We have some more OUT data to receive */
/* TODO: What if it's exact multiple of MPS? */
start_ep0rcvout_task();
}
/* Inform the device stack */
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_OUT);
}
break;
case EP_WRITE_COMPLETE:
/**
* ZLP or short packet? Initiate STATUS stage
* TODO: Should we do this here or during write (0)?
* TODO: What if the data is actually a multiple of max_sz??
*/
if (ep_ctx->buf.len < ep_ctx->cfg.max_sz) {
start_ep0status_task();
ep_ctx->state = EP_IDLE;
}
/* Indicate write completion to the device stack */
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_IN);
break;
case EP_DATA_RECV:
/**
* We have received OUT data on EPOUT0.
* Grab it into endpoint buffer with EasyDMA.
*/
nrf_usbd_ep_easydma_set(ep_ctx->cfg.addr,
(u32_t)ep_ctx->buf.data,
ep_ctx->buf.len);
/* Only one DMA can happen at a time */
k_sem_take(&ctx->dma_in_use, K_FOREVER);
start_epout_task(ep_ctx->cfg.addr);
break;
case EP_SETUP_RECV:
case EP_SOF:
SYS_LOG_ERR("invalid event %d in data state for EP %d",
ev->evt, ep_ctx->cfg.addr);
__ASSERT_NO_MSG(0);
break;
}
}
/* Handle all control endpoint events */
static void handle_ctrl_ep_event(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ep_ctx->state) {
case EP_IDLE:
handle_ctrl_ep_idle_state_events(ev);
break;
case EP_SETUP:
handle_ctrl_ep_setup_state_events(ev);
break;
case EP_DATA:
handle_ctrl_ep_data_state_events(ev);
break;
case EP_STATUS:
/**
* TODO: HW doesn't indicate STATUS stage completion to SW.
* Do we even need STATUS state?
*/
break;
}
}
/* Data IN/OUT: Handle the HW events during IDLE state */
static inline void handle_data_ep_idle_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_DMA_START:
/**
* For IN endpoints, application has initiated a write.
* For OUT endpoints work handler must've initiated EPOUT task
* Either way, move the state to DATA.
*/
ep_ctx->state = EP_DATA;
break;
case EP_DATA_RECV:
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
u8_t addr = ep_ctx->cfg.addr;
/**
* We have some OUT BULK/INTERRUT data received
* into the USBD's local buffer. Let's grab it.
*/
nrf_usbd_ep_easydma_set(addr, (u32_t)ep_ctx->buf.data,
nrf_usbd_epout_size_get(addr));
/* Only one DMA can happen at a time */
k_sem_take(&ctx->dma_in_use, K_FOREVER);
start_epout_task(addr);
}
break;
case EP_WRITE_COMPLETE:
case EP_DMA_END:
case EP_SETUP_RECV:
case EP_SOF:
SYS_LOG_ERR("invalid event %d in idle state", ev->evt);
__ASSERT_NO_MSG(0);
break;
}
}
/* Data IN/OUT: Handle the HW events during DATA state */
static inline void handle_data_ep_data_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_DMA_END:
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
/**
* EasyDMA can now be used by other threads
* possibly waiting for DMA time.
*/
if (NRF_USBD_EPIN_CHECK(ep_ctx->cfg.addr)) {
ep_ctx->flags |= BIT(EP_WRITE_PENDING);
}
k_sem_give(&ctx->dma_in_use);
if (NRF_USBD_EPOUT_CHECK(ep_ctx->cfg.addr)) {
/**
* We've received the OUT data in the endpoint buffer
* Inform upper layer.
*/
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_OUT);
/**
* Move back to IDLE state
* TODO: Should we do this in read() instead?
*/
ep_ctx->state = EP_IDLE;
}
}
break;
case EP_WRITE_COMPLETE:
{
ep_ctx->flags &= ~BIT(EP_WRITE_PENDING);
/* Inform stack and go back to IDLE state */
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_IN);
ep_ctx->state = EP_IDLE;
}
break;
case EP_DATA_RECV:
case EP_DMA_START:
case EP_SETUP_RECV:
case EP_SOF:
SYS_LOG_ERR("invalid event %d in data state", ev->evt);
__ASSERT_NO_MSG(0);
break;
}
}
/* Handle all data (Bulk/Interrupt) endpoint events */
static void handle_data_ep_event(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ep_ctx->state) {
case EP_IDLE:
handle_data_ep_idle_state_events(ev);
break;
case EP_DATA:
handle_data_ep_data_state_events(ev);
break;
case EP_SETUP:
case EP_STATUS:
SYS_LOG_ERR("invalid state(%d) for data ep %d",
ep_ctx->state, ep_ctx->cfg.addr);
break;
}
}
/* ISO IN/OUT: Handle the HW events during IDLE state - WIP */
static inline void handle_iso_ep_idle_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_SOF:
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
u8_t addr = ep_ctx->cfg.addr;
if (NRF_USBD_EPOUT_CHECK(ep_ctx->cfg.addr)) {
/**
* TODO: We should check the ISOCRC USB flag here and
* discard the whole data after DMA if need be. USBD
* will transfer the OUT data anyway if DMA is set up.
*/
/** Is buffer available? */
if (!ep_ctx->buf.len) {
u32_t maxcnt;
maxcnt = nrf_usbd_episoout_size_get(addr);
nrf_usbd_ep_easydma_set(addr,
(u32_t)ep_ctx->buf.data,
maxcnt);
/* Only one DMA can happen at a time */
k_sem_take(&ctx->dma_in_use, K_FOREVER);
start_epout_task(addr);
}
} else {
/**
* Have anything in the ISO IN buffer to write to
* USB bus?
*/
if (ep_ctx->buf.len) {
nrf_usbd_ep_easydma_set(addr,
(u32_t)ep_ctx->buf.data,
ep_ctx->cfg.max_sz);
/**
* TODO: What if we are stuck in this work
* handler forever?
* Because, a DMA done event would be waiting
* in the FIFO forever. Should we instead give
* the sem back in ISR?
*/
k_sem_take(&ctx->dma_in_use, K_FOREVER);
start_epin_task(addr);
}
}
}
break;
case EP_DMA_START:
/**
* For IN endpoints, application has initiated a write.
* For OUT endpoints work handler must've initiated EPOUT task
* Either way, move the state to DATA.
*/
ep_ctx->state = EP_DATA;
break;
case EP_WRITE_COMPLETE:
case EP_DMA_END:
case EP_SETUP_RECV:
case EP_DATA_RECV:
SYS_LOG_ERR("invalid event %d in idle state", ev->evt);
__ASSERT_NO_MSG(0);
break;
}
}
/* ISO IN/OUT: Handle the HW events during DATA state */
static inline void handle_iso_ep_data_state_events(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
switch (ev->evt) {
case EP_DMA_END:
if (NRF_USBD_EPOUT_CHECK(ep_ctx->cfg.addr)) {
/**
* We've received ISO OUT data. Indicate upper layer.
* Usually the buffer should be consumed all at once.
*/
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_OUT);
} else {
/**
* This is not an actual write complete perse.
* An implicit write complete callback. So to speak.
*/
ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_DATA_IN);
}
/* Move back to IDLE state */
ep_ctx->state = EP_IDLE;
break;
case EP_DATA_RECV:
case EP_SOF:
case EP_DMA_START:
case EP_WRITE_COMPLETE:
case EP_SETUP_RECV:
SYS_LOG_ERR("invalid event %d in idle state", ev->evt);
__ASSERT_NO_MSG(0);
break;
}
}
/* Handle all isochronous endpoint events - WIP */
static void handle_iso_ep_event(struct ep_usb_event *ev)
{
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
/**
* TODO:
* How to differentiate an SOF between IN and OUT endpoints?
* Right now we don't have the endpoint context set in ISR.
* So, just don't handle it for now.
*/
if (!ep_ctx) {
return;
}
switch (ep_ctx->state) {
case EP_IDLE:
handle_iso_ep_idle_state_events(ev);
break;
case EP_DATA:
handle_iso_ep_data_state_events(ev);
break;
case EP_SETUP:
case EP_STATUS:
SYS_LOG_ERR("invalid state(%d) for a iso ep %d",
ep_ctx->state, ep_ctx->cfg.addr);
break;
}
}
/* Work handler */
static void usbd_work_handler(struct k_work *item)
{
struct nrf5_usbd_ctx *ctx;
struct ep_usb_event *ev;
k_sched_lock();
ctx = CONTAINER_OF(item, struct nrf5_usbd_ctx, usb_work);
if (ctx->flags) {
irq_disable(NRF5_IRQ_POWER_CLOCK_IRQn);
if (ctx->flags & BIT(NRF5_USB_STATE_CHANGE)) {
usbd_handle_state_change(ctx);
ctx->flags &= ~BIT(NRF5_USB_STATE_CHANGE);
}
if (ctx->flags & BIT(NRF5_USB_STATUS_CHANGE)) {
usbd_handle_status_change(ctx);
ctx->flags &= ~BIT(NRF5_USB_STATUS_CHANGE);
}
irq_enable(NRF5_IRQ_POWER_CLOCK_IRQn);
}
irq_disable(CONFIG_USBD_NRF5_IRQ);
while ((ev = dequeue_ep_usb_event()) != NULL) {
struct nrf5_usbd_ep_ctx *ep_ctx = ev->ep;
if (ep_ctx) {
switch (ep_ctx->cfg.type) {
case USB_DC_EP_CONTROL:
handle_ctrl_ep_event(ev);
break;
case USB_DC_EP_BULK:
case USB_DC_EP_INTERRUPT:
handle_data_ep_event(ev);
break;
case USB_DC_EP_ISOCHRONOUS:
handle_iso_ep_event(ev);
break;
}
}
free_ep_usb_event(ev);
}
irq_enable(CONFIG_USBD_NRF5_IRQ);
k_sched_unlock();
}
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 nrf5_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) {
SYS_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;
}
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) {
SYS_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;
}
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) {
SYS_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;
}
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) {
SYS_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;
}
}
static void endpoint_ctx_deinit(void)
{
struct nrf5_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 int usbd_power_int_enable(bool enable)
{
struct device *dev = device_get_binding(CONFIG_USBD_NRF5_NAME);
if (!dev) {
SYS_LOG_ERR("could not get USBD power device binding");
return -ENODEV;
}
nrf5_power_usb_power_int_enable(dev, enable);
return 0;
}
int usb_dc_attach(void)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
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);
ret = usbd_power_int_enable(true);
if (ret) {
return ret;
}
usbd_install_isr();
usbd_enable_interrupts();
ret = hf_clock_enable(true, false);
if (ret) {
goto err_clk_enable;
}
endpoint_ctx_init();
ctx->attached = true;
return ret;
err_clk_enable:
usbd_disable_interrupts();
usbd_power_int_enable(false);
return ret;
}
int usb_dc_detach(void)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
int ret;
usbd_disable_interrupts();
if (nrf_usbd_pullup_check()) {
nrf_usbd_pullup_disable();
}
nrf_usbd_disable();
ret = hf_clock_enable(false, false);
if (ret) {
return ret;
}
ret = usbd_power_int_enable(false);
if (ret) {
return ret;
}
ctx->flags = 0;
ctx->state = USBD_DETACHED;
ctx->status_code = USB_DC_UNKNOWN;
flush_ep_usb_events();
k_sem_reset(&ctx->dma_in_use);
endpoint_ctx_deinit();
ctx->attached = false;
return ret;
}
int usb_dc_reset(void)
{
if (!dev_attached() || !dev_ready()) {
return -ENODEV;
}
/* TODO: Handle midway reset */
return 0;
}
int usb_dc_set_address(const u8_t addr)
{
struct nrf5_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;
ctx->address_set = true;
return 0;
}
int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg)
{
struct nrf5_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;
return 0;
}
int usb_dc_ep_set_stall(const u8_t ep)
{
struct nrf5_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:
start_ep0stall_task();
break;
case USB_DC_EP_BULK:
case USB_DC_EP_INTERRUPT:
nrf_usbd_ep_stall(ep);
break;
case USB_DC_EP_ISOCHRONOUS:
SYS_LOG_ERR("STALL unsupported on ISO endpoints");
return -EINVAL;
}
ep_ctx->state = EP_IDLE;
ep_ctx->buf.len = 0;
ep_ctx->buf.curr = ep_ctx->buf.data;
ep_ctx->flags = 0;
return 0;
}
int usb_dc_ep_clear_stall(const u8_t ep)
{
struct nrf5_usbd_ep_ctx *ep_ctx;
if (!dev_attached() || !dev_ready()) {
return -ENODEV;
}
ep_ctx = endpoint_ctx(ep);
if (!ep_ctx) {
return -EINVAL;
}
nrf_usbd_ep_unstall(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 nrf5_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)nrf_usbd_ep_is_stall(ep);
return 0;
}
int usb_dc_ep_enable(const u8_t ep)
{
struct nrf5_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;
}
ep_ctx->cfg.en = true;
/* Defer the endpoint enable if USBD is not ready yet */
if (dev_ready()) {
nrf_usbd_ep_enable(ep);
cfg_ep_interrupt(ep, true);
if (NRF_USBD_EPOUT_CHECK(ep)) {
nrf_usbd_epout_clear(ep);
}
}
return 0;
}
int usb_dc_ep_disable(const u8_t ep)
{
struct nrf5_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;
}
cfg_ep_interrupt(ep, false);
nrf_usbd_ep_disable(ep);
ep_ctx->cfg.en = false;
return 0;
}
int usb_dc_ep_flush(const u8_t ep)
{
struct nrf5_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;
ep_ctx->state = EP_IDLE;
ep_ctx->flags = 0;
switch (ep_ctx->cfg.type) {
case USB_DC_EP_CONTROL:
nrf_usbd_epout_clear(ep);
break;
case USB_DC_EP_BULK:
case USB_DC_EP_INTERRUPT:
nrf_usbd_epout_clear(ep);
break;
default:
break;
}
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)
{
struct nrf5_usbd_ctx *ctx = get_usbd_ctx();
struct nrf5_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;
}
/**
* TODO:
* With usb_write called multiple times over before the previous
* write is complete, there are data corruptions on the bus,
* possibly because software over-writes the HW's local buffer
* and initiates the next write before or while an ongoing
* USB write on the bus.
*/
if (ep_ctx->flags & BIT(EP_WRITE_PENDING)) {
return -EAGAIN;
}
k_sem_take(&ctx->dma_in_use, K_FOREVER);
bytes_to_copy = min(data_len, ep_ctx->cfg.max_sz);
memcpy(ep_ctx->buf.data, data, bytes_to_copy);
ep_ctx->buf.len = bytes_to_copy;
if (ret_bytes) {
*ret_bytes = bytes_to_copy;
}
nrf_usbd_ep_easydma_set(ep, (u32_t)ep_ctx->buf.data, ep_ctx->buf.len);
switch (ep_ctx->cfg.type) {
case USB_DC_EP_CONTROL:
{
if (bytes_to_copy) {
start_epin_task(ep);
} else {
if (get_usbd_ctx()->address_set) {
get_usbd_ctx()->address_set = false;
/**
* TODO: For SET_ADDRESS, HW takes care of
* initiating STATUS but for some reason
* it looks like we need to start ep0status
* task. Clarify this!
*/
/* break; */
}
start_ep0status_task();
k_sem_give(&ctx->dma_in_use);
ep_ctx->state = EP_IDLE;
}
}
break;
case USB_DC_EP_BULK:
case USB_DC_EP_INTERRUPT:
/* TODO: Should we set EP_WRITE_PENDING here? */
start_epin_task(ep);
break;
case USB_DC_EP_ISOCHRONOUS:
start_epin_task(ep);
break;
default:
SYS_LOG_ERR("Invalid endpoint type");
break;
}
return 0;
}
int usb_dc_ep_read_wait(u8_t ep, u8_t *data, u32_t max_data_len,
u32_t *read_bytes)
{
struct nrf5_usbd_ep_ctx *ep_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;
}
if (!data && !max_data_len) {
if (read_bytes) {
*read_bytes = ep_ctx->buf.len;
}
return 0;
}
bytes_to_copy = min(max_data_len, ep_ctx->buf.len);
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;
}
return 0;
}
int usb_dc_ep_read_continue(u8_t ep)
{
struct nrf5_usbd_ep_ctx *ep_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->buf.len) {
ep_ctx->buf.curr = ep_ctx->buf.data;
if (ep_ctx->cfg.type == USB_DC_EP_BULK ||
ep_ctx->cfg.type == USB_DC_EP_INTERRUPT) {
nrf_usbd_epout_clear(ep);
}
}
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)
{
int ret;
ret = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes);
if (ret) {
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 nrf5_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 nrf5_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;
}