/* Copyright (C) 2015 Robert Lippert 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. */ /** * @file USBv1/hal_usb_lld.c * @brief AVR/MEGA USB subsystem low level driver source. * * @addtogroup USB * @{ */ #include "hal.h" #if (HAL_USE_USB == TRUE) || defined(__DOXYGEN__) #ifndef F_USB #define F_USB F_CPU #endif /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ /** * @brief USB1 driver identifier. */ #if (AVR_USB_USE_USB1 == TRUE) || defined(__DOXYGEN__) USBDriver USBD1; #endif /*===========================================================================*/ /* Driver local variables and types. */ /*===========================================================================*/ /** * @brief EP0 state. * @note It is an union because IN and OUT endpoints are never used at the * same time for EP0. */ static union { /** * @brief IN EP0 state. */ USBInEndpointState in; /** * @brief OUT EP0 state. */ USBOutEndpointState out; } ep0_state; /** * @brief EP0 initialization structure. */ static const USBEndpointConfig ep0config = { USB_EP_MODE_TYPE_CTRL, _usb_ep0setup, _usb_ep0in, _usb_ep0out, 0x40, 0x40, &ep0_state.in, &ep0_state.out }; /*===========================================================================*/ /* Driver local variables and types. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver local functions. */ /*===========================================================================*/ #ifdef AVR_USB_PLL_OFF_IN_SUSPEND static __attribute__((unused)) void usb_pll_off(void) { PLLCSR = 0; } #endif static void usb_pll_on(void) { #if (F_USB == 8000000) #if (defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || \ defined(__AVR_ATmega8U2__) || defined(__AVR_ATmega16U2__) || \ defined(__AVR_ATmega32U2__)) #define PLL_VAL 0 #elif (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) #define PLL_VAL 0 #elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)) #define PLL_VAL ((0 << PLLP2) | (1 << PLLP1) | (1 << PLLP0)) #elif (defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1287__)) #define PLL_VAL ((0 << PLLP2) | (1 << PLLP1) | (1 << PLLP0)) #endif #elif (F_USB == 16000000) #if (defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || \ defined(__AVR_ATmega8U2__) || defined(__AVR_ATmega16U2__) || \ defined(__AVR_ATmega32U2__)) #define PLL_VAL ((0 << PLLP2) | (0 << PLLP1) | (1 << PLLP0)) #elif (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) #define PLL_VAL (1 << PINDIV) #elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__)) #define PLL_VAL ((1 << PLLP2) | (1 << PLLP1) | (0 << PLLP0)) #elif (defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)) #define PLL_VAL ((1 << PLLP2) | (0 << PLLP1) | (1 << PLLP0)) #endif #endif #ifndef PLL_VAL #error Could not determine PLL value, unsupported AVR USB model type #endif #ifdef PLLFRQ /* This initializes PLL on supported devices for USB 48MHz *only* */ PLLFRQ = (0 << PDIV3) | (1 << PDIV2) | (0 << PDIV1) | (0 << PDIV0); #endif PLLCSR = PLL_VAL; PLLCSR = PLL_VAL | (1 << PLLE); } static int usb_pll_is_locked(void) { return !!(PLLCSR & (1 << PLOCK)); } /*===========================================================================*/ /* Driver interrupt handlers and threads. */ /*===========================================================================*/ /** * @brief USB general/OTG/device management event interrupt handler. * * @isr */ OSAL_IRQ_HANDLER(USB_GEN_vect) { uint8_t usbint, udint; USBDriver * const usbp = &USBD1; OSAL_IRQ_PROLOGUE(); usbint = USBINT; udint = UDINT; if (usbint & (1 << VBUSTI)) { /* Connected. */ #ifdef AVR_USB_PLL_OFF_IN_SUSPEND usb_pll_on(); while (!usb_pll_is_locked()) {} #endif /* AVR_USB_PLL_OFF_IN_SUSPEND */ /* Attach to bus */ usb_lld_connect_bus(usbp); USBINT &= ~(1 << VBUSTI); } /* USB bus SUSPEND condition handling.*/ if (udint & (1 << SUSPI)) { /* Disable suspend interrupt, enable WAKEUP interrupt */ UDIEN |= (1 << WAKEUPE); UDINT &= ~(1 << WAKEUPI); UDIEN &= ~(1 << SUSPE); /* Freeze the clock to reduce power consumption */ USBCON |= (1 << FRZCLK); #ifdef AVR_USB_PLL_OFF_IN_SUSPEND usb_pll_off(); #endif /* AVR_USB_PLL_OFF_IN_SUSPEND */ /* Clear the interrupt */ UDINT &= ~(1 << SUSPI); _usb_isr_invoke_event_cb(usbp, USB_EVENT_SUSPEND); } /* USB bus WAKEUP condition handling.*/ if (udint & (1 << WAKEUPI)) { #ifdef AVR_USB_PLL_OFF_IN_SUSPEND usb_pll_on(); while (!usb_pll_is_locked()) {} #endif /* AVR_USB_PLL_OFF_IN_SUSPEND */ /* Unfreeze the clock */ USBCON &= ~(1 << FRZCLK); /* Clear & disable wakeup interrupt, enable suspend interrupt */ UDINT &= ~(1 << WAKEUPI); UDIEN &= ~(1 << WAKEUPE); UDIEN |= (1 << SUSPE); _usb_isr_invoke_event_cb(usbp, USB_EVENT_WAKEUP); } /* USB bus RESUME condition handling.*/ if (udint & (1 << EORSMI)) { UDINT &= ~(1 << EORSMI); UDIEN &= ~(1 << EORSME); } /* USB bus reset condition handling.*/ if (udint & (1 << EORSTI)) { UDINT &= ~(1 << EORSTI); /* Clear & disable suspend interrupt, enable WAKEUP interrupt */ UDINT &= ~(1 << SUSPI); UDIEN &= ~(1 << SUSPE); UDIEN |= (1 << WAKEUPE); /* Reinitialize EP0. This is not mentioned in the datasheet but * apparently is required. */ usb_lld_init_endpoint(usbp, 0); _usb_isr_invoke_event_cb(usbp, USB_EVENT_RESET); } /* Start-Of-Frame handling, only if enabled */ if ((UDIEN & (1 << SOFE)) && (udint & (1 << SOFI))) { _usb_isr_invoke_sof_cb(usbp); UDINT &= ~(1 << SOFI); } OSAL_IRQ_EPILOGUE(); } static void usb_fifo_write(USBDriver *usbp, usbep_t ep, size_t n) { const USBEndpointConfig *epcp = usbp->epc[ep]; USBInEndpointState *isp = epcp->in_state; syssts_t sts; if (n == 0) { isp->last_tx_size = 0; return; } if (n > epcp->in_maxsize) n = epcp->in_maxsize; /* i is number of bytes remaining to transmit minus 1 (to handle 256b case) */ uint8_t i = n - 1; /* Must lock for entire operation to ensure nothing changes the ENUM value */ sts = osalSysGetStatusAndLockX(); UENUM = ep & 0xf; do { UEDATX = *isp->txbuf++; } while (i--); isp->last_tx_size = n; osalSysRestoreStatusX(sts); } static void usb_fifo_read(USBDriver *usbp, usbep_t ep, size_t n) { const USBEndpointConfig *epcp = usbp->epc[ep]; USBOutEndpointState *osp = epcp->out_state; syssts_t sts; if (n == 0) return; if (n > epcp->out_maxsize) n = epcp->out_maxsize; // i is number of bytes remaining to receive minus 1 (to handle 256b case) uint8_t i = n - 1; /* Must lock for entire operation to ensure nothing changes the ENUM value */ sts = osalSysGetStatusAndLockX(); UENUM = ep & 0xf; do { *osp->rxbuf++ = UEDATX; } while (i--); osalSysRestoreStatusX(sts); } static void ep_isr(USBDriver *usbp, usbep_t ep) { const USBEndpointConfig *epcp = usbp->epc[ep]; size_t n; UENUM = ep & 0xf; /* TODO: if stalling is needed/expected remove this check */ osalDbgAssert(!(UEINTX & (1 << STALLEDI)), "Endpoint stalled!"); if ((UEIENX & (1 << TXINE)) && (UEINTX & (1 << TXINI))) { /* Ready to accept more IN data to transmit to host */ /* Update transaction counts to reflect newly transmitted bytes */ epcp->in_state->txcnt += epcp->in_state->last_tx_size; n = epcp->in_state->txsize - epcp->in_state->txcnt; if (n > 0) { /* Transfer not completed, there are more packets to send. */ usb_fifo_write(usbp, ep, n); /* Clear FIFOCON to send the data in the FIFO and switch bank */ UEINTX &= ~((1 << TXINI) | (1 << FIFOCON)); /* Enable the TX complete interrupt */ UEIENX |= (1 << TXINE); } else { /* Disable TXIN interrupt */ UEIENX &= ~(1 << TXINE); /* Handshake interrupt status */ UEINTX &= ~(1 << TXINI); _usb_isr_invoke_in_cb(usbp, ep); } } else if ((UEIENX & (1 << RXSTPE)) && (UEINTX & (1 << RXSTPI))) { /* Received SETUP data */ /* Reset transaction state for endpoint */ epcp->in_state->txcnt = 0; epcp->in_state->txsize = 0; epcp->in_state->last_tx_size = 0; /* Setup packets handling, setup packets are handled using a specific callback.*/ _usb_isr_invoke_setup_cb(usbp, ep); } else if ((UEIENX & (1 << RXOUTE)) && (UEINTX & (1 << RXOUTI))) { /* Received OUT data from host */ if (ep == 0 && usbp->ep0state == USB_EP0_WAITING_STS) { /* SETUP/control transaction complete, invoke the callback. */ UEIENX &= ~(1 << RXOUTE); UEINTX &= ~((1 << RXOUTI) | (1 << FIFOCON)); _usb_isr_invoke_out_cb(usbp, ep); } else { /* Check the FIFO byte count to see how many bytes were received */ n = UEBCX; usb_fifo_read(usbp, ep, n); /* Transaction state update */ epcp->out_state->rxcnt += n; epcp->out_state->rxsize -= n; epcp->out_state->rxpkts -= 1; if (n < epcp->out_maxsize || epcp->out_state->rxpkts == 0) { /* Disable OUT interrupt */ UEIENX &= ~(1 << RXOUTE); /* Mark OUT FIFO processed to allow more data to be received */ UEINTX &= ~((1 << RXOUTI) | (1 << FIFOCON)); /* Transfer complete, invokes the callback.*/ _usb_isr_invoke_out_cb(usbp, ep); } else { /* Mark OUT FIFO processed to allow more data to be received */ UEINTX &= ~((1 << RXOUTI) | (1 << FIFOCON)); } } } } /** * @brief USB communication event interrupt handler. * * @isr */ OSAL_IRQ_HANDLER(USB_COM_vect) { USBDriver *usbp = &USBD1; const uint8_t epnum_orig = UENUM; uint8_t i; OSAL_IRQ_PROLOGUE(); /* Figure out which endpoint(s) are interrupting */ for (i = 0; i < USB_MAX_ENDPOINTS; ++i) { if (UEINT & (1 << i)) { ep_isr(usbp, i); } } /* Restore endpoint selector to pre-interrupt state */ UENUM = epnum_orig; OSAL_IRQ_EPILOGUE(); } /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ /** * @brief Low level USB driver initialization. * * @notapi */ void usb_lld_init(void) { #if AVR_USB_USE_USB1 == TRUE /* Driver initialization.*/ usbObjectInit(&USBD1); /* Start and lock the USB 48MHz PLL (takes ~100ms) */ usb_pll_on(); while (!usb_pll_is_locked()) {} #endif } /** * @brief Configures and activates the USB peripheral. * * @param[in] usbp pointer to the @p USBDriver object * * @notapi */ void usb_lld_start(USBDriver *usbp) { if (usbp->state == USB_STOP) { /* Enables the peripheral.*/ #if AVR_USB_USE_USB1 == TRUE if (&USBD1 == usbp) { uint8_t i; /* * Workaround: disable pad drivers as first step in case bootloader left * it on. Otherwise VBUS detection interrupt will not trigger later. */ USBCON &= ~(1 << OTGPADE); /* Enable the internal 3.3V pad regulator */ UHWCON |= (1 << UVREGE); /* Reset and disable all endpoints */ UERST = 0x7f; UERST = 0; for (i = 0; i < USB_MAX_ENDPOINTS; ++i){ UENUM = i; UEIENX = 0; UEINTX = 0; UECFG1X = 0; UECONX &= ~(1 << EPEN); } } #endif /* Reset procedure enforced on driver start.*/ _usb_reset(usbp); } } /** * @brief Deactivates the USB peripheral. * * @param[in] usbp pointer to the @p USBDriver object * * @notapi */ void usb_lld_stop(USBDriver *usbp) { if (usbp->state == USB_READY) { /* Disables the peripheral.*/ #if AVR_USB_USE_USB1 == TRUE if (&USBD1 == usbp) { /* Disable and clear transition interrupts */ #if !defined(__AVR_ATmega32U4__) USBCON &= ~((1 << VBUSTE) | (1 << IDTE)); #else USBCON &= ~(1 << VBUSTE); #endif USBINT = 0; /* Disable and clear device interrupts */ UDIEN &= ~((1 << UPRSME) | (1 << EORSME) | (1 << WAKEUPE) | (1 << EORSTE) | (1 << SOFE) | (1 << SUSPE)); UDINT = 0; /* Freeze clock */ USBCON |= (1 << FRZCLK); /* Disable USB logic */ USBCON &= ~(1 << USBE); } #endif } } /** * @brief USB low level reset routine. * * @param[in] usbp pointer to the @p USBDriver object * * @notapi */ void usb_lld_reset(USBDriver *usbp) { /* Post-reset initialization.*/ /* Reset and enable via toggling the USB macro logic overall enable bit */ USBCON &= ~(1 << USBE); USBCON |= (1 << USBE); /* Unfreeze clock */ USBCON &= ~(1 << FRZCLK); /* Set Device mode */ /* TODO: Support HOST/OTG mode if needed */ #if !defined(__AVR_ATmega32U4__) UHWCON |= (1 << UIMOD); #endif /* Set FULL 12mbps speed */ UDCON &= ~(1 << LSM); /* Enable device pin interrupt */ USBCON |= (1 << VBUSTE); /* EP0 initialization.*/ UERST |= (1 << 0); UERST &= ~(1 << 0); usbp->epc[0] = &ep0config; usb_lld_init_endpoint(usbp, 0); /* Enable device-level event interrupts */ UDINT &= ~(1 << SUSPI); UDIEN = (1 << UPRSME) | (1 << EORSME) | (1 << WAKEUPE) | (1 << EORSTE) | (1 << SUSPE); /* The SOF interrupt is only enabled if a callback is defined for this service because it is a high rate source. */ if (usbp->config->sof_cb != NULL) UDIEN |= (1 << SOFE); /* Set OTG PAD to on which will trigger VBUS transition if plugged in. */ USBCON |= (1 << OTGPADE); } /** * @brief Sets the USB address. * * @param[in] usbp pointer to the @p USBDriver object * * @notapi */ void usb_lld_set_address(USBDriver *usbp) { UDADDR = (UDADDR & (1 << ADDEN)) | (usbp->address & 0x7F); UDADDR |= (1 << ADDEN); } /** * @brief Enables an endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_init_endpoint(USBDriver *usbp, usbep_t ep) { uint16_t size = 0; const USBEndpointConfig *epcp = usbp->epc[ep]; /* Select this endpoint number for subsequent commands */ UENUM = ep & 0xf; /* Enable endpoint to take out of reset */ UECONX |= (1 << EPEN); UECFG1X = 0; /* Set the endpoint type.*/ switch (epcp->ep_mode & USB_EP_MODE_TYPE) { case USB_EP_MODE_TYPE_ISOC: UECFG0X = (0 << EPTYPE1) | (1 << EPTYPE0); break; case USB_EP_MODE_TYPE_BULK: UECFG0X = (1 << EPTYPE1) | (0 << EPTYPE0); break; case USB_EP_MODE_TYPE_INTR: UECFG0X = (1 << EPTYPE1) | (1 << EPTYPE0); break; default: UECFG0X = (0 << EPTYPE1) | (0 << EPTYPE0); } if ((epcp->ep_mode & USB_EP_MODE_TYPE) == USB_EP_MODE_TYPE_CTRL) { /* CTRL endpoint */ osalDbgCheck(epcp->in_maxsize == epcp->out_maxsize); size = epcp->in_maxsize; } else { osalDbgAssert(!(epcp->in_cb != NULL && epcp->out_cb != NULL), "On AVR each endpoint can be IN or OUT not both"); /* IN endpoint? */ if (epcp->in_cb != NULL) { UECFG0X |= (1 << EPDIR); size = epcp->in_maxsize; } /* OUT endpoint? */ if (epcp->out_cb != NULL) { UECFG0X &= ~(1 << EPDIR); size = epcp->out_maxsize; } } /* Endpoint size and address initialization. */ switch (size) { case 8: UECFG1X = (0 << EPSIZE0) | (1 << ALLOC); break; case 16: UECFG1X = (1 << EPSIZE0) | (1 << ALLOC); break; case 32: UECFG1X = (2 << EPSIZE0) | (1 << ALLOC); break; case 64: UECFG1X = (3 << EPSIZE0) | (1 << ALLOC); break; case 128: osalDbgAssert(ep == 1, "Endpoint size of 128 bytes only valid for EP#1"); UECFG1X = (4 << EPSIZE0) | (1 << ALLOC); break; case 256: osalDbgAssert(ep == 1, "Endpoint size of 256 bytes only valid for EP#1"); UECFG1X = (5 << EPSIZE0) | (1 << ALLOC); break; default: osalDbgAssert(false, "Invalid size for USB endpoint"); } UEIENX |= (1 << RXSTPE)/* | (1 << RXOUTE)*/ | (1 << STALLEDE) ; osalDbgAssert((UESTA0X & (1 << CFGOK)), "Hardware reports endpoint config is INVALID"); } /** * @brief Disables all the active endpoints except the endpoint zero. * * @param[in] usbp pointer to the @p USBDriver object * * @notapi */ void usb_lld_disable_endpoints(USBDriver *usbp) { uint8_t i; for (i = 1; i <= USB_MAX_ENDPOINTS; ++i) { UENUM = i; UECFG1X &= ~(1 << ALLOC); UECONX &= ~(1 << EPEN); } } /** * @brief Returns the status of an OUT endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * @return The endpoint status. * @retval EP_STATUS_DISABLED The endpoint is not active. * @retval EP_STATUS_STALLED The endpoint is stalled. * @retval EP_STATUS_ACTIVE The endpoint is active. * * @notapi */ usbepstatus_t usb_lld_get_status_out(USBDriver *usbp, usbep_t ep) { /* Select this endpoint number for subsequent commands */ UENUM = ep & 0xf; if (!(UECONX & (1 << EPEN))) return EP_STATUS_DISABLED; if (UECONX & (1 << STALLRQ)) return EP_STATUS_STALLED; return EP_STATUS_ACTIVE; } /** * @brief Returns the status of an IN endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * @return The endpoint status. * @retval EP_STATUS_DISABLED The endpoint is not active. * @retval EP_STATUS_STALLED The endpoint is stalled. * @retval EP_STATUS_ACTIVE The endpoint is active. * * @notapi */ usbepstatus_t usb_lld_get_status_in(USBDriver *usbp, usbep_t ep) { return usb_lld_get_status_out(usbp, ep); } /** * @brief Reads a setup packet from the dedicated packet buffer. * @details This function must be invoked in the context of the @p setup_cb * callback in order to read the received setup packet. * @pre In order to use this function the endpoint must have been * initialized as a control endpoint. * @post The endpoint is ready to accept another packet. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * @param[out] buf buffer where to copy the packet data * * @notapi */ void usb_lld_read_setup(USBDriver *usbp, usbep_t ep, uint8_t *buf) { uint8_t i; /* Select this endpoint number for subsequent commands */ UENUM = ep & 0xf; for (i = 0; i < 8; ++i) { *buf++ = UEDATX; } /* Clear FIFOCON and RXSTPI to drain the setup packet data from the FIFO */ UEINTX &= ~((1 << FIFOCON) | (1 << RXSTPI)); } /** * @brief Ends a SETUP transaction * @details This function must be invoked in the context of the @p setup_cb * callback in order to finish an entire setup packet. * @pre In order to use this function the endpoint must have been * initialized as a control endpoint. * @post The endpoint is ready to accept another packet. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_end_setup(USBDriver *usbp, usbep_t ep) { /* Select this endpoint number for subsequent commands */ UENUM = ep & 0xf; if ((usbp->setup[0] & USB_RTYPE_DIR_MASK) == USB_RTYPE_DIR_DEV2HOST) { /* Enable interrupt and wait for OUT packet */ usbp->epc[ep]->out_state->rxsize = 0; usbp->epc[ep]->out_state->rxpkts = 1; UEINTX &= ~((1 << FIFOCON) | (1 << RXOUTI)); UEIENX |= (1 << RXOUTE); } else { /* Enable interrupt and wait for IN packet */ usbp->epc[ep]->in_state->last_tx_size = 0; usbp->epc[ep]->in_state->txcnt = 0; usbp->epc[ep]->in_state->txsize = 0; UEINTX &= ~((1 << FIFOCON) | (1 << TXINI)); UEIENX |= (1 << TXINE); } } /** * @brief Starts a receive operation on an OUT endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_start_out(USBDriver *usbp, usbep_t ep) { USBOutEndpointState *osp = usbp->epc[ep]->out_state; syssts_t sts; /* Initialize transfer by recording how many packets we expect to receive. */ if (osp->rxsize == 0) /* Special case for zero sized packets.*/ osp->rxpkts = 1; else osp->rxpkts = (uint8_t)((osp->rxsize + usbp->epc[ep]->out_maxsize - 1) / usbp->epc[ep]->out_maxsize); /* Select this endpoint number for subsequent commands */ /* Must lock for entire operation to ensure nothing changes the ENUM value */ sts = osalSysGetStatusAndLockX(); UENUM = ep & 0xf; UEIENX |= (1 << RXOUTE); osalSysRestoreStatusX(sts); } /** * @brief Starts a transmit operation on an IN endpoint. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_start_in(USBDriver *usbp, usbep_t ep) { USBInEndpointState *isp = usbp->epc[ep]->in_state; syssts_t sts; /* Initialize transfer by filling FIFO with passed data. */ usb_fifo_write(usbp, ep, isp->txsize); /* Select this endpoint number for subsequent commands */ /* Must lock for entire operation to ensure nothing changes the ENUM value */ sts = osalSysGetStatusAndLockX(); UENUM = ep & 0xf; /* Clear FIFOCON to send the data in the FIFO and switch bank */ UEINTX &= ~((1 << TXINI) | (1 << FIFOCON)); /* Enable the TX complete interrupt */ UEIENX |= (1 << TXINE); osalSysRestoreStatusX(sts); } /** * @brief Brings an OUT endpoint in the stalled state. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_stall_out(USBDriver *usbp, usbep_t ep) { syssts_t sts; (void)usbp; /* Select this endpoint number for subsequent commands */ /* Must lock for entire operation to ensure nothing changes the ENUM value */ sts = osalSysGetStatusAndLockX(); UENUM = ep & 0xf; UECONX |= (1 << STALLRQ); osalSysRestoreStatusX(sts); } /** * @brief Brings an IN endpoint in the stalled state. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_stall_in(USBDriver *usbp, usbep_t ep) { usb_lld_stall_out(usbp, ep); } /** * @brief Brings an OUT endpoint in the active state. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_clear_out(USBDriver *usbp, usbep_t ep) { syssts_t sts; (void)usbp; /* Select this endpoint number for subsequent commands */ /* Must lock for entire operation to ensure nothing changes the ENUM value */ sts = osalSysGetStatusAndLockX(); UENUM = ep & 0xf; UECONX |= (1 << STALLRQC); osalSysRestoreStatusX(sts); } /** * @brief Brings an IN endpoint in the active state. * * @param[in] usbp pointer to the @p USBDriver object * @param[in] ep endpoint number * * @notapi */ void usb_lld_clear_in(USBDriver *usbp, usbep_t ep) { usb_lld_clear_out(usbp, ep); } #endif /* HAL_USE_USB == TRUE */ /** @} */