@@ -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) {
214280static 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 :
0 commit comments