blob: 3ba38392f96bfb6d18d8f489e24d42f534679e3b [file] [log] [blame]
/*
* Copyright (c) 2021 Argentum Systems Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Atmel SAML MCU series initialization code
*/
#include <zephyr/arch/cpu.h>
#include <zephyr/arch/arm/aarch32/cortex_m/cmsis.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <soc.h>
/* the SAML21 currently operates only in Performance Level 2... sleep
* and low-power operation are not currently supported by the BSP
*
* the CPU clock will be configured to 48 MHz, and run via DFLL48M.
*
* Reference -> GCLK Gen 1 -> DFLL48M -> GCLK Gen 0 -> GCLK_MAIN
*
* GCLK Gen 0 -> GCLK_MAIN @ 48 Mhz
* GCLK Gen 1 -> DFLL48M (variable)
* GCLK Gen 2 -> USB @ 48 MHz
* GCLK Gen 3 -> ADC @ 24 MHz (further /2 in the ADC peripheral)
*/
static inline void gclk_reset(void)
{
GCLK->CTRLA.bit.SWRST = 1;
while (GCLK->SYNCBUSY.bit.SWRST) {
}
/* by default, OSC16M will be enabled at 4 MHz, and the CPU will
* run from it. to permit initialization, the CPU is temporarily
* clocked from OSCULP32K, and OSC16M is disabled
*/
GCLK->GENCTRL[0].bit.SRC = GCLK_GENCTRL_SRC_OSCULP32K_Val;
OSCCTRL->OSC16MCTRL.bit.ENABLE = 0;
}
#if !CONFIG_SOC_ATMEL_SAML_OSC32K
#define osc32k_init()
#else
static inline void osc32k_init(void)
{
uint32_t cal;
/* OSC32KCAL is in NVMCTRL_OTP5[12:6] */
cal = *((uint32_t *)NVMCTRL_OTP5);
cal >>= 6;
cal &= (1 << 7) - 1;
OSC32KCTRL->OSC32K.reg = 0
| OSC32KCTRL_OSC32K_CALIB(cal)
| OSC32KCTRL_OSC32K_STARTUP(0x5) /* 34 cycles / ~1.038ms */
| !OSC32KCTRL_OSC32K_ONDEMAND
| OSC32KCTRL_OSC32K_RUNSTDBY
| OSC32KCTRL_OSC32K_EN32K
| OSC32KCTRL_OSC32K_ENABLE;
/* wait for ready */
while (!OSC32KCTRL->STATUS.bit.OSC32KRDY) {
}
}
#endif
#if !CONFIG_SOC_ATMEL_SAML_XOSC32K
#define xosc32k_init()
#else
static inline void xosc32k_init(void)
{
OSC32KCTRL->XOSC32K.reg = 0
| OSC32KCTRL_XOSC32K_STARTUP(0x1) /* 4096 cycles / ~0.13s */
| !OSC32KCTRL_XOSC32K_ONDEMAND
| OSC32KCTRL_XOSC32K_RUNSTDBY
| OSC32KCTRL_XOSC32K_EN32K
#if CONFIG_SOC_ATMEL_SAML_XOSC32K_CRYSTAL
| OSC32KCTRL_XOSC32K_XTALEN
#endif
| OSC32KCTRL_XOSC32K_ENABLE;
/* wait for ready */
while (!OSC32KCTRL->STATUS.bit.XOSC32KRDY) {
}
}
#endif
#if !CONFIG_SOC_ATMEL_SAML_OSC16M
#define osc16m_init()
#else
static inline void osc16m_init(void)
{
OSCCTRL->OSC16MCTRL.reg = 0
| !OSCCTRL_OSC16MCTRL_ONDEMAND
| OSCCTRL_OSC16MCTRL_RUNSTDBY
| OSCCTRL_OSC16MCTRL_FSEL_16
| OSCCTRL_OSC16MCTRL_ENABLE;
/* wait for ready */
while (!OSCCTRL->STATUS.bit.OSC16MRDY) {
}
}
#endif
/* TODO: use CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC ?? */
static inline void dfll48m_init(void)
{
uint32_t cal;
/* setup the reference clock (if any) */
GCLK->GENCTRL[1].reg = 0
#if CONFIG_SOC_ATMEL_SAML_OSC32K_AS_MAIN
| GCLK_GENCTRL_SRC_OSC32K
#elif CONFIG_SOC_ATMEL_SAML_XOSC32K_AS_MAIN
| GCLK_GENCTRL_SRC_XOSC32K
#elif CONFIG_SOC_ATMEL_SAML_OSC16M_AS_MAIN
/* configure Fout = Fin / 2^(DIV+1) = 31.25 kHz
* Fgclk_dfll48m_ref max is 33 kHz
*/
| GCLK_GENCTRL_DIV(8)
| GCLK_GENCTRL_DIVSEL
| GCLK_GENCTRL_SRC_OSC16M
#endif
#if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
| GCLK_GENCTRL_RUNSTDBY
| GCLK_GENCTRL_GENEN
#endif
;
#if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
/* configure and enable the generator & peripheral channel */
GCLK->PCHCTRL[0].reg = 0
| GCLK_PCHCTRL_CHEN
| GCLK_PCHCTRL_GEN_GCLK1;
#endif
/* --- */
/* if the target frequency is 48 MHz, then the calibration value can be used to
* decrease the time until the coarse lock is acquired. this is loaded from
* NVMCTRL_OTP5[31:26]
*/
cal = *((uint32_t *)NVMCTRL_OTP5);
cal >>= 26;
cal &= (1 << 6) - 1;
OSCCTRL->DFLLCTRL.reg = 0
| OSCCTRL_DFLLCTRL_QLDIS
| !OSCCTRL_DFLLCTRL_ONDEMAND
| OSCCTRL_DFLLCTRL_RUNSTDBY
#if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
| OSCCTRL_DFLLCTRL_MODE
#endif
;
OSCCTRL->DFLLVAL.reg = 0
| OSCCTRL_DFLLVAL_COARSE(cal)
| OSCCTRL_DFLLVAL_FINE(512) /* use 50% */
;
OSCCTRL->DFLLMUL.reg = 0
/* use 25% of maximum value for the coarse and fine step
* ... I couldn't find details on the inner workings of the DFLL, or any
* example values for these - I have seen others using ~50%. hopefully these
* values will provide a good balance between startup time and overshoot
*/
| OSCCTRL_DFLLMUL_CSTEP(16)
| OSCCTRL_DFLLMUL_FSTEP(256)
#if CONFIG_SOC_ATMEL_SAML_OSC32K_AS_MAIN || CONFIG_SOC_ATMEL_SAML_XOSC32K_AS_MAIN
/* use a 32.768 kHz reference ... 48e6 / 32,768 = 1,464.843... */
| OSCCTRL_DFLLMUL_MUL(1465)
#elif CONFIG_SOC_ATMEL_SAML_OSC16M_AS_MAIN
/* use a 16 MHz -> 31.25 kHz reference... 48e6 / 31,250 = 1,536
* a small value can make the DFLL unstable, hence not using the
* 16 MHz source directly
*/
| OSCCTRL_DFLLMUL_MUL(1536)
#endif
;
/* --- */
/* enable */
while (!OSCCTRL->STATUS.bit.DFLLRDY) {
}
OSCCTRL->DFLLCTRL.bit.ENABLE = 1;
#if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
/* wait for ready... note in open loop mode, we won't get a lock */
while (!OSCCTRL->STATUS.bit.DFLLLCKC || !OSCCTRL->STATUS.bit.DFLLLCKF) {
}
#endif
}
static inline void flash_waitstates_init(void)
{
/* PL2, >= 2.7v, 48MHz = 2 wait states */
NVMCTRL->CTRLB.bit.RWS = 2;
}
static inline void pm_init(void)
{
PM->PLCFG.bit.PLDIS = 0;
PM->PLCFG.bit.PLSEL = 2;
}
static inline void gclk_main_configure(void)
{
/* finally, switch the CPU over to run from DFLL48M */
GCLK->GENCTRL[0].bit.SRC = GCLK_GENCTRL_SRC_DFLL48M_Val;
}
#if !CONFIG_ADC_SAM0
#define gclk_adc_configure()
#else
static inline void gclk_adc_configure(void)
{
GCLK->GENCTRL[3].reg = 0
| GCLK_GENCTRL_SRC_DFLL48M
| GCLK_GENCTRL_DIV(2)
| GCLK_GENCTRL_GENEN;
}
#endif
#if CONFIG_SOC_ATMEL_SAML_DEBUG_PAUSE
static inline void pause_for_debug(void)
{
/* for some reason, when attempting to flash / debug the target, the operations
* will time out... I suspect this is due to clock configuration, so instead of
* doing this immediately, we defer startup for a while to permit the debugger
* to jump in and interrupt us. ick
*/
for (uint32_t i = 0; i < 10000; i += 1) {
__asm__ volatile ("nop\n");
}
}
#else
static inline void pause_for_debug(void) {}
#endif
static int atmel_saml_init(const struct device *arg)
{
uint32_t key;
ARG_UNUSED(arg);
key = irq_lock();
pause_for_debug();
gclk_reset();
osc32k_init();
xosc32k_init();
osc16m_init();
dfll48m_init();
flash_waitstates_init();
pm_init();
gclk_main_configure();
gclk_adc_configure();
/* 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_saml_init, PRE_KERNEL_1, 0);