Revision:
0:63d45df56584
Child:
1:4461071ed964
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UsbHostController.cpp	Sun Jul 08 20:18:58 2012 +0000
@@ -0,0 +1,439 @@
+/*
+    A host controller driver from the mBed device.
+    Copyright (C) 2012  Richard e Collins - richard.collins@linux.com
+
+    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 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include <mbed.h>
+#include <stddef.h>
+#include <assert.h>
+
+#include "UsbHostController.h"
+#include "HardwareDefines.h"
+#include "Debug.h"
+#include "UsbStructures.h"
+#include "UsbEnums.h"
+
+#define HOST_CLK_EN        (1<<0)
+#define PORTSEL_CLK_EN    (1<<3)
+#define AHB_CLK_EN        (1<<4)
+#define CLOCK_MASK        (HOST_CLK_EN | PORTSEL_CLK_EN | AHB_CLK_EN)
+
+#define HOST_CONTROLLER_COMMUNICATIONS_AREA_ADDRESS (0x2007C000)
+
+/*
+ * The max number of connected devices I support.
+ */
+#define MAX_DEVICES 8
+
+//Do #if 0 to enable debug log on the host controller driver.
+#if 0
+#undef DEBUG
+#define  DEBUG(...)       do {} while(0)
+#endif
+
+namespace USB
+{
+
+/*
+ * Does not matter where this is allocated, later will make it come after all the important stuff in the first block of extra sram so the lib takes no main ram from the user.
+ */
+static HostController _controller;
+
+/**
+ * Flash a light so I get too see stuff changing.
+ */
+static DigitalOut irqTick(LED2);
+
+
+/*
+ * Handle interupts from the USB.
+ * Some parts of this are taken from the official documentation but I was not !00% sure why
+ * they did what they did. Lack of comments in their code or documentation.
+ *
+ * When the HC reaches the end of a frame and
+ * its deferred interrupt register is 0, it writes the
+ * current value of its HcDoneHead to this
+ * location and generates an interrupt if interrupts
+ * are enabled. This location is not written by the
+ * HC again until software clears the WD bit in
+ * the HcInterruptStatus register.
+ * The LSb of this entry is set to 1 to indicate
+ * whether an unmasked HcInterruptStatus was
+ * set when HccaDoneHead was written.
+ */
+extern "C" void USB_IRQHandler(void) __irq;
+void USB_IRQHandler (void) __irq
+{
+    // It is our interrupt, prevent HC from doing it to us again until we are finished
+    LPC_USB->HcInterruptDisable = MasterInterruptEnable;
+
+    //Writing back the interupts we have handled tells the system we did them.
+    //So for now say I done them all. ;)
+    //If we don't do this the system locks up.
+    //I guess because once we renable the interupts they refire so the main thread never gets a chance to runs again.
+    //It is done via the return value to stop the compiler from optimising it out, which some may do if you are not carful.   
+    LPC_USB->HcInterruptStatus = _controller.USBInterupt(LPC_USB->HcInterruptStatus); //Process the interupt.
+    
+    // It is our interrupt, prevent HC from doing it to us again until we are finished
+    LPC_USB->HcInterruptEnable = MasterInterruptEnable;
+}
+
+/*
+ * Called after we have recived a status changed intterupt from the USB.
+ */
+static void QueueRootHubStatusCheckCallback()
+{
+    DEBUG("Resetting hub port after connect");
+    //Reset the port ready for using the connected device.
+    //ROOTHUB_PORT_RESET_STATUS_CHANGE bit will be set when done.
+    LPC_USB->HcRhPortStatus1 = ROOTHUB_PORT_RESET_STATUS;
+}
+
+/**
+ * Once started never stopped, as the memory is in reservers space there is nothing gained in stopping it.
+ */
+HostController *HostController::get()
+{
+    return &_controller;
+}
+
+
+/*
+ * Process the interupt that just came in.
+ */
+uint32_t HostController::USBInterupt(const uint32_t interruptStatus)
+{
+    uint32_t servicedInterrupt = 0;
+    if( interruptStatus & INTERRUPT_UNRECOVERABLE_ERROR )
+    {
+        DEBUG("USB Controller has failed, please reset.");
+        servicedInterrupt |= INTERRUPT_UNRECOVERABLE_ERROR;
+    }
+    
+    //Have we recived and data?
+    if( interruptStatus & INTERRUPT_WRITEBACK_HEAD_DONE )
+    {//Process all the recived packets.
+        TransferDescriptor *transfer = commsArea->doneHead;
+        commsArea->doneHead = 0;
+        while( transfer != NULL )
+        {
+            messageCallback->TransferDone(transfer);
+            TransferDescriptor *next = (TransferDescriptor *)transfer->nextTD;
+            FreeMemoryPoolItem(transfer);
+            transfer = next;
+            DEBUG("TransferDescriptor freed");
+        }
+    
+        //All done re enable the interupt.
+        servicedInterrupt |= INTERRUPT_WRITEBACK_HEAD_DONE;
+    }
+    
+    if( interruptStatus & INTERRUPT_ROOTHUB_STATUS_CHANGE )
+    {
+        hubPortStatus = LPC_USB->HcRhPortStatus1;
+        if( hubPortStatus & ROOTHUB_CONNECT_STATUS_CHANGE )
+        {
+            if(hubPortStatus & ROOTHUB_CURRENT_CONNECT_STATUS)
+            {
+                DEBUG("Resetting hub port after connect");
+                //Reset the port ready for using the connected device.
+                //ROOTHUB_PORT_RESET_STATUS_CHANGE bit will be set when done.
+                delayedRootHubStatusChangeTimeout.attach(QueueRootHubStatusCheckCallback,0.5f);
+            }
+            else
+            {
+                DEBUG("Disconnected");
+            }
+        }
+        else if( (hubPortStatus&(ROOTHUB_PORT_RESET_STATUS_CHANGE|ROOTHUB_PORT_RESET_STATUS)) == ROOTHUB_PORT_RESET_STATUS_CHANGE )
+        {//Deal with port reset status.
+            flags.newDeviceConnected = 1;
+        }
+        LPC_USB->HcRhPortStatus1 = hubPortStatus & 0xffff0000;
+
+        servicedInterrupt |= INTERRUPT_ROOTHUB_STATUS_CHANGE;
+    }
+    
+    if( interruptStatus & INTERRUPT_FRAME_NUMBER_OVERFLOW )
+    {
+        DEBUG("FRAME_NUMBER_OVERFLOW");
+        servicedInterrupt |= INTERRUPT_FRAME_NUMBER_OVERFLOW;
+    }
+
+    irqTick = !irqTick;//Show we got an interupt.
+    
+    return servicedInterrupt;
+}
+
+/*
+ * Get it all going, I did want this to be done in the constructor but the chip did not like the USB starting before main was called.
+ */
+void HostController::Init(HostControllerMessages *messageCallback,Device*& deviceZero)
+{
+    this->messageCallback = messageCallback;
+
+    commsArea = (HostControllerCommunicationsArea*)HOST_CONTROLLER_COMMUNICATIONS_AREA_ADDRESS;
+    
+    DEBUG("Starting USB Host Controller Driver.");
+    DEBUG("sizeof(UsbHost::Controller) == %d",sizeof(HostController));
+    DEBUG("sizeof(SetupPacket) == %d",sizeof(SetupPacket));
+    DEBUG("%d %d %d %d %d",offsetof(SetupPacket,requestType),offsetof(SetupPacket,request),offsetof(SetupPacket,value),offsetof(SetupPacket,index),offsetof(SetupPacket,length));
+    
+    DEBUG("sizeof(HostControllerCommunicationsArea) == %d",sizeof(HostControllerCommunicationsArea));
+    DEBUG("sizeof(EndpointDescriptor) == %d",sizeof(EndpointDescriptor));
+    DEBUG("sizeof(TransferDescriptor) == %d",sizeof(TransferDescriptor));
+    DEBUG("sizeof(LinkedListItem) == %d",sizeof(LinkedListItem));
+    DEBUG("Memory pool size == %d",sizeof(commsArea->memoryPool));
+    
+    memset(commsArea,0,sizeof(HostControllerCommunicationsArea));
+    
+    deviceZero = &commsArea->deviceZero;
+    
+    //Init the memory pool, just creates a linked list of free objects.
+    for( int n = 1 ; n < (sizeof(commsArea->memoryPool) / sizeof(commsArea->memoryPool[0])) ; n++ )
+    {
+        commsArea->memoryPool[n-1].next = commsArea->memoryPool + n;
+    }
+    memoryPoolHead = commsArea->memoryPool;
+    numAllocatedPoolItems = 0;
+        
+    DEBUG("Disabling USB_IRQn");
+    NVIC_DisableIRQ(USB_IRQn);
+    
+    // Turn on USB interface power/clock control bit in PCONP (Power Control for Peripherals register)
+    LPC_SC->PCONP        |= PCUSB;//USB interface power/clock control bit. 
+    
+    // Enable USB host clock, port selection and AHB clock
+    LPC_USB->USBClkCtrl |= CLOCK_MASK;
+    
+    // Wait for clocks to become available
+    while ((LPC_USB->USBClkSt & CLOCK_MASK) != CLOCK_MASK);
+    
+    //    We are a Host
+    LPC_USB->OTGStCtrl |= 1;
+    LPC_USB->USBClkCtrl &= ~PORTSEL_CLK_EN;                // we don't need port selection clock until we do OTG
+
+    // configure USB pins
+    LPC_PINCON->PINSEL1 &= ~((3<<26)|(3<<28));    
+    LPC_PINCON->PINSEL1 |=    ((1<<26)|(1<<28));            // USB D+/D-
+    
+    LPC_PINCON->PINSEL3 &= ~((3 << 6) | (3 << 22));        // USB_PPWR, USB_OVRCR
+    LPC_PINCON->PINSEL3 |= ((2 << 6) | (2 << 22));
+    
+    LPC_PINCON->PINSEL4 &= ~(3 << 18);                    // USB_CONNECT
+    LPC_PINCON->PINSEL4 |= (1 << 18);
+    
+    //    Reset OHCI block
+    LPC_USB->HcControl         = 0;
+    LPC_USB->HcControlHeadED = 0;
+    LPC_USB->HcBulkHeadED     = 0;
+    
+    LPC_USB->HcCommandStatus = HostControllerReset;
+    LPC_USB->HcFmInterval     = DEFAULT_FMINTERVAL;
+    LPC_USB->HcPeriodicStart = FRAMEINTERVAL*90/100;
+    
+    LPC_USB->HcControl    = (LPC_USB->HcControl & (~HostControllerFunctionalState)) | OperationalMask;
+    LPC_USB->HcRhStatus = SetGlobalPower;
+    
+    LPC_USB->HcHCCA = (uint32_t)commsArea;
+    LPC_USB->HcInterruptStatus |= LPC_USB->HcInterruptStatus;
+    LPC_USB->HcInterruptEnable = INTERRUPT_MASTER_INTERRUPT_ENABLE | INTERRUPT_WRITEBACK_HEAD_DONE | INTERRUPT_ROOTHUB_STATUS_CHANGE | INTERRUPT_UNRECOVERABLE_ERROR | INTERRUPT_FRAME_NUMBER_OVERFLOW;
+    
+    DEBUG("Enabling USB_IRQn");
+    NVIC_SetPriority(USB_IRQn, 0);
+    NVIC_EnableIRQ(USB_IRQn);
+    
+    wait(1);//Without this things can go a bit wonky because of the above playing with IRQ's. ( I think )
+    
+    DEBUG("Host controller communication area (HcHCCA) set to 0x%08x",LPC_USB->HcHCCA);
+}
+
+/*
+ * Service the events from the interupts and the results of interupt transfers.
+ */
+void HostController::Update()
+{
+    irqTick = 0;//Clear this, may have been left on after an interupt.
+    if( flags.newDeviceConnected )
+    {
+        DEBUG("AddDevice START");
+        messageCallback->AddDevice(0,1,hubPortStatus);//Root hub is always hub zero, port 1. So add the device at this location.
+        DEBUG("AddDevice DONE");
+        flags.newDeviceConnected = 0;
+    }
+}
+
+void *HostController::AllocateMemoryPoolItem()
+{
+    if( memoryPoolHead == NULL )
+    {
+        return NULL;
+    }
+    LinkedListItem* f = memoryPoolHead;
+    memoryPoolHead = memoryPoolHead->next;
+    numAllocatedPoolItems++;
+    DEBUG("numAllocatedPoolItems (%d)",numAllocatedPoolItems);
+    
+    //Clear it.
+    ((uint32_t*)f)[0] = 0;
+    ((uint32_t*)f)[1] = 0;
+    ((uint32_t*)f)[2] = 0;
+    ((uint32_t*)f)[3] = 0;
+    
+    return (void*)f;
+}
+    
+void HostController::FreeMemoryPoolItem(void *item)
+{
+    assert(numAllocatedPoolItems > 0);
+    LinkedListItem* f = (LinkedListItem*)item;
+    f->next = memoryPoolHead;
+    memoryPoolHead = f;
+    numAllocatedPoolItems--;
+    DEBUG("numAllocatedPoolItems (%d)",numAllocatedPoolItems);
+}
+
+/**
+ * functionAddress  This is our deviceID. We tell the controller the ID we are using when it is inserted.
+ *                  The docs call it the 'functionAddress'.
+ * If the endpoint has need been used before it will not be linked into the list on the device. In that case we allocate it and add it.
+ * When the device is removed all the enpoints are removed from the system.
+ */
+EndpointDescriptor *HostController::GetEndpointDescriptor(EndpointDescriptor** headED,uint32_t functionAddress,int endpointNumber,int maximumPacketSize,int lowspeed)
+{
+    uint32_t control = (maximumPacketSize<<16) | (lowspeed<<13) | (endpointNumber<<7) | functionAddress;
+    
+    //have we already allocated and linked in the end point?
+    EndpointDescriptor* ep = *headED;
+    while( ep != NULL )
+    {
+        if( ep->control == control )
+        {
+            return ep;
+        }
+        ep = ep->NextED;
+    }
+
+    //Get here and we have not see the endpoint before.
+    //So we need to allocate it and link it in.
+    ep = (EndpointDescriptor*)AllocateMemoryPoolItem();
+    
+    if( ep == NULL )
+    {
+        DEBUG("AllocateEndpoint failed");
+        return NULL;
+    }
+    
+    //Link the endpoint onto the list of things to do the next frame when ControlListFilled of HcCommandStatus is set.    
+    //When the host controller reaches the end of the list pointed to by HcControlCurrentED
+    //It checks the ControlListEnable flag, if set then HcControlHeadED is copied to HcControlCurrentED and starts the new transfers.
+    ep->NextED = *headED;
+    *headED = ep;
+
+    ep->control = control;//Set the address etc...
+
+    DEBUG("endpoint allocated ep(%d) deviceID(%d)",endpointNumber,functionAddress);
+
+    return ep;
+}
+
+
+TransferDescriptor *HostController::AllocateTransferDescriptor(int deviceID,int direction,int dataToggle,const uint8_t* data,int dataLength)
+{
+    TransferDescriptor* td = (TransferDescriptor*)AllocateMemoryPoolItem();
+    
+    td->deviceID = (uint8_t)deviceID;//So we know who sent this transfer.
+    td->bufferRounding = 1;//No rounding needed
+    td->direction = direction;
+    td->dataToggle = dataToggle;//DATA0 and use this field for the toggle value. MSb states use this filed, LSb is zero as it's DATA0
+    td->conditionCode = 15;
+    if( data != NULL )
+    {
+        td->CurrentBufferPointer = data;
+        td->bufferEnd = (data + dataLength - 1);//Points to the last byte.
+    }
+    DEBUG("transfer desc allocated %08x",*((uint32_t*)td));
+    return td;
+}
+
+
+
+
+
+void HostController::QueueControlTransferStage(int deviceID,int direction,int endpointNumber,int maximumPacketSize,int lowspeed,int dataToggle,const uint8_t* data,int dataLength)
+{
+    //If endpoint zero then toggle is always DATA0 for setup packet and DATA1 for the rest.
+    if( endpointNumber == 0 )
+    {
+        dataToggle = direction == TDD_SETUP ? TOGGLE_DATA0 : TOGGLE_DATA1;
+    }
+
+    TransferDescriptor* td = AllocateTransferDescriptor(deviceID,direction,dataToggle,data,dataLength);
+    
+    //Get the endpoint needed for this transfer.
+    EndpointDescriptor* ep = GetEndpointDescriptor(((EndpointDescriptor**)&LPC_USB->HcControlHeadED),deviceID,endpointNumber,maximumPacketSize,lowspeed);
+    
+    td->nextTD = &commsArea->commonTail;
+    ep->TailTD = &commsArea->commonTail;
+    ep->HeadTD = td;
+    ep->NextED = NULL;
+
+
+    LPC_USB->HcCommandStatus = LPC_USB->HcCommandStatus | ControlListFilled;//Tell the host of new things to do.
+    LPC_USB->HcControl = LPC_USB->HcControl | ControlListEnable;//Make sure list processing is on.
+}
+
+uint8_t* HostController::getScratchRam()
+{
+    return commsArea->scratchRam;
+}
+
+void HostController::DetachDevice(int deviceID)
+{
+    RemoveEndpointsForDevice(deviceID,(EndpointDescriptor**)&LPC_USB->HcControlHeadED);
+    RemoveEndpointsForDevice(deviceID,(EndpointDescriptor**)&LPC_USB->HcBulkHeadED);
+    for( int n = 0 ; n < 32 ; n++ )
+    {
+        RemoveEndpointsForDevice(deviceID,(EndpointDescriptor**)&commsArea->interruptTable[n]);
+    }
+}
+
+void HostController::RemoveEndpointsForDevice(int deviceID,EndpointDescriptor **list)
+{
+    while( (*list) != NULL )
+    {
+        if( ( (*(list))->control&0x7f) == deviceID )
+        {
+            EndpointDescriptor* old = *list;
+            *list = old->NextED;//Unlink old.
+            //Should not be any transfers to do this this EP if we are removing it...... TODO Check this!
+            if( old->HeadTD != &commsArea->commonTail )
+            {
+                DEBUG("Removing endpoint with unfinished transfers, please fix bug richard!");
+            }
+            DEBUG("Freed EP for device(%d)",deviceID);
+            FreeMemoryPoolItem(old);
+        }
+        else
+        {
+            list = &(*(list))->NextED;//Move to next.
+        }
+    }
+}
+
+};//namespace USB
+