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 uint8_t ao_spi_pin_config[SAMD21_NUM_SERCOM];
21 struct ao_spi_samd21_info {
22 struct samd21_sercom *sercom;
25 static const struct ao_spi_samd21_info ao_spi_samd21_info[SAMD21_NUM_SERCOM] = {
27 .sercom = &samd21_sercom0,
30 .sercom = &samd21_sercom1,
33 .sercom = &samd21_sercom2,
36 .sercom = &samd21_sercom3,
39 .sercom = &samd21_sercom4,
42 .sercom = &samd21_sercom5,
46 static uint8_t spi_dev_null;
52 #define AO_SAMD21_SPI_MISO_DMA_ID 0
53 #define AO_SAMD21_SPI_MOSI_DMA_ID 1
55 static uint8_t ao_spi_done[SAMD21_NUM_SERCOM];
58 _ao_spi_recv_dma_done(uint8_t dma_id, void *closure)
60 uint8_t id = (uint8_t) (uintptr_t) closure;
64 ao_wakeup(&ao_spi_done[id]);
68 static inline uint32_t
69 dma_chctrlb(uint8_t id, bool tx)
73 /* No complicated actions needed */
74 chctrlb |= SAMD21_DMAC_CHCTRLB_CMD_NOACT << SAMD21_DMAC_CHCTRLB_CMD;
76 /* Trigger after each byte transferred */
77 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGACT_BEAT << SAMD21_DMAC_CHCTRLB_TRIGACT;
79 /* Set the trigger source */
81 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_TX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC;
83 chctrlb |= SAMD21_DMAC_CHCTRLB_TRIGSRC_SERCOM_RX(id) << SAMD21_DMAC_CHCTRLB_TRIGSRC;
85 /* RX has priority over TX so that we don't drop incoming bytes */
87 chctrlb |= SAMD21_DMAC_CHCTRLB_LVL_LVL0 << SAMD21_DMAC_CHCTRLB_LVL;
89 chctrlb |= SAMD21_DMAC_CHCTRLB_LVL_LVL3 << SAMD21_DMAC_CHCTRLB_LVL;
91 /* No events needed */
92 chctrlb |= 0UL << SAMD21_DMAC_CHCTRLB_EVOE;
93 chctrlb |= 0UL << SAMD21_DMAC_CHCTRLB_EVIE;
95 /* And no actions either */
96 chctrlb |= SAMD21_DMAC_CHCTRLB_EVACT_NOACT << SAMD21_DMAC_CHCTRLB_EVACT;
101 static inline uint16_t
102 dma_btctrl(bool tx, bool step)
106 /* Always step by 1 */
107 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSIZE_X1 << SAMD21_DMAC_DESC_BTCTRL_STEPSIZE;
109 /* Step the source if transmit, otherwise step the dest */
111 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSEL_SRC << SAMD21_DMAC_DESC_BTCTRL_STEPSEL;
113 btctrl |= SAMD21_DMAC_DESC_BTCTRL_STEPSEL_DST << SAMD21_DMAC_DESC_BTCTRL_STEPSEL;
115 /* Set the increment if stepping */
118 btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
120 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
121 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
123 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_SRCINC;
125 btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
127 btctrl |= 0UL << SAMD21_DMAC_DESC_BTCTRL_DSTINC;
130 /* byte at a time please */
131 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BEATSIZE_BYTE << SAMD21_DMAC_DESC_BTCTRL_BEATSIZE;
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
138 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_NOACT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT;
140 btctrl |= SAMD21_DMAC_DESC_BTCTRL_BLOCKACT_INT << SAMD21_DMAC_DESC_BTCTRL_BLOCKACT;
142 /* don't need any events */
143 btctrl |= SAMD21_DMAC_DESC_BTCTRL_EVOSEL_DISABLE << SAMD21_DMAC_DESC_BTCTRL_EVOSEL;
145 /* And make the descriptor valid */
146 btctrl |= 1UL << SAMD21_DMAC_DESC_BTCTRL_VALID;
152 ao_spi_send(const void *block, uint16_t len, uint8_t spi_index)
154 uint8_t id = AO_SPI_INDEX(spi_index);
155 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
158 ao_arch_block_interrupts();
161 _ao_dma_start_transfer(AO_SAMD21_SPI_MISO_DMA_ID,
162 (void *) &sercom->data,
165 dma_chctrlb(id, false),
166 dma_btctrl(false, false),
168 _ao_spi_recv_dma_done,
169 (void *) (uintptr_t) id
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,
176 dma_chctrlb(id, true),
177 dma_btctrl(true, true),
182 while (ao_spi_done[id] == 0)
183 ao_sleep(&ao_spi_done[id]);
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();
189 const uint8_t *b = block;
193 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
202 ao_spi_recv(void *block, uint16_t len, uint8_t spi_index)
204 uint8_t id = AO_SPI_INDEX(spi_index);
205 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
211 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
213 *b++ = (uint8_t) sercom->data;
219 ao_spi_duplex(const void *out, void *in, uint16_t len, uint8_t spi_index)
221 uint8_t id = AO_SPI_INDEX(spi_index);
222 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
224 const uint8_t *o = out;
229 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
231 *i++ = (uint8_t) sercom->data;
236 ao_spi_disable_pin_config(uint8_t spi_pin_config)
238 switch (spi_pin_config) {
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 */
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 */
255 ao_spi_enable_pin_config(uint8_t spi_pin_config)
257 switch (spi_pin_config) {
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 */
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 */
280 ao_spi_config(uint8_t spi_index, uint32_t baud)
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;
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;
292 sercom->baud = (uint16_t) baud;
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));
301 /* finish setup and enable the hardware */
302 ctrla |= (1 << SAMD21_SERCOM_CTRLA_ENABLE);
304 sercom->ctrla = ctrla;
306 while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
311 ao_spi_get(uint8_t spi_index, uint32_t speed)
313 uint8_t id = AO_SPI_INDEX(spi_index);
315 ao_mutex_get(&ao_spi_mutex[id]);
316 ao_spi_config(spi_index, speed);
320 ao_spi_put(uint8_t spi_index)
322 uint8_t id = AO_SPI_INDEX(spi_index);
323 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
325 sercom->ctrla &= ~(1UL << SAMD21_SERCOM_CTRLA_ENABLE);
326 while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
328 ao_mutex_put(&ao_spi_mutex[id]);
332 ao_spi_init_sercom(uint8_t id)
334 struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
336 /* Send a clock along */
337 samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_SERCOM0_CORE + id);
339 samd21_nvic_set_enable(SAMD21_NVIC_ISR_SERCOM0_POS + id);
340 samd21_nvic_set_priority(SAMD21_NVIC_ISR_SERCOM0_POS + id, 4);
343 samd21_pm.apbcmask |= (1 << (SAMD21_PM_APBCMASK_SERCOM0 + id));
346 sercom->ctrla = (1 << SAMD21_SERCOM_CTRLA_SWRST);
348 while ((sercom->ctrla & (1 << SAMD21_SERCOM_CTRLA_SWRST)) ||
349 (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_SWRST)))
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));
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));
373 ao_spi_enable_pin_config(id);
380 ao_spi_init_sercom(0);
383 ao_spi_init_sercom(1);
386 ao_spi_init_sercom(2);
389 ao_spi_init_sercom(3);
392 ao_spi_init_sercom(4);
395 ao_spi_init_sercom(5);