USB device stack

Dependents:   USBMSD_step1 USBMSD_step1_5 picossd_step1_2cs

Revision:
71:53949e6131f6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/TARGET_Silicon_Labs/src/em_usbdint.c	Thu Jul 27 12:14:04 2017 +0100
@@ -0,0 +1,946 @@
+/**************************************************************************//**
+ * @file em_usbdint.c
+ * @brief USB protocol stack library, USB device peripheral interrupt handlers.
+ * @version 3.20.14
+ ******************************************************************************
+ * @section License
+ * <b>(C) Copyright 2014 Silicon Labs, http://www.silabs.com</b>
+ *******************************************************************************
+ *
+ * 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 "em_device.h"
+#if defined( USB_PRESENT ) && ( USB_COUNT == 1 )
+#include "em_usb.h"
+#if defined( USB_DEVICE )
+
+#include "em_cmu.h"
+#include "em_usbtypes.h"
+#include "em_usbhal.h"
+#include "em_usbd.h"
+
+#ifdef __MBED__
+extern void usbhal_allow_em2(bool em2_allow);
+#endif
+
+/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
+
+#define HANDLE_INT( x ) if ( status & x ) { Handle_##x(); status &= ~x; }
+
+static void Handle_USB_GINTSTS_ENUMDONE  ( void );
+static void Handle_USB_GINTSTS_IEPINT    ( void );
+static void Handle_USB_GINTSTS_OEPINT    ( void );
+static void Handle_USB_GINTSTS_RESETDET  ( void );
+static void Handle_USB_GINTSTS_SOF       ( void );
+static void Handle_USB_GINTSTS_USBRST    ( void );
+static void Handle_USB_GINTSTS_USBSUSP   ( void );
+static void Handle_USB_GINTSTS_WKUPINT   ( void );
+#if defined( USB_DOEP0INT_STUPPKTRCVD )
+static void HandleOutEpIntr( uint32_t status, USBD_Ep_TypeDef *ep );
+#else
+static void ProcessSetup                 ( void );
+static void ProcessOepData               ( USBD_Ep_TypeDef *ep );
+#endif
+
+#if ( USB_PWRSAVE_MODE )
+/* Variables and prototypes for USB powerdown (suspend) functionality. */
+static bool UsbPowerDown( void );
+static bool UsbPowerUp( void );
+
+volatile bool USBD_poweredDown = false;
+
+/* Storage for backing up USB core registers. */
+static uint32_t  x_USB_GINTMSK;
+#if defined(_USB_GOTGCTL_MASK)
+static uint32_t  x_USB_GOTGCTL;
+#endif
+static uint32_t  x_USB_GAHBCFG;
+static uint32_t  x_USB_GUSBCFG;
+static uint32_t  x_USB_GRXFSIZ;
+static uint32_t  x_USB_GNPTXFSIZ;
+static uint32_t  x_USB_DCFG;
+static uint32_t  x_USB_DCTL;
+static uint32_t  x_USB_DAINTMSK;
+static uint32_t  x_USB_DIEPMSK;
+static uint32_t  x_USB_DOEPMSK;
+static uint32_t  x_USB_PCGCCTL;
+
+#if ( NUM_EP_USED > 0 )
+static uint32_t  x_USB_EP_CTL[ NUM_EP_USED ];
+static uint32_t  x_USB_EP_TSIZ[ NUM_EP_USED ];
+static uint32_t  x_USB_EP_DMAADDR[ NUM_EP_USED ];
+#endif
+
+#if ( NUM_EP_USED > MAX_NUM_TX_FIFOS )
+#define FIFO_CNT MAX_NUM_TX_FIFOS
+#else
+#define FIFO_CNT NUM_EP_USED
+#endif
+
+#if ( FIFO_CNT > 0 )
+static uint32_t  x_USB_DIEPTXFS[ FIFO_CNT ];
+#endif
+
+#if ( USB_PWRSAVE_MODE )
+static uint32_t cmuStatus = 0;
+#endif
+
+#endif /* if ( USB_PWRSAVE_MODE ) */
+
+/*
+ * USB_IRQHandler() is the first level handler for the USB peripheral interrupt.
+ */
+void USB_IRQHandler( void )
+{
+  uint32_t status;
+  bool servedVbusInterrupt = false;
+
+  INT_Disable();
+
+#if ( USB_PWRSAVE_MODE )
+  if ( USBD_poweredDown )
+  {
+    /* Switch USBC clock from 32kHz to a 48MHz clock to be able to  */
+    /* read USB peripheral registers.                               */
+    /* If we woke up from EM2, HFCLK is now HFRCO.                  */
+
+    /* Restore clock oscillators.*/
+#if defined( CMU_OSCENCMD_USHFRCOEN )
+    if ( ( CMU->STATUS & CMU_STATUS_USHFRCOENS ) == 0 )/*Wakeup from EM2 ?*/
+    {
+      CMU->OSCENCMD = ( cmuStatus
+                        & ( CMU_STATUS_AUXHFRCOENS | CMU_STATUS_HFXOENS ) )
+                      | CMU_OSCENCMD_USHFRCOEN;
+    }
+#else
+    if ( ( CMU->STATUS & CMU_STATUS_HFXOENS ) == 0 ) /* Wakeup from EM2 ? */
+    {
+      CMU->OSCENCMD = cmuStatus
+                      & ( CMU_STATUS_AUXHFRCOENS | CMU_STATUS_HFXOENS );
+    }
+#endif
+
+    /* Select correct USBC clock.*/
+#if defined( CMU_OSCENCMD_USHFRCOEN )
+    CMU->CMD = CMU_CMD_USBCCLKSEL_USHFRCO;
+    while ( ( CMU->STATUS & CMU_STATUS_USBCUSHFRCOSEL ) == 0 ){}
+#else
+    CMU->CMD = CMU_CMD_USBCCLKSEL_HFCLKNODIV;
+    while ( ( CMU->STATUS & CMU_STATUS_USBCHFCLKSEL ) == 0 ){}
+#endif
+  }
+#endif /* if ( USB_PWRSAVE_MODE ) */
+
+  if ( USB->IF && ( USB->CTRL & USB_CTRL_VREGOSEN ) )
+  {
+    if ( USB->IF & USB_IF_VREGOSH )
+    {
+      USB->IFC = USB_IFC_VREGOSH;
+
+      if ( USB->STATUS & USB_STATUS_VREGOS )
+      {
+        servedVbusInterrupt = true;
+        DEBUG_USB_INT_LO_PUTS( "\nVboN" );
+
+#if ( USB_PWRSAVE_MODE )
+        if ( UsbPowerUp() )
+        {
+          USBDHAL_EnableUsbResetAndSuspendInt();
+        }
+        USBD_SetUsbState( USBD_STATE_POWERED );
+#endif
+      }
+    }
+
+    if ( USB->IF & USB_IF_VREGOSL )
+    {
+      USB->IFC = USB_IFC_VREGOSL;
+
+      if ( ( USB->STATUS & USB_STATUS_VREGOS ) == 0 )
+      {
+        servedVbusInterrupt = true;
+        DEBUG_USB_INT_LO_PUTS( "\nVboF" );
+
+#if ( USB_PWRSAVE_MODE )
+#if ( USB_PWRSAVE_MODE & USB_PWRSAVE_MODE_ONVBUSOFF )
+        if ( !USBD_poweredDown )
+        {
+          USB->GINTMSK = 0;
+          USB->GINTSTS = 0xFFFFFFFF;
+        }
+
+        UsbPowerDown();
+#endif
+        USBD_SetUsbState( USBD_STATE_NONE );
+#endif
+      }
+    }
+  }
+
+  status = USBHAL_GetCoreInts();
+  if ( status == 0 )
+  {
+    INT_Enable();
+    if ( !servedVbusInterrupt )
+    {
+      DEBUG_USB_INT_LO_PUTS( "\nSinT" );
+    }
+    return;
+  }
+
+  HANDLE_INT( USB_GINTSTS_RESETDET   )
+  HANDLE_INT( USB_GINTSTS_WKUPINT    )
+  HANDLE_INT( USB_GINTSTS_USBSUSP    )
+  HANDLE_INT( USB_GINTSTS_SOF        )
+  HANDLE_INT( USB_GINTSTS_ENUMDONE   )
+  HANDLE_INT( USB_GINTSTS_USBRST     )
+  HANDLE_INT( USB_GINTSTS_IEPINT     )
+  HANDLE_INT( USB_GINTSTS_OEPINT     )
+
+  INT_Enable();
+
+  if ( status != 0 )
+  {
+    DEBUG_USB_INT_LO_PUTS( "\nUinT" );
+  }
+}
+
+/*
+ * Handle port enumeration interrupt. This has nothing to do with normal
+ * device enumeration.
+ */
+static void Handle_USB_GINTSTS_ENUMDONE( void )
+{
+#if ( USB_PWRSAVE_MODE )
+  UsbPowerUp();
+#endif
+
+  USBDHAL_Ep0Activate( dev->ep0MpsCode );
+  dev->ep[ 0 ].state = D_EP_IDLE;
+  USBDHAL_EnableInts( dev );
+  DEBUG_USB_INT_LO_PUTS( "EnumD" );
+}
+
+/*
+ * Handle IN endpoint transfer interrupt.
+ */
+static void Handle_USB_GINTSTS_IEPINT( void )
+{
+  int epnum;
+  uint16_t epint;
+  uint16_t epmask;
+  uint32_t status;
+  USBD_Ep_TypeDef *ep;
+
+  DEBUG_USB_INT_HI_PUTCHAR( 'i' );
+
+  epint = USBDHAL_GetAllInEpInts();
+  for ( epnum = 0,                epmask = 1;
+        epnum <= MAX_NUM_IN_EPS;
+        epnum++,                  epmask <<= 1 )
+  {
+    if ( epint & epmask )
+    {
+      ep = USBD_GetEpFromAddr( USB_SETUP_DIR_MASK | epnum );
+      status = USBDHAL_GetInEpInts( ep );
+
+      if ( status & USB_DIEP_INT_XFERCOMPL )
+      {
+        USB_DINEPS[ epnum ].INT = USB_DIEP_INT_XFERCOMPL;
+
+        DEBUG_USB_INT_HI_PUTCHAR( 'c' );
+
+        if ( epnum == 0 )
+        {
+          if ( ep->remaining > ep->packetSize )
+          {
+            ep->remaining -= ep->packetSize;
+            ep->xferred += ep->packetSize;
+          }
+          else
+          {
+            ep->xferred += ep->remaining;
+            ep->remaining = 0;
+          }
+          USBDEP_Ep0Handler( dev );
+        }
+        else
+        {
+          ep->xferred = ep->remaining -
+                        ( ( USB_DINEPS[ epnum ].TSIZ      &
+                            _USB_DIEP_TSIZ_XFERSIZE_MASK    ) >>
+                          _USB_DIEP_TSIZ_XFERSIZE_SHIFT          );
+          ep->remaining -= ep->xferred;
+
+          USBDEP_EpHandler( ep->addr );
+#if defined( USB_DOEP0INT_STUPPKTRCVD )
+          if ( USB_DINEPS[ ep->num ].INT & USB_DIEP_INT_NAKINTRPT )
+          {
+            USB_DINEPS[ ep->num ].INT = USB_DIEP_INT_NAKINTRPT;
+          }
+#endif
+        }
+      }
+    }
+  }
+}
+
+/*
+ * Handle OUT endpoint transfer interrupt.
+ */
+static void Handle_USB_GINTSTS_OEPINT( void )
+{
+  int epnum;
+  uint16_t epint;
+  uint16_t epmask;
+  uint32_t status;
+  USBD_Ep_TypeDef *ep;
+
+  DEBUG_USB_INT_HI_PUTCHAR( 'o' );
+
+  epint = USBDHAL_GetAllOutEpInts();
+  for ( epnum = 0,                epmask = 1;
+        epnum <= MAX_NUM_OUT_EPS;
+        epnum++,                  epmask <<= 1 )
+  {
+    if ( epint & epmask )
+    {
+      ep = USBD_GetEpFromAddr( epnum );
+      status = USBDHAL_GetOutEpInts( ep );
+
+#if defined( USB_DOEP0INT_STUPPKTRCVD )
+      HandleOutEpIntr( status, ep );
+#else
+      if ( status & USB_DOEP_INT_XFERCOMPL )
+      {
+        USB_DOUTEPS[ epnum ].INT = USB_DOEP_INT_XFERCOMPL;
+        DEBUG_USB_INT_HI_PUTCHAR( 'c' );
+        ProcessOepData( ep );
+      }
+
+      /* Setup Phase Done */
+      if ( status & USB_DOEP0INT_SETUP )
+      {
+        ProcessSetup();
+      }
+#endif
+    }
+  }
+}
+
+#if !defined( USB_DOEP0INT_STUPPKTRCVD )
+static void ProcessOepData( USBD_Ep_TypeDef *ep )
+{
+  if ( ep->num == 0 )
+  {
+
+#ifdef __MBED__
+    int xfer_size = ep->packetSize - (( USB->DOEP0TSIZ & _USB_DOEP0TSIZ_XFERSIZE_MASK )
+                                      >> _USB_DOEP0TSIZ_XFERSIZE_SHIFT);
+    int setup_pkt_received = USBDHAL_GetOutEpInts( ep ) & USB_DOEP0INT_SETUP;
+
+    if ( (!setup_pkt_received && xfer_size == 0) ||
+         (setup_pkt_received && xfer_size == 8) )
+    {
+      /* Higher levels need to see the correct transfer amount for ZLPs */
+      ep->remaining = 0;
+      ep->xferred = 0;
+    }
+    else
+    {
+      /* FIXME - does not work if actual read size > 56 */
+      if ( setup_pkt_received ) xfer_size -= 8;
+
+      ep->xferred = xfer_size;
+      ep->remaining -= xfer_size;
+    }
+#else
+    if ( ep->remaining > ep->packetSize )
+    {
+      ep->remaining -= ep->packetSize;
+      ep->xferred += ep->packetSize;
+    }
+    else
+    {
+      ep->xferred += ep->remaining;
+      ep->remaining = 0;
+    }
+#endif
+
+    USBDEP_Ep0Handler( dev );
+  }
+  else
+  {
+    ep->xferred = ep->hwXferSize -
+        ( ( USB_DOUTEPS[ ep->num ].TSIZ & _USB_DOEP_TSIZ_XFERSIZE_MASK )>>
+          _USB_DOEP_TSIZ_XFERSIZE_SHIFT );
+    ep->remaining -= ep->xferred;
+    USBDEP_EpHandler( ep->addr );
+  }
+}
+#endif
+
+#if !defined( USB_DOEP0INT_STUPPKTRCVD )
+static void ProcessSetup( void )
+{
+  DEBUG_USB_INT_LO_PUTS( "\nS" );
+
+  if ( USB->DOEP0INT & USB_DOEP0INT_BACK2BACKSETUP )
+  {                           /* Back to back setup packets received */
+    USB->DOEP0INT = USB_DOEP0INT_BACK2BACKSETUP;
+    DEBUG_USB_INT_LO_PUTS( "B2B" );
+
+    dev->setup = (USB_Setup_TypeDef*)( USB->DOEP0DMAADDR - USB_SETUP_PKT_SIZE );
+  }
+  else
+  {
+    /* Read SETUP packet counter from hw. */
+    int supCnt = ( USB->DOEP0TSIZ & _USB_DOEP0TSIZ_SUPCNT_MASK )
+                 >> _USB_DOEP0TSIZ_SUPCNT_SHIFT;
+
+    if ( supCnt == 3 )
+      supCnt = 2;
+
+    dev->setup = &dev->setupPkt[ 2 - supCnt ];
+  }
+  USB->DOEP0TSIZ |= 3 << _USB_DOEP0TSIZ_SUPCNT_SHIFT;
+  USB->DOEP0DMAADDR = (uint32_t)dev->setupPkt;
+  USB->DOEP0INT = USB_DOEP0INT_SETUP;
+
+  USBDEP_Ep0Handler( dev );   /* Call the SETUP handler for EP0 */
+}
+#endif
+
+/*
+ * Handle USB reset detected interrupt in suspend mode.
+ */
+static void Handle_USB_GINTSTS_RESETDET  ( void )
+{
+#if ( USB_PWRSAVE_MODE )
+  if ( ! USBD_poweredDown )
+  {
+    USB->GINTSTS = USB_GINTSTS_RESETDET;
+  }
+
+  if ( UsbPowerUp() )
+  {
+    USB->GINTSTS = USB_GINTSTS_RESETDET;
+  }
+
+#if ( USB_PWRSAVE_MODE & USB_PWRSAVE_MODE_ONVBUSOFF )
+  /* Power down immediately if VBUS is off. */
+  if ( ! ( USB->STATUS & USB_STATUS_VREGOS ) )
+  {
+    UsbPowerDown();
+  }
+#endif
+
+#else
+  USB->GINTSTS = USB_GINTSTS_RESETDET;
+#endif /* if ( USB_PWRSAVE_MODE ) */
+
+  if ( USB->STATUS & USB_STATUS_VREGOS )
+  {
+    USBD_SetUsbState( USBD_STATE_DEFAULT );
+  }
+  else
+  {
+    USBD_SetUsbState( USBD_STATE_NONE );
+  }
+  DEBUG_USB_INT_LO_PUTS( "RsuP\n" );
+}
+
+/*
+ * Handle Start Of Frame (SOF) interrupt.
+ */
+static void Handle_USB_GINTSTS_SOF( void )
+{
+  USB->GINTSTS = USB_GINTSTS_SOF;
+
+  if ( dev->callbacks->sofInt )
+  {
+    dev->callbacks->sofInt(
+      ( USB->DSTS & _USB_DSTS_SOFFN_MASK ) >> _USB_DSTS_SOFFN_SHIFT );
+  }
+}
+
+/*
+ * Handle USB port reset interrupt.
+ */
+static void Handle_USB_GINTSTS_USBRST( void )
+{
+  int i;
+
+  DEBUG_USB_INT_LO_PUTS( "ReseT" );
+
+  /* Clear Remote Wakeup Signalling */
+  USB->DCTL &= ~( DCTL_WO_BITMASK | USB_DCTL_RMTWKUPSIG );
+  USBHAL_FlushTxFifo( 0 );
+
+  /* Clear pending interrupts */
+  for ( i = 0; i <= MAX_NUM_IN_EPS; i++ )
+  {
+    USB_DINEPS[ i ].INT = 0xFFFFFFFF;
+  }
+
+  for ( i = 0; i <= MAX_NUM_OUT_EPS; i++ )
+  {
+    USB_DOUTEPS[ i ].INT = 0xFFFFFFFF;
+  }
+
+  USB->DAINTMSK = USB_DAINTMSK_INEPMSK0 | USB_DAINTMSK_OUTEPMSK0;
+#if defined( USB_DOEPMSK_STSPHSERCVDMSK )
+  USB->DOEPMSK  = USB_DOEPMSK_SETUPMSK  | USB_DOEPMSK_XFERCOMPLMSK
+                  | USB_DOEPMSK_STSPHSERCVDMSK;
+#else
+  USB->DOEPMSK  = USB_DOEPMSK_SETUPMSK  | USB_DOEPMSK_XFERCOMPLMSK;
+#endif
+  USB->DIEPMSK  = USB_DIEPMSK_XFERCOMPLMSK;
+
+  /* Reset Device Address */
+  USB->DCFG &= ~_USB_DCFG_DEVADDR_MASK;
+
+  /* Setup EP0 to receive SETUP packets */
+  USBDHAL_StartEp0Setup( dev );
+  USBDHAL_EnableInts( dev );
+
+  if ( dev->callbacks->usbReset )
+  {
+    dev->callbacks->usbReset();
+  }
+
+  USBD_SetUsbState( USBD_STATE_DEFAULT );
+  USBDHAL_AbortAllTransfers( USB_STATUS_DEVICE_RESET );
+}
+
+/*
+ * Handle USB port suspend interrupt.
+ */
+static void Handle_USB_GINTSTS_USBSUSP( void )
+{
+  USBD_State_TypeDef state;
+
+  USB->GINTSTS = USB_GINTSTS_USBSUSP;
+  USBDHAL_AbortAllTransfers( USB_STATUS_DEVICE_SUSPENDED );
+  DEBUG_USB_INT_LO_PUTS( "\nSusP" );
+
+  if ( USBD_GetUsbState() == USBD_STATE_NONE )
+  {
+    USBD_SetUsbState( USBD_STATE_POWERED );
+  }
+
+  state = USBD_GetUsbState();
+  if ( ( state == USBD_STATE_POWERED    ) ||
+       ( state == USBD_STATE_DEFAULT    ) ||
+       ( state == USBD_STATE_ADDRESSED  ) ||
+       ( state == USBD_STATE_CONFIGURED )    )
+  {
+#if ( USB_PWRSAVE_MODE )
+    UsbPowerDown();
+#endif
+    USBD_SetUsbState( USBD_STATE_SUSPENDED );
+  }
+}
+
+/*
+ * Handle USB port wakeup interrupt.
+ */
+static void Handle_USB_GINTSTS_WKUPINT( void )
+{
+#if ( USB_PWRSAVE_MODE )
+  if ( ! USBD_poweredDown )
+  {
+    USB->GINTSTS = USB_GINTSTS_WKUPINT;
+  }
+
+  if ( UsbPowerUp() )
+  {
+    USB->GINTSTS = USB_GINTSTS_WKUPINT;
+    USBDHAL_StartEp0Setup( dev );
+    USBDHAL_Ep0Activate( dev->ep0MpsCode );
+  }
+#else
+  USB->GINTSTS = USB_GINTSTS_WKUPINT;
+#endif
+
+  USBD_SetUsbState( dev->savedState );
+  DEBUG_USB_INT_LO_PUTS( "WkuP\n" );
+}
+
+#if ( USB_PWRSAVE_MODE )
+/*
+ * Backup essential USB core registers, and set the core in partial powerdown
+ * mode. Optionally prepare entry into EM2.
+ */
+static bool UsbPowerDown( void )
+{
+#if ( NUM_EP_USED > 0 ) || ( FIFO_CNT > 0 )
+  int i;
+#endif
+#if ( NUM_EP_USED > 0 )
+  int epNum;
+  USBD_Ep_TypeDef *ep;
+#endif
+
+  if ( !USBD_poweredDown )
+  {
+    USBD_poweredDown = true;
+    DEBUG_USB_INT_LO_PUTCHAR( '\\' );
+
+    /* Backup USB core registers. */
+    x_USB_GINTMSK   = USB->GINTMSK;
+#if defined(_USB_GOTGCTL_MASK)
+    x_USB_GOTGCTL   = USB->GOTGCTL;
+#endif
+    x_USB_GAHBCFG   = USB->GAHBCFG;
+    x_USB_GUSBCFG   = USB->GUSBCFG;
+    x_USB_GRXFSIZ   = USB->GRXFSIZ;
+    x_USB_GNPTXFSIZ = USB->GNPTXFSIZ;
+    x_USB_DCFG      = USB->DCFG;
+    x_USB_DCTL      = USB->DCTL;
+    x_USB_DAINTMSK  = USB->DAINTMSK;
+    x_USB_DIEPMSK   = USB->DIEPMSK;
+    x_USB_DOEPMSK   = USB->DOEPMSK;
+    x_USB_PCGCCTL   = USB->PCGCCTL;
+
+#if ( NUM_EP_USED > 0 )
+    for ( i = 0; i < NUM_EP_USED; i++ )
+    {
+      ep = &dev->ep[ i+1 ];
+      epNum = ep->num;
+      if ( ep->in )
+      {
+        x_USB_EP_CTL[     i ] = USB_DINEPS[ epNum ].CTL;
+        x_USB_EP_TSIZ[    i ] = USB_DINEPS[ epNum ].TSIZ;
+        x_USB_EP_DMAADDR[ i ] = USB_DINEPS[ epNum ].DMAADDR;
+      }
+      else
+      {
+        x_USB_EP_CTL[     i ] = USB_DOUTEPS[ epNum ].CTL;
+        x_USB_EP_TSIZ[    i ] = USB_DOUTEPS[ epNum ].TSIZ;
+        x_USB_EP_DMAADDR[ i ] = USB_DOUTEPS[ epNum ].DMAADDR;
+      }
+    }
+#endif
+
+#if ( FIFO_CNT > 0 )
+    for ( i = 0; i < FIFO_CNT; i++ )
+    {
+      x_USB_DIEPTXFS[ i ] = USB_DIEPTXFS[ i ];
+    }
+#endif
+
+    /* Prepare for wakeup on resume and reset. */
+    USB->DCFG    = (USB->DCFG & ~_USB_DCFG_RESVALID_MASK) |
+                   (4 << _USB_DCFG_RESVALID_SHIFT);
+    USB->DCFG   |= USB_DCFG_ENA32KHZSUSP;
+    USB->GINTMSK = USB_GINTMSK_RESETDETMSK | USB_GINTMSK_WKUPINTMSK;
+
+    /* Enter partial powerdown mode. */
+    USB->PCGCCTL |= USB_PCGCCTL_PWRCLMP;
+    USB->PCGCCTL |= USB_PCGCCTL_RSTPDWNMODULE;
+    USB->PCGCCTL |= USB_PCGCCTL_STOPPCLK;
+
+    /* Record current clock settings. */
+    cmuStatus = CMU->STATUS;
+
+#if ( USB_PWRSAVE_MODE & USB_PWRSAVE_MODE_ENTEREM2 )
+#ifndef __MBED__
+    /* Enter EM2 on interrupt exit. */
+    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk | SCB_SCR_SLEEPONEXIT_Msk;
+#else
+    usbhal_allow_em2(true);
+#endif
+#endif
+
+    /* Switch USBC clock to 32 kHz. */
+#if ( USB_USBC_32kHz_CLK == USB_USBC_32kHz_CLK_LFXO )
+    CMU->CMD = CMU_CMD_USBCCLKSEL_LFXO;
+    while ( ( CMU->STATUS & CMU_STATUS_USBCLFXOSEL ) == 0 ){}
+#else
+    CMU->CMD = CMU_CMD_USBCCLKSEL_LFRCO;
+    while ( ( CMU->STATUS & CMU_STATUS_USBCLFRCOSEL ) == 0 ){}
+#endif
+
+    return true;
+  }
+  return false;
+}
+#endif /* if ( USB_PWRSAVE_MODE ) */
+
+#if ( USB_PWRSAVE_MODE )
+/*
+ * Exit USB core partial powerdown mode, restore essential USB core registers.
+ * Will prevent re-entry back to EM2.
+ * Returns true if a powerup sequence was performed.
+ */
+static bool UsbPowerUp( void )
+{
+#if ( NUM_EP_USED > 0 ) || ( FIFO_CNT > 0 )
+  int i;
+#endif
+#if ( NUM_EP_USED > 0 )
+  int epNum;
+  uint32_t tmp;
+  USBD_Ep_TypeDef *ep;
+#endif
+
+  if ( USBD_poweredDown )
+  {
+    USBD_poweredDown = false;
+    DEBUG_USB_INT_LO_PUTCHAR( '/' );
+
+#if !defined( USB_CORECLK_HFRCO ) || !defined( CMU_OSCENCMD_USHFRCOEN )
+    /* Switch HFCLK from HFRCO to HFXO. */
+    CMU_ClockSelectSet( cmuClock_HF, cmuSelect_HFXO );
+#endif
+
+    /* Turn off HFRCO when not needed. */
+    if ( ( cmuStatus & CMU_STATUS_HFRCOENS ) == 0 )
+    {
+      CMU->OSCENCMD = CMU_OSCENCMD_HFRCODIS;
+    }
+
+    /* Exit partial powerdown mode. */
+    USB->PCGCCTL &= ~USB_PCGCCTL_STOPPCLK;
+    USB->PCGCCTL &= ~(USB_PCGCCTL_PWRCLMP | USB_PCGCCTL_RSTPDWNMODULE);
+
+    if (( USB->GINTSTS & ( USB_GINTSTS_WKUPINT | USB_GINTSTS_RESETDET ) ) == 0)
+    {
+      USB->DCTL = x_USB_DCTL | USB_DCTL_RMTWKUPSIG;
+      USB->DCTL = x_USB_DCTL;
+    }
+
+    /* Restore USB core registers. */
+    USB->GUSBCFG = x_USB_GUSBCFG;
+    USB->DCFG    = x_USB_DCFG;
+
+#if ( FIFO_CNT > 0 )
+    for ( i = 0; i < FIFO_CNT; i++ )
+    {
+      USB_DIEPTXFS[ i ] = x_USB_DIEPTXFS[ i ];
+    }
+#endif
+
+#if ( NUM_EP_USED > 0 )
+    for ( i = 0; i < NUM_EP_USED; i++ )
+    {
+      ep = &dev->ep[ i+1 ];
+      epNum = ep->num;
+
+      tmp = x_USB_EP_CTL[ i ] &
+            ~( USB_DIEP_CTL_CNAK       | USB_DIEP_CTL_SNAK       |
+               USB_DIEP_CTL_SETD0PIDEF | USB_DIEP_CTL_SETD1PIDOF   );
+
+      if ( x_USB_EP_CTL[ i ] & USB_DIEP_CTL_DPIDEOF )
+        tmp |= USB_DIEP_CTL_SETD1PIDOF;
+      else
+        tmp |= USB_DIEP_CTL_SETD0PIDEF;
+
+      if ( x_USB_EP_CTL[ i ] & USB_DIEP_CTL_NAKSTS )
+        tmp |= USB_DIEP_CTL_SNAK;
+      else
+        tmp |= USB_DIEP_CTL_CNAK;
+
+      if ( ep->in )
+      {
+        USB_DINEPS[ epNum ].CTL     = tmp;
+        USB_DINEPS[ epNum ].TSIZ    = x_USB_EP_TSIZ[    i ];
+        USB_DINEPS[ epNum ].DMAADDR = x_USB_EP_DMAADDR[ i ];
+      }
+      else
+      {
+        USB_DOUTEPS[ epNum ].CTL     = tmp;
+        USB_DOUTEPS[ epNum ].TSIZ    = x_USB_EP_TSIZ[    i ];
+        USB_DOUTEPS[ epNum ].DMAADDR = x_USB_EP_DMAADDR[ i ];
+      }
+    }
+#endif
+
+    USB->PCGCCTL   = x_USB_PCGCCTL;
+    USB->DOEPMSK   = x_USB_DOEPMSK;
+    USB->DIEPMSK   = x_USB_DIEPMSK;
+    USB->DAINTMSK  = x_USB_DAINTMSK;
+    USB->DCTL      = x_USB_DCTL;
+    USB->GNPTXFSIZ = x_USB_GNPTXFSIZ;
+    USB->GRXFSIZ   = x_USB_GRXFSIZ;
+    USB->GAHBCFG   = x_USB_GAHBCFG;
+#if defined(_USB_GOTGCTL_MASK)
+    USB->GOTGCTL   = x_USB_GOTGCTL;
+#endif
+    USB->GINTMSK   = x_USB_GINTMSK;
+
+    USB->DCTL |= USB_DCTL_PWRONPRGDONE;
+
+#if ( USB_PWRSAVE_MODE & USB_PWRSAVE_MODE_ENTEREM2 )
+#ifndef __MBED__
+    /* Do not reenter EM2 on interrupt exit. */
+    SCB->SCR &= ~(SCB_SCR_SLEEPDEEP_Msk | SCB_SCR_SLEEPONEXIT_Msk);
+#else
+    usbhal_allow_em2(false);
+#endif
+#endif
+
+    return true;
+  }
+  return false;
+}
+#endif /* if ( USB_PWRSAVE_MODE ) */
+
+#if defined( USB_DOEP0INT_STUPPKTRCVD )
+static void HandleOutEpIntr( uint32_t status, USBD_Ep_TypeDef *ep )
+{
+  uint32_t doeptsiz;
+
+  if ( ep->num == 0 )
+  {
+    if ( status & USB_DOEP0INT_XFERCOMPL )
+    {
+      USB->DOEP0INT = USB_DOEP0INT_XFERCOMPL;
+      doeptsiz      = USB->DOEP0TSIZ;
+
+      if ( ep->state == D_EP_IDLE )
+      {
+        if ( status & USB_DOEP0INT_STUPPKTRCVD )
+        {
+          USB->DOEP0INT = USB_DOEP0INT_STUPPKTRCVD;
+        }
+        status = USBDHAL_GetOutEpInts( ep );
+        doeptsiz = USB->DOEP0TSIZ;
+
+        if ( status & USB_DOEP0INT_SETUP )
+        {
+retry:
+          /* Already started data stage, clear setup */
+          USB->DOEP0INT = USB_DOEP0INT_SETUP;
+          status       &= ~USB_DOEP0INT_SETUP;
+          {
+            int supCnt = ( doeptsiz & _USB_DOEP0TSIZ_SUPCNT_MASK )
+                         >> _USB_DOEP0TSIZ_SUPCNT_SHIFT;
+
+            if ( supCnt == 3 )
+              supCnt = 2;
+
+            dev->setup = &dev->setupPkt[ 2 - supCnt ];
+          }
+          DEBUG_USB_INT_LO_PUTS( "\nS" );
+          USBDEP_Ep0Handler( dev );
+
+          /* Prepare for more setup packets */
+          if ( ep->state == D_EP0_IN_STATUS || ep->state == D_EP_TRANSMITTING )
+          {
+            USBDHAL_StartEp0Setup( dev );
+          }
+        }
+        else /* xfercompl && idle && !setup */
+        {
+            status = USBDHAL_GetOutEpInts( ep );
+            if ( status & USB_DOEP0INT_SETUP )
+              goto retry;
+            USBDHAL_StartEp0Setup( dev );
+        }
+      }
+      else /* ep0state != EP0_IDLE */
+      {
+#ifdef __MBED__
+        if ( ep->state == D_EP_RECEIVING )
+        {
+          int xfer_size = ep->packetSize - (( USB->DOEP0TSIZ & _USB_DOEP0TSIZ_XFERSIZE_MASK )
+                                            >> _USB_DOEP0TSIZ_XFERSIZE_SHIFT);
+          int setup_pkt_received = status & USB_DOEP0INT_SETUP;
+
+          if ( (!setup_pkt_received && xfer_size == 0) ||
+               (setup_pkt_received && xfer_size == 8) )
+          {
+            /* Higher levels need to see the correct transfer amount for ZLPs */
+            ep->remaining = 0;
+            ep->xferred = 0;
+          }
+          else
+          {
+            /* FIXME - does not work if actual read size > 56 */
+            if ( setup_pkt_received ) xfer_size -= 8;
+
+            ep->xferred = xfer_size;
+            ep->remaining -= xfer_size;
+          }
+
+          USBDEP_Ep0Handler( dev );
+        }
+#else
+        if ( ep->state == D_EP_RECEIVING )
+        {
+          if ( ep->remaining > ep->packetSize )
+          {
+            ep->remaining -= ep->packetSize;
+            ep->xferred += ep->packetSize;
+          }
+          else
+          {
+            ep->xferred += ep->remaining;
+            ep->remaining = 0;
+          }
+          USBDEP_Ep0Handler( dev );
+        }
+        else if ( ep->state == D_EP0_OUT_STATUS )
+        {
+          USBDEP_Ep0Handler( dev );
+        }
+#endif
+      }
+    } /* if ( status & USB_DOEP0INT_XFERCOMPL ) */
+
+    if ( status & USB_DOEP0INT_STSPHSERCVD )
+    {
+      USB->DOEP0INT = USB_DOEP0INT_STSPHSERCVD;
+    }
+
+    if ( status & USB_DOEP0INT_SETUP )
+    {
+      USB->DOEP0INT = USB_DOEP0INT_SETUP;
+      {
+        int supCnt = ( USB->DOEP0TSIZ & _USB_DOEP0TSIZ_SUPCNT_MASK )
+                     >> _USB_DOEP0TSIZ_SUPCNT_SHIFT;
+
+        if ( supCnt == 3 )
+          supCnt = 2;
+
+        dev->setup = &dev->setupPkt[ 2 - supCnt ];
+      }
+      DEBUG_USB_INT_LO_PUTS( "\nS" );
+      USBDEP_Ep0Handler( dev );
+    }
+  }
+  else /* epnum != 0 */
+  {
+    if ( status & USB_DOEP_INT_XFERCOMPL )
+    {
+      USB_DOUTEPS[ ep->num ].INT = USB_DOEP_INT_XFERCOMPL;
+
+      ep->xferred = ep->hwXferSize -
+          ( ( USB_DOUTEPS[ ep->num ].TSIZ & _USB_DOEP_TSIZ_XFERSIZE_MASK )>>
+            _USB_DOEP_TSIZ_XFERSIZE_SHIFT );
+      ep->remaining -= ep->xferred;
+
+      USBDEP_EpHandler( ep->addr );
+    }
+  }
+}
+#endif
+
+/** @endcond */
+
+#endif /* defined( USB_DEVICE ) */
+#endif /* defined( USB_PRESENT ) && ( USB_COUNT == 1 ) */