1 /*
2  * Copyright (c) 2021 Argentum Systems Ltd.
3  * Copyright (c) 2023-2025 Gerson Fernando Budke <nandojve@gmail.com>
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @file
10  * @brief Atmel SAML MCU series initialization code
11  */
12 
13 #include <zephyr/device.h>
14 #include <zephyr/init.h>
15 #include <zephyr/kernel.h>
16 #include <soc.h>
17 #include <cmsis_core.h>
18 
19 /* clang-format off */
20 
21 /* the SAML21 currently operates only in Performance Level 2... sleep
22  * and low-power operation are not currently supported by the BSP
23  *
24  * the CPU clock will be configured to 48 MHz, and run via DFLL48M.
25  *
26  *   Reference -> GCLK Gen 1 -> DFLL48M -> GCLK Gen 0 -> GCLK_MAIN
27  *
28  * GCLK Gen 0 -> GCLK_MAIN @ 48 Mhz
29  * GCLK Gen 1 -> DFLL48M (variable)
30  * GCLK Gen 2 -> USB @ 48 MHz
31  * GCLK Gen 3 -> ADC @ 24 MHz (further /2 in the ADC peripheral)
32  * GCLK Gen 4 -> RTC @ reserved
33  */
34 
gclk_reset(void)35 static inline void gclk_reset(void)
36 {
37 	/* by default, OSC16M will be enabled at 4 MHz, and the CPU will
38 	 * run from it. to permit initialization, the CPU is temporarily
39 	 * clocked from OSCULP32K, and OSC16M is disabled
40 	 */
41 	GCLK->GENCTRL[0].bit.SRC = GCLK_GENCTRL_SRC_OSCULP32K_Val;
42 	OSCCTRL->OSC16MCTRL.bit.ENABLE = 0;
43 }
44 
45 #if !CONFIG_SOC_ATMEL_SAML_OSC32K
46 #define osc32k_init()
47 #else
osc32k_init(void)48 static inline void osc32k_init(void)
49 {
50 	uint32_t cal;
51 
52 	/* OSC32KCAL is in NVMCTRL_OTP5[12:6] */
53 	cal = *((uint32_t *)NVMCTRL_OTP5);
54 	cal >>= 6;
55 	cal &= (1 << 7) - 1;
56 
57 	OSC32KCTRL->OSC32K.reg = 0
58 		|  OSC32KCTRL_OSC32K_CALIB(cal)
59 		|  OSC32KCTRL_OSC32K_STARTUP(0x5) /* 34 cycles / ~1.038ms */
60 		| !OSC32KCTRL_OSC32K_ONDEMAND
61 		|  OSC32KCTRL_OSC32K_RUNSTDBY
62 		|  OSC32KCTRL_OSC32K_EN32K
63 		|  OSC32KCTRL_OSC32K_EN1K
64 		|  OSC32KCTRL_OSC32K_ENABLE;
65 
66 	/* wait for ready */
67 	while (!OSC32KCTRL->STATUS.bit.OSC32KRDY) {
68 	}
69 }
70 #endif
71 
72 #if !CONFIG_SOC_ATMEL_SAML_XOSC32K
73 #define xosc32k_init()
74 #else
xosc32k_init(void)75 static inline void xosc32k_init(void)
76 {
77 	OSC32KCTRL->XOSC32K.reg = 0
78 		|  OSC32KCTRL_XOSC32K_STARTUP(0x1) /* 4096 cycles / ~0.13s */
79 		| !OSC32KCTRL_XOSC32K_ONDEMAND
80 		|  OSC32KCTRL_XOSC32K_RUNSTDBY
81 		|  OSC32KCTRL_XOSC32K_EN32K
82 		|  OSC32KCTRL_XOSC32K_EN1K
83 #if CONFIG_SOC_ATMEL_SAML_XOSC32K_CRYSTAL
84 		|  OSC32KCTRL_XOSC32K_XTALEN
85 #endif
86 		|  OSC32KCTRL_XOSC32K_ENABLE;
87 
88 	/* wait for ready */
89 	while (!OSC32KCTRL->STATUS.bit.XOSC32KRDY) {
90 	}
91 }
92 #endif
93 
94 #if !CONFIG_SOC_ATMEL_SAML_OSC16M
95 #define osc16m_init()
96 #else
osc16m_init(void)97 static inline void osc16m_init(void)
98 {
99 	OSCCTRL->OSC16MCTRL.reg = 0
100 		| !OSCCTRL_OSC16MCTRL_ONDEMAND
101 		|  OSCCTRL_OSC16MCTRL_RUNSTDBY
102 		|  OSCCTRL_OSC16MCTRL_FSEL_16
103 		|  OSCCTRL_OSC16MCTRL_ENABLE;
104 
105 	/* wait for ready */
106 	while (!OSCCTRL->STATUS.bit.OSC16MRDY) {
107 	}
108 }
109 #endif
110 
111 /* TODO: use CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC ?? */
dfll48m_init(void)112 static inline void dfll48m_init(void)
113 {
114 	uint32_t cal;
115 
116 	/* setup the reference clock (if any) */
117 	GCLK->GENCTRL[1].reg = 0
118 #if CONFIG_SOC_ATMEL_SAML_OSC32K_AS_MAIN
119 		|  GCLK_GENCTRL_SRC_OSC32K
120 #elif CONFIG_SOC_ATMEL_SAML_XOSC32K_AS_MAIN
121 		|  GCLK_GENCTRL_SRC_XOSC32K
122 #elif CONFIG_SOC_ATMEL_SAML_OSC16M_AS_MAIN
123 		/* configure Fout = Fin / 2^(DIV+1) = 31.25 kHz
124 		 * Fgclk_dfll48m_ref max is 33 kHz
125 		 */
126 		|  GCLK_GENCTRL_DIV(8)
127 		|  GCLK_GENCTRL_DIVSEL
128 		|  GCLK_GENCTRL_SRC_OSC16M
129 #endif
130 #if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
131 		|  GCLK_GENCTRL_RUNSTDBY
132 		|  GCLK_GENCTRL_GENEN
133 #endif
134 		;
135 
136 #if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
137 	/* configure and enable the generator & peripheral channel */
138 	GCLK->PCHCTRL[0].reg = 0
139 		|  GCLK_PCHCTRL_CHEN
140 		|  GCLK_PCHCTRL_GEN_GCLK1;
141 #endif
142 
143 	/* --- */
144 
145 	/* if the target frequency is 48 MHz, then the calibration value can be used to
146 	 * decrease the time until the coarse lock is acquired. this is loaded from
147 	 * NVMCTRL_OTP5[31:26]
148 	 */
149 	cal = *((uint32_t *)NVMCTRL_OTP5);
150 	cal >>= 26;
151 	cal &= (1 << 6) - 1;
152 
153 	OSCCTRL->DFLLCTRL.reg = 0
154 		|  OSCCTRL_DFLLCTRL_QLDIS
155 		| !OSCCTRL_DFLLCTRL_ONDEMAND
156 		|  OSCCTRL_DFLLCTRL_RUNSTDBY
157 #if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
158 		|  OSCCTRL_DFLLCTRL_MODE
159 #endif
160 		;
161 
162 	OSCCTRL->DFLLVAL.reg = 0
163 		|  OSCCTRL_DFLLVAL_COARSE(cal)
164 		|  OSCCTRL_DFLLVAL_FINE(512) /* use 50% */
165 		;
166 
167 	OSCCTRL->DFLLMUL.reg = 0
168 		/* use 25% of maximum value for the coarse and fine step
169 		 * ... I couldn't find details on the inner workings of the DFLL, or any
170 		 * example values for these - I have seen others using ~50%. hopefully these
171 		 * values will provide a good balance between startup time and overshoot
172 		 */
173 		|  OSCCTRL_DFLLMUL_CSTEP(16)
174 		|  OSCCTRL_DFLLMUL_FSTEP(256)
175 #if CONFIG_SOC_ATMEL_SAML_OSC32K_AS_MAIN || CONFIG_SOC_ATMEL_SAML_XOSC32K_AS_MAIN
176 		/* use a 32.768 kHz reference ... 48e6 / 32,768 = 1,464.843... */
177 		|  OSCCTRL_DFLLMUL_MUL(1465)
178 #elif CONFIG_SOC_ATMEL_SAML_OSC16M_AS_MAIN
179 		/* use a 16 MHz -> 31.25 kHz reference... 48e6 / 31,250 = 1,536
180 		 * a small value can make the DFLL unstable, hence not using the
181 		 * 16 MHz source directly
182 		 */
183 		|  OSCCTRL_DFLLMUL_MUL(1536)
184 #endif
185 		;
186 
187 	/* --- */
188 
189 	/* enable */
190 	while (!OSCCTRL->STATUS.bit.DFLLRDY) {
191 	}
192 	OSCCTRL->DFLLCTRL.bit.ENABLE = 1;
193 
194 #if !CONFIG_SOC_ATMEL_SAML_OPENLOOP_AS_MAIN
195 	/* wait for ready... note in open loop mode, we won't get a lock */
196 	while (!OSCCTRL->STATUS.bit.DFLLLCKC || !OSCCTRL->STATUS.bit.DFLLLCKF) {
197 	}
198 #endif
199 }
200 
flash_waitstates_init(void)201 static inline void flash_waitstates_init(void)
202 {
203 	/* PL2, >= 2.7v, 48MHz = 2 wait states */
204 	NVMCTRL->CTRLB.bit.RWS = 2;
205 }
206 
pm_init(void)207 static inline void pm_init(void)
208 {
209 	PM->PLCFG.bit.PLDIS = 0;
210 	PM->PLCFG.bit.PLSEL = 2;
211 }
212 
gclk_main_configure(void)213 static inline void gclk_main_configure(void)
214 {
215 	/* finally, switch the CPU over to run from DFLL48M */
216 	GCLK->GENCTRL[0].bit.SRC = GCLK_GENCTRL_SRC_DFLL48M_Val;
217 }
218 
219 #if !CONFIG_USB_DC_SAM0
220 #define gclk_usb_configure()
221 #else
gclk_usb_configure(void)222 static inline void gclk_usb_configure(void)
223 {
224 	GCLK->GENCTRL[2].reg = 0
225 		| GCLK_GENCTRL_SRC_DFLL48M
226 		| GCLK_GENCTRL_DIV(1)
227 		| GCLK_GENCTRL_GENEN;
228 }
229 #endif
230 
231 #if !CONFIG_ADC_SAM0
232 #define gclk_adc_configure()
233 #else
gclk_adc_configure(void)234 static inline void gclk_adc_configure(void)
235 {
236 	GCLK->GENCTRL[3].reg = 0
237 		| GCLK_GENCTRL_SRC_DFLL48M
238 		| GCLK_GENCTRL_DIV(2)
239 		| GCLK_GENCTRL_GENEN;
240 }
241 #endif
242 
243 #if CONFIG_SOC_ATMEL_SAML_DEBUG_PAUSE
pause_for_debug(void)244 static inline void pause_for_debug(void)
245 {
246 	/* for some reason, when attempting to flash / debug the target, the operations
247 	 * will time out... I suspect this is due to clock configuration, so instead of
248 	 * doing this immediately, we defer startup for a while to permit the debugger
249 	 * to jump in and interrupt us. ick
250 	 */
251 	for (uint32_t i = 0; i < 10000; i += 1) {
252 		__asm__ volatile ("nop\n");
253 	}
254 }
255 #else
pause_for_debug(void)256 static inline void pause_for_debug(void) {}
257 #endif
258 
soc_reset_hook(void)259 void soc_reset_hook(void)
260 {
261 	pause_for_debug();
262 
263 	gclk_reset();
264 	osc32k_init();
265 	xosc32k_init();
266 	osc16m_init();
267 	dfll48m_init();
268 	flash_waitstates_init();
269 	pm_init();
270 	gclk_main_configure();
271 	gclk_usb_configure();
272 	gclk_adc_configure();
273 }
274 
275 /* clang-format on */
276