soc: arm: atmel: Introduce sam4l SoC
Introduce SAM4L SoC initial files. Signed-off-by: Gerson Fernando Budke <nandojve@gmail.com>
This commit is contained in:
parent
18e608ee55
commit
ab31a2e543
13 changed files with 1061 additions and 22 deletions
304
soc/arm/atmel_sam/sam4l/soc.c
Normal file
304
soc/arm/atmel_sam/sam4l/soc.c
Normal file
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 <device.h>
|
||||
#include <init.h>
|
||||
#include <soc.h>
|
||||
#include <arch/cpu.h>
|
||||
|
||||
/** Watchdog control register first write keys */
|
||||
#define WDT_FIRST_KEY 0x55ul
|
||||
/** Watchdog control register second write keys */
|
||||
#define WDT_SECOND_KEY 0xAAul
|
||||
|
||||
/**
|
||||
* @brief Calculate \f$ \left\lceil \frac{a}{b} \right\rceil \f$ using
|
||||
* integer arithmetic.
|
||||
*
|
||||
* @param a An integer
|
||||
* @param b Another integer
|
||||
*
|
||||
* @return (\a a / \a b) rounded up to the nearest integer.
|
||||
*/
|
||||
#define div_ceil(a, b) (((a) + (b) - 1) / (b))
|
||||
|
||||
/**
|
||||
* @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_ceil(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Perform basic hardware initialization at boot.
|
||||
*
|
||||
* This needs to be run from the very beginning.
|
||||
* So the init priority has to be 0 (zero).
|
||||
*
|
||||
* @return 0
|
||||
*/
|
||||
static int atmel_sam4l_init(const struct device *arg)
|
||||
{
|
||||
uint32_t key;
|
||||
|
||||
ARG_UNUSED(arg);
|
||||
|
||||
key = irq_lock();
|
||||
|
||||
#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();
|
||||
|
||||
/*
|
||||
* Install default handler that simply resets the CPU
|
||||
* if configured in the kernel, NOP otherwise.
|
||||
*/
|
||||
NMI_INIT();
|
||||
|
||||
irq_unlock(key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(atmel_sam4l_init, PRE_KERNEL_1, 0);
|
Loading…
Add table
Add a link
Reference in a new issue