USBH: STM32 LLD: various improvements

- general cleanup
- implemented workaround to undocumented erratum (the OTG core may
report successful enabling of port when connecting a low-speed device,
but really it generates no traffic and remains in a "dumb" state)
- improved handling of disconnection of devices (avoid submitting URBs
if the port is disabled)
This commit is contained in:
Diego Ismirlian 2017-07-31 18:48:23 -03:00
parent ca1882c01b
commit 02585210d1
2 changed files with 134 additions and 112 deletions

View File

@ -127,16 +127,6 @@ static inline void _save_dt_mask(usbh_ep_t *ep, uint32_t hctsiz) {
ep->dt_mask = hctsiz & HCTSIZ_DPID_MASK;
}
#if 1
#define _transfer_completed _transfer_completedI
#else
static inline void _transfer_completed(usbh_ep_t *ep, usbh_urb_t *urb, usbh_urbstatus_t status) {
osalSysLockFromISR();
_transfer_completedI(ep, urb, status);
osalSysUnlockFromISR();
}
#endif
/*===========================================================================*/
/* Functions called from many places. */
/*===========================================================================*/
@ -466,7 +456,7 @@ static void _purge_queue(USBHDriver *host, struct list_head *list) {
_release_channel(host, hcm);
_update_urb(ep, hcm->hc->HCTSIZ, urb, FALSE);
}
_transfer_completed(ep, urb, USBH_URBSTATUS_DISCONNECTED);
_transfer_completedI(ep, urb, USBH_URBSTATUS_DISCONNECTED);
}
}
@ -622,6 +612,13 @@ bool usbh_lld_ep_reset(usbh_ep_t *ep) {
void usbh_lld_urb_submit(usbh_urb_t *urb) {
usbh_ep_t *const ep = urb->ep;
USBHDriver *const host = ep->device->host;
if (!(host->otg->HPRT & HPRT_PENA)) {
uwarnf("\t%s: Can't submit URB, port disabled", ep->name);
_usbh_urb_completeI(urb, USBH_URBSTATUS_DISCONNECTED);
return;
}
/* add the URB to the EP's queue */
list_add_tail(&urb->node, &ep->urb_list);
@ -633,7 +630,7 @@ void usbh_lld_urb_submit(usbh_urb_t *urb) {
_move_to_pending_queue(ep);
if (usbhEPIsPeriodic(ep)) {
ep->device->host->otg->GINTMSK |= GINTMSK_SOFM;
host->otg->GINTMSK |= GINTMSK_SOFM;
} else {
/* try to queue non-periodic transfers */
_try_commit_np(ep->device->host);
@ -666,7 +663,7 @@ bool usbh_lld_urb_abort(usbh_urb_t *urb, usbh_urbstatus_t status) {
return FALSE;
}
/* This URB is active, we can cancel it now */
/* This URB is inactive, we can cancel it now */
uinfof("\t%s: usbh_lld_urb_abort: URB is not active", ep->name);
_transfer_completedI(ep, urb, status);
@ -766,7 +763,7 @@ static void _complete_bulk_int(USBHDriver *host, stm32_hc_management_t *hcm, usb
_save_dt_mask(ep, hctsiz);
if (_update_urb(ep, hctsiz, urb, TRUE)) {
udbgf("\t%s: done", ep->name);
_transfer_completed(ep, urb, USBH_URBSTATUS_OK);
_transfer_completedI(ep, urb, USBH_URBSTATUS_OK);
} else {
osalDbgCheck(urb->requestedLength > 0x7FFFF);
uwarnf("\t%s: incomplete", ep->name);
@ -797,7 +794,7 @@ static void _complete_control(USBHDriver *host, stm32_hc_management_t *hcm, usbh
} else {
osalDbgCheck(ep->xfer.u.ctrl_phase == USBH_LLD_CTRLPHASE_STATUS);
udbgf("\t%s: STATUS done", ep->name);
_transfer_completed(ep, urb, USBH_URBSTATUS_OK);
_transfer_completedI(ep, urb, USBH_URBSTATUS_OK);
}
_try_commit_np(host);
}
@ -823,7 +820,7 @@ static void _complete_iso(USBHDriver *host, stm32_hc_management_t *hcm, usbh_ep_
udbgf("\t%s: done", hcm->ep->name);
_release_channel(host, hcm);
_update_urb(ep, hctsiz, urb, TRUE);
_transfer_completed(ep, urb, USBH_URBSTATUS_OK);
_transfer_completedI(ep, urb, USBH_URBSTATUS_OK);
_try_commit_p(host, FALSE);
}
@ -908,7 +905,7 @@ static inline void _chh_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_
switch (reason) {
case USBH_LLD_HALTREASON_NAK:
if ((ep->type == USBH_EPTYPE_INT) && ep->in) {
_transfer_completed(ep, urb, USBH_URBSTATUS_TIMEOUT);
_transfer_completedI(ep, urb, USBH_URBSTATUS_TIMEOUT);
} else {
ep->xfer.error_count = 0;
_move_to_pending_queue(ep);
@ -923,12 +920,12 @@ static inline void _chh_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_
} else {
ep->status = USBH_EPSTATUS_HALTED;
}
_transfer_completed(ep, urb, USBH_URBSTATUS_STALL);
_transfer_completedI(ep, urb, USBH_URBSTATUS_STALL);
break;
case USBH_LLD_HALTREASON_ERROR:
if ((ep->type == USBH_EPTYPE_ISO) || done || (ep->xfer.error_count >= 3)) {
_transfer_completed(ep, urb, USBH_URBSTATUS_ERROR);
_transfer_completedI(ep, urb, USBH_URBSTATUS_ERROR);
} else {
uerrf("\t%s: err=%d, done=%d, retry", ep->name, ep->xfer.error_count, done);
_move_to_pending_queue(ep);
@ -937,7 +934,7 @@ static inline void _chh_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_
case USBH_LLD_HALTREASON_ABORT:
uwarnf("\t%s: Abort", ep->name);
_transfer_completed(ep, urb, urb->status);
_transfer_completedI(ep, urb, urb->status);
break;
default:
@ -1024,6 +1021,36 @@ static inline void _hcint_int(USBHDriver *host) {
/* Host interrupts. */
/*===========================================================================*/
static inline void _sof_int(USBHDriver *host) {
/* this is part of the workaround to the LS bug in the OTG core */
#undef HPRT_PLSTS_MASK
#define HPRT_PLSTS_MASK (3U<<10)
if (host->check_ls_activity) {
uint16_t remaining = host->otg->HFNUM >> 16;
if (remaining < 5975) {
uwarnf("LS: ISR called too late (time=%d)", 6000 - remaining);
return;
}
/* 15us loop */
for (;;) {
uint32_t line_status = host->otg->HPRT & HPRT_PLSTS_MASK;
remaining = host->otg->HFNUM >> 16;
if (line_status != HPRT_PLSTS_DM) {
uinfof("LS: activity detected, line=%d, time=%d", line_status >> 10, 6000 - remaining);
host->check_ls_activity = FALSE;
host->otg->GINTMSK = (host->otg->GINTMSK & ~GINTMSK_SOFM) | (GINTMSK_HCM | GINTMSK_RXFLVLM);
host->rootport.lld_status |= USBH_PORTSTATUS_ENABLE;
host->rootport.lld_c_status |= USBH_PORTSTATUS_C_ENABLE;
return;
}
if (remaining < 5910) {
uwarn("LS: No activity detected");
return;
}
}
}
/* real SOF interrupt */
udbg("SOF");
_try_commit_p(host, TRUE);
}
@ -1143,17 +1170,19 @@ static inline void _ptxfe_int(USBHDriver *host) {
uinfo("PTXFE");
}
static inline void _discint_int(USBHDriver *host) {
uint32_t hprt = host->otg->HPRT;
static void _disable(USBHDriver *host) {
host->rootport.lld_status &= ~(USBH_PORTSTATUS_CONNECTION | USBH_PORTSTATUS_ENABLE);
host->rootport.lld_c_status |= USBH_PORTSTATUS_C_CONNECTION | USBH_PORTSTATUS_C_ENABLE;
uwarn("\tDISCINT");
if (!(hprt & HPRT_PCSTS)) {
host->rootport.lld_status &= ~(USBH_PORTSTATUS_CONNECTION | USBH_PORTSTATUS_ENABLE);
host->rootport.lld_c_status |= USBH_PORTSTATUS_C_CONNECTION | USBH_PORTSTATUS_C_ENABLE;
}
_purge_active(host);
_purge_pending(host);
host->otg->GINTMSK &= ~(GINTMSK_HCM | GINTMSK_RXFLVLM);
}
static inline void _discint_int(USBHDriver *host) {
uinfo("DISCINT: Port disconnection detected");
_disable(host);
}
static inline void _hprtint_int(USBHDriver *host) {
@ -1169,8 +1198,6 @@ static inline void _hprtint_int(USBHDriver *host) {
uinfo("\tHPRT: Port connection detected");
host->rootport.lld_status |= USBH_PORTSTATUS_CONNECTION;
host->rootport.lld_c_status |= USBH_PORTSTATUS_C_CONNECTION;
} else {
uinfo("\tHPRT: Port disconnection detected");
}
}
@ -1178,9 +1205,32 @@ static inline void _hprtint_int(USBHDriver *host) {
hprt_clr |= HPRT_PENCHNG;
if (hprt & HPRT_PENA) {
uinfo("\tHPRT: Port enabled");
host->rootport.lld_status |= USBH_PORTSTATUS_ENABLE;
host->rootport.lld_status &= ~(USBH_PORTSTATUS_HIGH_SPEED | USBH_PORTSTATUS_LOW_SPEED);
/* configure FIFOs */
#define HNPTXFSIZ DIEPTXF0
#if STM32_USBH_USE_OTG1
#if STM32_USBH_USE_OTG2
if (&USBHD1 == host)
#endif
{
otg->GRXFSIZ = GRXFSIZ_RXFD(STM32_OTG1_RXFIFO_SIZE / 4);
otg->HNPTXFSIZ = HPTXFSIZ_PTXSA(STM32_OTG1_RXFIFO_SIZE / 4) | HPTXFSIZ_PTXFD(STM32_OTG1_NPTXFIFO_SIZE / 4);
otg->HPTXFSIZ = HPTXFSIZ_PTXSA((STM32_OTG1_RXFIFO_SIZE / 4) + (STM32_OTG1_NPTXFIFO_SIZE / 4)) | HPTXFSIZ_PTXFD(STM32_OTG1_PTXFIFO_SIZE / 4);
}
#endif
#if STM32_USBH_USE_OTG2
#if STM32_USBH_USE_OTG1
if (&USBHD2 == host)
#endif
{
otg->GRXFSIZ = GRXFSIZ_RXFD(STM32_OTG2_RXFIFO_SIZE / 4);
otg->HNPTXFSIZ = HPTXFSIZ_PTXSA(STM32_OTG2_RXFIFO_SIZE / 4) | HPTXFSIZ_PTXFD(STM32_OTG2_NPTXFIFO_SIZE / 4);
otg->HPTXFSIZ = HPTXFSIZ_PTXSA((STM32_OTG2_RXFIFO_SIZE / 4) + (STM32_OTG2_NPTXFIFO_SIZE / 4)) | HPTXFSIZ_PTXFD(STM32_OTG2_PTXFIFO_SIZE / 4);
}
#endif
#undef HNPTXFSIZ
/* Make sure the FIFOs are flushed. */
otg_txfifo_flush(host, 0x10);
otg_rxfifo_flush(host);
@ -1197,9 +1247,23 @@ static inline void _hprtint_int(USBHDriver *host) {
host->rootport.lld_status |= USBH_PORTSTATUS_LOW_SPEED;
otg->HFIR = 6000;
otg->HCFG = (otg->HCFG & ~HCFG_FSLSPCS_MASK) | HCFG_FSLSPCS_6;
/* Low speed devices connected to the STM32's internal transceiver sometimes
* don't behave correctly. Although HPRT reports a port enable, really
* no traffic is generated, and the core is non-functional. To avoid
* this we won't report the port enable until we are sure that the
* port is working. */
host->check_ls_activity = TRUE;
otg->GINTMSK |= GINTMSK_SOFM;
} else {
otg->HFIR = 48000;
otg->HCFG = (otg->HCFG & ~HCFG_FSLSPCS_MASK) | HCFG_FSLSPCS_48;
host->check_ls_activity = FALSE;
/* enable channel and rx interrupts */
otg->GINTMSK |= GINTMSK_HCM | GINTMSK_RXFLVLM;
host->rootport.lld_status |= USBH_PORTSTATUS_ENABLE;
host->rootport.lld_c_status |= USBH_PORTSTATUS_C_ENABLE;
}
} else {
if (hprt & HPRT_PCSTS) {
@ -1211,13 +1275,8 @@ static inline void _hprtint_int(USBHDriver *host) {
} else {
uerr("\tHPRT: Port disabled due to disconnect");
}
_purge_active(host);
_purge_pending(host);
host->rootport.lld_status &= ~USBH_PORTSTATUS_ENABLE;
_disable(host);
}
host->rootport.lld_c_status |= USBH_PORTSTATUS_C_ENABLE;
}
if (hprt & HPRT_POCCHNG) {
@ -1249,24 +1308,19 @@ static void usb_lld_serve_interrupt(USBHDriver *host) {
}
/* check mismatch */
if (gintsts & GINTSTS_MMIS) {
uerr("Mode Mismatch");
otg->GINTSTS = gintsts;
return;
}
osalDbgAssert((gintsts & GINTSTS_MMIS) == 0, "mode mismatch");
gintsts &= otg->GINTMSK;
#if USBH_DEBUG_ENABLE_WARNINGS
if (!gintsts) {
#if USBH_DEBUG_ENABLE_WARNINGS
uint32_t a, b;
a = otg->GINTSTS;
b = otg->GINTMSK;
uwarnf("GINTSTS=%08x, GINTMSK=%08x", a, b);
uwarnf("Masked bits caused an ISR: GINTSTS=%08x, GINTMSK=%08x (unhandled bits=%08x)", a, b, a & ~b);
#endif
return;
}
#endif
// otg->GINTMSK &= ~(GINTMSK_NPTXFEM | GINTMSK_PTXFEM);
otg->GINTSTS = gintsts;
if (gintsts & GINTSTS_SOF)
@ -1365,24 +1419,22 @@ static void _init(USBHDriver *host) {
#if STM32_USBH_USE_OTG1
#if STM32_USBH_USE_OTG2
if (&USBHD1 == host) {
if (&USBHD1 == host)
#endif
{
host->otg = OTG_FS;
host->channels_number = STM32_OTG1_CHANNELS_NUMBER;
#if STM32_USBH_USE_OTG2
}
#endif
#endif
#if STM32_USBH_USE_OTG2
#if STM32_USBH_USE_OTG1
if (&USBHD2 == host) {
if (&USBHD2 == host)
#endif
{
host->otg = OTG_HS;
host->channels_number = STM32_OTG2_CHANNELS_NUMBER;
#if STM32_USBH_USE_OTG1
}
#endif
#endif
INIT_LIST_HEAD(&host->ch_free[0]);
INIT_LIST_HEAD(&host->ch_free[1]);
@ -1417,8 +1469,9 @@ static void _usbh_start(USBHDriver *usbh) {
/* Clock activation.*/
#if STM32_USBH_USE_OTG1
#if STM32_USBH_USE_OTG2
if (&USBHD1 == usbh) {
if (&USBHD1 == usbh)
#endif
{
/* OTG FS clock enable and reset.*/
rccEnableOTG_FS(FALSE);
rccResetOTG_FS();
@ -1427,15 +1480,14 @@ static void _usbh_start(USBHDriver *usbh) {
/* Enables IRQ vector.*/
nvicEnableVector(STM32_OTG1_NUMBER, STM32_USB_OTG1_IRQ_PRIORITY);
#if STM32_USBH_USE_OTG2
}
#endif
#endif
#if STM32_USBH_USE_OTG2
#if STM32_USBH_USE_OTG1
if (&USBHD2 == usbh) {
if (&USBHD2 == usbh)
#endif
{
/* OTG HS clock enable and reset.*/
rccEnableOTG_HS(TRUE); // Enable HS clock when cpu is in sleep mode
rccDisableOTG_HSULPI(TRUE); // Disable HS ULPI clock when cpu is in sleep mode
@ -1445,9 +1497,7 @@ static void _usbh_start(USBHDriver *usbh) {
/* Enables IRQ vector.*/
nvicEnableVector(STM32_OTG2_NUMBER, STM32_USB_OTG2_IRQ_PRIORITY);
#if STM32_USBH_USE_OTG1
}
#endif
#endif
otgp->GUSBCFG = GUSBCFG_PHYSEL | GUSBCFG_TRDT(5);
@ -1487,41 +1537,11 @@ static void _usbh_start(USBHDriver *usbh) {
otgp->HPRT |= HPRT_PPWR;
/* without this delay, the FIFO sizes are set INcorrectly */
osalThreadSleepS(MS2ST(200));
#define HNPTXFSIZ DIEPTXF0
#if STM32_USBH_USE_OTG1
#if STM32_USBH_USE_OTG2
if (&USBHD1 == usbh) {
#endif
otgp->GRXFSIZ = GRXFSIZ_RXFD(STM32_OTG1_RXFIFO_SIZE / 4);
otgp->HNPTXFSIZ = HPTXFSIZ_PTXSA((STM32_OTG1_RXFIFO_SIZE / 4)) | HPTXFSIZ_PTXFD(STM32_OTG1_NPTXFIFO_SIZE / 4);
otgp->HPTXFSIZ = HPTXFSIZ_PTXSA((STM32_OTG1_RXFIFO_SIZE / 4) + (STM32_OTG1_NPTXFIFO_SIZE / 4)) | HPTXFSIZ_PTXFD(STM32_OTG1_PTXFIFO_SIZE / 4);
#if STM32_USBH_USE_OTG2
}
#endif
#endif
#if STM32_USBH_USE_OTG2
#if STM32_USBH_USE_OTG1
if (&USBHD2 == usbh) {
#endif
otgp->GRXFSIZ = GRXFSIZ_RXFD(STM32_OTG2_RXFIFO_SIZE / 4);
otgp->HNPTXFSIZ = HPTXFSIZ_PTXSA((STM32_OTG2_RXFIFO_SIZE / 4)) | HPTXFSIZ_PTXFD(STM32_OTG2_NPTXFIFO_SIZE / 4);
otgp->HPTXFSIZ = HPTXFSIZ_PTXSA((STM32_OTG2_RXFIFO_SIZE / 4) + (STM32_OTG2_NPTXFIFO_SIZE / 4)) | HPTXFSIZ_PTXFD(STM32_OTG2_PTXFIFO_SIZE / 4);
#if STM32_USBH_USE_OTG1
}
#endif
#endif
#undef HNPTXFSIZ
otg_txfifo_flush(usbh, 0x10);
otg_rxfifo_flush(usbh);
otgp->GINTSTS = 0xffffffff;
otgp->GINTMSK = GINTMSK_DISCM /*| GINTMSK_PTXFEM*/ | GINTMSK_HCM | GINTMSK_HPRTM
/*| GINTMSK_IPXFRM | GINTMSK_NPTXFEM*/ | GINTMSK_RXFLVLM
/*| GINTMSK_SOFM */ | GINTMSK_MMISM;
otgp->GINTMSK = GINTMSK_DISCM | GINTMSK_HPRTM | GINTMSK_MMISM;
usbh->rootport.lld_status = USBH_PORTSTATUS_POWER;
usbh->rootport.lld_c_status = 0;
@ -1586,7 +1606,7 @@ usbh_urbstatus_t usbh_lld_root_hub_request(USBHDriver *usbh, uint8_t bmRequestTy
break;
case USBH_PORT_FEAT_C_OVERCURRENT:
usbh->rootport.lld_c_status &= USBH_PORTSTATUS_C_OVERCURRENT;
usbh->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_OVERCURRENT;
break;
default:
@ -1597,18 +1617,7 @@ usbh_urbstatus_t usbh_lld_root_hub_request(USBHDriver *usbh, uint8_t bmRequestTy
break;
case GetHubDescriptor:
/*dev_dbg(hsotg->dev, "GetHubDescriptor\n");
hub_desc = (struct usb_hub_descriptor *)buf;
hub_desc->bDescLength = 9;
hub_desc->bDescriptorType = USB_DT_HUB;
hub_desc->bNbrPorts = 1;
hub_desc->wHubCharacteristics =
cpu_to_le16(HUB_CHAR_COMMON_LPSM |
HUB_CHAR_INDV_PORT_OCPM);
hub_desc->bPwrOn2PwrGood = 1;
hub_desc->bHubContrCurrent = 0;
hub_desc->u.hs.DeviceRemovable[0] = 0;
hub_desc->u.hs.DeviceRemovable[1] = 0xff;*/
osalDbgAssert(0, "unsupported");
break;
case GetHubStatus:
@ -1644,11 +1653,29 @@ usbh_urbstatus_t usbh_lld_root_hub_request(USBHDriver *usbh, uint8_t bmRequestTy
uint32_t hprt;
otg->PCGCCTL = 0;
hprt = otg->HPRT;
if (hprt & HPRT_PENA) {
/* This can occur when the OTG core doesn't generate traffic
* despite reporting a successful por enable */
uerr("Detected enabled port; resetting OTG core");
otg->GAHBCFG = 0;
osalThreadSleepS(MS2ST(20));
_usbh_start(usbh); /* this effectively resets the core */
osalThreadSleepS(MS2ST(100)); /* during this delay, the core generates connect ISR */
uinfo("OTG reset ended");
if (otg->HPRT & HPRT_PCSTS) {
/* if the device is still connected, don't report a C_CONNECTION flag, which would cause
* the upper layer to abort enumeration */
uinfo("Clear connection change flag");
usbh->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_CONNECTION;
}
}
/* note: writing PENA = 1 actually disables the port */
hprt &= ~(HPRT_PSUSP | HPRT_PENA | HPRT_PCDET | HPRT_PENCHNG | HPRT_POCCHNG );
hprt &= ~(HPRT_PSUSP | HPRT_PENA | HPRT_PCDET | HPRT_PENCHNG | HPRT_POCCHNG);
while ((otg->GRSTCTL & GRSTCTL_AHBIDL) == 0);
otg->HPRT = hprt | HPRT_PRST;
osalThreadSleepS(MS2ST(60));
osalThreadSleepS(MS2ST(15));
otg->HPRT = hprt;
osalThreadSleepS(MS2ST(10));
usbh->rootport.lld_c_status |= USBH_PORTSTATUS_C_RESET;
osalSysUnlock();
} break;
@ -1672,14 +1699,7 @@ usbh_urbstatus_t usbh_lld_root_hub_request(USBHDriver *usbh, uint8_t bmRequestTy
}
uint8_t usbh_lld_roothub_get_statuschange_bitmap(USBHDriver *usbh) {
osalSysLock();
if (usbh->rootport.lld_c_status) {
osalSysUnlock();
return 1 << 1;
}
osalSysUnlock();
return 0;
return usbh->rootport.lld_c_status ? (1 << 1) : 0;
}
#endif

View File

@ -63,6 +63,8 @@ typedef struct stm32_hc_management {
#define _usbhdriver_ll_data \
stm32_otg_t *otg; \
/* low-speed port reset bug */ \
bool check_ls_activity; \
/* channels */ \
uint8_t channels_number; \
stm32_hc_management_t channels[STM32_OTG2_CHANNELS_NUMBER]; \