diff options
Diffstat (limited to 'src/atsamd/samc21_clock.c')
-rw-r--r-- | src/atsamd/samc21_clock.c | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/src/atsamd/samc21_clock.c b/src/atsamd/samc21_clock.c new file mode 100644 index 00000000..3d95cf0d --- /dev/null +++ b/src/atsamd/samc21_clock.c @@ -0,0 +1,148 @@ +// Code to setup peripheral clocks on the SAMC21 +// +// Copyright (C) 2018-2023 Kevin O'Connor <kevin@koconnor.net> +// Copyright (C) 2023 Luke Vuksta <wulfstawulfsta@gmail.com> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // DECL_CONSTANT_STR +#include "compiler.h" // DIV_ROUND_CLOSEST +#include "internal.h" // enable_pclock + +// The "generic clock generators" that are configured +#define CLKGEN_MAIN 0 + +#define FREQ_MAIN CONFIG_CLOCK_FREQ +#define FREQ_48M 48000000 + +// Configure a clock generator using a given source as input +static inline void +gen_clock(uint32_t clkgen_id, uint32_t flags) +{ + GCLK->GENCTRL[clkgen_id].reg = flags | GCLK_GENCTRL_GENEN; + while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL(1 << clkgen_id)) + ; +} + +// Route a peripheral clock to a given clkgen +static inline void +route_pclock(uint32_t pclk_id, uint32_t clkgen_id) +{ + uint32_t val = GCLK_PCHCTRL_GEN(clkgen_id) | GCLK_PCHCTRL_CHEN; + // Don't do anything if already enabled (Timer Counter). + if (GCLK->PCHCTRL[pclk_id].reg != val) { + GCLK->PCHCTRL[pclk_id].reg = val; + while (GCLK->PCHCTRL[pclk_id].reg != val) + ; + } +} + +// Enable a peripheral clock and power to that peripheral +void +enable_pclock(uint32_t pclk_id, int32_t pm_id) +{ + route_pclock(pclk_id, CLKGEN_MAIN); + if (pm_id >= 0) { + uint32_t pm_port = pm_id / 32, pm_bit = 1 << (pm_id % 32); + (&MCLK->APBAMASK.reg)[pm_port] |= pm_bit; + } +} + +// Return the frequency of the given peripheral clock +uint32_t +get_pclock_frequency(uint32_t pclk_id) +{ + return FREQ_MAIN; +} + +// Configure a dpll to a given clock multiplier +static void +config_dpll(uint32_t mul, uint32_t ctrlb) +{ + OSCCTRL->DPLLCTRLA.reg = 0; + while (OSCCTRL->DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_ENABLE) + ; + OSCCTRL->DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDR(mul - 1); + while (OSCCTRL->DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) + ; + OSCCTRL->DPLLCTRLB.reg = ctrlb | OSCCTRL_DPLLCTRLB_LBYPASS; + OSCCTRL->DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE; + uint32_t mask = OSCCTRL_DPLLSTATUS_CLKRDY | OSCCTRL_DPLLSTATUS_LOCK; + while ((OSCCTRL->DPLLSTATUS.reg & mask) != mask) + ; +} + +#if CONFIG_CLOCK_REF_X12M +DECL_CONSTANT_STR("RESERVE_PINS_crystal", "PA14,PA15"); +#endif + +// Initialize the clocks using an external 12M crystal +static void +clock_init_12m(void) +{ + // Enable XOSC1 + uint32_t freq_xosc = 12000000; + uint32_t val = (OSCCTRL_XOSCCTRL_ENABLE | OSCCTRL_XOSCCTRL_XTALEN + | OSCCTRL_XOSCCTRL_GAIN(3) | OSCCTRL_XOSCCTRL_AMPGC); + OSCCTRL->XOSCCTRL.reg = val; + while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_XOSCRDY)) + ; + + // Generate 48Mhz clock on PLL (with XOSC as reference) + uint32_t p0div = 24, p0mul = DIV_ROUND_CLOSEST(FREQ_MAIN, freq_xosc/p0div); + uint32_t p0ctrlb = OSCCTRL_DPLLCTRLB_DIV(p0div / 2 - 1); + config_dpll(p0mul, p0ctrlb | OSCCTRL_DPLLCTRLB_REFCLK(1)); + + // Switch main clock to 48Mhz PLL + gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DPLL96M_Val)); +} + + +#if CONFIG_CLOCK_REF_X25M +DECL_CONSTANT_STR("RESERVE_PINS_crystal", "PA14,PA15"); +#endif + +// Initialize the clocks using an external 25M crystal +static void +clock_init_25m(void) +{ + // Enable XOSC1 + uint32_t freq_xosc = 25000000; + uint32_t val = (OSCCTRL_XOSCCTRL_ENABLE | OSCCTRL_XOSCCTRL_XTALEN + | OSCCTRL_XOSCCTRL_GAIN(4) | OSCCTRL_XOSCCTRL_AMPGC); + OSCCTRL->XOSCCTRL.reg = val; + while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_XOSCRDY)) + ; + + // Generate 48Mhz clock on PLL (with XOSC as reference) + uint32_t p0div = 50, p0mul = DIV_ROUND_CLOSEST(FREQ_MAIN, freq_xosc/p0div); + uint32_t p0ctrlb = OSCCTRL_DPLLCTRLB_DIV(p0div / 2 - 1); + config_dpll(p0mul, p0ctrlb | OSCCTRL_DPLLCTRLB_REFCLK(1)); + + // Switch main clock to 48Mhz PLL + gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DPLL96M_Val)); +} + +void +SystemInit(void) +{ + // Set NVM wait states for 48MHz. This might need to be set higher if + // running at a lower voltage. + NVMCTRL->CTRLB.reg |= (NVMCTRL_CTRLB_SLEEPPRM_WAKEUPINSTANT + | NVMCTRL_CTRLB_RWS(2)); + + OSCCTRL->OSC48MDIV.reg = OSCCTRL_OSC48MDIV_DIV(0); + while (OSCCTRL->OSC48MSYNCBUSY.reg & OSCCTRL_OSC48MSYNCBUSY_OSC48MDIV) + ; + + // Reset GCLK + GCLK->CTRLA.reg = GCLK_CTRLA_SWRST; + while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_SWRST) + ; + + // Init clocks + if (CONFIG_CLOCK_REF_X12M) + clock_init_12m(); + else + clock_init_25m(); +} |