SAMA5 OHCI: Re-organize some endpoint list data structures.. Strange things happen when semaphores lie in DMA memory which is occasionally invalidated

This commit is contained in:
Gregory Nutt 2013-08-16 11:36:51 -06:00
parent 10daf06976
commit 1fb80e0917
2 changed files with 299 additions and 215 deletions

View File

@ -171,6 +171,21 @@
/*******************************************************************************
* Private Types
*******************************************************************************/
/* This structure contains one endpoint list. The main reason for the existence
* of this structure is to contain the sem_t value associated with the ED. It
* doesn't work well within the ED itself because then the semaphore counter
* is subject to DMA cache operations (invalidate a modified semaphore count
* is fatal!).
*/
struct sam_eplist_s
{
volatile bool wdhwait; /* TRUE: Thread is waiting for WDH interrupt */
sem_t wdhsem; /* Semaphore used to wait for Writeback Done Head event */
struct sam_ed_s *ed; /* Endpoint descriptor (ED) */
struct sam_gtd_s *tail; /* Tail transfer descriptor (TD) */
};
/* This structure retins the state of one root hub port */
struct sam_rhport_s
@ -184,13 +199,12 @@ struct sam_rhport_s
/* Root hub port status */
volatile bool connected; /* Connected to device */
volatile bool lowspeed; /* Low speed device attached. */
uint8_t rhpndx; /* Root hub port index */
bool ep0init; /* True: EP0 initialized */
volatile bool connected; /* Connected to device */
volatile bool lowspeed; /* Low speed device attached. */
uint8_t rhpndx; /* Root hub port index */
bool ep0init; /* True: EP0 initialized */
struct sam_ed_s *edctrl; /* EP0 control ED */
struct sam_gtd_s *tdtail; /* EP0 tail TD */
struct sam_eplist_s ep0; /* EP0 endpoint list */
/* The bound device class driver */
@ -203,14 +217,14 @@ struct sam_ohci_s
{
/* Driver status */
volatile bool rhswait; /* TRUE: Thread is waiting for Root Hub Status change */
volatile bool rhswait; /* TRUE: Thread is waiting for Root Hub Status change */
#ifndef CONFIG_USBHOST_INT_DISABLE
uint8_t ininterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */
uint8_t outinterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */
uint8_t ininterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */
uint8_t outinterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */
#endif
sem_t exclsem; /* Support mutually exclusive access */
sem_t rhssem; /* Semaphore to wait Writeback Done Head event */
sem_t exclsem; /* Support mutually exclusive access */
sem_t rhssem; /* Semaphore to wait Writeback Done Head event */
/* Root hub ports */
@ -219,29 +233,25 @@ struct sam_ohci_s
/* The OCHI expects the size of an endpoint descriptor to be 16 bytes.
* However, the size allocated for an endpoint descriptor is 32 bytes. This
* extra 16-bytes is used by the OHCI host driver in order to maintain
* is necessary first because the Cortex-A5 cache line size is 32 bytes and
* tht is the smallest amount of memory that we can perform cache operations
* on. The 16-bytes is also used by the OHCI host driver in order to maintain
* additional endpoint-specific data.
*/
#define SAMD_ED_PADSIZE (12 - sizeof(sem_t))
struct sam_ed_s
{
/* Hardware specific fields */
struct ohci_ed_s hw; /* 0-15 */
struct ohci_ed_s hw; /* 0-15 */
/* Software specific fields */
uint8_t xfrtype; /* 16: Transfer type. See SB_EP_ATTR_XFER_* in usb.h */
uint8_t interval; /* 17: Periodic EP polling interval: 2, 4, 6, 16, or 32 */
volatile uint8_t tdstatus; /* 18: TD control status bits from last Writeback Done Head event */
volatile bool wdhwait; /* 19: TRUE: Thread is waiting for WDH interrupt */
sem_t wdhsem; /* 20-23: Semaphore used to wait for Writeback Done Head event */
/* Padding to 32-bytes follow. The amount of padding depends on the size of sem_t */
uint8_t pad[SAMD_ED_PADSIZE];
struct sam_eplist_s *eplist; /* 16-19: List structure associated with the ED */
uint8_t xfrtype; /* 20: Transfer type. See SB_EP_ATTR_XFER_* in usb.h */
uint8_t interval; /* 21: Periodic EP polling interval: 2, 4, 6, 16, or 32 */
volatile uint8_t tdstatus; /* 22: TD control status bits from last Writeback Done Head event */
uint8_t pad[9]; /* 23-31: Pad to 32-bytes */
};
#define SIZEOF_SAM_ED_S 32
@ -256,12 +266,12 @@ struct sam_gtd_s
{
/* Hardware specific fields */
struct ohci_gtd_s hw; /* 0-15 */
struct ohci_gtd_s hw; /* 0-15 */
/* Software specific fields */
struct sam_ed_s *ed; /* 16-19: Pointer to parent ED */
uint8_t pad[12]; /* 20-31: Pad to 32 bytes */
struct sam_ed_s *ed; /* 16-19: Pointer to parent ED */
uint8_t pad[12]; /* 20-31: Pad to 32 bytes */
};
#define SIZEOF_SAM_TD_S 32
@ -270,8 +280,8 @@ struct sam_gtd_s
struct sam_list_s
{
struct sam_list_s *flink; /* Link to next buffer in the list */
/* Variable length buffer data follows */
struct sam_list_s *flink; /* Link to next buffer in the list */
/* Variable length buffer data follows */
};
/*******************************************************************************
@ -687,16 +697,10 @@ static struct sam_gtd_s *sam_tdalloc(void)
static void sam_tdfree(struct sam_gtd_s *td)
{
struct sam_list_s *tdfree = (struct sam_list_s *)td;
DEBUGASSERT(td);
/* This should not happen but just to be safe, don't free the common, pre-
* allocated tail TD.
*/
if (tdfree != NULL)
{
tdfree->flink = g_tdfree;
g_tdfree = tdfree;
}
tdfree->flink = g_tdfree;
g_tdfree = tdfree;
}
/*******************************************************************************
@ -1212,15 +1216,15 @@ static int sam_enqueuetd(struct sam_rhport_s *rhport, struct sam_ed_s *ed,
td = sam_tdalloc();
if (td != NULL)
{
/* Skip processing of this ED */
/* Skip processing of this ED while we modify the TD list. */
ed->hw.ctrl |= ED_CONTROL_K;
cp15_coherent_dcache((uintptr_t)ed,
(uintptr_t)ed + sizeof(struct sam_ed_s));
(uintptr_t)ed + sizeof(struct ohci_ed_s) - 1);
/* Get the tail ED for this root hub port */
tdtail = rhport->tdtail;
tdtail = rhport->ep0.tail;
/* Get physical addresses to support the DMA */
@ -1249,20 +1253,26 @@ static int sam_enqueuetd(struct sam_rhport_s *rhport, struct sam_ed_s *ed,
ed->hw.headp = (uint32_t)phytd | ((ed->hw.headp) & ED_HEADP_C);
ed->hw.tailp = (uint32_t)phytail;
/* Flush the buffer, the new TD, and the modified ED to RAM */
/* Flush the buffer (if there is one), the new TD, and the modified ED
* to RAM.
*/
if (buffer && buflen > 0)
{
cp15_coherent_dcache((uintptr_t)buffer,
(uintptr_t)buffer + buflen - 1);
}
cp15_coherent_dcache((uintptr_t)buffer,
(uintptr_t)buffer + buflen);
cp15_coherent_dcache((uintptr_t)tdtail,
(uintptr_t)tdtail + sizeof(struct sam_gtd_s));
(uintptr_t)tdtail + sizeof(struct ohci_gtd_s) - 1);
cp15_coherent_dcache((uintptr_t)td,
(uintptr_t)td + sizeof(struct sam_gtd_s));
(uintptr_t)td + sizeof(struct ohci_gtd_s) - 1);
/* Resume processing of this ED */
ed->hw.ctrl &= ~ED_CONTROL_K;
cp15_coherent_dcache((uintptr_t)ed,
(uintptr_t)ed + sizeof(struct sam_ed_s));
(uintptr_t)ed + sizeof(struct ohci_ed_s) - 1);
ret = OK;
}
@ -1292,8 +1302,8 @@ static int sam_ep0enqueue(struct sam_rhport_s *rhport)
uintptr_t physaddr;
uint32_t regval;
DEBUGASSERT(rhport && !rhport->ep0init && rhport->edctrl == NULL &&
rhport->tdtail == NULL);
DEBUGASSERT(rhport && !rhport->ep0init && rhport->ep0.ed == NULL &&
rhport->ep0.tail == NULL);
/* Allocate a control ED and a tail TD */
@ -1308,13 +1318,13 @@ static int sam_ep0enqueue(struct sam_rhport_s *rhport)
tdtail = sam_tdalloc();
if (!tdtail)
{
sam_edfree(rhport->edctrl);
sam_edfree(rhport->ep0.ed);
irqrestore(flags);
return -ENOMEM;
}
rhport->edctrl = edctrl;
rhport->tdtail = tdtail;
rhport->ep0.ed = edctrl;
rhport->ep0.tail = tdtail;
/* ControlListEnable. This bit is cleared to disable the processing of the
* Control list. We should never modify the control list while CLE is set.
@ -1329,17 +1339,15 @@ static int sam_ep0enqueue(struct sam_rhport_s *rhport)
memset(tdtail, 0, sizeof(struct sam_gtd_s));
tdtail->ed = edctrl;
/* Initialize the control endpoint for this port */
memset(edctrl, 0, sizeof(struct sam_ed_s));
sem_init(&edctrl->wdhsem, 0, 0);
/* Set up some default values (like max packetsize = 8).
/* Initialize the control endpoint for this port.
* Set up some default values (like max packetsize = 8).
* NOTE that the SKIP bit is set until the first readl TD is added.
*/
memset(edctrl, 0, sizeof(struct sam_ed_s));
(void)sam_ep0configure(&rhport->drvr, 0, 8);
edctrl->hw.ctrl |= ED_CONTROL_K;
edctrl->eplist = &rhport->ep0;
/* Link the common tail TD to the ED's TD list */
@ -1362,9 +1370,9 @@ static int sam_ep0enqueue(struct sam_rhport_s *rhport)
/* Flush the affected control ED and tail TD to RAM */
cp15_coherent_dcache((uintptr_t)edctrl,
(uintptr_t)edctrl + sizeof(struct sam_ed_s));
(uintptr_t)edctrl + sizeof(struct ohci_ed_s) - 1);
cp15_coherent_dcache((uintptr_t)tdtail,
(uintptr_t)tdtail + sizeof(struct sam_gtd_s));
(uintptr_t)tdtail + sizeof(struct ohci_gtd_s) - 1);
/* ControlListEnable. This bit is set to (re-)enable the processing of the
* Control list. Note: once enabled, it remains enabled and we may even
@ -1404,8 +1412,8 @@ static void sam_ep0dequeue(struct sam_rhport_s *rhport)
uintptr_t physcurr;
uint32_t regval;
DEBUGASSERT(rhport && rhport->ep0init && rhport->edctrl != NULL &&
rhport->tdtail != NULL);
DEBUGASSERT(rhport && rhport->ep0init && rhport->ep0.ed != NULL &&
rhport->ep0.tail != NULL);
/* ControlListEnable. This bit is cleared to disable the processing of the
* Control list. We should never modify the control list while CLE is set.
@ -1420,7 +1428,7 @@ static void sam_ep0dequeue(struct sam_rhport_s *rhport)
* precedessor).
*/
edctrl = rhport->edctrl;
edctrl = rhport->ep0.ed;
physcurr = sam_getreg(SAM_USBHOST_CTRLHEADED);
for (curred = (struct sam_ed_s *)sam_virtramaddr(physcurr),
@ -1445,7 +1453,7 @@ static void sam_ep0dequeue(struct sam_rhport_s *rhport)
/* Flush the modified ED to RAM */
cp15_coherent_dcache((uintptr_t)preved,
(uintptr_t)preved + sizeof(struct sam_ed_s));
(uintptr_t)preved + sizeof(struct ohci_ed_s) - 1);
}
else
{
@ -1466,9 +1474,9 @@ static void sam_ep0dequeue(struct sam_rhport_s *rhport)
}
irqrestore(flags);
/* Release any TDs that may still be attached to the ED */
/* Release any TDs that may still be attached to the ED. */
tdtail = rhport->tdtail;
tdtail = rhport->ep0.tail;
physcurr = edctrl->hw.headp;
for (currtd = (struct sam_gtd_s *)sam_virtramaddr(physcurr);
@ -1479,12 +1487,10 @@ static void sam_ep0dequeue(struct sam_rhport_s *rhport)
sam_tdfree(currtd);
}
/* Reset the control Ed and tail the common tail TD for this port.
* This is totally unnecessary but helps with debug.
*/
/* Free the tail TD and the control ED allocated for this port */
memset(tdtail, 0, sizeof(struct sam_gtd_s));
memset(edctrl, 0, sizeof(struct sam_ed_s));
sam_tdfree(tdtail);
sam_edfree(edctrl);
}
/*******************************************************************************
@ -1500,19 +1506,26 @@ static void sam_ep0dequeue(struct sam_rhport_s *rhport)
static int sam_wdhwait(struct sam_rhport_s *rhport, struct sam_ed_s *ed)
{
struct sam_eplist_s *eplist;
irqstate_t flags = irqsave();
int ret = -ENODEV;
int ret = -ENODEV;
/* Is the device still connected? */
if (rhport->connected)
{
/* Yes.. then set wdhwait to indicate that we expect to be informed when
* either (1) the device is disconnected, or (2) the transfer completed.
/* Yes.. Get the endpoint list associated with the ED */
eplist = ed->eplist;
DEBUGASSERT(eplist);
/* Then set wdhwait to indicate that we expect to be informed when
* either (1) the device is disconnected, or (2) the transfer
* completed.
*/
ed->wdhwait = true;
ret = OK;
eplist->wdhwait = true;
ret = OK;
}
irqrestore(flags);
@ -1536,6 +1549,7 @@ static int sam_wdhwait(struct sam_rhport_s *rhport, struct sam_ed_s *ed)
static int sam_ctrltd(struct sam_rhport_s *rhport, uint32_t dirpid,
uint8_t *buffer, size_t buflen)
{
struct sam_eplist_s *eplist;
struct sam_ed_s *edctrl;
uint32_t toggle;
uint32_t regval;
@ -1545,14 +1559,18 @@ static int sam_ctrltd(struct sam_rhport_s *rhport, uint32_t dirpid,
* transfer.
*/
edctrl = rhport->edctrl;
ret = sam_wdhwait(rhport, edctrl);
edctrl = rhport->ep0.ed;
ret = sam_wdhwait(rhport, edctrl);
if (ret != OK)
{
udbg("ERROR: Device disconnected\n");
return ret;
}
/* Get the endpoint list structure for the ED */
eplist = &rhport->ep0;
/* Configure the toggle field in the TD */
if (dirpid == GTD_STATUS_DP_SETUP)
@ -1578,16 +1596,14 @@ static int sam_ctrltd(struct sam_rhport_s *rhport, uint32_t dirpid,
regval |= OHCI_CMDST_CLF;
sam_putreg(regval, SAM_USBHOST_CMDST);
/* Wait for the Writeback Done Head interrupt */
sam_takesem(&edctrl->wdhsem);
/* Invalidate the D cache to force the control ED to be reloaded from
* RAM.
/* Wait for the Writeback Done Head interrupt. Loop to handle any false
* alarm semaphore counts.
*/
cp15_invalidate_dcache((uintptr_t)edctrl,
(uintptr_t)edctrl + sizeof(struct sam_ed_s));
while (eplist->wdhwait)
{
sam_takesem(&eplist->wdhsem);
}
/* Check the TD completion status bits */
@ -1604,7 +1620,7 @@ static int sam_ctrltd(struct sam_rhport_s *rhport, uint32_t dirpid,
/* Make sure that there is no outstanding request on this endpoint */
edctrl->wdhwait = false;
eplist->wdhwait = false;
return ret;
}
@ -1750,15 +1766,17 @@ static void sam_rhsc_interrupt(void)
static void sam_wdh_interrupt(void)
{
struct sam_eplist_s *eplist;
struct sam_gtd_s *td;
struct sam_gtd_s *next;
struct sam_ed_s *ed;
/* The host controller just wrote the list of finished TDs into the HCCA
/* The host controller just wrote the one finished TDs into the HCCA
* done head. This may include multiple packets that were transferred
* in the preceding frame.
*
* Remove the TD(s) from the Writeback Done Head in the HCCA and return
* them to the free list. Note that this is safe because the hardware
* Remove the TD from the Writeback Done Head in the HCCA and return
* it to the free list. Note that this is safe because the hardware
* will not modify the writeback done head again until the WDH bit is
* cleared in the interrupt status register.
*/
@ -1767,10 +1785,10 @@ static void sam_wdh_interrupt(void)
# if 0 /* Apparently insufficient */
cp15_invalidate_dcache((uintptr_t)&g_hcca.donehead,
(uintptr_t)&g_hcca.donehead + sizeof(uint32_t));
(uintptr_t)&g_hcca.donehead + sizeof(uint32_t) - 1);
#else
cp15_invalidate_dcache((uintptr_t)&g_hcca,
(uintptr_t)&g_hcca + sizeof(struct ohci_hcca_s));
(uintptr_t)&g_hcca + sizeof(struct ohci_hcca_s) - 1);
#endif
/* Now read the done head */
@ -1783,15 +1801,29 @@ static void sam_wdh_interrupt(void)
for (; td; td = next)
{
/* Invalidate the just-finished TD from D-cache to force it to be
* reloaded from memory.
*/
cp15_invalidate_dcache((uintptr_t)td,
(uintptr_t)td + sizeof( struct ohci_gtd_s) - 1);
/* Get the ED in which this TD was enqueued */
struct sam_ed_s *ed = td->ed;
ed = td->ed;
DEBUGASSERT(ed != NULL);
/* Invalidate the ED D-cache to force reload from memory */
/* Get the endpoint list that contains the ED */
eplist = ed->eplist;
DEBUGASSERT(eplist != NULL);
/* Also nvalidate the control ED so that it two will be re-read from
* memory.
*/
cp15_invalidate_dcache((uintptr_t)ed,
(uintptr_t)ed + sizeof( struct sam_ed_s));
(uintptr_t)ed + sizeof( struct ohci_ed_s) - 1);
/* Save the condition code from the (single) TD status/control
* word.
@ -1816,10 +1848,10 @@ static void sam_wdh_interrupt(void)
/* And wake up the thread waiting for the WDH event */
if (ed->wdhwait)
if (eplist->wdhwait)
{
sam_givesem(&ed->wdhsem);
ed->wdhwait = false;
sam_givesem(&eplist->wdhsem);
eplist->wdhwait = false;
}
}
}
@ -2104,7 +2136,7 @@ static int sam_ep0configure(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr,
funcaddr >= 0 && funcaddr <= SAM_USBHOST_NRHPORT &&
maxpacketsize < 2048);
edctrl = rhport->edctrl;
edctrl = rhport->ep0.ed;
/* We must have exclusive access to EP0 and the control list */
@ -2127,7 +2159,7 @@ static int sam_ep0configure(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr,
/* Flush the modified control ED to RAM */
cp15_coherent_dcache((uintptr_t)edctrl,
(uintptr_t)edctrl + sizeof(struct sam_ed_s));
(uintptr_t)edctrl + sizeof(struct ohci_ed_s) - 1);
sam_givesem(&g_ohci.exclsem);
uvdbg("RHPort%d EP0 CTRL: %08x\n", rhport->rhpndx + 1, edctrl->hw.ctrl);
@ -2160,7 +2192,9 @@ static int sam_epalloc(FAR struct usbhost_driver_s *drvr,
const FAR struct usbhost_epdesc_s *epdesc, usbhost_ep_t *ep)
{
struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr;
struct sam_eplist_s *eplist;
struct sam_ed_s *ed;
struct sam_gtd_s *td;
uintptr_t physaddr;
int ret = -ENOMEM;
@ -2170,111 +2204,144 @@ static int sam_epalloc(FAR struct usbhost_driver_s *drvr,
DEBUGASSERT(rhport && epdesc && ep && rhport->connected);
/* Allocate a container for the endpoint data */
eplist = (struct sam_eplist_s *)kzalloc(sizeof(struct sam_eplist_s));
if (!eplist)
{
udbg("ERROR: Failed to allocate EP list\n");
goto errout;
}
/* Initialize the endpoint container */
sem_init(&eplist->wdhsem, 0, 0);
/* We must have exclusive access to the ED pool, the bulk list, the periodic list
* and the interrupt table.
*/
sam_takesem(&g_ohci.exclsem);
/* Take the next ED from the beginning of the free list (if the list is
* non-empty.
*/
/* Allocate an ED and a tail TD for the new endpoint */
ed = sam_edalloc();
if (ed)
if (!ed)
{
/* Configure the endpoint descriptor. */
memset((void*)ed, 0, sizeof(struct sam_ed_s));
ed->hw.ctrl = (uint32_t)(epdesc->funcaddr) << ED_CONTROL_FA_SHIFT |
(uint32_t)(epdesc->addr) << ED_CONTROL_EN_SHIFT |
(uint32_t)(epdesc->mxpacketsize) << ED_CONTROL_MPS_SHIFT;
/* Get the direction of the endpoint */
if (epdesc->in)
{
ed->hw.ctrl |= ED_CONTROL_D_IN;
}
else
{
ed->hw.ctrl |= ED_CONTROL_D_OUT;
}
/* Check for a low-speed device */
if (rhport->lowspeed)
{
ed->hw.ctrl |= ED_CONTROL_S;
}
/* Set the transfer type */
ed->xfrtype = epdesc->xfrtype;
/* Special Case isochronous transfer types */
#if 0 /* Isochronous transfers not yet supported */
if (ed->xfrtype == USB_EP_ATTR_XFER_ISOC)
{
ed->hw.ctrl |= ED_CONTROL_F;
}
#endif
uvdbg("EP%d CTRL: %08x\n", epdesc->addr, ed->hw.ctrl);
/* Initialize the semaphore that is used to wait for the endpoint
* WDH event.
*/
sem_init(&ed->wdhsem, 0, 0);
/* Link the tail TD to the ED's TD list */
physaddr = (uintptr_t)sam_physramaddr((uintptr_t)rhport->tdtail);
ed->hw.headp = physaddr;
ed->hw.tailp = physaddr;
/* Now add the endpoint descriptor to the appropriate list */
switch (ed->xfrtype)
{
case USB_EP_ATTR_XFER_BULK:
ret = sam_addbulked(ed);
break;
case USB_EP_ATTR_XFER_INT:
ret = sam_addinted(epdesc, ed);
break;
case USB_EP_ATTR_XFER_ISOC:
ret = sam_addisoced(epdesc, ed);
break;
case USB_EP_ATTR_XFER_CONTROL:
default:
ret = -EINVAL;
break;
}
/* Was the ED successfully added? */
if (ret != OK)
{
/* No.. destroy it and report the error */
udbg("ERROR: Failed to queue ED for transfer type: %d\n", ed->xfrtype);
sem_destroy(&ed->wdhsem);
sam_edfree(ed);
}
else
{
/* Yes.. return an opaque reference to the ED */
*ep = (usbhost_ep_t)ed;
}
udbg("ERROR: Failed to allocate ED\n");
goto errout_with_semaphore;
}
td = sam_tdalloc();
if (!td)
{
udbg("ERROR: Failed to allocate TD\n");
goto errout_with_ed;
}
/* Save the descriptors in the endpoint container */
eplist->ed = ed;
eplist->tail = td;
/* Configure the endpoint descriptor. */
memset((void*)ed, 0, sizeof(struct sam_ed_s));
ed->hw.ctrl = (uint32_t)(epdesc->funcaddr) << ED_CONTROL_FA_SHIFT |
(uint32_t)(epdesc->addr) << ED_CONTROL_EN_SHIFT |
(uint32_t)(epdesc->mxpacketsize) << ED_CONTROL_MPS_SHIFT;
/* Get the direction of the endpoint */
if (epdesc->in)
{
ed->hw.ctrl |= ED_CONTROL_D_IN;
}
else
{
ed->hw.ctrl |= ED_CONTROL_D_OUT;
}
/* Check for a low-speed device */
if (rhport->lowspeed)
{
ed->hw.ctrl |= ED_CONTROL_S;
}
/* Set the transfer type */
ed->xfrtype = epdesc->xfrtype;
/* Special Case isochronous transfer types */
#if 0 /* Isochronous transfers not yet supported */
if (ed->xfrtype == USB_EP_ATTR_XFER_ISOC)
{
ed->hw.ctrl |= ED_CONTROL_F;
}
#endif
ed->eplist = eplist;
uvdbg("EP%d CTRL: %08x\n", epdesc->addr, ed->hw.ctrl);
/* Configure the tail descriptor. */
memset(td, 0, sizeof(struct sam_gtd_s));
td->ed = ed;
/* Link the tail TD to the ED's TD list */
physaddr = (uintptr_t)sam_physramaddr((uintptr_t)td);
ed->hw.headp = physaddr;
ed->hw.tailp = physaddr;
/* Now add the endpoint descriptor to the appropriate list */
switch (ed->xfrtype)
{
case USB_EP_ATTR_XFER_BULK:
ret = sam_addbulked(ed);
break;
case USB_EP_ATTR_XFER_INT:
ret = sam_addinted(epdesc, ed);
break;
case USB_EP_ATTR_XFER_ISOC:
ret = sam_addisoced(epdesc, ed);
break;
case USB_EP_ATTR_XFER_CONTROL:
default:
ret = -EINVAL;
break;
}
/* Was the ED successfully added? */
if (ret != OK)
{
/* No.. destroy it and report the error */
udbg("ERROR: Failed to queue ED for transfer type: %d\n", ed->xfrtype);
goto errout_with_td;
}
/* Success.. return an opaque reference to the endpoint list container */
*ep = (usbhost_ep_t)eplist;
sam_givesem(&g_ohci.exclsem);
return OK;
errout_with_td:
sam_tdfree(td);
errout_with_ed:
sam_edfree(ed);
errout_with_semaphore:
sam_givesem(&g_ohci.exclsem);
kfree(eplist);
errout:
return ret;
}
@ -2301,14 +2368,17 @@ static int sam_epalloc(FAR struct usbhost_driver_s *drvr,
static int sam_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
{
struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr;
struct sam_ed_s *ed = (struct sam_ed_s *)ep;
int ret;
struct sam_eplist_s *eplist = (struct sam_eplist_s *)ep;
struct sam_ed_s *ed;
int ret;
DEBUGASSERT(rhport && eplist && eplist->ed && eplist->tail);
/* There should not be any pending, real TDs linked to this ED */
DEBUGASSERT(rhport && ed &&
(ed->hw.headp & ED_HEADP_ADDR_MASK) ==
sam_physramaddr((uintptr_t)rhport->tdtail));
ed = eplist->ed;
DEBUGASSERT((ed->hw.headp & ED_HEADP_ADDR_MASK) ==
sam_physramaddr((uintptr_t)rhport->ep0.tail));
/* We must have exclusive access to the ED pool, the bulk list, the periodic list
* and the interrupt table.
@ -2316,20 +2386,20 @@ static int sam_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
sam_takesem(&g_ohci.exclsem);
/* Remove the ED to the correct list depending on the trasfer type */
/* Remove the ED to the correct list depending on the transfer type */
switch (ed->xfrtype)
{
case USB_EP_ATTR_XFER_BULK:
ret = sam_rembulked(ed);
ret = sam_rembulked(eplist->ed);
break;
case USB_EP_ATTR_XFER_INT:
ret = sam_reminted(ed);
ret = sam_reminted(eplist->ed);
break;
case USB_EP_ATTR_XFER_ISOC:
ret = sam_remisoced(ed);
ret = sam_remisoced(eplist->ed);
break;
case USB_EP_ATTR_XFER_CONTROL:
@ -2338,13 +2408,15 @@ static int sam_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep)
break;
}
/* Destroy the semaphore */
/* Put the ED and tail TDs back into the free list */
sem_destroy(&ed->wdhsem);
sam_edfree(eplist->ed);
sam_tdfree(eplist->tail);
/* Put the ED back into the free list */
/* And free the container */
sam_edfree(ed);
sem_destroy(&eplist->wdhsem);
kfree(eplist);
sam_givesem(&g_ohci.exclsem);
return ret;
}
@ -2581,8 +2653,7 @@ static int sam_ctrlin(FAR struct usbhost_driver_s *drvr,
*/
sam_givesem(&g_ohci.exclsem);
cp15_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + len);
cp15_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + len - 1);
return ret;
}
@ -2664,7 +2735,8 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
FAR uint8_t *buffer, size_t buflen)
{
struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr;
struct sam_ed_s *ed = (struct sam_ed_s *)ep;
struct sam_eplist_s *eplist = (struct sam_eplist_s *)ep;
struct sam_ed_s *ed;
uint32_t dirpid;
uint32_t regval;
#if SAM_IOBUFFERS > 0
@ -2673,8 +2745,10 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
bool in;
int ret;
DEBUGASSERT(rhport && ed && buffer && buflen > 0);
DEBUGASSERT(rhport && eplist && eplist->ed && eplist->tail &&
buffer && buflen > 0);
ed = eplist->ed;
in = (ed->hw.ctrl & ED_CONTROL_D_MASK) == ED_CONTROL_D_IN;
uvdbg("EP%d %s toggle: %d maxpacket: %d buflen: %d\n",
@ -2727,14 +2801,19 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
regval |= OHCI_CMDST_BLF;
sam_putreg(regval, SAM_USBHOST_CMDST);
/* Wait for the Writeback Done Head interrupt */
/* Wait for the Writeback Done Head interrupt Loop to handle any false
* alarm semaphore counts.
*/
sam_takesem(&ed->wdhsem);
while (eplist->wdhwait)
{
sam_takesem(&eplist->wdhsem);
}
/* Invalidate the D cache to force the ED to be reloaded from RAM */
cp15_invalidate_dcache((uintptr_t)ed,
(uintptr_t)ed + sizeof(struct sam_ed_s));
(uintptr_t)ed + sizeof(struct ohci_ed_s) - 1);
/* Check the TD completion status bits */
@ -2747,7 +2826,7 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
if (in)
{
cp15_invalidate_dcache((uintptr_t)buffer,
(uintptr_t)buffer + buflen);
(uintptr_t)buffer + buflen - 1);
}
ret = OK;
@ -2762,7 +2841,7 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep,
errout:
/* Make sure that there is no outstanding request on this endpoint */
ed->wdhwait = false;
eplist->wdhwait = false;
sam_givesem(&g_ohci.exclsem);
return ret;
}

View File

@ -1188,6 +1188,11 @@ Configurations
Application Configuration -> NSH Library
CONFIG_NSH_ARCHINIT=y : NSH board-initialization
NOTE: When OHCI is selected, the SAMA5 will operate at 384MHz instead
of 396MHz. This is so that the PLL generates a frequency which is a
multiple of the 48MHz needed for OHCI. The delay loop calibration
values that are used will be off slightly because of this.
STATUS:
2013-7-19: This configuration (as do the others) run at 396MHz.
The SAMA5D3 can run at 536MHz. I still need to figure out the