//-----------------------------------------------------------------------------
///
/// \brief  USB Device
///
///         This is the Low-Level-Layer for the USB support.
///
/// \date   20180925
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


// STM32F303 has not USB support
#if ! ( defined (STM32F303x8) || defined (STM32L431xx) || defined (STM32G0) )

//---Includes------------------------------------------------------------------


//---Own------------------------------

// Must not debug when stdio is USB. Sure.
#undef LEPTO_LOG_DEBUG
//#define LEPTO_LOG_DEBUG

#include <HALWrapper/stm32_usb.h>
#include <HALWrapper/stm32_hal.h>
#include <biwak/usb.hpp>
#include <biwak/biwak.h>         // exception
#include <biwak/handler.h>       // IRQ_Funcion_imp
#include <biwak/rcc.h>
#include <biwak/exception.h>
#include <biwak/nvic.h>          // setNVInterrupt
#include <lepto/log.h>           // lHint

//---Global---------------------------

#include <string.h>    // memset


//---Implementation------------------------------------------------------------


#define USB_HANDLE_TYPE    PCD_HandleTypeDef


int usbKeeper;

struct
{
   PCD_HandleTypeDef *pHandler;
   CUsb *object;
} static usbDynamicData[ 1 ];

class CUsbDimpl
{
   public:
      PCD_HandleTypeDef *m_pHandle;
};


CUsb::CUsb( EGpioPeripheral _eGpioPeripheralDm, EGpioPin _eGpioPinDm,
            EGpioPeripheral _eGpioPeripheralDp, EGpioPin _eGpioPinDp, int altFunc)
    :gpioDm(_eGpioPeripheralDm, _eGpioPinDm, gpioModeAfIn, altFunc )
    ,gpioDp(_eGpioPeripheralDp, _eGpioPinDp, gpioModeAfOut, altFunc)
{
   dimpl=new CUsbDimpl;
   dimpl->m_pHandle=new PCD_HandleTypeDef;
   memset(dimpl->m_pHandle, 0, sizeof(PCD_HandleTypeDef));

   interface=nullptr;

   usbDynamicData[0].object=this;
   usbDynamicData[0].pHandler=(PCD_HandleTypeDef*)dimpl->m_pHandle;
   
   /* Set LL Driver parameters */
   #if defined(STM32F4)
      dimpl->m_pHandle->Instance = USB_OTG_FS;
   #else
      dimpl->m_pHandle->Instance = USB;
   #endif
   dimpl->m_pHandle->Init.dev_endpoints = 3;
   #if defined STM32G4
      dimpl->m_pHandle->Init.ep0_mps = USB_HS_MAX_PACKET_SIZE;
   #else
      #if defined (DEP0CTL_MPS_8)
         dimpl->m_pHandle->Init.ep0_mps = DEP0CTL_MPS_8; // 0x40;
      #else
         dimpl->m_pHandle->Init.ep0_mps = 3;
      #endif
   #endif
   dimpl->m_pHandle->Init.phy_itface = PCD_PHY_EMBEDDED;
   dimpl->m_pHandle->Init.speed = PCD_SPEED_FULL;
   dimpl->m_pHandle->Init.low_power_enable = 0;
   dimpl->m_pHandle->Init.lpm_enable = 0;
   dimpl->m_pHandle->Init.battery_charging_enable = 0;
   dimpl->m_pHandle->Init.Sof_enable = 0;
   dimpl->m_pHandle->State = HAL_PCD_STATE_RESET;
   /* Link The driver to the stack */
   dimpl->m_pHandle->pData = this;
   /* Initialize LL Driver */

   rccUsbClockEnable(dimpl->m_pHandle->Instance);
#if defined( STM32F4 )
   setNVInterrupt(OTG_FS_IRQn , true, NVI_DEFAULT_PRIORITY);
#elif defined( STM32G4 ) || defined( STM32F1 ) || defined( STM32L1 )

   setNVInterrupt(USB_LP_IRQn , true, NVI_HIGH_PRIORITY );
   setNVInterrupt(USB_HP_IRQn , true, NVI_HIGH_PRIORITY );

#else
   setNVInterrupt(USB_IRQn , true, NVI_DEFAULT_PRIORITY);
#endif
   
   //__HAL_RCC_USB_CLK_ENABLE();
   //__HAL_RCC_USB_OTG_FS_CLK_ENABLE();
    HAL_PCD_Init(dimpl->m_pHandle);

#if 0
   HAL_PCDEx_PMAConfig( dimpl->m_pHandle , 0x00 , PCD_SNG_BUF, 0x18 );
   HAL_PCDEx_PMAConfig( dimpl->m_pHandle, 0x80 , PCD_SNG_BUF, 0x58 );
   HAL_PCDEx_PMAConfig( dimpl->m_pHandle , 0x81 , PCD_SNG_BUF, 0x100 );
#endif

#if ! defined (STM32F4)
   HAL_PCDEx_PMAConfig(dimpl->m_pHandle , 0x00 , PCD_SNG_BUF, 0x18);
   HAL_PCDEx_PMAConfig(dimpl->m_pHandle , 0x80 , PCD_SNG_BUF, 0x58);
   HAL_PCDEx_PMAConfig(dimpl->m_pHandle , 0x81 , PCD_SNG_BUF, 0xC0);
   HAL_PCDEx_PMAConfig(dimpl->m_pHandle , 0x01 , PCD_SNG_BUF, 0x110);
   HAL_PCDEx_PMAConfig(dimpl->m_pHandle , 0x82 , PCD_SNG_BUF, 0x100);
#endif

   // HAL_PCD_Start must not be started here
   // The interface to an CUsbDevice has to setup and an class has to be
   // registered.
   // Registering an class automatically starts the USB periphery.
};

#if defined ( STM32F0 ) || defined( STM32L1 )

IRQ_Funcion_imp( USB_IRQHandler )
{
   HAL_PCD_IRQHandler(usbDynamicData[0].pHandler);
}

#endif

CUsb::~CUsb()
{
   #if defined( STM32G4 ) || defined ( STM32L1 )
      setNVInterrupt(USB_LP_IRQn , false, NVI_DEFAULT_PRIORITY);
      setNVInterrupt(USB_HP_IRQn , false, NVI_DEFAULT_PRIORITY);
   #else
      #if defined ( USB_IRQn )
         setNVInterrupt(USB_IRQn , true, NVI_DEFAULT_PRIORITY);
      #endif
   #endif
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"

IRQ_Funcion_imp( USB_LP_IRQHandler )
{
   HAL_PCD_IRQHandler(usbDynamicData[0].pHandler);
   return;
}

IRQ_Funcion_imp( USB_HP_IRQHandler )
{
   // "USB_HP_IRQHandler"
   exception( );
   return;
}

#pragma GCC diagnostic pop

extern "C"
{
   void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd);
   void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd);
   void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd);
   void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd);
   void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd);
   void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd);
   void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd);

   void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum);
   void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum);
   void HAL_PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum);
   void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum);
}

extern "C"
{

void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
   CUsb *pdev=(CUsb *)hpcd->pData;

   lDebug ( "dataOutStageCB: C=%d/L=%d [%02X] [%02X] %d %d", hpcd->OUT_ep[epnum].xfer_count, hpcd->OUT_ep[epnum].xfer_len,
            hpcd->OUT_ep[epnum].xfer_buff[0], hpcd->OUT_ep[epnum].xfer_buff[1],
            hpcd->OUT_ep[epnum].xfer_len_db, hpcd->OUT_ep[epnum].xfer_fill_db
            );
   
   int len=hpcd->OUT_ep[epnum].xfer_count;
   pdev->interface->dataOutStage(epnum, hpcd->OUT_ep[epnum].xfer_buff, len );
   
   //hpcd->OUT_ep[epnum].xfer_len ? hpcd->OUT_ep[epnum].xfer_len : hpcd->OUT_ep[epnum].xfer_count );
   //pdev->interface->dataOutStage(epnum, hpcd->OUT_ep[epnum].xfer_buff, hpcd->OUT_ep[epnum].xfer_len);
}

#if 0
void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
   CUsb *pdev=(CUsb *)hpcd->pData;
   //USBD_Handle
   //pdev->interface->dataInStage(hpcd, epnum);
   pdev->interface->dataInStage(epnum, hpcd->IN_ep[epnum].xfer_buff, hpcd->IN_ep[epnum].xfer_len);
   USBD_EndpointTypeDef *pep;
   USBD_StatusTypeDef ret;
   exception("ddddee");
#if 0
   if (epnum == 0U)
   {
     pep = &pdev->ep_in[0];

     if (pdev->ep0_state == USBD_EP0_DATA_IN)
     {
       if (pep->rem_length > pep->maxpacket)
       {
         pep->rem_length -= pep->maxpacket;

         (void)USBD_CtlContinueSendData(pdev, pdata, pep->rem_length);

         /* Prepare endpoint for premature end of transfer */
         (void)USBD_LL_PrepareReceive(pdev, 0U, NULL, 0U);
       }
       else
       {
         /* last packet is MPS multiple, so send ZLP packet */
         if ((pep->maxpacket == pep->rem_length) &&
             (pep->total_length >= pep->maxpacket) &&
             (pep->total_length < pdev->ep0_data_len))
         {
           (void)USBD_CtlContinueSendData(pdev, NULL, 0U);
           pdev->ep0_data_len = 0U;

           /* Prepare endpoint for premature end of transfer */
           (void)USBD_LL_PrepareReceive(pdev, 0U, NULL, 0U);
         }
         else
         {
           if (pdev->dev_state == USBD_STATE_CONFIGURED)
           {
             if (pdev->pClass->EP0_TxSent != NULL)
             {
               pdev->pClass->EP0_TxSent(pdev);
             }
           }
           (void)USBD_LL_StallEP(pdev, 0x80U);
           (void)USBD_CtlReceiveStatus(pdev);
         }
       }
     }
     else
     {
   #if 0
       if ((pdev->ep0_state == USBD_EP0_STATUS_IN) ||
           (pdev->ep0_state == USBD_EP0_IDLE))
       {
         (void)USBD_LL_StallEP(pdev, 0x80U);
       }
   #endif
     }

     if (pdev->dev_test_mode == 1U)
     {
       (void)USBD_RunTestMode(pdev);
       pdev->dev_test_mode = 0U;
     }
   }
   else
   {
     if (pdev->dev_state == USBD_STATE_CONFIGURED)
     {
       if (pdev->pClass->DataIn != NULL)
       {
         ret = (USBD_StatusTypeDef)pdev->pClass->DataIn(pdev, epnum);

         if (ret != USBD_OK)
         {
           return ret;
         }
       }
     }
   }

   return USBD_OK;
#endif

}
#endif

void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
#if 1
   CUsb *pdev=(CUsb *)hpcd->pData;
   //PCD_EPTypeDef *ep=&hpcd->IN_ep[epnum];
   lDebug ( "dataInStageCB: C=%d/L=%d %d %d %d", hpcd->IN_ep[epnum].xfer_count, hpcd->IN_ep[epnum].xfer_len,
            hpcd->IN_ep[epnum].is_stall, hpcd->IN_ep[epnum].num, epnum
            );
   lDebug ( "   %d %d",
            hpcd->IN_ep[epnum].tx_fifo_num, hpcd->IN_ep[epnum].maxpacket
            );
   {
      //hpcd->IN_ep[epnum].xfer_len=10;
      pdev->interface->dataInStage(epnum, hpcd->IN_ep[epnum].xfer_buff, hpcd->IN_ep[epnum].xfer_count);
      // pdev->interface->dataInStage(epnum, hpcd->IN_ep[epnum].xfer_buff, hpcd->IN_ep[epnum].xfer_len);
   }
#endif

#if 0
   USBD_LL_DataInStage((PCD_HandleTypeDef*)hpcd->pData, epnum,
                       hpcd->IN_ep[epnum].xfer_buff);
#endif

#if 0
   if (ep->xfer_len == 0U)
   {
     /* TX COMPLETE */
     //HAL_PCD_DataInStageCallback(hpcd, ep->num);
   }
   else
   {
     //HAL_PCD_EP_Transmit(hpcd, ep->num, ep->xfer_buff, ep->xfer_len);
   }
#endif
}

void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd)
{
   CUsb *pdev=(CUsb *)hpcd->pData;
   pdev->interface->setupStage((uint8_t *) hpcd->Setup);
}

void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd)
{
   //exception("SOF", -1);
   #if 0
   CUsb *pdev=(CUsb *)hpcd->pData;
   if( pdev!=nullptr )
   {
      if( pdev->interface )
      {
         pdev->interface->SOF();
      }
   }
   #endif
}

void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd)
{
   CUsb *pdev=(CUsb *)hpcd->pData;
   //lWarning( "USB Reset" );
   if( pdev!=nullptr )
   {
      if( pdev->interface )
      {
         pdev->interface->reset();
      }
   }
}

void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd)
{
   CUsb *pdev=(CUsb *)hpcd->pData;
   //exception("Suspend", -1);
   //lWarning( "USB Suspend" );
   if(pdev->interface)
      pdev->interface->suspend();
}

void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd)
{
   CUsb *pdev=(CUsb *)hpcd->pData;
   //lWarning( "USB Resume" );
   if( pdev!=nullptr )
   {
      if( pdev->interface )
      {
         pdev->interface->resume();
      }
   }
};

void HAL_PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
   // "ISOOUTIncomplete"
   exception( );
};

void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
   // "ISOINIncomplete"
   exception( );
};

 void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd)
{
   // "Connect"
   exception( );
};

void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd)
{
   // "Disconnect"
   exception( );
};

}

void CUsb::start(IUsbCallbacks *_interface)
{
   interface=_interface;
   HAL_PCD_Start(dimpl->m_pHandle);
}

void CUsb::stallEP( uint8_t ep_addr)
{
   //lCritical("USB Stall");
   HAL_PCD_EP_SetStall(dimpl->m_pHandle, ep_addr);
}

void CUsb::clearStallEP( uint8_t ep_addr)
{
   //lCritical("USB Clear Stall");
   HAL_PCD_EP_ClrStall(dimpl->m_pHandle, ep_addr);
}

int CUsb::isStallEP(uint8_t ep_addr)
{
   if((ep_addr & 0x80) == 0x80)
   {
      return dimpl->m_pHandle->IN_ep[ep_addr & 0x7F].is_stall;
   }
   else
   {
      return dimpl->m_pHandle->OUT_ep[ep_addr & 0x7F].is_stall;
   }
}


USBD_StatusTypeDef CUsb::transmit(uint8_t ep_addr, uint8_t *pbuf, uint16_t size)
{
   lDebug("Transmit %d Bytes: [%02X] [%02X]; %d", size, pbuf[0], pbuf[1], ep_addr);
   if( pbuf && (!size) )
   {
      exception( );
   }
    HAL_StatusTypeDef sta=HAL_PCD_EP_Transmit(dimpl->m_pHandle, ep_addr, pbuf, size);
    if( sta != HAL_OK)
    {
        exception( );
    }
  return USBD_OK;
}


USBD_StatusTypeDef CUsb::receive(uint8_t ep_addr, uint8_t *pbuf, uint16_t size)
{
   HAL_StatusTypeDef sta=HAL_PCD_EP_Receive(dimpl->m_pHandle, ep_addr, pbuf, size);
   if( sta != HAL_OK)
   {
       exception( );
   }
   return USBD_OK;
}

void CUsb::openEP(uint8_t ep_addr, uint8_t ep_type, uint16_t ep_mps)
{
   HAL_PCD_EP_Open(dimpl->m_pHandle, ep_addr, ep_mps, ep_type);
}


void CUsb::closeEP(uint8_t ep_addr)
{
   HAL_PCD_EP_Close(dimpl->m_pHandle, ep_addr);
}


void CUsb::setUsbAddress(int dev_addr)
{
   HAL_PCD_SetAddress(dimpl->m_pHandle, dev_addr);
}

PCD_HandleTypeDef *CUsb::getPcdHandle()
{
    return(dimpl->m_pHandle);
}

#endif // ? ! STM32F303x8

//---fin-----------------------------------------------------------------------
