/* 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) && (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 |= (1 << 31); 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; }