268 lines
6.8 KiB
C
268 lines
6.8 KiB
C
/*
|
|
* Copyright (c) 2020-2023 Gerson Fernando Budke <nandojve@gmail.com>
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief Atmel SAM4L MCU series initialization code
|
|
*
|
|
* This module provides routines to initialize and support board-level hardware
|
|
* for the Atmel SAM4L series processor.
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
#include <soc.h>
|
|
#include <zephyr/sys/util.h>
|
|
|
|
/** Watchdog control register first write keys */
|
|
#define WDT_FIRST_KEY 0x55ul
|
|
/** Watchdog control register second write keys */
|
|
#define WDT_SECOND_KEY 0xAAul
|
|
|
|
/**
|
|
* @brief Sets the WatchDog Timer Control register to the \a ctrl value thanks
|
|
* to the WatchDog Timer key.
|
|
*
|
|
* @param ctrl Value to set the WatchDog Timer Control register to.
|
|
*/
|
|
static ALWAYS_INLINE void wdt_set_ctrl(uint32_t ctrl)
|
|
{
|
|
volatile uint32_t dly;
|
|
|
|
/** Calculate delay for internal synchronization
|
|
* see 45.1.3 WDT errata
|
|
*/
|
|
dly = DIV_ROUND_UP(48000000 * 2, 115000);
|
|
dly >>= 3; /* ~8 cycles for one while loop */
|
|
while (dly--) {
|
|
;
|
|
}
|
|
WDT->CTRL = ctrl | WDT_CTRL_KEY(WDT_FIRST_KEY);
|
|
WDT->CTRL = ctrl | WDT_CTRL_KEY(WDT_SECOND_KEY);
|
|
}
|
|
|
|
#define XTAL_FREQ 12000000
|
|
#define NR_PLLS 1
|
|
#define PLL_MAX_STARTUP_CYCLES (SCIF_PLL_PLLCOUNT_Msk >> SCIF_PLL_PLLCOUNT_Pos)
|
|
|
|
/**
|
|
* Fcpu = 48MHz
|
|
* Fpll = (Fclk * PLL_mul) / PLL_div
|
|
*/
|
|
#define PLL0_MUL (192000000 / XTAL_FREQ)
|
|
#define PLL0_DIV 4
|
|
|
|
static inline bool pll_is_locked(uint32_t pll_id)
|
|
{
|
|
return !!(SCIF->PCLKSR & (1U << (6 + pll_id)));
|
|
}
|
|
|
|
static inline bool osc_is_ready(uint8_t id)
|
|
{
|
|
switch (id) {
|
|
case OSC_ID_OSC0:
|
|
return !!(SCIF->PCLKSR & SCIF_PCLKSR_OSC0RDY);
|
|
case OSC_ID_OSC32:
|
|
return !!(BSCIF->PCLKSR & BSCIF_PCLKSR_OSC32RDY);
|
|
case OSC_ID_RC32K:
|
|
return !!(BSCIF->RC32KCR & (BSCIF_RC32KCR_EN));
|
|
case OSC_ID_RC80M:
|
|
return !!(SCIF->RC80MCR & (SCIF_RC80MCR_EN));
|
|
case OSC_ID_RCFAST:
|
|
return !!(SCIF->RCFASTCFG & (SCIF_RCFASTCFG_EN));
|
|
case OSC_ID_RC1M:
|
|
return !!(BSCIF->RC1MCR & (BSCIF_RC1MCR_CLKOE));
|
|
case OSC_ID_RCSYS:
|
|
/* RCSYS is always ready */
|
|
return true;
|
|
default:
|
|
/* unhandled_case(id); */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The PLL options #PLL_OPT_VCO_RANGE_HIGH and #PLL_OPT_OUTPUT_DIV will
|
|
* be set automatically based on the calculated target frequency.
|
|
*/
|
|
static inline uint32_t pll_config_init(uint32_t divide, uint32_t mul)
|
|
{
|
|
#define SCIF0_PLL_VCO_RANGE1_MAX_FREQ 240000000
|
|
#define SCIF_PLL0_VCO_RANGE1_MIN_FREQ 160000000
|
|
#define SCIF_PLL0_VCO_RANGE0_MAX_FREQ 180000000
|
|
#define SCIF_PLL0_VCO_RANGE0_MIN_FREQ 80000000
|
|
/* VCO frequency range is 160-240 MHz (80-180 MHz if unset) */
|
|
#define PLL_OPT_VCO_RANGE_HIGH 0
|
|
/* Divide output frequency by two */
|
|
#define PLL_OPT_OUTPUT_DIV 1
|
|
/* The threshold above which to set the #PLL_OPT_VCO_RANGE_HIGH option */
|
|
#define PLL_VCO_LOW_THRESHOLD ((SCIF_PLL0_VCO_RANGE1_MIN_FREQ \
|
|
+ SCIF_PLL0_VCO_RANGE0_MAX_FREQ) / 2)
|
|
#define PLL_MIN_HZ 40000000
|
|
#define PLL_MAX_HZ 240000000
|
|
#define MUL_MIN 2
|
|
#define MUL_MAX 16
|
|
#define DIV_MIN 0
|
|
#define DIV_MAX 15
|
|
|
|
uint32_t pll_value;
|
|
uint32_t vco_hz;
|
|
|
|
/* Calculate internal VCO frequency */
|
|
vco_hz = XTAL_FREQ * mul;
|
|
vco_hz /= divide;
|
|
|
|
pll_value = 0;
|
|
|
|
/* Bring the internal VCO frequency up to the minimum value */
|
|
if ((vco_hz < PLL_MIN_HZ * 2) && (mul <= 8)) {
|
|
mul *= 2;
|
|
vco_hz *= 2;
|
|
pll_value |= (1U << (SCIF_PLL_PLLOPT_Pos +
|
|
PLL_OPT_OUTPUT_DIV));
|
|
}
|
|
|
|
/* Set VCO frequency range according to calculated value */
|
|
if (vco_hz >= PLL_VCO_LOW_THRESHOLD) {
|
|
pll_value |= 1U << (SCIF_PLL_PLLOPT_Pos +
|
|
PLL_OPT_VCO_RANGE_HIGH);
|
|
}
|
|
|
|
pll_value |= ((mul - 1) << SCIF_PLL_PLLMUL_Pos) |
|
|
(divide << SCIF_PLL_PLLDIV_Pos) |
|
|
(PLL_MAX_STARTUP_CYCLES << SCIF_PLL_PLLCOUNT_Pos);
|
|
|
|
return pll_value;
|
|
}
|
|
|
|
static inline void flashcalw_set_wait_state(uint32_t wait_state)
|
|
{
|
|
HFLASHC->FCR = (HFLASHC->FCR & ~FLASHCALW_FCR_FWS) |
|
|
(wait_state ?
|
|
FLASHCALW_FCR_FWS_1 :
|
|
FLASHCALW_FCR_FWS_0);
|
|
}
|
|
|
|
static inline bool flashcalw_is_ready(void)
|
|
{
|
|
return ((HFLASHC->FSR & FLASHCALW_FSR_FRDY) != 0);
|
|
}
|
|
|
|
static inline void flashcalw_issue_command(uint32_t command, int page_number)
|
|
{
|
|
uint32_t time;
|
|
|
|
flashcalw_is_ready();
|
|
time = HFLASHC->FCMD;
|
|
|
|
/* Clear the command bitfield. */
|
|
time &= ~FLASHCALW_FCMD_CMD_Msk;
|
|
if (page_number >= 0) {
|
|
time = (FLASHCALW_FCMD_KEY_KEY |
|
|
FLASHCALW_FCMD_PAGEN(page_number) | command);
|
|
} else {
|
|
time |= (FLASHCALW_FCMD_KEY_KEY | command);
|
|
}
|
|
|
|
HFLASHC->FCMD = time;
|
|
flashcalw_is_ready();
|
|
}
|
|
|
|
/**
|
|
* @brief Setup various clock on SoC at boot time.
|
|
*
|
|
* Setup the SoC clocks according to section 28.12 in datasheet.
|
|
*
|
|
* Setup Slow, Main, PLLA, Processor and Master clocks during the device boot.
|
|
* It is assumed that the relevant registers are at their reset value.
|
|
*/
|
|
static ALWAYS_INLINE void clock_init(void)
|
|
{
|
|
/* Disable PicoCache and Enable HRAMC1 as extended RAM */
|
|
soc_pmc_peripheral_enable(
|
|
PM_CLOCK_MASK(PM_CLK_GRP_HSB, SYSCLK_HRAMC1_DATA));
|
|
soc_pmc_peripheral_enable(
|
|
PM_CLOCK_MASK(PM_CLK_GRP_PBB, SYSCLK_HRAMC1_REGS));
|
|
|
|
HCACHE->CTRL = HCACHE_CTRL_CEN_NO;
|
|
|
|
while (HCACHE->SR & HCACHE_SR_CSTS_EN) {
|
|
;
|
|
}
|
|
|
|
/* Enable PLL */
|
|
if (!pll_is_locked(0)) {
|
|
/* This assumes external 12MHz Crystal */
|
|
SCIF->UNLOCK = SCIF_UNLOCK_KEY(0xAAu) |
|
|
SCIF_UNLOCK_ADDR((uint32_t)&SCIF->OSCCTRL0 -
|
|
(uint32_t)SCIF);
|
|
SCIF->OSCCTRL0 = SCIF_OSCCTRL0_STARTUP(2) |
|
|
SCIF_OSCCTRL0_GAIN(3) |
|
|
SCIF_OSCCTRL0_MODE |
|
|
SCIF_OSCCTRL0_OSCEN;
|
|
|
|
while (!osc_is_ready(OSC_ID_OSC0)) {
|
|
;
|
|
}
|
|
uint32_t pll_config = pll_config_init(PLL0_DIV,
|
|
PLL0_MUL);
|
|
|
|
SCIF->UNLOCK = SCIF_UNLOCK_KEY(0xAAu) |
|
|
SCIF_UNLOCK_ADDR((uint32_t)&SCIF->PLL[0] -
|
|
(uint32_t)SCIF);
|
|
SCIF->PLL[0] = pll_config | SCIF_PLL_PLLEN;
|
|
|
|
while (!pll_is_locked(0)) {
|
|
;
|
|
}
|
|
}
|
|
|
|
/** Set a flash wait state depending on the new cpu frequency.
|
|
*/
|
|
flashcalw_set_wait_state(1);
|
|
flashcalw_issue_command(FLASHCALW_FCMD_CMD_HSEN, -1);
|
|
|
|
/** Set Clock CPU/BUS dividers
|
|
*/
|
|
PM->UNLOCK = PM_UNLOCK_KEY(0xAAu) |
|
|
PM_UNLOCK_ADDR((uint32_t)&PM->CPUSEL - (uint32_t)PM);
|
|
PM->CPUSEL = PM_CPUSEL_CPUSEL(0);
|
|
|
|
PM->UNLOCK = PM_UNLOCK_KEY(0xAAu) |
|
|
PM_UNLOCK_ADDR((uint32_t)&PM->PBASEL - (uint32_t)PM);
|
|
PM->PBASEL = PM_PBASEL_PBSEL(0);
|
|
|
|
PM->UNLOCK = PM_UNLOCK_KEY(0xAAu) |
|
|
PM_UNLOCK_ADDR((uint32_t)&PM->PBBSEL - (uint32_t)PM);
|
|
PM->PBBSEL = PM_PBBSEL_PBSEL(0);
|
|
|
|
PM->UNLOCK = PM_UNLOCK_KEY(0xAAu) |
|
|
PM_UNLOCK_ADDR((uint32_t)&PM->PBCSEL - (uint32_t)PM);
|
|
PM->PBCSEL = PM_PBCSEL_PBSEL(0);
|
|
|
|
PM->UNLOCK = PM_UNLOCK_KEY(0xAAu) |
|
|
PM_UNLOCK_ADDR((uint32_t)&PM->PBDSEL - (uint32_t)PM);
|
|
PM->PBDSEL = PM_PBDSEL_PBSEL(0);
|
|
|
|
/** Set PLL0 as source clock
|
|
*/
|
|
PM->UNLOCK = PM_UNLOCK_KEY(0xAAu) |
|
|
PM_UNLOCK_ADDR((uint32_t)&PM->MCCTRL - (uint32_t)PM);
|
|
PM->MCCTRL = OSC_SRC_PLL0;
|
|
}
|
|
|
|
void soc_reset_hook(void)
|
|
{
|
|
#if defined(CONFIG_WDT_DISABLE_AT_BOOT)
|
|
wdt_set_ctrl(WDT->CTRL & ~WDT_CTRL_EN);
|
|
while (WDT->CTRL & WDT_CTRL_EN) {
|
|
;
|
|
}
|
|
#endif
|
|
|
|
/* Setup system clocks. */
|
|
clock_init();
|
|
}
|