
#include "bl_Hw.h"
#include <hw_prcm.h>
#include <hw_gpio.h>
#include <hw_ioc.h>
#include <hw_nvic.h>
#include <hw_aon_ioc.h>
#include <hw_aon_event.h>
#include <hw_aon_pmctl.h>

#define  BEEP_COUNTER_RESOLUTION               (48000000UL)     /* tick frequency of counter */
#define  BEEPER_FREQUENCY                      4000U
#define  BEEP_COUNTER_PERIOD                   (BEEP_COUNTER_RESOLUTION/BEEPER_FREQUENCY) /* max count */
#define  BEEP_COUNTER_PERIOD_LOW_SOUND         (BEEP_COUNTER_PERIOD*2)
#define  BEEP_COUNTER_CCV                      (BEEP_COUNTER_PERIOD/2) /* compare value */


#pragma SET_CODE_SECTION(".bl_text")

//*******************************************************************************************************
Tbl_Hw::Tbl_Hw()
{
    mRedPin = 5;
    mRedIsActiveHigh = true;
    mGreenPin = 3;
    mGreenIsActiveHigh = true;
    mBluePin = 4;
    mBlueIsActiveHigh = true;
    mBeeperPin = 15;
    mBeeperSupported = true;

    HwInit();
    waitInit();
    LedInit();
    BeeperInit();

    //Den Busy Pin auf Input mit PullDown setzten und abfragen
    uint32_t BitMask = 1 << 9;
    uint32_t IOCOffset = 4 * 9;
    HWREG(GPIO_BASE + GPIO_O_DOE31_0) &= ~BitMask;
    HWREG(IOC_BASE + IOCOffset) |= IOC_IOCFG0_IE;
    HWREG(IOC_BASE + IOCOffset) &= ~IOC_IOCFG0_PULL_CTL_UP;
    HWREG(IOC_BASE + IOCOffset) |= IOC_IOCFG0_PULL_CTL_DWN;
    HWREG(IOC_BASE + IOCOffset) &= ~IOC_IOCFG0_PULL_CTL_DIS;

    uint32_t nHigh = 0;
    for (uint32_t i=0; i<1000; i++)
    {
        if ((HWREG(GPIO_BASE + GPIO_O_DIN31_0) & BitMask) != 0) nHigh++;
        wait_us(100);
    }
}

//*******************************************************************************************************
Tbl_Hw::~Tbl_Hw()
{
}

//************************************************************************************************
void Tbl_Hw::Cleanup()
{
    SignalPoint(1);
    //Den Busy Pin TriState setzen
    uint32_t IOCOffset = 4 * 9;
    HWREG(IOC_BASE + IOCOffset) &= ~IOC_IOCFG0_PULL_CTL_DWN;
    BeeperDeInit();
    //Die LED auf gelb
    switchBlueOff();
    switchGreenOn();
    switchRedOn();
}
//************************************************************************************************
void Tbl_Hw::HwInit()
{
    //Alle Interrupts deaktivieren
    HWREG(NVIC_DIS0) = 0xFFFFFFFF;
    HWREG(NVIC_DIS1) = 0x3F;  //Es gibt nur Interrupts auf den unteren 6 Bits
    //Eventuelle Interrupt Pending Bits löschen
    HWREG(NVIC_UNPEND0) = 0xFFFFFFFF;
    HWREG(NVIC_UNPEND0) = 0x3F;  //Es gibt nur Interrupts auf den unteren 6 Bits

    HWREG(PRCM_BASE + PRCM_O_OSCIMSC) = 0;
    HWREG(PRCM_BASE + PRCM_O_OSCIMSC) |= PRCM_OSCIMSC_LFSRCDONEIM_M;

    // Setup interrupts so that they wake up from standby (use MCU_WU1)
    HWREG(AON_EVENT_BASE + AON_EVENT_O_MCUWUSEL) =
        (HWREG(AON_EVENT_BASE + AON_EVENT_O_MCUWUSEL) & (~AON_EVENT_MCUWUSEL_WU1_EV_M)) |
            AON_EVENT_MCUWUSEL_WU1_EV_PAD;

    // Open latches out to I/Os
    // This might be unnecessary, but isn't when you start from debugger
    HWREG(AON_IOC_BASE + AON_IOC_O_IOCLATCH) = AON_IOC_IOCLATCH_EN;

    //Sleepmode für I/O ausschalten
    HWREG(AON_PMCTL_BASE + AON_PMCTL_O_SLEEPCTL) = AON_PMCTL_SLEEPCTL_IO_PAD_SLEEP_DIS;

    //Den Clock zu den benötigten HW Modulen aktivieren
    HWREG(PRCM_BASE + PRCM_O_GPIOCLKGR) = PRCM_GPIOCLKGR_AM_CLK_EN;   //IO Pins
    //Pheripherie und gesamte serielle Kommunikation (UART, SPI und I2C)
    HWREG(PRCM_BASE + PRCM_O_PDCTL0) = PRCM_PDCTL0_PERIPH_ON + PRCM_PDCTL0_SERIAL_ON;
    HWREG(PRCM_BASE + PRCM_O_SSICLKGR) = PRCM_SSICLKGR_AM_CLK_EN_SSI0; //SSI im Power Register einschalten

    //Einstellungen des Power- Reset- Clock Managements übernehmen.
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;

    for(uint32_t i = 0; i < 0xFF; i++)
        asm(" nop");

}

//************************************************************************************************
void Tbl_Hw::HwDeInit()
{
    //Alle Interrupts deaktivieren
    HWREG(NVIC_DIS0) = 0xFFFFFFFF;
    HWREG(NVIC_DIS1) = 0x3F;  //Es gibt nur Interrupts auf den unteren 6 Bits
    //Eventuelle Interrupt Pending Bits löschen
    HWREG(NVIC_UNPEND0) = 0xFFFFFFFF;
    HWREG(NVIC_UNPEND0) = 0x3F;  //Es gibt nur Interrupts auf den unteren 6 Bits

    HWREG(PRCM_BASE + PRCM_O_OSCIMSC) = 0;
    HWREG(PRCM_BASE + PRCM_O_OSCIMSC) |= PRCM_OSCIMSC_LFSRCDONEIM_M;

    // Setup interrupts so that they wake up from standby (use MCU_WU1)
    HWREG(AON_EVENT_BASE + AON_EVENT_O_MCUWUSEL) =
        (HWREG(AON_EVENT_BASE + AON_EVENT_O_MCUWUSEL) & (~AON_EVENT_MCUWUSEL_WU1_EV_M)) |
            AON_EVENT_MCUWUSEL_WU1_EV_PAD;

    // Open latches out to I/Os
    // This might be unnecessary, but isn't when you start from debugger
    HWREG(AON_IOC_BASE + AON_IOC_O_IOCLATCH) = AON_IOC_IOCLATCH_EN;

    //Sleepmode für I/O ausschalten
    HWREG(AON_PMCTL_BASE + AON_PMCTL_O_SLEEPCTL) = AON_PMCTL_SLEEPCTL_IO_PAD_SLEEP_DIS;

    //Den Clock zu den benötigten HW Modulen aktivieren
    HWREG(PRCM_BASE + PRCM_O_GPIOCLKGR) = PRCM_GPIOCLKGR_AM_CLK_EN;   //IO Pins
    //Pheripherie und gesamte serielle Kommunikation (UART, SPI und I2C)
    HWREG(PRCM_BASE + PRCM_O_PDCTL0) = 0;
    HWREG(PRCM_BASE + PRCM_O_SSICLKGR) = 0;

    //Einstellungen des Power- Reset- Clock Managements übernehmen.
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;

    for(uint32_t i = 0; i < 0xFF; i++)
        asm(" nop");

}

//************************************************************************************************
void Tbl_Hw::waitInit()
{
    //General Purpose Timer einschalten
    HWREG(PRCM_BASE + PRCM_O_GPTCLKGR) |= PRCM_GPTCLKGR_AM_CLK_EN_AM_GPT0;
    //Einstellungen des Power- Reset- Clock Managements übernehmen.
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;


    //Die zwei vorhandenen 16-Bit Timer als einen 32-Bit Timer benutzen.
    //Deswegen müssen die weiteren Konfigurationen auch nur für Timer A gemacht werden
    HWREG(GPT0_NONBUF_BASE + GPT_O_CFG) = GPT_CFG_CFG_32BIT_TIMER;

    //Hochzählen und Timeout Interrupts disablen
    HWREG(GPT0_NONBUF_BASE + GPT_O_TAMR) = GPT_TAMR_TACINTD_DIS_TO_INTR + GPT_TAMR_TACDIR_UP;

    //Als Prescaler 3 verwenden (Also eine 2 ins Register schreiben, da der Wert
    //0 für den Normalen Prescaler = 1 steht). Somit werden aus den 48MHz -> 16MHz.
    //Dadurch ist der Timer wie bei den XMega Geräten eingestellt.
    //HWREG(TIMES_TIMER_BASE + GPT_O_TAPR) = 2;
    //Das geht anscheinend nur, wenn die Timer als 16 Bit Timer verwendet werden

    //Timer A einschalten und enablen, dass der Timer mit dem Debugger pausiert (STALL)
    HWREG(GPT0_NONBUF_BASE + GPT_O_CTL) = GPT_CTL_TAEN_EN + GPT_CTL_TASTALL_EN;

}

//************************************************************************************************
void Tbl_Hw::waitDeInit()
{
    //Benutzte Register auf den HW Default setzen
    HWREG(GPT0_NONBUF_BASE + GPT_O_CTL) = 0;
    HWREG(GPT0_NONBUF_BASE + GPT_O_TAMR) = 0;
    HWREG(GPT0_NONBUF_BASE + GPT_O_CFG) = 0;

    //General Purpose Timer ausschalten
    HWREG(PRCM_BASE + PRCM_O_GPTCLKGR) &= ~PRCM_GPTCLKGR_AM_CLK_EN_AM_GPT0;
    //Einstellungen des Power- Reset- Clock Managements übernehmen.
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;
}

//************************************************************************************************
void Tbl_Hw::LedInit()
{
    //Pins für die LEDs auf Output schalten
    HWREG(GPIO_BASE + GPIO_O_DOE31_0) |= (1 << mRedPin) + (1 << mGreenPin) + (1 << mBluePin);
    //switchBlueOn();
    switchRedOn();
}

//************************************************************************************************
void Tbl_Hw::BeeperInit()
{
    //General Purpose Timer 1 (GPT1) einschalten
    HWREG(PRCM_BASE + PRCM_O_GPTCLKGR) |= PRCM_GPTCLKGR_AM_CLK_EN_AM_GPT1;
    //Einstellungen des Power- Reset- Clock Managements übernehmen.
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;

    //Beeper Timer einstellen
    HWREG(GPT1_NONBUF_BASE + GPT_O_CFG) = GPT_CFG_CFG_16BIT_TIMER; //GPT1 als 2x 16Bit Timer verwenden
    HWREG(GPT1_NONBUF_BASE + GPT_O_TAMATCHR) = BEEP_COUNTER_CCV; //Compare Wert festlegen
    HWREG(GPT1_NONBUF_BASE + GPT_O_TAMR) = GPT_TAMR_TAMR_PERIODIC | GPT_TAMR_TAAMS_PWM; //PWM
    //Den Output des PWM Pins invertieren. Da der Beeper-Pin über ein Pull-Up von auserhalb
    //stadardmäßig auf High ist, muss das sein "default" Zustand sein. Bei der Standardeinstellung
    //für PWM ist der Output aber zunächst low. Dadurch hört man beim Starten ein "knacken".
    //Der Output des PWM Pins muss also invertiert werden, damit er zu beginn auf High bleibt.
    //Da das Geräusch des Beepers durch PWM mit 50% High und 50% Low erzeugt wird, hat das invertieren
    //keinen Einfluss auf das erzeugte Geräusch.
    HWREG(GPT1_NONBUF_BASE + GPT_O_CTL) = GPT_CTL_TAPWML_INVERTED;

    //Pin als Output und High
    HWREG(GPIO_BASE + GPIO_O_DOE31_0) |= (1 << mBeeperPin);
    HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mBeeperPin);
    //Peeper als PWM Pin konfigurieren
    HWREG(IOC_BASE + (4 * mBeeperPin)) |= (IOC_IOCFG0_PORT_ID_M & IOC_IOCFG0_PORT_ID_PORT_EVENT2);
}

//************************************************************************************************
void Tbl_Hw::BeeperDeInit()
{
    //HWREG(IOC_BASE + (4 * mBeeperPin)) = IOC_IOCFG0_PULL_CTL_DIS;
    HWREG(GPIO_BASE + GPIO_O_DOE31_0) &= ~(1 << mBeeperPin);
    HWREG(GPT1_NONBUF_BASE + GPT_O_CTL) = 0;
    HWREG(GPT1_NONBUF_BASE + GPT_O_CFG) = 0;
    HWREG(PRCM_BASE + PRCM_O_GPTCLKGR) &= ~PRCM_GPTCLKGR_AM_CLK_EN_AM_GPT1;
    //Einstellungen des Power- Reset- Clock Managements übernehmen.
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;
}

//************************************************************************************************
void Tbl_Hw::switchRedOn()
{
    if (mRedIsActiveHigh)
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mRedPin);
    else
        HWREG(GPIO_BASE + GPIO_O_DOUTCLR31_0) = (1 << mRedPin);
}

//************************************************************************************************
void Tbl_Hw::switchRedOff()
{
    if (mRedIsActiveHigh)
        HWREG(GPIO_BASE + GPIO_O_DOUTCLR31_0) = (1 << mRedPin);
    else
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mRedPin);
}

//************************************************************************************************
void Tbl_Hw::switchGreenOn()
{
    if (mGreenIsActiveHigh)
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mGreenPin);
    else
        HWREG(GPIO_BASE + GPIO_O_DOUTCLR31_0) = (1 << mGreenPin);
}

//************************************************************************************************
void Tbl_Hw::switchGreenOff()
{
    if (mGreenIsActiveHigh)
        HWREG(GPIO_BASE + GPIO_O_DOUTCLR31_0) = (1 << mGreenPin);
    else
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mGreenPin);
}

//************************************************************************************************
void Tbl_Hw::switchBlueOn()
{
    if (mBlueIsActiveHigh)
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mBluePin);
    else
        HWREG(GPIO_BASE + GPIO_O_DOUTCLR31_0) = (1 << mBluePin);
}

//************************************************************************************************
void Tbl_Hw::switchBlueOff()
{
    if (mBlueIsActiveHigh)
        HWREG(GPIO_BASE + GPIO_O_DOUTCLR31_0) = (1 << mBluePin);
    else
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mBluePin);
}

//************************************************************************************************
void Tbl_Hw::switchBeeperOn()
{
    if (mBeeperSupported)
    {
        HWREG(GPT1_NONBUF_BASE + GPT_O_TAILR) = BEEP_COUNTER_PERIOD;
        HWREG(GPT1_NONBUF_BASE + GPT_O_CTL) |= GPT_CTL_TAEN_EN;
    }
}

//************************************************************************************************
void Tbl_Hw::switchBeeperOff()
{
    if (mBeeperSupported)
    {
        HWREG(GPT1_NONBUF_BASE + GPT_O_CTL) &= ~GPT_CTL_TAEN_EN;

        //Den Output Pin direkt auf High setzen (dann hat der Beeper keinen Strom)
        HWREG(GPIO_BASE + GPIO_O_DOUTSET31_0) = (1 << mBeeperPin);
    }
}

//************************************************************************************************
void Tbl_Hw::waitTicks(uint32_t nTicks)
{
    uint32_t startValue = HWREG(GPT0_NONBUF_BASE + GPT_O_TAR);
    uint32_t actValue;
    do
    {
        actValue = HWREG(GPT0_NONBUF_BASE + GPT_O_TAR);
    }
    while ((actValue-startValue) < nTicks);
}

//************************************************************************************************
void Tbl_Hw::signalError(uint32_t ErrorNumber)
{
    switchRedOff();
    wait_ms(300);
    for (uint32_t i=0; i<3; i++)
    {
        switchRedOn();
        switchBeeperOn();
        wait_ms(60);
        switchBeeperOff();
        switchRedOff();
        wait_ms(60);
    }
    while (ErrorNumber > 0)
    {
        wait_ms(240);   //Ergibt mit den vorigen 60mSek 300mSek aus
        switchRedOn();
        switchBeeperOn();
        wait_ms(300);
        switchBeeperOff();
        switchRedOff();
        wait_ms(60);
        ErrorNumber--;
    }
    wait_ms(300);
}

//************************************************************************************************
void Tbl_Hw::doReset()
{
    HWREG(AON_PMCTL_BASE + AON_PMCTL_O_RESETCTL) = AON_PMCTL_RESETCTL_SYSRESET;
}

//*******************************************************************************************************
void Tbl_Hw::SignalPoint(int nSignals)
{
    switchRedOff();
    //switchGreenOn();
    uint32_t StartTick = actTickValue();
    while (delta_ms(StartTick)<300){};
    while (nSignals > 0)
    {
        nSignals--;
        switchBlueOn();
        StartTick = actTickValue();
        while (delta_ms(StartTick)<200){};
        switchBlueOff();
        StartTick = actTickValue();
        while (delta_ms(StartTick)<200){};
    }
    //switchRedOn();
    //switchGreenOn();
    StartTick = actTickValue();
    while (delta_ms(StartTick)<700){};
    //switchGreenOff();
}

#pragma SET_CODE_SECTION()
