diff options
Diffstat (limited to 'src/atsamd/samd51_clock.c')
-rw-r--r-- | src/atsamd/samd51_clock.c | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/src/atsamd/samd51_clock.c b/src/atsamd/samd51_clock.c new file mode 100644 index 00000000..c7ffe2aa --- /dev/null +++ b/src/atsamd/samd51_clock.c @@ -0,0 +1,196 @@ +// Code to setup peripheral clocks on the SAMD51 +// +// Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "compiler.h" // DIV_ROUND_CLOSEST +#include "internal.h" // enable_pclock + +// The "generic clock generators" that are configured +#define CLKGEN_MAIN 0 +#define CLKGEN_200M 1 +#define CLKGEN_32K 2 +#define CLKGEN_48M 3 +#define CLKGEN_2M 4 +#define CLKGEN_100M 5 + +#define FREQ_MAIN 120000000 +#define FREQ_200M 200000000 +#define FREQ_32K 32768 +#define FREQ_48M 48000000 +#define FREQ_2M 2000000 +#define FREQ_100M 100000000 + +// 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(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; + 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, uint32_t pm_id) +{ + uint32_t clkgen_id = CLKGEN_100M; + if (pclk_id == TC0_GCLK_ID || pclk_id == TC1_GCLK_ID) + clkgen_id = CLKGEN_200M; + else if (pclk_id == USB_GCLK_ID) + clkgen_id = CLKGEN_48M; + route_pclock(pclk_id, clkgen_id); + 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) +{ + if (pclk_id == TC0_GCLK_ID || pclk_id == TC1_GCLK_ID) + return FREQ_200M; + else if (pclk_id == USB_GCLK_ID) + return FREQ_48M; + return FREQ_100M; +} + +// Initialize the clocks using an external 32K crystal +static void +clock_init_32k(void) +{ + // Enable external 32Khz crystal and route to CLKGEN_32K + uint32_t val = (OSC32KCTRL_XOSC32K_ENABLE | OSC32KCTRL_XOSC32K_EN32K + | OSC32KCTRL_XOSC32K_CGM_XT | OSC32KCTRL_XOSC32K_XTALEN); + OSC32KCTRL->XOSC32K.reg = val; + while (!(OSC32KCTRL->STATUS.reg & OSC32KCTRL_STATUS_XOSC32KRDY)) + ; + gen_clock(CLKGEN_32K, GCLK_GENCTRL_SRC_XOSC32K); + + // Generate 120Mhz clock on PLL0 (with CLKGEN_32K as reference) + route_pclock(OSCCTRL_GCLK_ID_FDPLL0, CLKGEN_32K); + uint32_t mul = DIV_ROUND_CLOSEST(FREQ_MAIN, FREQ_32K); + OSCCTRL->Dpll[0].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDR(mul - 1); + while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) + ; + OSCCTRL->Dpll[0].DPLLCTRLB.reg = (OSCCTRL_DPLLCTRLB_REFCLK_GCLK + | OSCCTRL_DPLLCTRLB_LBYPASS); + OSCCTRL->Dpll[0].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE; + uint32_t mask = OSCCTRL_DPLLSTATUS_CLKRDY | OSCCTRL_DPLLSTATUS_LOCK; + while ((OSCCTRL->Dpll[0].DPLLSTATUS.reg & mask) != mask) + ; + + // Switch main clock to 120Mhz PLL0 + gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC_DPLL0); + + // Generate 200Mhz clock on PLL1 (with CLKGEN_32K as reference) + route_pclock(OSCCTRL_GCLK_ID_FDPLL1, CLKGEN_32K); + mul = DIV_ROUND_CLOSEST(FREQ_200M, FREQ_32K); + OSCCTRL->Dpll[1].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDR(mul - 1); + while (OSCCTRL->Dpll[1].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) + ; + OSCCTRL->Dpll[1].DPLLCTRLB.reg = (OSCCTRL_DPLLCTRLB_REFCLK_GCLK + | OSCCTRL_DPLLCTRLB_LBYPASS); + OSCCTRL->Dpll[1].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE; + mask = OSCCTRL_DPLLSTATUS_CLKRDY | OSCCTRL_DPLLSTATUS_LOCK; + while ((OSCCTRL->Dpll[1].DPLLSTATUS.reg & mask) != mask) + ; + + // Produce 100Mhz and 200Mhz clocks from PLL1 + gen_clock(CLKGEN_200M, GCLK_GENCTRL_SRC_DPLL1); + uint32_t div = DIV_ROUND_CLOSEST(FREQ_200M, FREQ_100M); + gen_clock(CLKGEN_100M, GCLK_GENCTRL_SRC_DPLL1 | GCLK_GENCTRL_DIV(div)); + + // Configure DFLL48M clock (with CLKGEN_32K as reference) + OSCCTRL->DFLLCTRLA.reg = 0; + route_pclock(OSCCTRL_GCLK_ID_DFLL48, CLKGEN_32K); + mul = DIV_ROUND_CLOSEST(FREQ_48M, FREQ_32K); + OSCCTRL->DFLLMUL.reg = (OSCCTRL_DFLLMUL_CSTEP(31) + | OSCCTRL_DFLLMUL_FSTEP(511) + | OSCCTRL_DFLLMUL_MUL(mul)); + while (OSCCTRL->DFLLSYNC.reg & OSCCTRL_DFLLSYNC_DFLLMUL) + ; + OSCCTRL->DFLLCTRLB.reg = (OSCCTRL_DFLLCTRLB_MODE | OSCCTRL_DFLLCTRLB_QLDIS + | OSCCTRL_DFLLCTRLB_WAITLOCK); + while (OSCCTRL->DFLLSYNC.reg & OSCCTRL_DFLLSYNC_DFLLCTRLB) + ; + OSCCTRL->DFLLCTRLA.reg = OSCCTRL_DFLLCTRLA_ENABLE; + while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_DFLLRDY)) + ; + gen_clock(CLKGEN_48M, GCLK_GENCTRL_SRC_DFLL); +} + +// Initialize clocks from factory calibrated internal clock +static void +clock_init_internal(void) +{ + // Route factory calibrated DFLL48M to CLKGEN_48M + gen_clock(CLKGEN_48M, GCLK_GENCTRL_SRC_DFLL); + + // Generate CLKGEN_2M (with CLKGEN_48M as reference) + uint32_t div = DIV_ROUND_CLOSEST(FREQ_48M, FREQ_2M); + gen_clock(CLKGEN_2M, GCLK_GENCTRL_SRC_DFLL | GCLK_GENCTRL_DIV(div)); + + // Generate 120Mhz clock on PLL0 (with CLKGEN_2M as reference) + route_pclock(OSCCTRL_GCLK_ID_FDPLL0, CLKGEN_2M); + uint32_t mul = DIV_ROUND_CLOSEST(FREQ_MAIN, FREQ_2M); + OSCCTRL->Dpll[0].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDR(mul - 1); + while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) + ; + OSCCTRL->Dpll[0].DPLLCTRLB.reg = (OSCCTRL_DPLLCTRLB_REFCLK_GCLK + | OSCCTRL_DPLLCTRLB_LBYPASS); + OSCCTRL->Dpll[0].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE; + uint32_t mask = OSCCTRL_DPLLSTATUS_CLKRDY | OSCCTRL_DPLLSTATUS_LOCK; + while ((OSCCTRL->Dpll[0].DPLLSTATUS.reg & mask) != mask) + ; + + // Switch main clock to 120Mhz PLL0 + gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC_DPLL0); + + // Generate 200Mhz clock on PLL1 (with CLKGEN_2M as reference) + route_pclock(OSCCTRL_GCLK_ID_FDPLL1, CLKGEN_2M); + mul = DIV_ROUND_CLOSEST(FREQ_200M, FREQ_2M); + OSCCTRL->Dpll[1].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDR(mul - 1); + while (OSCCTRL->Dpll[1].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) + ; + OSCCTRL->Dpll[1].DPLLCTRLB.reg = (OSCCTRL_DPLLCTRLB_REFCLK_GCLK + | OSCCTRL_DPLLCTRLB_LBYPASS); + OSCCTRL->Dpll[1].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE; + mask = OSCCTRL_DPLLSTATUS_CLKRDY | OSCCTRL_DPLLSTATUS_LOCK; + while ((OSCCTRL->Dpll[1].DPLLSTATUS.reg & mask) != mask) + ; + + // Produce 100Mhz and 200Mhz clocks from PLL1 + gen_clock(CLKGEN_200M, GCLK_GENCTRL_SRC_DPLL1); + div = DIV_ROUND_CLOSEST(FREQ_200M, FREQ_100M); + gen_clock(CLKGEN_100M, GCLK_GENCTRL_SRC_DPLL1 | GCLK_GENCTRL_DIV(div)); +} + +void +SystemInit(void) +{ + // Reset GCLK + GCLK->CTRLA.reg = GCLK_CTRLA_SWRST; + while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_SWRST) + ; + + // Init clocks + if (CONFIG_CLOCK_REF_X32K) + clock_init_32k(); + else + clock_init_internal(); + + // Enable SAMD51 cache + CMCC->CTRL.reg = 1; +} |