aboutsummaryrefslogtreecommitdiffstats
path: root/src/atsamd/samc21_clock.c
blob: 3d95cf0d286534ee3713385279d2b11cc79b4241 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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();
}