samd21: Use DMA for SPI send
[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 void
69 ao_spi_send(const void *block, uint16_t len, uint8_t spi_index)
70 {
71         uint8_t                 id = AO_SPI_INDEX(spi_index);
72         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
73 #if USE_DMA
74
75         ao_arch_block_interrupts();
76         ao_spi_done[id] = 0;
77         _ao_dma_start_transfer(AO_SAMD21_SPI_MISO_DMA_ID,
78                                (void *) &sercom->data,
79                                &spi_dev_null,
80                                len,
81
82                                (SAMD21_DMAC_CHCTRLB_CMD_NOACT << SAMD21_DMAC_CHCTRLB_CMD) |
83                                (SAMD21_DMAC_CHCTRLB_TRIGACT_BEAT << SAMD21_DMAC_CHCTRLB_TRIGACT) |
84                                (SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_RX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC) |
85                                (SAMD21_DMAC_CHCTRLB_LVL_LVL3 << SAMD21_DMAC_CHCTRLB_LVL) |
86                                (0UL << SAMD21_DMAC_CHCTRLB_EVOE) |
87                                (0UL << SAMD21_DMAC_CHCTRLB_EVIE) |
88                                (SAMD21_DMAC_CHCTRLB_EVACT_NOACT << SAMD21_DMAC_CHCTRLB_EVACT),
89
90                                (SAMD21_DMAC_DESC_BTCTRL_STEPSIZE_X1 << SAMD21_DMAC_DESC_BTCTRL_STEPSIZE) |
91                                (SAMD21_DMAC_DESC_BTCTRL_STEPSEL_DST << SAMD21_DMAC_DESC_BTCTRL_STEPSEL) |
92                                (0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC) |
93                                (0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC) |
94                                (SAMD21_DMAC_DESC_BTCTRL_BEATSIZE_BYTE << SAMD21_DMAC_DESC_BTCTRL_BEATSIZE) |
95                                (SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_INT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT) |
96                                (SAMD21_DMAC_DESC_BTCTRL_EVOSEL_DISABLE << SAMD21_DMAC_DESC_BTCTRL_EVOSEL) |
97                                (1UL << SAMD21_DMAC_DESC_BTCTRL_VALID),
98
99                                _ao_spi_recv_dma_done,
100                                (void *) (uintptr_t) id
101                 );
102
103         _ao_dma_start_transfer(AO_SAMD21_SPI_MOSI_DMA_ID,
104                                (uint8_t *) block + len,
105                                (void *) &sercom->data,
106                                len,
107
108                                (SAMD21_DMAC_CHCTRLB_CMD_NOACT << SAMD21_DMAC_CHCTRLB_CMD) |
109                                (SAMD21_DMAC_CHCTRLB_TRIGACT_BEAT << SAMD21_DMAC_CHCTRLB_TRIGACT) |
110                                (SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_TX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC) |
111                                (SAMD21_DMAC_CHCTRLB_LVL_LVL2 << SAMD21_DMAC_CHCTRLB_LVL) |
112                                (0UL << SAMD21_DMAC_CHCTRLB_EVOE) |
113                                (0UL << SAMD21_DMAC_CHCTRLB_EVIE) |
114                                (SAMD21_DMAC_CHCTRLB_EVACT_NOACT << SAMD21_DMAC_CHCTRLB_EVACT),
115
116                                (SAMD21_DMAC_DESC_BTCTRL_STEPSIZE_X1 << SAMD21_DMAC_DESC_BTCTRL_STEPSIZE) |
117                                (SAMD21_DMAC_DESC_BTCTRL_STEPSEL_SRC << SAMD21_DMAC_DESC_BTCTRL_STEPSEL) |
118                                (0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC) |
119                                (1UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC) |
120                                (SAMD21_DMAC_DESC_BTCTRL_BEATSIZE_BYTE << SAMD21_DMAC_DESC_BTCTRL_BEATSIZE) |
121                                (SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_NOACT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT) |
122                                (SAMD21_DMAC_DESC_BTCTRL_EVOSEL_DISABLE << SAMD21_DMAC_DESC_BTCTRL_EVOSEL) |
123                                (1 << SAMD21_DMAC_DESC_BTCTRL_VALID),
124
125                                NULL,
126                                NULL
127                 );
128
129         while (ao_spi_done[id] == 0)
130                 ao_sleep(&ao_spi_done[id]);
131
132         _ao_dma_done_transfer(AO_SAMD21_SPI_MOSI_DMA_ID);
133         _ao_dma_done_transfer(AO_SAMD21_SPI_MISO_DMA_ID);
134         ao_arch_release_interrupts();
135 #else
136         const uint8_t *b = block;
137
138         while (len--) {
139                 sercom->data = *b++;
140                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
141                         ;
142                 (void) sercom->data;
143         }
144 #endif
145 }
146
147
148 void
149 ao_spi_recv(void *block, uint16_t len, uint8_t spi_index)
150 {
151         uint8_t                 id = AO_SPI_INDEX(spi_index);
152         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
153
154         uint8_t *b = block;
155
156         while (len--) {
157                 sercom->data = 0xff;
158                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
159                         ;
160                 *b++ = (uint8_t) sercom->data;
161         }
162 }
163
164
165 void
166 ao_spi_duplex(const void *out, void *in, uint16_t len, uint8_t spi_index)
167 {
168         uint8_t                 id = AO_SPI_INDEX(spi_index);
169         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
170
171         const uint8_t *o = out;
172         uint8_t *i = in;
173
174         while (len--) {
175                 sercom->data = *o++;
176                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
177                         ;
178                 *i++ = (uint8_t) sercom->data;
179         }
180 }
181
182 static void
183 ao_spi_disable_pin_config(uint8_t spi_pin_config)
184 {
185         switch (spi_pin_config) {
186 #if HAS_SPI_0
187         case AO_SPI_0_PA08_PA09_PA10:
188                 samd21_port_pmux_clr(&samd21_port_a, 8);        /* MOSI */
189                 samd21_port_pmux_clr(&samd21_port_a, 9);        /* SCLK */
190                 samd21_port_pmux_clr(&samd21_port_a, 10);       /* MISO */
191                 break;
192         case AO_SPI_0_PA04_PA05_PA06:
193                 samd21_port_pmux_clr(&samd21_port_a, 4);        /* MOSI */
194                 samd21_port_pmux_clr(&samd21_port_a, 5);        /* SCLK */
195                 samd21_port_pmux_clr(&samd21_port_a, 6);        /* MISO */
196                 break;
197 #endif
198         }
199 }
200
201 static void
202 ao_spi_enable_pin_config(uint8_t spi_pin_config)
203 {
204         switch (spi_pin_config) {
205 #if HAS_SPI_0
206         case AO_SPI_0_PA08_PA09_PA10:
207                 ao_enable_output(&samd21_port_a, 8, 1);
208                 ao_enable_output(&samd21_port_a, 9, 1);
209                 ao_enable_input(&samd21_port_a, 10, AO_MODE_PULL_NONE);
210                 samd21_port_pmux_set(&samd21_port_a, 8, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
211                 samd21_port_pmux_set(&samd21_port_a, 9, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
212                 samd21_port_pmux_set(&samd21_port_a, 10, SAMD21_PORT_PMUX_FUNC_C);      /* MISO */
213                 break;
214         case AO_SPI_0_PA04_PA05_PA06:
215                 ao_enable_output(&samd21_port_a, 4, 1);
216                 ao_enable_output(&samd21_port_a, 5, 1);
217                 ao_enable_input(&samd21_port_a, 6, AO_MODE_PULL_NONE);
218                 samd21_port_pmux_set(&samd21_port_a, 4, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
219                 samd21_port_pmux_set(&samd21_port_a, 5, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
220                 samd21_port_pmux_set(&samd21_port_a, 6, SAMD21_PORT_PMUX_FUNC_C);       /* MISO */
221                 break;
222 #endif
223         }
224 }
225
226 static void
227 ao_spi_config(uint8_t spi_index, uint32_t baud)
228 {
229         uint8_t                 spi_pin_config = AO_SPI_PIN_CONFIG(spi_index);
230         uint8_t                 id = AO_SPI_INDEX(spi_index);
231         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
232
233         if (spi_pin_config != ao_spi_pin_config[id]) {
234                 ao_spi_disable_pin_config(ao_spi_pin_config[id]);
235                 ao_spi_enable_pin_config(spi_pin_config);
236                 ao_spi_pin_config[id] = spi_pin_config;
237         }
238
239         sercom->baud = (uint16_t) baud;
240
241         /* Set spi mode */
242         uint32_t ctrla = sercom->ctrla;
243         ctrla &= ~((1UL << SAMD21_SERCOM_CTRLA_CPOL) |
244                    (1UL << SAMD21_SERCOM_CTRLA_CPHA));
245         ctrla |= ((AO_SPI_CPOL(spi_index) << SAMD21_SERCOM_CTRLA_CPOL) |
246                   (AO_SPI_CPHA(spi_index) << SAMD21_SERCOM_CTRLA_CPHA));
247
248         /* finish setup and enable the hardware */
249         ctrla |= (1 << SAMD21_SERCOM_CTRLA_ENABLE);
250
251         sercom->ctrla = ctrla;
252
253         while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
254                 ;
255 }
256
257 void
258 ao_spi_get(uint8_t spi_index, uint32_t speed)
259 {
260         uint8_t         id = AO_SPI_INDEX(spi_index);
261
262         ao_mutex_get(&ao_spi_mutex[id]);
263         ao_spi_config(spi_index, speed);
264 }
265
266 void
267 ao_spi_put(uint8_t spi_index)
268 {
269         uint8_t                 id = AO_SPI_INDEX(spi_index);
270         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
271
272         sercom->ctrla &= ~(1UL << SAMD21_SERCOM_CTRLA_ENABLE);
273         while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
274                 ;
275         ao_mutex_put(&ao_spi_mutex[id]);
276 }
277
278 static void
279 ao_spi_init_sercom(uint8_t id)
280 {
281         struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
282
283         /* Send a clock along */
284         samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_SERCOM0_CORE + id);
285
286         samd21_nvic_set_enable(SAMD21_NVIC_ISR_SERCOM0_POS + id);
287         samd21_nvic_set_priority(SAMD21_NVIC_ISR_SERCOM0_POS + id, 4);
288
289         /* Enable */
290         samd21_pm.apbcmask |= (1 << (SAMD21_PM_APBCMASK_SERCOM0 + id));
291
292         /* Reset */
293         sercom->ctrla = (1 << SAMD21_SERCOM_CTRLA_SWRST);
294
295         while ((sercom->ctrla & (1 << SAMD21_SERCOM_CTRLA_SWRST)) ||
296                (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_SWRST)))
297                 ;
298
299         /* set SPI mode */
300         sercom->ctrla = ((SAMD21_SERCOM_CTRLA_DORD_MSB << SAMD21_SERCOM_CTRLA_DORD) |
301                          (0 << SAMD21_SERCOM_CTRLA_CPOL) |
302                          (0 << SAMD21_SERCOM_CTRLA_CPHA) |
303                          (0 << SAMD21_SERCOM_CTRLA_FORM) |
304                          (2 << SAMD21_SERCOM_CTRLA_DIPO) |
305                          (0 << SAMD21_SERCOM_CTRLA_DOPO) |
306                          (0 << SAMD21_SERCOM_CTRLA_IBON) |
307                          (0 << SAMD21_SERCOM_CTRLA_RUNSTDBY) |
308                          (SAMD21_SERCOM_CTRLA_MODE_SPI_HOST << SAMD21_SERCOM_CTRLA_MODE) |
309                          (0 << SAMD21_SERCOM_CTRLA_ENABLE) |
310                          (0 << SAMD21_SERCOM_CTRLA_SWRST));
311
312         sercom->ctrlb = ((1 << SAMD21_SERCOM_CTRLB_RXEN) |
313                          (0 << SAMD21_SERCOM_CTRLB_AMODE) |
314                          (0 << SAMD21_SERCOM_CTRLB_MSSEN) |
315                          (0 << SAMD21_SERCOM_CTRLB_SSDE) |
316                          (0 << SAMD21_SERCOM_CTRLB_PLOADEN) |
317                          (SAMD21_SERCOM_CTRLB_CHSIZE_8 << SAMD21_SERCOM_CTRLB_CHSIZE));
318
319
320         ao_spi_enable_pin_config(id);
321 }
322
323 void
324 ao_spi_init(void)
325 {
326 #if HAS_SPI_0
327         ao_spi_init_sercom(0);
328 #endif
329 #if HAS_SPI_1
330         ao_spi_init_sercom(1);
331 #endif
332 #if HAS_SPI_2
333         ao_spi_init_sercom(2);
334 #endif
335 #if HAS_SPI_3
336         ao_spi_init_sercom(3);
337 #endif
338 #if HAS_SPI_4
339         ao_spi_init_sercom(4);
340 #endif
341 #if HAS_SPI_5
342         ao_spi_init_sercom(5);
343 #endif
344 }