#include "system.h"
#include "oneway.h"
#include "adcdma.h"

static AdcDma * pInstance = nullptr;

extern "C" {
  [[gnu::interrupt]] extern void DMA1_Channel1_IRQHandler();
}
void DMA1_Channel1_IRQHandler( void ) {
  DMA1_Type::INTFR_DEF state (DMA1.INTFR);
  DMA1.INTFCR.R = state.R;  // clear all
  if (!pInstance) return;
  if      (state.B.HTIF1 != RESET) pInstance->send (false);
  else if (state.B.TCIF1 != RESET) pInstance->send (true);
}

static inline void EnableClock (void) noexcept {
  // Enable DMA
  RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto {
    r.B.SRAMEN = SET;
    r.B.DMA1EN = SET;
    return r.R;
  });
  // Enable ADC + GPIOC
  RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> auto {
    r.B.ADC1EN = SET;
    r.B.IOPAEN = SET;
    return r.R;
  });
  RCC.APB1PCENR.B.TIM3EN = SET; // Enable TIM3
  RCC.CFGR0.B.ADCPRE     = 3u;  // PCLK2 divided by 8 as ADC clock (18 MHz, ! pretaktovano 14 MHz max).
  //  PIN PA2 / A2
  GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
    r.B.MODE2 = 0u;
    r.B.CNF2  = 0u;
    return r.R;
  });
}
static inline void Timer3Init (uint32_t us) noexcept {
  TIM3.PSC.R    = 143u;          // 1 MHz Fs
  TIM3.ATRLR.R  = us - 1u;
  // TRGO update for ADC
  TIM3.CTLR2.B.MMS = 2u;
}
static inline void AdcCalibrate (void) noexcept {
  // RESET
  RCC.APB2PRSTR.B.ADC1RST = SET;
  RCC.APB2PRSTR.B.ADC1RST = RESET;
  // set channels
  ADC1.RSQR3__CHANNEL.B.SQ1__CHSEL  = 2u;    // CH2
  ADC1.RSQR1.B.L      = 0u;                  // 1 regular conversion
  ADC1.SAMPTR2_CHARGE2.B.SMP2_TKCG2 = 7u;
  ADC1.CTLR1.B.SCAN   = SET;
  
  ADC1.CTLR2.B.ADON   = SET;
  ADC1.CTLR2.B.RSTCAL = SET;             // Launch the calibration by setting RSTCAL
  while (ADC1.CTLR2.B.RSTCAL != RESET);  // Wait until RSTCAL=0
  ADC1.CTLR2.B.CAL    = SET;             // Launch the calibration by setting CAL
  while (ADC1.CTLR2.B.CAL    != RESET);  // Wait until CAL=0  
}
typedef __SIZE_TYPE__ size_t;
static inline void Dma1Ch1Init (void * ptr) noexcept {
  // Configure the peripheral data register address
  DMA1.PADDR1.R = reinterpret_cast<size_t> (& ADC1.RDATAR_DR_ACT_DCG);
  // Configure the memory address
  DMA1.MADDR1.R = reinterpret_cast<size_t> (ptr);
  // Configure the number of DMA tranfer to be performs on DMA channel 1
  DMA1.CNTR1 .R = FULL_LEN;
  // Configure increment, size, interrupts and circular mode
  DMA1.CFGR1.modify([] (DMA1_Type::CFGR1_DEF & r) -> auto {
    r.B.PL      = 3u;       // highest priority
    r.B.MEM2MEM = RESET;    // periferal -> memory
    r.B.MINC    = SET;      // memory increment
    r.B.MSIZE   = 1u;       // 16-bit
    r.B.PSIZE   = 1u;       // 16-bit
    r.B.HTIE    = SET;      // INT Enable HALF
    r.B.TCIE    = SET;      // INT Enable FULL
    r.B.CIRC    = SET;      // Circular MODE
    // Enable DMA Channel 1
    r.B.EN    = SET;
    return r.R;
  });  
}
static inline void AdcPostInit (void) noexcept {
  ADC1.CTLR2.modify([](ADC1_Type::CTLR2_DEF & r) -> auto {
    r.B.DMA     = SET;
    r.B.EXTTRIG = SET;
    r.B.EXTSEL  = 4u;       // TRGO event of timer 3
    r.B.SWSTART = SET;
    return r.R;
  });
}
////////////////////////////////////////////////////////////////////////////////////
AdcDma::AdcDma() noexcept : pL (buffer), pH (buffer + HALF_LEN), dst (nullptr) {
  pInstance = this;
  EnableClock ();
  Timer3Init  (1000u);
  NVIC.EnableIRQ (DMA1_Channel1_IRQn);
  AdcCalibrate();
  Dma1Ch1Init (buffer);
  AdcPostInit ();
  // start timer
  TIM3.CTLR1.B.CEN = SET;
}
inline void AdcDma::send(const bool b) {
  if (!dst) return;
  if (b) dst->Send (pH, HALF_LEN);
  else   dst->Send (pL, HALF_LEN);
}