USB host library, support isochronous,bulk,interrupt and control.

Dependents:   BaseUsbHost_example BaseJpegDecode_example SimpleJpegDecode_example

Import programBaseUsbHost_example

BaseUsbHost example program

BaseUsbHost.cpp

Committer:
va009039
Date:
2013-02-11
Revision:
5:8a2d056e9b38
Parent:
4:d931d24c2f81

File content as of revision 5:8a2d056e9b38:

// BaseUsbHost.cpp 2013/1/25
#include "mbed.h"
#include "rtos.h"
#include "BaseUsbHost.h"
//#define DEBUG
#include "BaseUsbHostDebug.h"
#define TEST
#include "BaseUsbHostTest.h"

// bits of the USB/OTG clock control register
#define HOST_CLK_EN     (1<<0)
#define DEV_CLK_EN      (1<<1)
#define PORTSEL_CLK_EN  (1<<3)
#define AHB_CLK_EN      (1<<4)

// bits of the USB/OTG clock status register
#define HOST_CLK_ON     (1<<0)
#define DEV_CLK_ON      (1<<1)
#define PORTSEL_CLK_ON  (1<<3)
#define AHB_CLK_ON      (1<<4)

// we need host clock, OTG/portsel clock and AHB clock
#define CLOCK_MASK (HOST_CLK_EN | PORTSEL_CLK_EN | AHB_CLK_EN)

#define  FI                     0x2EDF           /* 12000 bits per frame (-1) */
#define  DEFAULT_FMINTERVAL     ((((6 * (FI - 210)) / 7) << 16) | FI)

static BaseUsbHost* pHost = NULL;

extern "C" void USB_IRQHandler(void) __irq;
void USB_IRQHandler(void) __irq
{
    if (pHost) {
        pHost->irqHandler();
    }
}

BaseUsbHost::BaseUsbHost()
{
    if (pHost) {
        TEST_ASSERT(pHost == NULL);
        return;
    }
    pHost = this;
    NVIC_DisableIRQ(USB_IRQn);
    m_pHcca = new HCCA();
    TEST_ASSERT(m_pHcca);
    init_hw_ohci(m_pHcca);
    ResetRootHub();
    NVIC_SetPriority(USB_IRQn, 0);
    NVIC_EnableIRQ(USB_IRQn);
}

void BaseUsbHost::init_hw_ohci(HCCA* pHcca)
{
    LPC_SC->PCONP &= ~(1UL<<31);    //Cut power
    wait(1);
    LPC_SC->PCONP |= (1UL<<31);     // turn on power for USB
    LPC_USB->USBClkCtrl |= CLOCK_MASK; // Enable USB host clock, port selection and AHB clock
    // Wait for clocks to become available
    while ((LPC_USB->USBClkSt & CLOCK_MASK) != CLOCK_MASK)
        ;
    LPC_USB->OTGStCtrl |= 1;
    LPC_USB->USBClkCtrl &= ~PORTSEL_CLK_EN;
    
    LPC_PINCON->PINSEL1 &= ~((3<<26) | (3<<28));  
    LPC_PINCON->PINSEL1 |=  ((1<<26)|(1<<28));     // 0x14000000
      
    DBG("initialize OHCI\n");
    wait_ms(100);                                   /* Wait 50 ms before apply reset              */
    TEST_ASSERT((LPC_USB->HcRevision&0xff) == 0x10); // check revision
    LPC_USB->HcControl       = 0;                       /* HARDWARE RESET                             */
    LPC_USB->HcControlHeadED = 0;                       /* Initialize Control list head to Zero       */
    LPC_USB->HcBulkHeadED    = 0;                       /* Initialize Bulk list head to Zero          */
                                                        /* SOFTWARE RESET                             */
    LPC_USB->HcCommandStatus = OR_CMD_STATUS_HCR;
    LPC_USB->HcFmInterval    = DEFAULT_FMINTERVAL;      /* Write Fm Interval and Largest Data Packet Counter */
    LPC_USB->HcPeriodicStart = FI*90/100;
                                                      /* Put HC in operational state                */
    LPC_USB->HcControl  = (LPC_USB->HcControl & (~OR_CONTROL_HCFS)) | OR_CONTROL_HC_OPER;
    LPC_USB->HcRhStatus = OR_RH_STATUS_LPSC;            /* Set Global Power */
    TEST_ASSERT(pHcca);
    for (int i = 0; i < 32; i++) {
        pHcca->InterruptTable[i] = NULL;
    }
    LPC_USB->HcHCCA = reinterpret_cast<uint32_t>(pHcca);
    LPC_USB->HcInterruptStatus |= LPC_USB->HcInterruptStatus; /* Clear Interrrupt Status */
    LPC_USB->HcInterruptEnable  = OR_INTR_ENABLE_MIE|OR_INTR_ENABLE_WDH|OR_INTR_ENABLE_FNO;

    LPC_USB->HcRhPortStatus1 = OR_RH_PORT_CSC;
    LPC_USB->HcRhPortStatus1 = OR_RH_PORT_PRSC;
}

void BaseUsbHost::ResetRootHub()
{
    wait_ms(100); /* USB 2.0 spec says at least 50ms delay before port reset */
    LPC_USB->HcRhPortStatus1 = OR_RH_PORT_PRS; // Initiate port reset
    DBG("Before loop\n");
    while (LPC_USB->HcRhPortStatus1 & OR_RH_PORT_PRS)
      ;
    LPC_USB->HcRhPortStatus1 = OR_RH_PORT_PRSC; // ...and clear port reset signal
    DBG("After loop\n");
    wait_ms(200); /* Wait for 100 MS after port reset  */
}

HCTD* td_reverse(HCTD* td)
{
    HCTD* result = NULL;
    HCTD* next;
    while(td) {
        next = const_cast<HCTD*>(td->Next);
        td->Next = result;
        result = td;
        td = next;
    }
    return result;
}

void BaseUsbHost::irqHandler()
{
    if (!(LPC_USB->HcInterruptStatus & LPC_USB->HcInterruptEnable)) {
        return;
    }
    m_report_irq++;
    uint32_t status = LPC_USB->HcInterruptStatus;
    if (status & OR_INTR_STATUS_FNO) {
        m_report_FNO++;
    }
    if (status & OR_INTR_STATUS_WDH) {
        union {
            HCTD* done_td;
            uint32_t lsb;
        };
        done_td = const_cast<HCTD*>(m_pHcca->DoneHead);
        TEST_ASSERT(done_td);
        m_pHcca->DoneHead = NULL; // reset
        if (lsb & 1) { // error ?
            lsb &= ~1;
        }
        HCTD* td = td_reverse(done_td);
        while(td) {
            BaseEp* ep = td->ep;
            TEST_ASSERT(ep);
            if (ep) {
                ep->irqWdhHandler(td);
            }
            td = td->Next;
        }
        m_report_WDH++;
    }
    LPC_USB->HcInterruptStatus = status; // Clear Interrrupt Status
}

BaseEp::BaseEp(int addr, uint8_t ep, uint16_t size, int lowSpeed):m_td_queue_count(0)
{
    DBG("%p FA=%d EN=%02x MPS=%d S=%d\n", this, addr, ep, size, lowSpeed);
    TEST_ASSERT(size >= 8 && size <= 1023);    
    TEST_ASSERT(lowSpeed == 0 || lowSpeed == 1);
    m_pED  = new HCED(addr, ep, size, lowSpeed);
    TEST_ASSERT(m_pED);
}

int BaseEp::GetAddr()
{
    TEST_ASSERT(m_pED);
    if (m_pED) {
        return m_pED->FunctionAddress();
    }
    return 0;
}

int BaseEp::GetLowSpeed()
{
    TEST_ASSERT(m_pED);
    if (m_pED) {
        return m_pED->Speed();
    }
    return 0;
}

void BaseEp::update_FunctionAddress(int addr)
{
    TEST_ASSERT(addr >= 0 && addr <= 127);
    TEST_ASSERT(m_pED);
    if (m_pED) {
        m_pED->setFunctionAddress(addr);
    }
}

void BaseEp::update_MaxPacketSize(uint16_t size)
{
    TEST_ASSERT(size >= 8 && size <= 1023);
    TEST_ASSERT(m_pED);
    if (m_pED) {
        m_pED->setMaxPacketSize(size);
    }
}

int BaseEp::transfer(uint8_t* buf, int len)
{
    HCTD* data_td = m_pED->TailTd;
    TEST_ASSERT(data_td);
    if (data_td == NULL) {
        return USB_ERROR;
    }
    data_td->transfer(buf, len);
    HCTD* blank_td = new HCTD(this);
    TEST_ASSERT(blank_td);
    if (blank_td == NULL) {
        return USB_ERROR_MEMORY;
    }
    data_td->Next = blank_td;
    m_pED->TailTd = blank_td;
    enable();
    return USB_PROCESSING;
}

int BaseEp::status(uint32_t millisec)
{
    HCTD* td = get_queue_HCTD(millisec);
    if (td == NULL) {
        return USB_PROCESSING;
    }
    uint8_t cc = td->ConditionCode();
    int ret;
    switch(cc) {
        case 0:
        case 8:
        case 9:
            ret = td->status();
            break;
        case 4:
            ret = USB_ERROR_STALL;
            break;
        case 5:
            ret = USB_ERROR_DEVICE_NOT_RESPONDING;
            break;
        default:
            DBG("ConditionCode=%d\n", cc);
            ret = USB_ERROR;
            break;
    }
    delete td; 
    return ret;
}

int BaseEp::send_receive(uint8_t* buf, int len, int millisec)
{
    if (m_td_queue_count == 0) {
        int rc = transfer(buf, len);
        if (rc != USB_PROCESSING) {
            return rc;
        }
        m_td_queue_count++;
    }
    
    int ret = status(millisec);
    if (ret != USB_PROCESSING) {
        m_td_queue_count--;
    }
    return ret;
}

HCTD* BaseEp::get_queue_HCTD(uint32_t millisec)
{
    for(int i = 0; i < 16; i++) {
        osEvent evt = m_queue.get(millisec);
        if (evt.status == osEventMessage) {
            HCTD* td = reinterpret_cast<HCTD*>(evt.value.p);
            TEST_ASSERT(td);
            uint8_t cc = td->ConditionCode();
            if (cc != 0) {
                m_ConditionCode = cc;
                DBG_TD(td);
            }
            return td;
        } else if (evt.status == osOK) {
            continue;
        } else if (evt.status == osEventTimeout) {
            return NULL;
        } else {
            DBG("evt.status: %02x\n", evt.status);
            TEST_ASSERT(evt.status == osEventMessage);
        }
    }
    return NULL;
}

int BaseEp::wait_queue_HCTD(HCTD* wait_td, uint32_t millisec)
{
    for(int i = 0; i < 16; i++) {
        HCTD* td = get_queue_HCTD(millisec);
        if (td) {
            uint8_t cc = td->ConditionCode();
            if (cc != 0) {
                DBG_TD(td);
                delete td;
                switch(cc) {
                    case 4:
                        return USB_ERROR_STALL; 
                    default:
                        return USB_ERROR;
                }
            }
            delete td;
            if (td == wait_td) {
                return USB_OK;
            }
        }
    }
    return USB_ERROR;
}