/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2008, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

//------------------------------------------------------------------------------
//         Headers
//------------------------------------------------------------------------------

#include "slck.h"
#include <board.h>
#if defined(at91sam3u)
#include <board_lowlevel.h>
#endif
#include <utility/assert.h>
#include <utility/trace.h>

//------------------------------------------------------------------------------
//         Local definitions
//------------------------------------------------------------------------------

/// Start Up Time Slow Clock 32K Oscillator // see DC characteritics in Datasheet
#define T_ST_SLCK_32K_IN_MS  1200

/// Start Up Time Slow Clock RC Oscillator  // see DC characteritics in Datasheet
#define T_ST_SLCK_RC_IN_US     75

#define FREQ_SLCK_32K     32768 // see DC characteritics in Datasheet
#define MIN_FREQ_SLCK_RC  20000 // see DC characteritics in Datasheet

#define TIME_5_CYCLES_32K_IN_US     ((2 * 5 * 1000000) / FREQ_SLCK_32K)
#define TIME_5_CYCLES_RC_IN_US      ((2 * 5 * 1000000) / MIN_FREQ_SLCK_RC)

// Define clock timeout
#define CLOCK_TIMEOUT           0xFFFFFFFF

//------------------------------------------------------------------------------
//         Local functions
//------------------------------------------------------------------------------
#if defined(at91cap7) || defined(at91cap9)
//------------------------------------------------------------------------------
/// Wait time in ms
//------------------------------------------------------------------------------
// not precise, depends on the compiler and on the options
static void WaitTimeInMs(unsigned int pck, unsigned int time_ms)
{
    register unsigned int i = 0;
    i = (pck / 1000) * time_ms;
    i = i / 4;
    while(i--);
}

//------------------------------------------------------------------------------
/// Wait time in us
//------------------------------------------------------------------------------
// not precise, depends on the compiler and on the options
static void WaitTimeInUs(unsigned int pck, unsigned int time_us)
{
    volatile unsigned int i = 0;
    i = (pck / 1000000) * time_us;
    i = i / 4;
    while(i--);
}
#endif
//------------------------------------------------------------------------------
//         Global functions
//------------------------------------------------------------------------------

#if defined(at91cap7)
//------------------------------------------------------------------------------
/// Return 1 if the slow clock is 32k
//------------------------------------------------------------------------------
unsigned char SLCK_Is32k(void)
{
    return ((*AT91C_PMC_SR & AT91C_PMC_OSC_SEL) != 0);
    //return ((*AT91C_SYS_SYS_OSCMR & AT91C_OSC32K_SEL) != 0);

}

//------------------------------------------------------------------------------
/// Configure the 32kHz oscillator for the slow clock
//------------------------------------------------------------------------------
void SLCK_RCto32k(void)
{
    // Check that the master clock has a different source than slow clock. If no,
    if( (AT91C_BASE_PMC->PMC_MCKR & AT91C_PMC_CSS) == 0)
    {
        TRACE_WARNING("The master clock use the slow clock. " \
            "Not possible to change Slow clock\n\r");
        return;
    }

    // Check that the slow clock source is RC
    if( SLCK_Is32k() )
    {
        TRACE_WARNING("The slow clock is already the external 32.768kHz crystal\n\r");
        return;
    }

    // Enable the 32,768 Hz oscillator by setting the bit OSC32K_XT_EN to 1.
    *AT91C_SYS_SYS_OSCMR |= AT91C_OSC32K_XT_EN;

    // Wait 32,768 Hz Startup Time for clock stabilization (software loop).
    WaitTimeInMs(BOARD_MCK, /*T_ST_SLCK_32K_IN_MS*/10);

    // Switch from internal RC to 32,768 Hz oscillator by setting the bit OSC32K_SEL to 1.
    *AT91C_SYS_SYS_OSCMR |= AT91C_OSC32K_SEL;

    TRACE_INFO("The slow clock is now the external 32.768kHz crystal\n\r");
}


//------------------------------------------------------------------------------
/// Configure the RC oscillator for the slow clock
//------------------------------------------------------------------------------
void SLCK_32ktoRC(void)
{
    // Check that the master clock has a different source than slow clock.
    if( (AT91C_BASE_PMC->PMC_MCKR & AT91C_PMC_CSS) == 0)
    {
        TRACE_WARNING("The master clock use the slow clock. " \
            "Not possible to change Slow clock\n\r");
        return;
    }

    // Check that the slow clock source is RC
    if( !SLCK_Is32k() )
    {
        TRACE_WARNING("The slow clock is already the internal RC oscillator\n\r");
        return;
    }

    // Enable the internal RC oscillator by setting the bit OSC32K_RC_EN to 1
    *AT91C_SYS_SYS_OSCMR |= AT91C_OSC32K_RC_EN;

    // Wait internal RC Startup Time for clock stabilization (software loop).
    WaitTimeInUs(BOARD_MCK, T_ST_SLCK_RC_IN_US);

    // Switch from 32768 Hz oscillator to internal RC by setting the bit OSC32K_SEL to 0.
    *AT91C_SYS_SYS_OSCMR &= (0xFFFFFFFF ^ AT91C_OSC32K_SEL);

    TRACE_INFO("The slow clock is now the internal RC oscillator\n\r");
}

//------------------------------------------------------------------------------
/// by pass the 32kHz oscillator (*** not supported in CAP7 ***)
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// set Slow Clock Mode
//------------------------------------------------------------------------------
void SLCK_UtilSetSlowClockMode(unsigned int timeInSlowClockMode)
{
    unsigned int oldPll;
    unsigned int oldMck;
    unsigned int timeout = 0;

    // Save previous values for PLL A and Master Clock configuration
    oldPll = AT91C_BASE_CKGR->CKGR_PLLAR;
    oldMck = AT91C_BASE_PMC->PMC_MCKR;

    // Slow clock is selected for Master Clock
    // 32kKz / 64 = 500Hz
    // PCK = 500Hz, MCK = 500 Hz
    AT91C_BASE_PMC->PMC_MCKR = AT91C_PMC_CSS_SLOW_CLK | AT91C_PMC_PRES_CLK_64;
    timeout = 0;
    while ( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) && timeout++ < CLOCK_TIMEOUT);

    // Stop PLL A
    // MULA: PLL A Multiplier 0 = The PLL A is deactivated.
    AT91C_BASE_CKGR->CKGR_PLLAR = 0x00003f00;

    // Stop Main Oscillator
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR & (~AT91C_CKGR_MOSCEN);

    // Wait a while. The clock is at 500Hz...
    while( timeInSlowClockMode-- );
    // End !

    // Restart Main Oscillator
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR | (AT91C_CKGR_OSCOUNT & (0x32<<8) );
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR | (AT91C_CKGR_MOSCEN);

    // Restart PLL A
    AT91C_BASE_CKGR->CKGR_PLLAR = oldPll;
    timeout = 0;
    while( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_LOCKA) && timeout++ < CLOCK_TIMEOUT);

    // Selection of Master Clock MCK (so Processor Clock PCK)
    AT91C_BASE_PMC->PMC_MCKR = oldMck;
    timeout = 0;
    while( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) && timeout++ < CLOCK_TIMEOUT);

    // Reconfigure DBGU
    TRACE_CONFIGURE(DBGU_STANDARD, 115200, BOARD_MCK);
}

//------------------------------------------------------------------------------
/// get the slow clock frequency
//------------------------------------------------------------------------------
unsigned int SLCK_UtilGetFreq(void)
{
    unsigned int freq = 0;

    SLCK_UtilSetSlowClockMode(0);

    if(AT91C_BASE_PMC->PMC_MCFR & (1<<16)) {
        freq = BOARD_MAINOSC / (AT91C_BASE_PMC->PMC_MCFR & 0x0000FFFF);
        freq *= 16;
    }
    return freq;
}

//#endif //#if defined(at91cap7)
#elif defined(at91cap9)
//------------------------------------------------------------------------------
/// Return 1 if the slow clock is 32k
//------------------------------------------------------------------------------
unsigned char SLCK_Is32k(void)
{
    return ((*AT91C_SYS_SLCKSEL & AT91C_SLCKSEL_OSCSEL) != 0);
}

//------------------------------------------------------------------------------
/// Configure the 32kHz oscillator for the slow clock
//------------------------------------------------------------------------------
void SLCK_RCto32k(void)
{
    // Check that the master clock has a different source than slow clock. If no,
    if( (AT91C_BASE_PMC->PMC_MCKR & AT91C_PMC_CSS) == 0)
    {
        TRACE_WARNING("The master clock use the slow clock. " \
            "Not possible to change Slow clock\n\r");
        return;
    }

    // Check that the slow clock source is RC
    if( SLCK_Is32k() )
    {
        TRACE_WARNING("The slow clock is already the external 32.768kHz crystal\n\r");
        return;
    }

    // Enable the 32,768 Hz oscillator by setting the bit OSC32EN to 1.
    *AT91C_SYS_SLCKSEL |= AT91C_SLCKSEL_OSC32EN;

    // Wait 32,768 Hz Startup Time for clock stabilization (software loop).
    WaitTimeInMs(BOARD_MCK*2, /*T_ST_SLCK_32K_IN_MS*/10);

    // Switch from internal RC to 32,768 Hz oscillator by setting the bit OSCSEL to 1.
    *AT91C_SYS_SLCKSEL |= AT91C_SLCKSEL_OSCSEL;

    // Wait 5 slow clock cycles for internal resynchronization.
    WaitTimeInUs(BOARD_MCK*2, TIME_5_CYCLES_32K_IN_US);

    // Disable the RC oscillator by setting the bit RCEN to 0.
    *AT91C_SYS_SLCKSEL &= (0xFFFFFFFF ^ AT91C_SLCKSEL_RCEN);

    TRACE_INFO("The slow clock is now the external 32.768kHz crystal\n\r");
}


//------------------------------------------------------------------------------
/// Configure the RC oscillator for the slow clock
//------------------------------------------------------------------------------
void SLCK_32ktoRC(void)
{
    // Check that the master clock has a different source than slow clock.
    if( (AT91C_BASE_PMC->PMC_MCKR & AT91C_PMC_CSS) == 0)
    {
        TRACE_WARNING("The master clock use the slow clock. " \
            "Not possible to change Slow clock\n\r");
        return;
    }

    // Check that the slow clock source is RC
    if( !SLCK_Is32k() )
    {
        TRACE_WARNING("The slow clock is already the internal RC oscillator\n\r");
        return;
    }

    // Enable the internal RC oscillator by setting the bit RCEN to 1
    *AT91C_SYS_SLCKSEL |= AT91C_SLCKSEL_RCEN;

    // Wait internal RC Startup Time for clock stabilization (software loop).
    WaitTimeInUs(BOARD_MCK*2, T_ST_SLCK_RC_IN_US);

    // Switch from 32768 Hz oscillator to internal RC by setting the bit OSCSEL to 0.
    *AT91C_SYS_SLCKSEL &= (0xFFFFFFFF ^ AT91C_SLCKSEL_OSCSEL);

    // Wait 5 slow clock cycles for internal resynchronization.
    WaitTimeInUs(BOARD_MCK*2, TIME_5_CYCLES_RC_IN_US);

    // Disable the 32768 Hz oscillator by setting the bit OSC32EN to 0.
    *AT91C_SYS_SLCKSEL &= (0xFFFFFFFF ^ AT91C_SLCKSEL_OSC32EN);

    TRACE_INFO("The slow clock is now the internal RC oscillator\n\r");
}

//------------------------------------------------------------------------------
/// by pass the 32kHz oscillator
//------------------------------------------------------------------------------
void SLCK_bypass32Kosc(void)
{
    // Enable the bypass path OSC32BYP bit set to 1
    *AT91C_SYS_SLCKSEL |= AT91C_SLCKSEL_OSC32BYP;

    // Disable the 32,768 Hz oscillator by setting the bit OSC32EN to 0
    *AT91C_SYS_SLCKSEL &= (0xFFFFFFFF ^ AT91C_SLCKSEL_OSC32EN);
}

//------------------------------------------------------------------------------
/// set Slow Clock Mode
//------------------------------------------------------------------------------
void SLCK_UtilSetSlowClockMode(unsigned int timeInSlowClockMode)
{
    unsigned int oldPll;
    unsigned int oldMck;
    unsigned int timeout = 0;

    // Save previous values for PLL A and Master Clock configuration
    oldPll = AT91C_BASE_CKGR->CKGR_PLLAR;
    oldMck = AT91C_BASE_PMC->PMC_MCKR;

    // Slow clock is selected for Master Clock
    // 32kKz / 64 = 500Hz
    // PCK = 500Hz, MCK = 250 Hz
    AT91C_BASE_PMC->PMC_MCKR = AT91C_PMC_CSS_SLOW_CLK | AT91C_PMC_PRES_CLK_64 | AT91C_PMC_MDIV_2;
    timeout = 0;
    while ( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) && timeout++ < CLOCK_TIMEOUT);

    // Stop PLL A
    // MULA: PLL A Multiplier 0 = The PLL A is deactivated.
    AT91C_BASE_CKGR->CKGR_PLLAR = 0x00003f00;

    // Stop Main Oscillator
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR & (~AT91C_CKGR_MOSCEN);

    // Wait a while. The clock is at 500Hz...
    while( timeInSlowClockMode-- );
    // End !

    // Restart Main Oscillator
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR | (AT91C_CKGR_OSCOUNT & (0x32<<8) );
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR | (AT91C_CKGR_MOSCEN);

    // Restart PLL A
    AT91C_BASE_CKGR->CKGR_PLLAR = oldPll;
    timeout = 0;
    while( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_LOCKA) && timeout++ < CLOCK_TIMEOUT);

    // Selection of Master Clock MCK (so Processor Clock PCK)
    AT91C_BASE_PMC->PMC_MCKR = oldMck;
    timeout = 0;
    while( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) && timeout++ < CLOCK_TIMEOUT);

    // Reconfigure DBGU
    TRACE_CONFIGURE(DBGU_STANDARD, 115200, BOARD_MCK);
}

//------------------------------------------------------------------------------
/// get the slow clock frequency
//------------------------------------------------------------------------------
unsigned int SLCK_UtilGetFreq(void)
{
    unsigned int freq = 0;

    SLCK_UtilSetSlowClockMode(0);

    if(AT91C_BASE_PMC->PMC_MCFR & (1<<16)) {
        freq = BOARD_MAINOSC / (AT91C_BASE_PMC->PMC_MCFR & 0x0000FFFF);
        freq *= 16;
    }
    return freq;
}

//#endif //#if defined(at91cap9)
#elif defined(at91sam3u)

//------------------------------------------------------------------------------
/// Return 1 if the slow clock is 32k
//------------------------------------------------------------------------------
unsigned char SLCK_Is32k(void)
{
    return ((AT91C_BASE_SUPC->SUPC_SR & AT91C_SUPC_SR_OSCSEL_CRYST) != 0);
}

//------------------------------------------------------------------------------
/// Configure the 32kHz oscillator for the slow clock
//------------------------------------------------------------------------------
void SLCK_RCto32k(void)
{
    unsigned int timeout = 0;

    // Check that the master clock has a different source than slow clock. If no,
    if( (AT91C_BASE_PMC->PMC_MCKR & AT91C_PMC_CSS) == 0)
    {
        TRACE_WARNING("The master clock use the slow clock. " \
            "Not possible to change Slow clock\n\r");
        return;
    }

    // Check that the slow clock source is RC
    if( SLCK_Is32k() )
    {
        TRACE_WARNING("The slow clock is already the external 32.768kHz crystal\n\r");
        return;
    }

    // Select the internal RC oscillator by set  XTALSEL to 1.
    AT91C_BASE_SUPC->SUPC_CR = AT91C_SUPC_CR_XTALSEL_CRYSTAL_SEL | (0xA5 << 24);
    timeout = 0;
    while (!(AT91C_BASE_SUPC->SUPC_SR & AT91C_SUPC_SR_OSCSEL_CRYST) && (timeout++ < CLOCK_TIMEOUT));

    TRACE_INFO("The slow clock is now the external 32.768kHz crystal\n\r");
}

//------------------------------------------------------------------------------
/// Configure the RC oscillator for the slow clock
//------------------------------------------------------------------------------
void SLCK_32ktoRC(void)
{
    TRACE_INFO("Switch to the internal RC oscillator is not possible for SAM3U!!!\n\r");
}

//------------------------------------------------------------------------------
/// by pass the 32kHz oscillator
//------------------------------------------------------------------------------
void SLCK_bypass32Kosc(void)
{
    // Enable the bypass path OSCBYPASS by set to 1.
    AT91C_BASE_SUPC->SUPC_MR |= AT91C_SUPC_MR_OSCBYPASS_BYPASS;
}

void SLCK_UtilSetSlowClockMode(unsigned int timeInSlowClockMode)
{
    unsigned int oldPll;
    unsigned int oldMck;
    unsigned int timeout = 0;

    // Save previous values for PLL A and Master Clock configuration
    oldPll = AT91C_BASE_CKGR->CKGR_PLLAR;
    oldMck = AT91C_BASE_PMC->PMC_MCKR;

    // Slow clock is selected for Master Clock
    // 32kKz / 64 = 500Hz
    // PCK = 500Hz, MCK = 500 Hz
    AT91C_BASE_PMC->PMC_MCKR = (oldMck & AT91C_PMC_PRES) |  AT91C_PMC_CSS_SLOW_CLK;
    timeout = 0;
    while ( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) && timeout++ < CLOCK_TIMEOUT);

    // Stop PLL A
    // MULA: PLL A Multiplier 0 = The PLL A is deactivated.
    AT91C_BASE_CKGR->CKGR_PLLAR = 0x00003f00;

    // Stop Main Oscillator
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR & (~AT91C_CKGR_MOSCXTEN);

    // Wait a while. The clock is at 500Hz...
    while( timeInSlowClockMode-- );
    // End !

    // Restart Main Oscillator
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR | (AT91C_CKGR_MOSCXTST & (0x32<<8) );
    AT91C_BASE_CKGR->CKGR_MOR = AT91C_BASE_CKGR->CKGR_MOR | (AT91C_CKGR_MOSCXTEN);

    // Restart PLL A
    AT91C_BASE_CKGR->CKGR_PLLAR = oldPll;
    timeout = 0;
    while( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_LOCKA) && timeout++ < CLOCK_TIMEOUT);

    // Selection of Master Clock MCK (so Processor Clock PCK)
    AT91C_BASE_PMC->PMC_MCKR = oldMck;
    timeout = 0;
    while( !(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) && timeout++ < CLOCK_TIMEOUT);

    // Reconfigure DBGU
    TRACE_CONFIGURE(DBGU_STANDARD, 115200, BOARD_MCK);
}

//------------------------------------------------------------------------------
/// get the slow clock frequency
//------------------------------------------------------------------------------
unsigned int SLCK_UtilGetFreq(void)
{
    unsigned int freq = 0;

    SetFlashWaitState(3);
    SLCK_UtilSetSlowClockMode(0);

    if(AT91C_BASE_PMC->PMC_MCFR & (1<<16)) {
        freq = BOARD_MAINOSC / (AT91C_BASE_PMC->PMC_MCFR & 0x0000FFFF);
        freq *= 16;
    }
    return freq;
}

#endif // endof at91sam3u

