#pragma once

#include <kinetis.h>
#include <core_pins.h>

namespace TeensyStepFTM
{


//========================================================================== 
// Available timer modules for the Teensy XX boards. Please note that those 
// timers are also used by the core libraries for PWM and AnalogWrite. 
// Therefore, choose a timer which isn't attached to the pins you need for 
// PWM or AnalogWrite. (TEENSY LC not yet supported)
//
// D: Default, X: available
//
//      TIMER           | Channels|        Timer is available for           |
//                      |         | T-LC | T3.0 |  T3.1 | T3.2 | T3.5 | T3.6|
//----------------------|---------|------|------|-------|------|------|-----|
#define TIMER_FTM0 1  //|   8     |      |  D   |   D   |  D   |  D   |  D  |
#define TIMER_FTM1 2  //|   2     |      |  X   |   X   |  X   |  X   |  X  |
#define TIMER_FTM2 3  //|   2     |      |      |   X   |  X   |  X   |  X  |
#define TIMER_FTM3 4  //|   8     |      |      |       |      |  X   |  X  |
#define TIMER_TPM0 5  //|   6     |      |      |       |      |      |     |
#define TIMER_TPM1 6  //|   2     |      |      |       |      |      |  X  |
#define TIMER_TPM2 7  //|   2     |      |      |       |      |      |  X  |
#define TIMER_DEFAULT -1

// If you need a special timer, please replace "TIMER_DEFAULT" by a timer from the list above
#define USE_TIMER TIMER_DEFAULT


//==========================================================================
// Nothing to be changed below here 
//==========================================================================



#if USE_TIMER == TIMER_DEFAULT
#undef USE_TIMER
#if defined __MKL26Z64__    
#define USE_TIMER TIMER_TPM0
#else
#define USE_TIMER TIMER_FTM0
#endif
#endif

    constexpr unsigned selTimer = USE_TIMER - 1;
    constexpr bool isFTM = selTimer <= 3;

    //================================================
    // Which board do we use

    enum class _boards
    {
        T_LC, T_30, T31_2, T_35, T_36
    };

#if defined __MKL26Z64__      
    constexpr _boards board = _boards::T_LC;
#elif defined __MK20DX128__    
    constexpr _boards board = _boards::T_30;
#elif defined __MK20DX256__     
    constexpr _boards board = _boards::T31_2;
#elif defined __MK64FX512__    
    constexpr _boards board = _boards::T_35;
#elif defined __MK66FX1M0__    
    constexpr _boards board = _boards::T_36;
#endif


    //====================================================================
    // Memory layout of register banks for FTM and TPM timers

    typedef struct      // FTM & TPM Channels
    {
        uint32_t SC;
        uint32_t CV;
    } FTM_CH_t;

    typedef struct       // FTM register block (this layout is compatible to a TPM register block)
    {
        uint32_t SC;
        uint32_t CNT;
        uint32_t MOD;
        FTM_CH_t CH[8];
        uint32_t CNTIN;
        uint32_t STATUS;
        uint32_t MODE;
        uint32_t SYNC;
        uint32_t OUTINIT;
        uint32_t OUTMASK;
        uint32_t COMBINE;
        uint32_t DEADTIME;
        uint32_t EXTTRIG;
        uint32_t POL;
        uint32_t FMS;
        uint32_t FILTER;
        uint32_t FLTCTRL;
        uint32_t QDCTRL;
        uint32_t CONF;
        uint32_t FLTPOL;
        uint32_t SYNCONF;
        uint32_t INVCTRL;
        uint32_t SWOCTRL;
        uint32_t PWMLOAD;
    }FTM_t;

    //-----------------------------------------------------------------------------------
    // Definition of base address for register block, number of channels and IRQ number
    //

    constexpr uintptr_t TimerBaseAddr[][7] =
    { //    FTM0         FTM1        FTM2        FTM3        TPM0        TMP1        TMP2
        { 0,          0,          0,          0,          0,          0,          0          },  // Teensy LC not yet supported
        { 0x40038000, 0x40039000, 0,          0,          0,          0,          0,         },  // Teensy 3.0
        { 0x40038000, 0x40039000, 0x400B8000, 0,          0,          0,          0,         },  // Teensy 3.1/3.2
        { 0x40038000, 0x40039000, 0x400B8000, 0x400B9000, 0,          0,          0,         },  // Teensy 3.5
        { 0x40038000, 0x40039000, 0x400B8000, 0x400B9000, 0,          0x400C9000, 0x400CA000 },  // Teensy 3.6
    };

    constexpr int IRQ_Number[][7]
    {
        //  FTM0  FTM1  FTM2  FTM3  TPM0  TPM1  TPM2
          {  0,    0,   0,     0,    0,    0,    0 },  // Teensy LC
          { 25,   26,   0,     0,    0,    0,    0 },  // Teensy 3.0
          { 62,   63,   64,    0,    0,    0,    0 },  // Teensy 3.1/3.2
          { 42,   43,   44,   71,    0,    0,    0 },  // Teensy 3.5
          { 42,   43,   44,   71,    0,   88,   89 },  // Teensy 3.6
    };

    constexpr int _nrOfChannels[]
    {
        8,  // FTM0
        2,  // FTM1
        2,  // FTM2
        8,  // FTM3
        6,  // TPM0
        2,  // TPM1
        2,  // TPM2 
    };

    constexpr uintptr_t timerAddr = TimerBaseAddr[(int)board][selTimer];
    constexpr volatile FTM_t* timer = __builtin_constant_p((FTM_t*)timerAddr) ? (FTM_t*)timerAddr : (FTM_t*)timerAddr; // base address for register block of selected timer
    constexpr unsigned irq = IRQ_Number[(int)board][selTimer];                     // IRQ number of selected timer
    constexpr unsigned maxChannel = _nrOfChannels[selTimer];                        // Number of channels for selected timer

    static_assert(timer != nullptr && irq != 0, "Board does not support choosen timer");  //Generate compiler error in case the board does not support the selected timer

    //-----------------------------------------------------------------------------------
    //Frequency dependent settings 

    constexpr unsigned _timer_frequency = isFTM ? F_BUS : 16000000;  // FTM timers are clocked with F_BUS, the TPM timers are clocked with OSCERCLK (16MHz for all teensies)

    // Choose prescaler such that one timer cycle corresponds to about 1µs
    constexpr unsigned prescale = 
        _timer_frequency > 120000000 ? 0b111 :
        _timer_frequency > 60000000 ? 0b110 :
        _timer_frequency > 30000000 ? 0b101 : 
        _timer_frequency > 15000000 ? 0b100 :
        _timer_frequency > 8000000 ? 0b011 :
        _timer_frequency > 4000000 ? 0b010 :
        _timer_frequency > 2000000 ? 0b001 : 0b000;

    // Calculates required reload value to get a delay of mu microseconds. 
    // this will be completely evaluated by the compiler as long as mu is known at compile time 
    constexpr int microsToReload(const float mu)
    {
        return  mu * 1E-6 * _timer_frequency / (1 << prescale) + 0.5;
    }
}
