altos: Get SAMD21 SPI driver working in non-DMA mode
[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
17 static uint8_t          ao_spi_mutex[SAMD21_NUM_SERCOM];
18 static uint8_t          ao_spi_pin_config[SAMD21_NUM_SERCOM];
19
20 struct ao_spi_samd21_info {
21         struct samd21_sercom    *sercom;
22 };
23
24 static const struct ao_spi_samd21_info ao_spi_samd21_info[SAMD21_NUM_SERCOM] = {
25         {
26                 .sercom = &samd21_sercom0,
27         },
28         {
29                 .sercom = &samd21_sercom1,
30         },
31         {
32                 .sercom = &samd21_sercom2,
33         },
34         {
35                 .sercom = &samd21_sercom3,
36         },
37         {
38                 .sercom = &samd21_sercom4,
39         },
40         {
41                 .sercom = &samd21_sercom5,
42         },
43 };
44
45 //static uint8_t        spi_dev_null;
46
47 void
48 ao_spi_send(const void *block, uint16_t len, uint8_t spi_index)
49 {
50         uint8_t                 id = AO_SPI_INDEX(spi_index);
51         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
52
53         const uint8_t *b = block;
54
55         while (len--) {
56                 sercom->data = *b++;
57                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
58                         ;
59                 (void) sercom->data;
60         }
61 }
62
63 void
64 ao_spi_recv(void *block, uint16_t len, uint8_t spi_index)
65 {
66         uint8_t                 id = AO_SPI_INDEX(spi_index);
67         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
68
69         uint8_t *b = block;
70
71         while (len--) {
72                 sercom->data = 0xff;
73                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
74                         ;
75                 *b++ = (uint8_t) sercom->data;
76         }
77 }
78
79
80 void
81 ao_spi_duplex(const void *out, void *in, uint16_t len, uint8_t spi_index)
82 {
83         uint8_t                 id = AO_SPI_INDEX(spi_index);
84         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
85
86         const uint8_t *o = out;
87         uint8_t *i = in;
88
89         while (len--) {
90                 sercom->data = *o++;
91                 while ((sercom->intflag & (1 << SAMD21_SERCOM_INTFLAG_RXC)) == 0)
92                         ;
93                 *i++ = (uint8_t) sercom->data;
94         }
95 }
96
97 static void
98 ao_spi_disable_pin_config(uint8_t spi_pin_config)
99 {
100         switch (spi_pin_config) {
101 #if HAS_SPI_0
102         case AO_SPI_0_PA08_PA09_PA10:
103                 samd21_port_pmux_clr(&samd21_port_a, 8);        /* MOSI */
104                 samd21_port_pmux_clr(&samd21_port_a, 9);        /* SCLK */
105                 samd21_port_pmux_clr(&samd21_port_a, 10);       /* MISO */
106                 break;
107         case AO_SPI_0_PA04_PA05_PA06:
108                 samd21_port_pmux_clr(&samd21_port_a, 4);        /* MOSI */
109                 samd21_port_pmux_clr(&samd21_port_a, 5);        /* SCLK */
110                 samd21_port_pmux_clr(&samd21_port_a, 6);        /* MISO */
111                 break;
112 #endif
113         }
114 }
115
116 static void
117 ao_spi_enable_pin_config(uint8_t spi_pin_config)
118 {
119         switch (spi_pin_config) {
120 #if HAS_SPI_0
121         case AO_SPI_0_PA08_PA09_PA10:
122                 ao_enable_output(&samd21_port_a, 8, 1);
123                 ao_enable_output(&samd21_port_a, 9, 1);
124                 ao_enable_input(&samd21_port_a, 10, AO_MODE_PULL_NONE);
125                 samd21_port_pmux_set(&samd21_port_a, 8, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
126                 samd21_port_pmux_set(&samd21_port_a, 9, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
127                 samd21_port_pmux_set(&samd21_port_a, 10, SAMD21_PORT_PMUX_FUNC_C);      /* MISO */
128                 break;
129         case AO_SPI_0_PA04_PA05_PA06:
130                 ao_enable_output(&samd21_port_a, 4, 1);
131                 ao_enable_output(&samd21_port_a, 5, 1);
132                 ao_enable_input(&samd21_port_a, 6, AO_MODE_PULL_NONE);
133                 samd21_port_pmux_set(&samd21_port_a, 4, SAMD21_PORT_PMUX_FUNC_C);       /* MOSI */
134                 samd21_port_pmux_set(&samd21_port_a, 5, SAMD21_PORT_PMUX_FUNC_C);       /* SCLK */
135                 samd21_port_pmux_set(&samd21_port_a, 6, SAMD21_PORT_PMUX_FUNC_C);       /* MISO */
136                 break;
137 #endif
138         }
139 }
140
141 static void
142 ao_spi_config(uint8_t spi_index, uint32_t baud)
143 {
144         uint8_t                 spi_pin_config = AO_SPI_PIN_CONFIG(spi_index);
145         uint8_t                 id = AO_SPI_INDEX(spi_index);
146         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
147
148         if (spi_pin_config != ao_spi_pin_config[id]) {
149                 ao_spi_disable_pin_config(ao_spi_pin_config[id]);
150                 ao_spi_enable_pin_config(spi_pin_config);
151                 ao_spi_pin_config[id] = spi_pin_config;
152         }
153
154         sercom->baud = (uint16_t) baud;
155
156         /* Set spi mode */
157         uint32_t ctrla = sercom->ctrla;
158         ctrla &= ~((1UL << SAMD21_SERCOM_CTRLA_CPOL) |
159                    (1UL << SAMD21_SERCOM_CTRLA_CPHA));
160         ctrla |= ((AO_SPI_CPOL(spi_index) << SAMD21_SERCOM_CTRLA_CPOL) |
161                   (AO_SPI_CPHA(spi_index) << SAMD21_SERCOM_CTRLA_CPHA));
162
163         /* finish setup and enable the hardware */
164         ctrla |= (1 << SAMD21_SERCOM_CTRLA_ENABLE);
165
166         sercom->ctrla = ctrla;
167
168         while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
169                 ;
170 }
171
172 void
173 ao_spi_get(uint8_t spi_index, uint32_t speed)
174 {
175         uint8_t         id = AO_SPI_INDEX(spi_index);
176
177         ao_mutex_get(&ao_spi_mutex[id]);
178         ao_spi_config(spi_index, speed);
179 }
180
181 void
182 ao_spi_put(uint8_t spi_index)
183 {
184         uint8_t                 id = AO_SPI_INDEX(spi_index);
185         struct samd21_sercom    *sercom = ao_spi_samd21_info[id].sercom;
186
187         sercom->ctrla &= ~(1UL << SAMD21_SERCOM_CTRLA_ENABLE);
188         while (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_ENABLE))
189                 ;
190         ao_mutex_put(&ao_spi_mutex[id]);
191 }
192
193 static void
194 ao_spi_init_sercom(uint8_t id)
195 {
196         struct samd21_sercom *sercom = ao_spi_samd21_info[id].sercom;
197
198         /* Send a clock along */
199         samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_SERCOM0_CORE + id);
200
201         samd21_nvic_set_enable(SAMD21_NVIC_ISR_SERCOM0_POS + id);
202         samd21_nvic_set_priority(SAMD21_NVIC_ISR_SERCOM0_POS + id, 4);
203
204         /* Enable */
205         samd21_pm.apbcmask |= (1 << (SAMD21_PM_APBCMASK_SERCOM0 + id));
206
207         /* Reset */
208         sercom->ctrla = (1 << SAMD21_SERCOM_CTRLA_SWRST);
209
210         while ((sercom->ctrla & (1 << SAMD21_SERCOM_CTRLA_SWRST)) ||
211                (sercom->syncbusy & (1 << SAMD21_SERCOM_SYNCBUSY_SWRST)))
212                 ;
213
214         /* set SPI mode */
215         sercom->ctrla = ((SAMD21_SERCOM_CTRLA_DORD_MSB << SAMD21_SERCOM_CTRLA_DORD) |
216                          (0 << SAMD21_SERCOM_CTRLA_CPOL) |
217                          (0 << SAMD21_SERCOM_CTRLA_CPHA) |
218                          (0 << SAMD21_SERCOM_CTRLA_FORM) |
219                          (2 << SAMD21_SERCOM_CTRLA_DIPO) |
220                          (0 << SAMD21_SERCOM_CTRLA_DOPO) |
221                          (0 << SAMD21_SERCOM_CTRLA_IBON) |
222                          (0 << SAMD21_SERCOM_CTRLA_RUNSTDBY) |
223                          (SAMD21_SERCOM_CTRLA_MODE_SPI_HOST << SAMD21_SERCOM_CTRLA_MODE) |
224                          (0 << SAMD21_SERCOM_CTRLA_ENABLE) |
225                          (0 << SAMD21_SERCOM_CTRLA_SWRST));
226
227         sercom->ctrlb = ((1 << SAMD21_SERCOM_CTRLB_RXEN) |
228                          (0 << SAMD21_SERCOM_CTRLB_AMODE) |
229                          (0 << SAMD21_SERCOM_CTRLB_MSSEN) |
230                          (0 << SAMD21_SERCOM_CTRLB_SSDE) |
231                          (0 << SAMD21_SERCOM_CTRLB_PLOADEN) |
232                          (SAMD21_SERCOM_CTRLB_CHSIZE_8 << SAMD21_SERCOM_CTRLB_CHSIZE));
233
234
235         ao_spi_enable_pin_config(id);
236 }
237
238 void
239 ao_spi_init(void)
240 {
241 #if HAS_SPI_0
242         ao_spi_init_sercom(0);
243 #endif
244 #if HAS_SPI_1
245         ao_spi_init_sercom(1);
246 #endif
247 #if HAS_SPI_2
248         ao_spi_init_sercom(2);
249 #endif
250 #if HAS_SPI_3
251         ao_spi_init_sercom(3);
252 #endif
253 #if HAS_SPI_4
254         ao_spi_init_sercom(4);
255 #endif
256 #if HAS_SPI_5
257         ao_spi_init_sercom(5);
258 #endif
259 }