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