arch/arm/sama5: Make EHCI work with slow devices.
Make low/full speed devices work with EHCI while OHCI is disabled. A high speed USB hub has to be plugged into the root hub. This change will also allow the optional use of a full speed hub between the high speed hub and the low/full speed device. A recursive mutex is used to avoid deadlocks.
This commit is contained in:
parent
7986a28216
commit
555506a584
@ -140,6 +140,8 @@
|
||||
|
||||
/* Port numbers */
|
||||
|
||||
#define HPNDX(hp) ((hp)->port)
|
||||
#define HPORT(hp) (HPNDX(hp)+1)
|
||||
#define RHPNDX(rh) ((rh)->hport.hport.port)
|
||||
#define RHPORT(rh) (RHPNDX(rh)+1)
|
||||
|
||||
@ -210,9 +212,15 @@ struct sam_epinfo_s
|
||||
uint32_t xfrd; /* On completion, will hold the number of bytes transferred */
|
||||
sem_t iocsem; /* Semaphore used to wait for transfer completion */
|
||||
#ifdef CONFIG_USBHOST_ASYNCH
|
||||
usbhost_asynch_t callback; /* Transfer complete callback */
|
||||
void *arg; /* Argument that accompanies the callback */
|
||||
usbhost_asynch_t callback; /* Transfer complete callback */
|
||||
void *arg; /* Argument that accompanies the callback */
|
||||
#endif
|
||||
|
||||
/* These fields are used in the split-transaction protocol. */
|
||||
|
||||
uint8_t hubaddr; /* USB device address of the high-speed hub below */
|
||||
/* which a full/low-speed device is attached. */
|
||||
uint8_t hubport; /* The port on the above high-speed hub. */
|
||||
};
|
||||
|
||||
/* This structure retains the state of one root hub port */
|
||||
@ -242,7 +250,7 @@ struct sam_ehci_s
|
||||
{
|
||||
volatile bool pscwait; /* TRUE: Thread is waiting for port status change event */
|
||||
|
||||
mutex_t lock; /* Support mutually exclusive access */
|
||||
rmutex_t lock; /* Support mutually exclusive access */
|
||||
sem_t pscsem; /* Semaphore to wait for port status change events */
|
||||
|
||||
struct sam_epinfo_s ep0; /* Endpoint 0 */
|
||||
@ -437,7 +445,7 @@ static int sam_reset(void);
|
||||
|
||||
static struct sam_ehci_s g_ehci =
|
||||
{
|
||||
.lock = NXMUTEX_INITIALIZER,
|
||||
.lock = NXRMUTEX_INITIALIZER,
|
||||
.pscsem = SEM_INITIALIZER(0),
|
||||
.ep0.iocsem = SEM_INITIALIZER(1),
|
||||
};
|
||||
@ -1470,10 +1478,8 @@ static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
|
||||
struct sam_epinfo_s *epinfo)
|
||||
{
|
||||
struct sam_qh_s *qh;
|
||||
uint32_t rhpndx;
|
||||
uint32_t regval;
|
||||
uint8_t hubaddr;
|
||||
uint8_t hubport;
|
||||
uint32_t rl = 0;
|
||||
|
||||
/* Allocate a new queue head structure */
|
||||
|
||||
@ -1499,15 +1505,22 @@ static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
|
||||
* DTC Data toggle control 1
|
||||
* MAXPKT Max packet size Endpoint structure
|
||||
* C Control endpoint Calculated
|
||||
* RL NAK count reloaded 8
|
||||
* RL NAK count reloaded Depends on speed and
|
||||
* epinfo->xfrtype
|
||||
*/
|
||||
|
||||
if (epinfo->speed == USB_SPEED_HIGH &&
|
||||
epinfo->xfrtype != USB_EP_ATTR_XFER_INT)
|
||||
{
|
||||
rl = 8;
|
||||
}
|
||||
|
||||
regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) |
|
||||
((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) |
|
||||
((uint32_t)sam_ehci_speed(epinfo->speed) << QH_EPCHAR_EPS_SHIFT) |
|
||||
QH_EPCHAR_DTC |
|
||||
((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) |
|
||||
((uint32_t)8 << QH_EPCHAR_RL_SHIFT);
|
||||
(rl << QH_EPCHAR_RL_SHIFT);
|
||||
|
||||
/* Paragraph 3.6.3: "Control Endpoint Flag (C). If the QH.EPS field
|
||||
* indicates the endpoint is not a high-speed device, and the endpoint
|
||||
@ -1530,37 +1543,14 @@ static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
|
||||
* FIELD DESCRIPTION VALUE/SOURCE
|
||||
* -------- ------------------------------- --------------------
|
||||
* SSMASK Interrupt Schedule Mask Depends on epinfo->xfrtype
|
||||
* SCMASK Split Completion Mask 0
|
||||
* HUBADDR Hub Address Always 0 for now
|
||||
* PORT Port number RH port index + 1
|
||||
* SCMASK Split Completion Mask Depends on epinfo->speed
|
||||
* HUBADDR Hub Address epinfo->hubaddr
|
||||
* PORT Port number epinfo->hubport
|
||||
* MULT High band width multiplier 1
|
||||
*/
|
||||
|
||||
rhpndx = RHPNDX(rhport);
|
||||
|
||||
#ifdef CONFIG_USBHOST_HUB
|
||||
/* REVISIT: Future HUB support will require the HUB port number
|
||||
* and HUB device address to be included here:
|
||||
*
|
||||
* - The HUB device address is the USB device address of the USB 2.0 Hub
|
||||
* below which a full- or low-speed device is attached.
|
||||
* - The HUB port number is the port number on the above USB 2.0 Hub
|
||||
*
|
||||
* These fields are used in the split-transaction protocol. The kludge
|
||||
* below should work for hubs connected directly to a root hub port,
|
||||
* but would not work for devices connected to downstream hubs.
|
||||
*/
|
||||
|
||||
#warning Missing logic
|
||||
hubaddr = rhport->ep0.devaddr;
|
||||
hubport = rhpndx + 1;
|
||||
#else
|
||||
hubaddr = rhport->ep0.devaddr;
|
||||
hubport = rhpndx + 1;
|
||||
#endif
|
||||
|
||||
regval = ((uint32_t)hubaddr << QH_EPCAPS_HUBADDR_SHIFT) |
|
||||
((uint32_t)hubport << QH_EPCAPS_PORT_SHIFT) |
|
||||
regval = ((uint32_t)(epinfo->hubaddr) << QH_EPCAPS_HUBADDR_SHIFT) |
|
||||
((uint32_t)(epinfo->hubport) << QH_EPCAPS_PORT_SHIFT) |
|
||||
((uint32_t)1 << QH_EPCAPS_MULT_SHIFT);
|
||||
|
||||
#ifndef CONFIG_USBHOST_INT_DISABLE
|
||||
@ -1579,10 +1569,17 @@ static struct sam_qh_s *sam_qh_create(struct sam_rhport_s *rhport,
|
||||
*/
|
||||
#warning REVISIT
|
||||
|
||||
regval |= ((uint32_t)1 << QH_EPCAPS_SSMASK_SHIFT);
|
||||
regval |= ((uint32_t)(1 << 0) << QH_EPCAPS_SSMASK_SHIFT);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (epinfo->speed != USB_SPEED_HIGH)
|
||||
{
|
||||
/* Choose micro-frame 2. */
|
||||
|
||||
regval |= ((uint32_t)(1 << 2) << QH_EPCAPS_SCMASK_SHIFT);
|
||||
}
|
||||
|
||||
qh->hw.epcaps = sam_swap32(regval);
|
||||
|
||||
/* Mark this as the end of this list. This will be overwritten if/when the
|
||||
@ -2288,7 +2285,7 @@ static ssize_t sam_transfer_wait(struct sam_epinfo_s *epinfo)
|
||||
* wakes this thread up needs the lock).
|
||||
*/
|
||||
#warning REVISIT
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
|
||||
/* Wait for the IOC completion event */
|
||||
|
||||
@ -2298,7 +2295,7 @@ static ssize_t sam_transfer_wait(struct sam_epinfo_s *epinfo)
|
||||
* this upon return.
|
||||
*/
|
||||
|
||||
ret2 = nxmutex_lock(&g_ehci.lock);
|
||||
ret2 = nxrmutex_lock(&g_ehci.lock);
|
||||
if (ret2 < 0)
|
||||
{
|
||||
ret = ret2;
|
||||
@ -2322,7 +2319,7 @@ static ssize_t sam_transfer_wait(struct sam_epinfo_s *epinfo)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Did sam_ioc_wait() or nxmutex_lock report an error? */
|
||||
/* Did sam_ioc_wait() or nxrmutex_lock report an error? */
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
@ -3032,7 +3029,7 @@ static void sam_ehci_bottomhalf(void *arg)
|
||||
* real option (other than to reschedule and delay).
|
||||
*/
|
||||
|
||||
nxmutex_lock(&g_ehci.lock);
|
||||
nxrmutex_lock(&g_ehci.lock);
|
||||
|
||||
/* Handle all unmasked interrupt sources */
|
||||
|
||||
@ -3142,7 +3139,7 @@ static void sam_ehci_bottomhalf(void *arg)
|
||||
|
||||
/* We are done with the EHCI structures */
|
||||
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
|
||||
/* Re-enable relevant EHCI interrupts. Interrupts should still be enabled
|
||||
* at the level of the AIC.
|
||||
@ -3318,7 +3315,7 @@ static int sam_wait(struct usbhost_connection_s *conn,
|
||||
leave_critical_section(flags);
|
||||
|
||||
usbhost_vtrace2(EHCI_VTRACE2_MONWAKEUP,
|
||||
connport->port + 1, connport->connected);
|
||||
HPORT(connport), connport->connected);
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
@ -3443,9 +3440,13 @@ static int sam_rh_enumerate(struct usbhost_connection_s *conn,
|
||||
* repeat."
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_SAMA5_OHCI)
|
||||
hport->speed = USB_SPEED_LOW;
|
||||
regval |= EHCI_PORTSC_OWNER;
|
||||
sam_putreg(regval, &HCOR->portsc[rhpndx]);
|
||||
#else
|
||||
uerr("EHCI Root hub does not support low speed, use a hub.\n");
|
||||
#endif
|
||||
|
||||
#if 0 /* #ifdef CONFIG_SAMA5_OHCI */
|
||||
/* Give the port to the OHCI controller. Zero is the reset value for
|
||||
@ -3566,8 +3567,12 @@ static int sam_rh_enumerate(struct usbhost_connection_s *conn,
|
||||
* repeat."
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_SAMA5_OHCI)
|
||||
regval |= EHCI_PORTSC_OWNER;
|
||||
sam_putreg(regval, &HCOR->portsc[rhpndx]);
|
||||
#else
|
||||
uerr("EHCI Root hub does not support this device, use a hub.\n");
|
||||
#endif
|
||||
|
||||
#if 0 /* #ifdef CONFIG_SAMA5_OHCI */
|
||||
/* Give the port to the OHCI controller. Zero is the reset value for
|
||||
@ -3612,13 +3617,13 @@ static int sam_enumerate(struct usbhost_connection_s *conn,
|
||||
|
||||
/* Then let the common usbhost_enumerate do the real enumeration. */
|
||||
|
||||
usbhost_vtrace1(EHCI_VTRACE1_CLASSENUM, hport->port);
|
||||
usbhost_vtrace1(EHCI_VTRACE1_CLASSENUM, HPORT(hport));
|
||||
ret = usbhost_enumerate(hport, &hport->devclass);
|
||||
if (ret < 0)
|
||||
{
|
||||
/* Failed to enumerate */
|
||||
|
||||
usbhost_trace2(EHCI_TRACE2_CLASSENUM_FAILED, hport->port + 1, -ret);
|
||||
usbhost_trace2(EHCI_TRACE2_CLASSENUM_FAILED, HPORT(hport), -ret);
|
||||
|
||||
/* If this is a root hub port, then marking the hub port not connected
|
||||
* will cause sam_wait() to return and we will try the connection
|
||||
@ -3670,7 +3675,7 @@ static int sam_ep0configure(struct usbhost_driver_s *drvr,
|
||||
|
||||
/* We must have exclusive access to the EHCI data structures. */
|
||||
|
||||
ret = nxmutex_lock(&g_ehci.lock);
|
||||
ret = nxrmutex_lock(&g_ehci.lock);
|
||||
if (ret >= 0)
|
||||
{
|
||||
/* Remember the new device address and max packet size */
|
||||
@ -3679,7 +3684,7 @@ static int sam_ep0configure(struct usbhost_driver_s *drvr,
|
||||
epinfo->speed = speed;
|
||||
epinfo->maxpacket = maxpacketsize;
|
||||
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -3758,6 +3763,32 @@ static int sam_epalloc(struct usbhost_driver_s *drvr,
|
||||
epinfo->speed = hport->speed;
|
||||
nxsem_init(&epinfo->iocsem, 0, 0);
|
||||
|
||||
#ifdef CONFIG_USBHOST_HUB
|
||||
if (hport->speed != USB_SPEED_HIGH)
|
||||
{
|
||||
/* A high speed hub exists between this device and the root hub
|
||||
* otherwise we would not get here.
|
||||
*/
|
||||
|
||||
struct usbhost_hubport_s *parent = hport->parent;
|
||||
|
||||
for (; parent->speed != USB_SPEED_HIGH; parent = hport->parent)
|
||||
{
|
||||
hport = parent;
|
||||
}
|
||||
|
||||
if (parent->speed == USB_SPEED_HIGH)
|
||||
{
|
||||
epinfo->hubport = HPORT(hport);
|
||||
epinfo->hubaddr = hport->parent->funcaddr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Success.. return an opaque reference to the endpoint information
|
||||
* structure instance
|
||||
*/
|
||||
@ -4034,7 +4065,7 @@ static int sam_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
||||
* structures.
|
||||
*/
|
||||
|
||||
ret = nxmutex_lock(&g_ehci.lock);
|
||||
ret = nxrmutex_lock(&g_ehci.lock);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
@ -4061,13 +4092,13 @@ static int sam_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
|
||||
/* And wait for the transfer to complete */
|
||||
|
||||
nbytes = sam_transfer_wait(ep0info);
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return nbytes >= 0 ? OK : (int)nbytes;
|
||||
|
||||
errout_with_iocwait:
|
||||
ep0info->iocwait = false;
|
||||
errout_with_lock:
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -4136,7 +4167,7 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr,
|
||||
* structures.
|
||||
*/
|
||||
|
||||
ret = nxmutex_lock(&g_ehci.lock);
|
||||
ret = nxrmutex_lock(&g_ehci.lock);
|
||||
if (ret < 0)
|
||||
{
|
||||
return (ssize_t)ret;
|
||||
@ -4187,13 +4218,13 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr,
|
||||
/* Then wait for the transfer to complete */
|
||||
|
||||
nbytes = sam_transfer_wait(epinfo);
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return nbytes;
|
||||
|
||||
errout_with_iocwait:
|
||||
epinfo->iocwait = false;
|
||||
errout_with_lock:
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return (ssize_t)ret;
|
||||
}
|
||||
|
||||
@ -4248,7 +4279,7 @@ static int sam_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
||||
* structures.
|
||||
*/
|
||||
|
||||
ret = nxmutex_lock(&g_ehci.lock);
|
||||
ret = nxrmutex_lock(&g_ehci.lock);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
@ -4297,14 +4328,14 @@ static int sam_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
|
||||
|
||||
/* The transfer is in progress */
|
||||
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return OK;
|
||||
|
||||
errout_with_callback:
|
||||
epinfo->callback = NULL;
|
||||
epinfo->arg = NULL;
|
||||
errout_with_lock:
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_USBHOST_ASYNCH */
|
||||
@ -4352,7 +4383,7 @@ static int sam_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
|
||||
* interrupt level.
|
||||
*/
|
||||
|
||||
ret = nxmutex_lock(&g_ehci.lock);
|
||||
ret = nxrmutex_lock(&g_ehci.lock);
|
||||
if (ret < 0)
|
||||
{
|
||||
return (ssize_t)ret;
|
||||
@ -4499,7 +4530,7 @@ exit_terminate:
|
||||
#endif
|
||||
|
||||
errout_with_lock:
|
||||
nxmutex_unlock(&g_ehci.lock);
|
||||
nxrmutex_unlock(&g_ehci.lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user