2007-08-27 16:04:32 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id: $
|
|
|
|
*
|
|
|
|
* Copyright (C) 2007 by Christian Gmeiner
|
|
|
|
*
|
|
|
|
* Based on code from the Linux Target Image Builder from Freescale
|
|
|
|
* available at http://www.bitshrine.org/ and
|
|
|
|
* http://www.bitshrine.org/gpp/linux-2.6.16-mx31-usb-2.patch
|
|
|
|
* Adapted for Rockbox in January 2007
|
|
|
|
* Original file: drivers/usb/gadget/arcotg_udc.c
|
|
|
|
*
|
|
|
|
* USB Device Controller Driver
|
|
|
|
* Driver for ARC OTG USB module in the i.MX31 platform, etc.
|
|
|
|
*
|
|
|
|
* Copyright 2004-2006 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Based on mpc-udc.h
|
|
|
|
* Author: Li Yang (leoli@freescale.com)
|
|
|
|
* Jiang Bo (Tanya.jiang@freescale.com)
|
|
|
|
*
|
|
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include "arcotg_dcd.h"
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static struct arcotg_dcd dcd_controller;
|
|
|
|
struct usb_response res;
|
|
|
|
|
|
|
|
/* datastructes to controll transfers */
|
|
|
|
struct dtd dev_td[USB_MAX_PIPES] IBSS_ATTR;
|
|
|
|
struct dqh dev_qh[USB_MAX_PIPES] __attribute((aligned (1 << 11))) IBSS_ATTR;
|
|
|
|
|
|
|
|
/* shared memory used by rockbox and dcd to exchange data */
|
|
|
|
#define BUFFER_SIZE 512
|
|
|
|
unsigned char buffer[BUFFER_SIZE] IBSS_ATTR;
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* description of our device driver operations */
|
|
|
|
struct usb_dcd_controller_ops arotg_dcd_ops = {
|
|
|
|
.enable = usb_arcotg_dcd_enable,
|
|
|
|
.disable = NULL,
|
|
|
|
.set_halt = usb_arcotg_dcd_set_halt,
|
|
|
|
.send = usb_arcotg_dcd_send,
|
|
|
|
.receive = usb_arcotg_dcd_receive,
|
|
|
|
.ep0 = &dcd_controller.endpoints[0],
|
|
|
|
};
|
|
|
|
|
|
|
|
/* description of our usb controller driver */
|
|
|
|
struct usb_controller arcotg_dcd = {
|
|
|
|
.name = "arcotg_dcd",
|
|
|
|
.type = DEVICE,
|
|
|
|
.speed = USB_SPEED_UNKNOWN,
|
|
|
|
.init = usb_arcotg_dcd_init,
|
|
|
|
.shutdown = usb_arcotg_dcd_shutdown,
|
|
|
|
.irq = usb_arcotg_dcd_irq,
|
|
|
|
.start = usb_arcotg_dcd_start,
|
|
|
|
.stop = usb_arcotg_dcd_stop,
|
|
|
|
.controller_ops = (void*)&arotg_dcd_ops,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct usb_response response;
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* TODO hmmm */
|
|
|
|
|
|
|
|
struct timer {
|
|
|
|
unsigned long s;
|
|
|
|
unsigned long e;
|
|
|
|
};
|
|
|
|
|
|
|
|
void
|
|
|
|
timer_set(struct timer * timer, unsigned long val)
|
|
|
|
{
|
|
|
|
timer->s = USEC_TIMER;
|
|
|
|
timer->e = timer->s + val + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
timer_expired(struct timer * timer)
|
|
|
|
{
|
|
|
|
unsigned long val = USEC_TIMER;
|
|
|
|
|
|
|
|
if (timer->e > timer->s) {
|
|
|
|
return !(val >= timer->s && val <= timer->e);
|
|
|
|
} else {
|
|
|
|
return (val > timer->e && val < timer->s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_PACKET_SIZE USB_MAX_CTRL_PAYLOAD
|
|
|
|
|
|
|
|
#define ERROR_TIMEOUT (-3)
|
|
|
|
#define ERROR_UNKNOWN (-7)
|
|
|
|
|
|
|
|
#define PRIME_TIMER 100000
|
|
|
|
#define TRANSFER_TIMER 1000000
|
|
|
|
#define RESET_TIMER 5000000
|
|
|
|
#define SETUP_TIMER 200000
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* gets called by usb_stack_init() to register
|
|
|
|
* this arcotg device conrtollder driver in the
|
|
|
|
* stack. */
|
2007-08-27 22:07:36 +00:00
|
|
|
void usb_dcd_init(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
usb_controller_register(&arcotg_dcd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
void usb_arcotg_dcd_init(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
struct timer t;
|
|
|
|
int i, ep_num = 0;
|
|
|
|
|
|
|
|
logf("arcotg_dcd: init");
|
|
|
|
memset(&dcd_controller, 0, sizeof(struct arcotg_dcd));
|
|
|
|
|
|
|
|
/* setup list of aviable endpoints */
|
|
|
|
INIT_LIST_HEAD(&arcotg_dcd.endpoints.list);
|
|
|
|
|
|
|
|
for (i = 0; i < USB_MAX_PIPES; i++) {
|
|
|
|
|
|
|
|
dcd_controller.endpoints[i].pipe_num = i;
|
|
|
|
|
|
|
|
if (i % 2 == 0) {
|
|
|
|
dcd_controller.endpoints[i].ep_num = ep_num;
|
|
|
|
} else {
|
|
|
|
dcd_controller.endpoints[i].ep_num = ep_num;
|
|
|
|
ep_num++;
|
|
|
|
}
|
|
|
|
|
|
|
|
logf("pipe %d -> ep %d %s", dcd_controller.endpoints[i].pipe_num, dcd_controller.endpoints[i].ep_num, dcd_controller.endpoints[i].name);
|
|
|
|
|
|
|
|
if (ep_name[i] != NULL) {
|
|
|
|
memcpy(&dcd_controller.endpoints[i].name, ep_name[i], sizeof(dcd_controller.endpoints[i].name));
|
|
|
|
|
|
|
|
if (i != 0) {
|
|
|
|
/* add to list of configurable endpoints */
|
|
|
|
list_add_tail(&dcd_controller.endpoints[i].list, &arcotg_dcd.endpoints.list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ep0 is special */
|
|
|
|
arcotg_dcd.ep0 = &dcd_controller.endpoints[0];
|
|
|
|
arcotg_dcd.ep0->maxpacket = USB_MAX_CTRL_PAYLOAD;
|
|
|
|
|
|
|
|
/* stop */
|
|
|
|
UDC_USBCMD &= ~USB_CMD_RUN;
|
|
|
|
|
|
|
|
udelay(50000);
|
|
|
|
timer_set(&t, RESET_TIMER);
|
|
|
|
|
|
|
|
/* reset */
|
|
|
|
UDC_USBCMD |= USB_CMD_CTRL_RESET;
|
|
|
|
|
|
|
|
while ((UDC_USBCMD & USB_CMD_CTRL_RESET)) {
|
|
|
|
if (timer_expired(&t)) {
|
|
|
|
logf("TIMEOUT->init");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* put controller in device mode */
|
|
|
|
UDC_USBMODE |= USB_MODE_CTRL_MODE_DEVICE;
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
/* init queue heads */
|
2007-08-27 16:04:32 +00:00
|
|
|
qh_init(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, 0, 0);
|
2007-08-27 22:07:36 +00:00
|
|
|
qh_init(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, 0, 0);
|
2007-08-27 16:04:32 +00:00
|
|
|
|
|
|
|
UDC_ENDPOINTLISTADDR = (unsigned int)dev_qh;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
void usb_arcotg_dcd_shutdown(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
void usb_arcotg_dcd_start(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
logf("start");
|
|
|
|
|
|
|
|
if (arcotg_dcd.device_driver != NULL) {
|
2007-08-27 22:07:36 +00:00
|
|
|
logf("YEEEEEEESSSSSSS");
|
2007-08-27 16:04:32 +00:00
|
|
|
} else {
|
2007-08-27 22:07:36 +00:00
|
|
|
logf("NOOOOOO");
|
2007-08-27 16:04:32 +00:00
|
|
|
}
|
2007-08-27 22:07:36 +00:00
|
|
|
|
2007-08-27 16:04:32 +00:00
|
|
|
/* clear stopped bit */
|
2007-08-27 22:07:36 +00:00
|
|
|
dcd_controller.stopped = false;
|
2007-08-27 16:04:32 +00:00
|
|
|
|
|
|
|
UDC_USBCMD |= USB_CMD_RUN;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
void usb_arcotg_dcd_stop(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
logf("stop");
|
|
|
|
|
|
|
|
/* set stopped bit */
|
|
|
|
dcd_controller.stopped = true;
|
|
|
|
|
|
|
|
UDC_USBCMD &= ~USB_CMD_RUN;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
void usb_arcotg_dcd_irq(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
if (dcd_controller.stopped == true) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if we need to wake up from suspend */
|
|
|
|
if (!(UDC_USBSTS & USB_STS_SUSPEND) && dcd_controller.resume_state) {
|
|
|
|
resume_int();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* USB Interrupt */
|
|
|
|
if (UDC_USBSTS & USB_STS_INT) {
|
|
|
|
|
|
|
|
/* setup packet, we only support ep0 as control ep */
|
|
|
|
if (UDC_ENDPTSETUPSTAT & EP_SETUP_STATUS_EP0) {
|
|
|
|
/* copy data from queue head to local buffer */
|
|
|
|
memcpy(&dcd_controller.local_setup_buff, (uint8_t *) &dev_qh[0].setup_buffer, 8);
|
|
|
|
/* ack setup packet*/
|
|
|
|
UDC_ENDPTSETUPSTAT = UDC_ENDPTSETUPSTAT;
|
|
|
|
setup_received_int(&dcd_controller.local_setup_buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UDC_ENDPTCOMPLETE) {
|
2007-08-27 22:07:36 +00:00
|
|
|
UDC_ENDPTCOMPLETE = UDC_ENDPTCOMPLETE;
|
2007-08-27 16:04:32 +00:00
|
|
|
}
|
|
|
|
}
|
2007-08-27 22:07:36 +00:00
|
|
|
|
2007-08-27 16:04:32 +00:00
|
|
|
if (UDC_USBSTS & USB_STS_PORT_CHANGE) {
|
|
|
|
port_change_int();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UDC_USBSTS & USB_STS_SUSPEND) {
|
|
|
|
suspend_int();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UDC_USBSTS & USB_STS_RESET) {
|
|
|
|
reset_int();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UDC_USBSTS & USB_STS_ERR) {
|
|
|
|
logf("!!! error !!!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UDC_USBSTS & USB_STS_SYS_ERR) {
|
|
|
|
logf("!!! sys error !!!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* interrupt handlers */
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void setup_received_int(struct usb_ctrlrequest* request)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
int error = 0;
|
|
|
|
uint8_t address = 0;
|
|
|
|
int handled = 0; /* set to zero if we do not handle the message, */
|
2007-08-27 22:07:36 +00:00
|
|
|
/* and should pass it to the driver */
|
2007-08-27 16:04:32 +00:00
|
|
|
|
|
|
|
logf("setup_int");
|
|
|
|
into_usb_ctrlrequest(request);
|
|
|
|
|
|
|
|
/* handle all requests we support */
|
|
|
|
switch (request->bRequestType & USB_TYPE_MASK) {
|
|
|
|
case USB_TYPE_STANDARD:
|
|
|
|
|
|
|
|
switch (request->bRequest) {
|
|
|
|
case USB_REQ_SET_ADDRESS:
|
|
|
|
|
|
|
|
/* store address as we need to ack before setting it */
|
|
|
|
address = (uint8_t)request->wValue;
|
|
|
|
|
|
|
|
handled = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USB_REQ_GET_STATUS:
|
|
|
|
|
|
|
|
logf("sending status..");
|
|
|
|
response.buf = &dcd_controller.usb_state;
|
|
|
|
response.length = 2;
|
|
|
|
|
|
|
|
handled = usb_arcotg_dcd_send(NULL, &response);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
|
|
case USB_REQ_SET_FEATURE:
|
|
|
|
/* we only support set/clear feature for endpoint */
|
|
|
|
if (request->bRequestType == USB_RECIP_ENDPOINT) {
|
|
|
|
int dir = (request->wIndex & 0x0080) ? EP_DIR_IN : EP_DIR_OUT;
|
|
|
|
int num = (request->wIndex & 0x000f);
|
|
|
|
struct usb_ep *ep;
|
|
|
|
|
|
|
|
if (request->wValue != 0 || request->wLength != 0 || (num * 2 + dir) > USB_MAX_PIPES) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ep = &dcd_controller.endpoints[num * 2 + dir];
|
|
|
|
|
|
|
|
if (request->bRequest == USB_REQ_SET_FEATURE) {
|
|
|
|
logf("SET_FEATURE doing set_halt");
|
|
|
|
handled = usb_arcotg_dcd_set_halt(ep, 1);
|
|
|
|
} else {
|
|
|
|
logf("CLEAR_FEATURE doing clear_halt");
|
|
|
|
handled = usb_arcotg_dcd_set_halt(ep, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handled == 0) {
|
2007-08-27 22:07:36 +00:00
|
|
|
handled = 1; /* dont pass it to driver */
|
2007-08-27 16:04:32 +00:00
|
|
|
}
|
|
|
|
}
|
2007-08-27 22:07:36 +00:00
|
|
|
#if 0
|
2007-08-27 16:04:32 +00:00
|
|
|
if (rc == 0) {
|
|
|
|
/* send status only if _arcotg_ep_set_halt success */
|
|
|
|
if (ep0_prime_status(udc, EP_DIR_IN))
|
|
|
|
Ep0Stall(udc);
|
2007-08-27 22:07:36 +00:00
|
|
|
}
|
|
|
|
#endif
|
2007-08-27 16:04:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if dcd can not handle reqeust, ask driver */
|
|
|
|
if (handled == 0) {
|
|
|
|
if (arcotg_dcd.device_driver != NULL && arcotg_dcd.device_driver->request != NULL) {
|
|
|
|
handled = arcotg_dcd.device_driver->request(request);
|
|
|
|
logf("result from driver %d", handled);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handled <= 0) {
|
|
|
|
error = handled;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ack transfer */
|
|
|
|
usb_ack(request, error);
|
|
|
|
|
|
|
|
if (address != 0) {
|
|
|
|
logf("setting address to %d", address);
|
|
|
|
UDC_DEVICEADDR = address << 25;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void port_change_int(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
//logf("port_change_int");
|
|
|
|
uint32_t tmp;
|
|
|
|
enum usb_device_speed speed = USB_SPEED_UNKNOWN;
|
|
|
|
|
|
|
|
/* bus resetting is finished */
|
|
|
|
if (!(UDC_PORTSC1 & PORTSCX_PORT_RESET)) {
|
|
|
|
/* Get the speed */
|
|
|
|
tmp = (UDC_PORTSC1 & PORTSCX_PORT_SPEED_MASK);
|
|
|
|
switch (tmp) {
|
|
|
|
case PORTSCX_PORT_SPEED_HIGH:
|
|
|
|
speed = USB_SPEED_HIGH;
|
|
|
|
break;
|
|
|
|
case PORTSCX_PORT_SPEED_FULL:
|
|
|
|
speed = USB_SPEED_FULL;
|
|
|
|
break;
|
|
|
|
case PORTSCX_PORT_SPEED_LOW:
|
|
|
|
speed = USB_SPEED_LOW;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
speed = USB_SPEED_UNKNOWN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update speed */
|
|
|
|
arcotg_dcd.speed = speed;
|
2007-08-27 22:07:36 +00:00
|
|
|
|
2007-08-27 16:04:32 +00:00
|
|
|
/* update USB state */
|
|
|
|
if (!dcd_controller.resume_state) {
|
|
|
|
dcd_controller.usb_state = USB_STATE_DEFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* inform device driver */
|
|
|
|
if (arcotg_dcd.device_driver != NULL && arcotg_dcd.device_driver->speed != NULL) {
|
|
|
|
arcotg_dcd.device_driver->speed(speed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void suspend_int(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
//logf("suspend_int");
|
|
|
|
dcd_controller.resume_state = dcd_controller.usb_state;
|
|
|
|
dcd_controller.usb_state = USB_STATE_SUSPENDED;
|
|
|
|
|
|
|
|
/* report suspend to the driver */
|
|
|
|
if (arcotg_dcd.device_driver != NULL && arcotg_dcd.device_driver->suspend != NULL) {
|
|
|
|
arcotg_dcd.device_driver->suspend();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void resume_int(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
//logf("resume_int");
|
|
|
|
dcd_controller.usb_state = dcd_controller.resume_state;
|
|
|
|
dcd_controller.resume_state = USB_STATE_NOTATTACHED;
|
|
|
|
|
|
|
|
/* report resume to the driver */
|
|
|
|
if (arcotg_dcd.device_driver != NULL && arcotg_dcd.device_driver->resume != NULL) {
|
|
|
|
arcotg_dcd.device_driver->resume();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void reset_int(void)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
//logf("reset_int");
|
|
|
|
struct timer t;
|
|
|
|
|
|
|
|
timer_set(&t, RESET_TIMER);
|
|
|
|
|
|
|
|
UDC_ENDPTSETUPSTAT = UDC_ENDPTSETUPSTAT;
|
|
|
|
UDC_ENDPTCOMPLETE = UDC_ENDPTCOMPLETE;
|
|
|
|
|
|
|
|
while (UDC_ENDPTPRIME) { /* prime and flush pending transfers */
|
|
|
|
if (timer_expired(&t)) {
|
|
|
|
logf("TIMEOUT->p&f");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UDC_ENDPTFLUSH = ~0;
|
|
|
|
|
|
|
|
if ((UDC_PORTSC1 & (1 << 8)) == 0) {
|
|
|
|
logf("TIMEOUT->port");
|
|
|
|
}
|
|
|
|
|
|
|
|
UDC_USBSTS = (1 << 6);
|
|
|
|
|
|
|
|
while ((UDC_USBSTS & (1 << 2)) == 0) { /* wait for port change */
|
|
|
|
if (timer_expired(&t)) {
|
|
|
|
logf("TIMEOUT->portchange");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UDC_USBSTS = (1 << 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* usb controller ops */
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
int usb_arcotg_dcd_enable(struct usb_ep* ep)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
unsigned short max = 0;
|
|
|
|
unsigned char mult = 0, zlt = 0;
|
|
|
|
int retval = 0;
|
2007-08-27 22:07:36 +00:00
|
|
|
char *val = NULL; /* for debug */
|
2007-08-27 16:04:32 +00:00
|
|
|
|
|
|
|
/* catch bogus parameter */
|
|
|
|
if (!ep) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
logf("ahhh %d", ep->desc->wMaxPacketSize);
|
|
|
|
max = ep->desc->wMaxPacketSize;
|
|
|
|
retval = -EINVAL;
|
|
|
|
|
|
|
|
/* check the max package size validate for this endpoint */
|
|
|
|
/* Refer to USB2.0 spec table 9-13,
|
|
|
|
*/
|
|
|
|
switch (ep->desc->bmAttributes & 0x03) {
|
|
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
|
|
zlt = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
|
|
zlt = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USB_ENDPOINT_XFER_CONTROL:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
switch (ep->desc->bmAttributes & 0x03) {
|
2007-08-27 22:07:36 +00:00
|
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
|
|
if (strstr(ep->ep.name, "-iso") || strstr(ep->ep.name, "-int")) {
|
|
|
|
goto en_done;
|
|
|
|
}
|
|
|
|
mult = 0;
|
|
|
|
zlt = 1;
|
|
|
|
|
|
|
|
switch (arcotg_dcd.speed) {
|
|
|
|
case USB_SPEED_HIGH:
|
|
|
|
if ((max == 128) || (max == 256) || (max == 512)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
switch (max) {
|
|
|
|
case 4:
|
|
|
|
case 8:
|
|
|
|
case 16:
|
|
|
|
case 32:
|
|
|
|
case 64:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
+ case USB_SPEED_LOW:
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case USB_ENDPOINT_XFER_INT:
|
|
|
|
+ if (strstr(ep->ep.name, "-iso")) /* bulk is ok */
|
|
|
|
+ goto en_done;
|
|
|
|
+ mult = 0;
|
|
|
|
+ zlt = 1;
|
|
|
|
+ switch (udc->gadget.speed) {
|
|
|
|
+ case USB_SPEED_HIGH:
|
|
|
|
+ if (max <= 1024)
|
|
|
|
+ break;
|
|
|
|
+ case USB_SPEED_FULL:
|
|
|
|
+ if (max <= 64)
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ if (max <= 8)
|
|
|
|
+ break;
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
+ if (strstr(ep->ep.name, "-bulk") || strstr(ep->ep.name, "-int"))
|
|
|
|
+ goto en_done;
|
|
|
|
+ mult = (unsigned char)
|
|
|
|
+ (1 + ((le16_to_cpu(desc->wMaxPacketSize) >> 11) & 0x03));
|
|
|
|
+ zlt = 0;
|
|
|
|
+ switch (udc->gadget.speed) {
|
|
|
|
+ case USB_SPEED_HIGH:
|
|
|
|
+ if (max <= 1024)
|
|
|
|
+ break;
|
|
|
|
+ case USB_SPEED_FULL:
|
|
|
|
+ if (max <= 1023)
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case USB_ENDPOINT_XFER_CONTROL:
|
|
|
|
+ if (strstr(ep->ep.name, "-iso") || strstr(ep->ep.name, "-int"))
|
|
|
|
+ goto en_done;
|
|
|
|
+ mult = 0;
|
|
|
|
+ zlt = 1;
|
|
|
|
+ switch (udc->gadget.speed) {
|
|
|
|
+ case USB_SPEED_HIGH:
|
|
|
|
+ case USB_SPEED_FULL:
|
|
|
|
+ switch (max) {
|
|
|
|
+ case 1:
|
|
|
|
+ case 2:
|
|
|
|
+ case 4:
|
|
|
|
+ case 8:
|
|
|
|
+ case 16:
|
|
|
|
+ case 32:
|
|
|
|
+ case 64:
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
|
|
|
+ case USB_SPEED_LOW:
|
|
|
|
+ switch (max) {
|
|
|
|
+ case 1:
|
|
|
|
+ case 2:
|
|
|
|
+ case 4:
|
|
|
|
+ case 8:
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ goto en_done;
|
|
|
|
+ }
|
2007-08-27 16:04:32 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* here initialize variable of ep */
|
|
|
|
ep->maxpacket = max;
|
|
|
|
|
|
|
|
/* hardware special operation */
|
|
|
|
|
|
|
|
/* Init EPx Queue Head (Ep Capabilites field in QH
|
|
|
|
* according to max, zlt, mult) */
|
|
|
|
qh_init(ep->ep_num,
|
|
|
|
(ep->desc->bEndpointAddress & USB_DIR_IN) ? USB_RECV : USB_SEND,
|
|
|
|
(unsigned char) (ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK),
|
|
|
|
max, zlt, mult);
|
|
|
|
|
|
|
|
/* Init endpoint x at here */
|
|
|
|
ep_setup(ep->ep_num,
|
|
|
|
(unsigned char)((ep->desc->bEndpointAddress & USB_DIR_IN) ? USB_RECV : USB_SEND),
|
|
|
|
(unsigned char)(ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK));
|
|
|
|
|
|
|
|
/* Now HW will be NAKing transfers to that EP,
|
|
|
|
* until a buffer is queued to it. */
|
|
|
|
|
|
|
|
retval = 0;
|
|
|
|
switch (ep->desc->bmAttributes & 0x03) {
|
|
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
|
|
val = "bulk";
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
val = "iso";
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
|
|
val = "intr";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
val = "ctrl";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
logf("ep num %d", (int)ep->ep_num);
|
|
|
|
|
|
|
|
logf("enabled %s (ep%d%s-%s)", ep->name,
|
|
|
|
ep->desc->bEndpointAddress & 0x0f,
|
|
|
|
(ep->desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", val);
|
|
|
|
logf(" maxpacket %d", max);
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
int usb_arcotg_dcd_set_halt(struct usb_ep* ep, bool halt)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
int status = -EOPNOTSUPP; /* operation not supported */
|
|
|
|
unsigned char dir = 0;
|
|
|
|
unsigned int tmp_epctrl = 0;
|
|
|
|
|
|
|
|
if (!ep) {
|
|
|
|
status = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
|
|
|
|
status = -EOPNOTSUPP;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
status = 0;
|
|
|
|
dir = ep_is_in(ep) ? USB_SEND : USB_RECV;
|
|
|
|
|
|
|
|
tmp_epctrl = UDC_ENDPTCTRL(ep->ep_num);
|
|
|
|
|
|
|
|
if (halt) {
|
|
|
|
/* set the stall bit */
|
|
|
|
if (dir) {
|
|
|
|
tmp_epctrl |= EPCTRL_TX_EP_STALL;
|
|
|
|
} else {
|
|
|
|
tmp_epctrl |= EPCTRL_RX_EP_STALL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* clear the stall bit and reset data toggle */
|
|
|
|
if (dir) {
|
|
|
|
tmp_epctrl &= ~EPCTRL_TX_EP_STALL;
|
|
|
|
tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST;
|
|
|
|
} else {
|
|
|
|
tmp_epctrl &= ~EPCTRL_RX_EP_STALL;
|
|
|
|
tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UDC_ENDPTCTRL(ep->ep_num) = tmp_epctrl;
|
|
|
|
|
|
|
|
out:
|
|
|
|
logf(" %s %s halt rc=%d", ep->name, halt ? "set" : "clear", status);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
int usb_arcotg_dcd_send(struct usb_ep* ep, struct usb_response* res)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
char* ptr;
|
|
|
|
int todo, error, size, done = 0;
|
|
|
|
int index = 1; /* use as default ep0 tx qh and td */
|
|
|
|
struct dtd* td;
|
|
|
|
struct dqh* qh;
|
|
|
|
unsigned int mask;
|
|
|
|
|
|
|
|
if (res == NULL) {
|
|
|
|
logf("invalid input");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep != NULL) {
|
|
|
|
index = ep->pipe_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
logf("buff: %x", res->buf);
|
|
|
|
logf("len: %d", res->length);
|
|
|
|
|
|
|
|
ptr = res->buf;
|
|
|
|
size = res->length;
|
|
|
|
|
|
|
|
td = &dev_td[index];
|
|
|
|
qh = &dev_qh[index];
|
|
|
|
mask = 1 << (15 + index);
|
|
|
|
logf("sending mask: %x", mask);
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* calculate how much to copy and send */
|
|
|
|
todo = MIN(size, BUFFER_SIZE);
|
|
|
|
|
|
|
|
/* copy data to shared memory area */
|
|
|
|
memcpy(buffer, ptr, todo);
|
|
|
|
|
|
|
|
/* init transfer descriptor */
|
|
|
|
td_init(td, buffer, todo);
|
|
|
|
|
|
|
|
/* start transfer*/
|
|
|
|
error = td_enqueue(td, qh, mask);
|
|
|
|
|
|
|
|
if (error == 0) {
|
|
|
|
/* waiting for finished transfer */
|
|
|
|
error = td_wait(td, mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
done = error;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
size -= todo;
|
|
|
|
ptr += todo;
|
|
|
|
done += todo;
|
|
|
|
|
|
|
|
} while (size > 0);
|
|
|
|
|
|
|
|
logf("usb_send done %d",done);
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
int usb_arcotg_dcd_receive(struct usb_ep* ep, struct usb_response* res)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
char* ptr;
|
|
|
|
int todo, error, size, done = 0;
|
|
|
|
int index = 0; /* use as default ep0 rx qh and td */
|
|
|
|
struct dtd* td;
|
|
|
|
struct dqh* qh;
|
|
|
|
unsigned int mask;
|
|
|
|
|
|
|
|
if (res == NULL) {
|
|
|
|
logf("invalid input");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep != NULL) {
|
|
|
|
index = ep->pipe_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
ptr = res->buf;
|
|
|
|
size = res->length;
|
|
|
|
|
|
|
|
td = &dev_td[index];
|
|
|
|
qh = &dev_qh[index];
|
|
|
|
mask = 1 << index;
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* calculate how much to receive in one step */
|
|
|
|
todo = MIN(size, BUFFER_SIZE);
|
|
|
|
|
|
|
|
/* init transfer descritpor */
|
|
|
|
td_init(td, buffer, size);
|
|
|
|
|
|
|
|
/* start transfer */
|
|
|
|
error = td_enqueue(td, qh, mask);
|
|
|
|
|
|
|
|
if (error == 0) {
|
|
|
|
/* wait until transfer is finished */
|
|
|
|
error = td_wait(td, mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
done = error;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy receive data to buffer */
|
|
|
|
memcpy(ptr, buffer, todo);
|
|
|
|
|
|
|
|
size -= todo;
|
|
|
|
ptr += todo;
|
|
|
|
done += todo;
|
|
|
|
|
|
|
|
} while (size > 0);
|
|
|
|
|
|
|
|
logf("usb_recive done %d",done);
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* lifecylce */
|
|
|
|
|
|
|
|
static void qh_init(unsigned char ep_num, unsigned char dir, unsigned char ep_type,
|
2007-08-27 22:07:36 +00:00
|
|
|
unsigned int max_pkt_len, unsigned int zlt, unsigned char mult)
|
|
|
|
{
|
|
|
|
struct dqh *qh = &dev_qh[2 * ep_num + dir];
|
2007-08-27 16:04:32 +00:00
|
|
|
uint32_t tmp = 0;
|
|
|
|
memset(qh, 0, sizeof(struct dqh));
|
|
|
|
|
|
|
|
/* set the Endpoint Capabilites Reg of QH */
|
|
|
|
switch (ep_type) {
|
|
|
|
case USB_ENDPOINT_XFER_CONTROL:
|
|
|
|
/* Interrupt On Setup (IOS). for control ep */
|
|
|
|
tmp = (max_pkt_len << LENGTH_BIT_POS) | INTERRUPT_ON_COMPLETE;
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
tmp = (max_pkt_len << LENGTH_BIT_POS) | (mult << EP_QUEUE_HEAD_MULT_POS);
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
|
|
tmp = max_pkt_len << LENGTH_BIT_POS;
|
|
|
|
if (zlt) {
|
|
|
|
tmp |= EP_QUEUE_HEAD_ZLT_SEL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
logf("error ep type is %d", ep_type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* see 32.14.4.1 Queue Head Initialization */
|
|
|
|
|
|
|
|
/* write the wMaxPacketSize field as required by the USB Chapter9 or application specific portocol */
|
|
|
|
qh->endpt_cap = tmp;
|
|
|
|
|
|
|
|
/* write the next dTD Terminate bit fild to 1 */
|
|
|
|
qh->dtd_ovrl.next_dtd = 1;
|
|
|
|
|
|
|
|
/* write the Active bit in the status field to 0 */
|
|
|
|
qh->dtd_ovrl.dtd_token &= ~STATUS_ACTIVE;
|
|
|
|
|
|
|
|
/* write the Hald bit in the status field to 0 */
|
|
|
|
qh->dtd_ovrl.dtd_token &= ~STATUS_HALTED;
|
|
|
|
|
|
|
|
logf("qh: init %d", (2 * ep_num + dir));
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void td_init(struct dtd* td, void* buffer, uint32_t todo)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
/* see 32.14.5.2 Building a Transfer Descriptor */
|
|
|
|
|
|
|
|
/* init first 7 dwords with 0 */
|
|
|
|
memset(td, 0, sizeof(struct dtd)); /* set set all to 0 */
|
|
|
|
|
|
|
|
/* set terminate bit to 1*/
|
|
|
|
td->next_dtd = 1;
|
|
|
|
|
|
|
|
/* fill in total bytes with transfer size */
|
|
|
|
td->dtd_token = (todo << 16);
|
|
|
|
|
|
|
|
/* set interrupt on compilte if desierd */
|
|
|
|
td->dtd_token |= INTERRUPT_ON_COMPLETE;
|
|
|
|
|
|
|
|
/* initialize the status field with the active bit set to 1 and all remaining status bits to 0 */
|
|
|
|
td->dtd_token |= STATUS_ACTIVE;
|
|
|
|
|
|
|
|
td->buf_ptr0 = (uint32_t)buffer;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static void ep_setup(unsigned char ep_num, unsigned char dir, unsigned char ep_type)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
unsigned int tmp_epctrl = 0;
|
|
|
|
struct timer t;
|
|
|
|
|
|
|
|
tmp_epctrl = UDC_ENDPTCTRL(ep_num);
|
|
|
|
if (dir) {
|
|
|
|
if (ep_num) {
|
|
|
|
tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST;
|
|
|
|
}
|
|
|
|
logf("tx enablde");
|
|
|
|
tmp_epctrl |= EPCTRL_TX_ENABLE;
|
|
|
|
tmp_epctrl |= ((unsigned int)(ep_type) << EPCTRL_TX_EP_TYPE_SHIFT);
|
|
|
|
} else {
|
|
|
|
if (ep_num) {
|
|
|
|
tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST;
|
|
|
|
}
|
|
|
|
logf("rx enablde");
|
|
|
|
tmp_epctrl |= EPCTRL_RX_ENABLE;
|
|
|
|
tmp_epctrl |= ((unsigned int)(ep_type) << EPCTRL_RX_EP_TYPE_SHIFT);
|
|
|
|
}
|
|
|
|
|
|
|
|
UDC_ENDPTCTRL(ep_num) = tmp_epctrl;
|
2007-08-27 22:07:36 +00:00
|
|
|
|
2007-08-27 16:04:32 +00:00
|
|
|
/* wait for the write reg to finish */
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
timer_set(&t, SETUP_TIMER);
|
2007-08-27 16:04:32 +00:00
|
|
|
while (!(UDC_ENDPTCTRL(ep_num) & (tmp_epctrl & (EPCTRL_TX_ENABLE | EPCTRL_RX_ENABLE)))) {
|
|
|
|
if (timer_expired(&t)) {
|
2007-08-27 22:07:36 +00:00
|
|
|
logf("TIMEOUT: enable ep");
|
2007-08-27 16:04:32 +00:00
|
|
|
return;
|
2007-08-27 22:07:36 +00:00
|
|
|
}
|
2007-08-27 16:04:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* helpers for sending/receiving */
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static int td_enqueue(struct dtd* td, struct dqh* qh, unsigned int mask)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
struct timer t;
|
|
|
|
|
|
|
|
qh->dtd_ovrl.next_dtd = (unsigned int)td;
|
|
|
|
qh->dtd_ovrl.dtd_token &= ~0xc0;
|
|
|
|
|
|
|
|
timer_set(&t, PRIME_TIMER);
|
|
|
|
UDC_ENDPTPRIME |= mask;
|
|
|
|
|
|
|
|
while ((UDC_ENDPTPRIME & mask)) {
|
|
|
|
if (timer_expired(&t)) {
|
|
|
|
logf("timeout->prime");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((UDC_ENDPTSTAT & mask) == 0) {
|
|
|
|
logf("Endptstat 0x%x", UDC_ENDPTSTAT);
|
|
|
|
logf("HW_ERROR");
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static int td_wait(struct dtd* td, unsigned int mask)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
struct timer t;
|
|
|
|
timer_set(&t, TRANSFER_TIMER);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if ((UDC_ENDPTCOMPLETE & mask) != 0) {
|
|
|
|
UDC_ENDPTCOMPLETE |= mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((td->dtd_token & (1 << 7)) == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timer_expired(&t)) {
|
|
|
|
return ERROR_TIMEOUT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-27 22:07:36 +00:00
|
|
|
static int usb_ack(struct usb_ctrlrequest * s, int error)
|
|
|
|
{
|
2007-08-27 16:04:32 +00:00
|
|
|
if (error) {
|
|
|
|
logf("STALLing ep0");
|
|
|
|
UDC_ENDPTCTRL0 |= 1 << 16; /* stall */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
res.buf = NULL;
|
|
|
|
res.length = 0;
|
|
|
|
|
|
|
|
if (s->bRequestType & 0x80) {
|
|
|
|
logf("ack in");
|
|
|
|
return usb_arcotg_dcd_receive(NULL, &res);
|
|
|
|
} else {
|
|
|
|
logf("ack out");
|
|
|
|
return usb_arcotg_dcd_send(NULL, &res);
|
|
|
|
}
|
|
|
|
}
|