/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2009, 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 "board.h"

/*----------------------------------------------------------------------------
 *        Types
 *----------------------------------------------------------------------------*/

/*
 * \brief Describes a possible clock configuration (processor clock & master clock),
 * including the necessary register values.
 */
typedef struct _ClockConfiguration
{

    /** Processor clock frequency (in MHz). */
    uint16_t pck;
    /** Master clock frequency (in MHz). */
    uint16_t mck;
    /** CKGR_PLL reqister value. */
    uint32_t pllr;
    /** PMC_MCKR register value. */
    uint32_t mckr;
} ClockConfiguration ;

/*----------------------------------------------------------------------------
 *        Local variables
 *----------------------------------------------------------------------------*/

/* Clock configurations for the AT91SAM3S4-EK */
#define CKGR_MUL_SHIFT         16
#define CKGR_PLLCOUNT_SHIFT     8
#define CKGR_DIV_SHIFT          0

/* Clock configuration for the AT91SAM3S */
static const ClockConfiguration clockConfigurations[] = {

    /* PCK = 24 MHz, MCK = 24 MHz
     * PCK = 12000000 * (7+1) / 2 / 2 = 24 MHz
     */
    {24, 24, (CKGR_PLLAR_STUCKTO1 | (7 << CKGR_MUL_SHIFT) \
        | (0x3f << CKGR_PLLCOUNT_SHIFT) \
        | (2 << CKGR_DIV_SHIFT)),
        ( PMC_MCKR_PRES_CLK_2 | PMC_MCKR_CSS_PLLA_CLK)},
    /* PCK = 48 MHz, MCK = 48 MHz
     * PCK = 12000000 * (7+1) / 1 / 2 = 48 MHz
     */
    {48, 48, (CKGR_PLLAR_STUCKTO1 | (7 << CKGR_MUL_SHIFT) \
        | (0x3f << CKGR_PLLCOUNT_SHIFT) \
        | (1 << CKGR_DIV_SHIFT)),
        ( PMC_MCKR_PRES_CLK_2 | PMC_MCKR_CSS_PLLA_CLK)},
    /* PCK = 64 MHz, MCK = 64 MHz
     * PCK = 12000000 * (15+1) / 3 / 1 = 64 MHz
     */
    {64, 64, (CKGR_PLLAR_STUCKTO1 | (15 << CKGR_MUL_SHIFT) \
        | (0x3f << CKGR_PLLCOUNT_SHIFT) \
        | (3 << CKGR_DIV_SHIFT)),
        ( PMC_MCKR_PRES_CLK | PMC_MCKR_CSS_PLLA_CLK)}
};

/* Number of available clock configurations */
#define NB_CLOCK_CONFIGURATION (sizeof(clockConfigurations)/sizeof(clockConfigurations[0]))

/* Current clock configuration */
uint32_t currentConfig = 0; /* 0 have to be the default configuration */

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Sets the specified clock configuration.
 *
 * \param configuration  Index of the configuration to set.
 */
void CLOCK_SetConfig(uint8_t configuration)
{
    TRACE_DEBUG("Setting clock configuration #%d ... ", configuration);
    currentConfig = configuration;

    /* Switch to main oscillator in two operations */
    PMC->PMC_MCKR = (PMC->PMC_MCKR & (uint32_t)~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK;
    while ((PMC->PMC_SR & PMC_SR_MCKRDY) == 0);

    /* Configure PLL */
    PMC->CKGR_PLLAR = clockConfigurations[configuration].pllr;
    while ((PMC->PMC_SR & PMC_SR_LOCKA) == 0);

    /* Configure master clock in two operations */
    PMC->PMC_MCKR = (clockConfigurations[configuration].mckr & (uint32_t)~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK;
    while ((PMC->PMC_SR & PMC_SR_MCKRDY) == 0);
    PMC->PMC_MCKR = clockConfigurations[configuration].mckr;
    while ((PMC->PMC_SR & PMC_SR_MCKRDY) == 0);

    /* UART reconfiguration */
    UART_Configure(115200, clockConfigurations[configuration].mck*1000000);
    TRACE_DEBUG("done.\n\r");
}

/**
 * \brief Display the user menu on the terminal.
 */
void CLOCK_DisplayMenu(void)
{
    uint32_t i;

    printf("\n\rMenu Clock configuration:\n\r");
    for (i = 0; i < NB_CLOCK_CONFIGURATION; i++) {

        printf("  %u: Set PCK = %3u MHz, MCK = %3u MHz   %s\n\r",
               (unsigned int)i,
               (unsigned int)clockConfigurations[i].pck,
               (unsigned int)clockConfigurations[i].mck,
               (currentConfig==i)?"(curr)":"");
    }
}

/**
 * \brief Get the current MCK
 */
uint16_t CLOCK_GetCurrMCK(void)
{
    return clockConfigurations[currentConfig].mck;
}

/**
 * \brief Get the current PCK
 */
uint16_t CLOCK_GetCurrPCK(void)
{
    return clockConfigurations[currentConfig].pck;
}

/**
 * \brief Change clock configuration.
 */
void CLOCK_UserChangeConfig(void)
{
    uint8_t key = 0;

    while (1)
    {
        CLOCK_DisplayMenu();
        key = UART_GetChar();

        if ((key >= '0') && (key <= ('0' + NB_CLOCK_CONFIGURATION - 1)))
        {
            CLOCK_SetConfig(key - '0');
            break;
        }
    }
}

