//-----------------------------------------------------------------------------
///
/// \brief  Timer unit for campo
///
///         Blah blah blub
///
/// \date   20121117
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Documentation-------------------------------------------------------------


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


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

#include <biwak/timer.hpp>
#include <biwak/stm32/timer.hpp>
#include <biwak/nvic.h>          // NVI_LOW_PRIOTITY
#include <memory.h>
#include <lepto/log.h>           // lAssert
#include <biwak/dma.hpp>
#include <biwak/exception.h>
#include <biwak/handler.h>       // IRQ_Funcion_imp
#include <HALWrapper/stm32_hal.h>    // HAL_RCC_GetSysClockFreq()


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


int timerChannelEnumToEnum(ETimerChannel channel)
{
   return((int)channel);
}

CTimer::CTimer( const STimerConfig &_configData,
                frequency_t frequency, ETimerMode _eTimerMode)
   :m_configData(_configData)
   ,eTimerMode(_eTimerMode)
{

#if defined ( BIWAK_AUTOMATIC_RCC_ENABLE )
   rccTimClockEnable(m_pHandler->Instance);
#endif

   #if IS_ENABLED( CONFIG_BIWAK_TIMER_INTERRUPT )
   // Wen need high priority because timer might be used in ISRs.
   setNVInterrupt(m_configData.irqn, true, NVI_LOW_PRIORITY);
   #endif
   
   m_pHandler=new TIM_HANDLE_TYPE{
      .Instance{ m_configData.instance },
      .Init
      {
         // Has to be a 16Bit value. The higher the value the slower the timer,
         .Prescaler = 0,
         .CounterMode = TIM_COUNTERMODE_UP,
         .Period = 0,
         .ClockDivision = TIM_CLOCKDIVISION_DIV1,
         #if ( ! defined ( STM32L1 ) ) && ( ! defined ( STM32L0 ) )
            .RepetitionCounter = 0,
         #endif
      },
      .Channel=HAL_TIM_ACTIVE_CHANNEL_3,
      .State=HAL_TIM_STATE_RESET,
   };

   m_configData.interrupt=this;

   calculateValuesByFrequency(frequency);
   init();
}


/// \brief  Le contructeur
///
CTimer::CTimer( const STimerConfig &configData )
   :CTimer( configData, 0, eTimerModePWM)
{
}

#if 0
/// \brief  Le contructeur
///
CTimer::CTimer( const STimerConfig &_configData, int prescaler, int period, ETimerMode _eTimerMode )
   :m_configData(_configData)
   ,eTimerMode(_eTimerMode)
   ,m_prescaler(prescaler)
   ,m_period(period)
{
   m_pHandler=new TIM_HANDLE_TYPE;
   //memset(m_pHandler, 0, sizeof(TIM_HANDLE_TYPE));
   m_pHandler->Instance=m_configData.instance;

   init();
}
#endif
void CTimer::init()
{
   HAL_StatusTypeDef sta;

   //memset(&m_pHandler->Init, 0, sizeof(m_pHandler->Init));

   // Has to be a 16Bit value. We keep it high to not waste time in ISRs
   /*
   *m_pHandler=
   {
      .Instance=m_pHandler->Instance,
      .Init
      {
         // Has to be a 16Bit value. The higher the value the slower the timer,
         .Prescaler = m_prescaler-1,
         .CounterMode = TIM_COUNTERMODE_UP,
         .Period = m_period-1,
         .ClockDivision = TIM_CLOCKDIVISION_DIV1,
         #if ( ! defined ( STM32L1 ) ) && ( ! defined ( STM32L0 ) )
            .RepetitionCounter = 0,
         #endif
      },
      .Channel=HAL_TIM_ACTIVE_CHANNEL_3,
      .State=HAL_TIM_STATE_RESET,
   };
   */

   switch( eTimerMode )
   {
      #if IS_ENABLED( CONFIG_BIWAK_TIMER_DEFAULT)
      case eTimerModeStandard:
         sta=HAL_TIM_Base_Init(m_pHandler);
         break;
      #endif
         
      #if IS_ENABLED( CONFIG_BIWAK_TIMER_OC)
      case eTimerModeDAC:
      case eTimerModeOC:
         sta=HAL_TIM_OC_Init(m_pHandler);
         break;
      #endif
         
      case eTimerModePWM:
         sta=HAL_TIM_PWM_Init(m_pHandler);
         break;
      default:
         // Unknown timer mode
         exception( );
         break;
   }
      
   bAssert( sta==HAL_OK, sta);

   //calculateFrequencyByValues();
   //calculateTicksPerUsecByFrequency();
}

#if 0

/// \brief  Le contructeur
///
void CTimer::reinit( int prescaler, int period )
{
   m_prescaler=prescaler;
   m_period=period;
   init();
}

#endif

void CTimer::setEnable(bool value)
{
   //TIM_Cmd(timerData[eTimer].timer, value?ENABLE:DISABLE);
}

/*
CTimer::~CTimer()
{

}
*/

uint32_t CTimer::getRemaining()
{
   return ( m_pHandler->Instance->CNT );
}

#if 0
uint32_t CTimer::getRemainingUsec()
{
   if(m_hrtPerUsec)
      return (m_pHandler->Instance->CNT/m_hrtPerUsec);

   return (m_pHandler->Instance->CNT*m_usecPerHrt);
}
#endif

/// \brief  hmmm, dont know exactly
void CTimer::setMasterConfig()
{
   HAL_StatusTypeDef sta __attribute__ (( unused ));
   TIM_MasterConfigTypeDef sMasterConfig{
   
      // Works for regular DAC_DMA and PWM:
         .MasterOutputTrigger { TIM_TRGO_UPDATE },
         #if ! ( defined STM32G0 || defined STM32G4 || defined STM32F3 || defined STM32L4 )
         .MasterSlaveMode { TIM_MASTERSLAVEMODE_DISABLE },
         #endif
         #if defined (TIM_TRGO2_UPDATE)
         .MasterOutputTrigger2 { TIM_TRGO2_UPDATE },
         #endif
         #if ( defined STM32G0 || defined STM32G4 || defined STM32F3 || defined STM32L4 )
         .MasterSlaveMode { TIM_MASTERSLAVEMODE_DISABLE },
         #endif
   };

   sta=HAL_TIMEx_MasterConfigSynchronization(m_pHandler, &sMasterConfig);
   bDebugAssert( sta == HAL_OK, sta );

   return;
}

/// \brief  configure output channel
///
///         Needed for PWM
void CTimer::setOutputChannel(ETimerChannel eTimerChannel, int permilli /*=500*/)
{
   TIM_OC_InitTypeDef outputChannelInit
   {
         .OCMode = TIM_OCMODE_PWM1,
         .Pulse = ( ( getPeriod() * permilli ) / 1000 ),
         .OCPolarity = TIM_OCPOLARITY_HIGH,
   #if defined ( TIM_OCNPOLARITY_HIGH )
         .OCNPolarity = TIM_OCNPOLARITY_HIGH,
   #endif
         .OCFastMode = TIM_OCFAST_DISABLE,

   #if defined ( TIM_OCIDLESTATE_RESET )
         .OCIdleState = TIM_OCIDLESTATE_RESET,
         .OCNIdleState = TIM_OCNIDLESTATE_RESET,
   #endif
   };
   
   bAssert ( HAL_TIM_PWM_ConfigChannel( m_pHandler, &outputChannelInit
                  , timerChannelEnumToEnum(eTimerChannel)) == HAL_OK, 1 );
}

int CTimer::startTimerPwm(ETimerChannel eTimerChannel)
{
   HAL_TIM_PWM_Stop( m_pHandler, timerChannelEnumToEnum(eTimerChannel) );
   return( HAL_TIM_PWM_Start(m_pHandler, timerChannelEnumToEnum( eTimerChannel) ) );
}

void CTimer::interrupt()
{
   HAL_TIM_IRQHandler( m_pHandler );
}

void CTimer::stop()
{
   HAL_StatusTypeDef sta;
   sta=HAL_TIM_Base_Stop(m_pHandler);
   bAssert( sta==HAL_OK, sta);
}

void CTimer::start()
{
   HAL_StatusTypeDef sta;
   sta=HAL_TIM_Base_Start_IT(m_pHandler);
   bAssert( sta==HAL_OK, sta);
}

void CTimer::startNoInt()
{
   HAL_StatusTypeDef sta;
   sta=HAL_TIM_Base_Start(m_pHandler);
   bAssert( sta==HAL_OK, sta);
}

frequency_t CTimer::calculateFrequencyByValues() const
{
   return( (frequency_t)HAL_RCC_GetSysClockFreq()
                  / ( getPrescaler() * getPeriod() ) );
}

void CTimer::calculateValuesByFrequency(frequency_t frequency)
{
   int clockFrequency=HAL_RCC_GetSysClockFreq();
   if(!frequency)
   {
      m_pHandler->Init.Prescaler=0;
      m_pHandler->Init.Period=0;
   }
   else
   {
      m_pHandler->Init.Prescaler=( clockFrequency / frequency / 65536 ) + 1 - 1;
      m_pHandler->Init.Period=( clockFrequency / frequency
            / ( m_pHandler->Init.Prescaler + 1 ) ) - 1;
   }

   return;
}

void CTimer::updateFrequency(frequency_t frequency)
{
   calculateValuesByFrequency( frequency );

   // Updating the whole config leads rattling. Changing the frequency values
   // directly works more smooth. But it does not work for all condition.
   // e.g., TIM2_CH3 stopped without TIM_Base_SetConfig()
   #if defined(STM32L0) || defined(STM32L1)
      HAL_TIM_Base_Init( m_pHandler );
   #else
      TIM_Base_SetConfig(m_pHandler->Instance, &m_pHandler->Init);
   #endif
}

void CTimer::updateFrequency(int prescaler, int period)
{
   m_pHandler->Init.Prescaler = prescaler - 1;
   m_pHandler->Init.Period = period - 1;
   //m_frequency=2;

   // Updating the whole config leads rattling. Changing the frequency values
   // directly works more smooth. But it does not work for all condition.
   // e.g., TIM2_CH3 stopped without TIM_Base_SetConfig()
   #if defined(STM32L0) || defined(STM32L1)
      HAL_TIM_Base_Init( m_pHandler );
   #else
      TIM_Base_SetConfig(m_pHandler->Instance, &m_pHandler->Init);
   #endif
}

// Only adjust registers without any call of TIM_Base_SetConfig() to avoid
// rattling in sound.
void CTimer::rupdateFrequency(frequency_t frequency)
{
   // What MCU does not support this
   #if defined STM32XXXX
      // || defined STM32G4
      updateFrequency(frequency);
      return;
   #else
   //_calculateValuesByFrequency( frequency, &m_prescaler, &m_period );
   //m_pHandler->Init.Prescaler=m_prescaler-1;
   //m_pHandler->Init.Period=m_period-1;
   calculateValuesByFrequency( frequency );
   //m_frequency=frequency;

   // Updating the whole config leads rattling. Changing the frequency values
   // directly works more smooth. But it does not work for all condition.
   // EG, TIM2_CH3 stopped without TIM_Base_SetConfig()

   //TIM_Base_SetConfig(m_pHandler->Instance, &m_pHandler->Init);
   m_pHandler->Instance->ARR = (uint32_t)m_pHandler->Init.Period ;
   m_pHandler->Instance->PSC = (uint32_t)m_pHandler->Init.Prescaler;
   
   // At least STM32G4: If the period already was above the new set one,
   // timer wont hit it for a long time, causing timer not to work for that time
   if( m_pHandler->Instance->CNT >= (uint32_t)m_pHandler->Init.Period )
   {
      m_pHandler->Instance->CNT = 0;
   }
#endif
}

void CTimer::rupdatePulse(int permilli)
{
   int value=( (m_pHandler->Init.Period * permilli ) / 1000 );
   m_pHandler->Instance->CCR1=value;
   m_pHandler->Instance->CCR2=value;
   m_pHandler->Instance->CCR3=value;
   m_pHandler->Instance->CCR4=value;
}

void CTimer::stopPwm(ETimerChannel channel)
{
   int halChannel=timerChannelEnumToEnum( channel );
   HAL_TIM_PWM_Stop( m_pHandler, halChannel );
   HAL_TIM_PWM_Stop_DMA( m_pHandler, halChannel );
}

void CTimer::startDmaPwm(ETimerChannel channel, const uint32_t *buffer, int size)
{
   int sta;
   // Must not pre-stop here or a bent slope appears.
   sta=HAL_TIM_PWM_Start_DMA( m_pHandler, timerChannelEnumToEnum( channel ), (uint32_t *)buffer, size);
   bAssert(!sta, 1);
   return;
}

void CTimer::setDmaChannel( CDmaChannel *dmaChannel )
{
   memset(m_pHandler->hdma, 0, sizeof(m_pHandler->hdma));
   __HAL_LINKDMA( getHandler(), hdma[TIM_DMA_ID_CC1], (*dmaChannel->getDmaHandler()) );
   __HAL_LINKDMA( getHandler(), hdma[TIM_DMA_ID_CC2], (*dmaChannel->getDmaHandler()) );
   __HAL_LINKDMA( getHandler(), hdma[TIM_DMA_ID_CC3], (*dmaChannel->getDmaHandler()) );
   __HAL_LINKDMA( getHandler(), hdma[TIM_DMA_ID_CC4], (*dmaChannel->getDmaHandler()) );
   __HAL_LINKDMA( getHandler(), hdma[TIM_DMA_ID_TRIGGER], (*dmaChannel->getDmaHandler()) );
}

extern "C"
{
   void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim);
}

void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim)
{
   lInfo("ETIM");
}

CTimer *interruptTim1=nullptr;
CTimer *interruptTim2=nullptr;
CTimer *interruptTim3=nullptr;
CTimer *interruptTim4=nullptr;
CTimer *interruptTim5=nullptr;
CTimer *interruptTim6=nullptr;
CTimer *interruptTim7=nullptr;
CTimer *interruptTim8=nullptr;
CTimer *interruptTim14=nullptr;
CTimer *interruptTim15=nullptr;
CTimer *interruptTim10=nullptr;

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

// !STM32L0
IRQ_Funcion_imp( TIM1_IRQHandler )
{
   callInterruptFix(interruptTim1);
}

// STM32L431xx
IRQ_Funcion_imp( TIM1_TRG_COM_IRQHandler )
{
   callInterruptFix(interruptTim1);
}

//#if defined ( TIM2_IRQn )
// STM32F3, STM32L1, STM32F0, STM32L0
IRQ_Funcion_imp( TIM2_IRQHandler )
{
   #if IS_ENABLED( CONFIG_BIWAK_TIMER_INTERRUPT )
   callInterruptFix(interruptTim2);
   #endif
}
//#endif

IRQ_Funcion_imp( TIM3_IRQHandler )
{
   callInterruptFix(interruptTim3);
}

// STM32F4, STM32F1
IRQ_Funcion_imp( TIM4_IRQHandler )
{
   callInterruptFix(interruptTim4);
}

// STM32F4
IRQ_Funcion_imp( TIM5_IRQHandler )
{
   callInterruptFix(interruptTim5);
}

// STM32F072xB
IRQ_Funcion_imp( TIM6_IRQHandler )
{
   callInterruptFix(interruptTim6);
}

// STM32F072xB
IRQ_Funcion_imp( TIM15_IRQHandler )
{
   callInterruptFix(interruptTim15);
}

// STM32F3, STM32F072xB
IRQ_Funcion_imp( TIM6_DAC1_IRQHandler_tim )
{
   callInterrupt(interruptTim6);
}

// STM32F072xB
IRQ_Funcion_imp( TIM7_IRQHandler )
{
   callInterruptFix(interruptTim7);
}

// STM32G441;
IRQ_Funcion_imp( TIM8_IRQHandler )
{
   callInterruptFix(interruptTim8);
}

// STM32F042;
IRQ_Funcion_imp( TIM14_IRQHandler )
{
   #if IS_ENABLED( CONFIG_BIWAK_TIMER_INTERRUPT )
      callInterruptFix( interruptTim14 );
   #endif
}

#if 0

IRQ_Funcion_imp( TIM16_IRQHandler )
{
   callInterruptFix(eInterruptTim16);
   HAL_TIM_IRQHandler( timerDynamicData[eTimer16].pHandler );
}

IRQ_Funcion_imp( TIM17_IRQHandler )
{
   callInterruptFix(eInterruptTim17);
   HAL_TIM_IRQHandler( timerDynamicData[eTimer17].pHandler );
}

#endif


#ifdef STM32F3

IRQ_Funcion_imp( TIM7_DAC2_IRQHandler_tim )
{
   //if( timerDynamicData[eTimer7].pHandler )
   //   HAL_TIM_IRQHandler(timerDynamicData[eTimer7].pHandler);
   callInterruptFix(interruptTim7);
}

IRQ_Funcion_imp( TIM1_UP_TIM16_IRQHandler )
{
   // NIY
   exception( );
   return;
}

IRQ_Funcion_imp( TIM1_TRG_COM_TIM17_IRQHandler )
{
   // NIY
   exception( );
   return;
}

IRQ_Funcion_imp( TIM1_BRK_TIM15_IRQHandler )
{
   // NIY
   exception( );
   return;
}

#endif

#pragma GCC diagnostic pop


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

