/* | |
LPCUSB, an USB device driver for LPC microcontrollers | |
Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. 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. | |
3. The name of the author may not be used to endorse or promote products | |
derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
IN NO EVENT SHALL THE AUTHOR 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. | |
*/ | |
/** @file | |
USB hardware layer | |
*/ | |
#include "usbdebug.h" | |
#include "usbhw_lpc.h" | |
#include "usbapi.h" | |
/** Installed device interrupt handler */ | |
static TFnDevIntHandler *_pfnDevIntHandler = NULL; | |
/** Installed endpoint interrupt handlers */ | |
static TFnEPIntHandler *_apfnEPIntHandlers[16]; | |
/** Installed frame interrupt handlers */ | |
static TFnFrameHandler *_pfnFrameHandler = NULL; | |
/** convert from endpoint address to endpoint index */ | |
#define EP2IDX(bEP) ((((bEP)&0xF)<<1)|(((bEP)&0x80)>>7)) | |
/** convert from endpoint index to endpoint address */ | |
#define IDX2EP(idx) ((((idx)<<7)&0x80)|(((idx)>>1)&0xF)) | |
/** | |
Local function to wait for a device interrupt (and clear it) | |
@param [in] dwIntr Bitmask of interrupts to wait for | |
*/ | |
static void Wait4DevInt(unsigned long dwIntr) | |
{ | |
// wait for specific interrupt | |
while ((USB->USBDevIntSt & dwIntr) != dwIntr); | |
// clear the interrupt bits | |
USB->USBDevIntClr = dwIntr; | |
} | |
/** | |
Local function to send a command to the USB protocol engine | |
@param [in] bCmd Command to send | |
*/ | |
static void USBHwCmd(unsigned char bCmd) | |
{ | |
// clear CDFULL/CCEMTY | |
USB->USBDevIntClr = CDFULL | CCEMTY; | |
// write command code | |
USB->USBCmdCode = 0x00000500 | (bCmd << 16); | |
Wait4DevInt(CCEMTY); | |
} | |
/** | |
Local function to send a command + data to the USB protocol engine | |
@param [in] bCmd Command to send | |
@param [in] bData Data to send | |
*/ | |
static void USBHwCmdWrite(unsigned char bCmd, unsigned short bData) | |
{ | |
// write command code | |
USBHwCmd(bCmd); | |
// write command data | |
USB->USBCmdCode = 0x00000100 | (bData << 16); | |
Wait4DevInt(CCEMTY); | |
} | |
/** | |
Local function to send a command to the USB protocol engine and read data | |
@param [in] bCmd Command to send | |
@return the data | |
*/ | |
static unsigned char USBHwCmdRead(unsigned char bCmd) | |
{ | |
// write command code | |
USBHwCmd(bCmd); | |
// get data | |
USB->USBCmdCode = 0x00000200 | (bCmd << 16); | |
Wait4DevInt(CDFULL); | |
return USB->USBCmdData; | |
} | |
/** | |
'Realizes' an endpoint, meaning that buffer space is reserved for | |
it. An endpoint needs to be realised before it can be used. | |
From experiments, it appears that a USB reset causes USBReEP to | |
re-initialise to 3 (= just the control endpoints). | |
However, a USB bus reset does not disturb the USBMaxPSize settings. | |
@param [in] idx Endpoint index | |
@param [in] wMaxPSize Maximum packet size for this endpoint | |
*/ | |
static void USBHwEPRealize(int idx, unsigned short wMaxPSize) | |
{ | |
USB->USBReEP |= (1 << idx); | |
USB->USBEpInd = idx; | |
USB->USBMaxPSize = wMaxPSize; | |
Wait4DevInt(EP_RLZED); | |
} | |
/** | |
Enables or disables an endpoint | |
@param [in] idx Endpoint index | |
@param [in] fEnable TRUE to enable, FALSE to disable | |
*/ | |
static void USBHwEPEnable(int idx, BOOL fEnable) | |
{ | |
USBHwCmdWrite(CMD_EP_SET_STATUS | idx, fEnable ? 0 : EP_DA); | |
} | |
/** | |
Configures an endpoint and enables it | |
@param [in] bEP Endpoint number | |
@param [in] wMaxPacketSize Maximum packet size for this EP | |
*/ | |
void USBHwEPConfig(unsigned char bEP, unsigned short wMaxPacketSize) | |
{ | |
int idx; | |
idx = EP2IDX(bEP); | |
// realise EP | |
USBHwEPRealize(idx, wMaxPacketSize); | |
// enable EP | |
USBHwEPEnable(idx, TRUE); | |
} | |
/** | |
Registers an endpoint event callback | |
@param [in] bEP Endpoint number | |
@param [in] pfnHandler Callback function | |
*/ | |
void USBHwRegisterEPIntHandler(unsigned char bEP, TFnEPIntHandler *pfnHandler) | |
{ | |
int idx; | |
idx = EP2IDX(bEP); | |
ASSERT(idx<32); | |
/* add handler to list of EP handlers */ | |
_apfnEPIntHandlers[idx / 2] = pfnHandler; | |
/* enable EP interrupt */ | |
USB->USBEpIntEn |= (1 << idx); | |
USB->USBDevIntEn |= EP_SLOW; | |
DBG("Registered handler for EP 0x%x\n", bEP); | |
} | |
/** | |
Registers an device status callback | |
@param [in] pfnHandler Callback function | |
*/ | |
void USBHwRegisterDevIntHandler(TFnDevIntHandler *pfnHandler) | |
{ | |
_pfnDevIntHandler = pfnHandler; | |
// enable device interrupt | |
USB->USBDevIntEn |= DEV_STAT; | |
DBG("Registered handler for device status\n"); | |
} | |
/** | |
Registers the frame callback | |
@param [in] pfnHandler Callback function | |
*/ | |
void USBHwRegisterFrameHandler(TFnFrameHandler *pfnHandler) | |
{ | |
_pfnFrameHandler = pfnHandler; | |
// enable device interrupt | |
USB->USBDevIntEn |= FRAME; | |
DBG("Registered handler for frame\n"); | |
} | |
/** | |
Sets the USB address. | |
@param [in] bAddr Device address to set | |
*/ | |
void USBHwSetAddress(unsigned char bAddr) | |
{ | |
USBHwCmdWrite(CMD_DEV_SET_ADDRESS, DEV_EN | bAddr); | |
} | |
/** | |
Connects or disconnects from the USB bus | |
@param [in] fConnect If TRUE, connect, otherwise disconnect | |
*/ | |
void USBHwConnect(BOOL fConnect) | |
{ | |
USBHwCmdWrite(CMD_DEV_STATUS, fConnect ? CON : 0); | |
} | |
/** | |
Enables interrupt on NAK condition | |
For IN endpoints a NAK is generated when the host wants to read data | |
from the device, but none is available in the endpoint buffer. | |
For OUT endpoints a NAK is generated when the host wants to write data | |
to the device, but the endpoint buffer is still full. | |
The endpoint interrupt handlers can distinguish regular (ACK) interrupts | |
from NAK interrupt by checking the bits in their bEPStatus argument. | |
@param [in] bIntBits Bitmap indicating which NAK interrupts to enable | |
*/ | |
void USBHwNakIntEnable(unsigned char bIntBits) | |
{ | |
USBHwCmdWrite(CMD_DEV_SET_MODE, bIntBits); | |
} | |
/** | |
Gets the status from a specific endpoint. | |
@param [in] bEP Endpoint number | |
@return Endpoint status byte (containing EP_STATUS_xxx bits) | |
*/ | |
unsigned char USBHwEPGetStatus(unsigned char bEP) | |
{ | |
int idx = EP2IDX(bEP); | |
return USBHwCmdRead(CMD_EP_SELECT | idx); | |
} | |
/** | |
Sets the stalled property of an endpoint | |
@param [in] bEP Endpoint number | |
@param [in] fStall TRUE to stall, FALSE to unstall | |
*/ | |
void USBHwEPStall(unsigned char bEP, BOOL fStall) | |
{ | |
int idx = EP2IDX(bEP); | |
USBHwCmdWrite(CMD_EP_SET_STATUS | idx, fStall ? EP_ST : 0); | |
} | |
/** | |
Writes data to an endpoint buffer | |
@param [in] bEP Endpoint number | |
@param [in] pbBuf Endpoint data | |
@param [in] iLen Number of bytes to write | |
@return TRUE if the data was successfully written or <0 in case of error. | |
*/ | |
int USBHwEPWrite(unsigned char bEP, unsigned char *pbBuf, int iLen) | |
{ | |
int idx; | |
idx = EP2IDX(bEP); | |
// set write enable for specific endpoint | |
USB->USBCtrl = WR_EN | ((bEP & 0xF) << 2); | |
// set packet length | |
USB->USBTxPLen = iLen; | |
// write data | |
while (USB->USBCtrl & WR_EN) { | |
USB->USBTxData = (pbBuf[3] << 24) | (pbBuf[2] << 16) | (pbBuf[1] << 8) | pbBuf[0]; | |
pbBuf += 4; | |
} | |
// select endpoint and validate buffer | |
USBHwCmd(CMD_EP_SELECT | idx); | |
USBHwCmd(CMD_EP_VALIDATE_BUFFER); | |
return iLen; | |
} | |
/** | |
Reads data from an endpoint buffer | |
@param [in] bEP Endpoint number | |
@param [in] pbBuf Endpoint data | |
@param [in] iMaxLen Maximum number of bytes to read | |
@return the number of bytes available in the EP (possibly more than iMaxLen), | |
or <0 in case of error. | |
*/ | |
int USBHwEPRead(unsigned char bEP, unsigned char *pbBuf, int iMaxLen) | |
{ | |
unsigned int i, idx; | |
unsigned long dwData, dwLen; | |
idx = EP2IDX(bEP); | |
// set read enable bit for specific endpoint | |
USB->USBCtrl = RD_EN | ((bEP & 0xF) << 2); | |
// wait for PKT_RDY | |
do { | |
dwLen = USB->USBRxPLen; | |
} while ((dwLen & PKT_RDY) == 0); | |
// packet valid? | |
if ((dwLen & DV) == 0) { | |
return -1; | |
} | |
// get length | |
dwLen &= PKT_LNGTH_MASK; | |
// get data | |
dwData = 0; | |
for (i = 0; i < dwLen; i++) { | |
if ((i % 4) == 0) { | |
dwData = USB->USBRxData; | |
} | |
if ((pbBuf != NULL) && ((int)i < iMaxLen)) { | |
pbBuf[i] = dwData & 0xFF; | |
} | |
dwData >>= 8; | |
} | |
// make sure RD_EN is clear | |
USB->USBCtrl = 0; | |
// select endpoint and clear buffer | |
USBHwCmd(CMD_EP_SELECT | idx); | |
USBHwCmd(CMD_EP_CLEAR_BUFFER); | |
return dwLen; | |
} | |
/** | |
Sets the 'configured' state. | |
All registered endpoints are 'realised' and enabled, and the | |
'configured' bit is set in the device status register. | |
@param [in] fConfigured If TRUE, configure device, else unconfigure | |
*/ | |
void USBHwConfigDevice(BOOL fConfigured) | |
{ | |
// set configured bit | |
USBHwCmdWrite(CMD_DEV_CONFIG, fConfigured ? CONF_DEVICE : 0); | |
} | |
/** | |
USB interrupt handler | |
@todo Get all 11 bits of frame number instead of just 8 | |
Endpoint interrupts are mapped to the slow interrupt | |
*/ | |
void USBHwISR(void) | |
{ | |
unsigned long dwStatus; | |
unsigned long dwIntBit; | |
unsigned char bEPStat, bDevStat, bStat; | |
int i; | |
unsigned short wFrame; | |
// handle device interrupts | |
dwStatus = USB->USBDevIntSt; | |
// frame interrupt | |
if (dwStatus & FRAME) { | |
// clear int | |
USB->USBDevIntClr = FRAME; | |
// call handler | |
if (_pfnFrameHandler != NULL) { | |
wFrame = USBHwCmdRead(CMD_DEV_READ_CUR_FRAME_NR); | |
_pfnFrameHandler(wFrame); | |
} | |
} | |
// device status interrupt | |
if (dwStatus & DEV_STAT) { | |
/* Clear DEV_STAT interrupt before reading DEV_STAT register. | |
This prevents corrupted device status reads, see | |
LPC2148 User manual revision 2, 25 july 2006. | |
*/ | |
USB->USBDevIntClr = DEV_STAT; | |
bDevStat = USBHwCmdRead(CMD_DEV_STATUS); | |
if (bDevStat & (CON_CH | SUS_CH | RST)) { | |
// convert device status into something HW independent | |
bStat = ((bDevStat & CON) ? DEV_STATUS_CONNECT : 0) | | |
((bDevStat & SUS) ? DEV_STATUS_SUSPEND : 0) | | |
((bDevStat & RST) ? DEV_STATUS_RESET : 0); | |
// call handler | |
if (_pfnDevIntHandler != NULL) { | |
_pfnDevIntHandler(bStat); | |
} | |
} | |
} | |
// endpoint interrupt | |
if (dwStatus & EP_SLOW) { | |
// clear EP_SLOW | |
USB->USBDevIntClr = EP_SLOW; | |
// check all endpoints | |
for (i = 0; i < 32; i++) { | |
dwIntBit = (1 << i); | |
if (USB->USBEpIntSt & dwIntBit) { | |
// clear int (and retrieve status) | |
USB->USBEpIntClr = dwIntBit; | |
Wait4DevInt(CDFULL); | |
bEPStat = USB->USBCmdData; | |
// convert EP pipe stat into something HW independent | |
bStat = ((bEPStat & EPSTAT_FE) ? EP_STATUS_DATA : 0) | | |
((bEPStat & EPSTAT_ST) ? EP_STATUS_STALLED : 0) | | |
((bEPStat & EPSTAT_STP) ? EP_STATUS_SETUP : 0) | | |
((bEPStat & EPSTAT_EPN) ? EP_STATUS_NACKED : 0) | | |
((bEPStat & EPSTAT_PO) ? EP_STATUS_ERROR : 0); | |
// call handler | |
if (_apfnEPIntHandlers[i / 2] != NULL) { | |
_apfnEPIntHandlers[i / 2](IDX2EP(i), bStat); | |
} | |
} | |
} | |
} | |
} | |
/** | |
Initialises the USB hardware | |
@return TRUE if the hardware was successfully initialised | |
*/ | |
BOOL USBHwInit(void) | |
{ | |
// P2.9 -> USB_CONNECT | |
PINCON->PINSEL4 &= ~0x000C0000; | |
PINCON->PINSEL4 |= 0x00040000; | |
// P1.18 -> USB_UP_LED | |
// P1.30 -> VBUS | |
PINCON->PINSEL3 &= ~0x30000030; | |
PINCON->PINSEL3 |= 0x20000010; | |
// P0.29 -> USB_D+ | |
// P0.30 -> USB_D- | |
PINCON->PINSEL1 &= ~0x3C000000; | |
PINCON->PINSEL1 |= 0x14000000; | |
// enable PUSB | |
SC->PCONP |= (1UL << 31UL); | |
USB->OTGClkCtrl = 0x12; /* Dev clock, AHB clock enable */ | |
while ((USB->OTGClkSt & 0x12) != 0x12); | |
// disable/clear all interrupts for now | |
USB->USBDevIntEn = 0; | |
USB->USBDevIntClr = 0xFFFFFFFF; | |
USB->USBDevIntPri = 0; | |
USB->USBEpIntEn = 0; | |
USB->USBEpIntClr = 0xFFFFFFFF; | |
USB->USBEpIntPri = 0; | |
// by default, only ACKs generate interrupts | |
USBHwNakIntEnable(0); | |
return TRUE; | |
} | |