1978 lines
58 KiB
C
1978 lines
58 KiB
C
/* Copyright (c) 2013-2015, 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 ";
|
|
}
|
|
|
|
static void dwc_print_ep_event_details(dwc_dev_t *dev, uint32_t *event)
|
|
{
|
|
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
|
|
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
|
|
uint8_t event_ctrl_stage = DWC_EVENT_EP_EVENT_CTRL_STAGE(*event);
|
|
uint8_t event_status = DWC_EVENT_EP_EVENT_STATUS(*event);
|
|
uint8_t xfer_res_idx = DWC_EVENT_EP_EVENT_XFER_RES_IDX(*event);
|
|
uint16_t event_param = DWC_EVENT_EP_EVENT_PARAM(*event);
|
|
dwc_dep_cmd_id_t cmd = DWC_EVENT_EP_EVENT_CMD_TYPE(*event);
|
|
|
|
ERR("\n\n");
|
|
ERR("EP event (0x%x) details\n", *event);
|
|
ERR("event_id = %d, %s\n", event_id, event_lookup_ep[event_id]);
|
|
ERR("ep_phy_num = %d\n", ep_phy_num);
|
|
ERR("event_ctrl_stage = %d, %s\n", event_ctrl_stage, dev_ctrl_state_lookup[event_ctrl_stage]);
|
|
ERR("event_status = %d\n", event_status);
|
|
ERR("xfer_res_idx = %d\n", xfer_res_idx);
|
|
ERR("event_param = %d\n", event_param);
|
|
ERR("event_cmd_type = %d, %s\n", cmd, cmd_lookup[cmd]);
|
|
ERR("\n");
|
|
}
|
|
|
|
static void dwc_print_ep_details(dwc_dev_t *dev, uint8_t ep_index)
|
|
{
|
|
ERR("\n");
|
|
ERR("EP ( index = %d) status: \n", ep_index);
|
|
ERR("phy_num = %d \n", dev->ep[ep_index].phy_num);
|
|
ERR("usb ep num = %d \n", dev->ep[ep_index].number);
|
|
ERR("dir = %d \n", dev->ep[ep_index].dir);
|
|
ERR("type = %d \n", dev->ep[ep_index].type);
|
|
ERR("resource_idx = %d \n", dev->ep[ep_index].resource_idx);
|
|
ERR("trb_queued = %d \n", dev->ep[ep_index].trb_queued);
|
|
ERR("bytes_queued = %d \n", dev->ep[ep_index].bytes_queued);
|
|
ERR("state = %d, %s \n", dev->ep[ep_index].state, ep_state_lookup[dev->ep[ep_index].state]);
|
|
ERR("ep req len = %d trbctl = %d\n", dev->ep[ep_index].req.len, dev->ep[ep_index].req.trbctl);
|
|
ERR("\n");
|
|
}
|
|
|
|
static void dwc_print_current_state(dwc_dev_t *dev)
|
|
{
|
|
uint8_t i = 0;
|
|
|
|
ERR("\n");
|
|
ERR("dwc core = 0x%x\n", dev->core_id);
|
|
ERR("ctrl_state = %d, %s\n", dev->ctrl_state, dev_ctrl_state_lookup[dev->ctrl_state]);
|
|
|
|
for (i = 0; i < DWC_MAX_NUM_OF_EP; i++)
|
|
{
|
|
dwc_print_ep_details(dev, i);
|
|
}
|
|
|
|
ERR("\n");
|
|
}
|
|
|
|
/******************************** 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(dwc_dev_t));
|
|
|
|
/* 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);
|
|
|
|
/* note: only for debug */
|
|
dwc_debug_lookup_init();
|
|
|
|
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:
|
|
ERR("\nUnhandled DEVICE_EVENT: %s in %s \n",
|
|
event_lookup_device[event_id],
|
|
dev_ctrl_state_lookup[dev->ctrl_state]);
|
|
dwc_print_current_state(dev);
|
|
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];
|
|
ASSERT(ep != NULL);
|
|
|
|
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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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);
|
|
#endif
|
|
|
|
/* 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:
|
|
ERR("Invalid ctrl_state = %d\n", dev->ctrl_state);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
ASSERT(0);
|
|
}
|
|
|
|
#ifdef DEBUG_USB
|
|
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]);
|
|
#endif
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(index < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[index];
|
|
ASSERT(ep != NULL);
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* point to next trb */
|
|
trb++;
|
|
|
|
/* decrement trb count */
|
|
num_of_trb--;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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 Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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 */
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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_CMD_COMPLETE:
|
|
{
|
|
DBG("\n cmd = %s has no action. ignored.",
|
|
cmd_lookup[DWC_EVENT_EP_EVENT_CMD_TYPE(*event)]);
|
|
}
|
|
break;
|
|
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. */
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
switch (event_id)
|
|
{
|
|
case DWC_EVENT_EP_CMD_COMPLETE:
|
|
{
|
|
DBG("\n cmd = %s has no action. ignored.",
|
|
cmd_lookup[DWC_EVENT_EP_EVENT_CMD_TYPE(*event)]);
|
|
}
|
|
break;
|
|
case DWC_EVENT_EP_XFER_NOT_READY:
|
|
{
|
|
if (event_ctrl_stage == CONTROL_DATA_REQUEST)/* data request */
|
|
{
|
|
if (ep->state == EP_STATE_START_TRANSFER ||
|
|
ep->state == EP_STATE_XFER_IN_PROG) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
dwc_request_t req;
|
|
|
|
req.callback = 0x0;
|
|
req.context = 0x0;
|
|
req.data = 0x0;
|
|
req.len = 0x0;
|
|
req.trbctl = TRBCTL_CONTROL_DATA;
|
|
|
|
DBG("\n Sending the elp to host as the end of xfer\n");
|
|
dwc_request_queue(dev, ep_phy_num, &req);
|
|
dev->ctrl_state = EP_FSM_CTRL_DATA;
|
|
} else {
|
|
DBG("\n attempting to start data when setup did not indicate"
|
|
"data stage. stall...\n\n");
|
|
dwc_ep_cmd_stall(dev, ep_phy_num);
|
|
dev->ctrl_state = EP_FSM_STALL;
|
|
}
|
|
}
|
|
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 */
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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);
|
|
if (dev->is_test_mode)
|
|
{
|
|
/* Write DCTL register with test mode value */
|
|
dwc_device_enter_test_mode(dev);
|
|
dprintf(INFO, "Device entered test mode, please reset the device once testing is finished\n");
|
|
break;
|
|
}
|
|
|
|
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 */
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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 */
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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)
|
|
{
|
|
dwc_event_ep_event_id_t event_id = DWC_EVENT_EP_EVENT_ID(*event);
|
|
#ifdef DEBUG_USB
|
|
uint8_t ep_phy_num = DWC_EVENT_EP_EVENT_EP_NUM(*event);
|
|
#endif
|
|
|
|
switch (event_id)
|
|
{
|
|
case DWC_EVENT_EP_CMD_COMPLETE:
|
|
{
|
|
DBG("\n cmd = %s has no action. ignored.",
|
|
cmd_lookup[DWC_EVENT_EP_EVENT_CMD_TYPE(*event)]);
|
|
}
|
|
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 */
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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:
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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:
|
|
ERR("\n Ignore the unexpected EP event: %s\n", event_lookup_ep[event_id]);
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
#ifdef DEBUG_USB
|
|
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);
|
|
#endif
|
|
|
|
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:
|
|
ERR("\n EP state is invalid. Asserting...\n");
|
|
dwc_print_ep_event_details(dev, event);
|
|
dwc_print_current_state(dev);
|
|
ASSERT(0);
|
|
}
|
|
|
|
#ifdef DEBUG_USB
|
|
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]);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************** 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)
|
|
{
|
|
ASSERT(index < DWC_MAX_NUM_OF_EP);
|
|
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);
|
|
ASSERT(index < DWC_MAX_NUM_OF_EP);
|
|
|
|
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_IN);
|
|
|
|
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)
|
|
{
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
/* 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_SUSPEND_ENTRY) |
|
|
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);
|
|
|
|
ASSERT(index < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[index];
|
|
ASSERT(ep != NULL);
|
|
|
|
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)
|
|
{
|
|
ASSERT(DWC_EP_PHY_TO_INDEX(ep_phy_num) < DWC_MAX_NUM_OF_EP);
|
|
dwc_ep_t *ep = &dev->ep[DWC_EP_PHY_TO_INDEX(ep_phy_num)];
|
|
ASSERT(ep != NULL);
|
|
|
|
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 || ep->type == EP_TYPE_INTERRUPT)
|
|
{
|
|
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
|
|
{
|
|
ERR("\n Attempting START_TRANSFER in invalid state: %s. .......\n",
|
|
ep_state_lookup[ep->state]);
|
|
dwc_print_current_state(dev);
|
|
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);
|
|
}
|