#include "system.h"
#include "pwmclass.h"
#include "gpio.h"

static PwmClass * pInstance = nullptr;
extern "C" void DMA1_Channel5_IRQHandler( void ) __attribute__((interrupt));
void DMA1_Channel5_IRQHandler( void ) {
  DMA1_Type::INTFR_DEF state (DMA1.INTFR);
  DMA1.INTFCR.R = state.R;  // clear all
  if (!pInstance) return;
  if      (state.B.HTIF5 != RESET) pInstance->send(false);
  else if (state.B.TCIF5 != RESET) pInstance->send(true);
}

/*
 * initialize TIM1 for PWM
 */
static inline void tim1pwm_init () noexcept {
	// Enable GPIOD and TIM1
  RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto {
    r.B.IOPDEN = SET;
    r.B.TIM1EN = SET;
    return r.R;
  });
    // PD0 is T1CH1N, PD2 is T1CH1, 10MHz Output alt func, push-pull
  GPIOD.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
    r.B.CNF0  = 2u;
    r.B.MODE0 = 1u;
    r.B.CNF2  = 2u;
    r.B.MODE2 = 1u;
    return r.R;
  });
	// Reset TIM1 to init all regs
  RCC.APB2PRSTR.B.TIM1RST = SET;
  RCC.APB2PRSTR.B.TIM1RST = RESET;
	// CTLR1: default is up, events generated, edge align
	// SMCFGR: default clk input is CK_INT
	// Prescaler 
  TIM1.PSC.R = 0u;
	// Auto Reload - sets period
  TIM1.ATRLR.R = MAXPWM - 1;
  
  TIM1.CCER.modify([](TIM1_Type::CCER_DEF & r) -> auto {
	// Enable CH1N, CH1 output, positive pol
    r.B.CC1NE = SET;
    r.B.CC1E  = SET;
    /*
    r.B.CC1NP = SET; // active Low
    r.B.CC1P  = SET;
    */
    return r.R;
  });
	// CH1 Mode is output, PWM1 (CC1S = 00, OC1M = 110)
  TIM1.CHCTLR1_Output.modify([](TIM1_Type::CHCTLR1_Output_DEF & r) -> auto {
    r.B.OC1M = 0x6u;
    return r.R;
  });
	// Enable TIM1 outputs
  TIM1.BDTR.modify([](TIM1_Type::BDTR_DEF & r) -> auto {
    r.B.MOE = SET;
    r.B.DTG = 48u;  // Dead time 1us
    return r.R;
  });
  
	// Reload immediately + Trigger DMA
  TIM1.SWEVGR.B.UG     = SET;
  TIM1.DMAINTENR.B.UDE = SET;
	// Enable TIM1
  TIM1.CTLR1.B.CEN     = SET;
}
typedef __SIZE_TYPE__ size_t;
static inline void dma1ch5_init (void * ptr) noexcept {
	// Enable DMA
  RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto {
    r.B.SRAMEN = SET;
    r.B.DMA1EN = SET;
    return r.R;
  });
	// DMA5 can be configured to attach to T1UP
	// The system can only DMA out at ~2.2MSPS.  2MHz is stable.
  DMA1.CNTR5 .R = FULL_LEN;
  DMA1.MADDR5.R = reinterpret_cast<size_t>(ptr);
  DMA1.PADDR5.R = reinterpret_cast<size_t>(& TIM1.CH1CVR);
  NVIC.EnableIRQ (DMA1_Channel5_IRQn);
  DMA1.CFGR5.modify([](DMA1_Type::CFGR5_DEF & r) -> auto {
    r.B.DIR   = SET;                    // MEM2PERIPHERAL
    r.B.PL    = 2u;                     // High priority.
    r.B.PSIZE = 1u;                     // 16-bit peripheral
    r.B.MSIZE = 1u;                     // 16-bit memory
    r.B.MINC  = SET;                    // Increase memory.
    r.B.CIRC  = SET;                    // Circular mode.
    r.B.HTIE  = SET;                    // Half-trigger
    r.B.TCIE  = SET;                    // Whole-trigger
    // Enable DMA1 ch5
    r.B.EN    = SET;
    return r.R;
  });
}

PwmClass::PwmClass() noexcept : pL(buffer), pH(buffer + HALF_LEN), src(nullptr) {
  pInstance = this;
  tim1pwm_init ();
  dma1ch5_init (buffer);
}