/* ChibiOS - Copyright (C) 2006..2017 Giovanni Di Sirio Copyright (C) 2015..2019 Diego Ismirlian, (dismirlian(at)google's mail) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "hal.h" #if HAL_USE_USBH #include "usbh/internal.h" #include #if STM32_USBH_USE_OTG1 #if !defined(STM32_OTG1_CHANNELS_NUMBER) #error "STM32_OTG1_CHANNELS_NUMBER must be defined" #endif #if !defined(STM32_OTG1_RXFIFO_SIZE) #define STM32_OTG1_RXFIFO_SIZE 1024 #endif #if !defined(STM32_OTG1_PTXFIFO_SIZE) #define STM32_OTG1_PTXFIFO_SIZE 128 #endif #if !defined(STM32_OTG1_NPTXFIFO_SIZE) #define STM32_OTG1_NPTXFIFO_SIZE 128 #endif #if (STM32_OTG1_RXFIFO_SIZE + STM32_OTG1_PTXFIFO_SIZE + STM32_OTG1_NPTXFIFO_SIZE) > (STM32_OTG1_FIFO_MEM_SIZE * 4) #error "Not enough memory in OTG1 implementation" #elif (STM32_OTG1_RXFIFO_SIZE + STM32_OTG1_PTXFIFO_SIZE + STM32_OTG1_NPTXFIFO_SIZE) < (STM32_OTG1_FIFO_MEM_SIZE * 4) #warning "Spare memory in OTG1; could enlarge RX, PTX or NPTX FIFO sizes" #endif #if (STM32_OTG1_RXFIFO_SIZE % 4) || (STM32_OTG1_PTXFIFO_SIZE % 4) || (STM32_OTG1_NPTXFIFO_SIZE % 4) #error "FIFO sizes must be a multiple of 32-bit words" #endif #endif #if STM32_USBH_USE_OTG2 #if !defined(STM32_OTG2_CHANNELS_NUMBER) #error "STM32_OTG2_CHANNELS_NUMBER must be defined" #endif #if !defined(STM32_OTG2_RXFIFO_SIZE) #define STM32_OTG2_RXFIFO_SIZE 2048 #endif #if !defined(STM32_OTG2_PTXFIFO_SIZE) #define STM32_OTG2_PTXFIFO_SIZE 1024 #endif #if !defined(STM32_OTG2_NPTXFIFO_SIZE) #define STM32_OTG2_NPTXFIFO_SIZE 1024 #endif #if (STM32_OTG2_RXFIFO_SIZE + STM32_OTG2_PTXFIFO_SIZE + STM32_OTG2_NPTXFIFO_SIZE) > (STM32_OTG2_FIFO_MEM_SIZE * 4) #error "Not enough memory in OTG2 implementation" #elif (STM32_OTG2_RXFIFO_SIZE + STM32_OTG2_PTXFIFO_SIZE + STM32_OTG2_NPTXFIFO_SIZE) < (STM32_OTG2_FIFO_MEM_SIZE * 4) #warning "Spare memory in OTG2; could enlarge RX, PTX or NPTX FIFO sizes" #endif #if (STM32_OTG2_RXFIFO_SIZE % 4) || (STM32_OTG2_PTXFIFO_SIZE % 4) || (STM32_OTG2_NPTXFIFO_SIZE % 4) #error "FIFO sizes must be a multiple of 32-bit words" #endif #endif #define _USBH_DEBUG_HELPER_ENABLE_TRACE USBH_LLD_DEBUG_ENABLE_TRACE #define _USBH_DEBUG_HELPER_ENABLE_INFO USBH_LLD_DEBUG_ENABLE_INFO #define _USBH_DEBUG_HELPER_ENABLE_WARNINGS USBH_LLD_DEBUG_ENABLE_WARNINGS #define _USBH_DEBUG_HELPER_ENABLE_ERRORS USBH_LLD_DEBUG_ENABLE_ERRORS #include "usbh/debug_helpers.h" static void _transfer_completedI(usbh_ep_t *ep, usbh_urb_t *urb, usbh_urbstatus_t status); static void _try_commit_np(USBHDriver *host); static void otg_rxfifo_flush(USBHDriver *usbp); static void otg_txfifo_flush(USBHDriver *usbp, uint32_t fifo); #if STM32_USBH_USE_OTG1 USBHDriver USBHD1; #endif #if STM32_USBH_USE_OTG2 USBHDriver USBHD2; #endif /*===========================================================================*/ /* Little helper functions. */ /*===========================================================================*/ static inline void _move_to_pending_queue(usbh_ep_t *ep) { list_move_tail(&ep->node, ep->pending_list); } static inline usbh_urb_t *_active_urb(usbh_ep_t *ep) { return list_first_entry(&ep->urb_list, usbh_urb_t, node); } static inline void _save_dt_mask(usbh_ep_t *ep, uint32_t hctsiz) { ep->dt_mask = hctsiz & HCTSIZ_DPID_MASK; } /*===========================================================================*/ /* Functions called from many places. */ /*===========================================================================*/ static void _transfer_completedI(usbh_ep_t *ep, usbh_urb_t *urb, usbh_urbstatus_t status) { osalDbgCheckClassI(); urb->queued = FALSE; /* remove URB from EP's queue */ list_del_init(&urb->node); /* Call the callback function now, so that if it calls usbhURBSubmitI, * the list_empty check below will be false. Also, note that the * if (list_empty(&ep->node)) { * ... * } * in usbh_lld_urb_submit will be false, since the endpoint is * still in the active queue. */ _usbh_urb_completeI(urb, status); if (list_empty(&ep->urb_list)) { /* no more URBs to process in this EP, remove EP from the host's queue */ list_del_init(&ep->node); } else { /* more URBs to process */ _move_to_pending_queue(ep); } } static void _halt_channel(USBHDriver *host, stm32_hc_management_t *hcm, usbh_lld_halt_reason_t reason) { (void)host; if (hcm->halt_reason != USBH_LLD_HALTREASON_NONE) { #if USBH_DEBUG_ENABLE && USBH_LLD_DEBUG_ENABLE_WARNINGS usbh_ep_t *const ep = hcm->ep; uepwarnf("Repeated halt (original=%d, new=%d)", hcm->halt_reason, reason); #endif return; } #if CH_DBG_ENABLE_CHECKS if (usbhEPIsPeriodic(hcm->ep)) { osalDbgCheck(host->otg->HPTXSTS & HPTXSTS_PTXQSAV_MASK); } else { osalDbgCheck(host->otg->HNPTXSTS & HPTXSTS_PTXQSAV_MASK); } #endif hcm->halt_reason = reason; hcm->hc->HCCHAR |= HCCHAR_CHENA | HCCHAR_CHDIS; } static void _release_channel(USBHDriver *host, stm32_hc_management_t *hcm) { usbh_ep_t *const ep = hcm->ep; #if USBH_DEBUG_ENABLE && USBH_LLD_DEBUG_ENABLE_TRACE static const char *reason[] = {"XFRC", "XFRC", "NAK", "STALL", "ERROR", "ABORT"}; uepdbgf("release (%s)", reason[hcm->halt_reason]); #endif hcm->hc->HCINTMSK = 0; host->otg->HAINTMSK &= ~hcm->haintmsk; hcm->halt_reason = USBH_LLD_HALTREASON_NONE; if (usbhEPIsPeriodic(ep)) { list_add(&hcm->node, &host->ch_free[0]); } else { list_add(&hcm->node, &host->ch_free[1]); } ep->xfer.hcm = 0; hcm->ep = 0; } static bool _activate_ep(USBHDriver *host, usbh_ep_t *ep) { struct list_head *list; uint16_t spc; osalDbgCheck(ep->xfer.hcm == NULL); if (usbhEPIsPeriodic(ep)) { list = &host->ch_free[0]; spc = (host->otg->HPTXSTS >> 16) & 0xff; } else { list = &host->ch_free[1]; spc = (host->otg->HNPTXSTS >> 16) & 0xff; } if (list_empty(list)) { uepwarnf("No free %s channels", usbhEPIsPeriodic(ep) ? "P" : "NP"); return FALSE; } if (spc <= STM32_USBH_MIN_QSPACE) { uepwarnf("No space in %s Queue (spc=%d)", usbhEPIsPeriodic(ep) ? "P" : "NP", spc); return FALSE; } /* get the first channel */ stm32_hc_management_t *hcm = list_first_entry(list, stm32_hc_management_t, node); osalDbgCheck((hcm->halt_reason == USBH_LLD_HALTREASON_NONE) && (hcm->ep == NULL)); usbh_urb_t *const urb = _active_urb(ep); uint32_t hcintmsk = ep->hcintmsk; uint32_t hcchar = ep->hcchar; uint16_t mps = ep->wMaxPacketSize; uint32_t xfer_packets; uint32_t xfer_len = 0; //Initialize just to shut up a compiler warning osalDbgCheck(urb->status == USBH_URBSTATUS_PENDING); /* check if the URB is a new one, or we must continue a previously started URB */ if (urb->queued == FALSE) { /* prepare EP for a new URB */ if (ep->type == USBH_EPTYPE_CTRL) { xfer_len = 8; ep->xfer.buf = (uint8_t *)urb->setup_buff; ep->dt_mask = HCTSIZ_DPID_SETUP; ep->in = FALSE; ep->xfer.u.ctrl_phase = USBH_LLD_CTRLPHASE_SETUP; } else { xfer_len = urb->requestedLength; ep->xfer.buf = urb->buff; } ep->xfer.error_count = 0; } else { osalDbgCheck(urb->requestedLength >= urb->actualLength); if (ep->type == USBH_EPTYPE_CTRL) { switch (ep->xfer.u.ctrl_phase) { case USBH_LLD_CTRLPHASE_SETUP: xfer_len = 8; ep->xfer.buf = (uint8_t *)urb->setup_buff; ep->dt_mask = HCTSIZ_DPID_SETUP; break; case USBH_LLD_CTRLPHASE_DATA: xfer_len = urb->requestedLength - urb->actualLength; ep->xfer.buf = (uint8_t *) urb->buff + urb->actualLength; break; case USBH_LLD_CTRLPHASE_STATUS: xfer_len = 0; ep->dt_mask = HCTSIZ_DPID_DATA1; ep->xfer.error_count = 0; break; default: osalDbgCheck(0); } if (ep->in) { hcintmsk |= HCINTMSK_DTERRM | HCINTMSK_BBERRM; hcchar |= HCCHAR_EPDIR; } } else { xfer_len = urb->requestedLength - urb->actualLength; ep->xfer.buf = (uint8_t *) urb->buff + urb->actualLength; } if (ep->xfer.error_count) hcintmsk |= HCINTMSK_ACKM; } ep->xfer.partial = 0; if (ep->type == USBH_EPTYPE_ISO) { ep->dt_mask = HCTSIZ_DPID_DATA0; /* [USB 2.0 spec, 5.6.4]: A host must not issue more than 1 * transaction in a (micro)frame for an isochronous endpoint * unless the endpoint is high-speed, high-bandwidth. */ if (xfer_len > mps) xfer_len = mps; } else if (xfer_len > 0x7FFFF) { xfer_len = 0x7FFFF - mps + 1; } /* calculate required packets */ if (xfer_len) { xfer_packets = (xfer_len + mps - 1) / mps; if (xfer_packets > 0x3FF) { xfer_packets = 0x3FF; xfer_len = xfer_packets * mps; } } else { xfer_packets = 1; /* Need 1 packet for transfer length of 0 */ } if (ep->in) xfer_len = xfer_packets * mps; /* Clear old interrupt conditions, * configure transfer size, * enable required interrupts */ stm32_otg_host_chn_t *const hc = hcm->hc; hc->HCINT = 0xffffffff; hc->HCTSIZ = ep->dt_mask | HCTSIZ_PKTCNT(xfer_packets) | HCTSIZ_XFRSIZ(xfer_len); hc->HCINTMSK = hcintmsk; /* Queue the transfer for the next frame (no effect for non-periodic transfers) */ if (!(host->otg->HFNUM & 1)) hcchar |= HCCHAR_ODDFRM; /* configure channel characteristics and queue a request */ hc->HCCHAR = hcchar; if (ep->in && (xfer_packets > 1)) { /* For IN transfers, try to queue two back-to-back packets. * This results in a 1% performance gain for Full Speed transfers */ if (--spc > STM32_USBH_MIN_QSPACE) { hc->HCCHAR |= HCCHAR_CHENA; } else { uepwarnf("Could not queue back-to-back packets"); } } if (urb->queued == FALSE) { urb->queued = TRUE; uepdbgf("Start (%dB)", xfer_len); } else { uepdbgf("Restart (%dB)", xfer_len); } ep->xfer.len = xfer_len; ep->xfer.packets = (uint16_t)xfer_packets; /* remove the channel from the free list, link endpoint <-> channel and move to the active queue*/ list_del(&hcm->node); ep->xfer.hcm = hcm; hcm->ep = ep; list_move_tail(&ep->node, ep->active_list); stm32_otg_t *const otg = host->otg; /* enable this channel's interrupt and global channel interrupt */ otg->HAINTMSK |= hcm->haintmsk; if (ep->in) { otg->GINTMSK |= GINTMSK_HCM; } else if (usbhEPIsPeriodic(ep)) { otg->GINTMSK |= GINTMSK_HCM | GINTMSK_PTXFEM; } else { //TODO: write to the FIFO now otg->GINTMSK |= GINTMSK_HCM | GINTMSK_NPTXFEM; } return TRUE; } static bool _update_urb(usbh_ep_t *ep, uint32_t hctsiz, usbh_urb_t *urb, bool completed) { uint32_t len; if (!completed) { len = ep->wMaxPacketSize * (ep->xfer.packets - ((hctsiz & HCTSIZ_PKTCNT_MASK) >> 19)); } else { if (ep->in) { len = ep->xfer.len - ((hctsiz & HCTSIZ_XFRSIZ_MASK) >> 0); } else { len = ep->xfer.len; } osalDbgCheck(len == ep->xfer.partial); //TODO: if len == ep->xfer.partial, use this instead of the above code } #if 0 osalDbgAssert(urb->actualLength + len <= urb->requestedLength, "what happened?"); #else if (urb->actualLength + len > urb->requestedLength) { ueperrf("Trimming actualLength %u -> %u", urb->actualLength + len, urb->requestedLength); urb->actualLength = urb->requestedLength; return TRUE; } #endif urb->actualLength += len; if ((urb->actualLength == urb->requestedLength) || (ep->in && completed && (hctsiz & HCTSIZ_XFRSIZ_MASK))) return TRUE; return FALSE; } static void _try_commit_np(USBHDriver *host) { usbh_ep_t *item, *tmp; list_for_each_entry_safe(item, usbh_ep_t, tmp, &host->ep_pending_lists[USBH_EPTYPE_CTRL], node) { if (!_activate_ep(host, item)) return; } list_for_each_entry_safe(item, usbh_ep_t, tmp, &host->ep_pending_lists[USBH_EPTYPE_BULK], node) { if (!_activate_ep(host, item)) return; } } static void _try_commit_p(USBHDriver *host, bool sof) { usbh_ep_t *item, *tmp; list_for_each_entry_safe(item, usbh_ep_t, tmp, &host->ep_pending_lists[USBH_EPTYPE_ISO], node) { if (!_activate_ep(host, item)) return; } list_for_each_entry_safe(item, usbh_ep_t, tmp, &host->ep_pending_lists[USBH_EPTYPE_INT], node) { osalDbgCheck(item); /* TODO: improve this */ if (sof && item->xfer.u.frame_counter) --item->xfer.u.frame_counter; if (item->xfer.u.frame_counter == 0) { if (!_activate_ep(host, item)) return; item->xfer.u.frame_counter = item->bInterval; } } if (list_empty(&host->ep_pending_lists[USBH_EPTYPE_ISO]) && list_empty(&host->ep_pending_lists[USBH_EPTYPE_INT])) { host->otg->GINTMSK &= ~GINTMSK_SOFM; } else { host->otg->GINTMSK |= GINTMSK_SOFM; } } static void _purge_queue(USBHDriver *host, struct list_head *list) { usbh_ep_t *ep, *tmp; list_for_each_entry_safe(ep, usbh_ep_t, tmp, list, node) { usbh_urb_t *const urb = _active_urb(ep); stm32_hc_management_t *const hcm = ep->xfer.hcm; uepwarnf("Abort URB, USBH_URBSTATUS_DISCONNECTED"); if (hcm) { uepwarnf("URB had channel %d assigned, halt_reason = %d", hcm - host->channels, hcm->halt_reason); _release_channel(host, hcm); _update_urb(ep, hcm->hc->HCTSIZ, urb, FALSE); } _transfer_completedI(ep, urb, USBH_URBSTATUS_DISCONNECTED); } } static void _purge_active(USBHDriver *host) { _purge_queue(host, &host->ep_active_lists[0]); _purge_queue(host, &host->ep_active_lists[1]); _purge_queue(host, &host->ep_active_lists[2]); _purge_queue(host, &host->ep_active_lists[3]); } static void _purge_pending(USBHDriver *host) { _purge_queue(host, &host->ep_pending_lists[0]); _purge_queue(host, &host->ep_pending_lists[1]); _purge_queue(host, &host->ep_pending_lists[2]); _purge_queue(host, &host->ep_pending_lists[3]); } static uint32_t _write_packet(struct list_head *list, uint32_t space_available) { usbh_ep_t *ep; uint32_t remaining = 0; list_for_each_entry(ep, usbh_ep_t, list, node) { if (ep->in || (ep->xfer.hcm->halt_reason != USBH_LLD_HALTREASON_NONE)) continue; int32_t rem = ep->xfer.len - ep->xfer.partial; osalDbgCheck(rem >= 0); if (rem <= 0) continue; remaining += rem; if (!space_available) { if (remaining) break; continue; } /* write one packet only */ if (rem > ep->wMaxPacketSize) rem = ep->wMaxPacketSize; /* round up to dwords */ uint32_t words = (rem + 3) / 4; if (words > space_available) words = space_available; space_available -= words; uint32_t written = words * 4; if ((int32_t)written > rem) written = rem; volatile uint32_t *dest = ep->xfer.hcm->fifo; uint32_t *src = (uint32_t *)ep->xfer.buf; uepdbgf("write %d words (%dB), partial=%d", words, written, ep->xfer.partial); while (words--) { *dest = *src++; } ep->xfer.buf += written; ep->xfer.partial += written; remaining -= written; } return remaining; } /*===========================================================================*/ /* API. */ /*===========================================================================*/ void usbh_lld_ep_object_init(usbh_ep_t *ep) { /* CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) * STALL si solo DAT/STAT si si si si no no ep->type != ISO && (ep->type != CTRL || ctrlphase != SETUP) * ACK si si si si si si no no ep->type != ISO * NAK si si si si si si no no ep->type != ISO * BBERR si no si no si no si no ep->in * TRERR si si si si si si si no ep->type != ISO || ep->in * DTERR si no si no si no no no ep->type != ISO && ep->in * FRMOR no no si si no no si si ep->type = PERIODIC */ USBHDriver *host = ep->device->host; uint32_t hcintmsk = HCINTMSK_CHHM | HCINTMSK_XFRCM | HCINTMSK_AHBERRM; switch (ep->type) { case USBH_EPTYPE_ISO: hcintmsk |= HCINTMSK_FRMORM; if (ep->in) { hcintmsk |= HCINTMSK_TRERRM | HCINTMSK_BBERRM; } break; case USBH_EPTYPE_INT: hcintmsk |= HCINTMSK_TRERRM | HCINTMSK_FRMORM | HCINTMSK_STALLM | HCINTMSK_NAKM; if (ep->in) { hcintmsk |= HCINTMSK_DTERRM | HCINTMSK_BBERRM; } ep->xfer.u.frame_counter = 1; break; case USBH_EPTYPE_CTRL: hcintmsk |= HCINTMSK_TRERRM | HCINTMSK_STALLM | HCINTMSK_NAKM; break; case USBH_EPTYPE_BULK: hcintmsk |= HCINTMSK_TRERRM | HCINTMSK_STALLM | HCINTMSK_NAKM; if (ep->in) { hcintmsk |= HCINTMSK_DTERRM | HCINTMSK_BBERRM; } break; default: chDbgCheck(0); } ep->active_list = &host->ep_active_lists[ep->type]; ep->pending_list = &host->ep_pending_lists[ep->type]; INIT_LIST_HEAD(&ep->urb_list); INIT_LIST_HEAD(&ep->node); ep->hcintmsk = hcintmsk; ep->hcchar = HCCHAR_CHENA | HCCHAR_DAD(ep->device->address) | HCCHAR_MCNT(1) | HCCHAR_EPTYP(ep->type) | ((ep->device->speed == USBH_DEVSPEED_LOW) ? HCCHAR_LSDEV : 0) | (ep->in ? HCCHAR_EPDIR : 0) | HCCHAR_EPNUM(ep->address) | HCCHAR_MPS(ep->wMaxPacketSize); } void usbh_lld_ep_open(usbh_ep_t *ep) { uepinfof("Open EP"); ep->status = USBH_EPSTATUS_OPEN; } void usbh_lld_ep_close(usbh_ep_t *ep) { usbh_urb_t *urb; uepinfof("Closing EP..."); while (!list_empty(&ep->urb_list)) { urb = list_first_entry(&ep->urb_list, usbh_urb_t, node); uepinfof("Abort URB, USBH_URBSTATUS_DISCONNECTED"); _usbh_urb_abort_and_waitS(urb, USBH_URBSTATUS_DISCONNECTED); } uepinfof("Closed"); ep->status = USBH_EPSTATUS_CLOSED; } bool usbh_lld_ep_reset(usbh_ep_t *ep) { ep->dt_mask = HCTSIZ_DPID_DATA0; return TRUE; } 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)) { uepwarnf("Can't submit URB, port disabled"); _usbh_urb_completeI(urb, USBH_URBSTATUS_DISCONNECTED); return; } /* add the URB to the EP's queue */ list_add_tail(&urb->node, &ep->urb_list); /* check if the EP wasn't in any queue (pending nor active) */ if (list_empty(&ep->node)) { /* add the EP to the pending queue */ _move_to_pending_queue(ep); if (usbhEPIsPeriodic(ep)) { host->otg->GINTMSK |= GINTMSK_SOFM; } else { /* try to queue non-periodic transfers */ _try_commit_np(ep->device->host); } } } /* usbh_lld_urb_abort may require a reschedule if called from a S-locked state */ bool usbh_lld_urb_abort(usbh_urb_t *urb, usbh_urbstatus_t status) { osalDbgCheck(usbhURBIsBusy(urb)); usbh_ep_t *const ep = urb->ep; osalDbgCheck(ep); stm32_hc_management_t *const hcm = ep->xfer.hcm; if ((hcm != NULL) && (urb == _active_urb(ep))) { /* This URB is active (channel assigned, top of the EP's URB list) */ if (hcm->halt_reason == USBH_LLD_HALTREASON_NONE) { /* The channel is not being halted */ uepinfof("usbh_lld_urb_abort: channel is not being halted"); urb->status = status; _halt_channel(ep->device->host, hcm, USBH_LLD_HALTREASON_ABORT); } else { /* The channel is being halted, so we can't re-halt it. The CHH interrupt will * be in charge of completing the transfer, but the URB will not have the specified status. */ uepinfof("usbh_lld_urb_abort: channel is being halted"); } return FALSE; } /* This URB is inactive, we can cancel it now */ uepinfof("usbh_lld_urb_abort: URB is not active"); _transfer_completedI(ep, urb, status); return TRUE; } /*===========================================================================*/ /* Channel Interrupts. */ /*===========================================================================*/ //CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // si si si si si si no no ep->type != ISO && !ep->in static inline void _ack_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { (void)host; usbh_ep_t *const ep = hcm->ep; osalDbgAssert(ep->type != USBH_EPTYPE_ISO, "ACK should not happen in ISO endpoints"); ep->xfer.error_count = 0; hc->HCINTMSK &= ~HCINTMSK_ACKM; uepdbgf("ACK"); } //CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // si no si no si no no no ep->type != ISO && ep->in static inline void _dterr_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { (void)host; usbh_ep_t *const ep = hcm->ep; osalDbgAssert(ep->in && (ep->type != USBH_EPTYPE_ISO), "DTERR should not happen in OUT or ISO endpoints"); #if 0 hc->HCINTMSK &= ~(HCINTMSK_DTERRM | HCINTMSK_ACKM); ep->xfer.error_count = 0; _halt_channel(host, hcm, USBH_LLD_HALTREASON_ERROR); #else /* restart directly, no need to halt it in this case */ ep->xfer.error_count = 0; hc->HCINTMSK &= ~HCINTMSK_ACKM; hc->HCCHAR |= HCCHAR_CHENA; #endif ueperrf("DTERR"); } //CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // si no si no si no si no ep->in static inline void _bberr_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { usbh_ep_t *const ep = hcm->ep; osalDbgAssert(ep->in, "BBERR should not happen in OUT endpoints"); hc->HCINTMSK &= ~HCINTMSK_BBERRM; ep->xfer.error_count = 3; _halt_channel(host, hcm, USBH_LLD_HALTREASON_ERROR); ueperrf("BBERR"); } ///CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // si si si si si si si no ep->type != ISO || ep->in static inline void _trerr_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { usbh_ep_t *const ep = hcm->ep; osalDbgAssert(ep->in || (ep->type != USBH_EPTYPE_ISO), "TRERR should not happen in ISO OUT endpoints"); hc->HCINTMSK &= ~HCINTMSK_TRERRM; ++ep->xfer.error_count; _halt_channel(host, hcm, USBH_LLD_HALTREASON_ERROR); ueperrf("TRERR"); } //CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // no no si si no no si si ep->type = PERIODIC static inline void _frmor_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { usbh_ep_t *const ep = hcm->ep; osalDbgAssert(usbhEPIsPeriodic(ep), "FRMOR should not happen in non-periodic endpoints"); hc->HCINTMSK &= ~HCINTMSK_FRMORM; ep->xfer.error_count = 3; _halt_channel(host, hcm, USBH_LLD_HALTREASON_ERROR); ueperrf("FRMOR"); } //CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // si si si si si si no no ep->type != ISO static inline void _nak_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { usbh_ep_t *const ep = hcm->ep; osalDbgAssert(hcm->ep->type != USBH_EPTYPE_ISO, "NAK should not happen in ISO endpoints"); if (!ep->in || (ep->type == USBH_EPTYPE_INT)) { hc->HCINTMSK &= ~HCINTMSK_NAKM; _halt_channel(host, hcm, USBH_LLD_HALTREASON_NAK); } else { /* restart directly, no need to halt it in this case */ ep->xfer.error_count = 0; hc->HCINTMSK &= ~HCINTMSK_ACKM; hc->HCCHAR |= HCCHAR_CHENA; } uepdbgf("NAK"); } //CTRL(IN) CTRL(OUT) INT(IN) INT(OUT) BULK(IN) BULK(OUT) ISO(IN) ISO(OUT) // si solo DAT/STAT si si si si no no ep->type != ISO && (ep->type != CTRL || ctrlphase != SETUP) static inline void _stall_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { osalDbgAssert(hcm->ep->type != USBH_EPTYPE_ISO, "STALL should not happen in ISO endpoints"); hc->HCINTMSK &= ~HCINTMSK_STALLM; _halt_channel(host, hcm, USBH_LLD_HALTREASON_STALL); #if USBH_DEBUG_ENABLE && USBH_LLD_DEBUG_ENABLE_WARNINGS usbh_ep_t *const ep = hcm->ep; uepwarnf("STALL"); #endif } static void _complete_bulk_int(USBHDriver *host, stm32_hc_management_t *hcm, usbh_ep_t *ep, usbh_urb_t *urb, uint32_t hctsiz) { _release_channel(host, hcm); _save_dt_mask(ep, hctsiz); if (_update_urb(ep, hctsiz, urb, TRUE)) { uepdbgf("done"); _transfer_completedI(ep, urb, USBH_URBSTATUS_OK); } else { osalDbgCheck(urb->requestedLength > 0x7FFFF); uepwarnf("incomplete"); _move_to_pending_queue(ep); } if (usbhEPIsPeriodic(ep)) { _try_commit_p(host, FALSE); } else { _try_commit_np(host); } } static void _complete_control(USBHDriver *host, stm32_hc_management_t *hcm, usbh_ep_t *ep, usbh_urb_t *urb, uint32_t hctsiz) { osalDbgCheck(ep->xfer.u.ctrl_phase != USBH_LLD_CTRLPHASE_SETUP); _release_channel(host, hcm); if (ep->xfer.u.ctrl_phase == USBH_LLD_CTRLPHASE_DATA) { if (_update_urb(ep, hctsiz, urb, TRUE)) { uepdbgf("DATA done"); ep->xfer.u.ctrl_phase = USBH_LLD_CTRLPHASE_STATUS; ep->in = !ep->in; } else { osalDbgCheck(urb->requestedLength > 0x7FFFF); uepwarnf("DATA incomplete"); _save_dt_mask(ep, hctsiz); } _move_to_pending_queue(ep); } else { osalDbgCheck(ep->xfer.u.ctrl_phase == USBH_LLD_CTRLPHASE_STATUS); uepdbgf("STATUS done"); _transfer_completedI(ep, urb, USBH_URBSTATUS_OK); } _try_commit_np(host); } static void _complete_control_setup(USBHDriver *host, stm32_hc_management_t *hcm, usbh_ep_t *ep, usbh_urb_t *urb) { _release_channel(host, hcm); if (urb->requestedLength) { uepdbgf("SETUP done -> DATA"); ep->xfer.u.ctrl_phase = USBH_LLD_CTRLPHASE_DATA; ep->in = *((uint8_t *)urb->setup_buff) & 0x80 ? TRUE : FALSE; ep->dt_mask = HCTSIZ_DPID_DATA1; ep->xfer.error_count = 0; } else { uepdbgf("SETUP done -> STATUS"); ep->in = TRUE; ep->xfer.u.ctrl_phase = USBH_LLD_CTRLPHASE_STATUS; } _move_to_pending_queue(ep); _try_commit_np(host); } static void _complete_iso(USBHDriver *host, stm32_hc_management_t *hcm, usbh_ep_t *ep, usbh_urb_t *urb, uint32_t hctsiz) { uepdbgf("done"); _release_channel(host, hcm); _update_urb(ep, hctsiz, urb, TRUE); _transfer_completedI(ep, urb, USBH_URBSTATUS_OK); _try_commit_p(host, FALSE); } static inline void _xfrc_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { usbh_ep_t *const ep = hcm->ep; usbh_urb_t *const urb = _active_urb(ep); osalDbgCheck(urb); uint32_t hctsiz = hc->HCTSIZ; hc->HCINTMSK &= ~HCINTMSK_XFRCM; switch (ep->type) { case USBH_EPTYPE_CTRL: if (ep->xfer.u.ctrl_phase == USBH_LLD_CTRLPHASE_SETUP) { _complete_control_setup(host, hcm, ep, urb); } else if (ep->in) { _halt_channel(host, hcm, USBH_LLD_HALTREASON_XFRC); } else { _complete_control(host, hcm, ep, urb, hctsiz); } break; case USBH_EPTYPE_BULK: if (ep->in) { _halt_channel(host, hcm, USBH_LLD_HALTREASON_XFRC); } else { _complete_bulk_int(host, hcm, ep, urb, hctsiz); } break; case USBH_EPTYPE_INT: if (ep->in && (hctsiz & HCTSIZ_PKTCNT_MASK)) { _halt_channel(host, hcm, USBH_LLD_HALTREASON_XFRC); } else { _complete_bulk_int(host, hcm, ep, urb, hctsiz); } break; case USBH_EPTYPE_ISO: if (ep->in && (hctsiz & HCTSIZ_PKTCNT_MASK)) { _halt_channel(host, hcm, USBH_LLD_HALTREASON_XFRC); } else { _complete_iso(host, hcm, ep, urb, hctsiz); } break; } } static inline void _chh_int(USBHDriver *host, stm32_hc_management_t *hcm, stm32_otg_host_chn_t *hc) { usbh_ep_t *const ep = hcm->ep; usbh_urb_t *const urb = _active_urb(ep); osalDbgCheck(urb); uint32_t hctsiz = hc->HCTSIZ; usbh_lld_halt_reason_t reason = hcm->halt_reason; //osalDbgCheck(reason != USBH_LLD_HALTREASON_NONE); if (reason == USBH_LLD_HALTREASON_NONE) { uwarnf("\tCHH: ch=%d, USBH_LLD_HALTREASON_NONE", hcm - host->channels); return; } if (reason == USBH_LLD_HALTREASON_XFRC) { osalDbgCheck(ep->in); switch (ep->type) { case USBH_EPTYPE_CTRL: _complete_control(host, hcm, ep, urb, hctsiz); break; case USBH_EPTYPE_BULK: case USBH_EPTYPE_INT: _complete_bulk_int(host, hcm, ep, urb, hctsiz); break; case USBH_EPTYPE_ISO: _complete_iso(host, hcm, ep, urb, hctsiz); break; } } else { _release_channel(host, hcm); _save_dt_mask(ep, hctsiz); bool done = _update_urb(ep, hctsiz, urb, FALSE); switch (reason) { case USBH_LLD_HALTREASON_NAK: if ((ep->type == USBH_EPTYPE_INT) && ep->in) { _transfer_completedI(ep, urb, USBH_URBSTATUS_TIMEOUT); } else { ep->xfer.error_count = 0; _move_to_pending_queue(ep); } break; case USBH_LLD_HALTREASON_STALL: if (ep->type == USBH_EPTYPE_CTRL) { if (ep->xfer.u.ctrl_phase == USBH_LLD_CTRLPHASE_SETUP) { ueperrf("Faulty device: STALLed SETUP phase"); } } else { ep->status = USBH_EPSTATUS_HALTED; } _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_completedI(ep, urb, USBH_URBSTATUS_ERROR); } else { ueperrf("err=%d, done=%d, retry", ep->xfer.error_count, done); _move_to_pending_queue(ep); } break; case USBH_LLD_HALTREASON_ABORT: uepwarnf("Abort"); _transfer_completedI(ep, urb, urb->status); break; default: osalDbgCheck(0); break; } if (usbhEPIsPeriodic(ep)) { _try_commit_p(host, FALSE); } else { _try_commit_np(host); } } } static void _hcint_n_int(USBHDriver *host, uint8_t chn) { stm32_hc_management_t *const hcm = &host->channels[chn]; stm32_otg_host_chn_t *const hc = hcm->hc; uint32_t hcint = hc->HCINT; hcint &= hc->HCINTMSK; hc->HCINT = hcint; osalDbgCheck((hcint & HCINTMSK_AHBERRM) == 0); osalDbgCheck(hcm->ep); if (hcint & HCINTMSK_STALLM) _stall_int(host, hcm, hc); if (hcint & HCINTMSK_NAKM) _nak_int(host, hcm, hc); if (hcint & HCINTMSK_ACKM) _ack_int(host, hcm, hc); if (hcint & HCINTMSK_TRERRM) _trerr_int(host, hcm, hc); if (hcint & HCINTMSK_BBERRM) _bberr_int(host, hcm, hc); if (hcint & HCINTMSK_FRMORM) _frmor_int(host, hcm, hc); if (hcint & HCINTMSK_DTERRM) _dterr_int(host, hcm, hc); if (hcint & HCINTMSK_XFRCM) _xfrc_int(host, hcm, hc); if (hcint & HCINTMSK_CHHM) _chh_int(host, hcm, hc); } static inline void _hcint_int(USBHDriver *host) { uint32_t haint; haint = host->otg->HAINT; haint &= host->otg->HAINTMSK; #if USBH_DEBUG_ENABLE && USBH_LLD_DEBUG_ENABLE_ERRORS if (!haint) { uint32_t a, b; a = host->otg->HAINT; b = host->otg->HAINTMSK; uerrf("HAINT=%08x, HAINTMSK=%08x", a, b); return; } #endif #if 1 //channel lookup loop uint8_t i; for (i = 0; haint && (i < host->channels_number); i++) { if (haint & (1 << i)) { _hcint_n_int(host, i); haint &= ~(1 << i); } } #else //faster calculation, with __CLZ (count leading zeroes) while (haint) { uint8_t chn = (uint8_t)(31 - __CLZ(haint)); osalDbgAssert(chn < host->channels_number, "what?"); haint &= ~host->channels[chn].haintmsk; _hcint_n_int(host, chn); } #endif } /*===========================================================================*/ /* 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) { stm32_otg_t *const otg = host->otg; uint16_t remaining = otg->HFNUM >> 16; if (remaining < 5975) { uwarnf("LS: ISR called too late (time=%d)", 6000 - remaining); return; } /* 15us loop during which we check if the core generates an actual keep-alive * (or activity other than idle) on the DP/DM lines. After 15us, we abort * the loop and wait for the next SOF. If no activity is detected, the upper * layer will time-out waiting for the reset to happen, and the port will remain * enabled (though in a dumb state). This will be detected on the next port reset * request and the OTG core will be reset. */ for (;;) { uint32_t line_status = otg->HPRT & HPRT_PLSTS_MASK; remaining = otg->HFNUM >> 16; if (!(otg->HPRT & HPRT_PENA)) { uwarn("LS: Port disabled"); return; } if (line_status != HPRT_PLSTS_DM) { /* success; report that the port is enabled */ uinfof("LS: activity detected, line=%d, time=%d", line_status >> 10, 6000 - remaining); host->check_ls_activity = FALSE; otg->GINTMSK = (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) { udbg("LS: No activity detected"); return; } } } /* real SOF interrupt */ udbg("SOF"); _try_commit_p(host, TRUE); } static inline void _rxflvl_int(USBHDriver *host) { stm32_otg_t *const otg = host->otg; otg->GINTMSK &= ~GINTMSK_RXFLVLM; while (otg->GINTSTS & GINTSTS_RXFLVL) { uint32_t grxstsp = otg->GRXSTSP; osalDbgCheck((grxstsp & GRXSTSP_CHNUM_MASK) < host->channels_number); stm32_hc_management_t *const hcm = &host->channels[grxstsp & GRXSTSP_CHNUM_MASK]; uint32_t hctsiz = hcm->hc->HCTSIZ; if ((grxstsp & GRXSTSP_PKTSTS_MASK) == GRXSTSP_PKTSTS(2)) { /* 0010: IN data packet received */ usbh_ep_t *const ep = hcm->ep; osalDbgCheck(ep); /* restart the channel ASAP */ if (hctsiz & HCTSIZ_PKTCNT_MASK) { #if CH_DBG_ENABLE_CHECKS if (usbhEPIsPeriodic(ep)) { osalDbgCheck(host->otg->HPTXSTS & HPTXSTS_PTXQSAV_MASK); } else { osalDbgCheck(host->otg->HNPTXSTS & HPTXSTS_PTXQSAV_MASK); } #endif hcm->hc->HCCHAR |= HCCHAR_CHENA; } uepdbgf("RXFLVL rx=%dB, rem=%dB (%dpkts)", (grxstsp & GRXSTSP_BCNT_MASK) >> 4, (hctsiz & HCTSIZ_XFRSIZ_MASK), (hctsiz & HCTSIZ_PKTCNT_MASK) >> 19); /* Read */ uint32_t *dest = (uint32_t *)ep->xfer.buf; volatile uint32_t *const src = hcm->fifo; uint32_t bcnt = (grxstsp & GRXSTSP_BCNT_MASK) >> GRXSTSP_BCNT_OFF; osalDbgCheck(bcnt + ep->xfer.partial <= ep->xfer.len); //TODO: optimize this uint32_t words = bcnt / 4; uint8_t bytes = bcnt & 3; while (words--) { *dest++ = *src; } if (bytes) { uint32_t r = *src; uint8_t *bsrc = (uint8_t *)&r; uint8_t *bdest = (uint8_t *)dest; do { *bdest++ = *bsrc++; } while (--bytes); } ep->xfer.buf += bcnt; ep->xfer.partial += bcnt; #if 0 //STM32_USBH_CHANNELS_NP > 1 /* check bug */ if (hctsiz & HCTSIZ_PKTCNT_MASK) { uint32_t pkt = (hctsiz & HCTSIZ_PKTCNT_MASK) >> 19; uint32_t siz = (hctsiz & HCTSIZ_XFRSIZ_MASK); if (pkt * ep->wMaxPacketSize != siz) { ueperrf("whatttt???"); } } #endif #if USBH_DEBUG_ENABLE && USBH_LLD_DEBUG_ENABLE_ERRORS } else { /* 0011: IN transfer completed (triggers an interrupt) * 0101: Data toggle error (triggers an interrupt) * 0111: Channel halted (triggers an interrupt) */ switch (grxstsp & GRXSTSP_PKTSTS_MASK) { case GRXSTSP_PKTSTS(3): case GRXSTSP_PKTSTS(5): case GRXSTSP_PKTSTS(7): break; default: uerrf("\tRXFLVL: ch=%d, UNK=%d", grxstsp & GRXSTSP_CHNUM_MASK, (grxstsp & GRXSTSP_PKTSTS_MASK) >> 17); break; } #endif } } otg->GINTMSK |= GINTMSK_RXFLVLM; } static inline void _nptxfe_int(USBHDriver *host) { uint32_t rem; stm32_otg_t *const otg = host->otg; rem = _write_packet(&host->ep_active_lists[USBH_EPTYPE_CTRL], otg->HNPTXSTS & HPTXSTS_PTXFSAVL_MASK); rem += _write_packet(&host->ep_active_lists[USBH_EPTYPE_BULK], otg->HNPTXSTS & HPTXSTS_PTXFSAVL_MASK); if (!rem) otg->GINTMSK &= ~GINTMSK_NPTXFEM; } static inline void _ptxfe_int(USBHDriver *host) { uint32_t rem; stm32_otg_t *const otg = host->otg; rem = _write_packet(&host->ep_active_lists[USBH_EPTYPE_ISO], otg->HPTXSTS & HPTXSTS_PTXFSAVL_MASK); rem += _write_packet(&host->ep_active_lists[USBH_EPTYPE_INT], otg->HPTXSTS & HPTXSTS_PTXFSAVL_MASK); if (!rem) otg->GINTMSK &= ~GINTMSK_PTXFEM; } 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; _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) { stm32_otg_t *const otg = host->otg; uint32_t hprt = otg->HPRT; /* note: writing PENA = 1 actually disables the port */ uint32_t hprt_clr = hprt & ~(HPRT_PENA | HPRT_PCDET | HPRT_PENCHNG | HPRT_POCCHNG); if (hprt & HPRT_PCDET) { hprt_clr |= HPRT_PCDET; if (hprt & HPRT_PCSTS) { uinfo("\tHPRT: Port connection detected"); host->rootport.lld_status |= USBH_PORTSTATUS_CONNECTION; host->rootport.lld_c_status |= USBH_PORTSTATUS_C_CONNECTION; } } if (hprt & HPRT_PENCHNG) { hprt_clr |= HPRT_PENCHNG; if (hprt & HPRT_PENA) { uinfo("\tHPRT: Port enabled"); 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); /* Clear all pending HC Interrupts */ uint8_t i; for (i = 0; i < host->channels_number; i++) { otg->hc[i].HCINTMSK = 0; otg->hc[i].HCINT = 0xFFFFFFFF; } /* configure speed */ if ((hprt & HPRT_PSPD_MASK) == HPRT_PSPD_LS) { 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) { if (hprt & HPRT_POCA) { uerr("\tHPRT: Port disabled due to overcurrent"); } else { uerr("\tHPRT: Port disabled due to port babble"); } } else { uerr("\tHPRT: Port disabled due to disconnect"); } _disable(host); } } if (hprt & HPRT_POCCHNG) { hprt_clr |= HPRT_POCCHNG; if (hprt & HPRT_POCA) { uerr("\tHPRT: Overcurrent"); host->rootport.lld_status |= USBH_PORTSTATUS_OVERCURRENT; } else { udbg("\tHPRT: Clear overcurrent"); host->rootport.lld_status &= ~USBH_PORTSTATUS_OVERCURRENT; } host->rootport.lld_c_status |= USBH_PORTSTATUS_C_OVERCURRENT; } otg->HPRT = hprt_clr; } static void usb_lld_serve_interrupt(USBHDriver *host) { osalDbgCheck(host && (host->status != USBH_STATUS_STOPPED)); stm32_otg_t *const otg = host->otg; uint32_t gintsts = otg->GINTSTS; /* check host mode */ if (!(gintsts & GINTSTS_CMOD)) { uerr("Device mode"); otg->GINTSTS = gintsts; return; } /* check mismatch */ osalDbgAssert((gintsts & GINTSTS_MMIS) == 0, "mode mismatch"); gintsts &= otg->GINTMSK; if (!gintsts) { #if USBH_DEBUG_ENABLE && USBH_DEBUG_ENABLE_WARNINGS uint32_t a, b; a = otg->GINTSTS; b = otg->GINTMSK; uwarnf("Masked bits caused an ISR: GINTSTS=%08x, GINTMSK=%08x (unhandled bits=%08x)", a, b, a & ~b); #endif return; } otg->GINTSTS = gintsts; if (gintsts & GINTSTS_SOF) _sof_int(host); if (gintsts & GINTSTS_RXFLVL) _rxflvl_int(host); if (gintsts & GINTSTS_HPRTINT) _hprtint_int(host); if (gintsts & GINTSTS_DISCINT) _discint_int(host); if (gintsts & GINTSTS_HCINT) _hcint_int(host); if (gintsts & GINTSTS_NPTXFE) _nptxfe_int(host); if (gintsts & GINTSTS_PTXFE) _ptxfe_int(host); if (gintsts & GINTSTS_IPXFR) { uerr("IPXFRM"); } } /*===========================================================================*/ /* Interrupt handlers. */ /*===========================================================================*/ #if STM32_USBH_USE_OTG1 OSAL_IRQ_HANDLER(STM32_OTG1_HANDLER) { OSAL_IRQ_PROLOGUE(); osalSysLockFromISR(); usb_lld_serve_interrupt(&USBHD1); osalSysUnlockFromISR(); OSAL_IRQ_EPILOGUE(); } #endif #if STM32_USBH_USE_OTG2 OSAL_IRQ_HANDLER(STM32_OTG2_HANDLER) { OSAL_IRQ_PROLOGUE(); osalSysLockFromISR(); usb_lld_serve_interrupt(&USBHD2); osalSysUnlockFromISR(); OSAL_IRQ_EPILOGUE(); } #endif /*===========================================================================*/ /* Initialization functions. */ /*===========================================================================*/ static void otg_core_reset(USBHDriver *usbp) { stm32_otg_t *const otgp = usbp->otg; /* Wait AHB idle condition.*/ while ((otgp->GRSTCTL & GRSTCTL_AHBIDL) == 0) ; osalSysPolledDelayX(64); /* Core reset and delay of at least 3 PHY cycles.*/ otgp->GRSTCTL = GRSTCTL_CSRST; while ((otgp->GRSTCTL & GRSTCTL_CSRST) != 0) ; osalSysPolledDelayX(24); /* Wait AHB idle condition.*/ while ((otgp->GRSTCTL & GRSTCTL_AHBIDL) == 0) ; } static void otg_rxfifo_flush(USBHDriver *usbp) { stm32_otg_t *const otgp = usbp->otg; otgp->GRSTCTL = GRSTCTL_RXFFLSH; while ((otgp->GRSTCTL & GRSTCTL_RXFFLSH) != 0) ; /* Wait for 3 PHY Clocks.*/ osalSysPolledDelayX(24); } static void otg_txfifo_flush(USBHDriver *usbp, uint32_t fifo) { stm32_otg_t *const otgp = usbp->otg; otgp->GRSTCTL = GRSTCTL_TXFNUM(fifo) | GRSTCTL_TXFFLSH; while ((otgp->GRSTCTL & GRSTCTL_TXFFLSH) != 0) ; /* Wait for 3 PHY Clocks.*/ osalSysPolledDelayX(24); } static void _init(USBHDriver *host) { int i; usbhObjectInit(host); #if STM32_USBH_USE_OTG1 #if STM32_USBH_USE_OTG2 if (&USBHD1 == host) #endif { host->otg = OTG_FS; host->channels_number = STM32_OTG1_CHANNELS_NUMBER; } #endif #if STM32_USBH_USE_OTG2 #if STM32_USBH_USE_OTG1 if (&USBHD2 == host) #endif { host->otg = OTG_HS; host->channels_number = STM32_OTG2_CHANNELS_NUMBER; } #endif INIT_LIST_HEAD(&host->ch_free[0]); INIT_LIST_HEAD(&host->ch_free[1]); for (i = 0; i < host->channels_number; i++) { host->channels[i].haintmsk = 1 << i; host->channels[i].hc = &host->otg->hc[i]; host->channels[i].fifo = host->otg->FIFO[i]; if (i < STM32_USBH_CHANNELS_NP) { list_add_tail(&host->channels[i].node, &host->ch_free[1]); } else { list_add_tail(&host->channels[i].node, &host->ch_free[0]); } } for (i = 0; i < 4; i++) { INIT_LIST_HEAD(&host->ep_active_lists[i]); INIT_LIST_HEAD(&host->ep_pending_lists[i]); } } void usbh_lld_init(void) { #if STM32_USBH_USE_OTG1 _init(&USBHD1); #endif #if STM32_USBH_USE_OTG2 _init(&USBHD2); #endif } static void _usbh_start(USBHDriver *host) { stm32_otg_t *const otgp = host->otg; /* Clock activation.*/ #if STM32_USBH_USE_OTG1 #if STM32_USBH_USE_OTG2 if (&USBHD1 == host) #endif { /* OTG FS clock enable and reset.*/ rccEnableOTG_FS(FALSE); rccResetOTG_FS(); otgp->GINTMSK = 0; /* Enables IRQ vector.*/ nvicEnableVector(STM32_OTG1_NUMBER, STM32_USB_OTG1_IRQ_PRIORITY); } #endif #if STM32_USBH_USE_OTG2 #if STM32_USBH_USE_OTG1 if (&USBHD2 == host) #endif { /* OTG HS clock enable and reset.*/ rccEnableOTG_HS(FALSE); // Disable HS clock when cpu is in sleep mode rccDisableOTG_HSULPI(); rccResetOTG_HS(); otgp->GINTMSK = 0; /* Enables IRQ vector.*/ nvicEnableVector(STM32_OTG2_NUMBER, STM32_USB_OTG2_IRQ_PRIORITY); } #endif otgp->GUSBCFG = GUSBCFG_PHYSEL | GUSBCFG_TRDT(5); otg_core_reset(host); otgp->GCCFG = GCCFG_PWRDWN; /* Forced host mode. */ otgp->GUSBCFG = GUSBCFG_FHMOD | GUSBCFG_PHYSEL | GUSBCFG_TRDT(5); /* PHY enabled.*/ otgp->PCGCCTL = 0; /* Internal FS PHY activation.*/ #if STM32_OTG_STEPPING == 1 #if defined(BOARD_OTG_NOVBUSSENS) otgp->GCCFG = GCCFG_NOVBUSSENS | GCCFG_PWRDWN; #else otgp->GCCFG = GCCFG_PWRDWN; #endif #elif STM32_OTG_STEPPING == 2 #if defined(BOARD_OTG_NOVBUSSENS) otgp->GCCFG = GCCFG_PWRDWN; #else otgp->GCCFG = (GCCFG_VBDEN | GCCFG_PWRDWN); #endif #endif /* 48MHz 1.1 PHY.*/ otgp->HCFG = HCFG_FSLSS | HCFG_FSLSPCS_48; /* Interrupts on FIFOs half empty.*/ otgp->GAHBCFG = 0; otgp->GOTGINT = 0xFFFFFFFF; otgp->HPRT |= HPRT_PPWR; otg_txfifo_flush(host, 0x10); otg_rxfifo_flush(host); otgp->GINTSTS = 0xffffffff; otgp->GINTMSK = GINTMSK_DISCM | GINTMSK_HPRTM | GINTMSK_MMISM; host->rootport.lld_status = USBH_PORTSTATUS_POWER; host->rootport.lld_c_status = 0; /* Global interrupts enable.*/ otgp->GAHBCFG |= GAHBCFG_GINTMSK; } void usbh_lld_start(USBHDriver *host) { if (host->status != USBH_STATUS_STOPPED) return; _usbh_start(host); } /*===========================================================================*/ /* Root Hub request handler. */ /*===========================================================================*/ usbh_urbstatus_t usbh_lld_root_hub_request(USBHDriver *host, uint8_t bmRequestType, uint8_t bRequest, uint16_t wvalue, uint16_t windex, uint16_t wlength, uint8_t *buf) { uint16_t typereq = (bmRequestType << 8) | bRequest; switch (typereq) { case ClearHubFeature: switch (wvalue) { case USBH_HUB_FEAT_C_HUB_LOCAL_POWER: case USBH_HUB_FEAT_C_HUB_OVER_CURRENT: break; default: osalDbgAssert(0, "invalid wvalue"); } break; case ClearPortFeature: osalDbgAssert(windex == 1, "invalid windex"); osalSysLock(); switch (wvalue) { case USBH_PORT_FEAT_ENABLE: case USBH_PORT_FEAT_SUSPEND: case USBH_PORT_FEAT_POWER: osalDbgAssert(0, "unimplemented"); /* TODO */ break; case USBH_PORT_FEAT_INDICATOR: osalDbgAssert(0, "unsupported"); break; case USBH_PORT_FEAT_C_CONNECTION: host->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_CONNECTION; break; case USBH_PORT_FEAT_C_RESET: host->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_RESET; break; case USBH_PORT_FEAT_C_ENABLE: host->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_ENABLE; break; case USBH_PORT_FEAT_C_SUSPEND: host->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_SUSPEND; break; case USBH_PORT_FEAT_C_OVERCURRENT: host->rootport.lld_c_status &= ~USBH_PORTSTATUS_C_OVERCURRENT; break; default: osalDbgAssert(0, "invalid wvalue"); break; } osalSysUnlock(); break; case GetHubDescriptor: osalDbgAssert(0, "unsupported"); break; case GetHubStatus: osalDbgCheck(wlength >= 4); *(uint32_t *)buf = 0; break; case GetPortStatus: osalDbgAssert(windex == 1, "invalid windex"); osalDbgCheck(wlength >= 4); osalSysLock(); *(uint32_t *)buf = host->rootport.lld_status | (host->rootport.lld_c_status << 16); osalSysUnlock(); break; case SetHubFeature: osalDbgAssert(0, "unsupported"); break; case SetPortFeature: osalDbgAssert(windex == 1, "invalid windex"); switch (wvalue) { case USBH_PORT_FEAT_TEST: case USBH_PORT_FEAT_SUSPEND: case USBH_PORT_FEAT_POWER: osalDbgAssert(0, "unimplemented"); /* TODO */ break; case USBH_PORT_FEAT_RESET: { osalSysLock(); stm32_otg_t *const otg = host->otg; 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(OSAL_MS2I(20)); _usbh_start(host); /* this effectively resets the core */ osalThreadSleepS(OSAL_MS2I(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"); host->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); while ((otg->GRSTCTL & GRSTCTL_AHBIDL) == 0); otg->HPRT = hprt | HPRT_PRST; osalThreadSleepS(OSAL_MS2I(15)); otg->HPRT = hprt; osalThreadSleepS(OSAL_MS2I(10)); host->rootport.lld_c_status |= USBH_PORTSTATUS_C_RESET; osalSysUnlock(); } break; case USBH_PORT_FEAT_INDICATOR: osalDbgAssert(0, "unsupported"); break; default: osalDbgAssert(0, "invalid wvalue"); break; } break; default: osalDbgAssert(0, "invalid typereq"); break; } return USBH_URBSTATUS_OK; } uint8_t usbh_lld_roothub_get_statuschange_bitmap(USBHDriver *host) { return host->rootport.lld_c_status ? (1 << 1) : 0; } #endif