#include "STM32F0x1.h"
#include "CortexM0.h"
#include "gpio.h"
#include "adcdma.h"
#include "oneway.h"

static constexpr uint32_t AdcChanel = 2u;
static constexpr uint32_t AdcPin    = 2u;

static inline void EnableClock (void) {
  RCC.APB2ENR.B.ADCEN  = SET;
  RCC.AHBENR.B.DMA1EN  = SET;
  RCC.APB1ENR.B.TIM3EN = SET;
}
static inline void Timer3Init (uint32_t us) {
  TIM3.PSC.R  = 47u;
  TIM3.ARR.R  = us - 1u;
  // Preload, enable
  TIM3.CR1.B.ARPE = SET;
  // TRGO update for ADC
  TIM3.CR2.B.MMS  = 2;
}
typedef __SIZE_TYPE__ size_t;
static inline void Dma1Ch1Init (void * ptr) {
  // Enable DMA transfer on ADC and circular mode
  ADC.CFGR1.B.DMAEN  = SET;
  ADC.CFGR1.B.DMACFG = SET;
  // Configure the peripheral data register address
  DMA1.CPAR1.R = reinterpret_cast<size_t> (&(ADC.DR));
  // Configure the memory address
  DMA1.CMAR1.R = reinterpret_cast<size_t> (ptr);
  // Configure the number of DMA tranfer to be performs on DMA channel 1
  DMA1.CNDTR1.R = FULL_LEN;
  // Configure increment, size, interrupts and circular mode
  DMA1.CCR1.modify([] (auto & r) -> auto {
    r.B.MINC  = SET;
    r.B.MSIZE = 1u;
    r.B.PSIZE = 1u;
    r.B.HTIE  = SET;
    r.B.TCIE  = SET;
    r.B.CIRC  = SET;
    return r.R;
  });
  // Enable DMA Channel 1
  DMA1.CCR1.B.EN = SET;

}
static inline void AdcCalibrate (void) {
  // Ensure that ADEN = 0
  // Clear ADEN
  if (ADC.CR.B.ADEN != RESET) {
    ADC.CR.B.ADEN = RESET;
  }
  // Launch the calibration by setting ADCAL
  ADC.CR.B.ADCAL = SET;
  // Wait until ADCAL=0
  while (ADC.CR.B.ADCAL != RESET);
  //__NOP();
  //__NOP();   // This 2 NOPs are to ensure 2 ADC Cycles  before setting ADEN bit
}
static inline void AdcInit (void) {
  // PCLK / 2 - jitter
  ADC.CFGR2.B.JITOFF_D2 = SET;
  // Select TRG TIM3
  ADC.CFGR1.modify([] (auto & r) -> auto {
    r.B.EXTEN  = 1u;
    r.B.EXTSEL = 3u;
    return r.R;
  });
  // Select CHSELx
  ADC.CHSELR.R = (1 << AdcChanel);
  // Select a sampling mode of 000
  ADC.SMPR.R   = 1u;
}
static inline void AdcStart (void) {
  ADC.CR.B.ADEN    = SET;
  TIM3.CR1.B.CEN   = SET;
  ADC.CR.B.ADSTART = SET;
}
static AdcDma * Instance = nullptr;

AdcDma::AdcDma() noexcept : pL (buffer), pH (buffer + HALF_LEN), dst (nullptr) {
  Instance = this;
  EnableClock ();
  AdcCalibrate();
  GpioClass in   (GpioPortA, AdcPin, GPIO_Mode_AN);
  Timer3Init     (1000);
  NVIC_EnableIRQ (DMA1_CH1_IRQn);
  Dma1Ch1Init (buffer);
  AdcInit     ();
  AdcStart    ();
}
/*
void AdcDma::dmaIrq (void) {
  volatile DMA_ISR_s  status (DMA1.ISR);
  current = nullptr;
  if (status.B.HTIF1) current = ptr_l;
  if (status.B.TCIF1) current = ptr_h;
  // znuluj příznaky
  DMA1.IFCR.R = status.R;
  if (!current) return;
  // zpracuj data, pokud je potřeba
  send (current, PERIOD);
  ~led;
}
*/
extern "C" void DMA1_Channel1_IRQHandler (void) {
  volatile DMA1_Type::ISR_DEF status (DMA1.ISR);
  // znuluj příznaky
  DMA1.IFCR.R = status.R;
  if (!Instance) return;
  if      (status.B.HTIF1 != RESET) Instance->send (false);
  else if (status.B.TCIF1 != RESET) Instance->send (true);
}
inline void AdcDma::send(const bool b) {
  if (!dst) return;
  if (b) dst->Send (pH, HALF_LEN);
  else   dst->Send (pL, HALF_LEN);
}