Satellite Observers Workbench. NOT yet complete, just published for forum posters to \"cherry pick\" pieces of code as requiered as an example.

Dependencies:   mbed

usbeh/usbeh_controller.cpp

Committer:
AjK
Date:
2010-10-11
Revision:
0:0a841b89d614

File content as of revision 0:0a841b89d614:

/****************************************************************************
 *    Copyright 2010 Andy Kirkham, Stellar Technologies Ltd
 *    
 *    This file is part of the Satellite Observers Workbench (SOWB).
 *
 *    SOWB 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.
 *
 *    SOWB 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 SOWB.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    $Id: main.cpp 5 2010-07-12 20:51:11Z ajk $
 *    
 ***************************************************************************/

#include "sowb.h"
#include "usbeh_endpoint.h"
#include "usbeh_device.h"
#include "usbeh_controller.h"
#include "usbeh_api.h"

#include "main.h"
#include "debug.h"

void USBEH_Controller::process(void) {
    USBEH_Endpoint *endpoint;
    USBEH_U16 elapsed = hcca.frameNumber - (USBEH_U16)this->frameNumber;
    this->frameNumber += elapsed;
    
    while (this->callbacksPending) {
        for (int i = 0; i < USBEH_MAX_ENDPOINTS_TOTAL; i++) {
            endpoint = this->endpoints + i;
            if (endpoint->currentState == USBEH_Endpoint::callbackPending) {
                this->callbacksPending--;
                endpoint->currentState = USBEH_Endpoint::idle;
                endpoint->callback(
                    endpoint->device(),
                    endpoint->address(),
                    endpoint->status(),
                    (USBEH_U08 *)endpoint->userData,
                    endpoint->length,
                    endpoint->userData);
            }
        }
    }
    
    if (this->rootHubStatusChange) {
        USBEH_U32 status = USBEH_HcRhPortStatus1;
        this->rootHubStatusChange = 0;
        if (status >> 16) {
            hubStatusChange(0, 1, status);
            USBEH_HcRhPortStatus1 = status & 0xFFFF0000;
        }
    }
    
    if (this->connectCountdown) {
        if (elapsed >= this->connectCountdown) {
            this->connectCountdown = 0;
            connect(this->connectHub, this->connectPort & 0x7F, this->connectPort & 0x80);
        }
        else {
            this->connectCountdown -= elapsed;
        }
    } 
}

void USBEH_Controller::init(void) {
    memset(this, 0, sizeof(USBEH_Controller));
    endpointZero.currentState = USBEH_Endpoint::notQueued;
    initHW(&hcca);
    delayMS(10);
}

void USBEH_Controller::hubInterrupt(int device) {
    USBEH_Device *dev = &devices[device - 1];
    
    for (int i = 0; i < dev->hubPortCount; i++) {
        int port = i + 1;
        if (dev->hubInterruptData * (1 << port)) {
            USBEH_U32 status = 0;
            usbeh_api_get_port_status(device, port, &status);
            if (status >> 16) {
                if (connectPending && (status & USBEH_CONNECT_STATUS_CHANGE)) {
                    hubStatusChange(device, port, status);
                    if (status & USBEH_CONNECT_STATUS_CHANGE) {
                        usbeh_api_clear_port_feature(device, USBEH_C_PORT_CONNECTION, port);
                    }    
                    if (status & USBEH_PORT_RESET_STATUS_CAHNGE) {
                        usbeh_api_clear_port_feature(device, USBEH_C_PORT_RESET, port);
                    }
                }
            }
        }
    }
}

void USBEH_Controller::hubInterruptCallback(int device, int endpoint, int status, USBEH_U08 *data, int length, void *userData) {
    USBEH_Controller *controller = (USBEH_Controller *)userData;
    if (status == 0) {
        controller->hubInterrupt(device);
    }
    usbeh_api_interrupt_transfer(device, endpoint, data, 1, hubInterruptCallback, userData);
}

int USBEH_Controller::initHub(int device) {
    USBEH_U08 buffer[16];
    int r = usbeh_api_control_transfer(device, USBEH_DEVICE_TO_HOST | USBEH_REQUEST_TYPE_CLASS | USBEH_RECIPIENT_DEVICE, USBEH_GET_DESCRIPTOR, (USBEH_DESCRIPTOR_TYPE_HUB << 8), 0, buffer, sizeof(buffer), 0, 0);
    if (r < 0) return -1;
    
    USBEH_Device *dev = &this->devices[device - 1];
    
    int ports = buffer[2];
    
    for (int i = 0; i < ports; i++) {
        usbeh_api_set_port_power(device, i + 1);
    }
    
    return usbeh_api_interrupt_transfer(device, 0x81, &dev->hubInterruptData,1, hubInterruptCallback, this); 
}

int USBEH_Controller::transfer(USBEH_Endpoint *endpoint, int token, USBEH_U08 *data, int len, int state) {
   
    int toggle = 0;
    
    if (endpoint->address() == 0) {
        toggle = (token == USBEH_TOKEN_SETUP) ? USBEH_TD_TOGGLE_0 : USBEH_TD_TOGGLE_1;
    }
    
    if (token != USBEH_TOKEN_SETUP) {
        token = (token == USBEH_TOKEN_IN ? USBEH_TD_IN : USBEH_TD_OUT);
    }
    
    USBEH_HCTD *head = &endpoint->tdHead;
    USBEH_HCTD *tail = &this->commonTail;
    
    head->control = USBEH_TD_ROUNDING | token | USBEH_TD_DELAY_INT(0) | toggle | USBEH_TD_CC;
    head->currentBufferPointer = (USBEH_U32)data;
    head->bufferEnd = (USBEH_U32)(data + len - 1);
    head->next = (USBEH_U32)tail;
   
    USBEH_HCED *ed = &endpoint->endpointDescriptor;
    ed->headTd = (USBEH_U32)head | (ed->headTd & 0x00000002);
    ed->tailTd = (USBEH_U32)tail;
    
    switch (endpoint->flags & 3) {
        case USBEH_ENDPOINT_CONTROL:
            USBEH_HcControlHeadED = endpoint->queue(USBEH_HcControlHeadED);
            endpoint->currentState = state;
            USBEH_HcCommandStatus = USBEH_HcCommandStatus | USBEH_CONTROL_LIST_FILLED;
            USBEH_HcControl |= USBEH_CONTROL_LIST_ENABLE;
            break;
        case USBEH_ENDPOINT_BULK:
            USBEH_HcBulkHeadED = endpoint->queue(USBEH_HcBulkHeadED);
            endpoint->currentState = state;
            USBEH_HcCommandStatus = USBEH_HcCommandStatus | USBEH_BULK_LIST_FILLED;
            USBEH_HcControl |= USBEH_BULK_LIST_ENABLE;
            break;
        case USBEH_ENDPOINT_INTERRUPT:
            hcca.interruptTable[0] = endpoint->queue(hcca.interruptTable[0]);
            endpoint->currentState = state;
            USBEH_HcControl |= USBEH_PERIODIC_LIST_ENABLE;
            break;
    }
    
    return 0;
}

bool USBEH_Controller::remove(USBEH_HCED *ed, volatile USBEH_HCED **queue) {
    if (*queue == 0) return false;
    if (*queue == (volatile USBEH_HCED *)ed) {
        *queue = (volatile USBEH_HCED *)ed->next;
        return true;    
    }
    
    volatile USBEH_HCED *head = *queue;
    while (head) {
        if (head->next == (USBEH_U32)ed) {
            head->next = ed->next;
            return true;
        }
        head = (volatile USBEH_HCED *)head->next;
    }
    
    return false;
}

void USBEH_Controller::release(USBEH_Endpoint *endpoint) {
    if (endpoint->currentState != USBEH_Endpoint::notQueued) {
        USBEH_HCED *ed = (USBEH_HCED *)endpoint;
        ed->control |= 0x4000;
        switch (endpoint->flags & 0x3) {
            case USBEH_ENDPOINT_CONTROL:
                remove(ed, (volatile USBEH_HCED **)&USBEH_HcControlHeadED);
                break;
            case USBEH_ENDPOINT_BULK:
                remove(ed, (volatile USBEH_HCED **)&USBEH_HcBulkHeadED);
                break;
            case USBEH_ENDPOINT_INTERRUPT:
                for (int i = 0; i < 32; i++) {
                    remove(ed, (volatile USBEH_HCED **)&hcca.interruptTable[i]);
                }
                break;
        }
        
        USBEH_U16 fn = hcca.frameNumber;
        while (fn == hcca.frameNumber) ;
    }
    
    memset(endpoint, 0, sizeof(USBEH_Endpoint));
}

int USBEH_Controller::addEndpoint(int device, USBEH_endpointDescriptor *endpoint) {
    return addEndpoint(device, endpoint->bEndpointAddress, endpoint->bmAttributes, endpoint->wMaxPacketSize, endpoint->bInterval);
}

int USBEH_Controller::addEndpoint(int device, int endpoint, int attributes, int maxPacketSize, int interval) {
    USBEH_Device *dev = &this->devices[device - 1];
    USBEH_Endpoint *ep = allocateEndpoint(device, endpoint, attributes, maxPacketSize);
    if (!ep) {
        return -1;
    }
    dev->setEndpointIndex(endpoint, ep - this->endpoints);
    ep->endpointDescriptor.control |= dev->flags;
    return 0;

}

USBEH_Endpoint * USBEH_Controller::allocateEndpoint(int device, int endpointAddress, int type, int maxPacketSize) {
    for (int i = 0; i < USBEH_MAX_ENDPOINTS_TOTAL; i++) {
        USBEH_Endpoint *ep = &this->endpoints[i];
        if (ep->currentState == 0) {
            ep->flags = (endpointAddress & 0x80) | (type & 0x3);
            ep->currentState = USBEH_Endpoint::notQueued;
            ep->endpointDescriptor.control = (maxPacketSize << 16) | ((endpointAddress & 0x7F) << 7) | device;
            return ep;
        }
    }
    return 0;
}

int USBEH_Controller::addDevice(int hub, int port, bool isLowSpeed) {
    int device = addDeviceCore(hub, port, isLowSpeed);
    if (device < 0) {
        disconnect(hub, port);
        resetPort(hub, port);
        return -1;
    }
    return device;
}

int USBEH_Controller::addDeviceCore(int hub, int port, bool isLowSpeed) {
    
    int lowSpeed = isLowSpeed ? 0x2000 : 0;
    
    USBEH_deviceDescriptor desc;
    
    endpointZero.endpointDescriptor.control = (8 << 16) | lowSpeed;
    
    int result = usbeh_api_get_descriptor(0, USBEH_DESCRIPTOR_TYPE_DEVICE, 0, (USBEH_U08 *)&desc, 8);
        
    if (result < 0) {
        #ifdef DEBUG_USB_DRIVER
        debug_printf("usbeh_api_get_descriptor() failed with %d (0x%x) at line %d\r\n", result, result, __LINE__);
        #endif
        return result;
    }
    
    endpointZero.endpointDescriptor.control = (desc.bMaxPacketSize << 16) | lowSpeed;
    
    result = usbeh_api_get_descriptor(0, USBEH_DESCRIPTOR_TYPE_DEVICE, 0, (USBEH_U08 *)&desc, sizeof(desc));
    if (result < 0) {
        #ifdef DEBUG_USB_DRIVER
        debug_printf("usbeh_api_get_descriptor() failed with %d at line %d\r\n", result, __LINE__);
        #endif
        return result;
    }
    
    int device = 0;
    for (int i = 0; i < USBEH_MAX_DEVICES; i++) {
        if (devices[i].port == 0) {
            device = i + 1;
            break;
        }
    }
    
    if (!device) {
        return -1;
    }
    
    result = usbeh_api_set_address(0, device);
    if (result) {
        return result;
    }
    
    delayMS(2);
    
    USBEH_Device *dev = &devices[device - 1];
    dev->init(&desc, hub, port, device, lowSpeed);
    addEndpoint(device, 0, USBEH_ENDPOINT_CONTROL, desc.bMaxPacketSize, 0);
    this->connectPending = 0;
    
    if ((result = usbeh_api_get_descriptor(device, USBEH_DESCRIPTOR_TYPE_DEVICE, 0, (USBEH_U08 *)&desc, sizeof(desc))) < 0) {
        #ifdef DEBUG_USB_DRIVER
        debug_printf("usbeh_api_get_descriptor() failed with %d at line %d\r\n", result, __LINE__);
        #endif
        return result;
    }
    
    result = setConfigurationAndInterface(device, 1, -1, &desc);
    
    if (desc.bDeviceClass == CLASS_HUB) {
        initHub(device);
    }
    
    return device;
}

int USBEH_Controller::setConfigurationAndInterface(int device, int configuration, int interfaceNumber, USBEH_deviceDescriptor *desc) {
    USBEH_U08 buffer[255];
    USBEH_interfaceDescriptor *found[16];
    USBEH_endpointDescriptor *ed;
    
    for (int i = 0; i < 16; i++) {
        found[i] = (USBEH_interfaceDescriptor *)NULL;
    }
    
    int err = usbeh_api_get_descriptor(device, USBEH_DESCRIPTOR_TYPE_CONFIGURATION, 0, buffer, sizeof(buffer));
    if (err < 0) {
        #ifdef DEBUG_USB_DRIVER
        debug_printf("GET_DESCRIPTOR failed at line %d\r\n", __LINE__);
        #endif
        return err;
    }
    
    err = usbeh_api_set_configuration(device, configuration);
    if (err < 0) {
        return err;
    }
    
    int interfaceCounter = 0;
    int len        = buffer[2] | (buffer[3] << 8);
    USBEH_U08 *d   = buffer;
    USBEH_U08 *end = d + len;
    while (d < end) {
        //printf("Testing descriptor type %02x\n\r", d[1]);
        if (d[1] == USBEH_DESCRIPTOR_TYPE_INTERFACE) {
            //printf("  Found interface descriptor type %02x\n\r", d[1]);
            USBEH_interfaceDescriptor *id = (USBEH_interfaceDescriptor *)d;
            if (interfaceNumber == -1 || id->bInterfaceNumber == interfaceNumber) {
                found[interfaceCounter++] = id;
                d += d[0];
                while (d < end && d[1] != USBEH_DESCRIPTOR_TYPE_INTERFACE) {
                    switch (d[1]) {
                        case USBEH_DESCRIPTOR_TYPE_ENDPOINT:
                            ed = (USBEH_endpointDescriptor *)d;
                            //printf("  Adding endpoint 0x%02x for interface %d\r\n", ed->bEndpointAddress, id->bInterfaceNumber);
                            addEndpoint(device, ed);
                            break;
                        default:
                            // Skip unknown descriptor.
                            //printf("  Unknown descriptor type: %02x\r\n", d[1]);
                            break;
                    }
                    d += d[0];
                }
            }
        }
        else {
            d += d[0];
        }
    }
    
    if (interfaceCounter == 0) {
        return USBEH_ERR_INTERFACE_NOT_FOUND;
    }
    
    usbeh_api_on_load_device(device, desc, found);
    
    return 0;
}

void USBEH_Controller::processDoneQueue(USBEH_U32 tdList) {
    USBEH_Endpoint *endpoint;
    USBEH_HCTD *list = reverse((USBEH_HCTD *)tdList);
    while (list) {
        endpoint = (USBEH_Endpoint *)(list - 1);
        list = (USBEH_HCTD *)list->next;
        int ep = endpoint->address();
        bool in = endpoint->flags & 0x80;
        int status = (endpoint->tdHead.control >> 28) & 0xF;
        
        if (status != 0) {
            endpoint->currentState = USBEH_Endpoint::idle;
        }
        else {
            switch (endpoint->currentState) {
                case USBEH_Endpoint::setupQueued:
                    if (endpoint->length == 0) {
                        transfer(endpoint, in ? USBEH_TOKEN_OUT : USBEH_TOKEN_IN, 0, 0, USBEH_Endpoint::statusQueued);
                    }
                    else {
                        transfer(endpoint, in ? USBEH_TOKEN_IN : USBEH_TOKEN_OUT, (USBEH_U08 *)endpoint->data, endpoint->length, USBEH_Endpoint::dataQueued);
                    }
                    break;
                    
                case USBEH_Endpoint::dataQueued:
                    if (endpoint->tdHead.currentBufferPointer) {
                        endpoint->length = endpoint->tdHead.currentBufferPointer - (USBEH_U32)endpoint->data;
                    }
                    
                    if (ep == 0) {
                        transfer(endpoint, in ? USBEH_TOKEN_OUT : USBEH_TOKEN_IN, 0, 0, USBEH_Endpoint::statusQueued);
                    }
                    else {
                        endpoint->currentState = USBEH_Endpoint::idle;    
                    }
                    break;
                    
                case USBEH_Endpoint::statusQueued:
                    endpoint->currentState = USBEH_Endpoint::idle;    
                    break;
            }
        }
        
        if (endpoint->callback && endpoint->currentState == USBEH_Endpoint::idle) {
            //if (endpoint->address() != 0x81) printf("\r\nCallback pending for 0x%02X\r\n", endpoint->address());
            endpoint->currentState = USBEH_Endpoint::callbackPending;
            this->callbacksPending++;
        }
    }
}

void USBEH_Controller::resetPort(int hub, int port) {
    this->connectPending++;
    if (hub == 0) {
        USBEH_HcRhPortStatus1 = USBEH_PORT_RESET_STATUS;
    }
    else {
        usbeh_api_set_port_reset(hub, port);
    }
}

void USBEH_Controller::connect(int hub, int port, bool lowSpeed) {
    #ifdef DEBUG_USB_DRIVER
    debug_printf("%s called at line %d\r\n", __FUNCTION__, __LINE__);
    #endif
    addDevice(hub, port, lowSpeed);
}

void USBEH_Controller::disconnect(int hub, int port) {
    for (int i = 0; i < USBEH_MAX_DEVICES; i++) {
        USBEH_Device *dev = this->devices + i;
        if (dev->port == port && dev->hub == hub) {
            for (int p = 0; p < dev->hubPortCount; p++) {
                disconnect(i + 1, p + 1);
            }
            for (int j = 1; j < USBEH_MAX_ENDPOINTS_PER_DEVICE * 2; j += 2) {
                USBEH_U08 endpointIndex = dev->endpoints[j];
                if (endpointIndex != 0xFF) {
                    release(this->endpoints + endpointIndex);
                }
                dev->port = 0;
                dev->flags = 0;
                return;
            }
        }
    }
}

void USBEH_Controller::hubStatusChange(int hub, int port, USBEH_U32 status) {
    if (status & USBEH_CONNECT_STATUS_CHANGE) {
        if (status & USBEH_CURRENT_CONNECT_STATUS) {
            resetPort(hub, port);
        }
        else {
            disconnect(hub, port);
        }
    }
    
    if (status & USBEH_PORT_RESET_STATUS_CHANGE) {
        if (!(status & USBEH_PORT_RESET_STATUS)) {
            this->connectCountdown = 200;
            if (status & USBEH_LOW_SPEED_DEVICE) {
                port |= 0x80;
            }
            this->connectHub = hub;
            this->connectPort = port;
        }
    }
}

void USBEH_Controller::delayMS(int ms) {
    USBEH_U16 f = ms + hcca.frameNumber;
    while (f != hcca.frameNumber) ;
}

void USBEH_Controller::initHW(USBEH_HCCA *cca) {
    NVIC_DisableIRQ(USB_IRQn);
    
    LPC_SC->PCONP           |= (1UL << 31);
    LPC_USB->USBClkCtrl     |= USBEH_CLOCK_MASK;
    while ((LPC_USB->USBClkSt & USBEH_CLOCK_MASK) != USBEH_CLOCK_MASK);
    
    USBEH_OTGStCtrl |= 1;
    USBEH_USBClkCtrl &= ~USBEH_PORTSEL_CLK_EN;
    
    LPC_PINCON->PINSEL1 &= ~( (3<<26) | (3<<28) );    
    LPC_PINCON->PINSEL1 |=  ( (1<<26) | (1<<28));
    
    USBEH_HcControl = 0;
    USBEH_HcControlHeadED = 0;
    USBEH_HcBulkHeadED = 0;
    USBEH_HcCommandStatus = USBEH_HOST_CONTROLLER_RESET;
    USBEH_HcFmInterval = USBEH_DEFAULT_FMINTERVAL;
    USBEH_HcPeriodicStart = USBEH_FRAMEINTERVAL * 90 / 100;
    
    USBEH_HcControl = (USBEH_HcControl & (~USBEH_HOST_CONTROLLER_FUNCTIONAL_STATE)) | USBEH_OPERATIONAL_MASK;
    USBEH_HcRhStatus = USBEH_SET_GLOBAL_POWER;
    
    USBEH_HcHCCA = (USBEH_U32)cca;
    USBEH_HcInterruptStatus |= USBEH_HcInterruptStatus;
    USBEH_HcInterruptEnable |= USBEH_MASTER_IRQ_ENABLE | USBEH_WRITEBACK_DONE_HEAD | USBEH_ROOT_HUB_STATUS_CHANGE | USBEH_FRAME_NUMBER_OVERFLOW | USBEH_START_OF_FRAME;
    
    NVIC_EnableIRQ(USB_IRQn);
    while (cca->frameNumber < 10);
}

USBEH_HCTD * USBEH_Controller::reverse(USBEH_HCTD *current) {
    USBEH_HCTD *result = NULL, *temp;
    while (current) {
        temp = (USBEH_HCTD *)current->next;
        current->next = (USBEH_U32)result;
        result = current;
        current = temp;
    }
    return result;
}

USBEH_Endpoint * USBEH_Controller::getEndpoint(int device, int ep) {
    if (device == 0) {
        return &endpointZero;
    }
    if (device > USBEH_MAX_DEVICES) {
        return 0;
    }
    int i = devices[device - 1].getEndpointIndex(ep);
    if (i == -1) {
        return 0;
    }
    return endpoints + i;
}

/* The controller is defined withinn the API section. */
extern USBEH_Controller sys_usb_controller;

USBEH_SOF_COUNTER   *sof_counter_head = NULL;

/* The USB interrupt handler. */
extern "C" void USB_IRQHandler (void) __irq {

    USBEH_U32 int_status = USBEH_HcInterruptStatus;
  
    if (int_status & USBEH_ROOT_HUB_STATUS_CHANGE) {
        sys_usb_controller.rootHubStatusChange++;  
    }

    USBEH_U32 head = 0;
    
    if (int_status & USBEH_WRITEBACK_DONE_HEAD) {
        head = sys_usb_controller.hcca.doneHead;
        sys_usb_controller.hcca.doneHead = 0;
    }
    
    if (int_status & USBEH_START_OF_FRAME) {
        for (USBEH_SOF_COUNTER *list = sof_counter_head; list != NULL; list = list->next) {
            if (list->mode & USBEH_SOF_COUNTER_DEC && list->flag == 0) {
                if (list->counter > 0) list->counter--;
                if (list->counter == 0) {
                    list->flag = 1;
                    list->counter = list->reload;
                    if (list->callback != NULL) {
                        (list->callback)((struct _sof_counter *)list);
                    }
                }   
            }
        }
    }
                 
    USBEH_HcInterruptStatus = int_status;

    if (head) {
        sys_usb_controller.processDoneQueue(head);
    }
}