/****************************************************************************
 * arch/arm/src/samdl/samd_clockconfig.c
 *
 *   Copyright (C) 2014-2015 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * References:
 *   1. "Atmel SAM D20J / SAM D20G / SAM D20E ARM-Based Microcontroller
 *      Datasheet", 42129J–SAM–12/2013
 *   2. "Atmel SAM D21E / SAM D21G / SAM D21J SMART ARM-Based Microcontroller
 *      Datasheet", Atmel-42181E–SAM-D21_Datasheet–02/2015
 *   3. Atmel sample code for the SAMD20.  This code has an ASF license
 *      with is compatible with the NuttX BSD license, but includes the
 *      provision that this code not be used in non-Atmel products.  That
 *      sample code was used only as a reference so I believe that only the
 *      NuttX BSD license applies.
 *
 * 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 <assert.h>

#include "up_arch.h"

#include "chip/samd_pm.h"
#include "chip/samd_sysctrl.h"
#include "chip/samd_gclk.h"
#include "chip/samd_nvmctrl.h"
#include "sam_fuses.h"
#include "sam_gclk.h"

#include <arch/board/board.h>

#include "samd_periphclks.h"
#include "sam_clockconfig.h"

#if defined(CONFIG_ARCH_FAMILY_SAMD20) || defined(CONFIG_ARCH_FAMILY_SAMD21)

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

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

/****************************************************************************
 * Private Data
 ****************************************************************************/
/* This structure describes the configuration of every enabled GCLK */

#ifdef BOARD_GCLK_ENABLE
static const struct sam_gclkconfig_s g_gclkconfig[] =
{
  /* GCLK generator 0 (Main Clock) */

  {
    .gclk       = 0,
#ifdef BOARD_GCLK0_RUN_IN_STANDBY
    .runstandby = true,
#endif
#ifdef BOARD_GCLK0_OUTPUT_ENABLE
    .output     = true,
#endif
    .prescaler  = BOARD_GCLK0_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK0_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }

  /* GCLK generator 1 */

#ifdef BOARD_GCLK1_ENABLE
  ,
  {
    .gclk       = 1,
#ifdef BOARD_GCLK1_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK1_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK1_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK1_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 2 (RTC) */

#ifdef BOARD_GCLK2_ENABLE
  ,
  {
    .gclk       = 2,
#ifdef BOARD_GCLK2_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK2_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK2_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK2_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 3 */

#ifdef BOARD_GCLK3_ENABLE
  ,
  {
    .gclk       = 3,
#ifdef BOARD_GCLK3_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK3_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK3_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK3_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 4 */

#ifdef BOARD_GCLK4_ENABLE
  ,
  {
    .gclk       = 4,
#ifdef BOARD_GCLK4_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK4_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK4_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK4_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 5 */

#ifdef BOARD_GCLK5_ENABLE
  ,
  {
    .gclk       = 5,
#ifdef BOARD_GCLK5_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK5_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK5_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK5_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 6 */

#ifdef BOARD_GCLK6_ENABLE
  ,
  {
    .gclk       = 6,
#ifdef BOARD_GCLK6_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK6_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK6_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK6_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 7 */

#ifdef BOARD_GCLK7_ENABLE
  ,
  {
    .gclk       = 7,
#ifdef BOARD_GCLK7_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK7_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK7_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK7_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif

  /* GCLK generator 8 */

#ifdef BOARD_GCLK8_ENABLE
  ,
  {
    .gclk       = 8,
#ifdef BOARD_GCLK8_RUN_IN_STANDBY
    .runstandby = true;
#endif
#ifdef BOARD_GCLK8_OUTPUT_ENABLE
    .output     = true;
#endif
    .prescaler  = BOARD_GCLK8_PRESCALER,
    .clksrc     = (uint8_t)(BOARD_GCLK8_CLOCK_SOURCE >> GCLK_GENCTRL_SRC_SHIFT),
  }
#endif
};

#define NGCLKS_ENABLED (sizeof(g_gclkconfig) / sizeof(struct sam_gclkconfig_s))
#endif

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

/****************************************************************************
 * Name: sam_flash_waitstates
 *
 * Description:
 *   Set the FLASH wait states based on settings in the board.h header file
 *   Depends on:
 *
 *     BOARD_FLASH_WAITSTATES - Number of wait states
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static inline void sam_flash_waitstates(void)
{
  uint32_t regval;

  regval = getreg32(SAM_NVMCTRL_CTRLB);
  regval &= ~NVMCTRL_CTRLB_RWS_MASK;
  regval |= NVMCTRL_CTRLB_RWS(BOARD_FLASH_WAITSTATES);
  putreg32(regval, SAM_NVMCTRL_CTRLB);
}

/****************************************************************************
 * Name: sam_xosc_config
 *
 * Description:
 *   Configure XOSC based on settings in the board.h header file
 *   Depends on:
 *
 *     BOARD_XOSC_ENABLE       - Boolean (defined / not defined)
 *     BOARD_XOSC_FREQUENCY    - In Hz
 *     BOARD_XOSC_STARTUPTIME  - See SYSCTRL_XOSC_STARTUP_* definitions
 *     BOARD_XOSC_ISCRYSTAL    - Boolean (defined / not defined)
 *     BOARD_XOSC_AMPGC        - Boolean (defined / not defined)
 *     BOARD_XOSC_ONDEMAND     - Boolean (defined / not defined)
 *     BOARD_XOSC_RUNINSTANDBY - Boolean (defined / not defined)
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef BOARD_XOSC_ENABLE
static inline void sam_xosc_config(void)
{
  uint16_t regval;

  /* Configure the XOSC clock */

  regval = BOARD_XOSC_STARTUPTIME

#ifdef BOARD_XOSC_ISCRYSTAL
  /* XOSC is a crystal */

  regval |= SYSCTRL_XOSC_XTALEN;
#endif

#ifdef BOARD_XOSC_AMPGC
  /* Enable automatic gain control */

  regval |= SYSCTRL_XOSC_AMPGC;

#else
  /* Set gain if automatic gain control is not selected */

#if BOARD_XOSC_FREQUENCY <= 2000000
  regval |= SYSCTRL_XOSC_GAIN_2MHZ;
#elif BOARD_XOSC_FREQUENCY <= 4000000
  regval |= SYSCTRL_XOSC_GAIN_4MHZ;
#elif BOARD_XOSC_FREQUENCY <= 8000000
  regval |= SYSCTRL_XOSC_GAIN_8MHZ;
#elif BOARD_XOSC_FREQUENCY <= 16000000
  regval |= SYSCTRL_XOSC_GAIN_16MHZ;
#elif BOARD_XOSC_FREQUENCY <= 30000000
  regval |= SYSCTRL_XOSC_GAIN_30MHZ;
#else
#  error BOARD_XOSC_FREQUENCY out of range
#endif
#endif /* BOARD_XOSC_AMPGC */

#ifdef BOARD_XOSC_ONDEMAND
  regval |= SYSCTRL_XOSC_ONDEMAND;
#endif

#ifdef BOARD_XOSC_RUNINSTANDBY
  regval |= SYSCTRL_XOSC_RUNSTDBY;
#endif

  putreg16(regval, SAM_SYSCTRL_XOSC);

  /* Then enable the XOSC clock */

  regval |= SYSCTRL_XOSC_ENABLE;
  putreg16(regval, SAM_SYSCTRL_XOSC);
}
#else
#  define sam_xosc_config()
#endif

/****************************************************************************
 * Name: sam_xosc32k_config
 *
 * Description:
 *   Configure XOSC32K based on settings in the board.h header file.
 *   Depends on:
 *
 *     BOARD_XOSC32K_ENABLE       - Boolean (defined / not defined)
 *     BOARD_XOSC32K_FREQUENCY    - In Hz
 *     BOARD_XOSC32K_STARTUPTIME  - See SYSCTRL_XOSC32K_STARTUP_* definitions
 *     BOARD_XOSC32K_ISCRYSTAL    - Boolean (defined / not defined)
 *     BOARD_XOSC32K_AAMPEN       - Boolean (defined / not defined)
 *     BOARD_XOSC32K_EN1KHZ       - Boolean (defined / not defined)
 *     BOARD_XOSC32K_EN32KHZ      - Boolean (defined / not defined)
 *     BOARD_XOSC32K_ONDEMAND     - Boolean (defined / not defined)
 *     BOARD_XOSC32K_RUNINSTANDBY - Boolean (defined / not defined)
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef BOARD_XOSC32K_ENABLE
static inline void sam_xosc32k_config(void)
{
  uint16_t regval;

  /* Configure XOSC32K */

  regval = BOARD_XOSC32K_STARTUPTIME

#ifdef BOARD_XOSC32K_ISCRYSTAL
  regval |= SYSCTRL_XOSC32K_XTALEN;
#endif

#ifdef BOARD_XOSC32K_AAMPEN
  regval |= SYSCTRL_XOSC32K_AAMPEN;
#endif

#ifdef BOARD_XOSC32K_EN1KHZ
  regval |= SYSCTRL_XOSC32K_EN1K;
#endif

#ifdef BOARD_XOSC32K_EN32KHZ
  regval |= SYSCTRL_XOSC32K_EN32K;
#endif

#ifdef BOARD_XOSC32K_ONDEMAND
  regval |= SYSCTRL_XOSC32K_ONDEMAND;
#endif

#ifdef BOARD_XOSC32K_RUNINSTANDBY
  regval |= SYSCTRL_XOSC32K_RUNSTDBY;
#endif

  putreg16(regval, SAM_SYSCTRL_XOSC32K);

  /* Then enable the XOSC clock */

  regval |= SYSCTRL_XOSC32K_ENABLE;
  putreg16(regval, SAM_SYSCTRL_XOSC32K);
}
#else
#  define sam_xosc32k_config()
#endif

/****************************************************************************
 * Name: sam_osc32k_config
 *
 * Description:
 *   Configure OSC32K based on settings in the board.h header file.
 *   Depends on:
 *
 *     BOARD_OSC32K_ENABLE       - Boolean (defined / not defined)
 *     BOARD_OSC32K_FREQUENCY    - In Hz
 *     BOARD_OSC32K_STARTUPTIME  - See SYSCTRL_OSC32K_STARTUP_* definitions
 *     BOARD_OSC32K_EN1KHZ       - Boolean (defined / not defined)
 *     BOARD_OSC32K_EN32KHZ      - Boolean (defined / not defined)
 *     BOARD_OSC32K_ONDEMAND     - Boolean (defined / not defined)
 *     BOARD_OSC32K_RUNINSTANDBY - Boolean (defined / not defined)
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef BOARD_OSC32K_ENABLE
static inline void sam_osc32k_config(void)
{
  uint32_t regval;
  uint32_t calib;

  /* Recover OSC32K calibration data from OTP "fuse" memory */

  regval  = getreg32(SYSCTRL_FUSES_OSC32KCAL_ADDR);
  calib   = (regval & SYSCTRL_FUSES_OSC32KCAL_MASK) >> SYSCTRL_FUSES_OSC32KCAL_SHIFT;
  regval  = calib << SYSCTRL_OSC32K_CALIB_SHIFT;

  /* Configure OSC32K */

  regval |= BOARD_OSC32K_STARTUPTIME;

#ifdef BOARD_OSC32K_EN1KHZ
  regval |= SYSCTRL_OSC32K_EN1K;
#endif

#ifdef BOARD_OSC32K_EN32KHZ
  regval |= SYSCTRL_OSC32K_EN32K;
#endif

#ifdef BOARD_OSC32K_ONDEMAND
  regval |= SYSCTRL_OSC32K_ONDEMAND;
#endif

#ifdef BOARD_OSC32K_RUNINSTANDBY
  regval |= SYSCTRL_OSC32K_RUNSTDBY;
#endif

  putreg32(regval, SAM_SYSCTRL_OSC32K);

  /* Then enable OSC32K */

  regval |= SYSCTRL_OSC32K_ENABLE;
  putreg32(regval, SAM_SYSCTRL_OSC32K);
}
#else
#  define sam_osc32k_config()
#endif

/****************************************************************************
 * Name: sam_osc8m_config
 *
 * Description:
 *   Configure OSC8M based on settings in the board.h header file.
 *   Depends on:
 *
 *     BOARD_OSC8M_PRESCALER     - See SYSCTRL_OSC8M_PRESC_DIV* definitions
 *     BOARD_OSC8M_ONDEMAND      - Boolean (defined / not defined)
 *     BOARD_OSC8M_RUNINSTANDBY  - Boolean (defined / not defined)
 *
 *   On any reset the synchronous clocks start to their initial state:
 *
 *     OSC8M is enabled and divided by 8
 *     GCLK_MAIN uses OSC8M as source
 *     CPU and BUS clocks are undivided
 *
 *   The reset state of the OSC8M register is:
 *
 *     FFxx CCCC CCCC CCCC xxxx xxPP ORxx xxEx
 *     xx00 xxxx xxxx xxxx 0000 0011 1000 0010
 *
 *     FRANGE   FF      Loaded from FLASH calibration at startup
 *     CALIB    CCC...C Loaded from FLASH calibration at startup
 *     PRESC    PP      3 = Divide by 8
 *     ONDEMAND O       1
 *     RUNSTBY  R       0
 *     ENABLE   1       1
 *
 *   NOTE that since we are running from OSC8M, it cannot be disable!
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static inline void sam_osc8m_config(void)
{
  uint32_t regval;

  /* Configure OSC8M */

  regval  = getreg32(SAM_SYSCTRL_OSC8M);
  regval &= ~(SYSCTRL_OSC8M_PRESC_MASK | SYSCTRL_OSC8M_ONDEMAND |
              SYSCTRL_OSC8M_RUNSTDBY);

  /* Select the prescaler */

  regval |= (BOARD_OSC8M_PRESCALER | SYSCTRL_OSC8M_ENABLE);

#ifdef BOARD_OSC8M_ONDEMAND
  /* Select on-demand oscillator controls */

  regval |= SYSCTRL_OSC8M_ONDEMAND;
#endif

#ifdef BOARD_OSC8M_RUNINSTANDBY
  /* The oscillator continues to run in standby sleep mode  */

  regval |= SYSCTRL_OSC8M_RUNSTDBY;
#endif

  /* Set the OSC8M configuration */

  putreg32(regval, SAM_SYSCTRL_OSC8M);
}

/****************************************************************************
 * Name: sam_dfll_config
 *
 * Description:
 *   Configure the DFLL based on settings in the board.h header file.
 *   Depends on:
 *
 *   BOARD_DFLL_OPENLOOP            - Boolean (defined / not defined)
 *   BOARD_DFLL_TRACKAFTERFINELOCK  - Boolean (defined / not defined)
 *   BOARD_DFLL_KEEPLOCKONWAKEUP    - Boolean (defined / not defined)
 *   BOARD_DFLL_ENABLECHILLCYCLE    - Boolean (defined / not defined)
 *   BOARD_DFLL_QUICKLOCK           - Boolean (defined / not defined)
 *   BOARD_DFLL_ONDEMAND            - Boolean (defined / not defined)
 *   BOARD_DFLL_COARSEVALUE         - Value
 *   BOARD_DFLL_FINEVALUE           - Value
 *
 * Open Loop mode only:
 *   BOARD_DFLL_COARSEVALUE         - Value
 *   BOARD_DFLL_FINEVALUE           - Value
 *
 * Closed loop mode only:
 *   BOARD_DFLL_SRCGCLKGEN          - GCLK index
 *   BOARD_DFLL_MULTIPLIER          - Value
 *   BOARD_DFLL_MAXCOARSESTEP       - Value
 *   BOARD_DFLL_MAXFINESTEP         - Value
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef BOARD_DFLL_ENABLE
static inline void sam_dfll_config(void)
{
  uint16_t control;
  uint32_t regval;

  /* Set up the DFLL control register */

  control  = SYSCTRL_DFLLCTRL_ENABLE;   /* Enable the DFLL */

#ifndef BOARD_DFLL_OPENLOOP
  control |= SYSCTRL_DFLLCTRL_MODE;     /* Closed loop mode */
#endif

#ifndef BOARD_DFLL_TRACKAFTERFINELOCK
  control |= SYSCTRL_DFLLCTRL_STABLE;   /* FINE calibration fixed after a fine lock */
#endif

#ifndef BOARD_DFLL_KEEPLOCKONWAKEUP
  control |= SYSCTRL_DFLLCTRL_LLAW;     /* Lose lock after wake */
#endif

#ifndef BOARD_DFLL_ENABLECHILLCYCLE
  control |= SYSCTRL_DFLLCTRL_CCDIS;    /* Chill cycle disable */
#endif

#ifndef BOARD_DFLL_QUICKLOCK
  control |= SYSCTRL_DFLLCTRL_QLDIS; /* Quick lock disable */
#endif

  /* Then enable the DFLL (with ONDEMAND set to zero). */

  putreg16(control, SAM_SYSCTRL_DFLLCTRL);

  /* Wait for the DFLL to synchronize */

  while ((getreg32(SAM_SYSCTRL_PCLKSR) & SYSCTRL_INT_DFLLRDY) == 0);

  /* Set up the open loop mode multiplier register */

#ifndef BOARD_DFLL_OPENLOOP
  regval = SYSCTRL_DFLLMUL_CSTEP(BOARD_DFLL_MAXCOARSESTEP) |
           SYSCTRL_DFLLMUL_FSTEP(BOARD_DFLL_MAXFINESTEP) |
           SYSCTRL_DFLLMUL_MUL(BOARD_DFLL_MULTIPLIER);
  putreg32(regval, SAM_SYSCTRL_DFLLMUL);
#else
  putreg32(0, SAM_SYSCTRL_DFLLMUL);
#endif

  /* Set up the DFLL value register */

  regval = SYSCTRL_DFLLVAL_COARSE(BOARD_DFLL_COARSEVALUE) |
           SYSCTRL_DFLLVAL_FINE(BOARD_DFLL_FINEVALUE);
  putreg32(regval, SAM_SYSCTRL_DFLLVAL);

  /* Finally, set the state of the ONDEMAND bit if necessary */

#ifdef BOARD_DFLL_ONDEMAND
  control |= SYSCTRL_DFLLCTRL_ONDEMAND; /* On demand control */
  putreg16(control, SAM_SYSCTRL_DFLLCTRL);
#endif
}
#else
#  define sam_dfll_config()
#endif

/****************************************************************************
 * Name: sam_dfll_reference
 *
 * Description:
 *   Enable DFLL reference clock if in closed loop mode.
 *   Depends on:
 *
 *   BOARD_DFLL_SRCGCLKGEN - GCLK index
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if defined(BOARD_GCLK_ENABLE) && defined(BOARD_DFLL_ENABLE) && \
   !defined(BOARD_DFLL_OPENLOOP)
static inline void sam_dfll_reference(void)
{
  uint16_t regval;

  /* Disabled the DFLL reference clock */

  regval = GCLK_CLKCTRL_ID_DFLL48M;
  putreg16(regval, SAM_GCLK_CLKCTRL);

  /* Wait for the clock to become disabled */

  while ((getreg16(SAM_GCLK_CLKCTRL) & GCLK_CLKCTRL_CLKEN) != 0);

  /* Select the configured clock generator as the source for the DFLL
   * reference clock.
   *
   * NOTE: We could enable write lock here to prevent further modification
   */

  regval = ((BOARD_DFLL_SRCGCLKGEN << GCLK_CLKCTRL_GEN_SHIFT) |
            GCLK_CLKCTRL_ID_DFLL48M);
  putreg16(regval, SAM_GCLK_CLKCTRL);

  /* Enable the DFLL reference clock */

  regval |= GCLK_CLKCTRL_CLKEN;
  putreg16(regval, SAM_GCLK_CLKCTRL);

  /* The CLKCTRL.CLKEN bit must be synchronized to the generic clock domain.
   * CLKCTRL.CLKEN will continue to read as its previous state until the
   * synchronization is complete.
   */

  while ((getreg16(SAM_GCLK_CLKCTRL) & GCLK_CLKCTRL_CLKEN) == 0);
}
#else
#  define sam_dfll_reference()
#endif

/****************************************************************************
 * Name: sam_config_gclks
 *
 * Description:
 *   Configure GCLK(s) based on settings in the board.h header file.
 *   Depends on:
 *
 *   Global enable/disable.
 *
 *     BOARD_GCLK_ENABLE            - Boolean (defined / not defined)
 *
 *   For n=1-7:
 *     BOARD_GCLKn_ENABLE           - Boolean (defined / not defined)
 *
 *   For n=0-8:
 *     BOARD_GCLKn_RUN_IN_STANDBY   - Boolean (defined / not defined)
 *     BOARD_GCLKn_CLOCK_SOURCE     - See GCLK_GENCTRL_SRC_* definitions
 *     BOARD_GCLKn_PRESCALER        - Value
 *     BOARD_GCLKn_OUTPUT_ENABLE    - Boolean (defined / not defined)
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef BOARD_GCLK_ENABLE
static inline void sam_config_gclks(void)
{
  int i;

  /* Turn on the GCLK interface clock */

  sam_gclk_enableperiph();

  /* Reset the GCLK module */

  putreg8(GCLK_CTRL_SWRST, SAM_GCLK_CTRL);

  /* Wait for the reset to complete */

  while ((getreg8(SAM_GCLK_CTRL) & GCLK_CTRL_SWRST) != 0);

  /* Configure all GCLK generators, skipping GLCK_MAIN which is configured
   * below.
   */

  for (i = 1; i < NGCLKS_ENABLED; i++)
    {
      sam_gclk_config(&g_gclkconfig[i]);
    }

  /* Enable DFLL reference clock if the DFLL is enabled in closed loop mode */

  sam_dfll_reference();

  /* Configure the GCLK_MAIN last as it may depend on the DFLL or other
   * generators
   */

  sam_gclk_config(&g_gclkconfig[0]);
}
#else
#  define sam_config_gclks()
#endif

/****************************************************************************
 * Name: sam_dividers
 *
 * Description:
 *   Setup PM main clock dividers to generate CPU, AHB, and APB clocks.
 *   Depends on:
 *
 *  BOARD_CPU_DIVIDER   - See PM_CPUSEL_CPUDIV_* definitions
 *  BOARD_CPU_FRQUENCY  - In Hz
 *  BOARD_CPU_FAILDECT  - Boolean (defined / not defined)
 *  BOARD_APBA_DIVIDER  - See M_APBASEL_APBADIV_* definitions
 *  BOARD_APBA_FRQUENCY - In Hz
 *  BOARD_APBB_DIVIDER  - See M_APBBSEL_APBBDIV_* definitions
 *  BOARD_APBB_FRQUENCY - In Hz
 *  BOARD_APBC_DIVIDER  - See M_APBCSEL_APBCDIV_* definitions
 *  BOARD_APBC_FRQUENCY - In Hz
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static inline void sam_dividers(void)
{
#ifdef PM_CTRL_CFDEN
  uint8_t regval;
#endif

  /* Set the CPU divider using the divider value from the board.h header file */

  putreg8(BOARD_CPU_DIVIDER, SAM_PM_CPUSEL);

#ifdef PM_CTRL_CFDEN
  /* Optionally, enable failure detection */

  regval  = getreg8(SAM_PM_CTRL);
#ifdef BOARD_CPU_FAILDECT
  regval |= PM_CTRL_CFDEN;
#else
  regval &= ~PM_CTRL_CFDEN;
#endif
  putreg8(regval, SAM_PM_CTRL);
#endif

  /* Set the APBA, B, and C dividers */

  putreg8(BOARD_APBA_DIVIDER, SAM_PM_APBASEL);
  putreg8(BOARD_APBB_DIVIDER, SAM_PM_APBBSEL);
  putreg8(BOARD_APBC_DIVIDER, SAM_PM_APBCSEL);
}

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

/****************************************************************************
 * Name: sam_clockconfig
 *
 * Description:
 *   Called to establish the clock settings based on the values in board.h.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void sam_clockconfig(void)
{
  /* Clear pending interrupt status. */

  putreg32(SYSCTRL_INT_ALL, SAM_SYSCTRL_INTFLAG);

  /* Set FLASH wait states */

  sam_flash_waitstates();

  /* Configure XOSC */

  sam_xosc_config();

  /* Configure XOSC32K */

  sam_xosc32k_config();

  /* Configure OSCK32K */

  sam_osc32k_config();

  /* Configure DFLL */

  sam_dfll_config();

  /* Configure OSC8M */

  sam_osc8m_config();

  /* Configure GCLK(s) */

  sam_config_gclks();

  /* Set CPU and BUS clock dividers */

  sam_dividers();
}

#endif /* CONFIG_ARCH_FAMILY_SAMD20 || CONFIG_ARCH_FAMILY_SAMD21*/
