91d74b12142d49fe9896ab7163d8340567478d8c
[fw/altos] / src / samd21 / ao_timer.c
1 /*
2  * Copyright © 2019 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  */
14
15 #include <ao.h>
16
17 #ifndef HAS_TICK
18 #define HAS_TICK 1
19 #endif
20
21 #if HAS_TICK
22 volatile AO_TICK_TYPE ao_tick_count;
23
24 AO_TICK_TYPE
25 ao_time(void)
26 {
27         return ao_tick_count;
28 }
29
30 uint64_t
31 ao_time_ns(void)
32 {
33         AO_TICK_TYPE    before, after;
34         uint32_t        cvr;
35
36         do {
37                 before = ao_tick_count;
38                 cvr = samd21_systick.cvr;
39                 after = ao_tick_count;
40         } while (before != after);
41
42         return (uint64_t) after * (1000000000ULL / AO_HERTZ) +
43                 (uint64_t) cvr * (1000000000ULL / AO_SYSTICK);
44 }
45
46 #if AO_DATA_ALL
47 volatile uint8_t        ao_data_interval = 1;
48 volatile uint8_t        ao_data_count;
49 #endif
50
51 void samd21_systick_isr(void)
52 {
53         ao_arch_release_interrupts();
54         if (samd21_systick.csr & (1 << SAMD21_SYSTICK_CSR_COUNTFLAG)) {
55                 ++ao_tick_count;
56                 ao_task_check_alarm();
57 #if AO_DATA_ALL
58                 if (++ao_data_count == ao_data_interval && ao_data_interval) {
59                         ao_data_count = 0;
60 #if HAS_FAKE_FLIGHT
61                         if (ao_fake_flight_active)
62                                 ao_fake_flight_poll();
63                         else
64 #endif
65                                 ao_adc_poll();
66 #if (AO_DATA_ALL & ~(AO_DATA_ADC))
67                         ao_wakeup((void *) &ao_data_count);
68 #endif
69                 }
70 #endif
71 #ifdef AO_TIMER_HOOK
72                 AO_TIMER_HOOK;
73 #endif
74         }
75 }
76
77 #if HAS_ADC
78 void
79 ao_timer_set_adc_interval(uint8_t interval)
80 {
81         ao_arch_critical(
82                 ao_data_interval = interval;
83                 ao_data_count = 0;
84                 );
85 }
86 #endif
87
88 #define SYSTICK_RELOAD (AO_SYSTICK / AO_HERTZ - 1)
89
90 void
91 ao_timer_init(void)
92 {
93         samd21_systick.csr = 0;
94         samd21_systick.rvr = SYSTICK_RELOAD;
95         samd21_systick.cvr = 0;
96         samd21_systick.csr = ((1 << SAMD21_SYSTICK_CSR_ENABLE) |
97                            (1 << SAMD21_SYSTICK_CSR_TICKINT) |
98                            (SAMD21_SYSTICK_CSR_CLKSOURCE_HCLK_8 << SAMD21_SYSTICK_CSR_CLKSOURCE));
99         /* Set clock to lowest priority */
100         samd21_scb.shpr3 |= 3UL << 30;
101 }
102
103 #endif
104
105
106 void
107 ao_clock_init(void)
108 {
109         /* Set flash wait state to tolerate 48MHz */
110         samd21_nvmctrl.ctrlb |= (1 << SAMD21_NVMCTRL_CTRLB_RWS);
111
112         samd21_pm.apbamask |= ((1 << SAMD21_PM_APBAMASK_GCLK) |
113                                (1 << SAMD21_PM_APBAMASK_SYSCTRL));
114
115         /* Reset gclk */
116         samd21_gclk.ctrl = (1 << SAMD21_GCLK_CTRL_SWRST)
117                 ;
118
119         /* Wait for reset to complete */
120         while ((samd21_gclk.ctrl & (1 << SAMD21_GCLK_CTRL_SWRST)) &&
121                (samd21_gclk.status & (1 << SAMD21_GCLK_STATUS_SYNCBUSY)))
122                 ;
123
124 #ifdef AO_XOSC
125         ao_enable_output(&samd21_port_b, 10, 1);
126         /* Enable xosc (external xtal oscillator) */
127         samd21_sysctrl.xosc = ((SAMD21_SYSCTRL_XOSC_STARTUP_8192 << SAMD21_SYSCTRL_XOSC_STARTUP) |
128                                (0 << SAMD21_SYSCTRL_XOSC_AMPGC) |
129                                (SAMD21_SYSCTRL_XOSC_GAIN_16MHz << SAMD21_SYSCTRL_XOSC_GAIN) |
130                                (0 << SAMD21_SYSCTRL_XOSC_ONDEMAND) |
131                                (1 << SAMD21_SYSCTRL_XOSC_RUNSTDBY) |
132                                (1 << SAMD21_SYSCTRL_XOSC_XTALEN));
133         samd21_sysctrl.xosc |= ((1 << SAMD21_SYSCTRL_XOSC_ENABLE));
134
135         /* Wait for xosc */
136         while ((samd21_sysctrl.pclksr & (1 << SAMD21_SYSCTRL_PCLKSR_XOSCRDY)) == 0)
137                 ;
138
139         ao_enable_output(&samd21_port_b, 11, 1);
140
141         /* program DPLL */
142
143         /* Divide down */
144         samd21_sysctrl.dpllctrlb = (((AO_XOSC_DIV/2 - 1) << SAMD21_SYSCTRL_DPLLCTRLB_DIV) |
145                                     (0 << SAMD21_SYSCTRL_DPLLCTRLB_LBYPASS) |
146                                     (SAMD21_SYSCTRL_DPLLCTRLB_LTIME_DEFAULT << SAMD21_SYSCTRL_DPLLCTRLB_LTIME) |
147                                     (SAMD21_SYSCTRL_DPLLCTRLB_REFCLK_XOSC << SAMD21_SYSCTRL_DPLLCTRLB_REFCLK) |
148                                     (0 << SAMD21_SYSCTRL_DPLLCTRLB_WUF) |
149                                     (1 << SAMD21_SYSCTRL_DPLLCTRLB_LPEN) |
150                                     (SAMD21_SYSCTRL_DPLLCTRLB_FILTER_DEFAULT << SAMD21_SYSCTRL_DPLLCTRLB_FILTER));
151
152         /* Multiply up */
153         samd21_sysctrl.dpllratio = ((AO_XOSC_MUL - 1) << SAMD21_SYSCTRL_DPLLRATIO_LDR);
154
155         /* Always on in run mode, off in standby mode */
156         samd21_sysctrl.dpllctrla = ((0 << SAMD21_SYSCTRL_DPLLCTRLA_ONDEMAND) |
157                                     (0 << SAMD21_SYSCTRL_DPLLCTRLA_RUNSTDBY));
158
159         /* Enable DPLL */
160         samd21_sysctrl.dpllctrla |= (1 << SAMD21_SYSCTRL_DPLLCTRLA_ENABLE);
161
162         /* Wait for the DPLL to be enabled */
163         while ((samd21_sysctrl.dpllstatus & (1 << SAMD21_SYSCTRL_DPLLSTATUS_ENABLE)) == 0)
164                 ;
165
166         /* Wait for the DPLL to be ready */
167         while ((samd21_sysctrl.dpllstatus & (1 << SAMD21_SYSCTRL_DPLLSTATUS_CLKRDY)) == 0)
168                 ;
169
170         samd21_gclk_wait_sync();
171
172         /*
173          * Switch generator 0 (CPU clock) to DPLL
174          */
175
176         /* divide by 1 */
177         samd21_gclk_gendiv(AO_GCLK_SYSCLK, AO_XOSC_GCLK_DIV);
178
179         /* select DPLL as source */
180         samd21_gclk_genctrl(SAMD21_GCLK_GENCTRL_SRC_FDPLL96M, AO_GCLK_FDPLL96M);
181 #endif
182
183 #ifdef AO_DFLL48M
184
185         /*
186          * Enable DFLL48M clock
187          */
188
189         samd21_sysctrl.dfllctrl = (1 << SAMD21_SYSCTRL_DFLLCTRL_ENABLE);
190         samd21_dfll_wait_sync();
191
192 #ifdef AO_XOSC32K
193 #define AO_GCLK_XOSC32K 1
194
195         /* Enable xosc32k (external 32.768kHz oscillator) */
196         samd21_sysctrl.xosc32k = ((6 << SAMD21_SYSCTRL_XOSC32K_STARTUP) |
197                                   (1 << SAMD21_SYSCTRL_XOSC32K_XTALEN) |
198                                   (1 << SAMD21_SYSCTRL_XOSC32K_EN32K));
199
200         /* requires separate store */
201         samd21_sysctrl.xosc32k |= (1 << SAMD21_SYSCTRL_XOSC32K_ENABLE);
202
203         /* Wait for osc */
204         while ((samd21_sysctrl.pclksr & (1 << SAMD21_SYSCTRL_PCLKSR_XOSC32KRDY)) == 0)
205                 ;
206
207         /*
208          * Use xosc32k as source of gclk generator AO_GCLK_XOSC32K
209          */
210
211         samd21_gclk_gendiv(AO_GCLK_XOSC32K, 1);
212         samd21_gclk_genctrl(SAMD21_GCLK_GENCTRL_SRC_XOSC32K, AO_GCLK_XOSC32K);
213
214         /*
215          * Use generator as source for dfm48m reference
216          */
217
218         samd21_gclk_clkctrl(AO_GCLK_XOSC32K, SAMD21_GCLK_CLKCTRL_ID_DFLL48M_REF);
219
220         /* Set multiplier to get as close to 48MHz as we can without going over */
221         samd21_sysctrl.dfllmul = (((31/4) << SAMD21_SYSCTRL_DFLLMUL_CSTEP) |
222                                   ((255/4) << SAMD21_SYSCTRL_DFLLMUL_FSTEP) |
223                                   ((AO_DFLL48M / AO_XOSC32K) << SAMD21_SYSCTRL_DFLLMUL_MUL));
224
225         /* pull out coarse calibration value from rom */
226         uint32_t coarse = ((samd21_aux1.calibration >> SAMD21_AUX1_CALIBRATION_DFLL48M_COARSE_CAL) &
227                            SAMD21_AUX1_CALIBRATION_DFLL48M_COARSE_CAL_MASK);
228
229         samd21_sysctrl.dfllval = ((coarse << SAMD21_SYSCTRL_DFLLVAL_COARSE) |
230                                   (512 << SAMD21_SYSCTRL_DFLLVAL_FINE));
231
232         samd21_sysctrl.dfllctrl = 0;
233         samd21_dfll_wait_sync();
234
235         samd21_sysctrl.dfllctrl = ((1 << SAMD21_SYSCTRL_DFLLCTRL_MODE) |
236                                    (1 << SAMD21_SYSCTRL_DFLLCTRL_ENABLE));
237
238         samd21_dfll_wait_sync();
239         samd21_gclk_wait_sync();
240
241         /* wait for fine lock */
242         while ((samd21_sysctrl.pclksr & (1 << SAMD21_SYSCTRL_PCLKSR_DFLLLCKC)) == 0 ||
243                (samd21_sysctrl.pclksr & (1 << SAMD21_SYSCTRL_PCLKSR_DFLLLCKF)) == 0)
244                 ;
245 #else
246         samd21_sysctrl.dfllmul = (((31/4) << SAMD21_SYSCTRL_DFLLMUL_CSTEP) |
247                                   ((255/4) << SAMD21_SYSCTRL_DFLLMUL_FSTEP) |
248                                   ((AO_DFLL48M / 1000) << SAMD21_SYSCTRL_DFLLMUL_MUL));
249
250         /* pull out coarse calibration value from rom */
251         uint32_t coarse = ((samd21_aux1.calibration >> SAMD21_AUX1_CALIBRATION_DFLL48M_COARSE_CAL) &
252                            SAMD21_AUX1_CALIBRATION_DFLL48M_COARSE_CAL_MASK);
253
254         samd21_sysctrl.dfllval = ((coarse << SAMD21_SYSCTRL_DFLLVAL_COARSE) |
255                                   (512 << SAMD21_SYSCTRL_DFLLVAL_FINE));
256
257         samd21_sysctrl.dfllctrl = 0;
258         samd21_dfll_wait_sync();
259
260         samd21_sysctrl.dfllctrl = ((1 << SAMD21_SYSCTRL_DFLLCTRL_MODE) |
261                                    (1 << SAMD21_SYSCTRL_DFLLCTRL_BPLCKC) |
262                                    (1 << SAMD21_SYSCTRL_DFLLCTRL_CCDIS) |
263                                    (1 << SAMD21_SYSCTRL_DFLLCTRL_USBCRM) |
264                                    (1 << SAMD21_SYSCTRL_DFLLCTRL_ENABLE));
265
266         samd21_dfll_wait_sync();
267         samd21_gclk_wait_sync();
268 #endif
269
270         /*
271          * Switch generator to DFLL48M
272          */
273
274         /* divide by 1 */
275         samd21_gclk_gendiv(AO_GCLK_SYSCLK, 1);
276
277         /* select DFLL48M as source */
278         samd21_gclk_genctrl(SAMD21_GCLK_GENCTRL_SRC_DFLL48M, AO_GCLK_DFLL48M);
279 #endif
280
281         /* Set up all of the clocks to be /1 */
282
283         samd21_pm.cpusel = ((0 << SAMD21_PM_CPUSEL_CPUDIV));
284         samd21_pm.apbasel = ((0 << SAMD21_PM_APBASEL_APBADIV));
285         samd21_pm.apbbsel = ((0 << SAMD21_PM_APBBSEL_APBBDIV));
286         samd21_pm.apbcsel = ((0 << SAMD21_PM_APBCSEL_APBCDIV));
287
288         /* Disable OSC8M */
289         samd21_sysctrl.osc8m &= ~(1UL << SAMD21_SYSCTRL_OSC8M_ENABLE);
290
291         /* Additional misc configuration stuff */
292
293         /* Disable automatic NVM write operations */
294         samd21_nvmctrl.ctrlb |= (1UL << SAMD21_NVMCTRL_CTRLB_MANW);
295
296         ao_gpio_set(&samd21_port_b, 10, 0);
297 }