Skip to content

Commit d31fca2

Browse files
authored
Merge pull request #45 from Qyriad/features/samx5x-external-clock
samx5x: support external clock sources
2 parents baca4c0 + 82e514b commit d31fca2

File tree

3 files changed

+123
-12
lines changed

3 files changed

+123
-12
lines changed

samd/clocks.h

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,33 @@ void disconnect_gclk_from_peripheral(uint8_t gclk, uint8_t peripheral);
5656
void enable_clock_generator(uint8_t gclk, uint32_t source, uint16_t divisor);
5757
void disable_clock_generator(uint8_t gclk);
5858

59-
void clock_init(bool has_crystal, uint32_t dfll48m_fine_calibration);
59+
/**
60+
* @brief Called during port_init to setup system clocks.
61+
*
62+
* @param has_rtc_crystal Indicates that the board has a crystal for the real-time
63+
* clock (RTC). If true, uses the microcontroller's XOSC32k as the RTC's clock source.
64+
* For an individual board, this value is configured from BOARD_HAS_CRYSTAL in
65+
* mpconfigboard.h.
66+
*
67+
* @param xosc_freq The frequency of a connected external oscillator, or 0 if no
68+
* external oscillator is connected. Non-zero values should be the frequency
69+
* in Hertz (Hz) of an external oscillator connected to an XIN pin on this
70+
* microcontroller. This is currently only implemented for SAMx5x chips.
71+
* For an individual board, this value is configured from BOARD_XOSC_FREQ_HZ
72+
* in mpconfigboard.h.
73+
*
74+
* @param xosc_is_crystal Set to true if the external oscillator (XOSC) described by
75+
* `xosc_freq` is a crystal oscillator, or false if it is not. If there is no XOSC,
76+
* then `xosc_freq` should be set to 0, in which case this parameter is ignored.
77+
* This is currently only implemented for SAMx5x chips.
78+
* For an individual board, this value is configured from BOARD_XOSC_IS_CRYSTAL
79+
* in mpconfigboard.h.
80+
*
81+
* @param dfll48m_fine_calibration The fine calibration value for the DFLL48M.
82+
* Currently only implemented for SAMD21 chips, and only used if `has_rtc_crystal`
83+
* is false.
84+
*/
85+
void clock_init(bool has_rtc_crystal, uint32_t xosc_freq, bool xosc_is_crystal, uint32_t dfll48m_fine_calibration);
6086
void init_dynamic_clocks(void);
6187

6288
bool clock_get_enabled(uint8_t type, uint8_t index);

samd/sam_d5x_e5x/clocks.c

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,77 @@ static void init_clock_source_xosc32k(void) {
8888
OSC32KCTRL_XOSC32K_CGM(1);
8989
}
9090

91-
static void init_clock_source_dpll0(void)
91+
/**
92+
* @brief Initialize the DPLL clock source, which sources the main system clock.
93+
*/
94+
static void init_clock_source_dpll0(uint32_t xosc_freq, bool xosc_is_crystal)
9295
{
93-
GCLK->PCHCTRL[OSCCTRL_GCLK_ID_FDPLL0].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(5);
94-
OSCCTRL->Dpll[0].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDRFRAC(0) | OSCCTRL_DPLLRATIO_LDR(59);
95-
OSCCTRL->Dpll[0].DPLLCTRLB.reg = OSCCTRL_DPLLCTRLB_REFCLK(0);
96+
bool has_xosc = (xosc_freq != 0);
97+
98+
uint8_t refclk_setting;
99+
100+
if (has_xosc) {
101+
// If we have an external oscillator, use that as DPLL0's REFCLK.
102+
uint8_t xtalen = xosc_is_crystal ? OSCCTRL_XOSCCTRL_XTALEN : 0;
103+
refclk_setting = OSCCTRL_DPLLCTRLB_REFCLK_XOSC0_Val;
104+
105+
// When we're using an external clock source, we need to configure DPLL0 based on
106+
// the frequency of that external clock source.
107+
//
108+
// According to the datasheet (28.6.5.1), the frequency of DPLL0 is dependent on its REFCLK
109+
// by this formula:
110+
// f_DPLL0 = f_REFCLK * (LDR + 1 + (LDRFRAC / 32)).
111+
// If we have an external clock source, then we'll be setting REFCLK to XOSC0.
112+
//
113+
// Our desired output frequency for DPLL0 (f_DPLL0) is 120 MHz.
114+
// We currently require xosc_freq to be an integer factor of 120 MHz, so we can ignore LDRFRAC.
115+
// With LDRFRAC at 0, we just need to calculate LDR.
116+
// Rearranging the variables, we get this:
117+
// LDR = (f_DPLL0 / f_XOSC0) - 1
118+
// LDR = (120 MHz / f_XOSC0) - 1
119+
uint32_t ldr = (120000000 / xosc_freq) - 1;
120+
121+
OSCCTRL->XOSCCTRL[0].reg = OSCCTRL_XOSCCTRL_ENABLE | xtalen;
122+
OSCCTRL->Dpll[0].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDRFRAC(0) | OSCCTRL_DPLLRATIO_LDR(ldr);
123+
124+
} else {
125+
// If we don't have an external oscillator, use GCLK 5 as DPLL0's REFLCK.
126+
refclk_setting = OSCCTRL_DPLLCTRLB_REFCLK_GCLK_Val;
127+
GCLK->PCHCTRL[OSCCTRL_GCLK_ID_FDPLL0].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(5);
128+
129+
// We also need to configure DPLL0 based on the the frequency of its REFCLK,
130+
// which here is GCLK 5.
131+
// GCLK 5 itself uses the DFLL48M as its source, but with a divisor of 24.
132+
// So the output frequency of GCLK 5 is 2 MHz:
133+
// f_DFLL = 48 MHz
134+
// f_GCLK5 = 48 MHz / 24 = 2 MHz
135+
//
136+
// According to the datasheet (28.6.5.1), the frequency of DPLL0 is dependent on its REFCLK
137+
// by this formula:
138+
// f_DPLL0 = f_REFCLK * (LDR + 1 + (LDRFRAC / 32)).
139+
//
140+
// We want f_DPLL0 to be 120 MHz, and f_REFCLK is known beforehand as shown above.
141+
// If we ignore LDRFRAC and leave it at 0, then we can calculate LDR with:
142+
// LDR = (f_DPLL0 / f_REFCLK) - 1.
143+
// LDR = (120 MHz / 2 MHz) - 1 = 59
144+
OSCCTRL->Dpll[0].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDRFRAC(0) | OSCCTRL_DPLLRATIO_LDR(59);
145+
}
146+
147+
// Apply the REFCLK that was determined above.
148+
OSCCTRL->Dpll[0].DPLLCTRLB.reg = OSCCTRL_DPLLCTRLB_REFCLK(refclk_setting);
149+
// Enable this clock.
96150
OSCCTRL->Dpll[0].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE;
97151

98152
while (!(OSCCTRL->Dpll[0].DPLLSTATUS.bit.LOCK || OSCCTRL->Dpll[0].DPLLSTATUS.bit.CLKRDY)) {}
99153
}
100154

101-
void clock_init(bool has_crystal, uint32_t dfll48m_fine_calibration) {
155+
void clock_init(bool has_rtc_crystal, uint32_t xosc_freq, bool xosc_is_crystal, uint32_t dfll48m_fine_calibration) {
102156
// DFLL48M is enabled by default
103157
// TODO: handle fine calibration data.
104158

105159
init_clock_source_osculp32k();
106160

107-
if (has_crystal) {
161+
if (has_rtc_crystal) {
108162
init_clock_source_xosc32k();
109163
OSC32KCTRL->RTCCTRL.bit.RTCSEL = OSC32KCTRL_RTCCTRL_RTCSEL_XOSC32K_Val;
110164
} else {
@@ -113,13 +167,18 @@ void clock_init(bool has_crystal, uint32_t dfll48m_fine_calibration) {
113167

114168
MCLK->CPUDIV.reg = MCLK_CPUDIV_DIV(1);
115169

170+
// Set GCLK_GEN[0], which is used for GCLK_MAIN, to use DPLL0 as its source.
171+
// DPLL0's REFCLK is set in the init_clock_source_dpll0() call below.
116172
enable_clock_generator_sync(0, GCLK_GENCTRL_SRC_DPLL0_Val, 1, false);
117173
enable_clock_generator_sync(1, GCLK_GENCTRL_SRC_DFLL_Val, 1, false);
118174
enable_clock_generator_sync(4, GCLK_GENCTRL_SRC_DPLL0_Val, 1, false);
175+
// Note(Qyriad): if !has_xosc, GCLK 5 is set as the REFCLK source for DPLL0 in
176+
// init_clock_source_dpll0(), but I don't know if GCLK 5 is used elsewhere too,
177+
// so I haven't made enabling GCLK 5 conditional on has_xosc here.
119178
enable_clock_generator_sync(5, GCLK_GENCTRL_SRC_DFLL_Val, 24, false);
120179
enable_clock_generator_sync(6, GCLK_GENCTRL_SRC_DFLL_Val, 4, false);
121180

122-
init_clock_source_dpll0();
181+
init_clock_source_dpll0(xosc_freq, xosc_is_crystal);
123182

124183
// Do this after all static clock init so that they aren't used dynamically.
125184
init_dynamic_clocks();
@@ -202,6 +261,13 @@ static uint32_t dpll_get_frequency(uint8_t index) {
202261
freq = 32768;
203262
break;
204263
case 0x2: // XOSC0
264+
// If XOSC0 is being used as DPLL0's REFCLK, then we can figure out XOSC0's frequency.
265+
if (OSCCTRL->Dpll[0].DPLLCTRLB.bit.REFCLK == OSCCTRL_DPLLCTRLB_REFCLK_XOSC0_Val) {
266+
freq = osc_get_frequency(GCLK_SOURCE_XOSC0);
267+
} else {
268+
freq = 0;
269+
}
270+
break;
205271
case 0x3: // XOSC1
206272
default:
207273
return 0; // unknown
@@ -214,6 +280,22 @@ static uint32_t dpll_get_frequency(uint8_t index) {
214280
static uint32_t osc_get_frequency(uint8_t index) {
215281
switch (index) {
216282
case GCLK_SOURCE_XOSC0:
283+
// If we're using XOSC0 as the REFCLK for DPLLL0, we can calculate XOSC0's frequency.
284+
if (OSCCTRL->Dpll[0].DPLLCTRLB.bit.REFCLK == OSCCTRL_DPLLCTRLB_REFCLK_XOSC0_Val) {
285+
// From the datasheet (28.6.5.1), the frequency of DPLL0 is dependent on
286+
// its REFCLK by this formula:
287+
// f_DPLL0 = f_REFCLK * (LDR + 1 + (LDRFRAC / 32)).
288+
// We know f_DPLL is 120 MHz (the system clock rate).
289+
// We currently always set LDRFRAC to 0, and we can retrieve LDR from the
290+
// register its set in. Rearranging the variables, we get:
291+
// f_XOSC0 = f_DPLL0 / (LDR + 1).
292+
uint32_t ldr = OSCCTRL->Dpll[0].DPLLRATIO.bit.LDR;
293+
uint32_t f_dpll0 = 120000000;
294+
uint32_t f_xosc0 = f_dpll0 / (ldr + 1);
295+
return f_xosc0;
296+
}
297+
// Otherwise, we don't know.
298+
return 0;
217299
case GCLK_SOURCE_XOSC1:
218300
return 0; // unknown
219301
case GCLK_SOURCE_OSCULP32K:

samd/samd21/clocks.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,19 @@ static void init_clock_source_dfll48m_usb(uint32_t fine_calibration) {
142142
while (GCLK->STATUS.bit.SYNCBUSY) {}
143143
}
144144

145-
void clock_init(bool has_crystal, uint32_t dfll48m_fine_calibration)
145+
void clock_init(bool has_rtc_crystal, uint32_t xosc_freq, bool xosc_is_crystal, uint32_t dfll48m_fine_calibration)
146146
{
147+
// TODO: support using the external oscillator as the system clock source
148+
// like the sam_d5x_e5x version of this function.
149+
147150
init_clock_source_osc8m();
148-
if (has_crystal) {
151+
if (has_rtc_crystal) {
149152
init_clock_source_xosc32k();
150153
} else {
151154
init_clock_source_osc32k();
152155
}
153156

154-
if (has_crystal) {
157+
if (has_rtc_crystal) {
155158
enable_clock_generator(3, GCLK_GENCTRL_SRC_XOSC32K_Val, 1);
156159
connect_gclk_to_peripheral(3, GCLK_CLKCTRL_ID_DFLL48_Val);
157160
init_clock_source_dfll48m_xosc();
@@ -160,7 +163,7 @@ void clock_init(bool has_crystal, uint32_t dfll48m_fine_calibration)
160163
}
161164

162165
enable_clock_generator(0, GCLK_GENCTRL_SRC_DFLL48M_Val, 1);
163-
if (has_crystal) {
166+
if (has_rtc_crystal) {
164167
enable_clock_generator(2, GCLK_GENCTRL_SRC_XOSC32K_Val, 1);
165168
} else {
166169
enable_clock_generator(2, GCLK_GENCTRL_SRC_OSC32K_Val, 1);

0 commit comments

Comments
 (0)