2 * Copyright © 2022 Keith Packard <keithp@keithp.com>
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.
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.
16 #include <ao_dma_samd21.h>
18 static uint8_t ao_spi_mutex[SAMD21_NUM_SERCOM];
19 static uint16_t ao_spi_pin_config[SAMD21_NUM_SERCOM];
25 * DMA is only used for USARTs in altos, which makes assigning DMA IDs
29 #define MISO_DMA_ID(id) ((uint8_t) ((id) * 2U + 0U))
30 #define MOSI_DMA_ID(id) ((uint8_t) ((id) * 2U + 1U))
32 struct ao_spi_samd21_info {
33 struct samd21_sercom *sercom;
36 static const struct ao_spi_samd21_info ao_spi_samd21_info[SAMD21_NUM_SERCOM] = {
38 .sercom = &samd21_sercom0,
41 .sercom = &samd21_sercom1,
44 .sercom = &samd21_sercom2,
47 .sercom = &samd21_sercom3,
50 .sercom = &samd21_sercom4,
53 .sercom = &samd21_sercom5,
57 static uint8_t spi_dev_null;
61 static uint8_t ao_spi_done[SAMD21_NUM_SERCOM];
64 _ao_spi_recv_dma_done(uint8_t dma_id, void *closure)
66 uint8_t id = (uint8_t) (uintptr_t) closure;
70 ao_wakeup(&ao_spi_done[id]);
73 static inline uint32_t
74 dma_chctrlb(uint8_t id, bool tx)
78 /* No complicated actions needed */
79 chctrlb |= SAMD21_DMAC_CHCTRLB_CMD_NOACT << SAMD21_DMAC_CHCTRLB_CMD;
81 /* Trigger after each byte transferred */
82 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGACT_BEAT << SAMD21_DMAC_CHCTRLB_TRIGACT;
84 /* Set the trigger source */
86 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_TX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC;
88 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_RX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC;
90 /* RX has priority over TX so that we don't drop incoming bytes */
92 chctrlb |= SAMD21_DMAC_CHCTRLB_LVL_LVL0 << SAMD21_DMAC_CHCTRLB_LVL;
94 chctrlb |= SAMD21_DMAC_CHCTRLB_LVL_LVL3 << SAMD21_DMAC_CHCTRLB_LVL;
96 /* No events needed */
97 chctrlb |= 0UL << SAMD21_DMAC_CHCTRLB_EVOE;
98 chctrlb |= 0UL << SAMD21_DMAC_CHCTRLB_EVIE;
100 /* And no actions either */
101 chctrlb |= SAMD21_DMAC_CHCTRLB_EVACT_NOACT << SAMD21_DMAC_CHCTRLB_EVACT;
106 static inline uint16_t
107 dma_btctrl(bool step, bool tx)
111 /* Always step by 1 */
112 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSIZE_X1 << SAMD21_DMAC_DESC_BTCTRL_STEPSIZE;
114 /* Step the source if transmit, otherwise step the dest */
116 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSEL_SRC << SAMD21_DMAC_DESC_BTCTRL_STEPSEL;
118 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSEL_DST << SAMD21_DMAC_DESC_BTCTRL_STEPSEL;
120 /* Set the increment if stepping */
123 btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
125 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
126 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
128 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
130 btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
132 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
135 /* byte at a time please */
136 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BEATSIZE_BYTE << SAMD21_DMAC_DESC_BTCTRL_BEATSIZE;
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
143 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_NOACT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT;
145 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_INT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT;
147 /* don't need any events */
148 btctrl |= SAMD21_DMAC_DESC_BTCTRL_EVOSEL_DISABLE << SAMD21_DMAC_DESC_BTCTRL_EVOSEL;
150 /* And make the descriptor valid */
151 btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_VALID;
157 spi_run(const void *out, void *in, uint16_t len, uint16_t spi_index, bool step_out, bool step_in)
159 const uint8_t *o = out;
161 uint8_t id = AO_SPI_INDEX(spi_index);
162 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
164 ao_arch_block_interrupts();
168 * Stepped addresses to the DMA engine point past the end of
176 /* read any stuck data */
179 _ao_dma_start_transfer(MISO_DMA_ID(id),
180 (void *) &sercom->data,
183 dma_chctrlb(id, false),
184 dma_btctrl(step_in, false),
186 _ao_spi_recv_dma_done,
187 (void *) (uintptr_t) id
190 _ao_dma_start_transfer(MOSI_DMA_ID(id),
192 (void *) &sercom->data,
194 dma_chctrlb(id, true),
195 dma_btctrl(step_out, true),
200 while (ao_spi_done[id] == 0)
201 ao_sleep(&ao_spi_done[id]);
203 _ao_dma_done_transfer(MOSI_DMA_ID(id));
204 _ao_dma_done_transfer(MISO_DMA_ID(id));
205 ao_arch_release_interrupts();
211 spi_run(const void *out, void *in, uint16_t len, uint16_t spi_index, bool step_out, bool step_in)
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;
223 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
225 *i = (uint8_t) sercom->data;
227 printf("\t%02x\n", *i);
239 ao_spi_send(const void *block, uint16_t len, uint16_t spi_index)
241 spi_run(block, &spi_dev_null, len, spi_index, true, false);
245 ao_spi_send_fixed(uint8_t data, uint16_t len, uint16_t spi_index)
247 spi_run(&data, &spi_dev_null, len, spi_index, false, false);
251 ao_spi_recv(void *block, uint16_t len, uint16_t spi_index)
254 spi_run(&spi_dev_null, block, len, spi_index, false, true);
259 ao_spi_duplex(const void *out, void *in, uint16_t len, uint16_t spi_index)
261 spi_run(out, in, len, spi_index, true, true);
265 ao_spi_disable_pin_config(uint16_t spi_pin_config)
267 switch (spi_pin_config) {
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 */
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 */
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 */
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 */
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 */
307 ao_spi_enable_pin_config(uint16_t spi_pin_config)
309 switch (spi_pin_config) {
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);
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 */
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);
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 */
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);
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 */
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);
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 */
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);
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 */
364 ao_panic(AO_PANIC_SPI);
370 ao_spi_config(uint16_t spi_index, uint32_t baud)
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;
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;
382 sercom->baud = (uint16_t) baud;
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));
395 /* finish setup and enable the hardware */
396 ctrla |= (1 << SAMD21_SERCOM_CTRLA_ENABLE);
399 printf("ctrla %08lx\n", ctrla);
402 sercom->ctrla = ctrla;
404 while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
409 ao_spi_get(uint16_t spi_index, uint32_t speed)
411 uint8_t id = AO_SPI_INDEX(spi_index);
413 ao_mutex_get(&ao_spi_mutex[id]);
414 ao_spi_config(spi_index, speed);
418 ao_spi_put(uint16_t spi_index)
420 uint8_t id = AO_SPI_INDEX(spi_index);
421 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
423 sercom->ctrla &= ~(1UL << SAMD21_SERCOM_CTRLA_ENABLE);
424 while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
426 ao_mutex_put(&ao_spi_mutex[id]);
430 ao_spi_init_sercom(uint8_t id)
432 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
434 /* Send a clock along */
435 samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_SERCOM0_CORE + id);
437 samd21_nvic_set_enable(SAMD21_NVIC_ISR_SERCOM0_POS + id);
438 samd21_nvic_set_priority(SAMD21_NVIC_ISR_SERCOM0_POS + id, 4);
441 samd21_pm.apbcmask |= (1 << (SAMD21_PM_APBCMASK_SERCOM0 + id));
444 sercom->ctrla = (1 << SAMD21_SERCOM_CTRLA_SWRST);
446 while ((sercom->ctrla & (1 << SAMD21_SERCOM_CTRLA_SWRST)) ||
447 (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_SWRST)))
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));
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));
470 ao_spi_pin_config[id] = 0xffff;
477 ao_spi_init_sercom(0);
480 ao_spi_init_sercom(1);
483 ao_spi_init_sercom(2);
486 ao_spi_init_sercom(3);
489 ao_spi_init_sercom(4);
492 ao_spi_init_sercom(5);