mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-07-10 13:29:52 -04:00
this commit is a combination of the following changes, which significantly refactors usb core and class drivers. 1. unify usb buffers of each class driver to reduce iram usage currently, many class drivers allocate their own buffer to receive control out data, which is a waste of iram. share one common buffer for that usage to address the issue. 2. simplify control request handling by implicitly receiving write request data packets change 1 above fixed the data destination. therefore, having the core receive the data allows us to reduce the class driver's work and simplifies the api. 3. enhance usb core's control request handling and unify the legacy driver api in order to implement change 2, both the legacy and new driver apis should be supported. so that, using the designware driver as a reference, the new driver api functionality is move into usb core. this simplifies the usb device drivers by requiring them to implement only the functionalities equivalent to the legacy api. tested with ipodvideo(arc) and erosqnative(designware) Change-Id: I3627daa90278751f599e2108ec150ec3f8f6c524
850 lines
21 KiB
C
850 lines
21 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2008 by Maurus Cuelenaere
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "config.h"
|
|
/*#define LOGF_ENABLE*/
|
|
#include "logf.h"
|
|
#include "system.h"
|
|
#include "usb_ch9.h"
|
|
#include "usb_drv.h"
|
|
#include "usb_core.h"
|
|
#include "jz4740.h"
|
|
#include "thread.h"
|
|
|
|
/*
|
|
The Jz4740 USB controller is called MUSBHSFC in the datasheet.
|
|
It also seems to be a more generic controller, with support for
|
|
up to 15 endpoints (the Jz4740 only has 5).
|
|
*/
|
|
|
|
#define EP_BUF_LEFT(ep) ((ep)->length - (ep)->sent)
|
|
#define EP_PTR(ep) ((void*)((unsigned int)(ep)->buf + (ep)->sent))
|
|
#define EP_NUMBER(ep) (((int)(ep) - (int)&endpoints[0])/sizeof(struct usb_endpoint))
|
|
#define EP_NUMBER2(ep) (EP_NUMBER((ep))/2)
|
|
#define TOTAL_EP() (sizeof(endpoints)/sizeof(struct usb_endpoint))
|
|
#define EP_IS_IN(ep) (EP_NUMBER((ep))%2)
|
|
|
|
enum ep_type
|
|
{
|
|
ep_control,
|
|
ep_bulk,
|
|
ep_interrupt,
|
|
ep_isochronous
|
|
};
|
|
|
|
struct usb_endpoint
|
|
{
|
|
void *buf;
|
|
size_t length;
|
|
union
|
|
{
|
|
size_t sent;
|
|
size_t received;
|
|
};
|
|
bool busy;
|
|
|
|
const enum ep_type type;
|
|
const bool use_dma;
|
|
|
|
const long fifo_addr;
|
|
unsigned short fifo_size;
|
|
|
|
bool wait;
|
|
struct semaphore complete;
|
|
};
|
|
|
|
static unsigned char ep0_rx_buf[64];
|
|
static struct usb_endpoint endpoints[] =
|
|
{
|
|
{ .type = ep_control, .fifo_addr = USB_FIFO_EP0, .fifo_size = 64 },
|
|
{ .type = ep_control, .fifo_addr = USB_FIFO_EP0, .buf = ep0_rx_buf },
|
|
{ .type = ep_bulk, .fifo_addr = USB_FIFO_EP1, .fifo_size = 512 },
|
|
{ .type = ep_bulk, .fifo_addr = USB_FIFO_EP1, .fifo_size = 512 },
|
|
{ .type = ep_interrupt, .fifo_addr = USB_FIFO_EP2, .fifo_size = 64 },
|
|
};
|
|
|
|
struct usb_drv_ep_spec usb_drv_ep_specs[USB_NUM_ENDPOINTS] = {
|
|
{.type = {USB_ENDPOINT_XFER_CONTROL, USB_ENDPOINT_XFER_CONTROL}},
|
|
{.type = {USB_ENDPOINT_XFER_BULK, USB_ENDPOINT_XFER_BULK}},
|
|
{.type = {USB_ENDPOINT_TYPE_NONE, USB_ENDPOINT_XFER_INT}},
|
|
};
|
|
uint8_t usb_drv_ep_specs_flags = 0;
|
|
|
|
static inline void select_endpoint(int ep)
|
|
{
|
|
REG_USB_REG_INDEX = ep;
|
|
}
|
|
|
|
static void readFIFO(struct usb_endpoint *ep, unsigned int size)
|
|
{
|
|
logf("%s(EP%d, %d)", __func__, EP_NUMBER2(ep), size);
|
|
|
|
register unsigned char *ptr = (unsigned char*)EP_PTR(ep);
|
|
register unsigned int *ptr32 = (unsigned int*)ptr;
|
|
register unsigned int s = size >> 2;
|
|
register unsigned int x;
|
|
|
|
if(size > 0)
|
|
{
|
|
if( ((unsigned int)ptr & 3) == 0 )
|
|
{
|
|
while(s--)
|
|
*ptr32++ = REG32(ep->fifo_addr);
|
|
|
|
ptr = (unsigned char*)ptr32;
|
|
}
|
|
else
|
|
{
|
|
while(s--)
|
|
{
|
|
x = REG32(ep->fifo_addr);
|
|
*ptr++ = x & 0xFF; x >>= 8;
|
|
*ptr++ = x & 0xFF; x >>= 8;
|
|
*ptr++ = x & 0xFF; x >>= 8;
|
|
*ptr++ = x;
|
|
}
|
|
}
|
|
|
|
s = size & 3;
|
|
while(s--)
|
|
*ptr++ = REG8(ep->fifo_addr);
|
|
}
|
|
}
|
|
|
|
static void writeFIFO(struct usb_endpoint *ep, size_t size)
|
|
{
|
|
logf("%s(EP%d, %d)", __func__, EP_NUMBER2(ep), size);
|
|
|
|
register unsigned int *d32 = (unsigned int *)EP_PTR(ep);
|
|
register size_t s = size >> 2;
|
|
|
|
if(size > 0)
|
|
{
|
|
while (s--)
|
|
REG32(ep->fifo_addr) = *d32++;
|
|
|
|
if( (s = size & 3) )
|
|
{
|
|
register unsigned char *d8 = (unsigned char *)d32;
|
|
while (s--)
|
|
REG8(ep->fifo_addr) = *d8++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void flushFIFO(struct usb_endpoint *ep)
|
|
{
|
|
logf("%s(%d)", __func__, EP_NUMBER(ep));
|
|
|
|
switch (ep->type)
|
|
{
|
|
case ep_control:
|
|
break;
|
|
|
|
case ep_bulk:
|
|
case ep_interrupt:
|
|
case ep_isochronous:
|
|
if(EP_IS_IN(ep))
|
|
REG_USB_REG_INCSR |= (USB_INCSR_FF | USB_INCSR_CDT);
|
|
else
|
|
REG_USB_REG_OUTCSR |= (USB_OUTCSR_FF | USB_OUTCSR_CDT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline void ep_transfer_completed(struct usb_endpoint* ep)
|
|
{
|
|
ep->sent = 0;
|
|
ep->length = 0;
|
|
ep->buf = NULL;
|
|
ep->busy = false;
|
|
if(ep->wait)
|
|
semaphore_release(&ep->complete);
|
|
}
|
|
|
|
static void EP0_send(void)
|
|
{
|
|
struct usb_endpoint* ep = &endpoints[0];
|
|
unsigned int length;
|
|
unsigned char csr0;
|
|
|
|
select_endpoint(0);
|
|
csr0 = REG_USB_REG_CSR0;
|
|
|
|
if(ep->sent == 0)
|
|
length = MIN(ep->length, ep->fifo_size);
|
|
else
|
|
length = MIN(EP_BUF_LEFT(ep), ep->fifo_size);
|
|
|
|
writeFIFO(ep, length);
|
|
ep->sent += length;
|
|
|
|
if(ep->sent >= ep->length)
|
|
{
|
|
REG_USB_REG_CSR0 = (csr0 | USB_CSR0_INPKTRDY | USB_CSR0_DATAEND); /* Set data end! */
|
|
usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, 0, ep->sent);
|
|
ep_transfer_completed(ep);
|
|
}
|
|
else
|
|
REG_USB_REG_CSR0 = (csr0 | USB_CSR0_INPKTRDY);
|
|
}
|
|
|
|
static void EP0_handler(void)
|
|
{
|
|
logf("%s()", __func__);
|
|
|
|
unsigned char csr0;
|
|
struct usb_endpoint *ep_send = &endpoints[0];
|
|
struct usb_endpoint *ep_recv = &endpoints[1];
|
|
|
|
/* Read CSR0 */
|
|
select_endpoint(0);
|
|
csr0 = REG_USB_REG_CSR0;
|
|
|
|
/* Check for SentStall:
|
|
This bit is set when a STALL handshake is transmitted. The CPU should clear this bit.
|
|
*/
|
|
if(csr0 & USB_CSR0_SENTSTALL)
|
|
{
|
|
REG_USB_REG_CSR0 = csr0 & ~USB_CSR0_SENTSTALL;
|
|
return;
|
|
}
|
|
|
|
/* Check for SetupEnd:
|
|
This bit will be set when a control transaction ends before the DataEnd bit has been set.
|
|
An interrupt will be generated and the FIFO flushed at this time.
|
|
The bit is cleared by the CPU writing a 1 to the ServicedSetupEnd bit.
|
|
*/
|
|
if(csr0 & USB_CSR0_SETUPEND)
|
|
{
|
|
REG_USB_REG_CSR0 = csr0 | USB_CSR0_SVDSETUPEND;
|
|
return;
|
|
}
|
|
|
|
/* Call relevant routines for endpoint 0 state */
|
|
if(ep_send->busy)
|
|
EP0_send();
|
|
else if(csr0 & USB_CSR0_OUTPKTRDY) /* There is a packet in the fifo */
|
|
{
|
|
readFIFO(ep_recv, REG_USB_REG_COUNT0);
|
|
REG_USB_REG_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */
|
|
usb_core_setup_received((struct usb_ctrlrequest*)ep_recv->buf);
|
|
}
|
|
}
|
|
|
|
static void EPIN_handler(unsigned int endpoint)
|
|
{
|
|
struct usb_endpoint* ep = &endpoints[endpoint*2];
|
|
unsigned int length, csr;
|
|
|
|
select_endpoint(endpoint);
|
|
csr = REG_USB_REG_INCSR;
|
|
logf("%s(%d): 0x%x", __func__, endpoint, csr);
|
|
|
|
if(!ep->busy)
|
|
{
|
|
logf("Entered EPIN handler without work!");
|
|
return;
|
|
}
|
|
|
|
if(csr & USB_INCSR_SENTSTALL)
|
|
{
|
|
REG_USB_REG_INCSR = csr & ~USB_INCSR_SENTSTALL;
|
|
return;
|
|
}
|
|
|
|
if(ep->use_dma)
|
|
return;
|
|
|
|
if(csr & USB_INCSR_FFNOTEMPT)
|
|
{
|
|
logf("FIFO is not empty! 0x%x", csr);
|
|
return;
|
|
}
|
|
|
|
logf("EP%d: %d -> %d", endpoint, ep->sent, ep->length);
|
|
|
|
if(ep->sent == 0)
|
|
length = MIN(ep->length, ep->fifo_size);
|
|
else
|
|
length = MIN(EP_BUF_LEFT(ep), ep->fifo_size);
|
|
|
|
writeFIFO(ep, length);
|
|
REG_USB_REG_INCSR = csr | USB_INCSR_INPKTRDY;
|
|
ep->sent += length;
|
|
|
|
if(ep->sent >= ep->length)
|
|
{
|
|
usb_core_transfer_complete(endpoint, USB_DIR_IN, 0, ep->sent);
|
|
ep_transfer_completed(ep);
|
|
logf("sent complete");
|
|
}
|
|
}
|
|
|
|
static void EPOUT_handler(unsigned int endpoint)
|
|
{
|
|
struct usb_endpoint* ep = &endpoints[endpoint*2+1];
|
|
unsigned int size, csr;
|
|
|
|
if(!ep->busy)
|
|
{
|
|
logf("Entered EPOUT handler without work!");
|
|
return;
|
|
}
|
|
|
|
select_endpoint(endpoint);
|
|
while((csr = REG_USB_REG_OUTCSR) & (USB_OUTCSR_SENTSTALL|USB_OUTCSR_OUTPKTRDY))
|
|
{
|
|
logf("%s(%d): 0x%x", __func__, endpoint, csr);
|
|
if(csr & USB_OUTCSR_SENTSTALL)
|
|
{
|
|
logf("stall sent, flushing fifo..");
|
|
flushFIFO(ep);
|
|
REG_USB_REG_OUTCSR = csr & ~USB_OUTCSR_SENTSTALL;
|
|
return;
|
|
}
|
|
|
|
if(ep->use_dma)
|
|
return;
|
|
|
|
if(csr & USB_OUTCSR_OUTPKTRDY) /* There is a packet in the fifo */
|
|
{
|
|
size = REG_USB_REG_OUTCOUNT;
|
|
|
|
readFIFO(ep, size);
|
|
ep->received += size;
|
|
|
|
/*if(csr & USB_OUTCSR_FFFULL)
|
|
csr &= ~USB_OUTCSR_FFFULL;*/
|
|
|
|
REG_USB_REG_OUTCSR = csr & ~USB_OUTCSR_OUTPKTRDY;
|
|
|
|
logf("received: %d max length: %d", ep->received, ep->length);
|
|
|
|
if(size < ep->fifo_size || ep->received >= ep->length)
|
|
{
|
|
usb_core_transfer_complete(endpoint, USB_DIR_OUT, 0, ep->received);
|
|
ep_transfer_completed(ep);
|
|
logf("receive transfer_complete");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void EPDMA_handler(int number)
|
|
{
|
|
int endpoint = -1;
|
|
unsigned int size = 0;
|
|
|
|
if(number == USB_INTR_DMA_BULKIN)
|
|
endpoint = (REG_USB_REG_CNTL1 >> 4) & 0xF;
|
|
else if(number == USB_INTR_DMA_BULKOUT)
|
|
endpoint = (REG_USB_REG_CNTL2 >> 4) & 0xF;
|
|
|
|
struct usb_endpoint* ep = &endpoints[endpoint];
|
|
logf("DMA_BULK%d %d", number, endpoint);
|
|
|
|
if(number == USB_INTR_DMA_BULKIN)
|
|
size = (unsigned int)ep->buf - REG_USB_REG_ADDR1;
|
|
else if(number == USB_INTR_DMA_BULKOUT)
|
|
size = (unsigned int)ep->buf - REG_USB_REG_ADDR2;
|
|
|
|
if(number == USB_INTR_DMA_BULKOUT)
|
|
{
|
|
/* Disable DMA */
|
|
REG_USB_REG_CNTL2 = 0;
|
|
|
|
select_endpoint(endpoint);
|
|
/* Read out last packet manually */
|
|
unsigned int lpack_size = REG_USB_REG_OUTCOUNT;
|
|
if(lpack_size > 0)
|
|
{
|
|
ep->buf += ep->length - lpack_size;
|
|
readFIFO(ep, lpack_size);
|
|
REG_USB_REG_OUTCSR &= ~USB_OUTCSR_OUTPKTRDY;
|
|
}
|
|
}
|
|
else if(number == USB_INTR_DMA_BULKIN && size % ep->fifo_size)
|
|
{
|
|
/* If the last packet is less than MAXP, set INPKTRDY manually */
|
|
REG_USB_REG_INCSR |= USB_INCSR_INPKTRDY;
|
|
}
|
|
|
|
usb_core_transfer_complete(endpoint, EP_IS_IN(ep) ? USB_DIR_IN : USB_DIR_OUT,
|
|
0, ep->length);
|
|
ep_transfer_completed(ep);
|
|
}
|
|
|
|
static void setup_endpoint(struct usb_endpoint *ep)
|
|
{
|
|
int csr, csrh;
|
|
|
|
select_endpoint(EP_NUMBER2(ep));
|
|
|
|
ep->busy = false;
|
|
ep->wait = false;
|
|
ep->sent = 0;
|
|
ep->length = 0;
|
|
|
|
if(ep->type == ep_bulk)
|
|
{
|
|
if(REG_USB_REG_POWER & USB_POWER_HSMODE)
|
|
ep->fifo_size = 512;
|
|
else
|
|
ep->fifo_size = 64;
|
|
}
|
|
|
|
if(EP_IS_IN(ep))
|
|
{
|
|
csr = (USB_INCSR_FF | USB_INCSR_CDT);
|
|
csrh = USB_INCSRH_MODE;
|
|
|
|
if(ep->use_dma)
|
|
csrh |= (USB_INCSRH_DMAREQENAB | USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQMODE);
|
|
|
|
if(ep->type == ep_interrupt)
|
|
csrh |= USB_INCSRH_FRCDATATOG;
|
|
|
|
REG_USB_REG_INMAXP = ep->fifo_size;
|
|
REG_USB_REG_INCSR = csr;
|
|
REG_USB_REG_INCSRH = csrh;
|
|
REG_USB_REG_INTRINE |= USB_INTR_EP(EP_NUMBER2(ep));
|
|
}
|
|
else
|
|
{
|
|
csr = (USB_OUTCSR_FF | USB_OUTCSR_CDT);
|
|
csrh = 0;
|
|
|
|
if(ep->type == ep_interrupt)
|
|
csrh |= USB_OUTCSRH_DNYT;
|
|
|
|
if(ep->use_dma)
|
|
csrh |= (USB_OUTCSRH_DMAREQENAB | USB_OUTCSRH_AUTOCLR | USB_OUTCSRH_DMAREQMODE);
|
|
|
|
REG_USB_REG_OUTMAXP = ep->fifo_size;
|
|
REG_USB_REG_OUTCSR = csr;
|
|
REG_USB_REG_OUTCSRH = csrh;
|
|
REG_USB_REG_INTROUTE |= USB_INTR_EP(EP_NUMBER2(ep));
|
|
}
|
|
}
|
|
|
|
static void udc_reset(void)
|
|
{
|
|
/* From the datasheet:
|
|
|
|
When a reset condition is detected on the USB, the controller performs the following actions:
|
|
* Sets FAddr to 0.
|
|
* Sets Index to 0.
|
|
* Flushes all endpoint FIFOs.
|
|
* Clears all control/status registers.
|
|
* Enables all endpoint interrupts.
|
|
* Generates a Reset interrupt.
|
|
*/
|
|
|
|
logf("%s()", __func__);
|
|
|
|
unsigned int i;
|
|
|
|
/* Disable interrupts */
|
|
REG_USB_REG_INTRINE = 0;
|
|
REG_USB_REG_INTROUTE = 0;
|
|
REG_USB_REG_INTRUSBE = 0;
|
|
|
|
/* Disable DMA */
|
|
REG_USB_REG_CNTL1 = 0;
|
|
REG_USB_REG_CNTL2 = 0;
|
|
|
|
/* High speed, softconnect and suspend/resume */
|
|
REG_USB_REG_POWER = (USB_POWER_SOFTCONN | USB_POWER_HSENAB | USB_POWER_SUSPENDM);
|
|
|
|
/* Reset EP0 */
|
|
select_endpoint(0);
|
|
REG_USB_REG_CSR0 = (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SVDSETUPEND);
|
|
|
|
/* Reset other endpoints */
|
|
for(i=2; i<TOTAL_EP(); i++)
|
|
setup_endpoint(&endpoints[i]);
|
|
|
|
/* Enable interrupts */
|
|
REG_USB_REG_INTRINE |= USB_INTR_EP0;
|
|
REG_USB_REG_INTRUSBE |= USB_INTR_RESET;
|
|
|
|
usb_core_bus_reset();
|
|
}
|
|
|
|
/* Interrupt handler */
|
|
void UDC(void)
|
|
{
|
|
/* Read interrupt registers */
|
|
unsigned char intrUSB = REG_USB_REG_INTRUSB & 0x07; /* Mask SOF */
|
|
unsigned short intrIn = REG_USB_REG_INTRIN;
|
|
unsigned short intrOut = REG_USB_REG_INTROUT;
|
|
unsigned char intrDMA = REG_USB_REG_INTR;
|
|
|
|
logf("%x %x %x %x", intrUSB, intrIn, intrOut, intrDMA);
|
|
|
|
/* EPIN & EPOUT are all handled in DMA */
|
|
if(intrIn & USB_INTR_EP0)
|
|
EP0_handler();
|
|
if(intrIn & USB_INTR_INEP1)
|
|
EPIN_handler(1);
|
|
if(intrIn & USB_INTR_INEP2)
|
|
EPIN_handler(2);
|
|
if(intrOut & USB_INTR_OUTEP1)
|
|
EPOUT_handler(1);
|
|
if(intrOut & USB_INTR_OUTEP2)
|
|
EPOUT_handler(2);
|
|
if(intrUSB & USB_INTR_RESET)
|
|
udc_reset();
|
|
if(intrUSB & USB_INTR_SUSPEND)
|
|
{
|
|
logf("USB suspend");
|
|
}
|
|
if(intrUSB & USB_INTR_RESUME)
|
|
{
|
|
logf("USB resume");
|
|
}
|
|
if(intrDMA & USB_INTR_DMA_BULKIN)
|
|
EPDMA_handler(USB_INTR_DMA_BULKIN);
|
|
if(intrDMA & USB_INTR_DMA_BULKOUT)
|
|
EPDMA_handler(USB_INTR_DMA_BULKOUT);
|
|
}
|
|
|
|
bool usb_drv_stalled(int endpoint, bool in)
|
|
{
|
|
endpoint &= 0x7F;
|
|
|
|
logf("%s(%d, %s)", __func__, endpoint, in?"IN":"OUT");
|
|
|
|
select_endpoint(endpoint);
|
|
|
|
if(endpoint == EP_CONTROL)
|
|
return (REG_USB_REG_CSR0 & USB_CSR0_SENDSTALL) != 0;
|
|
else
|
|
{
|
|
if(in)
|
|
return (REG_USB_REG_INCSR & USB_INCSR_SENDSTALL) != 0;
|
|
else
|
|
return (REG_USB_REG_OUTCSR & USB_OUTCSR_SENDSTALL) != 0;
|
|
}
|
|
}
|
|
|
|
void usb_drv_stall(int endpoint, bool stall, bool in)
|
|
{
|
|
endpoint &= 0x7F;
|
|
|
|
logf("%s(%d,%s,%s)", __func__, endpoint, stall?"Y":"N", in?"IN":"OUT");
|
|
|
|
select_endpoint(endpoint);
|
|
|
|
if(endpoint == EP_CONTROL)
|
|
{
|
|
if(stall)
|
|
REG_USB_REG_CSR0 |= USB_CSR0_SENDSTALL;
|
|
else
|
|
REG_USB_REG_CSR0 &= ~USB_CSR0_SENDSTALL;
|
|
}
|
|
else
|
|
{
|
|
if(in)
|
|
{
|
|
if(stall)
|
|
REG_USB_REG_INCSR |= USB_INCSR_SENDSTALL;
|
|
else
|
|
REG_USB_REG_INCSR = (REG_USB_REG_INCSR & ~USB_INCSR_SENDSTALL) | USB_INCSR_CDT;
|
|
}
|
|
else
|
|
{
|
|
if(stall)
|
|
REG_USB_REG_OUTCSR |= USB_OUTCSR_SENDSTALL;
|
|
else
|
|
REG_USB_REG_OUTCSR = (REG_USB_REG_OUTCSR & ~USB_OUTCSR_SENDSTALL) | USB_OUTCSR_CDT;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define GPIO_UDC_DETE (32 * 3 + 28) /* A18 = ADP_CHK */
|
|
#define IRQ_GPIO_UDC_DETE (IRQ_GPIO_0 + GPIO_UDC_DETE)
|
|
#ifndef ONDA_VX767
|
|
# define USB_GPIO_IRQ GPIO124
|
|
#endif
|
|
|
|
int usb_detect(void)
|
|
{
|
|
return (__gpio_get_pin(GPIO_UDC_DETE) == 1)
|
|
? USB_INSERTED : USB_EXTRACTED;
|
|
}
|
|
|
|
void usb_init_device(void)
|
|
{
|
|
#ifdef ONDA_VX767
|
|
REG_GPIO_PXFUNS(3) = 0x10000000;
|
|
REG_GPIO_PXSELS(3) = 0x10000000;
|
|
REG_GPIO_PXPES(3) = 0x10000000;
|
|
__gpio_as_input(GPIO_UDC_DETE);
|
|
#else
|
|
REG_GPIO_PXPES(3) = 0x10000000;
|
|
__gpio_as_irq_rise_edge(GPIO_UDC_DETE);
|
|
#endif
|
|
|
|
#ifdef USB_GPIO_IRQ
|
|
system_enable_irq(IRQ_GPIO_UDC_DETE);
|
|
#endif
|
|
system_enable_irq(IRQ_UDC);
|
|
|
|
for(unsigned i=0; i<TOTAL_EP(); i++)
|
|
semaphore_init(&endpoints[i].complete, 1, 0);
|
|
}
|
|
|
|
#ifdef USB_GPIO_IRQ
|
|
static unsigned long last_tick;
|
|
void USB_GPIO_IRQ(void)
|
|
{
|
|
/* Prevent enabled-disabled bouncing */
|
|
if(current_tick - last_tick > HZ/16)
|
|
{
|
|
usb_status_event(usb_detect());
|
|
last_tick = current_tick;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void usb_enable(bool on)
|
|
{
|
|
if(on)
|
|
usb_core_init();
|
|
else
|
|
usb_core_exit();
|
|
}
|
|
|
|
void usb_attach(void)
|
|
{
|
|
usb_enable(true);
|
|
}
|
|
|
|
void usb_drv_init(void)
|
|
{
|
|
logf("%s()", __func__);
|
|
|
|
/* Set this bit to allow the UDC entering low-power mode when
|
|
* there are no actions on the USB bus.
|
|
* UDC still works during this bit was set.
|
|
*/
|
|
//__cpm_stop_udc();
|
|
__cpm_start_udc();
|
|
|
|
/* Enable the USB PHY */
|
|
REG_CPM_SCR |= CPM_SCR_USBPHY_ENABLE;
|
|
|
|
/* Dis- and reconnect from USB */
|
|
REG_USB_REG_POWER &= ~USB_POWER_SOFTCONN;
|
|
REG_USB_REG_POWER |= USB_POWER_SOFTCONN;
|
|
|
|
udc_reset();
|
|
}
|
|
|
|
void usb_drv_exit(void)
|
|
{
|
|
logf("%s()", __func__);
|
|
|
|
/* Disable interrupts */
|
|
REG_USB_REG_INTRINE = 0;
|
|
REG_USB_REG_INTROUTE = 0;
|
|
REG_USB_REG_INTRUSBE = 0;
|
|
|
|
/* Disable DMA */
|
|
REG_USB_REG_CNTL1 = 0;
|
|
REG_USB_REG_CNTL2 = 0;
|
|
|
|
/* Disconnect from USB */
|
|
REG_USB_REG_POWER &= ~USB_POWER_SOFTCONN;
|
|
|
|
/* Disable the USB PHY */
|
|
REG_CPM_SCR &= ~CPM_SCR_USBPHY_ENABLE;
|
|
|
|
__cpm_stop_udc();
|
|
}
|
|
|
|
void usb_drv_set_address(int address)
|
|
{
|
|
logf("%s(%d)", __func__, address);
|
|
|
|
REG_USB_REG_FADDR = address;
|
|
}
|
|
|
|
static void usb_drv_send_internal(struct usb_endpoint* ep, void* ptr, int length, bool blocking)
|
|
{
|
|
if(ep->type == ep_control && ptr == NULL && length == 0)
|
|
return; /* ACK request, handled in the ISR */
|
|
|
|
int flags = disable_irq_save();
|
|
|
|
ep->buf = ptr;
|
|
ep->sent = 0;
|
|
ep->length = length;
|
|
ep->busy = true;
|
|
if(blocking)
|
|
ep->wait = true;
|
|
|
|
if(ep->type == ep_control)
|
|
{
|
|
EP0_send();
|
|
}
|
|
else
|
|
{
|
|
if(ep->use_dma)
|
|
{
|
|
commit_discard_dcache_range(ptr, length);
|
|
REG_USB_REG_ADDR1 = PHYSADDR((unsigned long)ptr);
|
|
REG_USB_REG_COUNT1 = length;
|
|
REG_USB_REG_CNTL1 = (USB_CNTL_INTR_EN | USB_CNTL_MODE_1 |
|
|
USB_CNTL_DIR_IN | USB_CNTL_ENA |
|
|
USB_CNTL_EP(EP_NUMBER2(ep)) | USB_CNTL_BURST_16);
|
|
}
|
|
else
|
|
EPIN_handler(EP_NUMBER2(ep));
|
|
}
|
|
|
|
restore_irq(flags);
|
|
|
|
if(blocking)
|
|
{
|
|
semaphore_wait(&ep->complete, TIMEOUT_BLOCK);
|
|
ep->wait = false;
|
|
}
|
|
}
|
|
|
|
int usb_drv_send_nonblocking(int endpoint, void* ptr, int length)
|
|
{
|
|
logf("%s(%d, 0x%x, %d)", __func__, endpoint, (int)ptr, length);
|
|
|
|
usb_drv_send_internal(&endpoints[(endpoint & 0x7F)*2], ptr, length, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_drv_send(int endpoint, void* ptr, int length)
|
|
{
|
|
logf("%s(%d, 0x%x, %d)", __func__, endpoint, (int)ptr, length);
|
|
|
|
usb_drv_send_internal(&endpoints[(endpoint & 0x7F)*2], ptr, length, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length)
|
|
{
|
|
int flags;
|
|
struct usb_endpoint *ep;
|
|
endpoint &= 0x7F;
|
|
|
|
logf("%s(%d, 0x%x, %d)", __func__, endpoint, (int)ptr, length);
|
|
|
|
if(endpoint == EP_CONTROL)
|
|
return 0; /* all EP0 OUT transactions are handled within the ISR */
|
|
else
|
|
{
|
|
flags = disable_irq_save();
|
|
ep = &endpoints[endpoint*2+1];
|
|
|
|
ep->buf = ptr;
|
|
ep->received = 0;
|
|
ep->length = length;
|
|
ep->busy = true;
|
|
if(ep->use_dma)
|
|
{
|
|
discard_dcache_range(ptr, length);
|
|
REG_USB_REG_ADDR2 = PHYSADDR((unsigned long)ptr);
|
|
REG_USB_REG_COUNT2 = length;
|
|
REG_USB_REG_CNTL2 = (USB_CNTL_INTR_EN | USB_CNTL_MODE_1 |
|
|
USB_CNTL_ENA | USB_CNTL_EP(endpoint) |
|
|
USB_CNTL_BURST_16);
|
|
}
|
|
else
|
|
EPOUT_handler(endpoint);
|
|
|
|
restore_irq(flags);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void usb_drv_set_test_mode(int mode)
|
|
{
|
|
logf("%s(%d)", __func__, mode);
|
|
|
|
switch(mode)
|
|
{
|
|
case 0:
|
|
REG_USB_REG_TESTMODE &= ~USB_TEST_ALL;
|
|
break;
|
|
case 1:
|
|
REG_USB_REG_TESTMODE |= USB_TEST_J;
|
|
break;
|
|
case 2:
|
|
REG_USB_REG_TESTMODE |= USB_TEST_K;
|
|
break;
|
|
case 3:
|
|
REG_USB_REG_TESTMODE |= USB_TEST_SE0NAK;
|
|
break;
|
|
case 4:
|
|
REG_USB_REG_TESTMODE |= USB_TEST_PACKET;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int usb_drv_port_speed(void)
|
|
{
|
|
return (REG_USB_REG_POWER & USB_POWER_HSMODE) ? 1 : 0;
|
|
}
|
|
|
|
void usb_drv_cancel_all_transfers(void)
|
|
{
|
|
logf("%s()", __func__);
|
|
|
|
unsigned int i, flags;
|
|
flags = disable_irq_save();
|
|
|
|
for(i=0; i<TOTAL_EP(); i++)
|
|
{
|
|
if(i != 1) /* ep0 out needs special handling */
|
|
endpoints[i].buf = NULL;
|
|
|
|
endpoints[i].sent = 0;
|
|
endpoints[i].length = 0;
|
|
|
|
select_endpoint(i/2);
|
|
flushFIFO(&endpoints[i]);
|
|
}
|
|
restore_irq(flags);
|
|
}
|
|
|
|
void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep)
|
|
{
|
|
/* FIXME: support max packet size override */
|
|
(void)ctx;
|
|
(void)ep;
|
|
}
|
|
|
|
void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep)
|
|
{
|
|
(void)ctx;
|
|
(void)ep;
|
|
}
|