aboutsummaryrefslogtreecommitdiffstats
path: root/src/atsamd/samd51_clock.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/atsamd/samd51_clock.c')
-rw-r--r--src/atsamd/samd51_clock.c196
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;
+}