/**************************************************************************//**
 * Copyright (c) 2015 by Silicon Laboratories Inc. All rights reserved.
 *
 * http://developer.silabs.com/legal/version/v11/Silicon_Labs_Software_License_Agreement.txt
 *****************************************************************************/

#include "si_toolchain.h"
#include "efm8_usb.h"
#include <stdint.h>
#include <endian.h>

// -----------------------------------------------------------------------------
// Function Prototypes

static USB_Status_TypeDef ClearFeature(void);
static USB_Status_TypeDef GetConfiguration(void);
static USB_Status_TypeDef GetDescriptor(void);
static USB_Status_TypeDef GetInterface(void);
static USB_Status_TypeDef GetStatus(void);
static USB_Status_TypeDef SetAddress(void);
static USB_Status_TypeDef SetConfiguration(void);
static USB_Status_TypeDef SetFeature(void);
static USB_Status_TypeDef SetInterface(void);
static void USBD_ActivateAllEps(bool forceIdle);
static void EP0_Write(SI_VARIABLE_SEGMENT_POINTER(dat, uint8_t, SI_SEG_GENERIC), uint16_t numBytes);

// -----------------------------------------------------------------------------
// Global Variables

extern SI_SEGMENT_VARIABLE(myUsbDevice, USBD_Device_TypeDef, MEM_MODEL_SEG);
const SI_SEGMENT_VARIABLE(txZero[2], uint8_t, SI_SEG_CODE);

// -----------------------------------------------------------------------------
// Static Global Variables

static uint16_t pStatus;

// -----------------------------------------------------------------------------
// Chapter 9 Functions

/***************************************************************************//**
 * @brief       Processes Standard Request (Chapter 9 Command)
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
USB_Status_TypeDef USBDCH9_SetupCmd(void)
{
  USB_Status_TypeDef status = USB_STATUS_OK;

  switch (myUsbDevice.setup.bRequest)
  {
    case GET_STATUS:
      status = GetStatus();
      break;

    case CLEAR_FEATURE:
      status = ClearFeature();
      break;

    case SET_FEATURE:
      status = SetFeature();
      break;

    case SET_ADDRESS:
      status = SetAddress();
      break;

    case GET_DESCRIPTOR:
      status = GetDescriptor();
      break;

    case GET_CONFIGURATION:
      status = GetConfiguration();
      break;

    case SET_CONFIGURATION:
      status = SetConfiguration();
      break;

    case GET_INTERFACE:
      status = GetInterface();
      break;

    case SET_INTERFACE:
      status = SetInterface();
      break;

    default:
      status = USB_STATUS_REQ_ERR;
      break;
  }

  return status;
}

/***************************************************************************//**
 * @brief       Clears the requested feature
 * @details     Supports CLEAR_FEATURE for Remote Wakeup and Endpoint Halt
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef ClearFeature(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if (myUsbDevice.setup.wLength == 0)
  {
    switch (myUsbDevice.setup.bmRequestType.Recipient)
    {
  #if SLAB_USB_REMOTE_WAKEUP_ENABLED
      case USB_SETUP_RECIPIENT_DEVICE:
        if ((myUsbDevice.setup.wIndex == 0)
            && (myUsbDevice.setup.wValue == USB_FEATURE_DEVICE_REMOTE_WAKEUP)
            && (myUsbDevice.state >= USBD_STATE_ADDRESSED))
        {
          // Remote wakeup feature clear
          myUsbDevice.remoteWakeupEnabled = false;
          retVal = USB_STATUS_OK;
        }
        break;
  #endif // SLAB_USB_REMOTE_WAKEUP_ENABLED
      case USB_SETUP_RECIPIENT_ENDPOINT:
        if (myUsbDevice.setup.wValue == USB_FEATURE_ENDPOINT_HALT)
        {
          // Device does not support halting endpoint 0, but do not return
          // an error as this is a valid request
          if (((myUsbDevice.setup.wIndex & ~USB_EP_DIR_IN) == 0)
              && (myUsbDevice.state >= USBD_STATE_ADDRESSED))
          {
            retVal = USB_STATUS_OK;
          }
          else if (((myUsbDevice.setup.wIndex & ~USB_SETUP_DIR_D2H) < SLAB_USB_NUM_EPS_USED)
                   && (myUsbDevice.state == USBD_STATE_CONFIGURED))
          {
            retVal = USB_STATUS_OK;
            USB_SetIndex((myUsbDevice.setup.wIndex & 0xFF) & ~USB_SETUP_DIR_D2H);

#if (SLAB_USB_EP1IN_USED || SLAB_USB_EP2IN_USED || SLAB_USB_EP3IN_USED)
            if ((myUsbDevice.setup.wIndex & 0xFF) & USB_EP_DIR_IN)
            {
              USB_EpnInEndStallAndClearDataToggle();
            }
#endif
#if (SLAB_USB_EP1OUT_USED || SLAB_USB_EP2OUT_USED || SLAB_USB_EP3OUT_USED)
            if (((myUsbDevice.setup.wIndex & 0xFF) & USB_EP_DIR_IN) == 0)
            {
              USB_EpnOutEndStallAndClearDataToggle();
            }
#endif

            switch (myUsbDevice.setup.wIndex & 0xFF)
            {
#if SLAB_USB_EP1OUT_USED
              case (USB_EP_DIR_OUT | 1):
                if (myUsbDevice.ep1out.state != D_EP_RECEIVING)
                {
                  myUsbDevice.ep1out.state = D_EP_IDLE;
                }
                break;
#endif
#if SLAB_USB_EP2OUT_USED
              case (USB_EP_DIR_OUT | 2):
                if (myUsbDevice.ep2out.state != D_EP_RECEIVING)
                {
                  myUsbDevice.ep2out.state = D_EP_IDLE;
                }
                break;
#endif
#if SLAB_USB_EP3OUT_USED
              case (USB_EP_DIR_OUT | 3):
                if (myUsbDevice.ep3out.state != D_EP_RECEIVING)
                {
                  myUsbDevice.ep3out.state = D_EP_IDLE;
                }
                break;
#endif
#if SLAB_USB_EP1IN_USED
              case (USB_EP_DIR_IN | 1):
                if (myUsbDevice.ep1in.state != D_EP_TRANSMITTING)
                {
                  myUsbDevice.ep1in.state = D_EP_IDLE;
                }
                break;
#endif
#if SLAB_USB_EP2IN_USED
              case (USB_EP_DIR_IN | 2):
                if (myUsbDevice.ep2in.state != D_EP_TRANSMITTING)
                {
                  myUsbDevice.ep2in.state = D_EP_IDLE;
                }
                break;
#endif
#if SLAB_USB_EP3IN_USED
              case (USB_EP_DIR_IN | 3):
                if (myUsbDevice.ep3in.state != D_EP_TRANSMITTING)
                {
                  myUsbDevice.ep3in.state = D_EP_IDLE;
                }
                break;
#endif
            }
          }
        }
    }
  }
  return retVal;
}

/***************************************************************************//**
 * @brief       Gets the current configuration value
 * @details     Zero means the device is not configured, a non-zero value
 *              is the configuration value of the configured device.
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef GetConfiguration(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if ((myUsbDevice.setup.wIndex == 0)
      && (myUsbDevice.setup.wValue == 0)
      && (myUsbDevice.setup.wLength == 1)
      && (myUsbDevice.setup.bmRequestType.Direction == USB_SETUP_DIR_IN)
      && (myUsbDevice.setup.bmRequestType.Recipient == USB_SETUP_RECIPIENT_DEVICE))
  {
    if (myUsbDevice.state == USBD_STATE_ADDRESSED)
    {
      EP0_Write((SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))txZero, 1);
      retVal = USB_STATUS_OK;
    }
    else if (myUsbDevice.state == USBD_STATE_CONFIGURED)
    {
      EP0_Write(&myUsbDevice.configurationValue, 1);
      retVal = USB_STATUS_OK;
    }
  }
  return retVal;
}

/***************************************************************************//**
 * @brief       Sends the requested USB Descriptor
 * @details     Supports single or multiple languages (configured by
 *              @ref SLAB_USB_NUM_LANGUAGES).
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef GetDescriptor(void)
{
#if (SLAB_USB_NUM_LANGUAGES > 1)
  bool langSupported;
  uint8_t lang;
#endif

  uint8_t index;
  uint16_t length = 0;
  SI_VARIABLE_SEGMENT_POINTER(dat, uint8_t, SI_SEG_GENERIC);
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if (*((uint8_t *)&myUsbDevice.setup.bmRequestType) ==
      (USB_SETUP_DIR_D2H | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE))
  {
    index = myUsbDevice.setup.wValue & 0xFF;

    switch (myUsbDevice.setup.wValue >> 8)
    {
      case USB_DEVICE_DESCRIPTOR:
        if (index != 0)
        {
          break;
        }
        dat = (SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))myUsbDevice.deviceDescriptor;
        length = myUsbDevice.deviceDescriptor->bLength;
        break;

      case USB_CONFIG_DESCRIPTOR:
        if (index != 0)
        {
          break;
        }
        dat = (SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))myUsbDevice.configDescriptor;
        length = le16toh(myUsbDevice.configDescriptor->wTotalLength);
        break;

      case USB_STRING_DESCRIPTOR:
  #if (SLAB_USB_NUM_LANGUAGES == 1)

        dat = (SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))myUsbDevice.stringDescriptors[index];

        // Index 0 is the language string. If SLAB_USB_NUM_LANGUAGES == 1, we
        // know the length will be 4 and the format will be UTF16LE.
        if (index == 0)
        {
          length = 4;
          myUsbDevice.ep0String.encoding.type = USB_STRING_DESCRIPTOR_UTF16LE;
        }
        // Otherwise, verify the language is correct (either the value set as
        // SLAB_USB_LANGUAGE in usbconfig.h, or 0).
        else if ((myUsbDevice.setup.wIndex == 0) || (myUsbDevice.setup.wIndex == SLAB_USB_LANGUAGE))
        {
          // Verify the index is valid
          if (index < myUsbDevice.numberOfStrings)
          {
            length = *(dat + USB_STRING_DESCRIPTOR_LENGTH);
            myUsbDevice.ep0String.encoding.type = *(dat + USB_STRING_DESCRIPTOR_ENCODING);
            dat += USB_STRING_DESCRIPTOR_LENGTH;
            myUsbDevice.ep0String.encoding.init = true;
          }
        }
  #elif (SLAB_USB_NUM_LANGUAGES > 1)

        langSupported = false;

        // Index 0 is the language.
        if (index == 0)
        {
          dat = ((SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))myUsbDevice.stringDescriptors->languageArray[0][index]);
          length = *((uint8_t *)dat);
          myUsbDevice.ep0String.encoding.type = USB_STRING_DESCRIPTOR_UTF16LE;
        }
        else
        {
          // Otherwise, verify the language is one of the supported languages or 0.
          for (lang = 0; lang < SLAB_USB_NUM_LANGUAGES; lang++)
          {
            if ((myUsbDevice.stringDescriptors->languageIDs[lang] == myUsbDevice.setup.wIndex)
                || (myUsbDevice.stringDescriptors->languageIDs[lang] == 0))
            {
              langSupported = true;
              break;
            }
          }
          if ((langSupported == true) && (index < myUsbDevice.numberOfStrings))
          {
            dat = ((SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))myUsbDevice.stringDescriptors->languageArray[lang][index]);
            length = *(dat + USB_STRING_DESCRIPTOR_LENGTH);
            myUsbDevice.ep0String.encoding.type = *(dat + USB_STRING_DESCRIPTOR_ENCODING);
            dat += USB_STRING_DESCRIPTOR_LENGTH;

            if (myUsbDevice.ep0String.encoding.type == USB_STRING_DESCRIPTOR_UTF16LE_PACKED)
            {
              myUsbDevice.ep0String.encoding.init = true;
            }
            else
            {
              myUsbDevice.ep0String.encoding.init = false;
            }
          }
        }
  #endif // ( SLAB_USB_NUM_LANGUAGES == 1 )
    }

    // If there is a descriptor to send, get the proper length, then call
    // EP0_Write() to send.
    if (length)
    {
      if (length > myUsbDevice.setup.wLength)
      {
        length = myUsbDevice.setup.wLength;
      }

      EP0_Write(dat, length);

      retVal = USB_STATUS_OK;
    }
  }

  return retVal;
}

/***************************************************************************//**
 * @brief       Sends the current interface alternate setting
 * @details     Sends 0x0000 if alternate interfaces are not supported.
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef GetInterface(void)
{
  uint16_t interface = myUsbDevice.setup.wIndex;
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if ((interface < SLAB_USB_NUM_INTERFACES)
      && (myUsbDevice.setup.wLength == 1)
      && (myUsbDevice.setup.wValue == 0)
      && (*((uint8_t *)&myUsbDevice.setup.bmRequestType) ==
          (USB_SETUP_DIR_D2H | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE)))
  {
    if (myUsbDevice.state == USBD_STATE_CONFIGURED)
    {
#if (SLAB_USB_SUPPORT_ALT_INTERFACES)
      // Return the alternate setting for the specified interface
      EP0_Write((SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))&myUsbDevice.interfaceAltSetting[interface], 1);
#else
      // Alternate interfaces are not supported, so return 0x0000.
      EP0_Write((SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))&txZero, 1);
#endif
      retVal = USB_STATUS_OK;
    }
  }
  return retVal;
}

/***************************************************************************//**
 * @brief       Sends the requested Remote Wakeup, Self-Powered, or
 *              Endpoint Status
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef GetStatus(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if ((myUsbDevice.setup.wLength == 2)
      && (myUsbDevice.setup.wValue == 0)
      && (myUsbDevice.setup.bmRequestType.Direction == USB_SETUP_DIR_IN)
      && (myUsbDevice.state >= USBD_STATE_ADDRESSED))
  {
    pStatus = htole16(0);         // Default return value is 0x0000

    switch (myUsbDevice.setup.bmRequestType.Recipient)
    {
      case USB_SETUP_RECIPIENT_DEVICE:
        if (myUsbDevice.setup.wIndex == 0)
        {
  #if SLAB_USB_REMOTE_WAKEUP_ENABLED
          // Remote wakeup feature status
          if (myUsbDevice.remoteWakeupEnabled)
          {
            pStatus |= htole16(REMOTE_WAKEUP_ENABLED);
          }
  #endif // SLAB_USB_REMOTE_WAKEUP_ENABLED

  #if SLAB_USB_IS_SELF_POWERED_CB
          // Current self/bus power status
          if (USBD_IsSelfPoweredCb())
          {
            pStatus |= htole16(DEVICE_IS_SELFPOWERED);
          }
  #elif (SLAB_USB_BUS_POWERED == 0)
          pStatus |= htole16(DEVICE_IS_SELFPOWERED);
  #endif // SLAB_USB_IS_SELF_POWERED_CB

          retVal = USB_STATUS_OK;
        }
        break;

      case USB_SETUP_RECIPIENT_INTERFACE:
        if (myUsbDevice.setup.wIndex < SLAB_USB_NUM_INTERFACES)
        {
          retVal = USB_STATUS_OK;
        }
        break;


      case USB_SETUP_RECIPIENT_ENDPOINT:
        // Device does not support halting endpoint 0, but do not give
        // an error as this is a valid request
        if (((myUsbDevice.setup.wIndex & ~USB_EP_DIR_IN) == 0)
            && (myUsbDevice.state == USBD_STATE_ADDRESSED))
        {
          retVal = USB_STATUS_OK;
        }
        else if (myUsbDevice.state == USBD_STATE_CONFIGURED)
        {
          switch (myUsbDevice.setup.wIndex & 0xFF)
          {
  #if SLAB_USB_EP1OUT_USED
            case (USB_EP_DIR_OUT | 1):
              if (myUsbDevice.ep1out.state == D_EP_HALT)
              {
                pStatus = htole16(1);
              }
              retVal = USB_STATUS_OK;
              break;
  #endif
  #if SLAB_USB_EP2OUT_USED
            case (USB_EP_DIR_OUT | 2):
              if (myUsbDevice.ep2out.state == D_EP_HALT)
              {
                pStatus = htole16(1);
              }
              retVal = USB_STATUS_OK;
              break;
  #endif
  #if SLAB_USB_EP3OUT_USED
            case (USB_EP_DIR_OUT | 3):
              if (myUsbDevice.ep3out.state == D_EP_HALT)
              {
                pStatus = htole16(1);
              }
              retVal = USB_STATUS_OK;
              break;
  #endif
  #if SLAB_USB_EP1IN_USED
            case (USB_EP_DIR_IN | 1):
              if (myUsbDevice.ep1in.state == D_EP_HALT)
              {
                pStatus = htole16(1);
              }
              retVal = USB_STATUS_OK;
              break;
  #endif
  #if SLAB_USB_EP2IN_USED
            case (USB_EP_DIR_IN | 2):
              if (myUsbDevice.ep2in.state == D_EP_HALT)
              {
                pStatus = htole16(1);
              }
              retVal = USB_STATUS_OK;
              break;
  #endif
  #if SLAB_USB_EP3IN_USED
            case (USB_EP_DIR_IN | 3):
              if (myUsbDevice.ep3in.state == D_EP_HALT)
              {
                pStatus = htole16(1);
              }
              retVal = USB_STATUS_OK;
              break;
  #endif
          }
        }
        break;
    }

    // If the command was valid, send the requested status.
    if (retVal == USB_STATUS_OK)
    {
      EP0_Write((SI_VARIABLE_SEGMENT_POINTER(, uint8_t, SI_SEG_GENERIC))&pStatus, 2);
    }
  }

  return retVal;
}

/***************************************************************************//**
 * @brief       Sets the Address
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef SetAddress(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if ((myUsbDevice.setup.wValue < 128)
      && (myUsbDevice.setup.wLength == 0)
      && (myUsbDevice.setup.bmRequestType.Recipient == USB_SETUP_RECIPIENT_DEVICE)
      && (myUsbDevice.setup.wIndex == 0))
  {
    // If the device is in the Default state and the address is non-zero, put
    // the device in the Addressed state.
    if (myUsbDevice.state == USBD_STATE_DEFAULT)
    {
      if (myUsbDevice.setup.wValue != 0)
      {
        USBD_SetUsbState(USBD_STATE_ADDRESSED);
      }
      retVal = USB_STATUS_OK;
    }
    // If the device is already addressed and the address is zero, put the
    // device in the Default state.
    else if (myUsbDevice.state == USBD_STATE_ADDRESSED)
    {
      if (myUsbDevice.setup.wValue == 0)
      {
        USBD_SetUsbState(USBD_STATE_DEFAULT);
      }
      retVal = USB_STATUS_OK;
    }

    // Set the new address if the request was valid.
    if (retVal == USB_STATUS_OK)
    {
      USB_SetAddress(myUsbDevice.setup.wValue);
    }
  }

  return retVal;
}

/***************************************************************************//**
 * @brief       Sets the Configuration
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef SetConfiguration(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if (((myUsbDevice.setup.wValue >> 8) == 0)
      && (myUsbDevice.setup.bmRequestType.Recipient == USB_SETUP_RECIPIENT_DEVICE)
      && (myUsbDevice.setup.wLength == 0)
      && (myUsbDevice.setup.wIndex == 0))
  {
    // If the device is in the Addressed state and a valid Configuration value
    // was sent, enter the Configured state.
    if (myUsbDevice.state == USBD_STATE_ADDRESSED)
    {
      if ((myUsbDevice.setup.wValue == 0)
          || (myUsbDevice.setup.wValue == myUsbDevice.configDescriptor->bConfigurationValue))
      {
        myUsbDevice.configurationValue = myUsbDevice.setup.wValue;
        if (myUsbDevice.setup.wValue == myUsbDevice.configDescriptor->bConfigurationValue)
        {
          USBD_ActivateAllEps(true);
          USBD_SetUsbState(USBD_STATE_CONFIGURED);
        }
        retVal = USB_STATUS_OK;
      }
    }
    // If the device is in the Configured state and Configuration zero is sent,
    // abort all transfer and enter the Addressed state.
    else if (myUsbDevice.state == USBD_STATE_CONFIGURED)
    {
      if ((myUsbDevice.setup.wValue == 0)
          || (myUsbDevice.setup.wValue == myUsbDevice.configDescriptor->bConfigurationValue))
      {
        myUsbDevice.configurationValue = myUsbDevice.setup.wValue;
        if (myUsbDevice.setup.wValue == 0)
        {
          USBD_SetUsbState(USBD_STATE_ADDRESSED);
          USBD_AbortAllTransfers();
        }
        else
        {
          // Reenable device endpoints, will reset data toggles
          USBD_ActivateAllEps(false);
        }
        retVal = USB_STATUS_OK;
      }
    }
  }

  return retVal;
}

/***************************************************************************//**
 * @brief       Sets the Remote Wakeup or Endpoint Halt Feature
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef SetFeature(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;

  if (myUsbDevice.setup.wLength == 0)
  {
    switch (myUsbDevice.setup.bmRequestType.Recipient)
    {
  #if SLAB_USB_REMOTE_WAKEUP_ENABLED
      case USB_SETUP_RECIPIENT_DEVICE:
        if ((myUsbDevice.setup.wIndex == 0)     // ITF no. 0
            && (myUsbDevice.setup.wValue == USB_FEATURE_DEVICE_REMOTE_WAKEUP)
            && (myUsbDevice.state == USBD_STATE_CONFIGURED))
        {
          myUsbDevice.remoteWakeupEnabled = true;
          retVal = USB_STATUS_OK;
        }
        break;
  #endif // SLAB_USB_REMOTE_WAKEUP_ENABLED
      case USB_SETUP_RECIPIENT_ENDPOINT:
        // Device does not support halting endpoint 0, but do not return
        // an error as this is a valid request
        if (((myUsbDevice.setup.wIndex & ~USB_EP_DIR_IN) == 0)
            && (myUsbDevice.state >= USBD_STATE_ADDRESSED))
        {
          retVal = USB_STATUS_OK;
        }
        else if ((((myUsbDevice.setup.wIndex) & ~USB_SETUP_DIR_D2H) < SLAB_USB_NUM_EPS_USED)
                 && (myUsbDevice.setup.wValue == USB_FEATURE_ENDPOINT_HALT)
                 && (myUsbDevice.state == USBD_STATE_CONFIGURED))
        {
          retVal = USB_STATUS_OK;
          USB_SetIndex((myUsbDevice.setup.wIndex & 0xFF) & ~USB_SETUP_DIR_D2H);

          // Enable Stalls on the specified endpoint.
#if (SLAB_USB_EP1IN_USED || SLAB_USB_EP2IN_USED || SLAB_USB_EP3IN_USED)
          if ((myUsbDevice.setup.wIndex & 0xFF) & USB_EP_DIR_IN)
          {
            USB_EpnInStall();
          }
#endif
#if (SLAB_USB_EP1OUT_USED || SLAB_USB_EP2OUT_USED || SLAB_USB_EP3OUT_USED)
          if (((myUsbDevice.setup.wIndex & 0xFF) & USB_EP_DIR_IN) == 0)
          {
            USB_EpnOutStall();
          }
#endif

          // Put the specified endpoint in the Halted state.
          switch (myUsbDevice.setup.wIndex & 0xFF)
          {
  #if SLAB_USB_EP1OUT_USED
            case (USB_EP_DIR_OUT | 1):
              myUsbDevice.ep1out.state = D_EP_HALT;
              break;
  #endif
  #if SLAB_USB_EP2OUT_USED
            case (USB_EP_DIR_OUT | 2):
              myUsbDevice.ep2out.state = D_EP_HALT;
              break;
  #endif
  #if SLAB_USB_EP3OUT_USED
            case (USB_EP_DIR_OUT | 3):
              myUsbDevice.ep3out.state = D_EP_HALT;
              break;
  #endif
  #if SLAB_USB_EP1IN_USED
            case (USB_EP_DIR_IN | 1):
              myUsbDevice.ep1in.state = D_EP_HALT;
              break;
  #endif
  #if SLAB_USB_EP2IN_USED
            case (USB_EP_DIR_IN | 2):
              myUsbDevice.ep2in.state = D_EP_HALT;
              break;
  #endif
  #if SLAB_USB_EP3IN_USED
            case (USB_EP_DIR_IN | 3):
              myUsbDevice.ep3in.state = D_EP_HALT;
              break;
  #endif
          }
        }
    }
  }

  return retVal;
}

/***************************************************************************//**
 * @brief       Sets the Interface and Alternate Interface (if supported)
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static USB_Status_TypeDef SetInterface(void)
{
  USB_Status_TypeDef retVal = USB_STATUS_REQ_ERR;
  uint8_t interface = (uint8_t)myUsbDevice.setup.wIndex;
  uint8_t altSetting = (uint8_t)myUsbDevice.setup.wValue;

  if ((interface < SLAB_USB_NUM_INTERFACES)
      && (myUsbDevice.state == USBD_STATE_CONFIGURED)
      && (myUsbDevice.setup.wLength == 0)
#if (SLAB_USB_SUPPORT_ALT_INTERFACES == 0)
      && (altSetting == 0)
#endif
      && (myUsbDevice.setup.bmRequestType.Recipient == USB_SETUP_RECIPIENT_INTERFACE))
  {
#if (SLAB_USB_SUPPORT_ALT_INTERFACES)
    if (USBD_SetInterfaceCb(interface, altSetting) == USB_STATUS_OK)
    {
      myUsbDevice.interfaceAltSetting[interface] = altSetting;
      retVal = USB_STATUS_OK;
    }
#else
#if (SLAB_USB_NUM_INTERFACES == 1)
    // Reset data toggles on EP's
    USBD_ActivateAllEps(false);
#endif // ( SLAB_USB_NUM_INTERFACES == 1 )
    retVal = USB_STATUS_OK;
#endif // ( SLAB_USB_SUPPORT_ALT_INTERFACES )
  }

  return retVal;
}

// -----------------------------------------------------------------------------
// Utility Functions

/***************************************************************************//**
 * @brief       Enables all endpoints for data transfers
 * @return      Status of request (type @ref USB_Status_TypeDef)
 * @note        This function takes no parameters, but it uses the setup command
 *              stored in @ref myUsbDevice.setup.
 ******************************************************************************/
static void USBD_ActivateAllEps(bool forceIdle)
{
  if (forceIdle == true)
  {
#if SLAB_USB_EP1IN_USED
    myUsbDevice.ep1in.state = D_EP_IDLE;
#endif
#if SLAB_USB_EP2IN_USED
    myUsbDevice.ep2in.state = D_EP_IDLE;
#endif
#if SLAB_USB_EP3IN_USED
    myUsbDevice.ep3in.state = D_EP_IDLE;
#endif
#if SLAB_USB_EP1OUT_USED
    myUsbDevice.ep1out.state = D_EP_IDLE;
#endif
#if SLAB_USB_EP2OUT_USED
    myUsbDevice.ep2out.state = D_EP_IDLE;
#endif
#if SLAB_USB_EP3OUT_USED
    myUsbDevice.ep3out.state = D_EP_IDLE;
#endif
  }

#if SLAB_USB_EP1IN_USED
  USB_ActivateEp(1,                                                   // ep
                 SLAB_USB_EP1IN_MAX_PACKET_SIZE,                      // packetSize
                 1,                                                   // inDir
                 SLAB_USB_EP1OUT_USED,                                // splitMode
                 0);                                                  // isoMod
#endif // SLAB_USB_EP1IN_USED
#if SLAB_USB_EP2IN_USED
  USB_ActivateEp(2,                                                   // ep
                 SLAB_USB_EP2IN_MAX_PACKET_SIZE,                      // packetSize
                 1,                                                   // inDir
                 SLAB_USB_EP2OUT_USED,                                // splitMode
                 0);                                                  // isoMod
#endif // SLAB_USB_EP2IN_USED
#if SLAB_USB_EP3IN_USED
  USB_ActivateEp(3,                                                   // ep
                 SLAB_USB_EP3IN_MAX_PACKET_SIZE,                      // packetSize
                 1,                                                   // inDir
                 SLAB_USB_EP3OUT_USED,                                // splitMode
                 (SLAB_USB_EP3IN_TRANSFER_TYPE == USB_EPTYPE_ISOC));  // isoMod
#endif // SLAB_USB_EP3IN_USED
#if SLAB_USB_EP1OUT_USED
  USB_ActivateEp(1,                                                   // ep
                 SLAB_USB_EP1OUT_MAX_PACKET_SIZE,                     // packetSize
                 0,                                                   // inDir
                 SLAB_USB_EP1IN_USED,                                 // splitMode
                 0);                                                  // isoMod
#endif // SLAB_USB_EP1OUT_USED
#if SLAB_USB_EP2OUT_USED
  USB_ActivateEp(2,                                                   // ep
                 SLAB_USB_EP2OUT_MAX_PACKET_SIZE,                     // packetSize
                 0,                                                   // inDir
                 SLAB_USB_EP2IN_USED,                                 // splitMode
                 0);                                                  // isoMod
#endif // SLAB_USB_EP2OUT_USED
#if SLAB_USB_EP3OUT_USED
  USB_ActivateEp(3,                                                   // ep
                 SLAB_USB_EP3OUT_MAX_PACKET_SIZE,                     // packetSize
                 0,                                                   // inDir
                 SLAB_USB_EP3IN_USED,                                 // splitMode
                 (SLAB_USB_EP3OUT_TRANSFER_TYPE == USB_EPTYPE_ISOC)); // isoMod
#endif // SLAB_USB_EP1OUT_USED
}

/***************************************************************************//**
 * @brief       Sets up an Endpoint 0 Write
 * @param       dat
 *              Data to transmit on Endpoint 0
 * @param       numBytes
 *              Number of bytes to transmit on Endpoint 0
 ******************************************************************************/
static void EP0_Write(SI_VARIABLE_SEGMENT_POINTER(dat, uint8_t, SI_SEG_GENERIC), uint16_t numBytes)
{
  if (myUsbDevice.ep0.state == D_EP_IDLE)
  {
    myUsbDevice.ep0.buf = dat;
    myUsbDevice.ep0.remaining = numBytes;
    myUsbDevice.ep0.state = D_EP_TRANSMITTING;
    myUsbDevice.ep0.misc.c = 0;
  }
}
