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