samd21: AO_SPI_0_PA04_PA05_PA06 is FUNC_D
[fw/altos] / src / samd21 / ao_spi_samd21.c
1 /*
2  * Copyright © 2022 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 #include <ao_dma_samd21.h>
17
18 static uint8_t          ao_spi_mutex[SAMD21_NUM_SERCOM];
19 static uint16_t         ao_spi_pin_config[SAMD21_NUM_SERCOM];
20
21 #define SPI_DEBUG       0
22 #define SPI_USE_DMA     1
23
24 struct ao_spi_samd21_info {
25         struct samd21_sercom    *sercom;
26 };
27
28 static const struct ao_spi_samd21_info ao_spi_samd21_info[SAMD21_NUM_SERCOM] = {
29         {
30                 .sercom = &samd21_sercom0,
31         },
32         {
33                 .sercom = &samd21_sercom1,
34         },
35         {
36                 .sercom = &samd21_sercom2,
37         },
38         {
39                 .sercom = &samd21_sercom3,
40         },
41         {
42                 .sercom = &samd21_sercom4,
43         },
44         {
45                 .sercom = &samd21_sercom5,
46         },
47 };
48
49 static uint8_t  spi_dev_null;
50
51 #if SPI_USE_DMA
52
53 static uint8_t  ao_spi_done[SAMD21_NUM_SERCOM];
54
55 static void
56 _ao_spi_recv_dma_done(uint8_t dma_id, void *closure)
57 {
58         uint8_t id = (uint8_t) (uintptr_t) closure;
59
60         (void) dma_id;
61         ao_spi_done[id] = 1;
62         ao_wakeup(&ao_spi_done[id]);
63 }
64
65 static inline uint32_t
66 dma_chctrlb(uint8_t id, bool tx)
67 {
68         uint32_t        chctrlb = 0;
69
70         /* No complicated actions needed */
71         chctrlb |= SAMD21_DMAC_CHCTRLB_CMD_NOACT << SAMD21_DMAC_CHCTRLB_CMD;
72
73         /* Trigger after each byte transferred */
74         chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGACT_BEAT << SAMD21_DMAC_CHCTRLB_TRIGACT;
75
76         /* Set the trigger source */
77         if (tx)
78                 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_TX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC;
79         else
80                 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_RX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC;
81
82         /* RX has priority over TX so that we don't drop incoming bytes */
83         if (tx)
84                 chctrlb |= SAMD21_DMAC_CHCTRLB_LVL_LVL0 << SAMD21_DMAC_CHCTRLB_LVL;
85         else
86                 chctrlb |= SAMD21_DMAC_CHCTRLB_LVL_LVL3 << SAMD21_DMAC_CHCTRLB_LVL;
87
88         /* No events needed */
89         chctrlb |= 0UL << SAMD21_DMAC_CHCTRLB_EVOE;
90         chctrlb |= 0UL << SAMD21_DMAC_CHCTRLB_EVIE;
91
92         /* And no actions either */
93         chctrlb |= SAMD21_DMAC_CHCTRLB_EVACT_NOACT << SAMD21_DMAC_CHCTRLB_EVACT;
94
95         return chctrlb;
96 }
97
98 static inline uint16_t
99 dma_btctrl(bool step, bool tx)
100 {
101         uint16_t        btctrl = 0;
102
103         /* Always step by 1 */
104         btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSIZE_X1 << SAMD21_DMAC_DESC_BTCTRL_STEPSIZE;
105
106         /* Step the source if transmit, otherwise step the dest */
107         if (tx)
108                 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSEL_SRC << SAMD21_DMAC_DESC_BTCTRL_STEPSEL;
109         else
110                 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSEL_DST << SAMD21_DMAC_DESC_BTCTRL_STEPSEL;
111
112         /* Set the increment if stepping */
113         if (tx) {
114                 if (step)
115                         btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
116                 else
117                         btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
118                 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
119         } else {
120                 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
121                 if (step)
122                         btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
123                 else
124                         btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
125         }
126
127         /* byte at a time please */
128         btctrl |= SAMD21_DMAC_DESC_BTCTRL_BEATSIZE_BYTE << SAMD21_DMAC_DESC_BTCTRL_BEATSIZE;
129
130         /*
131          * Watch for interrupts on RX -- we need to wait for the last byte to get received
132          * to know the SPI bus is idle
133          */
134         if (tx)
135                 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_NOACT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT;
136         else
137                 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_INT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT;
138
139         /* don't need any events */
140         btctrl |= SAMD21_DMAC_DESC_BTCTRL_EVOSEL_DISABLE << SAMD21_DMAC_DESC_BTCTRL_EVOSEL;
141
142         /* And make the descriptor valid */
143         btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_VALID;
144
145         return btctrl;
146 }
147
148 static void
149 spi_run(const void *out, void *in, uint16_t len, uint16_t spi_index, bool step_out, bool step_in)
150 {
151         const uint8_t           *o = out;
152         uint8_t                 *i = in;
153         uint8_t                 id = AO_SPI_INDEX(spi_index);
154         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
155
156         ao_arch_block_interrupts();
157         ao_spi_done[id] = 0;
158
159         /*
160          * Stepped addresses to the DMA engine point past the end of
161          * the block
162          */
163         if (step_out)
164                 o += len;
165         if (step_in)
166                 i += len;
167
168         /* read any stuck data */
169         (void) sercom->data;
170
171         _ao_dma_start_transfer(AO_SERCOM_INPUT_DMA_ID(id),
172                                (void *) &sercom->data,
173                                i,
174                                len,
175                                dma_chctrlb(id, false),
176                                dma_btctrl(step_in, false),
177
178                                _ao_spi_recv_dma_done,
179                                (void *) (uintptr_t) id
180                 );
181
182         _ao_dma_start_transfer(AO_SERCOM_OUTPUT_DMA_ID(id),
183                                o,
184                                (void *) &sercom->data,
185                                len,
186                                dma_chctrlb(id, true),
187                                dma_btctrl(step_out, true),
188                                NULL,
189                                NULL
190                 );
191
192         while (ao_spi_done[id] == 0)
193                 ao_sleep(&ao_spi_done[id]);
194
195         _ao_dma_done_transfer(AO_SERCOM_OUTPUT_DMA_ID(id));
196         _ao_dma_done_transfer(AO_SERCOM_INPUT_DMA_ID(id));
197         ao_arch_release_interrupts();
198 }
199
200 #else
201
202 static void
203 spi_run(const void *out, void *in, uint16_t len, uint16_t spi_index, bool step_out, bool step_in)
204 {
205         uint8_t                 id = AO_SPI_INDEX(spi_index);
206         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
207         const uint8_t           *o = out;
208         uint8_t                 *i = in;
209
210         while (len--) {
211 #if SPI_DEBUG
212                 printf("%02x", *o);
213 #endif
214                 sercom->data = *o;
215                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
216                         ;
217                 *i = (uint8_t) sercom->data;
218 #if SPI_DEBUG
219                 printf("\t%02x\n", *i);
220 #endif
221                 if (step_out)
222                         o++;
223                 if (step_in)
224                         i++;
225         }
226 }
227
228 #endif
229
230 void
231 ao_spi_send(const void *block, uint16_t len, uint16_t spi_index)
232 {
233         spi_run(block, &spi_dev_null, len, spi_index, true, false);
234 }
235
236 void
237 ao_spi_send_fixed(uint8_t data, uint16_t len, uint16_t spi_index)
238 {
239         spi_run(&data, &spi_dev_null, len, spi_index, false, false);
240 }
241
242 void
243 ao_spi_recv(void *block, uint16_t len, uint16_t spi_index)
244 {
245         spi_dev_null = 0xff;
246         spi_run(&spi_dev_null, block, len, spi_index, false, true);
247 }
248
249
250 void
251 ao_spi_duplex(const void *out, void *in, uint16_t len, uint16_t spi_index)
252 {
253         spi_run(out, in, len, spi_index, true, true);
254 }
255
256 static void
257 ao_spi_disable_pin_config(uint16_t spi_pin_config)
258 {
259         switch (spi_pin_config) {
260 #if HAS_SPI_0
261         case AO_SPI_PIN_CONFIG(AO_SPI_0_PA08_PA09_PA10):
262                 samd21_port_pmux_clr(&samd21_port_a, 8);        /* MOSI */
263                 samd21_port_pmux_clr(&samd21_port_a, 9);        /* SCLK */
264                 samd21_port_pmux_clr(&samd21_port_a, 10);       /* MISO */
265                 break;
266         case AO_SPI_PIN_CONFIG(AO_SPI_0_PA04_PA05_PA06):
267                 samd21_port_pmux_clr(&samd21_port_a, 4);        /* MOSI */
268                 samd21_port_pmux_clr(&samd21_port_a, 5);        /* SCLK */
269                 samd21_port_pmux_clr(&samd21_port_a, 6);        /* MISO */
270                 break;
271 #endif
272 #if HAS_SPI_3
273         case AO_SPI_PIN_CONFIG(AO_SPI_3_PA22_PA23_PA20):
274                 samd21_port_pmux_clr(&samd21_port_a, 22);       /* MOSI */
275                 samd21_port_pmux_clr(&samd21_port_a, 23);       /* SCLK */
276                 samd21_port_pmux_clr(&samd21_port_a, 20);       /* MISO */
277                 break;
278 #endif
279 #if HAS_SPI_4
280         case AO_SPI_PIN_CONFIG(AO_SPI_4_PB10_PB11_PA12):
281                 samd21_port_pmux_clr(&samd21_port_b, 10);       /* MOSI */
282                 samd21_port_pmux_clr(&samd21_port_b, 11);       /* SCLK */
283                 samd21_port_pmux_clr(&samd21_port_a, 12);       /* MISO */
284                 break;
285 #endif
286 #if HAS_SPI_5
287         case AO_SPI_PIN_CONFIG(AO_SPI_5_PB22_PB23_PB03):
288                 samd21_port_pmux_clr(&samd21_port_b, 22);       /* MOSI */
289                 samd21_port_pmux_clr(&samd21_port_b, 23);       /* SCLK */
290                 samd21_port_pmux_clr(&samd21_port_b, 3);        /* MISO */
291                 break;
292 #endif
293         case 0xffff:
294                 break;
295         }
296 }
297
298 static void
299 ao_spi_enable_pin_config(uint16_t spi_pin_config)
300 {
301         switch (spi_pin_config) {
302 #if HAS_SPI_0
303         case AO_SPI_PIN_CONFIG(AO_SPI_0_PA08_PA09_PA10):
304                 ao_enable_output(&samd21_port_a, 8, 1);
305                 ao_enable_output(&samd21_port_a, 9, 1);
306                 ao_enable_input(&samd21_port_a, 10, AO_MODE_PULL_NONE);
307
308                 samd21_port_pmux_set(&samd21_port_a, 8, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
309                 samd21_port_pmux_set(&samd21_port_a, 9, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
310                 samd21_port_pmux_set(&samd21_port_a, 10, SAMD21_PORT_PMUX_FUNC_C);      /* MISO */
311                 break;
312         case AO_SPI_PIN_CONFIG(AO_SPI_0_PA04_PA05_PA06):
313                 ao_enable_output(&samd21_port_a, 4, 1);
314                 ao_enable_output(&samd21_port_a, 5, 1);
315                 ao_enable_input(&samd21_port_a, 6, AO_MODE_PULL_NONE);
316
317                 samd21_port_pmux_set(&samd21_port_a, 4, SAMD21_PORT_PMUX_FUNC_D);       /* MOSI */
318                 samd21_port_pmux_set(&samd21_port_a, 5, SAMD21_PORT_PMUX_FUNC_D);       /* SCLK */
319                 samd21_port_pmux_set(&samd21_port_a, 6, SAMD21_PORT_PMUX_FUNC_D);       /* MISO */
320                 break;
321 #endif
322 #if HAS_SPI_3
323         case AO_SPI_PIN_CONFIG(AO_SPI_3_PA22_PA23_PA20):
324                 ao_enable_output(&samd21_port_a, 22, 1);
325                 ao_enable_output(&samd21_port_a, 23, 1);
326                 ao_enable_input(&samd21_port_a, 20, AO_MODE_PULL_NONE);
327
328                 samd21_port_pmux_set(&samd21_port_a, 22, SAMD21_PORT_PMUX_FUNC_C);      /* MOSI */
329                 samd21_port_pmux_set(&samd21_port_a, 23, SAMD21_PORT_PMUX_FUNC_C);      /* SCLK */
330                 samd21_port_pmux_set(&samd21_port_a, 20, SAMD21_PORT_PMUX_FUNC_D);      /* MISO */
331                 break;
332 #endif
333 #if HAS_SPI_4
334         case AO_SPI_PIN_CONFIG(AO_SPI_4_PB10_PB11_PA12):
335                 ao_enable_output(&samd21_port_b, 10, 1);
336                 ao_enable_output(&samd21_port_b, 11, 1);
337                 ao_enable_input(&samd21_port_a, 12, AO_MODE_PULL_NONE);
338
339                 samd21_port_pmux_set(&samd21_port_b, 10, SAMD21_PORT_PMUX_FUNC_D);      /* MOSI */
340                 samd21_port_pmux_set(&samd21_port_b, 11, SAMD21_PORT_PMUX_FUNC_D);      /* SCLK */
341                 samd21_port_pmux_set(&samd21_port_a, 12, SAMD21_PORT_PMUX_FUNC_D);      /* MISO */
342                 break;
343 #endif
344 #if HAS_SPI_5
345         case AO_SPI_PIN_CONFIG(AO_SPI_5_PB22_PB23_PB03):
346                 ao_enable_output(&samd21_port_b, 22, 1);
347                 ao_enable_output(&samd21_port_b, 23, 1);
348                 ao_enable_input(&samd21_port_b, 3, AO_MODE_PULL_NONE);
349
350                 samd21_port_pmux_set(&samd21_port_b, 22, SAMD21_PORT_PMUX_FUNC_D);      /* 5.2 MOSI */
351                 samd21_port_pmux_set(&samd21_port_b, 23, SAMD21_PORT_PMUX_FUNC_D);      /* 5.3 SCLK */
352                 samd21_port_pmux_set(&samd21_port_b, 3, SAMD21_PORT_PMUX_FUNC_D);       /* 5.1 MISO */
353                 break;
354 #endif
355         default:
356                 ao_panic(AO_PANIC_SPI);
357                 break;
358         }
359 }
360
361 static void
362 ao_spi_config(uint16_t spi_index, uint32_t baud)
363 {
364         uint16_t                spi_pin_config = AO_SPI_PIN_CONFIG(spi_index);
365         uint8_t                 id = AO_SPI_INDEX(spi_index);
366         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
367
368         if (spi_pin_config != ao_spi_pin_config[id]) {
369                 ao_spi_disable_pin_config(ao_spi_pin_config[id]);
370                 ao_spi_enable_pin_config(spi_pin_config);
371                 ao_spi_pin_config[id] = spi_pin_config;
372         }
373
374         sercom->baud = (uint16_t) baud;
375
376         /* Set spi mode */
377         uint32_t ctrla = sercom->ctrla;
378         ctrla &= ~((1UL << SAMD21_SERCOM_CTRLA_CPOL) |
379                    (1UL << SAMD21_SERCOM_CTRLA_CPHA) |
380                    (SAMD21_SERCOM_CTRLA_DOPO_MASK << SAMD21_SERCOM_CTRLA_DOPO) |
381                    (SAMD21_SERCOM_CTRLA_DIPO_MASK << SAMD21_SERCOM_CTRLA_DIPO));
382         ctrla |= ((AO_SPI_CPOL(spi_index) << SAMD21_SERCOM_CTRLA_CPOL) |
383                   (AO_SPI_CPHA(spi_index) << SAMD21_SERCOM_CTRLA_CPHA) |
384                   (AO_SPI_DOPO(spi_index) << SAMD21_SERCOM_CTRLA_DOPO) |
385                   (AO_SPI_DIPO(spi_index) << SAMD21_SERCOM_CTRLA_DIPO));
386
387         /* finish setup and enable the hardware */
388         ctrla |= (1 << SAMD21_SERCOM_CTRLA_ENABLE);
389
390 #if SPI_DEBUG
391         printf("ctrla %08lx\n", ctrla);
392 #endif
393
394         sercom->ctrla = ctrla;
395
396         while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
397                 ;
398 }
399
400 void
401 ao_spi_get(uint16_t spi_index, uint32_t speed)
402 {
403         uint8_t         id = AO_SPI_INDEX(spi_index);
404
405         ao_mutex_get(&ao_spi_mutex[id]);
406         ao_spi_config(spi_index, speed);
407 }
408
409 void
410 ao_spi_put(uint16_t spi_index)
411 {
412         uint8_t                 id = AO_SPI_INDEX(spi_index);
413         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
414
415         sercom->ctrla &= ~(1UL << SAMD21_SERCOM_CTRLA_ENABLE);
416         while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
417                 ;
418         ao_mutex_put(&ao_spi_mutex[id]);
419 }
420
421 static void
422 ao_spi_init_sercom(uint8_t id)
423 {
424         struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
425
426         /* Send a clock along */
427         samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_SERCOM0_CORE + id);
428
429         samd21_nvic_set_enable(SAMD21_NVIC_ISR_SERCOM0_POS + id);
430         samd21_nvic_set_priority(SAMD21_NVIC_ISR_SERCOM0_POS + id, 4);
431
432         /* Enable */
433         samd21_pm.apbcmask |= (1 << (SAMD21_PM_APBCMASK_SERCOM0 + id));
434
435         /* Reset */
436         sercom->ctrla = (1 << SAMD21_SERCOM_CTRLA_SWRST);
437
438         while ((sercom->ctrla & (1 << SAMD21_SERCOM_CTRLA_SWRST)) ||
439                (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_SWRST)))
440                 ;
441
442         /* set SPI mode */
443         sercom->ctrla = ((SAMD21_SERCOM_CTRLA_DORD_MSB << SAMD21_SERCOM_CTRLA_DORD) |
444                          (0 << SAMD21_SERCOM_CTRLA_CPOL) |
445                          (0 << SAMD21_SERCOM_CTRLA_CPHA) |
446                          (0 << SAMD21_SERCOM_CTRLA_FORM) |
447                          (2 << SAMD21_SERCOM_CTRLA_DIPO) |
448                          (0 << SAMD21_SERCOM_CTRLA_DOPO) |
449                          (0 << SAMD21_SERCOM_CTRLA_IBON) |
450                          (0 << SAMD21_SERCOM_CTRLA_RUNSTDBY) |
451                          (SAMD21_SERCOM_CTRLA_MODE_SPI_HOST << SAMD21_SERCOM_CTRLA_MODE) |
452                          (0 << SAMD21_SERCOM_CTRLA_ENABLE) |
453                          (0 << SAMD21_SERCOM_CTRLA_SWRST));
454
455         sercom->ctrlb = ((1 << SAMD21_SERCOM_CTRLB_RXEN) |
456                          (0 << SAMD21_SERCOM_CTRLB_AMODE) |
457                          (0 << SAMD21_SERCOM_CTRLB_MSSEN) |
458                          (0 << SAMD21_SERCOM_CTRLB_SSDE) |
459                          (0 << SAMD21_SERCOM_CTRLB_PLOADEN) |
460                          (SAMD21_SERCOM_CTRLB_CHSIZE_8 << SAMD21_SERCOM_CTRLB_CHSIZE));
461
462         ao_spi_pin_config[id] = 0xffff;
463 }
464
465 void
466 ao_spi_init(void)
467 {
468 #if HAS_SPI_0
469         ao_spi_init_sercom(0);
470 #endif
471 #if HAS_SPI_1
472         ao_spi_init_sercom(1);
473 #endif
474 #if HAS_SPI_2
475         ao_spi_init_sercom(2);
476 #endif
477 #if HAS_SPI_3
478         ao_spi_init_sercom(3);
479 #endif
480 #if HAS_SPI_4
481         ao_spi_init_sercom(4);
482 #endif
483 #if HAS_SPI_5
484         ao_spi_init_sercom(5);
485 #endif
486 }