From 02585210d1f33b3d5dd52267faa15576d5f7f8b2 Mon Sep 17 00:00:00 2001 From: Diego Ismirlian Date: Mon, 31 Jul 2017 18:48:23 -0300 Subject: 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) --- os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.c | 244 +++++++++++++++------------ os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.h | 2 + 2 files changed, 134 insertions(+), 112 deletions(-) diff --git a/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.c b/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.c index 3a581f5..3944a79 100644 --- a/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.c +++ b/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.c @@ -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; - - uwarn("\tDISCINT"); +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; - 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 diff --git a/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.h b/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.h index a2b7628..fd7f4e0 100644 --- a/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.h +++ b/os/hal/ports/STM32/LLD/USBHv1/hal_usbh_lld.h @@ -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]; \ -- cgit v1.2.3