Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2018 Sean Nyekjaer
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Atmel SAMD MCU series initialization code
 */

#include <arch/cpu.h>
#include <cortex_m/exc.h>
#include <device.h>
#include <init.h>
#include <kernel.h>
#include <soc.h>

static void flash_waitstates_init(void)
{
	/* One wait state at 48 MHz. */
	NVMCTRL->CTRLB.bit.RWS = NVMCTRL_CTRLB_RWS_HALF_Val;
}

/* Follows the same structure as the Arduino Zero, namely:
 *  XOSC32K -> GCLK1 -> DFLL48M -> GCLK0
 *  OSC8M -> 8 MHz -> GCLK3
 */

static void xosc_init(void)
{
#ifdef CONFIG_SOC_ATMEL_SAMD_XOSC
#error External oscillator support is not implemented.
#endif
}

static void wait_gclk_synchronization(void)
{
	while (GCLK->STATUS.bit.SYNCBUSY) {
	}
}

static void xosc32k_init(void)
{
#ifdef CONFIG_SOC_ATMEL_SAMD_XOSC32K
	SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_STARTUP(6) |
			       SYSCTRL_XOSC32K_XTALEN | SYSCTRL_XOSC32K_EN32K;

	SYSCTRL->XOSC32K.bit.ENABLE = 1;
	/* Wait for the crystal to stabalise. */
	while (!SYSCTRL->PCLKSR.bit.XOSC32KRDY) {
	}
#endif
}

static void osc32k_init(void)
{
	u32_t fuse = *(u32_t *)FUSES_OSC32KCAL_ADDR;
	u32_t calib = (fuse & FUSES_OSC32KCAL_Msk) >> FUSES_OSC32KCAL_Pos;

	SYSCTRL->OSC32K.reg = SYSCTRL_OSC32K_CALIB(calib) |
			      SYSCTRL_OSC32K_STARTUP(0x6u) |
			      SYSCTRL_OSC32K_EN32K | SYSCTRL_OSC32K_ENABLE;

	/* Wait for the oscillator to stabalise. */
	while (!SYSCTRL->PCLKSR.bit.OSC32KRDY) {
	}
}

static void dfll_init(void)
{
	/* No prescaler */
	GCLK->GENDIV.reg = GCLK_GENDIV_ID(1) | GCLK_GENDIV_DIV(0);
	wait_gclk_synchronization();


#if defined(CONFIG_SOC_ATMEL_SAMD_XOSC32K_AS_MAIN)
	/* Route XOSC32K to GCLK1 */
	GCLK->GENCTRL.reg =
	    GCLK_GENCTRL_ID(1) | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_GENEN;
#elif defined(CONFIG_SOC_ATMEL_SAMD_OSC8M_AS_MAIN)
	/* Route OSC8M to GCLK1 */
	GCLK->GENCTRL.reg =
	    GCLK_GENCTRL_ID(1) | GCLK_GENCTRL_SRC_OSC8M | GCLK_GENCTRL_GENEN;
#else
#error Unsupported main clock source.
#endif

	wait_gclk_synchronization();

	/* Route GCLK1 to multiplexer 1 */
	GCLK->CLKCTRL.reg =
	    GCLK_CLKCTRL_ID(0) | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_CLKEN;
	wait_gclk_synchronization();

	SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE;
	while (!SYSCTRL->PCLKSR.bit.DFLLRDY) {
	}

	u32_t mul = (SOC_ATMEL_SAM0_MCK_FREQ_HZ +
		     SOC_ATMEL_SAM0_GCLK1_FREQ_HZ / 2) /
		    SOC_ATMEL_SAM0_GCLK1_FREQ_HZ;

	SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_CSTEP(31) |
			       SYSCTRL_DFLLMUL_FSTEP(511) |
			       SYSCTRL_DFLLMUL_MUL(mul);
	while (!SYSCTRL->PCLKSR.bit.DFLLRDY) {
	}

	SYSCTRL->DFLLCTRL.reg |= SYSCTRL_DFLLCTRL_MODE |
				 SYSCTRL_DFLLCTRL_QLDIS;
	while (!SYSCTRL->PCLKSR.bit.DFLLRDY) {
	}

	/* Enable the DFLL */
	SYSCTRL->DFLLCTRL.bit.ENABLE = 1;

	while (!SYSCTRL->PCLKSR.bit.DFLLLCKC || !SYSCTRL->PCLKSR.bit.DFLLLCKF) {
	}

	while (!SYSCTRL->PCLKSR.bit.DFLLRDY) {
	}
}

static void osc8m_init(void)
{
	/* Turn off the prescaler */
	SYSCTRL->OSC8M.bit.PRESC = SYSCTRL_OSC8M_PRESC(0);
	SYSCTRL->OSC8M.bit.ONDEMAND = 0;
}

static void gclks_init(void)
{
	/* DFLL/1 -> GCLK0 */
	GCLK->GENDIV.reg = GCLK_GENDIV_ID(0) | GCLK_GENDIV_DIV(0);
	wait_gclk_synchronization();

	GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(0) | GCLK_GENCTRL_SRC_DFLL48M |
			    GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN;
	wait_gclk_synchronization();

	/* OSC8M/1 -> GCLK3 */
	GCLK->GENCTRL.reg =
	    GCLK_GENCTRL_ID(3) | GCLK_GENCTRL_SRC_OSC8M | GCLK_GENCTRL_GENEN;
	wait_gclk_synchronization();

	/* OSCULP32K/32 -> GCLK2 */
	GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(32 - 1);
	wait_gclk_synchronization();

	GCLK->GENCTRL.reg =
	    GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_SRC_OSC32K | GCLK_GENCTRL_GENEN;
	wait_gclk_synchronization();
}

static void dividers_init(void)
{
	/* Set the CPU, APBA, B, and C dividers */
	PM->CPUSEL.reg = PM_CPUSEL_CPUDIV_DIV1;
	PM->APBASEL.reg = PM_APBASEL_APBADIV_DIV1_Val;
	PM->APBBSEL.reg = PM_APBBSEL_APBBDIV_DIV1_Val;
	PM->APBCSEL.reg = PM_APBCSEL_APBCDIV_DIV1_Val;

	/* TODO(mlhx): enable clock failure detection? */
}

static int atmel_samd_init(struct device *arg)
{
	u32_t key;

	ARG_UNUSED(arg);

	key = irq_lock();

	z_clearfaults();

	flash_waitstates_init();
	osc8m_init();
	osc32k_init();
	xosc_init();
	xosc32k_init();
	dfll_init();
	gclks_init();
	dividers_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_samd_init, PRE_KERNEL_1, 0);