diff options
Diffstat (limited to 'lib/chibios-contrib/os/hal/ports/NUMICRO/NUC123/hal_lld.c')
-rw-r--r-- | lib/chibios-contrib/os/hal/ports/NUMICRO/NUC123/hal_lld.c | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/lib/chibios-contrib/os/hal/ports/NUMICRO/NUC123/hal_lld.c b/lib/chibios-contrib/os/hal/ports/NUMICRO/NUC123/hal_lld.c new file mode 100644 index 000000000..4cfd1f777 --- /dev/null +++ b/lib/chibios-contrib/os/hal/ports/NUMICRO/NUC123/hal_lld.c | |||
@@ -0,0 +1,497 @@ | |||
1 | /* | ||
2 | Copyright (C) 2020 Alex Lewontin | ||
3 | Copyright (C) 2019 /u/KeepItUnder | ||
4 | |||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | you may not use this file except in compliance with the License. | ||
7 | You may obtain a copy of the License at | ||
8 | |||
9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | |||
11 | Unless required by applicable law or agreed to in writing, software | ||
12 | distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | See the License for the specific language governing permissions and | ||
15 | limitations under the License. | ||
16 | */ | ||
17 | |||
18 | /** | ||
19 | * @file hal_lld.c | ||
20 | * @brief NUC123 HAL subsystem low level driver source. | ||
21 | * | ||
22 | * @addtogroup HAL | ||
23 | * @{ | ||
24 | */ | ||
25 | |||
26 | #include "hal.h" | ||
27 | |||
28 | /*===========================================================================*/ | ||
29 | /* Driver local definitions. */ | ||
30 | /*===========================================================================*/ | ||
31 | #define FREQ_25MHZ 25000000 | ||
32 | #define FREQ_50MHZ 50000000 | ||
33 | #define FREQ_72MHZ 72000000 | ||
34 | #define FREQ_100MHZ 100000000 | ||
35 | #define FREQ_200MHZ 200000000 | ||
36 | |||
37 | #define CLK_CLKDIV_HCLK(x) (((x)-1) << CLK_CLKDIV_HCLK_N_Pos) | ||
38 | |||
39 | /*===========================================================================*/ | ||
40 | /* Driver exported variables. */ | ||
41 | /*===========================================================================*/ | ||
42 | |||
43 | /*===========================================================================*/ | ||
44 | /* Driver local variables and types. */ | ||
45 | /*===========================================================================*/ | ||
46 | |||
47 | _Bool clock_initialized = FALSE; | ||
48 | |||
49 | uint32_t SystemCoreClock = __HSI; /* System Clock Frequency (Core Clock)*/ | ||
50 | uint32_t CyclesPerUs = (__HSI / 1000000); /* Cycles per micro second */ | ||
51 | uint32_t PllClock = __HSI; /*!< PLL Clock Frequency */ | ||
52 | |||
53 | #if (NUC123_CONFIG_ENABLED == TRUE) | ||
54 | |||
55 | static volatile const uint32_t config0 __attribute__((used, unused, section(".nuc123_config0"))) = NUC123_CONFIG0; | ||
56 | static volatile const uint32_t config1 __attribute__((used, unused, section(".nuc123_config1"))) = NUC123_CONFIG1; | ||
57 | |||
58 | #endif | ||
59 | |||
60 | /*===========================================================================*/ | ||
61 | /* Driver local functions. */ | ||
62 | /*===========================================================================*/ | ||
63 | |||
64 | void SystemCoreClockUpdate(void) /* Get Core Clock Frequency */ | ||
65 | { | ||
66 | /* ToDo: add code to calculate the system frequency based upon the current | ||
67 | register settings. | ||
68 | This function can be used to retrieve the system core clock frequeny | ||
69 | after user changed register sittings. */ | ||
70 | /* SystemCoreClock = SYSTEM_CLOCK; */ | ||
71 | |||
72 | uint32_t clkFreq; | ||
73 | uint32_t PllReg; | ||
74 | |||
75 | uint32_t pllFIN, pllNF, pllNR, pllNO; | ||
76 | |||
77 | /* Update PLL Clock */ | ||
78 | /* PllClock = clks_lld_get_pll_clock_freq(); */ | ||
79 | PllReg = CLK->PLLCON; | ||
80 | |||
81 | if (PllReg & (CLK_PLLCON_PD_Msk | CLK_PLLCON_OE_Msk)) { | ||
82 | PllClock = 0; /* PLL is off. */ | ||
83 | } else { | ||
84 | |||
85 | if (PllReg & 0x00080000ul) { | ||
86 | pllFIN = __HIRC; /* Use HXT for PLL clock */ | ||
87 | } else { | ||
88 | pllFIN = NUC123_HSECLK; /* Use HXT for PLL clock */ | ||
89 | } | ||
90 | |||
91 | if (PllReg & CLK_PLLCON_BP_Msk) { | ||
92 | PllClock = pllFIN; | ||
93 | } else { | ||
94 | switch (((PllReg & CLK_PLLCON_OUT_DV_Msk) >> CLK_PLLCON_OUT_DV_Pos)) { | ||
95 | case 0: /* OUT_DIV == 00 : NO = 1 */ | ||
96 | pllNO = 1; | ||
97 | break; | ||
98 | case 3: /* OUT_DIV == 11 : NO = 4 */ | ||
99 | pllNO = 4; | ||
100 | break; | ||
101 | default: /* OUT_DIV == 01 or 10 : NO = 2 */ | ||
102 | pllNO = 2; | ||
103 | break; | ||
104 | } | ||
105 | |||
106 | pllNF = ((PllReg & CLK_PLLCON_FB_DV_Msk) >> CLK_PLLCON_FB_DV_Pos) + 2; | ||
107 | pllNR = ((PllReg & CLK_PLLCON_IN_DV_Msk) >> CLK_PLLCON_IN_DV_Pos) + 2; | ||
108 | |||
109 | /* Shift right to avoid overflow condition */ | ||
110 | PllClock = (((pllFIN >> 2) * pllNF) / (pllNR * pllNO) << 2); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | /* Pick Clock Source */ | ||
115 | switch (CLK->CLKSEL0 & CLK_CLKSEL0_HCLK_S_Msk) { | ||
116 | case 0: /* External HF Xtal */ | ||
117 | clkFreq = NUC123_HSECLK; | ||
118 | break; | ||
119 | case 1: /* PLL clock / 2 */ | ||
120 | clkFreq = PllClock >> 1; | ||
121 | break; | ||
122 | case 3: /* Internal 10kHz */ | ||
123 | clkFreq = __LIRC; | ||
124 | break; | ||
125 | case 2: /* PLL clock */ | ||
126 | clkFreq = PllClock; | ||
127 | break; | ||
128 | case 7: /* Internal 22.184MHz */ | ||
129 | clkFreq = __HIRC; | ||
130 | break; | ||
131 | default: | ||
132 | clkFreq = 0; | ||
133 | break; | ||
134 | } | ||
135 | |||
136 | SystemCoreClock = clkFreq / ((CLK->CLKDIV & CLK_CLKDIV_HCLK_N_Msk) + 1); | ||
137 | CyclesPerUs = SystemCoreClock / 1000000; | ||
138 | } | ||
139 | |||
140 | /** | ||
141 | * @brief Get PLL clock frequency | ||
142 | * @param None | ||
143 | * @return PLL frequency | ||
144 | * @details This function get PLL frequency. The frequency unit is Hz. | ||
145 | */ | ||
146 | static inline uint32_t get_pll_clock_freq(void) | ||
147 | { | ||
148 | uint32_t PllReg; | ||
149 | uint32_t pllFIN, pllNF, pllNR, pllNO; | ||
150 | |||
151 | PllReg = CLK->PLLCON; | ||
152 | |||
153 | if (PllReg & (CLK_PLLCON_PD_Msk | CLK_PLLCON_OE_Msk)) { | ||
154 | PllClock = 0; /* PLL is in power down mode or fix low */ | ||
155 | } else { | ||
156 | |||
157 | if (PllReg & NUC123_PLLSRC_HSI) { | ||
158 | pllFIN = __HIRC; /* Use HXT for PLL clock */ | ||
159 | } else { | ||
160 | pllFIN = NUC123_HSECLK; /* Use HXT for PLL clock */ | ||
161 | } | ||
162 | |||
163 | if (PllReg & CLK_PLLCON_BP_Msk) { | ||
164 | PllClock = pllFIN; | ||
165 | } else { | ||
166 | switch (((PllReg & CLK_PLLCON_OUT_DV_Msk) >> CLK_PLLCON_OUT_DV_Pos)) { | ||
167 | case 0: /* OUT_DIV == 00 : NO = 1 */ | ||
168 | pllNO = 1; | ||
169 | break; | ||
170 | case 3: /* OUT_DIV == 11 : NO = 4 */ | ||
171 | pllNO = 4; | ||
172 | break; | ||
173 | default: /* OUT_DIV == 01 or 10 : NO = 2 */ | ||
174 | pllNO = 2; | ||
175 | break; | ||
176 | } | ||
177 | |||
178 | pllNF = ((PllReg & CLK_PLLCON_FB_DV_Msk) >> CLK_PLLCON_FB_DV_Pos) + 2; | ||
179 | pllNR = ((PllReg & CLK_PLLCON_IN_DV_Msk) >> CLK_PLLCON_IN_DV_Pos) + 2; | ||
180 | |||
181 | /* Shift to avoid overflow condition */ | ||
182 | PllClock = (((pllFIN >> 2) * pllNF) / (pllNR * pllNO) << 2); | ||
183 | } | ||
184 | } | ||
185 | |||
186 | return PllClock; | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * @brief Wait for stable clock | ||
191 | * | ||
192 | * @description Always wait around 300ms for clock to be stable | ||
193 | * | ||
194 | */ | ||
195 | static uint32_t wait_for_clock_ready(uint32_t clkMask) | ||
196 | { | ||
197 | int32_t timeout = 2180000; | ||
198 | |||
199 | while (timeout-- > 0) { | ||
200 | if ((CLK->CLKSTATUS & clkMask) == clkMask) { | ||
201 | return 1; | ||
202 | } | ||
203 | } | ||
204 | |||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | /** @brief Set system HCLK | ||
209 | * | ||
210 | * @description Setup HCLK source and divider | ||
211 | * | ||
212 | * Always switch to a known stable clock source before changing a | ||
213 | * system clock, to avoid issues related to the original clock's | ||
214 | * speed/settings. | ||
215 | * | ||
216 | */ | ||
217 | static void set_HCLK(uint32_t clkSource, uint32_t clkDivider) | ||
218 | { | ||
219 | uint32_t stableHIRC; | ||
220 | |||
221 | /* Read HIRC clock source stable flag */ | ||
222 | stableHIRC = CLK->CLKSTATUS & CLK_CLKSTATUS_OSC22M_STB_Msk; | ||
223 | |||
224 | /* Setup __HIRC */ | ||
225 | CLK->PWRCON |= CLK_PWRCON_OSC22M_EN_Msk; | ||
226 | |||
227 | wait_for_clock_ready(CLK_CLKSTATUS_OSC22M_STB_Msk); | ||
228 | |||
229 | /* Use __HIRC as HCLK, temporarily */ | ||
230 | CLK->CLKSEL0 = | ||
231 | (CLK->CLKSEL0 & (~CLK_CLKSEL0_HCLK_S_Msk)) | NUC123_HCLKSRC_HSI; | ||
232 | |||
233 | /* Set new clock divider */ | ||
234 | CLK->CLKDIV = (CLK->CLKDIV & (~CLK_CLKDIV_HCLK_N_Msk)) | clkDivider; | ||
235 | |||
236 | /* Switch HCLK to new HCLK source */ | ||
237 | CLK->CLKSEL0 = (CLK->CLKSEL0 & (~CLK_CLKSEL0_HCLK_S_Msk)) | clkSource; | ||
238 | |||
239 | /* Update System Core Clock */ | ||
240 | SystemCoreClockUpdate(); | ||
241 | |||
242 | /* Disable HIRC if HIRC was disabled before we started */ | ||
243 | if (stableHIRC == 0) { | ||
244 | CLK->PWRCON &= ~CLK_PWRCON_OSC22M_EN_Msk; | ||
245 | } | ||
246 | } | ||
247 | |||
248 | #if NUC123_PLL_ENABLED | ||
249 | static uint32_t enable_pll(uint32_t pllSrc, uint32_t pllFreq) | ||
250 | { | ||
251 | /* Disable PLL first to avoid unstable when setting PLL. */ | ||
252 | CLK->PLLCON = CLK_PLLCON_PD_Msk; | ||
253 | |||
254 | /* Check and setup correct clock source */ | ||
255 | switch (pllSrc) { | ||
256 | case NUC123_PLLSRC_HSE: | ||
257 | /* Use HXT clock */ | ||
258 | CLK->PWRCON |= CLK_PWRCON_XTL12M_EN_Msk; | ||
259 | |||
260 | /* Wait for stable HXT */ | ||
261 | wait_for_clock_ready(CLK_CLKSTATUS_XTL12M_STB_Msk); | ||
262 | |||
263 | break; | ||
264 | case NUC123_PLLSRC_HSI: | ||
265 | /* Use HIRC clock */ | ||
266 | CLK->PWRCON |= CLK_PWRCON_OSC22M_EN_Msk; | ||
267 | |||
268 | /* Wait for stable HIRC */ | ||
269 | wait_for_clock_ready(CLK_CLKSTATUS_OSC22M_STB_Msk); | ||
270 | |||
271 | break; | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * Calculate best PLL variables from requested frequency | ||
276 | * | ||
277 | * See NUC123 Technical Reference Manual 5.4.8 PLL Control Register Description, page 124 | ||
278 | * | ||
279 | * NF 1 | ||
280 | * FOUT = FIN x -- x -- | ||
281 | * NR NO | ||
282 | * | ||
283 | */ | ||
284 | |||
285 | uint32_t NO = 0; | ||
286 | uint32_t NR = 0; | ||
287 | uint32_t clkCalc = 0; | ||
288 | |||
289 | /* Set "NO" for requested frequency */ | ||
290 | /* We're using "NO" first to set the PLLCON - so make it "NO" - 1; */ | ||
291 | if (pllFreq >= FREQ_25MHZ && pllFreq <= FREQ_50MHZ) { | ||
292 | /* Low frequency - use full variable headroom */ | ||
293 | pllFreq <<= 2; | ||
294 | NO = 3; | ||
295 | } else if (pllFreq > FREQ_50MHZ && pllFreq <= FREQ_100MHZ) { | ||
296 | /* Medium frequency - use full variable headroom */ | ||
297 | pllFreq <<= 1; | ||
298 | NO = 1; | ||
299 | } else if (pllFreq > FREQ_100MHZ && pllFreq <= FREQ_200MHZ) { | ||
300 | /* High frequency - full variable headroom already used */ | ||
301 | NO = 0; | ||
302 | } else { | ||
303 | /* Frequency out of range - use default PLL settings | ||
304 | * | ||
305 | * See NUC123 Technical Reference Manual PLL COntrol Register Description, page 124 | ||
306 | * The default value: 0xC22E | ||
307 | * FIN = 12 MHz | ||
308 | * NR = (1+2) = 3 | ||
309 | * NF = (46+2) = 48 | ||
310 | * NO = 4 | ||
311 | * FOUT = 12/4 x 48 x 1/3 = 48 MHz | ||
312 | */ | ||
313 | if (pllSrc == NUC123_PLLSRC_HSE) { | ||
314 | CLK->PLLCON = 0xC22E; | ||
315 | } else { | ||
316 | CLK->PLLCON = 0xD66F; | ||
317 | } | ||
318 | |||
319 | /* Wait for stable PLL clock */ | ||
320 | wait_for_clock_ready(CLK_CLKSTATUS_PLL_STB_Msk); | ||
321 | |||
322 | return get_pll_clock_freq(); | ||
323 | } | ||
324 | |||
325 | /* Setup "NR" and clkCalc */ | ||
326 | switch (pllSrc) { | ||
327 | case NUC123_PLLSRC_HSE: | ||
328 | NR = 2; | ||
329 | clkCalc = NUC123_HSECLK; | ||
330 | break; | ||
331 | case NUC123_PLLSRC_HSI: | ||
332 | NR = 4; | ||
333 | clkCalc = __HIRC; | ||
334 | break; | ||
335 | } | ||
336 | |||
337 | /** | ||
338 | * Loop to calculate best/lowest NR (between 0 or 2 and 31) and best/lowest NF (between 0 and 511) | ||
339 | * | ||
340 | * Best results are off-by-2 until final equation calculation (to allow use in PLLCON) | ||
341 | * | ||
342 | */ | ||
343 | uint32_t bestNR = 0; | ||
344 | uint32_t bestNF = 0; | ||
345 | uint32_t minLimit = -1; | ||
346 | |||
347 | while (NR <= 33) { | ||
348 | uint32_t tmpCalc1 = clkCalc / NR; | ||
349 | |||
350 | if (tmpCalc1 > 1600000 && tmpCalc1 < 16000000) { | ||
351 | uint32_t NF = 2; | ||
352 | |||
353 | while (NF <= 513) { | ||
354 | uint32_t tmpCalc2 = tmpCalc1 * NF; | ||
355 | |||
356 | if (tmpCalc2 >= 100000000 && tmpCalc2 <= 200000000) { | ||
357 | uint32_t tmpCalc3; | ||
358 | |||
359 | if (tmpCalc2 > pllFreq) { | ||
360 | tmpCalc3 = tmpCalc2 - pllFreq; | ||
361 | } else { | ||
362 | tmpCalc3 = pllFreq - tmpCalc2; | ||
363 | } | ||
364 | |||
365 | if (tmpCalc3 < minLimit) { | ||
366 | minLimit = tmpCalc3; | ||
367 | bestNF = NF; | ||
368 | bestNR = NR; | ||
369 | |||
370 | /* Stop NF calc loop when minLimit tends back to 0 */ | ||
371 | if (minLimit == 0) | ||
372 | break; | ||
373 | } | ||
374 | } | ||
375 | |||
376 | NF++; | ||
377 | } | ||
378 | } | ||
379 | |||
380 | NR++; | ||
381 | } | ||
382 | |||
383 | /* Enable and apply new PLL setting. */ | ||
384 | CLK->PLLCON = pllSrc | (NO << 14) | ((bestNR - 2) << 9) | (bestNF - 2); | ||
385 | |||
386 | /* Wait for stable PLL clock */ | ||
387 | wait_for_clock_ready(CLK_CLKSTATUS_PLL_STB_Msk); | ||
388 | |||
389 | /* Return equation result */ | ||
390 | return (clkCalc / ((NO + 1) * bestNR) * bestNF); | ||
391 | } | ||
392 | |||
393 | /** @brief Set Core Clock | ||
394 | * | ||
395 | * @description Set the core system clock some reference speed (Hz). | ||
396 | * This should be between 25MHz and 72MHz for the NUC123SD4AN0. | ||
397 | * | ||
398 | * Use either the HXT (exact) or HIRC (nearest using 22.1184MHz) | ||
399 | * as the clock source. | ||
400 | * | ||
401 | */ | ||
402 | static uint32_t set_core_clock(uint32_t clkCore) | ||
403 | { | ||
404 | uint32_t stableHIRC; | ||
405 | |||
406 | /* Read HIRC clock source stable flag */ | ||
407 | stableHIRC = CLK->CLKSTATUS & CLK_CLKSTATUS_OSC22M_STB_Msk; | ||
408 | |||
409 | /* Setup __HIRC */ | ||
410 | CLK->PWRCON |= CLK_PWRCON_OSC22M_EN_Msk; | ||
411 | |||
412 | wait_for_clock_ready(CLK_CLKSTATUS_OSC22M_STB_Msk); | ||
413 | |||
414 | /* Use __HIRC as HCLK temporarily */ | ||
415 | CLK->CLKSEL0 |= CLK_CLKSEL0_HCLK_S_Msk; | ||
416 | CLK->CLKDIV &= (~CLK_CLKDIV_HCLK_N_Msk); | ||
417 | |||
418 | /* Is HXT stable ? */ | ||
419 | if (CLK->CLKSTATUS & CLK_CLKSTATUS_XTL12M_STB_Msk) { | ||
420 | /* Use NUC123_HSECLK as PLL source */ | ||
421 | clkCore = enable_pll(NUC123_PLLSRC_HSE, (2 * clkCore)); | ||
422 | } else { | ||
423 | /* Use __HIRC as PLL source */ | ||
424 | clkCore = enable_pll(NUC123_PLLSRC_HSI, (2 * clkCore)); | ||
425 | |||
426 | /* Read HIRC clock source stable flag again (since we're using it now) */ | ||
427 | stableHIRC = CLK->CLKSTATUS & CLK_CLKSTATUS_OSC22M_STB_Msk; | ||
428 | } | ||
429 | |||
430 | /* Set HCLK clock source to PLL */ | ||
431 | set_HCLK(NUC123_HCLKSRC_PLL_2, CLK_CLKDIV_HCLK(1)); | ||
432 | |||
433 | /* Disable HIRC if HIRC was disabled before we started */ | ||
434 | if (stableHIRC == 0) { | ||
435 | CLK->PWRCON &= ~CLK_PWRCON_OSC22M_EN_Msk; | ||
436 | } | ||
437 | |||
438 | /* Return actual HCLK frequency is PLL frequency divide 2 */ | ||
439 | return (clkCore >> 1); | ||
440 | } | ||
441 | #endif | ||
442 | |||
443 | /*===========================================================================*/ | ||
444 | /* Driver interrupt handlers. */ | ||
445 | /*===========================================================================*/ | ||
446 | |||
447 | /*===========================================================================*/ | ||
448 | /* Driver exported functions. */ | ||
449 | /*===========================================================================*/ | ||
450 | |||
451 | /** | ||
452 | * @brief Low level HAL driver initialization. | ||
453 | * | ||
454 | * @notapi | ||
455 | */ | ||
456 | void hal_lld_init(void) | ||
457 | { | ||
458 | if (!clock_initialized) { | ||
459 | NUC123_clock_init(); | ||
460 | } | ||
461 | } | ||
462 | |||
463 | void NUC123_clock_init(void) | ||
464 | { | ||
465 | clock_initialized = TRUE; | ||
466 | UNLOCKREG(); | ||
467 | |||
468 | /* Always initialize HSI and go from there, things can change later */ | ||
469 | /* TODO: Technically this could also be the crystal, figure out how to allow | ||
470 | * config in linker? */ | ||
471 | /* Enable HSI */ | ||
472 | CLK->PWRCON |= CLK_PWRCON_OSC22M_EN_Msk; | ||
473 | wait_for_clock_ready(CLK_CLKSTATUS_OSC22M_STB_Msk); | ||
474 | |||
475 | set_HCLK(NUC123_HCLKSRC_HSI, CLK_CLKDIV_HCLK(1)); | ||
476 | |||
477 | #if NUC123_HSE_ENABLED | ||
478 | /* SYS->GPF_MFP |= (SYS_GPF_MFP_PF0_XT1_OUT | SYS_GPF_MFP_PF1_XT1_IN); */ | ||
479 | SYS->GPF_MFP |= (SYS_GPF_MFP_GPF_MFP0_Msk | SYS_GPF_MFP_GPF_MFP1_Msk); | ||
480 | |||
481 | CLK->PWRCON |= CLK_PWRCON_XTL12M_EN_Msk; | ||
482 | wait_for_clock_ready(CLK_CLKSTATUS_XTL12M_STB_Msk); | ||
483 | #endif /* NUC123_HSE_ENABLED */ | ||
484 | |||
485 | #if NUC123_LSI_ENABLED | ||
486 | CLK->PWRCON |= CLK_PWRCON_IRC10K_EN_Msk; | ||
487 | wait_for_clock_ready(CLK_CLKSTATUS_IRC10K_STB_Msk); | ||
488 | #endif /* NUC123_LSI_ENABLED */ | ||
489 | |||
490 | #if NUC123_PLL_ENABLED | ||
491 | set_core_clock(NUC123_HCLK); | ||
492 | #endif /* NUC123_PLL_ENABLED */ | ||
493 | |||
494 | LOCKREG(); | ||
495 | } | ||
496 | |||
497 | /** @} */ | ||