/*
 * pwm.h
 *
 *  Created on: 29.07.2018
 *      Author: eyck
 */

#ifndef PWM_H_
#define PWM_H_

#include <sifive/devices/pwm.h>
#include "util/bit_field.h"
#include <limits>
#include <cstdint>

template<uint32_t BASE_ADDR>
class pwm_regs {
public:
    BEGIN_BF_DECL(pwmcfg_t, uint32_t);
        BF_FIELD(scale, 0, 4);
        BF_FIELD(sticky, 8, 1);
        BF_FIELD(zerocmp, 9, 1);
        BF_FIELD(deglitch, 10, 1);
        BF_FIELD(enalways, 12, 1);
        BF_FIELD(enoneshot, 13, 1);
        BF_FIELD(cmp0center, 16, 1);
        BF_FIELD(cmp1center, 17, 1);
        BF_FIELD(cmp2center, 18, 1);
        BF_FIELD(cmp3center, 19, 1);
        BF_FIELD(cmp0gang, 24, 1);
        BF_FIELD(cmp1gang, 25, 1);
        BF_FIELD(cmp2gang, 26, 1);
        BF_FIELD(cmp3gang, 27, 1);
        BF_FIELD(cmp0ip, 28, 1);
        BF_FIELD(cmp1ip, 29, 1);
        BF_FIELD(cmp2ip, 30, 1);
        BF_FIELD(cmp3ip, 31, 1);
    END_BF_DECL();

    BEGIN_BF_DECL(pwms_t, uint32_t);
        BF_FIELD(s, 0, 16);
    END_BF_DECL() r_pwms;

    BEGIN_BF_DECL(pwmcmp0_t, uint32_t);
        BF_FIELD(cmp0, 0, 16);
    END_BF_DECL() r_pwmcmp0;

    BEGIN_BF_DECL(pwmcmp1_t, uint32_t);
        BF_FIELD(cmp0, 0, 16);
    END_BF_DECL() r_pwmcmp1;

    BEGIN_BF_DECL(pwmcmp2_t, uint32_t);
        BF_FIELD(cmp0, 0, 16);
    END_BF_DECL() r_pwmcmp2;

    BEGIN_BF_DECL(pwmcmp3_t, uint32_t);
        BF_FIELD(cmp0, 0, 16);
    END_BF_DECL() r_pwmcmp3;

    static inline pwmcfg_t& cfg_reg(){
        return *reinterpret_cast<pwmcfg_t*>(BASE_ADDR+PWM_CFG);
    }

    static inline uint32_t& count_reg(){
        return *reinterpret_cast<uint32_t*>(BASE_ADDR+PWM_COUNT);
    }

    static inline pwms_t& s_reg(){
        return *reinterpret_cast<pwms_t*>(BASE_ADDR+PWM_S);
    }

    static inline pwmcmp0_t& cmp0_reg(){
        return *reinterpret_cast<pwmcmp0_t*>(BASE_ADDR+PWM_CMP0);
    }

    static inline pwmcmp1_t& cmp1_reg(){
        return *reinterpret_cast<pwmcmp1_t*>(BASE_ADDR+PWM_CMP1);
    }

    static inline pwmcmp2_t& cmp2_reg(){
        return *reinterpret_cast<pwmcmp2_t*>(BASE_ADDR+PWM_CMP2);
    }

    static inline pwmcmp3_t& cmp3_reg(){
        return *reinterpret_cast<pwmcmp3_t*>(BASE_ADDR+PWM_CMP3);
    }

    static inline bool oneshot_delay(long delay_us){
        auto scaling_factor=0;
        while(delay_us/(1<<scaling_factor) > std::numeric_limits<unsigned short>::max()){
            scaling_factor++;
        }
        cfg_reg()=0;
        count_reg()=0;
        cfg_reg().scale=4+scaling_factor; // divide by 16 so we get 1us per pwm clock
        cmp0_reg().cmp0 = delay_us/(1<<scaling_factor);
        pwm_active=true;
        cfg_reg().enoneshot=true;
        do{
            asm("wfi");
            asm("nop");
        }while(pwm_active);
        return true;
    }

    static void pwm_interrupt_handler(){
        cfg_reg().cmp0ip=false;
        pwm_active=false;
    }

    inline
    static bool is_active(){ return pwm_active; }

    inline
    static void set_active() {pwm_active=true;}

private:
    static volatile bool pwm_active;
};


#endif /* PWM_H_ */
