/* -----------------------------------------------------------------------------
 * Copyright (c) 2013-2017 ARM Ltd.
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software. Permission is granted to anyone to use this
 * software for any purpose, including commercial applications, and to alter
 * it and redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software in
 *    a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 *
 *
 * $Date:        19. September 2017
 * $Revision:    V2.4
 *
 * Driver:       Driver_USBH0
 * Configured:   via RTE_Device.h configuration file
 * Project:      USB Full/Low-Speed Host Driver for ST STM32F10x
 *               Connectivity Line
 * --------------------------------------------------------------------------
 * Use the following configuration settings in the middleware component
 * to connect to this driver.
 *
 *   Configuration Setting                  Value
 *   ---------------------                  -----
 *   Connect to hardware via Driver_USBH# = 0
 *   USB Host controller interface        = Custom
 * --------------------------------------------------------------------------
 * Defines used for driver configuration (at compile time):
 *
 *   USBH_MAX_PIPE_NUM: defines maximum number of Pipes that driver will
 *                      support, this value impacts driver memory
 *                      requirements
 *     - default value: 8
 *     - maximum value: 8
 * -------------------------------------------------------------------------- */

/* History:
 *  Version 2.4
 *    Added support for CMSIS-RTOS2
 *  Version 2.3
 *    Removed interrupt priority handling
 *  Version 2.2
 *    Corrected multiple packet sending.
 *    Corrected PowerControl function for:
 *      - Unconditional Power Off
 *      - Conditional Power full (driver must be initialized)
 *  Version 2.1
 *    Update for USB Host CMSIS Driver API v2.01
 *  Version 2.0
 *    Initial release for USB Host CMSIS Driver API v2.0
 */

#include <stdint.h>
#include <string.h>

#include "RTE_Components.h"

#if       defined(RTE_CMSIS_RTOS2)
#include "cmsis_os2.h"
#elif     defined(RTE_CMSIS_RTOS)
#include "cmsis_os.h"
#endif

#include "stm32f10x.h"

#include "Driver_USBH.h"

#include "OTG_STM32F10x_cl.h"

#ifndef USBH_MAX_PIPE_NUM
#define USBH_MAX_PIPE_NUM               8U
#endif
#if    (USBH_MAX_PIPE_NUM > 8)
#error  Too many Pipes, maximum Pipes that this driver supports is 8 !!!
#endif

extern uint8_t otg_fs_role;

extern void OTG_FS_PinsConfigure   (uint8_t pins_mask);
extern void OTG_FS_PinsUnconfigure (uint8_t pins_mask);
extern void OTG_FS_PinVbusOnOff    (bool state);
extern bool OTG_FS_PinGetOC        (void);


// USBH Driver *****************************************************************

#define ARM_USBH_DRV_VERSION ARM_DRIVER_VERSION_MAJOR_MINOR(2,4)

// Driver Version
static const ARM_DRIVER_VERSION usbh_driver_version = { ARM_USBH_API_VERSION, ARM_USBH_DRV_VERSION };

// Driver Capabilities
static const ARM_USBH_CAPABILITIES usbh_driver_capabilities = {
  0x0001U,      // Root HUB available Ports Mask
  0U,           // Automatic SPLIT packet handling
  1U,           // Signal Connect event
  1U,           // Signal Disconnect event
  0U            // Signal Overcurrent event
};

#define OTG                     OTG_FS

// FIFO sizes in bytes (total available memory for FIFOs is 1.25 kB)
#define RX_FIFO_SIZE            640U    // RxFIFO depth is half of max 1.25 kB
#define TX_FIFO_SIZE_NON_PERI   512U    // Non-periodic Tx FIFO size
#define TX_FIFO_SIZE_PERI       128U    // Periodic Tx FIFO size

static volatile uint32_t *OTG_DFIFO[] = { OTG_FS_DFIFO0,
                                          OTG_FS_DFIFO1,
                                          OTG_FS_DFIFO2,
                                          OTG_FS_DFIFO3,
                                          OTG_FS_DFIFO4,
                                          OTG_FS_DFIFO5,
                                          OTG_FS_DFIFO6,
                                          OTG_FS_DFIFO7
                                        };

typedef struct {                        // Pipe structure definition
  uint32_t  packet;
  uint8_t  *data;
  uint32_t  num;
  uint32_t  num_transferred_total;
  uint32_t  num_transferring;
  uint16_t  ep_max_packet_size;
  uint16_t  interval_reload;
  uint16_t  interval;
  uint8_t   ep_type;
  uint8_t   active;
  uint8_t   in_progress;
  uint8_t   event;
} PIPE_t;

static ARM_USBH_SignalPortEvent_t SignalPortEvent;
static ARM_USBH_SignalPipeEvent_t SignalPipeEvent;

static bool            hw_initialized = false;
static bool            hw_powered     = false;
static bool            port_reset;

// Pipes runtime information
static volatile PIPE_t pipe[USBH_MAX_PIPE_NUM];


// Auxiliary functions

/**
  \fn          uint32_t USBH_CH_GetIndexFromAddress (OTG_FS_HC *ptr_ch)
  \brief       Get the Index of Channel from it's Address.
  \param[in]   ptr_ch   Pointer to the Channel
  \return      Index of the Channel
*/
__INLINE static uint32_t USBH_CH_GetIndexFromAddress (OTG_FS_HC *ptr_ch) {
  return (ptr_ch - ((OTG_FS_HC *)(&OTG->HCCHAR0)));
}

/**
  \fn          OTG_FS_HC *USBH_CH_GetAddressFromIndex (uint32_t index)
  \brief       Get the Channel Address from it's Index.
  \param[in]   index    Index of the Channel
  \return      Address of the Channel
*/
__INLINE static OTG_FS_HC *USBH_CH_GetAddressFromIndex (uint32_t index) {
  return (((OTG_FS_HC *)(&OTG->HCCHAR0)) + index);
}

/**
  \fn          OTG_FS_HC *USBH_CH_FindFree (void)
  \brief       Find a free Channel.
  \return      Pointer to the first free Channel (NULL = no free Channel is available)
*/
__INLINE static OTG_FS_HC *USBH_CH_FindFree (void) {
  OTG_FS_HC *ptr_ch;
  uint32_t   i;

  ptr_ch = (OTG_FS_HC *)(&(OTG->HCCHAR0));

  for (i = 0U; i < USBH_MAX_PIPE_NUM; i++) {
    if ((ptr_ch->HCCHAR & 0x3FFFFFFFU) == 0U) { return ptr_ch; }
    ptr_ch++;
  }

  return NULL;
}

/**
  \fn          bool USBH_CH_Disable (OTG_FS_HC *ptr_ch)
  \brief       Disable the Channel.
  \param[in]   ptr_ch   Pointer to the Channel
  \return      true = success, false = fail
*/
__INLINE static bool USBH_CH_Disable (OTG_FS_HC *ptr_ch) {
  uint32_t i;

  if (ptr_ch == 0U) { return false; }

  if ((ptr_ch->HCINT & OTG_FS_HCINTx_CHH) != 0U) { return true; }

  if ((ptr_ch->HCCHAR & OTG_FS_HCCHARx_CHENA) != 0U) {
    ptr_ch->HCINTMSK = 0U;
    osDelay(1U);
    if ((ptr_ch->HCINT & OTG_FS_HCINTx_NAK) != 0U) {
      ptr_ch->HCINT  =  0x7BBU;
      return true;
    }
    ptr_ch->HCINT  =  0x7BBU;
    ptr_ch->HCCHAR =  ptr_ch->HCCHAR | OTG_FS_HCCHARx_CHENA | OTG_FS_HCCHARx_CHDIS;
    for (i = 0U; i < 1000U; i++) {
      if ((ptr_ch->HCINT & OTG_FS_HCINTx_CHH) != 0U) {
        ptr_ch->HCINT = 0x7BBU;
        return true;
      }
    }
    return false;
  }

  return true;
}

/**
  \fn          bool USBH_HW_StartTransfer (PIPE_t *ptr_pipe, OTG_FS_HC *ptr_ch)
  \brief       Start transfer on Pipe.
  \param[in]   ptr_pipe Pointer to Pipe
  \param[in]   ptr_ch   Pointer to Channel
  \return      true = success, false = fail
*/
static bool USBH_HW_StartTransfer (PIPE_t *ptr_pipe, OTG_FS_HC *ptr_ch) {
  uint32_t           hcchar;
  uint32_t           hctsiz;
  uint32_t           hcintmsk;
  uint32_t           txsts;
  uint32_t           pckt_num;
  uint32_t           data_num;
  uint32_t           max_pckt_size;
  uint32_t           max_data;
  uint32_t           max_num_pckt;
  uint32_t           num_to_transfer;
  uint8_t           *ptr_src;
  volatile uint32_t *ptr_dest;
  uint16_t           cnt;
  uint8_t            out;

  if (ptr_pipe == 0U)                        { return false; }
  if (ptr_ch   == 0U)                        { return false; }
  if ((OTG->HPRT & OTG_FS_HPRT_PCSTS) == 0U) { return false; }

  hcchar   = ptr_ch->HCCHAR;                    // Read channel characteristics
  hctsiz   = ptr_ch->HCTSIZ;                    // Read channel size info
  hcintmsk = 0U;
  cnt      = 0U;
  out      = 0U;

  // Prepare transfer
                                                // Prepare HCCHAR register
  hcchar       &= (OTG_FS_HCCHARx_ODDFRM    |   // Keep ODDFRM
                   OTG_FS_HCCHARx_DAD_MSK   |   // Keep DAD
                   OTG_FS_HCCHARx_MCNT_MSK  |   // Keep MCNT
                   OTG_FS_HCCHARx_EPTYP_MSK |   // Keep EPTYP
                   OTG_FS_HCCHARx_LSDEV     |   // Keep LSDEV
                   OTG_FS_HCCHARx_EPNUM_MSK |   // Keep EPNUM
                   OTG_FS_HCCHARx_MPSIZ_MSK);   // Keep MPSIZ
  hctsiz       &=  OTG_FS_HCTSIZx_DPID_MSK;     // Keep DPID
  switch (ptr_pipe->packet & ARM_USBH_PACKET_TOKEN_Msk) {
    case ARM_USBH_PACKET_IN:
      hcchar   |=  OTG_FS_HCCHARx_EPDIR;
      hcintmsk  = (OTG_FS_HCINTMSKx_DTERRM  |
                   OTG_FS_HCINTMSKx_BBERRM  |
                   OTG_FS_HCINTMSKx_TXERRM  |
                   OTG_FS_HCINTMSKx_ACKM    |
                   OTG_FS_HCINTMSKx_NAKM    |
                   OTG_FS_HCINTMSKx_STALLM  |
                   OTG_FS_HCINTMSKx_XFRCM)  ;
      break;
    case ARM_USBH_PACKET_OUT:
      hcchar   &= ~OTG_FS_HCCHARx_EPDIR;
      hcintmsk  = (OTG_FS_HCINTMSKx_TXERRM  |
//                 OTG_FS_HCINTMSKx_ACKM    |         // After ACK there must be other relevant interrupt so ACK is ignorred
                   OTG_FS_HCINTMSKx_NAKM    |
                   OTG_FS_HCINTMSKx_STALLM  |
                   OTG_FS_HCINTMSKx_XFRCM)  ;
      out       =  1U;
      break;
    case ARM_USBH_PACKET_SETUP:
      hcchar   &= ~OTG_FS_HCCHARx_EPDIR;
      hcintmsk  = (OTG_FS_HCINTMSKx_TXERRM  |
                   OTG_FS_HCINTMSKx_XFRCM)  ;
      hctsiz   &= ~OTG_FS_HCTSIZx_DPID_MSK;
      hctsiz   |=  OTG_FS_HCTSIZx_DPID_MDATA;
      out       =  1U;
      break;
    default:
      return false;
  }

  num_to_transfer = ptr_pipe->num - ptr_pipe->num_transferred_total;
  switch (ptr_pipe->ep_type) {
    case ARM_USB_ENDPOINT_CONTROL:
    case ARM_USB_ENDPOINT_BULK:
      if (out != 0U) {
        txsts = OTG->HNPTXSTS;          // Read non-periodic FIFO status
      }
      break;
    case ARM_USB_ENDPOINT_ISOCHRONOUS:
    case ARM_USB_ENDPOINT_INTERRUPT:
      if (out != 0U) {
        txsts = OTG->HPTXSTS;           // Read non-periodic FIFO status
      }
      if ((OTG->HFNUM & 1U) != 0U) {
        hcchar &= ~OTG_FS_HCCHARx_ODDFRM;
      } else {
        hcchar |=  OTG_FS_HCCHARx_ODDFRM;
      }
      break;
  }
  if (out != 0U) {
    // For packet OUT/SETUP limit number of bytes to send to maximum FIFO size
    // and maximum number of packets
    max_pckt_size =  ptr_pipe->ep_max_packet_size;
    max_data      = (txsts & 0x0000FFFFU) <<  2;
    max_num_pckt  = (txsts & 0x00FF0000U) >> 16;
    if (num_to_transfer > max_data) {
      num_to_transfer = max_data;
    }
    data_num = num_to_transfer;
    for (pckt_num = 1; data_num > max_pckt_size; pckt_num++) {
      data_num -= max_pckt_size;
    }
    if (pckt_num > max_num_pckt) {
      pckt_num = max_num_pckt;
    }

    if (num_to_transfer > (pckt_num * max_pckt_size)) {
      num_to_transfer = pckt_num * max_pckt_size;
    }
    cnt = (num_to_transfer + 3U) / 4U;
  }

  hcchar &= ~OTG_FS_HCCHARx_CHDIS;
  hcchar |=  OTG_FS_HCCHARx_CHENA;

                                                  // Prepare HCTSIZ register
  switch (ptr_pipe->packet & ARM_USBH_PACKET_DATA_Msk) {
    case ARM_USBH_PACKET_DATA0:
      hctsiz   &= ~OTG_FS_HCTSIZx_DPID_MSK;
      hctsiz   |=  OTG_FS_HCTSIZx_DPID_DATA0;
      break;
    case ARM_USBH_PACKET_DATA1:
      hctsiz   &= ~OTG_FS_HCTSIZx_DPID_MSK;
      hctsiz   |=  OTG_FS_HCTSIZx_DPID_DATA1;
      break;
    default:
      break;
  }

                                                  // Prepare HCTSIZ register
  if (num_to_transfer != 0U) {                    // Normal packet
                                                  // Prepare PKTCNT field
    hctsiz |= ((num_to_transfer + ptr_pipe->ep_max_packet_size - 1U) / ptr_pipe->ep_max_packet_size) << 19;
    hctsiz |=   num_to_transfer;                  // Prepare XFRSIZ field
  } else {                                        // Zero length packet
    hctsiz |=   1U << 19;                         // Prepare PKTCNT field
    hctsiz |=   0U;                               // Prepare XFRSIZ field
  }

  if (cnt != 0U) {
    ptr_src  = ptr_pipe->data + ptr_pipe->num_transferred_total;
    ptr_dest = OTG_DFIFO[USBH_CH_GetIndexFromAddress (ptr_ch)];
  }
  if (out != 0U) {
    // For OUT/SETUP transfer num_transferring represents num of bytes to be sent
    ptr_pipe->num_transferring = num_to_transfer;
  } else {
    // For IN transfer num_transferring represents num of bytes received (handled in IRQ)
    ptr_pipe->num_transferring = 0U;
  }
  NVIC_DisableIRQ (OTG_FS_IRQn);                  // Disable OTG interrupt
  ptr_ch->HCINTMSK = hcintmsk;                    // Enable channel interrupts
  ptr_ch->HCTSIZ   = hctsiz;                      // Write ch transfer size
  ptr_ch->HCCHAR   = hcchar;                      // Write ch characteristics
  while (cnt != 0U) {                             // Load data
    *ptr_dest = *((__packed uint32_t *)ptr_src);
    ptr_src  += 4U;
    cnt--;
  }
  NVIC_EnableIRQ  (OTG_FS_IRQn);                  // Enable OTG interrupt

  return true;
}


// USBH Driver functions

/**
  \fn          ARM_DRIVER_VERSION USBH_GetVersion (void)
  \brief       Get driver version.
  \return      \ref ARM_DRIVER_VERSION
*/
static ARM_DRIVER_VERSION USBH_GetVersion (void) { return usbh_driver_version; }

/**
  \fn          ARM_USBH_CAPABILITIES USBH_GetCapabilities (void)
  \brief       Get driver capabilities.
  \return      \ref ARM_USBH_CAPABILITIES
*/
static ARM_USBH_CAPABILITIES USBH_GetCapabilities (void) { return usbh_driver_capabilities; }

/**
  \fn          int32_t USBH_Initialize (ARM_USBH_SignalPortEvent_t cb_port_event,
                                        ARM_USBH_SignalPipeEvent_t cb_pipe_event)
  \brief       Initialize USB Host Interface.
  \param[in]   cb_port_event  Pointer to \ref ARM_USBH_SignalPortEvent
  \param[in]   cb_pipe_event  Pointer to \ref ARM_USBH_SignalPipeEvent
  \return      \ref execution_status
*/
static int32_t USBH_Initialize (ARM_USBH_SignalPortEvent_t cb_port_event,
                                ARM_USBH_SignalPipeEvent_t cb_pipe_event) {

  if (hw_initialized == true) { return ARM_DRIVER_OK; }

  SignalPortEvent = cb_port_event;
  SignalPipeEvent = cb_pipe_event;

  otg_fs_role = ARM_USB_ROLE_HOST;

  OTG_FS_PinsConfigure (ARM_USB_PIN_DP | ARM_USB_PIN_DM | ARM_USB_PIN_OC | ARM_USB_PIN_VBUS);

  hw_initialized = true;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_Uninitialize (void)
  \brief       De-initialize USB Host Interface.
  \return      \ref execution_status
*/
static int32_t USBH_Uninitialize (void) {

  OTG_FS_PinsUnconfigure (ARM_USB_PIN_DP | ARM_USB_PIN_DM | ARM_USB_PIN_OC | ARM_USB_PIN_VBUS);

  otg_fs_role = ARM_USB_ROLE_NONE;

  hw_initialized = false;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PowerControl (ARM_POWER_STATE state)
  \brief       Control USB Host Interface Power.
  \param[in]   state  Power state
  \return      \ref execution_status
*/
static int32_t USBH_PowerControl (ARM_POWER_STATE state) {

  switch (state) {
    case ARM_POWER_OFF:
      RCC->AHBENR   |=  RCC_AHBENR_OTGFSEN;             // OTG FS clock enable
      NVIC_DisableIRQ      (OTG_FS_IRQn);               // Disable interrupt
      NVIC_ClearPendingIRQ (OTG_FS_IRQn);               // Clear pending interrupt

      hw_powered     =  false;                          // Clear powered flag
      OTG->GAHBCFG  &= ~OTG_FS_GAHBCFG_GINTMSK;         // Disable USB interrupts
      RCC->AHBRSTR  |=  RCC_AHBRSTR_OTGFSRST;           // Reset OTG FS module
      port_reset     =  false;                          // Reset variables
      memset((void *)(pipe), 0, sizeof(pipe));

      OTG->GCCFG    &= ~OTG_FS_GCCFG_PWRDWN;            // Enable PHY power down
      OTG->PCGCCTL  |=  OTG_FS_PCGCCTL_STPPCLK;         // Stop PHY clock
      OTG->GCCFG     =  0U;                             // Reset core configuration

      RCC->AHBENR   &= ~RCC_AHBENR_OTGFSEN;             // Disable OTG FS clock
      break;

    case ARM_POWER_FULL:
      if (hw_initialized == false) { return ARM_DRIVER_ERROR; }
      if (hw_powered     == true)  { return ARM_DRIVER_OK;    }

      RCC->AHBENR   |=  RCC_AHBENR_OTGFSEN;             // OTG FS clock enable
      RCC->AHBRSTR  |=  RCC_AHBRSTR_OTGFSRST;           // Reset OTG FS module
      osDelay(1U);
      RCC->AHBRSTR  &= ~RCC_AHBRSTR_OTGFSRST;           // Clear reset of OTG FS module
      osDelay(1U);

      // On-chip Full-speed PHY
      OTG->PCGCCTL  &= ~OTG_FS_PCGCCTL_STPPCLK;         // Start PHY clock
      OTG->GCCFG    |=  OTG_FS_GCCFG_PWRDWN;            // Disable power down
      OTG->GUSBCFG  |=  OTG_FS_GUSBCFG_PHYSEL;          // Full-speed transceiver

      OTG->HCFG     |=  OTG_FS_HCFG_FSLSS;              // Support for FS/LS only

      // Wait until AHB Master state machine is in the idle condition
      while ((OTG->GRSTCTL & OTG_FS_GRSTCTL_AHBIDL) == 0U);

      OTG->GRSTCTL  |=  OTG_FS_GRSTCTL_CSRST;           // Core soft reset
      while ((OTG->GRSTCTL & OTG_FS_GRSTCTL_CSRST)  != 0U);
      osDelay(1U);

      // Wait until AHB Master state machine is in the idle condition
      while ((OTG->GRSTCTL & OTG_FS_GRSTCTL_AHBIDL) == 0U);

      port_reset     =  false;                          // Reset variables
      memset((void *)(pipe), 0, sizeof(pipe));

      OTG->GCCFG    &= ~OTG_FS_GCCFG_VBUSBSEN;          // Disable VBUS sensing device "B"
      OTG->GCCFG    &= ~OTG_FS_GCCFG_VBUSASEN;          // Disable VBUS sensing device "A"
      OTG->GCCFG    |=  OTG_FS_GCCFG_NOVBUSSENS;        // Disable VBUS sensing

      if (((OTG->GUSBCFG & OTG_FS_GUSBCFG_FHMOD) == 0U) || ((OTG->GUSBCFG & OTG_FS_GUSBCFG_FDMOD) != 0U)) {
        OTG->GUSBCFG &= ~OTG_FS_GUSBCFG_FDMOD;          // Clear force device mode
        OTG->GUSBCFG |=  OTG_FS_GUSBCFG_FHMOD;          // Force host mode
        osDelay(100U);
      }

      // Rx FIFO setting
      OTG->GRXFSIZ   = (RX_FIFO_SIZE/4U);
      // Non-periodic Tx FIFO setting
      OTG->HNPTXFSIZ = ((TX_FIFO_SIZE_NON_PERI/4U) << 16) |  (RX_FIFO_SIZE / 4U);
      // Periodic Tx FIFO setting
      OTG->HPTXFSIZ  = ((TX_FIFO_SIZE_PERI    /4U) << 16) | ((RX_FIFO_SIZE + TX_FIFO_SIZE_NON_PERI) / 4U);

      OTG->HAINTMSK  = (1U << USBH_MAX_PIPE_NUM) - 1U;  // Enable channel interrupts
      OTG->GINTMSK   = (OTG_FS_GINTMSK_DISCINT |        // Unmask interrupts
                        OTG_FS_GINTMSK_HCIM    |
                        OTG_FS_GINTMSK_PRTIM   |
                        OTG_FS_GINTMSK_RXFLVLM |
                        OTG_FS_GINTMSK_SOFM)   ;

      OTG->GAHBCFG  |=  OTG_FS_GAHBCFG_GINTMSK;         // Enable interrupts

      hw_powered     = true;                            // Set powered flag
      NVIC_EnableIRQ   (OTG_FS_IRQn);                   // Enable interrupt
      break;

    default:
      return ARM_DRIVER_ERROR_UNSUPPORTED;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PortVbusOnOff (uint8_t port, bool vbus)
  \brief       Root HUB Port VBUS on/off.
  \param[in]   port  Root HUB Port Number
  \param[in]   vbus
                - \b false VBUS off
                - \b true  VBUS on
  \return      \ref execution_status
*/
static int32_t USBH_PortVbusOnOff (uint8_t port, bool vbus) {

  if (hw_powered == false) { return ARM_DRIVER_ERROR;           }
  if (port != 0U)          { return ARM_DRIVER_ERROR_PARAMETER; }

  if (vbus != 0U) {                                     // VBUS power on
    OTG->HPRT    |=  OTG_FS_HPRT_PPWR;                  // Port power on
    OTG_FS_PinVbusOnOff (true);                         // VBUS pin on
  } else {                                              // VBUS power off
    OTG_FS_PinVbusOnOff (false);                        // VBUS pin off
    OTG->HPRT    &= ~OTG_FS_HPRT_PPWR;                  // Port power off
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PortReset (uint8_t port)
  \brief       Do Root HUB Port Reset.
  \param[in]   port  Root HUB Port Number
  \return      \ref execution_status
*/
static int32_t USBH_PortReset (uint8_t port) {
  uint32_t hprt;

  if (hw_powered == false) { return ARM_DRIVER_ERROR;           }
  if (port != 0U)          { return ARM_DRIVER_ERROR_PARAMETER; }

  port_reset = true;
  hprt  =  OTG->HPRT;
  hprt &= ~OTG_FS_HPRT_PENA;                            // Disable port
  hprt |=  OTG_FS_HPRT_PRST;                            // Port reset
  OTG->HPRT = hprt;
  osDelay(18U);
  hprt &= ~OTG_FS_HPRT_PRST;                            // Clear port reset
  OTG->HPRT = hprt;
  osDelay(50U);
  if (port_reset == true) {
    port_reset = false;
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PortSuspend (uint8_t port)
  \brief       Suspend Root HUB Port (stop generating SOFs).
  \param[in]   port  Root HUB Port Number
  \return      \ref execution_status
*/
static int32_t USBH_PortSuspend (uint8_t port) {

  if (hw_powered == false) { return ARM_DRIVER_ERROR;           }
  if (port != 0U)          { return ARM_DRIVER_ERROR_PARAMETER; }

  OTG->HPRT |=  OTG_FS_HPRT_PSUSP;                      // Port suspend

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PortResume (uint8_t port)
  \brief       Resume Root HUB Port (start generating SOFs).
  \param[in]   port  Root HUB Port Number
  \return      \ref execution_status
*/
static int32_t USBH_PortResume (uint8_t port) {

  if (hw_powered == false) { return ARM_DRIVER_ERROR;           }
  if (port != 0U)          { return ARM_DRIVER_ERROR_PARAMETER; }

  OTG->HPRT |=  OTG_FS_HPRT_PRES;                       // Port resume

  return ARM_DRIVER_OK;
}

/**
  \fn          ARM_USBH_PORT_STATE USBH_PortGetState (uint8_t port)
  \brief       Get current Root HUB Port State.
  \param[in]   port  Root HUB Port Number
  \return      Port State \ref ARM_USBH_PORT_STATE
*/
static ARM_USBH_PORT_STATE USBH_PortGetState (uint8_t port) {
  ARM_USBH_PORT_STATE port_state = { 0U, 0U, 0U };
  uint32_t hprt;

  if (hw_powered == false) { return port_state; }
  if (port != 0U)          { return port_state; }

  hprt = OTG->HPRT;

  port_state.connected   = (hprt & OTG_FS_HPRT_PCSTS) != 0U;
#ifdef MX_USB_OTG_FS_Overcurrent_Pin
  port_state.overcurrent = OTG_FS_PinGetOC ();
#else
  port_state.overcurrent = 0U;
#endif
  switch ((hprt & OTG_FS_HPRT_PSPD_MSK) >> OTG_FS_HPRT_PSPD_POS) {
    case 0:                                             // High speed
     break;
    case 1:                                             // Full speed
     port_state.speed = ARM_USB_SPEED_FULL;
     break;
    case 2:                                             // Low speed
     port_state.speed = ARM_USB_SPEED_LOW;
     break;
    default:
     break;
  }

  return port_state;
}

/**
  \fn          ARM_USBH_PIPE_HANDLE USBH_PipeCreate (uint8_t  dev_addr,
                                                     uint8_t  dev_speed,
                                                     uint8_t  hub_addr,
                                                     uint8_t  hub_port,
                                                     uint8_t  ep_addr,
                                                     uint8_t  ep_type,
                                                     uint16_t ep_max_packet_size,
                                                     uint8_t  ep_interval)
  \brief       Create Pipe in System.
  \param[in]   dev_addr   Device Address
  \param[in]   dev_speed  Device Speed
  \param[in]   hub_addr   Hub Address
  \param[in]   hub_port   Hub Port
  \param[in]   ep_addr    Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \param[in]   ep_type    Endpoint Type (ARM_USB_ENDPOINT_xxx)
  \param[in]   ep_max_packet_size Endpoint Maximum Packet Size
  \param[in]   ep_interval        Endpoint Polling Interval
  \return      Pipe Handle \ref ARM_USBH_PIPE_HANDLE
*/
static ARM_USBH_PIPE_HANDLE USBH_PipeCreate (uint8_t dev_addr, uint8_t dev_speed, uint8_t hub_addr, uint8_t hub_port, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_max_packet_size, uint8_t  ep_interval) {
  PIPE_t    *ptr_pipe;
  OTG_FS_HC *ptr_ch;

  if (hw_powered == false) { return 0U; }

  ptr_ch = USBH_CH_FindFree ();                 // Find free Channel
  if (ptr_ch == 0U) { return 0U; }              // If no free

  ptr_pipe = (PIPE_t *)(&pipe[USBH_CH_GetIndexFromAddress (ptr_ch)]);

  memset((void *)ptr_pipe, 0, sizeof(PIPE_t));  // Clear Pipes runtime information

  // Fill in all fields of Endpoint Descriptor
  ptr_ch->HCCHAR = (OTG_FS_HCCHARx_MPSIZ   (ep_max_packet_size)              ) |
                   (OTG_FS_HCCHARx_EPNUM   (ep_addr)                         ) |
                   (OTG_FS_HCCHARx_EPDIR * (((ep_addr >> 7) & 0x0001U) == 0U)) |
                   (OTG_FS_HCCHARx_LSDEV * (dev_speed == ARM_USB_SPEED_LOW)  ) |
                   (OTG_FS_HCCHARx_EPTYP   (ep_type)                         ) |
                   (OTG_FS_HCCHARx_DAD     (dev_addr)                        ) ;

  // Store Pipe settings
  ptr_pipe->ep_max_packet_size = ep_max_packet_size;
  ptr_pipe->ep_type            = ep_type;
  switch (ep_type) {
    case ARM_USB_ENDPOINT_CONTROL:
    case ARM_USB_ENDPOINT_BULK:
      break;
    case ARM_USB_ENDPOINT_ISOCHRONOUS:
    case ARM_USB_ENDPOINT_INTERRUPT:
      if (dev_speed == ARM_USB_SPEED_HIGH) {
        if ((ep_interval > 0U) && (ep_interval <= 16U)) {
          ptr_pipe->interval_reload = (uint16_t)1U << (ep_interval - 1U);
        }
      } else if ((dev_speed == ARM_USB_SPEED_FULL) || (dev_speed == ARM_USB_SPEED_LOW)) {
        if (ep_interval > 0U) {
          ptr_pipe->interval_reload = ep_interval;
        }
      }
      ptr_pipe->interval = 0U;
      ptr_ch->HCCHAR |= OTG_FS_HCCHARx_MCNT((((ep_max_packet_size >> 11) + 1U) & 3U));
      break;
  }

  return ((ARM_USBH_EP_HANDLE)ptr_ch);
}

/**
  \fn          int32_t USBH_PipeModify (ARM_USBH_PIPE_HANDLE pipe_hndl,
                                        uint8_t              dev_addr,
                                        uint8_t              dev_speed,
                                        uint8_t              hub_addr,
                                        uint8_t              hub_port,
                                        uint16_t             ep_max_packet_size)
  \brief       Modify Pipe in System.
  \param[in]   pipe_hndl  Pipe Handle
  \param[in]   dev_addr   Device Address
  \param[in]   dev_speed  Device Speed
  \param[in]   hub_addr   Hub Address
  \param[in]   hub_port   Hub Port
  \param[in]   ep_max_packet_size Endpoint Maximum Packet Size
  \return      \ref execution_status
*/
static int32_t USBH_PipeModify (ARM_USBH_PIPE_HANDLE pipe_hndl, uint8_t dev_addr, uint8_t dev_speed, uint8_t hub_addr, uint8_t hub_port, uint16_t ep_max_packet_size) {
  PIPE_t    *ptr_pipe;
  OTG_FS_HC *ptr_ch;
  uint32_t   hcchar;

  if (hw_powered == false)    { return ARM_DRIVER_ERROR;           }
  if (pipe_hndl  == 0U)       { return ARM_DRIVER_ERROR_PARAMETER; }

  ptr_ch   = (OTG_FS_HC *)(pipe_hndl);
  ptr_pipe = (PIPE_t    *)(&pipe[USBH_CH_GetIndexFromAddress (ptr_ch)]);
  if (ptr_pipe->active != 0U) { return ARM_DRIVER_ERROR_BUSY;      }

  // Fill in all fields of Endpoint Descriptor
  hcchar  =   ptr_ch->HCCHAR;
  hcchar &= ~(OTG_FS_HCCHARx_MPSIZ_MSK |                // Clear maximum packet size field
              OTG_FS_HCCHARx_LSDEV     |                // Clear device speed bit
              OTG_FS_HCCHARx_DAD_MSK)  ;                // Clear device address field
  hcchar |=   OTG_FS_HCCHARx_MPSIZ   (ep_max_packet_size)              |
             (OTG_FS_HCCHARx_LSDEV * (dev_speed == ARM_USB_SPEED_LOW)) |
             (OTG_FS_HCCHARx_DAD     (dev_addr))                       ;
  ptr_ch->HCCHAR = hcchar;                              // Update modified fields

  ptr_pipe->ep_max_packet_size = ep_max_packet_size;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PipeDelete (ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Delete Pipe from System.
  \param[in]   pipe_hndl  Pipe Handle
  \return      \ref execution_status
*/
static int32_t USBH_PipeDelete (ARM_USBH_PIPE_HANDLE pipe_hndl) {
  PIPE_t    *ptr_pipe;
  OTG_FS_HC *ptr_ch;

  if (hw_powered == false)    { return ARM_DRIVER_ERROR;           }
  if (pipe_hndl  == 0U)       { return ARM_DRIVER_ERROR_PARAMETER; }

  ptr_ch   = (OTG_FS_HC *)(pipe_hndl);
  ptr_pipe = (PIPE_t    *)(&pipe[USBH_CH_GetIndexFromAddress (ptr_ch)]);
  if (ptr_pipe->active != 0U) { return ARM_DRIVER_ERROR_BUSY;      }

  ptr_ch->HCCHAR   = 0U;
  ptr_ch->HCINT    = 0U;
  ptr_ch->HCINTMSK = 0U;
  ptr_ch->HCTSIZ   = 0U;

  // Clear all fields of Pipe structure
  memset((void *)ptr_pipe, 0, sizeof(PIPE_t));

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PipeReset (ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Reset Pipe.
  \param[in]   pipe_hndl  Pipe Handle
  \return      \ref execution_status
*/
static int32_t USBH_PipeReset (ARM_USBH_PIPE_HANDLE pipe_hndl) {
  PIPE_t    *ptr_pipe;
  OTG_FS_HC *ptr_ch;

  if (hw_powered == false)    { return ARM_DRIVER_ERROR;           }
  if (pipe_hndl  == 0U)       { return ARM_DRIVER_ERROR_PARAMETER; }

  ptr_ch   = (OTG_FS_HC *)(pipe_hndl);
  ptr_pipe = (PIPE_t    *)(&pipe[USBH_CH_GetIndexFromAddress (ptr_ch)]);
  if (ptr_pipe->active != 0U) { return ARM_DRIVER_ERROR_BUSY;      }

  ptr_ch->HCINT    = 0U;
  ptr_ch->HCINTMSK = 0U;
  ptr_ch->HCTSIZ   = 0U;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBH_PipeTransfer (ARM_USBH_PIPE_HANDLE pipe_hndl,
                                          uint32_t             packet,
                                          uint8_t             *data,
                                          uint32_t             num)
  \brief       Transfer packets through USB Pipe.
  \param[in]   pipe_hndl  Pipe Handle
  \param[in]   packet     Packet information
  \param[in]   data       Pointer to buffer with data to send or for data to receive
  \param[in]   num        Number of data bytes to transfer
  \return      \ref execution_status
*/
static int32_t USBH_PipeTransfer (ARM_USBH_PIPE_HANDLE pipe_hndl, uint32_t packet, uint8_t *data, uint32_t num) {
  PIPE_t *ptr_pipe;

  if (hw_powered == false)                   { return ARM_DRIVER_ERROR;           }
  if (pipe_hndl  == 0U)                      { return ARM_DRIVER_ERROR_PARAMETER; }
  if ((OTG->HPRT & OTG_FS_HPRT_PCSTS) == 0U) { return ARM_DRIVER_ERROR;           }

  ptr_pipe = (PIPE_t *)(&pipe[USBH_CH_GetIndexFromAddress ((OTG_FS_HC *)(pipe_hndl))]);
  if (ptr_pipe->active != 0U)                         { return ARM_DRIVER_ERROR_BUSY;      }

  // Update current transfer information
  ptr_pipe->packet                = packet;
  ptr_pipe->data                  = data;
  ptr_pipe->num                   = num;
  ptr_pipe->num_transferred_total = 0U;
  ptr_pipe->num_transferring      = 0U;
  ptr_pipe->active                = 0U;
  ptr_pipe->in_progress           = 0U;
  ptr_pipe->event                 = 0U;
  if ((ptr_pipe->ep_type == ARM_USB_ENDPOINT_INTERRUPT) && (ptr_pipe->interval != 0U)) {
    // Already active interrupt endpoint (it will restart in IRQ based on interval)
    ptr_pipe->active              = 1U;
  } else {
    ptr_pipe->in_progress         = 1U;
    ptr_pipe->active              = 1U;
    if (USBH_HW_StartTransfer (ptr_pipe, (OTG_FS_HC *)pipe_hndl) == 0U) {
      ptr_pipe->in_progress       = 0U;
      ptr_pipe->active            = 0U;
      return ARM_DRIVER_ERROR;
    }
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          uint32_t USBH_PipeTransferGetResult (ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Get result of USB Pipe transfer.
  \param[in]   pipe_hndl  Pipe Handle
  \return      number of successfully transferred data bytes
*/
static uint32_t USBH_PipeTransferGetResult (ARM_USBH_PIPE_HANDLE pipe_hndl) {

  if (pipe_hndl == 0U) { return 0U; }

  return (pipe[USBH_CH_GetIndexFromAddress((OTG_FS_HC *)pipe_hndl)].num_transferred_total);
}

/**
  \fn          int32_t USBH_PipeTransferAbort (ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Abort current USB Pipe transfer.
  \param[in]   pipe_hndl  Pipe Handle
  \return      \ref execution_status
*/
static int32_t USBH_PipeTransferAbort (ARM_USBH_PIPE_HANDLE pipe_hndl) {
  PIPE_t *ptr_pipe;

  if (hw_powered == false) { return ARM_DRIVER_ERROR;           }
  if (pipe_hndl  == 0U)    { return ARM_DRIVER_ERROR_PARAMETER; }

  ptr_pipe = (PIPE_t *)(&pipe[USBH_CH_GetIndexFromAddress ((OTG_FS_HC *)(pipe_hndl))]);

  if (ptr_pipe->active != 0U) {
    ptr_pipe->active = 0U;
    if (USBH_CH_Disable((OTG_FS_HC *)(pipe_hndl)) == 0U) { return ARM_DRIVER_ERROR; }
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          uint16_t USBH_GetFrameNumber (void)
  \brief       Get current USB Frame Number.
  \return      Frame Number
*/
static uint16_t USBH_GetFrameNumber (void) {

  if (hw_powered == false) { return 0U; }

  return ((OTG->HFNUM >> 3) & 0x7FFU);
}

/**
  \fn          void USBH_FS_IRQ (uint32_t gintsts)
  \brief       USB Host Interrupt Routine (IRQ).
*/
void USBH_FS_IRQ (uint32_t gintsts) {
  PIPE_t            *ptr_pipe;
  OTG_FS_HC         *ptr_ch;
  uint8_t           *ptr_data;
  volatile uint32_t *dfifo;
  uint32_t           hprt, haint, hcint, pktcnt, mpsiz;
  uint32_t           grxsts, bcnt, ch, dat, len, len_rest;
  uint8_t            hchalt;

  if ((gintsts & OTG_FS_GINTSTS_HPRTINT) != 0U) {       // If host port interrupt
    hprt = OTG->HPRT;
    OTG->HPRT = hprt & (~OTG_FS_HPRT_PENA);             // Leave PENA bit
    if ((hprt & OTG_FS_HPRT_PCDET) != 0U) {             // Port connect detected
      switch ((hprt >> 17) & 3U) {
        case 0:                                         // High-speed detected
          break;
        case 1:                                         // Full-speed detected
          OTG->HCFG = OTG_FS_HCFG_FSLSPCS(1U);
          break;
        case 2:                                         // Low-speed detected
          OTG->HCFG = OTG_FS_HCFG_FSLSPCS(2U);
          break;
        default:
          break;
      }
      if (port_reset == false) {                        // If port not under reset
        SignalPortEvent(0, ARM_USBH_EVENT_CONNECT);
      }
    }
    if ((hprt & OTG_FS_HPRT_PENCHNG) != 0U) {           // If port enable changed
      if ((hprt & OTG_FS_HPRT_PENA) != 0U) {            // If device connected
        if (port_reset == true) {
          port_reset = false;
          SignalPortEvent(0, ARM_USBH_EVENT_RESET);
        }
      }
    }
  }
  if ((gintsts & OTG_FS_GINTSTS_DISCINT) != 0U) {       // If device disconnected
    OTG->GINTSTS = OTG_FS_GINTSTS_DISCINT;              // Clear disconnect interrupt
    if (port_reset == false) {                          // Ignore disconnect under reset
      ptr_ch   = (OTG_FS_HC *)(&OTG->HCCHAR0);
      ptr_pipe = (PIPE_t    *)(pipe);
      for (ch = 0U; ch < USBH_MAX_PIPE_NUM; ch++) {
        if (ptr_pipe->active != 0U) {
          ptr_pipe->active = 0U;
          SignalPipeEvent((ARM_USBH_EP_HANDLE)ptr_ch, ARM_USBH_EVENT_BUS_ERROR);
        }
        ptr_ch++;
        ptr_pipe++;
      }
      SignalPortEvent(0, ARM_USBH_EVENT_DISCONNECT);
    }
  }
                                                        // Handle reception interrupt
  if ((gintsts & OTG_FS_GINTSTS_RXFLVL) != 0U) {        // If RXFIFO non-empty interrupt
    OTG->GINTMSK &= ~OTG_FS_GINTMSK_RXFLVLM;
    grxsts = OTG->GRXSTSR;
    if (((grxsts >> 17) & 0x0FU) == 0x02U) {            // If PKTSTS = 0x02
      grxsts     = (OTG->GRXSTSP);
      ch         = (grxsts     ) & 0x00FU;
      bcnt       = (grxsts >> 4) & 0x7FFU;
      dfifo      =  OTG_DFIFO[ch];
      ptr_data   =  pipe[ch].data + pipe[ch].num_transferred_total;
      len        =  bcnt / 4U;                          // Received number of 32-bit data
      len_rest   =  bcnt & 3U;                          // Number of bytes left
      while (len != 0U) {
        *((__packed uint32_t *)ptr_data) = *dfifo;
        ptr_data += 4U;
        len--;
      }
      if (len_rest != 0U) {
        dat = *((__packed uint32_t *)dfifo);
        while (len_rest != 0U) {
          *ptr_data++ = dat;
          dat >>= 8;
          len_rest--;
        }
      }
      pipe[ch].num_transferring      += bcnt;
      pipe[ch].num_transferred_total += bcnt;
    } else {                                            // If PKTSTS != 0x02
      grxsts      = OTG->GRXSTSP;
    }
    OTG->GINTMSK |= OTG_FS_GINTMSK_RXFLVLM;
  }
                                                        // Handle host ctrl interrupt
  if ((gintsts & OTG_FS_GINTSTS_HCINT) != 0U) {         // If host channel interrupt
    haint = OTG->HAINT;
    for (ch = 0U; ch < USBH_MAX_PIPE_NUM; ch++) {
      if (haint == 0U) { break; }
      if ((haint & (1U << ch)) != 0U) {                 // If channels interrupt active
        haint     &= ~(1U << ch);
        ptr_ch     =  (OTG_FS_HC *)(&OTG->HCCHAR0) + ch;
        ptr_pipe   =  (PIPE_t    *)(&pipe[ch]);
        hcint      =   ptr_ch->HCINT & ptr_ch->HCINTMSK;
        hchalt     =   0U;
        if ((hcint & OTG_FS_HCINTx_CHH) != 0U) {        // If channel halted
          ptr_ch->HCINT    = OTG_FS_HCINTx_CHH;         // Clear halt interrupt
          ptr_ch->HCINTMSK = 0U;                        // Disable all channel interrupts
          ptr_ch->HCINT    = 0x7BBU;                    // Clear all interrupts
          ptr_pipe->in_progress = 0U;                   // Transfer not in progress
        } else if ((hcint & OTG_FS_HCINTx_XFRC) != 0U) {// If data transfer finished
          ptr_ch->HCINT   = 0x7BBU;                     // Clear all interrupts
          if ((ptr_ch->HCCHAR & (1U << 15)) != 0U) {    // If endpoint IN
            ptr_pipe->event = ARM_USBH_EVENT_TRANSFER_COMPLETE;
          } else {                                      // If endpoint OUT
            ptr_pipe->num_transferred_total += ptr_pipe->num_transferring;
            ptr_pipe->num_transferring       = 0U;
            if (ptr_pipe->num_transferred_total == ptr_pipe->num) {
              ptr_pipe->event = ARM_USBH_EVENT_TRANSFER_COMPLETE;
            }
          }
          hchalt = 1U;
        } else {
          if ((hcint & OTG_FS_HCINTx_ACK) != 0U) {      // If ACK received
            ptr_ch->HCINT = OTG_FS_HCINTx_ACK;          // Clear ACK interrupt
            // On ACK, ACK is not an event that can be returned so if transfer
            // is completed another interrupt will happen otherwise for IN
            // endpoint transfer will be restarted for remaining data
            if ((ptr_ch->HCCHAR & (1U << 15)) != 0U) {  // If endpoint IN
              if ((ptr_pipe->num != ptr_pipe->num_transferred_total) &&                   // If all data was not transferred
                  (ptr_pipe->num_transferring != 0U)                 &&                   // If zero-length packet was not received
                 ((ptr_pipe->num_transferred_total%ptr_pipe->ep_max_packet_size) == 0U)){ // If short packet was not received
                ptr_ch->HCCHAR |= OTG_FS_HCCHARx_CHENA;
              }
            } else {                                    // If endpoint OUT
              hchalt = 1U;
            }
          } else if ((hcint & (OTG_FS_HCINTx_STALL |            // If STALL received
                               OTG_FS_HCINTx_NAK   |            // If NAK received
                               OTG_FS_HCINTx_ERR   )) != 0U) {  // If any error occurred
            if ((ptr_ch->HCCHAR & (1U << 15)) == 0U) {          // If endpoint OUT
              // Update transferred count
              pktcnt = (ptr_ch->HCTSIZ >> 19) & 0x000003FFU;
              mpsiz  = (ptr_ch->HCCHAR      ) & 0x000007FFU;
              if ((ptr_pipe->num_transferring >= mpsiz) && (pktcnt > 0U)) {
                ptr_pipe->num_transferred_total += ptr_pipe->num_transferring - mpsiz * pktcnt;
              }
              ptr_pipe->num_transferring       = 0U;
            }
            if ((hcint & OTG_FS_HCINTx_NAK)!=0U){       // If NAK received
              ptr_ch->HCINT = OTG_FS_HCINTx_NAK;        // Clear NAK interrupt
              // On NAK, NAK is not returned to middle layer but transfer is
              // restarted from driver for remaining data, unless it is interrupt
              // endpoint in which case transfer is restarted on SOF event
              // when period has expired
              if ((ptr_ch->HCCHAR & (1U << 15)) != 0U) {// If endpoint IN
                if (ptr_pipe->ep_type == ARM_USB_ENDPOINT_INTERRUPT) {
                  hchalt = 1U;
                } else {
                  ptr_ch->HCCHAR |= OTG_FS_HCCHARx_CHENA;
                }
              } else {                                  // If endpoint OUT
                hchalt = 1U;
              }
            } else if ((hcint&OTG_FS_HCINTx_STALL)!=0U){// If STALL received
              ptr_ch->HCINT   = OTG_FS_HCINTx_STALL;    // Clear STALL interrupt
              ptr_pipe->event = ARM_USBH_EVENT_HANDSHAKE_STALL;
              hchalt = 1U;
            } else {
              ptr_ch->HCINT   = OTG_FS_HCINTx_ERR;      // Clear all error interrupts
              ptr_pipe->event = ARM_USBH_EVENT_BUS_ERROR;
              hchalt = 1U;
            }
          }
        }
        if (hchalt != 0U) {                             // If channel should be halted
          ptr_ch->HCINTMSK = OTG_FS_HCINTx_CHH;         // Enable halt interrupt
          ptr_ch->HCCHAR  |= OTG_FS_HCCHARx_CHENA | OTG_FS_HCCHARx_CHDIS;
        }
        if ((ptr_pipe->in_progress == 0U) && (ptr_pipe->event != 0U)) {
          ptr_pipe->active = 0U;
          SignalPipeEvent((ARM_USBH_EP_HANDLE)ptr_ch, ptr_pipe->event);
        }
      }
    }
  }

  // Handle periodic transfer timings
  if ((gintsts & OTG_FS_GINTSTS_SOF) != 0U) {           // If start of frame interrupt
    OTG->GINTSTS =  OTG_FS_GINTSTS_SOF;                 // Clear SOF interrupt
    ptr_pipe     = (PIPE_t *)(pipe);
    for (ch = 0U; ch < USBH_MAX_PIPE_NUM; ch++) {
      // If interrupt transfer is active handle period (interval)
      if ((ptr_pipe->ep_type == ARM_USB_ENDPOINT_INTERRUPT) && (ptr_pipe->active != 0U) && (ptr_pipe->interval != 0U)) {
        ptr_pipe->interval--;
      }
      ptr_pipe++;
    }
  }

  // Handle restarts of unfinished transfers (due to NAK or ACK)
  ptr_pipe = (PIPE_t *)(pipe);
  for (ch = 0U; ch < USBH_MAX_PIPE_NUM; ch++) {
    if ((ptr_pipe->active != 0U) && (ptr_pipe->in_progress == 0U)) {
      // Restart periodic transfer if not in progress and interval expired
      if (ptr_pipe->ep_type == ARM_USB_ENDPOINT_INTERRUPT) {
        if (ptr_pipe->interval == 0U) {
          ptr_pipe->interval = ptr_pipe->interval_reload;
        } else {
          continue;
        }
      }

      ptr_pipe->in_progress = 1;
      if (USBH_HW_StartTransfer (ptr_pipe, USBH_CH_GetAddressFromIndex (ch)) == 0U) {
        ptr_pipe->in_progress = 0U;
        ptr_pipe->active      = 0U;
      }
    }
    ptr_pipe++;
  }
}

ARM_DRIVER_USBH Driver_USBH0 = {
  USBH_GetVersion,
  USBH_GetCapabilities,
  USBH_Initialize,
  USBH_Uninitialize,
  USBH_PowerControl,
  USBH_PortVbusOnOff,
  USBH_PortReset,
  USBH_PortSuspend,
  USBH_PortResume,
  USBH_PortGetState,
  USBH_PipeCreate,
  USBH_PipeModify,
  USBH_PipeDelete,
  USBH_PipeReset,
  USBH_PipeTransfer,
  USBH_PipeTransferGetResult,
  USBH_PipeTransferAbort,
  USBH_GetFrameNumber
};
