blob: 1347bd8bcb4ce018af3765b7bd0d21135886d4c4 [file] [log] [blame]
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* This code implements the DesignWare Cores USB 3.0 driver. This file only
* implements the state machine and higher level logic as described in the
* SNPS DesignWare Cores SS USB3.0 Controller databook.
* Another file (dwc_hw.c) file implements the functions to interact with
* the dwc hardware registers directly.
*/
#include <debug.h>
#include <reg.h>
#include <bits.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <arch/defines.h>
#include <platform/timer.h>
#include <platform/interrupts.h>
#include <platform/irqs.h>
#include <kernel/event.h>
#include <usb30_dwc.h>
#include <usb30_dwc_hw.h>
#include <usb30_dwc_hwio.h>
//#define DEBUG_USB
#ifdef DEBUG_USB
#define DBG(...) dprintf(ALWAYS, __VA_ARGS__)
#else
#define DBG(...)
#endif
#define ERR(...) dprintf(ALWAYS, __VA_ARGS__)
/* debug only: enum string lookup for debug purpose */
char* ss_link_state_lookup[20];
char* hs_link_state_lookup[20];
char* event_lookup_device[20];
char* event_lookup_ep[20];
char* dev_ctrl_state_lookup[20];
char* ep_state_lookup[20];
char* speed_lookup[20];
char* cmd_lookup[20];
/* debug only: initialize the enum lookup */
void dwc_debug_lookup_init(void)
{
/* EP event */
event_lookup_ep[DWC_EVENT_EP_CMD_COMPLETE] = "DWC_EVENT_EP_CMD_COMPLETE ";
event_lookup_ep[DWC_EVENT_EP_XFER_NOT_READY] = "DWC_EVENT_EP_XFER_NOT_READY ";
event_lookup_ep[DWC_EVENT_EP_XFER_IN_PROGRESS] = "DWC_EVENT_EP_XFER_IN_PROGRESS";
event_lookup_ep[DWC_EVENT_EP_XFER_COMPLETE] = "DWC_EVENT_EP_XFER_COMPLETE ";
/* Device event */
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_VENDOR_DEVICE_TEST_LMP] = "DWC_EVENT_DEVICE_EVENT_ID_VENDOR_DEVICE_TEST_LMP";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_BUFFER_OVERFLOW] = "DWC_EVENT_DEVICE_EVENT_ID_BUFFER_OVERFLOW ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_GENERIC_CMD_COMPLETE] = "DWC_EVENT_DEVICE_EVENT_ID_GENERIC_CMD_COMPLETE ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_ERRATIC_ERROR] = "DWC_EVENT_DEVICE_EVENT_ID_ERRATIC_ERROR ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_SOF] = "DWC_EVENT_DEVICE_EVENT_ID_SOF ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_SUSPEND_ENTRY] = "DWC_EVENT_DEVICE_EVENT_ID_SUSPEND_ENTRY ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_HIBER] = "DWC_EVENT_DEVICE_EVENT_ID_HIBER ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_WAKEUP] = "DWC_EVENT_DEVICE_EVENT_ID_WAKEUP ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_USB_LINK_STATUS_CHANGE] = "DWC_EVENT_DEVICE_EVENT_ID_USB_LINK_STATUS_CHANGE";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_CONNECT_DONE] = "DWC_EVENT_DEVICE_EVENT_ID_CONNECT_DONE ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_USB_RESET] = "DWC_EVENT_DEVICE_EVENT_ID_USB_RESET ";
event_lookup_device[DWC_EVENT_DEVICE_EVENT_ID_DISCONNECT] = "DWC_EVENT_DEVICE_EVENT_ID_DISCONNECT ";
/* Control state */
dev_ctrl_state_lookup[EP_FSM_INIT] = "EP_FSM_INIT ";
dev_ctrl_state_lookup[EP_FSM_SETUP] = "EP_FSM_SETUP ";
dev_ctrl_state_lookup[EP_FSM_CTRL_DATA] = "EP_FSM_CTRL_DATA ";
dev_ctrl_state_lookup[EP_FSM_WAIT_FOR_HOST_2] = "EP_FSM_WAIT_FOR_HOST_2";
dev_ctrl_state_lookup[EP_FSM_WAIT_FOR_HOST_3] = "EP_FSM_WAIT_FOR_HOST_3";
dev_ctrl_state_lookup[EP_FSM_STATUS_2] = "EP_FSM_STATUS_2 ";
dev_ctrl_state_lookup[EP_FSM_STATUS_3] = "EP_FSM_STATUS_3 ";
dev_ctrl_state_lookup[EP_FSM_STALL] = "EP_FSM_STALL ";
/* EP state */
ep_state_lookup[EP_STATE_INIT] = "EP_STATE_INIT";
ep_state_lookup[EP_STATE_INACTIVE] = "EP_STATE_INACTIVE";
ep_state_lookup[EP_STATE_START_TRANSFER] = "EP_STATE_START_TRANSFER";
ep_state_lookup[EP_STATE_XFER_IN_PROG] = "EP_STATE_XFER_IN_PROG";
/* HS link status */
hs_link_state_lookup[ON] = "ON ";
hs_link_state_lookup[L1] = "L1 ";
hs_link_state_lookup[L2] = "L2 ";
hs_link_state_lookup[DISCONNECTED] = "DISCONNECTED ";
hs_link_state_lookup[EARLY_SUSPEND] = "EARLY_SUSPEND";
hs_link_state_lookup[RESET] = "RESET ";
hs_link_state_lookup[RESUME] = "RESUME ";
/* SS link status */
ss_link_state_lookup[U0] = "U0 ";
ss_link_state_lookup[U1] = "U1 ";
ss_link_state_lookup[U2] = "U2 ";
ss_link_state_lookup[U3] = "U3 ";
ss_link_state_lookup[SS_DIS] = "SS_DIS ";
ss_link_state_lookup[RX_DET] = "RX_DET ";
ss_link_state_lookup[SS_INACT] = "SS_INACT ";
ss_link_state_lookup[POLL] = "POLL ";
ss_link_state_lookup[RECOV] = "RECOV ";
ss_link_state_lookup[HRESET] = "HRESET ";
ss_link_state_lookup[CMPLY] = "CMPLY ";
ss_link_state_lookup[LPBK] = "LPBK ";
ss_link_state_lookup[RESUME_RESET] = "RESUME_RESET";
/* connection speed */
speed_lookup[DSTS_CONNECTSPD_HS] = "DSTS_CONNECTSPD_HS ";
speed_lookup[DSTS_CONNECTSPD_FS1] = "DSTS_CONNECTSPD_FS1";
speed_lookup[DSTS_CONNECTSPD_LS] = "DSTS_CONNECTSPD_LS ";
speed_lookup[DSTS_CONNECTSPD_FS2] = "DSTS_CONNECTSPD_FS1";
speed_lookup[DSTS_CONNECTSPD_SS] = "DSTS_CONNECTSPD_SS ";
/* dwc command */
cmd_lookup[DEPCMD_CMD_SET_EP_CONF] = "DEPCMD_CMD_SET_EP_CONF ";
cmd_lookup[DEPCMD_CMD_SET_TR_CONF] = "DEPCMD_CMD_SET_TR_CONF ";
cmd_lookup[DEPCMD_CMD_GET_EP_STATE] = "DEPCMD_CMD_GET_EP_STATE ";
cmd_lookup[DEPCMD_CMD_SET_STALL] = "DEPCMD_CMD_SET_STALL ";
cmd_lookup[DEPCMD_CMD_CLEAR_STALL] = "DEPCMD_CMD_CLEAR_STALL ";
cmd_lookup[DEPCMD_CMD_START_TRANSFER] = "DEPCMD_CMD_START_TRANSFER ";
cmd_lookup[DEPCMD_CMD_UPDATE_TRANSFER] = "DEPCMD_CMD_UPDATE_TRANSFER";
cmd_lookup[DEPCMD_CMD_END_TRANSFER] = "DEPCMD_CMD_END_TRANSFER ";
cmd_lookup[DEPCMD_CMD_START_NEW_CONF] = "DEPCMD_CMD_START_NEW_CONF ";
}
/******************************** DWC global **********************************/
/* Initialize DWC driver. */
dwc_dev_t* dwc_init(dwc_config_t *config)
{
dwc_dev_t *dev = (dwc_dev_t*) malloc(sizeof(dwc_dev_t));
ASSERT(dev);
memset(dev, 0, sizeof(dev));
/* save config info */
dev->base = config->base;
dev->event_buf.buf = config->event_buf;
dev->event_buf.buf_size = config->event_buf_size;
dev->event_buf.index = 0;
dev->event_buf.max_index = (config->event_buf_size)/4 - 1; /* (max num of 4 byte events) - 1 */
dev->notify_context = config->notify_context;
dev->notify = config->notify;
/* allocate buffer for receiving setup packet */
dev->setup_pkt = memalign(CACHE_LINE, ROUNDUP(DWC_SETUP_PKT_LEN, CACHE_LINE));
ASSERT(dev->setup_pkt);
/* callback function to handler setup packet */
dev->setup_context = config->setup_context;
dev->setup_handler = config->setup_handler;
/* read core version from h/w */
dev->core_id = dwc_coreid(dev);
/* register for interrupt */
register_int_handler(USB30_EE1_IRQ, dwc_irq_handler_ee1, dev);
#ifdef DEBUG_USB
/* note: only for debug */
dwc_debug_lookup_init();
#endif
return dev;
}
/* interrupt handler */
static enum handler_return dwc_irq_handler_ee1(void* context)
{
dwc_dev_t *dev;
uint16_t event_size; /* number of bytes used by the event */
uint32_t event[3] = {0x0, 0x0, 0x0};
/* get the device on which this interrupt occurred */
dev = (dwc_dev_t *) context;
/* while there are events to be processed */
while((event_size = dwc_event_get_next(dev, event)))
{
/* device event? */
if(DWC_EVENT_IS_DEVICE_EVENT(*event))
{
/* handle device events */
dwc_event_handler_device(dev, event);
}
else
{
/* endpoint event */
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
if ((ep_phy_num == 0) ||
(ep_phy_num == 1))
{
/* handle control ep event */
dwc_event_handler_ep_ctrl(dev, event);
}
else
{
/* handle non-control ep event. only bulk ep is supported.*/
dwc_event_handler_ep_bulk(dev, event);
}
}
/* update number of bytes processed */
dwc_event_processed(dev, event_size);
}
return INT_NO_RESCHEDULE;
}
/*====================== DWC Event configuration/handling functions========== */
/* handles all device specific events */
void dwc_event_handler_device(dwc_dev_t *dev, uint32_t *event)
{
dwc_event_device_event_id_t event_id = DWC_EVENT_DEVICE_EVENT_ID(*event);
DBG("\nDEVICE_EVENT: %s in %s \n", event_lookup_device[event_id],
dev_ctrl_state_lookup[dev->ctrl_state]);
switch (event_id)
{
case DWC_EVENT_DEVICE_EVENT_ID_VENDOR_DEVICE_TEST_LMP:
case DWC_EVENT_DEVICE_EVENT_ID_BUFFER_OVERFLOW:
case DWC_EVENT_DEVICE_EVENT_ID_GENERIC_CMD_COMPLETE:
case DWC_EVENT_DEVICE_EVENT_ID_ERRATIC_ERROR:
case DWC_EVENT_DEVICE_EVENT_ID_SOF:
case DWC_EVENT_DEVICE_EVENT_ID_SUSPEND_ENTRY:
case DWC_EVENT_DEVICE_EVENT_ID_HIBER:
case DWC_EVENT_DEVICE_EVENT_ID_WAKEUP:
break;
case DWC_EVENT_DEVICE_EVENT_ID_USB_LINK_STATUS_CHANGE:
{
dwc_event_device_link_status_change(dev, event);
}
break;
case DWC_EVENT_DEVICE_EVENT_ID_CONNECT_DONE:
{
dwc_event_device_connect_done(dev);
}
break;
case DWC_EVENT_DEVICE_EVENT_ID_USB_RESET:
{
dwc_event_device_reset(dev);
}
break;
case DWC_EVENT_DEVICE_EVENT_ID_DISCONNECT:
{
dwc_event_device_disconnect(dev);
}
break;
default:
ASSERT(0);
}
}
/* handle link status change event: does nothing for now.
* only for debug purpose.
*/
static void dwc_event_device_link_status_change(dwc_dev_t *dev, uint32_t *event)
{
#ifdef DEBUG_USB
uint8_t event_info = DWC_EVENT_DEVICE_EVENT_INFO(*event);
uint8_t ss_event = DWC_EVENT_DEVICE_EVENT_INFO_SS_EVENT(*event);
uint8_t link_state = DWC_EVENT_DEVICE_EVENT_INFO_LINK_STATE(event_info);
if(ss_event)
{
DBG("\n SS link state = %s (%d)\n", ss_link_state_lookup[link_state], link_state);
}
else
{
DBG("\n HS link state = %s (%d)\n", hs_link_state_lookup[link_state], link_state);
}
#endif
}
/* handle disconnect event */
static void dwc_event_device_disconnect(dwc_dev_t *dev)
{
/* inform client that device is disconnected */
if (dev->notify)
dev->notify(dev->notify_context, DWC_NOTIFY_EVENT_DISCONNECTED);
}
/* handle connect event: snps 8.1.3 */
static void dwc_event_device_connect_done(dwc_dev_t *dev)
{
uint8_t speed;
uint16_t max_pkt_size = 0;
dwc_notify_event_t dwc_event = DWC_NOTIFY_EVENT_DISCONNECTED;
/* get connection speed */
speed = dwc_connectspeed(dev);
switch (speed)
{
case DSTS_CONNECTSPD_SS:
{
max_pkt_size = 512;
dwc_event = DWC_NOTIFY_EVENT_CONNECTED_SS;
}
break;
case DSTS_CONNECTSPD_HS:
{
max_pkt_size = 64;
dwc_event = DWC_NOTIFY_EVENT_CONNECTED_HS;
}
break;
case DSTS_CONNECTSPD_FS1:
case DSTS_CONNECTSPD_FS2:
{
max_pkt_size = 64;
dwc_event = DWC_NOTIFY_EVENT_CONNECTED_FS;
}
break;
case DSTS_CONNECTSPD_LS:
{
max_pkt_size = 8;
dwc_event = DWC_NOTIFY_EVENT_CONNECTED_LS;
}
break;
default:
ASSERT(0);
}
DBG("\nspeed = %d : %s max_pkt_size %d \n", speed,
speed_lookup[speed],
max_pkt_size);
/* save max pkt size for control endpoints */
dev->ep[0].max_pkt_size = max_pkt_size;
dev->ep[1].max_pkt_size = max_pkt_size;
/* Issue a DEPCFG command (with Config Action set to "Modify") for
* physical endpoints 0 & 1 using the same endpoint characteristics from
* Power-On Reset, but set
* MaxPacketSize to 512 (SuperSpeed), 64 (High-Speed),
* 8/16/32/64 (Full-Speed), or 8 (Low-Speed).
*/
dwc_ep_cmd_set_config(dev, 0, SET_CONFIG_ACTION_MODIFY);
dwc_ep_cmd_set_config(dev, 1, SET_CONFIG_ACTION_MODIFY);
/* TODO: future optimization:
* GUSB2CFG/GUSB3PIPECTL
* Depending on the connected speed, write to the other PHY's control
* register to suspend it.
* GTXFIFOSIZn (optional) Based on the new MaxPacketSize of IN endpoint 0,
* software may choose to re-allocate
* the TX FIFO sizes by writing to these registers.
*/
/* inform client that device is connected */
if (dev->notify)
dev->notify(dev->notify_context, dwc_event);
}
/* handle usb reset event:
* snps 8.1.2:
* Set DevAddr to 0
* end transfer for any active transfers (except for the default control EP)
*/
void dwc_event_device_reset(dwc_dev_t *dev)
{
/* set dev address to 0 */
dwc_device_set_addr(dev, 0x0);
/* Send "stop transfer" on any non-control ep
* which has a transfer in progress: snps 8.2.5
*/
for (uint8_t ep_index = 2; ep_index < DWC_MAX_NUM_OF_EP; ep_index++)
{
dwc_ep_t *ep = &dev->ep[ep_index];
DBG("\n RESET on EP = %d while state = %s", ep_index,
ep_state_lookup[ep->state]);
if ((ep->state == EP_STATE_START_TRANSFER) ||
(ep->state == EP_STATE_XFER_IN_PROG))
{
DBG("\n NEED to do end transfer");
dwc_ep_cmd_end_transfer(dev, ep->phy_num);
}
}
/* inform client that device is offline */
if (dev->notify)
{
DBG("\n calling Notify for OFFLINE event.\n");
dev->notify(dev->notify_context, DWC_NOTIFY_EVENT_OFFLINE);
}
}
/* handle control endpoint specific events:
* implements the control transfer programming model as described
* in snps chapter 8.4, figure 8-2.
*/
void dwc_event_handler_ep_ctrl(dwc_dev_t *dev, uint32_t *event)
{
#ifdef DEBUG_USB
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
uint16_t event_param = DWC_EVENT_EP_EVENT_PARAM(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
#endif
DBG("\n\n\n++EP_EVENT: %s in ctrl_state: %s ep_state[%d]: %s",
event_lookup_ep[event_id],
dev_ctrl_state_lookup[dev->ctrl_state],
ep_phy_num,
ep_state_lookup[ep->state]);
DBG("\n ep_phy_num = %d param = 0x%x status = 0x%x", ep_phy_num,
event_param,
event_status);
/* call the handler for the current control state */
switch (dev->ctrl_state)
{
case EP_FSM_SETUP:
{
dwc_event_handler_ep_ctrl_state_setup(dev, event);
}
break;
case EP_FSM_CTRL_DATA:
{
dwc_event_handler_ep_ctrl_state_data(dev, event);
}
break;
case EP_FSM_WAIT_FOR_HOST_2:
{
dwc_event_handler_ep_ctrl_state_wait_for_host_2(dev, event);
}
break;
case EP_FSM_WAIT_FOR_HOST_3:
{
dwc_event_handler_ep_ctrl_state_wait_for_host_3(dev, event);
}
break;
case EP_FSM_STATUS_2:
{
dwc_event_handler_ep_ctrl_state_status_2(dev, event);
}
break;
case EP_FSM_STATUS_3:
{
dwc_event_handler_ep_ctrl_state_status_3(dev, event);
}
break;
case EP_FSM_STALL:
{
dwc_event_handler_ep_ctrl_state_stall(dev, event);
}
break;
default:
ASSERT(0);
}
DBG("\n--EP_EVENT: %s in ctrl_state: %s ep_state[%d]: %s",
event_lookup_ep[event_id],
dev_ctrl_state_lookup[dev->ctrl_state],
ep_phy_num,
ep_state_lookup[ep->state]);
}
/* check status of transfer:
* returns TRB status: non-zero value indicates failure to complete transfer.
* Also updates the "bytes_in_buf". This field indicates the number of bytes
* still remaining to be transferred. This field will be zero when all the
* requested data is transferred.
*/
uint8_t dwc_event_check_trb_status(dwc_dev_t *dev,
uint32_t *event,
uint8_t index,
uint32_t *bytes_in_buf)
{
uint8_t status = 0;
uint8_t trb_updated = 0;
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
dwc_ep_t *ep = &dev->ep[index];
dwc_trb_t *trb = ep->trb;
uint32_t num_of_trb = ep->trb_queued;
uint32_t bytes_remaining = 0;
/* sanity ck. */
ASSERT(num_of_trb);
/* invalidate trb data before reading */
arch_invalidate_cache_range((addr_t) trb, sizeof(dwc_trb_t)*num_of_trb);
while (num_of_trb)
{
bytes_remaining += REG_READ_FIELD_LOCAL(&trb->f3, TRB_F3, BUFSIZ);
/* point to next trb */
trb++;
/* decrement trb count */
num_of_trb--;
/* The first non-zero status indicates the transfer status. Update
* "status" only once but still go through all the TRBs to find out
* the bytes still remaining to be transferred.
*/
if (!status)
{
status = REG_READ_FIELD_LOCAL(&trb->f3, TRB_F3, TRBSTS);
}
if ((event_status & DWC_XFER_COMPLETE_EVT_STATUS_SHORT_PKT) &&
(REG_READ_FIELD_LOCAL(&trb->f4, TRB_F4, HWO)))
{
/* This TRB needs to be reclaimed since transfer completed due to
* reception of a short pkt.
* "fast-forward" condition as described in snps 8.2.3.2.
*/
DBG("\n TRB needs to be reclaimed by sw. trb = 0x%x\n", (uint32_t) trb);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, HWO, 0x0);
trb_updated = 1;
}
}
/* flush out any updates to trb before continuing */
if (trb_updated)
{
arch_clean_invalidate_cache_range((addr_t) ep->trb,
sizeof(dwc_trb_t)*ep->trb_queued);
}
/* reset the EP's queued trb count */
ep->trb_queued = 0;
*bytes_in_buf = bytes_remaining;
DBG("\n trb_status: %d total buf size = 0x%x \n", status, *bytes_in_buf);
return status;
}
/* handle all events occurring in Control-Setup state */
static void dwc_event_handler_ep_ctrl_state_setup(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_START_TRANSFER)
{
ASSERT(ep->state == EP_STATE_START_TRANSFER);
ASSERT(event_status == 0);
/* save the resource id assigned to this ep. */
ep->state = EP_STATE_XFER_IN_PROG;
ep->resource_idx = DWC_EVENT_EP_EVENT_XFER_RES_IDX(*event);
}
else
{
DBG("\n cmd = %s has no action. ignored.", cmd_lookup[cmd]);
}
}
break;
case DWC_EVENT_EP_XFER_NOT_READY:
{
/* attempting to start data/status before setup. snps 8.4.2 #2 */
DBG("\nattempting to start data/status before setup. stalling..\n");
dwc_ep_cmd_stall(dev, ep_phy_num);
/* new state is stall */
dev->ctrl_state = EP_FSM_STALL;
}
break;
case DWC_EVENT_EP_XFER_COMPLETE:
{
uint32_t bytes_remaining = 0;
uint8_t status = 0;
/* cannot happen on any other ep */
ASSERT(ep_phy_num == 0);
/* Assert if ep state is not xfer_in_prog. fatal error. */
ASSERT(ep->state == EP_STATE_XFER_IN_PROG);
/* update ep state to inactive. */
ep->state = EP_STATE_INACTIVE;
/* check transfer status. */
status = dwc_event_check_trb_status(dev,
event,
DWC_EP_PHY_TO_INDEX(ep_phy_num),
&bytes_remaining);
if (status || bytes_remaining)
{
/* transfer failed. queue another transfer. */
dwc_ep_ctrl_state_setup_enter(dev);
}
else
{
int ret;
uint8_t *data = dev->setup_pkt; /* setup pkt data */
/* invalidate any cached setup data before reading */
arch_invalidate_cache_range((addr_t) data, DWC_SETUP_PKT_LEN);
/* call setup handler */
ret = dev->setup_handler(dev->setup_context, data);
if (ret == DWC_SETUP_2_STAGE)
{
/* this is a 2 stage setup. */
dev->ctrl_state = EP_FSM_WAIT_FOR_HOST_2;
}
else if (ret == DWC_SETUP_3_STAGE)
{
/* this is a 3 stage setup. */
dev->ctrl_state = EP_FSM_CTRL_DATA;
}
else
{
/* bad setup bytes. stall */
dwc_ep_cmd_stall(dev, ep_phy_num);
/* new state is stall */
dev->ctrl_state = EP_FSM_STALL;
}
}
}
break;
case DWC_EVENT_EP_XFER_IN_PROGRESS:
default:
/* event is not expected in this state */
ERR("\n unhandled event_id = %d \n", event_id);
ASSERT(0);
}
}
/* handle all events occurring in Control-Data state */
static void dwc_event_handler_ep_ctrl_state_data(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_ctrl_stage = DWC_EVENT_EP_EVENT_CTRL_STAGE(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_START_TRANSFER)
{
ASSERT(ep->state == EP_STATE_START_TRANSFER);
ASSERT(event_status == 0);
/* save the resource id assigned to this ep. */
ep->state = EP_STATE_XFER_IN_PROG;
ep->resource_idx = DWC_EVENT_EP_EVENT_XFER_RES_IDX(*event);
}
else
{
DBG("\n cmd = %s has no action. ignored.", cmd_lookup[cmd]);
}
}
break;
case DWC_EVENT_EP_XFER_NOT_READY:
{
/* confirm that this is control data request.
* control_status_request is invalid event in this state.
* assert if it ever occurs.
* something must be wrong in fsm implementation.
*/
ASSERT(event_ctrl_stage == CONTROL_DATA_REQUEST);
/* In this state, the ep must be in transfer state.
* otherwise this came on an ep that we are not expecting any data.
*/
if((ep->state == EP_STATE_START_TRANSFER) ||
(ep->state == EP_STATE_XFER_IN_PROG))
{
DBG("\n Host requested data on ep_phy_num = %d."
"Transfer already started. No action....", ep_phy_num);
}
else
{
/* host attempting to move data in wrong direction.
* end transfer for the direction that we started and stall.
*/
uint8_t end_ep_phy_num;
/* end the other ep */
end_ep_phy_num = (ep_phy_num == 0) ? 1 : 0;
DBG("\nAttempting to move data in wrong direction. stalling. ");
dwc_ep_cmd_end_transfer(dev, end_ep_phy_num);
/* stall */
dwc_ep_cmd_stall(dev, end_ep_phy_num);
/* move to stall state. */
dev->ctrl_state = EP_FSM_STALL;
}
}
break;
case DWC_EVENT_EP_XFER_COMPLETE:
{
uint32_t bytes_remaining;
uint8_t status;
/* should never happen in any other state.
* something wrong in fsm implementation.
*/
ASSERT(ep->state == EP_STATE_XFER_IN_PROG);
/* transfer is complete */
ep->state = EP_STATE_INACTIVE;
/* check transfer status */
status = dwc_event_check_trb_status(dev,
event,
DWC_EP_PHY_TO_INDEX(ep_phy_num),
&bytes_remaining);
if (status || bytes_remaining)
{
DBG("\n\n ********DATA TRANSFER FAILED ************* "
"status = %d bytes_remaining = %d\n\n",
status, bytes_remaining);
}
/* wait for host to request status */
dev->ctrl_state = EP_FSM_WAIT_FOR_HOST_3;
}
break;
case DWC_EVENT_EP_XFER_IN_PROGRESS:
default:
/* event is not expected in this state */
ASSERT(0);
}
}
/* handle all events occurring in Wait-for-Host-2 state */
static void dwc_event_handler_ep_ctrl_state_wait_for_host_2(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_ctrl_stage = DWC_EVENT_EP_EVENT_CTRL_STAGE(*event);
switch (event_id)
{
case DWC_EVENT_EP_XFER_NOT_READY:
{
if (event_ctrl_stage == CONTROL_DATA_REQUEST)
{
DBG("\n\n attempting to start data when setup did not indicate"
"data stage. stall...\n\n");
dwc_ep_cmd_stall(dev, ep_phy_num);
/* move to stall state. */
dev->ctrl_state = EP_FSM_STALL;
}
else if (event_ctrl_stage == CONTROL_STATUS_REQUEST)
{
/* status cannot happen on phy = 0 */
ASSERT(ep_phy_num == 1);
dwc_request_t req;
req.callback = 0x0;
req.context = 0x0;
req.data = 0x0;
req.len = 0x0;
req.trbctl = TRBCTL_CONTROL_STATUS_2;
dwc_request_queue(dev, ep_phy_num, &req);
dev->ctrl_state = EP_FSM_STATUS_2;
}
else
{
ASSERT(0);
}
}
break;
case DWC_EVENT_EP_XFER_IN_PROGRESS:
case DWC_EVENT_EP_XFER_COMPLETE:
default:
/* event not expected in this state. */
ASSERT(0);
}
}
/* handle all events occurring in Wait-for-Host-3 state */
static void dwc_event_handler_ep_ctrl_state_wait_for_host_3(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_ctrl_stage = DWC_EVENT_EP_EVENT_CTRL_STAGE(*event);
switch (event_id)
{
case DWC_EVENT_EP_XFER_NOT_READY:
{
if (event_ctrl_stage == CONTROL_DATA_REQUEST)/* data request */
{
/* TODO:
* special case handling when data stage transfer length
* was exact multiple of max_pkt_size.
* Need to setup a TRB to complete data stage with a zero
* length pkt transfer.
* Not implemented currently since all data during enumeration
* is less then max_pkt_size.
*/
ASSERT(0);
}
else if (event_ctrl_stage == CONTROL_STATUS_REQUEST)/* stat req */
{
dwc_request_t req;
req.callback = 0x0;
req.context = 0x0;
req.data = 0x0;
req.len = 0x0;
req.trbctl = TRBCTL_CONTROL_STATUS_3;
dwc_request_queue(dev, ep_phy_num, &req);
dev->ctrl_state = EP_FSM_STATUS_3;
}
else
{
ASSERT(0);
}
}
break;
case DWC_EVENT_EP_XFER_IN_PROGRESS:
case DWC_EVENT_EP_XFER_COMPLETE:
default:
/* event is not expected in this state */
ASSERT(0);
}
}
/* handle all events occurring in Status-2 state */
static void dwc_event_handler_ep_ctrl_state_status_2(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_START_TRANSFER)
{
ASSERT(ep->state == EP_STATE_START_TRANSFER);
ASSERT(event_status == 0);
/* save the resource id assigned to this ep. */
ep->state = EP_STATE_XFER_IN_PROG;
ep->resource_idx = DWC_EVENT_EP_EVENT_XFER_RES_IDX(*event);
}
else
{
DBG("\n cmd = %s has no action. ignored.", cmd_lookup[cmd]);
}
}
break;
case DWC_EVENT_EP_XFER_COMPLETE:
{
uint32_t bytes_remaining;
uint8_t status;
/* cannot happen on ep 0 */
ASSERT(ep_phy_num == 1);
/* should never happen in any other state.
* something wrong in fsm implementation.
*/
ASSERT(ep->state == EP_STATE_XFER_IN_PROG);
ep->state = EP_STATE_INACTIVE;
/* check transfer status */
status = dwc_event_check_trb_status(dev,
event,
DWC_EP_PHY_TO_INDEX(ep_phy_num),
&bytes_remaining);
if (status || bytes_remaining)
{
DBG("\n\n ******** TRANSFER FAILED ************* status ="
" %d bytes_remaining = %d\n\n", status, bytes_remaining);
}
dwc_ep_ctrl_state_setup_enter(dev);
}
break;
case DWC_EVENT_EP_XFER_NOT_READY:
case DWC_EVENT_EP_XFER_IN_PROGRESS:
default:
/* event is not expected in this state */
ASSERT(0);
}
}
/* handle all events occurring in Status-3 state */
static void dwc_event_handler_ep_ctrl_state_status_3(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_START_TRANSFER)
{
/* something wrong with fsm. cannot happen in any other ep state.*/
ASSERT(ep->state == EP_STATE_START_TRANSFER);
ASSERT(event_status == 0);
/* save the resource id assigned to this ep. */
ep->state = EP_STATE_XFER_IN_PROG;
ep->resource_idx = DWC_EVENT_EP_EVENT_XFER_RES_IDX(*event);
}
else
{
DBG("\n cmd = %s has no action. ignored.", cmd_lookup[cmd]);
}
}
break;
case DWC_EVENT_EP_XFER_COMPLETE:
{
uint32_t bytes_remaining;
uint8_t status;
/* should never happen in any other state.
* something wrong in fsm implementation.
*/
ASSERT(ep->state == EP_STATE_XFER_IN_PROG);
ep->state = EP_STATE_INACTIVE;
/* check transfer status */
status = dwc_event_check_trb_status(dev,
event,
DWC_EP_PHY_TO_INDEX(ep_phy_num),
&bytes_remaining);
if (status || bytes_remaining)
{
DBG("\n\n ******** TRANSFER FAILED ************* status ="
" %d bytes_remaining = %d\n\n", status, bytes_remaining);
/* data stage failed. */
dwc_ep_cmd_stall(dev, ep_phy_num);
/* move to stall state. */
dev->ctrl_state = EP_FSM_STALL;
}
else
{
dwc_ep_ctrl_state_setup_enter(dev);
}
}
break;
case DWC_EVENT_EP_XFER_NOT_READY:
case DWC_EVENT_EP_XFER_IN_PROGRESS:
default:
/* event is not expected in this state */
ASSERT(0);
}
}
/* handle all events occurring in stall state */
static void dwc_event_handler_ep_ctrl_state_stall(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_SET_STALL)
{
/* stall complete. go back to setup state. */
dwc_ep_ctrl_state_setup_enter(dev);
}
else if (cmd == DEPCMD_CMD_END_TRANSFER)
{
/* reset state and resource index */
ep->state = EP_STATE_INACTIVE;
ep->resource_idx = 0;
}
else
{
DBG("\n cmd = %s has no action. ignored.", cmd_lookup[cmd]);
}
}
break;
default:
DBG("\n\n ********No Action defined for this event. ignored. \n\n");
break;
}
}
/* event handler for INACTIVE state of bulk endpoint */
static void dwc_event_handler_ep_bulk_state_inactive(dwc_dev_t *dev,
uint32_t *event)
{
#ifdef DEBUG_USB
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
#endif
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
DBG("\n cmd = %s has no action. ignored.", cmd_lookup[cmd]);
}
break;
case DWC_EVENT_EP_XFER_NOT_READY:
{
/* This is a valid scenario where host is requesting data and
* our client has not queued the request yet.
*/
DBG("\n Host requested data on ep_phy_num = %d. "
"No action. ignored.", ep_phy_num);
}
break;
case DWC_EVENT_EP_XFER_IN_PROGRESS:
case DWC_EVENT_EP_XFER_COMPLETE:
default:
/* event is not expected in this state */
ASSERT(0);
}
}
/* event handler for START_TRANSFER state of bulk endpoint */
static void dwc_event_handler_ep_bulk_state_start_transfer(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_START_TRANSFER)
{
if (event_status == 0)
{
/* save the resource id assigned to this ep. */
ep->state = EP_STATE_XFER_IN_PROG;
ep->resource_idx = DWC_EVENT_EP_EVENT_XFER_RES_IDX(*event);
}
else
{
/* start transfer failed. inform client */
if (ep->req.callback)
{
ep->req.callback(ep->req.context, 0, -1);
}
/* back to inactive state */
dwc_ep_bulk_state_inactive_enter(dev, ep_phy_num);
}
}
else
{
DBG("\n cmd = %s has no action. ignored.\n", cmd_lookup[cmd]);
}
}
break;
default:
ASSERT(0);
}
}
/* event handler for TRANSFER_IN_PROGRESS state of bulk endpoint */
static void dwc_event_handler_ep_bulk_state_xfer_in_prog(dwc_dev_t *dev,
uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
switch (event_id)
{
case DWC_EVENT_EP_CMD_COMPLETE:
{
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
if (cmd == DEPCMD_CMD_END_TRANSFER)
{
/* transfer was cancelled for some reason. */
DBG("\n transfer was cancelled on ep_phy_num = %d\n", ep_phy_num);
/* inform client that transfer failed. */
if (ep->req.callback)
{
ep->req.callback(ep->req.context, 0, -1);
}
/* back to inactive state */
dwc_ep_bulk_state_inactive_enter(dev, ep_phy_num);
}
else
{
DBG("\n cmd = %s has no action. ignored.\n", cmd_lookup[cmd]);
}
}
break;
case DWC_EVENT_EP_XFER_NOT_READY:
{
/* This is a valid scenario where host is requesting data and
* we have not yet moved to start transfer state.
*/
DBG("\n Host requested data on ep_phy_num = %d."
"No action. ignored.", ep_phy_num);
}
break;
case DWC_EVENT_EP_XFER_COMPLETE:
{
uint32_t bytes_remaining;
uint8_t status;
/* Check how many TRBs were processed and how much data got
* transferred. If there are bytes_remaining, it does not
* necessarily mean failed xfer. We could have queued a 512 byte
* read and receive say 13 bytes of data which is a valid scenario.
*/
status = dwc_event_check_trb_status(dev,
event,
DWC_EP_PHY_TO_INDEX(ep_phy_num),
&bytes_remaining);
DBG("\n\n ******DATA TRANSFER COMPLETED (ep_phy_num = %d) ********"
"bytes_remaining = %d\n\n", ep_phy_num, bytes_remaining);
if (ep->req.callback)
{
ep->req.callback(ep->req.context,
ep->bytes_queued - bytes_remaining,
status ? -1 : 0);
}
dwc_ep_bulk_state_inactive_enter(dev, ep_phy_num);
}
break;
default:
ASSERT(0);
}
}
/* bulk endpoint event handler */
void dwc_event_handler_ep_bulk(dwc_dev_t *dev, uint32_t *event)
{
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
#ifdef DEBUG_USB
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
uint16_t event_param = DWC_EVENT_EP_EVENT_PARAM(*event);
#endif
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
DBG("\n\n\n++EP_EVENT: %s in ctrl_state: %s ep_state[%d]: %s",
event_lookup_ep[event_id],
dev_ctrl_state_lookup[dev->ctrl_state],
ep_phy_num,
ep_state_lookup[ep->state]);
DBG("\n ep_phy_num = %d param = 0x%x status = 0x%x",
ep_phy_num, event_param, event_status);
switch (ep->state)
{
case EP_STATE_INACTIVE:
{
dwc_event_handler_ep_bulk_state_inactive(dev, event);
}
break;
case EP_STATE_START_TRANSFER:
{
dwc_event_handler_ep_bulk_state_start_transfer(dev, event);
}
break;
case EP_STATE_XFER_IN_PROG:
{
dwc_event_handler_ep_bulk_state_xfer_in_prog(dev, event);
}
break;
default:
DBG("\n EP state is invalid. Asserting...\n");
ASSERT(0);
}
DBG("\n--EP_EVENT: %s in ctrl_state: %s ep_state[%d]: %s",
event_lookup_ep[event_id],
dev_ctrl_state_lookup[dev->ctrl_state],
ep_phy_num,
ep_state_lookup[ep->state]);
}
/******************** Endpoint related APIs **********************************/
/* Initialize and enable EP:
* - set the initial configuration for an endpoint
* - set transfer resources
* - enable the endpoint
*/
static void dwc_ep_config_init_enable(dwc_dev_t *dev, uint8_t index)
{
uint8_t ep_phy_num = dev->ep[index].phy_num;
dwc_ep_cmd_set_config(dev, index, SET_CONFIG_ACTION_INIT);
dev->ep[index].state = EP_STATE_INACTIVE;
/* Set transfer resource configs for the end points */
dwc_ep_cmd_set_transfer_resource(dev, ep_phy_num);
/* enable endpoint */
dwc_ep_enable(dev, ep_phy_num);
}
/* Initialize control EPs:
* Do the one time initialization of control EPs
*/
static void dwc_ep_ctrl_init(dwc_dev_t *dev)
{
uint8_t index;
/* Control OUT */
index = DWC_EP_INDEX(0, DWC_EP_DIRECTION_OUT);
dev->ep[index].number = 0;
dev->ep[index].dir = DWC_EP_DIRECTION_OUT;
dev->ep[index].phy_num = 0;
dev->ep[index].type = EP_TYPE_CONTROL;
dev->ep[index].state = EP_STATE_INIT;
dev->ep[index].max_pkt_size = 512;
dev->ep[index].burst_size = 0;
dev->ep[index].tx_fifo_num = 0;
dev->ep[index].zlp = 0;
dev->ep[index].trb_count = 1;
/* TRB must be aligned to 16 */
dev->ep[index].trb = memalign(lcm(CACHE_LINE, 16),
ROUNDUP(dev->ep[index].trb_count*sizeof(dwc_trb_t), CACHE_LINE));
ASSERT(dev->ep[index].trb);
dev->ep[index].trb_queued = 0;
dev->ep[index].bytes_queued = 0;
/* Control IN */
index = DWC_EP_INDEX(0, DWC_EP_DIRECTION_OUT);
dev->ep[index].number = 0;
dev->ep[index].dir = DWC_EP_DIRECTION_IN;
dev->ep[index].phy_num = 1;
dev->ep[index].type = EP_TYPE_CONTROL;
dev->ep[index].state = EP_STATE_INIT;
dev->ep[index].max_pkt_size = 512;
dev->ep[index].burst_size = 0;
dev->ep[index].tx_fifo_num = 0;
dev->ep[index].zlp = 0;
dev->ep[index].trb_count = 1;
/* TRB must be aligned to 16 */
dev->ep[index].trb = memalign(lcm(CACHE_LINE, 16),
ROUNDUP(dev->ep[index].trb_count*sizeof(dwc_trb_t), CACHE_LINE));
ASSERT(dev->ep[index].trb);
dev->ep[index].trb_queued = 0;
dev->ep[index].bytes_queued = 0;
/* configure and enable the endpoints */
dwc_ep_config_init_enable(dev, 0);
dwc_ep_config_init_enable(dev, 1);
}
/* entry function into setup state for control fsm */
static void dwc_ep_ctrl_state_setup_enter(dwc_dev_t *dev)
{
dwc_request_t req;
/* queue request to receive the first setup pkt from host */
memset(dev->setup_pkt, 0, DWC_SETUP_PKT_LEN);
/* flush data */
arch_clean_invalidate_cache_range((addr_t) dev->setup_pkt, DWC_SETUP_PKT_LEN);
req.data = dev->setup_pkt;
req.len = DWC_SETUP_PKT_LEN;
req.trbctl = TRBCTL_CONTROL_SETUP;
req.callback = NULL;
req.context = NULL;
dwc_request_queue(dev, 0, &req);
/* reset control ep state to "setup" state */
dev->ctrl_state = EP_FSM_SETUP;
}
/* entry function into inactive state for data transfer fsm */
static void dwc_ep_bulk_state_inactive_enter(dwc_dev_t *dev, uint8_t ep_phy_num)
{
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
/* queue request to receive the first setup pkt from host */
ep->req.data = NULL;
ep->req.len = 0;
ep->req.trbctl = 0;
ep->req.callback = NULL;
ep->req.context = NULL;
/* inactive state */
ep->state = EP_STATE_INACTIVE;
/* reset the resource index, trb and bytes queued */
ep->resource_idx = 0;
ep->trb_queued = 0;
ep->bytes_queued = 0;
}
/*************************** External APIs ************************************/
/* Initialize controller for device mode operation.
* Implements sequence as described in HPG.
* Numbers indicate the step # in HPG.
*/
void dwc_device_init(dwc_dev_t *dev)
{
/* 15. reset device ctrl. */
dwc_device_reset(dev);
/* 16. initialize global control reg for device mode operation */
dwc_gctl_init(dev);
/* 17. AXI master config */
dwc_axi_master_config(dev);
/* 18. */
/* a. tx fifo config */
/* reset value is good. */
/* b. rx fifo config */
/* reset value is good. */
/* 18.c */
dwc_event_init(dev);
/* 18.d */
/* enable device event generation */
dwc_event_device_enable(dev, BIT(DWC_EVENT_DEVICE_EVENT_ID_DISCONNECT) |
BIT(DWC_EVENT_DEVICE_EVENT_ID_USB_RESET) |
BIT(DWC_EVENT_DEVICE_EVENT_ID_CONNECT_DONE));
/* 18.e initialize control end point
* start new config on end point 0. only needed for ep0.
* resource index must be set to 0.
*/
dwc_ep_cmd_start_new_config(dev, 0, 0);
/* steps described in snps 8.1 */
dwc_ep_ctrl_init(dev);
/* Unmask interrupts */
unmask_interrupt(USB30_EE1_IRQ);
/* start the control ep fsm */
dwc_ep_ctrl_state_setup_enter(dev);
}
/* add a new non-control endpoint belonging to the selected USB configuration.
* This is called when "set config" setup is received from host.
* udc layer first adds the endpoints which are part of the selected
* configuration by calling this api.
* Then it calls dwc_device_set_configuration() to enable that configuration.
* TODO: need better api to manage this. possibly a single api to do both.
* also, currently this only works as long as same configuration is selected
* every time. The cleanup during usb reset is not cleanly removing the
* endpoints added during a set config. This works fine for our usecase.
*/
void dwc_device_add_ep(dwc_dev_t *dev, dwc_ep_t new_ep)
{
uint8_t index = DWC_EP_INDEX(new_ep.number, new_ep.dir);
dwc_ep_t *ep = &dev->ep[index];
memset(ep, 0, sizeof(ep));
/* copy client specified params */
ep->number = new_ep.number;
ep->dir = new_ep.dir;
ep->type = new_ep.type;
ep->max_pkt_size = new_ep.max_pkt_size;
ep->burst_size = new_ep.burst_size;
ep->zlp = new_ep.zlp;
ep->trb_count = new_ep.trb_count;
ep->trb = new_ep.trb;
ASSERT(ep->trb);
/* clear out trb memory space. */
memset(ep->trb, 0, (ep->trb_count)*sizeof(ep->trb));
arch_clean_invalidate_cache_range((addr_t) ep->trb,
(ep->trb_count)*sizeof(ep->trb));
/* initialize dwc specified params */
/* map this usb ep to the next available phy ep.
* we assume non-flexible endpoint mapping.
* so the physical ep number is same as the index into our EP array.
*/
ep->phy_num = index;
if (ep->dir == DWC_EP_DIRECTION_IN)
{
/* TODO: this works only as long as we just one IN EP (non-control).
* Need to increment this for every new IN ep added.
*/
ep->tx_fifo_num = 1;
}
else
{
ep->tx_fifo_num = 0; /* tx fifo num must be 0 for OUT ep */
}
ep->trb_queued = 0;
ep->bytes_queued = 0;
ep->resource_idx = 0;
ep->state = EP_STATE_INIT;
}
/* Configure and enable non-control endpoints:
* This is called when "set config" setup is received from host.
* Implements sequence as described in snps databook 8.1.5.
*/
void dwc_device_set_configuration(dwc_dev_t *dev)
{
/* disable every ep other than control EPs */
dwc_ep_disable_non_control(dev);
/* re-initialize TX FIFO by sending set config cmd to ep-1 */
dwc_ep_cmd_set_config(dev, 1, SET_CONFIG_ACTION_MODIFY);
/* re-initialize transfer resource allocation:
* only needed for ep0.
* resource index must be set to 2 when doing set config
*/
dwc_ep_cmd_start_new_config(dev, 0, 2);
/* Initialize config for each non-control EP in the new configuration */
for (uint8_t ep_index = 2; ep_index < DWC_MAX_NUM_OF_EP; ep_index++)
{
/* non-zero phy ep num indicates that this ep data is initialized
* and ready for use.
*/
if (dev->ep[ep_index].phy_num)
{
dwc_ep_config_init_enable(dev, ep_index);
}
}
/* optional: re-initialize tx FIFO : GTXFIFOSIZn*/
}
/* Enqueue new data transfer request on an endpoint. */
static int dwc_request_queue(dwc_dev_t *dev,
uint8_t ep_phy_num,
dwc_request_t *req)
{
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
dwc_trb_t *trb = ep->trb;
uint8_t *data_ptr = req->data;
uint32_t transfer_len = req->len;
dwc_trb_trbctl_t trbctl = req->trbctl;
uint32_t pad_len;
if(ep->state != EP_STATE_INACTIVE)
{
DBG("\n EP must be in INACTIVE state to start queue transfer. ep_phy_num = %d current state = %s",
ep_phy_num, ep_state_lookup[ep->state]);
return -1;
}
/* trb queued must be 0 at this time. */
ASSERT(ep->trb_queued == 0);
/* save the original request for this ep */
ep->req = *req;
ep->bytes_queued = 0;
if (ep->type == EP_TYPE_CONTROL)
{
memset(trb, 0, sizeof(dwc_trb_t));
REG_WRITE_FIELD_LOCAL(&trb->f1, TRB_F1, PTR_LOW, (uint32_t) data_ptr);
REG_WRITE_FIELD_LOCAL(&trb->f2, TRB_F2, PTR_HIGH, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f3, TRB_F3, BUFSIZ, transfer_len);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, LST, 0x1);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CHN, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CSP, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, TRBCTL, trbctl);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, IOC, 0x1);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, HWO, 0x1);
/* increment the queued trb count */
ep->trb_queued++;
ep->bytes_queued += transfer_len;
data_ptr += transfer_len;
}
else if (ep->type == EP_TYPE_BULK)
{
/* reserve 1 trb for pad/zero-length pkt */
uint32_t trb_available = ep->trb_count - 1;
uint32_t max_bytes_per_trb;
uint32_t offset;
uint32_t trb_len = 0;
/* snps 7.2 table 7-1. applies only to older versions of the controller:
* - data_ptr in first TRB can be aligned to byte
* - but the following TRBs should point to data that is aligned
* to master bus data width.
*/
/* align default MAX_BYTES_PER_TRB to DWC_MASTER_BUS_WIDTH */
max_bytes_per_trb = ROUNDDOWN(DWC_MAX_BYTES_PER_TRB, DWC_MASTER_BUS_WIDTH);
while (trb_available && transfer_len)
{
/* clear out trb fields */
memset(trb, 0, sizeof(dwc_trb_t));
if (ep->trb_queued == 0)
{
/* first trb: limit the transfer length in this TRB such that
* the next trb data_ptr will be aligned to master bus width.
*/
offset = ((uint32_t) data_ptr) & (DWC_MASTER_BUS_WIDTH - 1);
trb_len = (transfer_len <= max_bytes_per_trb) ?
transfer_len : (max_bytes_per_trb - offset);
}
else
{
trb_len = (transfer_len <= max_bytes_per_trb) ?
transfer_len : max_bytes_per_trb;
}
REG_WRITE_FIELD_LOCAL(&trb->f1, TRB_F1, PTR_LOW, (uint32_t) data_ptr);
REG_WRITE_FIELD_LOCAL(&trb->f2, TRB_F2, PTR_HIGH, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f3, TRB_F3, BUFSIZ, trb_len);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, LST, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CHN, 0x1);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CSP, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, TRBCTL, trbctl);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, IOC, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, HWO, 0x1);
/* increment the queued trb count */
ep->trb_queued++;
ep->bytes_queued += trb_len;
data_ptr += trb_len;
/* remaining transfer len */
transfer_len -= trb_len;
/* remaining trb */
trb_available--;
/* point to the next trb */
trb++;
}
if (transfer_len)
{
/* TRBs not available to queue the entire request.
* If more data is expected in each request, increase the number
* of TRBs allocated for this EP.
*/
ERR("\n ERROR: Enough TRBs are not available to setup transfer\n");
ERR("\n ERROR: Increase TRB chain for the ep.\n");
ERR("\n ERROR: phy_ep_num = %d xfer len = %d\n", ep_phy_num, req->len);
ASSERT(0);
}
/* snps 8.2.3.3:
* For an OUT ep:
* (a) The "buffer descriptor" must be exact multiple of max_pkt_size
* "buffer descriptor" consists of one or more TRBs upto the TRB
* with CHN (chain) flag is not set.
* Add a TRB to pad the len if it is not exact multiple.
*
* (b) If the expected amount of data is exact multiple of max_pkt_size:
* add a max_pkt_size trb to sink in zero-length pkt, only if
* the EP expects it.
*/
uint32_t roundup = req->len % ep->max_pkt_size;
if ( (ep->dir == DWC_EP_DIRECTION_OUT) &&
(roundup || ep->zlp))
{
if(roundup)
{
/* add a TRB to make it exact multiple of max_pkt_size */
pad_len = ep->max_pkt_size - roundup;
}
else
{
/* "buffer descriptor" is exact multiple of max_pkt_size and
* ep expects a zero-length pkt.
* Add a TRB to sink in the zero-length pkt.
*/
pad_len = ep->max_pkt_size;
}
memset(trb, 0, sizeof(dwc_trb_t));
memset(ep->zlp_buf, 0, DWC_ZLP_BUF_SIZE);
REG_WRITE_FIELD_LOCAL(&trb->f1, TRB_F1, PTR_LOW, (uint32_t) ep->zlp_buf);
REG_WRITE_FIELD_LOCAL(&trb->f2, TRB_F2, PTR_HIGH, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f3, TRB_F3, BUFSIZ, pad_len);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, LST, 0x1);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CHN, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CSP, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, TRBCTL, trbctl);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, IOC, 0x1);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, HWO, 0x1);
/* increment the queued trb count */
ep->trb_queued++;
ep->bytes_queued += pad_len;
}
else /* pad trb not needed. */
{
/* point trb to the last programmed trb */
trb--;
/* setup params for last TRB */
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, CHN, 0x0);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, LST, 0x1);
REG_WRITE_FIELD_LOCAL(&trb->f4, TRB_F4, IOC, 0x1);
}
}
else
{
/* invalid EP type */
ASSERT(0);
}
/* flush the trb data to main memory */
arch_clean_invalidate_cache_range((addr_t) ep->trb,
sizeof(dwc_trb_t)*ep->trb_queued);
DBG("\n Starting new xfer on ep_phy_num = %d TRB_QUEUED = %d \n",
ep_phy_num, ep->trb_queued);
/* dwc_request_queue could be triggered from app and thus
* outside of interrupt context. Use critical section to make sure all
* states are updated properly before we handle other interrupts.
*/
enter_critical_section();
if(ep->state == EP_STATE_INACTIVE)
{
dwc_ep_cmd_start_transfer(dev, ep_phy_num);
if(dwc_device_run_status(dev))
{
ep->state = EP_STATE_START_TRANSFER;
}
else
{
/* no interrupt expected on completion of start transfer.
* directly move to xfer in prog state.
*/
ep->state = EP_STATE_XFER_IN_PROG;
}
}
else
{
DBG("\n Attempting START_TRANSFER in invalid state: %s. .......\n",
ep_state_lookup[ep->state]);
ASSERT(0);
}
exit_critical_section();
return 0;
}
/* data transfer request:
* NOTE: Assumes that the data to be transferred is already in main memory.
* Any cache management must be done by caller .
* For received data, cache mgmt must be done in callback function.
*/
int dwc_transfer_request(dwc_dev_t *dwc,
uint8_t usb_ep,
dwc_ep_direction_t dir,
void *buf,
uint32_t len,
dwc_transfer_callback_t callback,
void *callback_context)
{
uint8_t ep_phy_num;
dwc_request_t req;
/* map usb ep to physical ep */
ep_phy_num = DWC_EP_PHY_NUM(usb_ep, dir);
req.data = buf;
req.len = len;
req.callback = callback;
req.context = callback_context;
if (usb_ep == 0)
{
/* control EP always has CONTROL_DATA trb */
req.trbctl = TRBCTL_CONTROL_DATA;
}
else
{
req.trbctl = TRBCTL_NORMAL;
}
return dwc_request_queue(dwc, ep_phy_num, &req);
}