/****************************************************************************
 * arch/avr/src/sam34/sam4l_clockconfig.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * This file is derived from nuttx/arch/avr/src/at32uc3/at32uc3_clkinit.c
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdbool.h>

#include <arch/irq.h>
#include <arch/board/board.h>

#include "up_arch.h"

#include "up_internal.h"
#include "chip/sam4l_pm.h"
#include "chip/sam4l_scif.h"
#include "chip/sam4l_bpm.h"
#include "chip/sam4l_bscif.h"
#include "chip/sam4l_flashcalw.h"

#include "sam4l_periphclks.h"
#include "sam_clockconfig.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Configuration ************************************************************/

#ifndef CONFIG_ARCH_RAMFUNCS
# error "CONFIG_ARCH_RAMFUNCS must be defined"
#endif

/* Board/Clock Setup *******************************************************/
/* Verify dividers */

#if ((BOARD_CPU_SHIFT > BOARD_PBA_SHIFT) || (BOARD_CPU_SHIFT > BOARD_PBB_SHIFT) || \
     (BOARD_CPU_SHIFT > BOARD_PBC_SHIFT) || (BOARD_CPU_SHIFT > BOARD_PBD_SHIFT))
#  error BOARD_PBx_SHIFT must be greater than or equal to BOARD_CPU_SHIFT
#endif

/* Nominal frequencies in on-chip RC oscillators.  These may frequencies
 * may vary with temperature changes.
 */

#define SAM_RCSYS_FREQUENCY            115000 /* Nominal frequency of RCSYS (Hz) */
#define SAM_RC32K_FREQUENCY             32768 /* Nominal frequency of RC32K (Hz) */
#define SAM_RC80M_FREQUENCY          80000000 /* Nominal frequency of RC80M (Hz) */
#define SAM_RCFAST4M_FREQUENCY        4000000 /* Nominal frequency of RCFAST4M (Hz) */
#define SAM_RCFAST8M_FREQUENCY        8000000 /* Nominal frequency of RCFAST8M (Hz) */
#define SAM_RCFAST12M_FREQUENCY      12000000 /* Nominal frequency of RCFAST12M (Hz) */
#define SAM_RC1M_FREQUENCY            1000000 /* Nominal frequency of RC1M (Hz) */

/* Oscillator 0.  This might be the system clock or the source clock for
 * either PLL0 or DFPLL.  It might also be needed if OSC0 is the source
 * clock for GCLK9.
 *
 * By selecting CONFIG_SAM34_OSC0, you can also force the clock to be enabled
 * at boot time.
 */

#if defined(CONFIG_SAM34_OSC0) || defined(BOARD_SYSCLK_SOURCE_OSC0) || \
    defined(BOARD_DFLL0_SOURCE_OSC0) || defined(BOARD_PLL0_SOURCE_OSC0) || \
    defined(BOARD_GLCK9_SOURCE_OSC0)
#  define NEED_OSC0                  1
#endif

#ifdef NEED_OSC0
#  if !defined(BOARD_OSC0_STARTUP_US)
#    error BOARD_OSC0_STARTUP_US is not defined
#  elif BOARD_OSC0_STARTUP_US == 0
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_0
#    define SAM_OSC0_STARTUP_TIMEOUT 8
#  elif BOARD_OSC0_STARTUP_US <= 557
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_64
#    define SAM_OSC0_STARTUP_TIMEOUT 80
#  elif BOARD_OSC0_STARTUP_US <= 1100
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_128
#    define SAM_OSC0_STARTUP_TIMEOUT 160
#  elif BOARD_OSC0_STARTUP_US <= 18000
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_2K
#    define SAM_OSC0_STARTUP_TIMEOUT 2560
#  elif BOARD_OSC0_STARTUP_US <= 36000
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_4K
#    define SAM_OSC0_STARTUP_TIMEOUT 5120
#  elif BOARD_OSC0_STARTUP_US <= 71000
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_8K
#    define SAM_OSC0_STARTUP_TIMEOUT 10240
#  elif BOARD_OSC0_STARTUP_US <= 143000
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_16K
#    define SAM_OSC0_STARTUP_TIMEOUT 20480
#  elif BOARD_OSC0_STARTUP_US <= 285000
#    define SAM_OSC0_STARTUP_VALUE   SCIF_OSCCTRL0_STARTUP_32K
#    define SAM_OSC0_STARTUP_TIMEOUT 40960
#  else
#    error BOARD_OSC0_STARTUP_US is out of range
#  endif

#  ifdef BOARD_OSC0_ISXTAL
#    define SAM_OSC0_MODE_VALUE      SCIF_OSCCTRL0_MODE
#    if BOARD_OSC0_FREQUENCY < 2000000
#      define SAM_OSC0_GAIN_VALUE    SCIF_OSCCTRL0_GAIN(0)
#    elif BOARD_OSC0_FREQUENCY < 4000000
#      define SAM_OSC0_GAIN_VALUE    SCIF_OSCCTRL0_GAIN(1)
#    elif BOARD_OSC0_FREQUENCY < 8000000
#      define SAM_OSC0_GAIN_VALUE    SCIF_OSCCTRL0_GAIN(2)
#    elif BOARD_OSC0_FREQUENCY < 16000000
#      define SAM_OSC0_GAIN_VALUE    SCIF_OSCCTRL0_GAIN(3)
#    else
#      define SAM_OSC0_GAIN_VALUE    ((0x1u << 4) | SCIF_OSCCTRL0_GAIN(0))
#    endif
#  else
#    define SAM_OSC0_MODE_VALUE      0
#    define SAM_OSC0_GAIN_VALUE      0
#  endif
#endif

/* OSC32.  The 32K oscillator may be the source clock for DFPLL0 or
 * the source clock for GLK9 that might be used to driver PLL0.
 *
 * By selecting CONFIG_SAM34_OSC32K, you can also force the clock to be
 * enabled at boot time.  OSC32 may needed by other devices as well
 * (AST, WDT, PICUART, RTC).
 */

#if defined(CONFIG_SAM34_OSC32K) || defined(BOARD_DFLL0_SOURCE_OSC32K) || \
    defined(BOARD_GLCK9_SOURCE_OSC32K)
#  define NEED_OSC32K                1
#endif

#ifdef NEED_OSC32K
#  if !defined(BOARD_OSC32_STARTUP_US)
#    error BOARD_OSC32_STARTUP_US is not defined
#  elif BOARD_OSC32_STARTUP_US == 0
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_0
#  elif BOARD_OSC32_STARTUP_US   <= 1100
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_128
#  elif BOARD_OSC32_STARTUP_US   <= 72300
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_8K
#  elif BOARD_OSC32_STARTUP_US   <= 143000
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_16K
#  elif BOARD_OSC32_STARTUP_US   <= 570000
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_64K
#  elif BOARD_OSC32_STARTUP_US   <= 1100000
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_128K
#  elif BOARD_OSC32_STARTUP_US   <= 2300000
#    define SAM_OSC32_STARTUP_VALUE  BSCIF_OSCCTRL32_STARTUP_256K
#  elif BOARD_OSC32_STARTUP_US   <= 4600000
#    define SAM_OSC32_STARTUP_VALUE BSCIF_OSCCTRL32_STARTUP_512K
#  else
#    error BOARD_OSC32_STARTUP_US is out of range
#  endif

#  ifdef BOARD_OSC32_ISXTAL
#    define SAM_OSC32_MODE_VALUE     BSCIF_OSCCTRL32_MODE_XTAL
#  else
#    define SAM_OSC32_MODE_VALUE     BSCIF_OSCCTRL32_MODE_EXTCLK
#  endif

#  ifndef BOARD_OSC32_SELCURR
#    define BOARD_OSC32_SELCURR      BSCIF_OSCCTRL32_SELCURR_300
#  endif
#endif

/* RC80M.  This might be the system clock or the source clock for the DFPLL
 * or it could be the source for GCLK9 that drives PLL0.
 *
 * By selecting CONFIG_SAM34_RC80M, you can also force the clock to be enabled
 * at boot time.
 */

#if defined(CONFIG_SAM34_RC80M) || defined(BOARD_SYSCLK_SOURCE_RC80M) || \
    defined(BOARD_DFLL0_SOURCE_RC80M) || defined(BOARD_GLCK9_SOURCE_RC80M)
#  define NEED_RC80M                 1
#endif

/* RCFAST.  The 12/8/4 fast RC oscillator may be used as the system clock
 * or as the source for GLCK9 that drives PLL0.
 * If not then, it may be enabled by setting the CONFIG_SAM34_RCFASTxM
 * configuration variable.
 */

#if defined(CONFIG_SAM34_RCFAST12M)
#  undef CONFIG_SAM34_RCFAST8M
#  undef CONFIG_SAM34_RCFAST4M
#elif defined(CONFIG_SAM34_RCFAST8M)
#  undef CONFIG_SAM34_RCFAST4M
#endif

#if defined(BOARD_SYSCLK_SOURCE_FCFAST12M)
#  if defined(CONFIG_SAM34_RCFAST8M) || defined(CONFIG_SAM34_RCFAST4M)
#    error BOARD_SYSCLK_SOURCE_FCFAST12M inconsistent with CONFIG_SAM34_RCFAST8/4M
#  endif
#  define NEED_RCFAST                1
#  define SAM_RCFAST_RANGE           SCIF_RCFASTCFG_FRANGE_12MHZ
#  define SAM_RCFAST_FREQUENCY       SAM_RCFAST12M_FREQUENCY
#elif defined(BOARD_SYSCLK_SOURCE_FCFAST8M)
#  if defined(CONFIG_SAM34_RCFAST12M) || defined(CONFIG_SAM34_RCFAST4M)
#    error BOARD_SYSCLK_SOURCE_FCFAST8M inconsistent with CONFIG_SAM34_RCFAST12/4M
#  endif
#  define NEED_RCFAST                1
#  define SAM_RCFAST_RANGE           SCIF_RCFASTCFG_FRANGE_8MHZ
#  define SAM_RCFAST_FREQUENCY       SAM_RCFAST8M_FREQUENCY
#elif defined(BOARD_SYSCLK_SOURCE_FCFAST4M)
#  if defined(CONFIG_SAM34_RCFAST12M) || defined(CONFIG_SAM34_RCFAST8M)
#    error BOARD_SYSCLK_SOURCE_FCFAST4M inconsistent with CONFIG_SAM34_RCFAST12/8M
#  endif
#  define NEED_RCFAST                1
#  define SAM_RCFAST_RANGE           SCIF_RCFASTCFG_FRANGE_4MHZ
#  define SAM_RCFAST_FREQUENCY       SAM_RCFAST4M_FREQUENCY
#elif defined(CONFIG_SAM34_RCFAST12M)
#  define NEED_RCFAST                1
#  define SAM_RCFAST_RANGE           SCIF_RCFASTCFG_FRANGE_12MHZ
#  define SAM_RCFAST_FREQUENCY       SAM_RCFAST12M_FREQUENCY
#elif defined(CONFIG_SAM34_RCFAST8M)
#  define NEED_RCFAST                1
#  define SAM_RCFAST_RANGE           SCIF_RCFASTCFG_FRANGE_8MHZ
#  define SAM_RCFAST_FREQUENCY       SAM_RCFAST8M_FREQUENCY
#elif defined(CONFIG_SAM34_RCFAST4M)
#  define NEED_RCFAST                1
#  define SAM_RCFAST_RANGE           SCIF_RCFASTCFG_FRANGE_4MHZ
#  define SAM_RCFAST_FREQUENCY       SAM_RCFAST4M_FREQUENCY
#endif

/* RC1M.  The 1M RC oscillator may be used as the system block or
 * may be the source clock for GLCK9 that drives PLL0
 *
 * By selecting CONFIG_SAM34_RC1M, you can also force the clock to be
 * enabled at boot time.
 */

#if defined(CONFIG_SAM34_RC1M) || defined(BOARD_SYSCLK_SOURCE_RC1M) || \
    defined(BOARD_GLCK9_SOURCE_RC1M)
#  define NEED_RC1M                  1
#endif

/* RC32K.  The 32KHz RC oscillator may be used as the input to DFLL0
 * or as the input to GCLK9 that drives PLL0.
 *
 * By selecting CONFIG_SAM34_RC32K, you can also force the clock to be
 * enabled at boot time.
 */

#if defined(CONFIG_SAM34_RC32K) || defined(BOARD_DFLL0_SOURCE_RC32K) || \
    defined(BOARD_GLCK9_SOURCE_RC32K)
#  define NEED_RC32K                 1
#endif

/* GCLK9.  May used as a source clock for PLL0 */

#ifdef BOARD_PLL0_SOURCE_GCLK9
#  define NEED_GLCK9                 1
#endif

#ifdef NEED_GLCK9
#  if defined(BOARD_GLCK9_SOURCE_RCSYS)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_RCSYS
#    define SAM_GCLK9_FREQUENCY      SAM_RCSYS_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_OSC32K)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_OSC32K
#    define SAM_GCLK9_FREQUENCY      BOARD_OSC32_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_DFLL0)
#    error BOARD_GLCK9_SOURCE_DFLL0 is not supported
#  elif defined(BOARD_GLCK9_SOURCE_OSC0)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_OSC0
#    define SAM_GCLK9_FREQUENCY      BOARD_OSC0_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_RC80M)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_RC80M
#    define SAM_GCLK9_FREQUENCY      SAM_RC80M_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_RCFAST)
#    error BOARD_GLCK9_SOURCE_RCFAST is not supported (needs RCFAST configuration)
#  elif defined(BOARD_GLCK9_SOURCE_RC1M)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_RC1M
#    define SAM_GCLK9_FREQUENCY      SAM_RCFAST_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_CPUCLK)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_CPUCLK
#    define SAM_GCLK9_FREQUENCY      BOARD_CPU_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_HSBCLK)
#    error BOARD_GLCK9_SOURCE_HSBCLK is not supported (REVISIT)
#  elif defined(BOARD_GLCK9_SOURCE_PBACLK)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_PBACLK
#    define SAM_GCLK9_FREQUENCY      BOARD_PBA_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_PBBCLK)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_PBBCLK
#    define SAM_GCLK9_FREQUENCY      BOARD_PBB_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_PBCCLK)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_PBCCLK
#    define SAM_GCLK9_FREQUENCY      BOARD_PBC_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_PBDCLK)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_PBDCLK
#    define SAM_GCLK9_FREQUENCY      BOARD_PBD_FREQUENCY
#  elif defined(BOARD_GLCK9_SOURCE_RC32K)
#    define SAM_GCLK9_SOURCE_VALUE   SCIF_GCCTRL_OSCSEL_RC32K
#    define SAM_GCLK9_FREQUENCY      SAM_RC32K_FREQUENCY
#  else
#    error Missing GCLK9 source
#  endif
#endif

/* PLL0 */

#ifdef BOARD_SYSCLK_SOURCE_PLL0
/* PLL0 source */

#  if defined(BOARD_PLL0_SOURCE_OSC0)
#    define SAM_PLL0_SOURCE            SCIF_PLL0_PLLOSC_OSC0
#    define SAM_PLL0_SOURCE_FREQUENCY  BOARD_OSC0_FREQUENCY
#  elif defined(BOARD_PLL0_SOURCE_GCLK9)
#    define SAM_PLL0_SOURCE            SCIF_PLL0_PLLOSC_GCLK9
#    define SAM_PLL0_SOURCE_FREQUENCY  SAM_GCLK9_FREQUENCY
#  else
#    error Missing PLL0 source
#  endif

/* PLL0 Multipler and Divider */

#  if !defined(BOARD_PLL0_MUL)
#    error BOARD_PLL0_MUL is not defined
#  elif BOARD_PLL0_MUL <= 2 || BOARD_PLL0_MUL > 16
#    error BOARD_PLL0_MUL is out of range
#  endif

#  if !defined(BOARD_PLL0_DIV)
#    error BOARD_PLL0_DIV is not defined
#  elif BOARD_PLL0_DIV < 1 || BOARD_PLL0_DIV > 15
#    error BOARD_PLL0_DIV is out of range
#  endif

/* PLL0 frequency ranges */

#  define SAM_PLL0_MIN_FREQUENCY      40000000
#  define SAM_PLL0_MAX_FREQUENCY     240000000

/* PLL0 VCO frequency */

#  define SAM_PLL0_VCO_DIV1_FREQUENCY \
    (SAM_PLL0_SOURCE_FREQUENCY * BOARD_PLL0_MUL / BOARD_PLL0_DIV)

#  if (SAM_PLL0_VCO_DIV1_FREQUENCY < SAM_PLL0_MIN_FREQUENCY) || \
      (SAM_PLL0_VCO_DIV1_FREQUENCY > SAM_PLL0_MAX_FREQUENCY)
#    error PLL0 VCO frequency is out of range
#  endif

/* PLL0 Options:
 *
 * PLL0 supports an option to divide the frequency output by 2.  We
 * will do this division to bring the internal VCO frequency up to the
 * minimum value
 *
 * PLL0 operates in two frequency ranges as determined by
 * SCIF_PLL0_PLLOPT_FVO:
 *
 * 0: 80MHz  < fvco < 180MHz
 * 1: 160MHz < fvco < 240MHz
 *
 * Select the correct frequncy range using the recommended threshold
 * value.
 */

#  if SAM_PLL0_VCO_DIV1_FREQUENCY < (2*SAM_PLL0_MIN_FREQUENCY) && BOARD_PLL0_MUL <= 8
#    define SAM_PLL0_VCO_FREQUENCY   (2 * SAM_PLL0_VCO_DIV1_FREQUENCY)
#    define SAM_PLL0_MUL             (2 * BOARD_PLL0_MUL)

#    if SAM_PLL0_VCO_FREQUENCY > (SAM_PLL0_VCO_RANGE_THRESHOLD / 2)
#      define SAM_PLL0_OPTIONS       (SCIF_PLL0_PLLOPT_DIV2 | SCIF_PLL0_PLLOPT_FVO)
#    else
#      define SAM_PLL0_OPTIONS       SCIF_PLL0_PLLOPT_DIV2
#    endif

#  else
#    define SAM_PLL0_VCO_FREQUENCY   SAM_PLL0_VCO_DIV1_FREQUENCY
#    define SAM_PLL0_MUL             BOARD_PLL0_MUL

#    if SAM_PLL0_VCO_FREQUENCY > SAM_PLL0_VCO_RANGE_THRESHOLD
#      define SAM_PLL0_OPTIONS       SCIF_PLL0_PLLOPT_FVO
#    else
#      define SAM_PLL0_OPTIONS       0
#    endif
#  endif
#endif

/* DFLL0 */

#ifdef BOARD_SYSCLK_SOURCE_DFLL0
/* DFLL0 reference clock */

#  if defined(BOARD_DFLL0_SOURCE_RCSYS)
#    define SAM_DFLLO_REFCLK         SCIF_GCCTRL_OSCSEL_RCSYS
#  elif defined(BOARD_DFLL0_SOURCE_OSC32K)
#    define SAM_DFLLO_REFCLK         SCIF_GCCTRL_OSCSEL_OSC32K
#  elif defined(BOARD_DFLL0_SOURCE_OSC0)
#    define SAM_DFLLO_REFCLK         SCIF_GCCTRL_OSCSEL_OSC0
#  elif defined(BOARD_DFLL0_SOURCE_RC80M)
#    define SAM_DFLLO_REFCLK         SCIF_GCCTRL_OSCSEL_RC80M
#  elif defined(BOARD_DFLL0_SOURCE_RC32K)
#    define SAM_DFLLO_REFCLK         SCIF_GCCTRL_OSCSEL_RC32K
#  else
#    error No DFLL0 source for reference clock defined
#  endif

#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: sam_picocache
 *
 * Description:
 *   Initialiaze the PICOCACHE.
 *
 ****************************************************************************/

#ifdef CONFIG_SAM34_PICOCACHE
static inline void sam_picocache(void)
{
  /* Enable clocking to the PICOCACHE */

  sam_hsb_enableperipheral(PM_HSBMASK_HRAMC1);
  sam_pbb_enableperipheral(PM_PBBMASK_HRAMC1);

  /* Enable the PICOCACHE and wait for it to become ready */

  putreg32(PICOCACHE_CTRL_CEN, SAM_PICOCACHE_CTRL);
  while ((getreg32(SAM_PICOCACHE_SR) & PICOCACHE_SR_CSTS) == 0);
}
#else
#  define sam_picocache()
#endif

/****************************************************************************
 * Name: sam_enableosc0
 *
 * Description:
 *   Initialiaze OSC0 settings per the definitions in the board.h file.
 *
 ****************************************************************************/

#ifdef NEED_OSC0
static inline void sam_enableosc0(void)
{
  uint32_t regval;

  /* Enable and configure OSC0 */

  regval = SAM_OSC0_STARTUP_VALUE | SAM_OSC0_GAIN_VALUE | SAM_OSC0_MODE_VALUE |
           SCIF_OSCCTRL0_OSCEN;
  putreg32(SCIF_UNLOCK_KEY(0xaa) | SCIF_UNLOCK_ADDR(SAM_SCIF_OSCCTRL0_OFFSET),
           SAM_SCIF_UNLOCK);
  putreg32(regval, SAM_SCIF_OSCCTRL0);

  /* Wait for OSC0 to be ready */

  while (getreg32(SAM_SCIF_PCLKSR) & SCIF_INT_OSC0RDY) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enableosc32
 *
 * Description:
 *   Initialiaze the 32KHz oscillator per settings in the board.h header
 *   file.
 *
 ****************************************************************************/

#ifdef NEED_OSC32K
static inline void sam_enableosc32(void)
{
  uint32_t regval;

  /* Set up the OSCCTRL32 register using settings from the board.h file.
   * Also  enable the oscillator and provide bother the 32KHz and 1KHz output.
   */

  regval = SAM_OSC32_STARTUP_VALUE | BOARD_OSC32_SELCURR | SAM_OSC32_MODE_VALUE |
           BSCIF_OSCCTRL32_EN1K |  BSCIF_OSCCTRL32_EN32K |
           BSCIF_OSCCTRL32_OSC32EN;

  putreg32(BSCIF_UNLOCK_KEY(0xaa) | BSCIF_UNLOCK_ADDR(SAM_BSCIF_OSCCTRL32_OFFSET),
           SAM_BSCIF_UNLOCK);
  putreg32(regval, SAM_BSCIF_OSCCTRL32);

  /* Wait for OSC32 to be ready */

  while ((getreg32(SAM_BSCIF_PCLKSR) & BSCIF_INT_OSC32RDY) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enablerc80m
 *
 * Description:
 *   Initialiaze the 80 MHz RC oscillator per settings in the board.h header
 *   file.
 *
 ****************************************************************************/

#ifdef NEED_RC80M
static inline void sam_enablerc80m(void)
{
  uint32_t regval;

  /* Configure and enable RC80M */

  regval = getreg32(SAM_SCIF_RC80MCR);
  putreg32(SCIF_UNLOCK_KEY(0xaa) | SCIF_UNLOCK_ADDR(SAM_SCIF_RC80MCR_OFFSET),
           SAM_SCIF_UNLOCK);
  putreg32(regval | SCIF_RC80MCR_EN, SAM_SCIF_RC80MCR);

  /* Wait for OSC32 to be ready */

  while (getreg32(SAM_SCIF_RC80MCR) & SCIF_RC80MCR_EN) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enablerc80m
 *
 * Description:
 *   Initialiaze the 12/8/4 RC fast oscillator per settings in the board.h
 *   header file.
 *
 ****************************************************************************/

#ifdef NEED_RCFAST
static inline void sam_enablercfast(void)
{
  uint32_t regval;

  /* Configure and enable RCFAST */

  regval  = getreg32(SAM_SCIF_RCFASTCFG);
  regval &= ~SCIF_RCFASTCFG_FRANGE_MASK;
  regval |= (SAM_RCFAST_RANGE | SCIF_RCFASTCFG_EN);

  putreg32(SCIF_UNLOCK_KEY(0xaa) | SCIF_UNLOCK_ADDR(SAM_SCIF_RCFASTCFG_OFFSET),
           SAM_SCIF_UNLOCK);
  putreg32(regval, SAM_SCIF_RCFASTCFG);

  /* Wait for RCFAST to be ready */

  while (getreg32(SAM_SCIF_RCFASTCFG) & SCIF_RCFASTCFG_EN) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enablerc1m
 *
 * Description:
 *   Initialiaze the 1M RC oscillator per settings in the board.h header
 *   file.
 *
 ****************************************************************************/

#ifdef NEED_RC1M
static inline void sam_enablerc1m(void)
{
  uint32_t regval;

  /* Configure and enable RC1M */

  regval  = getreg32(SAM_BSCIF_RC1MCR);
  regval &= ~BSCIF_RCFASTCFG_FRANGE_MASK;
  regval |= (SAM_RCFAST_RANGE | BSCIF_RCFASTCFG_EN);

  putreg32(BSCIF_UNLOCK_KEY(0xaa) | BSCIF_UNLOCK_ADDR(SAM_BSCIF_RC1MCR_OFFSET),
           SAM_BSCIF_UNLOCK);
  putreg32(regval  | BSCIF_RC1MCR_CLKOEN, SAM_BSCIF_RC1MCR);

  /* Wait for RCFAST to be ready */

  while (getreg32(SAM_BSCIF_RC1MCR) & BSCIF_RC1MCR_CLKOEN) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enablerc32k
 *
 * Description:
 *   Initialiaze the 23KHz RC oscillator per settings in the board.h header
 *   file.
 *
 ****************************************************************************/

#ifdef NEED_RC32K
static inline void sam_enablerc32k(void)
{
  uint32_t regval;

  /* Configure and enable RC32K */

  regval  = getreg32(SAM_BSCIF_RC32KCR);
  putreg32(BSCIF_UNLOCK_KEY(0xaa) | BSCIF_UNLOCK_ADDR(SAM_BSCIF_RC32KCR_OFFSET),
           SAM_BSCIF_UNLOCK);
  putreg32(regval | BSCIF_RC32KCR_EN32K | BSCIF_RC32KCR_EN, SAM_BSCIF_RC32KCR);

  /* Wait for RCFAST to be ready */

  while (getreg32(SAM_BSCIF_RC32KCR) & BSCIF_RC32KCR_EN) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enableglck9
 *
 * Description:
 *   Enable GLCK9.
 *
 ****************************************************************************/

#ifdef NEED_GLCK9
static inline void sam_enableglck9(void)
{
  /* Enable the generic clock using the source specified in the board.h
   * file.  No division is used so that the GCLK9 frequency is the same
   * as the source frequency.
   */

  putreg32(SAM_GCLK9_SOURCE_VALUE | SCIF_GCCTRL_CEN, SAM_SCIF_GCCTRL9);
}
#endif

/****************************************************************************
 * Name: sam_enablepll0 (and its helper sam_pll0putreg())
 *
 * Description:
 *   Initialiaze PLL0 settings per the definitions in the board.h file.
 *
 ****************************************************************************/

#ifdef BOARD_SYSCLK_SOURCE_PLL0
static inline void sam_pll0putreg(uint32_t regval, uint32_t regaddr,
                                  uint32_t regoffset)
{
  putreg32(SCIF_UNLOCK_KEY(0xaa) | SCIF_UNLOCK_ADDR(regoffset),
           SAM_SCIF_UNLOCK);
  putreg32(regval, regaddr);
}

static inline void sam_enablepll0(void)
{
  uint32_t regval;

  /* Clear the PLL0 control register */

  sam_pll0putreg(0, SAM_SCIF_PLL0, SAM_SCIF_PLL0_OFFSET);

  /* Write the selected options */

  regval  = getreg32(SAM_SCIF_PLL0);
  regval &= SCIF_PLL0_PLLOPT_MASK;
  regval |= SAM_PLL0_OPTIONS;
  sam_pll0putreg(regval, SAM_SCIF_PLL0, SAM_SCIF_PLL0_OFFSET);

  /* Set up the multiers and dividers */

  regval  = getreg32(SAM_SCIF_PLL0);
  regval &= ~(SCIF_PLL0_PLLOSC_MASK | SCIF_PLL0_PLLDIV_MASK | SCIF_PLL0_PLLMUL_MASK);
  regval |= ((SAM_PLL0_MUL - 1) << SCIF_PLL0_PLLMUL_SHIFT) |
            (BOARD_DFLL0_DIV << SCIF_PLL0_PLLDIV_SHIFT) |
            SCIF_PLL0_PLLCOUNT_MAX | SAM_PLL0_SOURCE;
  sam_pll0putreg(regval, SAM_SCIF_PLL0, SAM_SCIF_PLL0_OFFSET);

  /* And, finally, enable PLL0 */

  regval  = getreg32(SAM_SCIF_PLL0);
  regval |= SCIF_PLL_PLLEN;
  sam_pll0putreg(regval, SAM_SCIF_PLL0, SAM_SCIF_PLL0_OFFSET);

  /* Wait for PLL0 to become locked */

  while ((getreg32(SAM_SCIF_PCLKSR) & SCIF_INT_PLL0LOCK) == 0);
}
#endif

/****************************************************************************
 * Name: sam_enabledfll0 (and its helper sam_dfll0_putreg32())
 *
 * Description:
 *   Initialiaze DFLL0 settings per the definitions in the board.h file.
 *
 ****************************************************************************/

#ifdef BOARD_SYSCLK_SOURCE_DFLL0
static inline void sam_dfll0_putreg32(uint32_t regval, uint32_t regaddr,
                                     uint32_t regoffset)
{
  /* Wait until DFLL0 is completes the last setting */

  while ((getreg32(SAM_SCIF_PCLKSR) & SCIF_INT_DFLL0RDY) == 0);

  /* Then unlock the register and write the next value */

  putreg32(SCIF_UNLOCK_KEY(0xaa) | SCIF_UNLOCK_ADDR(regoffset),
           SAM_SCIF_UNLOCK);
  putreg32(regval, regaddr);
}

static inline void sam_enabledfll0(void)
{
  uint32_t regval;
  uint32_t conf;

  /* Set up generic clock source with specified reference clock
   * and divider.
   */

  putreg32(0, SAM_SCIF_GCCTRL0);

  /* Set the generic clock 0 source */

  regval  = getreg32(SAM_SCIF_GCCTRL0);
  regval &= ~SCIF_GCCTRL_OSCSEL_MASK;
  regval |= SAM_DFLLO_REFCLK;
  putreg32(regval, SAM_SCIF_GCCTRL0);

  /* Get the generic clock 0 divider */

  regval  = getreg32(SAM_SCIF_GCCTRL0);
  regval &= ~(SCIF_GCCTRL_DIVEN | SCIF_GCCTRL_DIV_MASK);

#if BOARD_DFLL0_DIV > 1
  regval |= SCIF_GCCTRL_DIVEN;
  regval |= SCIF_GCCTRL_DIV(((BOARD_DFLL0_DIV + 1) / 2) - 1);
#endif

  putreg32(regval, SAM_SCIF_GCCTRL0);

  /* Sync before reading a dfll conf register */

  putreg32(SCIF_DFLL0SYNC_SYNC, SAM_SCIF_DFLL0SYNC);
  while ((getreg32(SAM_SCIF_PCLKSR) & SCIF_INT_DFLL0RDY) == 0);

  /* Select Closed Loop Mode */

  conf  = getreg32(SAM_SCIF_DFLL0CONF);
  conf &= ~SCIF_DFLL0CONF_RANGE_MASK;
  conf |= SCIF_DFLL0CONF_MODE;

  /* Select the DFLL0 Frequency Range */

#if BOARD_DFLL0_FREQUENCY < SCIF_DFLL0CONF_MAX_RANGE3
  conf |= SCIF_DFLL0CONF_RANGE3;
#elif BOARD_DFLL0_FREQUENCY < SCIF_DFLL0CONF_MAX_RANGE2
  conf |= SCIF_DFLL0CONF_RANGE2;
#elif BOARD_DFLL0_FREQUENCY < SCIF_DFLL0CONF_MAX_RANGE1
  conf |= SCIF_DFLL0CONF_RANGE1;
#else
  conf |= SCIF_DFLL0CONF_RANGE0;
#endif

  /* Enable the reference generic clock 0 */

  regval  = getreg32(SAM_SCIF_GCCTRL0);
  regval |= SCIF_GCCTRL_CEN;
  putreg32(regval, SAM_SCIF_GCCTRL0);

  /* Enable DFLL0.  Here we assume DFLL0RDY because the DFLL was disabled
   * before this function was called.
   */

  putreg32(SCIF_UNLOCK_KEY(0xaa) | SCIF_UNLOCK_ADDR(SAM_SCIF_DFLL0CONF_OFFSET),
           SAM_SCIF_UNLOCK);
  putreg32(SCIF_DFLL0CONF_EN, SAM_SCIF_DFLL0CONF);

  /* Configure DFLL0.  Note that now we do have to wait for DFLL0RDY before
   * every write.
   *
   * Set the initial coarse and fine step lengths to 4. If this is set
   * too high, DFLL0 may fail to lock.
   */

  sam_dfll0_putreg32(SCIF_DFLL0STEP_CSTEP(4) | SCIF_DFLL0STEP_FSTEP(4),
                     SAM_SCIF_DFLL0STEP,
                     SAM_SCIF_DFLL0STEP_OFFSET);

  /* Set the DFLL0 multipler register */

  sam_dfll0_putreg32(BOARD_DFLL0_MUL, SAM_SCIF_DFLL0MUL,
                     SAM_SCIF_DFLL0MUL_OFFSET);

  /* Set the multipler and spread spectrum generator control registers */

  sam_dfll0_putreg32(0, SAM_SCIF_DFLL0SSG, SAM_SCIF_DFLL0SSG_OFFSET);

  /* Finally, set the DFLL0 configuration */

  sam_dfll0_putreg32(conf | SCIF_DFLL0CONF_EN,
                     SAM_SCIF_DFLL0CONF, SAM_SCIF_DFLL0CONF_OFFSET);

  /* Wait until we are locked on the fine value */

  while ((getreg32(SAM_SCIF_PCLKSR) & SCIF_INT_DFLL0LOCKF) == 0);
}
#endif

/****************************************************************************
 * Name: sam_setdividers
 *
 * Description:
 *   Configure derived clocks.
 *
 ****************************************************************************/

static inline void sam_setdividers(void)
{
  uint32_t cpusel;
  uint32_t pbasel;
  uint32_t pbbsel;
  uint32_t pbcsel;
  uint32_t pbdsel;

  /* Get the register setting for each divider value */

#if BOARD_CPU_SHIFT > 0
  cpusel = (PM_CPUSEL(BOARD_CPU_SHIFT - 1)) | PM_CPUSEL_DIV;
#else
  cpusel = 0;
#endif

#if BOARD_PBA_SHIFT > 0
  pbasel = (PM_PBSEL(BOARD_PBA_SHIFT - 1)) | PM_PBSEL_DIV;
#else
  pbasel = 0;
#endif

#if BOARD_PBB_SHIFT  >0
  pbbsel = (PM_PBSEL(BOARD_PBB_SHIFT - 1)) | PM_PBSEL_DIV;
#else
  pbbsel = 0;
#endif

#if BOARD_PBC_SHIFT > 0
  pbcsel = (PM_PBSEL(BOARD_PBC_SHIFT - 1)) | PM_PBSEL_DIV;
#else
  pbcsel = 0;
#endif

#if BOARD_PBD_SHIFT > 0
  pbdsel = (PM_PBSEL(BOARD_PBD_SHIFT - 1)) | PM_PBSEL_DIV;
#else
  pbdsel = 0;
#endif

  /* Then set the divider values. */

  putreg32(PM_UNLOCK_KEY(0xaa) | PM_UNLOCK_ADDR(SAM_PM_CPUSEL_OFFSET), SAM_PM_UNLOCK);
  putreg32(cpusel, SAM_PM_CPUSEL);

  putreg32(PM_UNLOCK_KEY(0xaa) | PM_UNLOCK_ADDR(SAM_PM_PBASEL_OFFSET), SAM_PM_UNLOCK);
  putreg32(pbasel, SAM_PM_PBASEL);

  putreg32(PM_UNLOCK_KEY(0xaa) | PM_UNLOCK_ADDR(SAM_PM_PBBSEL_OFFSET), SAM_PM_UNLOCK);
  putreg32(pbbsel, SAM_PM_PBBSEL);

  putreg32(PM_UNLOCK_KEY(0xaa) | PM_UNLOCK_ADDR(SAM_PM_PBCSEL_OFFSET), SAM_PM_UNLOCK);
  putreg32(pbcsel, SAM_PM_PBCSEL);

  putreg32(PM_UNLOCK_KEY(0xaa) | PM_UNLOCK_ADDR(SAM_PM_PBDSEL_OFFSET), SAM_PM_UNLOCK);
  putreg32(pbdsel, SAM_PM_PBDSEL);
}

/****************************************************************************
 * Name: sam_enable_fastwakeup
 *
 * Description:
 *   Enable FLASH fast wakeup mode.
 *
 ****************************************************************************/

static inline void sam_enable_fastwakeup(void)
{
  uint32_t regval;

  regval  = getreg32(SAM_BPM_PMCON);
  regval |= BPM_PMCON_FASTWKUP;
  putreg32(BPM_UNLOCK_KEY(0xaa) | BPM_UNLOCK_ADDR(SAM_BPM_PMCON_OFFSET),
          SAM_BPM_UNLOCK);
  putreg32(regval, SAM_BPM_PMCON);
}

/****************************************************************************
 * Name: set_flash_waitstate
 *
 * Description:
 *   Setup one or two FLASH wait states.
 *
 ****************************************************************************/

static inline void set_flash_waitstate(bool waitstate)
{
  uint32_t regval;

  /* Set or clear the FLASH wait state (FWS) bit in the FLASH control
   * register (FCR).
   */

  regval = getreg32(SAM_FLASHCALW_FCR);

  if (waitstate)
    {
      regval |= FLASHCALW_FCR_FWS;
    }
  else
    {
      regval &= ~FLASHCALW_FCR_FWS;
    }

  putreg32(regval, SAM_FLASHCALW_FCR);
}

/****************************************************************************
 * Name: sam_flash_readmode
 *
 * Description:
 *   Send a FLASH command to enable to disable high speed FLASH read mode.
 *
 ****************************************************************************/

static inline void sam_flash_readmode(uint32_t command)
{
  uint32_t regval;

  /* Make sure that any previous FLASH operation is completed */

  while ((getreg32(SAM_FLASHCALW_FSR) & FLASHCALW_FSR_FRDY) == 0);

  /* Write the specified FLASH command to the FCMD register */

  regval  = getreg32(SAM_FLASHCALW_FCMD);
  regval &= ~FLASHCALW_FCMD_CMD_MASK;
  regval |= (FLASHCALW_FCMD_KEY | command);
  putreg32(regval, SAM_FLASHCALW_FCMD);

  /* Wait for this FLASH operation to complete */

  while ((getreg32(SAM_FLASHCALW_FSR) & FLASHCALW_FSR_FRDY) == 0);
}

/****************************************************************************
 * Name: sam_flash_config
 *
 * Description:
 *   Configure FLASH read mode and wait states.
 *
 *   Maximum CPU frequency for 0 and 1 FLASH wait states (FWS) in various modes
 *   (Table 42-30 in the big data sheet).
 *
 *     ------- ------------------- ---------- ----------
 *     Power     Flash Read Mode     Flash     Maximum
 *     Sclaing                        Wait    Operating
 *     Mode    HSEN HSDIS FASTWKUP   States   Frequency
 *     ------- ---- ----- -------- ---------- ----------
 *       PS0          X       X        1        12MHz
 *       " "          X                0        18MHz
 *       " "          X                1        36MHz
 *       PS1          X       X        1        12MHz
 *       " "          X                0         8MHz
 *       " "          X                1        12MHz
 *       PS2     X                     0        24Mhz
 *       " "     X                     1        48MHz
 *     ------- ---- ----- -------- ---------- ----------
 *
 ****************************************************************************/

static inline void sam_flash_config(uint32_t cpuclock, uint32_t psm, bool fastwkup)
{
  bool waitstate;
  uint32_t command;

#ifdef CONFIG_SAM34_FLASH_HSEN
  /* High speed flash read mode (with power scaling mode == 2).  Set one
   * wait state if the CPU clock frequency exceeds the threshold value
   * and enable high speed read mode.
   */

  waitstate = (cpuclock > FLASH_MAXFREQ_PS2_HSEN_FWS0);
  command   = FLASHCALW_FCMD_CMD_HSEN;
#else
  /* Assume that we will select no wait states and that we will disable high-
   * speed read mode.
   */

  waitstate = false;
  command   = FLASHCALW_FCMD_CMD_HSDIS;

  /* Handle power scaling mode == 0 FLASH configuration */

  if (psm == 0)
    {
      /* Power scaling mode 0.  We need to set wait state the CPU clock if
       * the CPU frequency exceeds a threshold.
       */

      if (cpuclock > FLASH_MAXFREQ_PS0_HSDIS_FWS0)
        {
          /* Set one wait state */

          waitstate = true;

          /* Enable high speed read mode if the frequency exceed the maximum
           * for the low speed configuration.  This mode is not documented
           * in the data sheet, but I see that they do this in some Atmel
           * code examples.
           */

          if (cpuclock > FLASH_MAXFREQ_PS0_HSDIS_FWS1)
            {
              /* Enable high speed read mode. */

              command = FLASHCALW_FCMD_CMD_HSEN;
            }
        }

      /* The is below the threshold that requires one wait state.  But we
       * have to check a few more things.
       */

      else
        {
          /* If FLASH wake-up mode is selected and the we are in the lower
           * operating frequency for this mode, then set 1 waitate and
           * disable high speed read mode.
           */

          if ((fastwkup == true) &&
              (cpuclock <= FLASH_MAXFREQ_PS1_HSDIS_FASTWKUP_FWS1))
            {
              /* Set one wait state */

              waitstate = true;
            }
        }
    }

  /* Otherwise, this is power scaling mode 1 */

  else /* if (psm == 1) */
    {
      /* If we are in the lower operating frequency range, then select
       * zero wait states.  Otherwise, select one wait state.
       */

      if (cpuclock > FLASH_MAXFREQ_PS1_HSDIS_FWS0)
        {
          /* Set one wait state */

          waitstate = true;
        }
    }

#endif

  /* Set 0 or 1 waitstates */

  set_flash_waitstate(waitstate);

  /* Enable/disable the high-speed read mode. */

  sam_flash_readmode(command);
}

/****************************************************************************
 * Name: sam_mainclk
 *
 * Description:
 *   Select the main clock.
 *
 ****************************************************************************/

static inline void sam_mainclk(uint32_t mcsel)
{
  uint32_t regval;

  regval = getreg32(SAM_PM_MCCTRL);
  regval &= ~PM_MCCTRL_MCSEL_MASK;
  regval |= mcsel;

  putreg32(PM_UNLOCK_KEY(0xaa) | PM_UNLOCK_ADDR(SAM_PM_MCCTRL_OFFSET),
           SAM_PM_UNLOCK);
  putreg32(regval, SAM_PM_MCCTRL);
}

/****************************************************************************
 * Name: sam_setpsm (and its helper, sam_instantiatepsm())
 *
 * Description:
 *   Switch to the selected power scaling mode.
 *
 ****************************************************************************/

static __ramfunc__ void sam_instantiatepsm(uint32_t regval)
{
  /* Set the BMP PCOM register (containing the new power scaling mode) */

  putreg32(BPM_UNLOCK_KEY(0xaa) | BPM_UNLOCK_ADDR(SAM_BPM_PMCON_OFFSET),
           SAM_BPM_UNLOCK);
  putreg32(regval, SAM_BPM_PMCON);

  /* Wait for new power scaling mode to become active.  There should be
   * timeout on this wait.
   */

  while ((getreg32(SAM_BPM_SR) & BPM_INT_PSOK) == 0);
}

static inline void sam_setpsm(uint32_t psm)
{
  uint32_t regval;

  /* Setup the PMCON register content fo the new power scaling mode */

  regval  = getreg32(SAM_BPM_PMCON);
  regval &= ~BPM_PMCON_PS_MASK;
  regval |= (psm | BPM_PMCON_PSCM | BPM_PMCON_PSCREQ);

  /* Then call the RAMFUNC sam_setpsm() to set the new power scaling mode */

  sam_instantiatepsm(regval);
}

/****************************************************************************
 * Name: sam_usbclock
 *
 * Description:
 *   Setup the USBB GCLK.
 *
 ****************************************************************************/

#ifdef CONFIG_USBDEV
static inline void sam_usbclock(void)
{
  uint32_t regval = 0;

#if defined(SAM_CLOCK_USB_PLL0) || defined(SAM_CLOCK_USB_PLL1)
  regval |= PM_GCCTRL_PLLSEL;
#endif
#if defined(SAM_CLOCK_USB_OSC1) || defined(SAM_CLOCK_USB_PLL1)
  regval |= PM_GCCTRL_OSCSEL;
#endif
#if SAM_CLOCK_USB_DIV > 0


  u_avr32_pm_gcctrl.GCCTRL.diven  = diven;
  u_avr32_pm_gcctrl.GCCTRL.div    = div;
#endif
  putreg32(regval, SAM_PM_GCCTRL(SAM_PM_GCLK_USBB))

  /* Enable USB GCLK */

  regval = getreg32(SAM_PM_GCCTRL(SAM_PM_GCLK_USBB))
  regval |= PM_GCCTRL_CEN;
  putreg32(regval, SAM_PM_GCCTRL(SAM_PM_GCLK_USBB))
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: sam_clockconfig
 *
 * Description:
 *   Called to initialize the SAM3/4.  This does whatever setup is needed to
 *   put the SoC in a usable state.  This includes the initialization of
 *   clocking using the settings in board.h.
 *
 ****************************************************************************/

void sam_clockconfig(void)
{
  uint32_t psm;
  bool fastwkup;

  /* Enable clocking to the PICOCACHE */

  sam_picocache();

  /* Configure dividers for derived clocks.  These divider definitions must
   * be provided in the board.h header file.
   */

  sam_setdividers();

  /* Select a power scaling mode and possible fast wakeup so that we get the
   * best possible flash performance.  The following table shows the maximum
   * CPU frequency for 0 and 1 FLASH wait states (FWS) in various modes
   * (Table 42-30 in the big data sheet).
   *
   *   ------- ------------------- ---------- ----------
   *   Power     Flash Read Mode     Flash     Maximum
   *   Sclaing                        Wait    Operating
   *   Mode    HSEN HSDIS FASTWKUP   States   Frequency
   *   ------- ---- ----- -------- ---------- ----------
   *     PS0          X       X        1        12MHz
   *     " "          X                0        18MHz
   *     " "          X                1        36MHz
   *     PS1          X       X        1        12MHz
   *     " "          X                0         8MHz
   *     " "          X                1        12MHz
   *     PS2     X                     0        24Mhz
   *     " "     X                     1        48MHz
   *   ------- ---- ----- -------- ---------- ----------
   */

#ifdef CONFIG_SAM34_FLASH_HSEN
  /* The high speed FLASH mode has been enabled.  Select power scaling
   * mode 2, no fast wakeup.
   */

  psm      = BPM_PMCON_PS2;
  fastwkup = false;

#elif BOARD_CPU_FREQUENCY <= FLASH_MAXFREQ_PS1_HSDIS_FWS1
  /* Not high speed mode and frequency is below the thrshold.  We can go to
   * power scaling mode 1.
   */

  psm = BPM_PMCON_PS1;

#  if BOARD_CPU_FREQUENCY > FLASH_MAXFREQ_PS1_HSDIS_FWS0
  /* We need to enable fast wakeup */

  sam_enable_fastwakeup()
  fastwkup = true;
#  endif
#else
  /* Power scaling mode 0, disable high speed mode, no fast wakeup */

  psm      = BPM_PMCON_PS0;
  fastwkup = false;
#endif

  /* Enable clock sources:
   *
   * OSC0:  Might by the system clock or the source clock for PLL0 or DFLL0
   * OSC32: Might be source clock for DFLL0
   */

#ifdef NEED_OSC0
  /* Enable OSC0 using the settings in board.h */

  sam_enableosc0();
#endif

#ifdef NEED_OSC32K
  /* Enable the 32KHz oscillator using the settings in board.h */

  sam_enableosc32();
#endif

#ifdef NEED_RC80M
  /* Enable the 32KHz oscillator using the settings in board.h */

  sam_enablerc80m();
#endif

#ifdef NEED_RCFAST
  /* Enable the 12/8/4MHz RC fast oscillator using the settings in board.h */

  sam_enablercrcfast();
#endif

#ifdef NEED_RC1M
  /* Enable the 1MHz RC oscillator using the settings in board.h */

  sam_enablerc1m();
#endif

#ifdef NEED_RC32K
  /* Enable the 32KHz RC oscillator using the settings in board.h */

  sam_enablerc32k();
#endif

#ifdef NEED_GLCK9
  /* Enable the GLCK9 */

  sam_enableglck9();
#endif

  /* Switch to the system clock selected by the settings in the board.h
   * header file.
   */

#if defined(BOARD_SYSCLK_SOURCE_RCSYS)
  /* Since this function only executes at power up, we know that we are
   * already running from RCSYS.
   */

  // sam_mainclk(PM_MCCTRL_MCSEL_RCSYS);
#elif defined(BOARD_SYSCLK_SOURCE_OSC0)

  /* Configure FLASH read mode and wait states */

  sam_flash_config(BOARD_CPU_FREQUENCY, psm, fastwkup);

  /* Then switch the main clock to OSC0 */

  sam_mainclk(PM_MCCTRL_MCSEL_OSC0);

#elif defined(BOARD_SYSCLK_SOURCE_PLL0)

  /* Enable PLL0 using the settings in board.h */

  sam_enablepll0();

  /* Configure FLASH read mode and wait states */

  sam_flash_config(BOARD_CPU_FREQUENCY, psm, fastwkup);

  /* Then switch the main clock to PLL0 */

  sam_mainclk(PM_MCCTRL_MCSEL_PLL);

#elif defined(BOARD_SYSCLK_SOURCE_DFLL0)

  /* Enable PLL0 using the settings in board.h */

  sam_enabledfll0();

  /* Configure FLASH read mode and wait states */

  sam_flash_config(BOARD_CPU_FREQUENCY, psm, fastwkup);

  /* Then switch the main clock to DFLL0 */

  sam_mainclk(PM_MCCTRL_MCSEL_DFLL);

#elif defined(BOARD_SYSCLK_SOURCE_RC80M)

  /* Configure FLASH read mode and wait states */

  sam_flash_config(BOARD_CPU_FREQUENCY, psm, fastwkup);

  /* Then switch the main clock to RCM80 */

  sam_mainclk(PM_MCCTRL_MCSEL_RC80M);

#elif defined(BOARD_SYSCLK_SOURCE_FCFAST12M) || defined(BOARD_SYSCLK_SOURCE_FCFAST8M) || \
      defined(BOARD_SYSCLK_SOURCE_FCFAST4M)

  /* Configure FLASH read mode and wait states */

  sam_flash_config(BOARD_CPU_FREQUENCY, psm, fastwkup);

  /* Then switch the main clock to RCFAST */

  sam_mainclk(PM_MCCTRL_MCSEL_RCFAST);

#elif defined(BOARD_SYSCLK_SOURCE_RC1M)

  /* Configure FLASH read mode and wait states */

  sam_flash_config(BOARD_CPU_FREQUENCY, psm, fastwkup);

  /* Then switch the main clock to RC1M */

  sam_mainclk(PM_MCCTRL_MCSEL_RC1M);

#else
#  error "No SYSCLK source provided"
#endif

  /* Switch to the selected power scaling mode */

  sam_setpsm(psm);

  /* Enable all selected peripheral cloks */

  sam_init_periphclks();

  /* Configure clocking to the USB controller */

#ifdef CONFIG_USBDEV
  sam_usbc_enableclk();
#endif
}
