altos/lpc: Switch LPC SPI driver to interrupt-driven
[fw/altos] / src / lpc / ao_spi_lpc.c
1 /*
2  * Copyright © 2013 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; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19
20 static uint8_t          ao_spi_mutex[LPC_NUM_SPI];
21
22 struct ao_lpc_ssp_state {
23         int             tx_count;
24         const uint8_t   *tx;
25         int             tx_inc;
26         int             rx_count;
27         uint8_t         *rx;
28         int             rx_inc;
29 };
30
31 static struct ao_lpc_ssp_state ao_lpc_ssp_state[LPC_NUM_SPI];
32
33 static struct lpc_ssp * const ao_lpc_ssp[LPC_NUM_SPI] = { &lpc_ssp0, &lpc_ssp1 };
34
35 static inline void
36 ao_lpc_ssp_recv(struct lpc_ssp *lpc_ssp, struct ao_lpc_ssp_state *state)
37 {
38         while ((lpc_ssp->sr & (1 << LPC_SSP_SR_RNE)) &&
39                state->rx_count)
40         {
41                 /* RX ready, read a byte */
42                 *state->rx = lpc_ssp->dr;
43                 state->rx += state->rx_inc;
44                 state->rx_count--;
45         }
46 }
47
48 static void
49 ao_lpc_ssp_isr(struct lpc_ssp *lpc_ssp, struct ao_lpc_ssp_state *state)
50 {
51         ao_lpc_ssp_recv(lpc_ssp, state);
52         while ((lpc_ssp->sr & (1 << LPC_SSP_SR_TNF)) &&
53                state->tx_count)
54         {
55                 /* TX ready, write a byte */
56                 lpc_ssp->dr = *state->tx;
57                 state->tx += state->tx_inc;
58                 state->tx_count--;
59                 ao_lpc_ssp_recv(lpc_ssp, state);
60         }
61         if (!state->rx_count) {
62                 lpc_ssp->imsc &= ~(1 << LPC_SSP_IMSC_TXIM);
63                 ao_wakeup(state);
64         }
65 }
66
67 void
68 lpc_ssp0_isr(void)
69 {
70         ao_lpc_ssp_isr(&lpc_ssp0, &ao_lpc_ssp_state[0]);
71 }
72
73 void
74 lpc_ssp1_isr(void)
75 {
76         ao_lpc_ssp_isr(&lpc_ssp1, &ao_lpc_ssp_state[1]);
77 }
78
79 static void
80 ao_spi_run(struct lpc_ssp *lpc_ssp, struct ao_lpc_ssp_state *state)
81 {
82         ao_arch_block_interrupts();
83         lpc_ssp->imsc = (1 << LPC_SSP_IMSC_TXIM);
84         while (state->rx_count)
85                 ao_sleep(state);
86         ao_arch_release_interrupts();
87 }
88
89 static uint8_t  ao_spi_tx_dummy = 0xff;
90 static uint8_t  ao_spi_rx_dummy;
91
92 void
93 ao_spi_send(const void *block, uint16_t len, uint8_t id)
94 {
95         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
96         struct ao_lpc_ssp_state *state = &ao_lpc_ssp_state[id];
97
98         state->tx_count = state->rx_count = len;
99         state->tx = block;
100         state->tx_inc = 1;
101         state->rx = &ao_spi_rx_dummy;
102         state->rx_inc = 0;
103         ao_spi_run(lpc_ssp, state);
104 }
105
106 void
107 ao_spi_send_fixed(uint8_t value, uint16_t len, uint8_t id)
108 {
109         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
110         struct ao_lpc_ssp_state *state = &ao_lpc_ssp_state[id];
111
112         state->tx_count = state->rx_count = len;
113         state->tx = &value;
114         state->tx_inc = 0;
115         state->rx = &ao_spi_rx_dummy;
116         state->rx_inc = 0;
117         ao_spi_run(lpc_ssp, state);
118 }
119
120 void
121 ao_spi_recv(void *block, uint16_t len, uint8_t id)
122 {
123         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
124         struct ao_lpc_ssp_state *state = &ao_lpc_ssp_state[id];
125
126         state->tx_count = state->rx_count = len;
127         state->tx = &ao_spi_tx_dummy;
128         state->tx_inc = 0;
129         state->rx = block;
130         state->rx_inc = 1;
131         ao_spi_run(lpc_ssp, state);
132 }
133
134 void
135 ao_spi_duplex(const void *out, void *in, uint16_t len, uint8_t id)
136 {
137         struct lpc_ssp *lpc_ssp = ao_lpc_ssp[id];
138         struct ao_lpc_ssp_state *state = &ao_lpc_ssp_state[id];
139
140         state->tx_count = state->rx_count = len;
141         state->tx = out;
142         state->tx_inc = 1;
143         state->rx = in;
144         state->rx_inc = 1;
145         ao_spi_run(lpc_ssp, state);
146 }
147
148 void
149 ao_spi_get(uint8_t id, uint32_t speed)
150 {
151         struct lpc_ssp  *lpc_ssp = ao_lpc_ssp[id];
152
153         ao_mutex_get(&ao_spi_mutex[id]);
154
155         /* Set the clock prescale */
156         lpc_ssp->cpsr = speed;
157 }
158
159 void
160 ao_spi_put(uint8_t id)
161 {
162         ao_mutex_put(&ao_spi_mutex[id]);
163 }
164
165 static void
166 ao_spi_channel_init(uint8_t id)
167 {
168         struct lpc_ssp  *lpc_ssp = ao_lpc_ssp[id];
169         uint8_t d;
170
171         /* Clear interrupt registers */
172         lpc_ssp->imsc = 0;
173         lpc_ssp->ris = 0;
174         lpc_ssp->mis = 0;
175
176         lpc_ssp->cr0 = ((LPC_SSP_CR0_DSS_8 << LPC_SSP_CR0_DSS) |
177                         (LPC_SSP_CR0_FRF_SPI << LPC_SSP_CR0_FRF) |
178                         (0 << LPC_SSP_CR0_CPOL) |
179                         (0 << LPC_SSP_CR0_CPHA) |
180                         (0 << LPC_SSP_CR0_SCR));
181
182         /* Enable the device */
183         lpc_ssp->cr1 = ((0 << LPC_SSP_CR1_LBM) |
184                         (1 << LPC_SSP_CR1_SSE) |
185                         (LPC_SSP_CR1_MS_MASTER << LPC_SSP_CR1_MS) |
186                         (0 << LPC_SSP_CR1_SOD));
187
188         /* Drain the receive fifo */
189         for (d = 0; d < LPC_SSP_FIFOSIZE; d++)
190                 (void) lpc_ssp->dr;
191 }
192
193 void
194 ao_spi_init(void)
195 {
196 #if HAS_SPI_0
197         /* Configure pins */
198 #if SPI_SCK0_P0_6
199         lpc_ioconf.pio0_6 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO0_6_SCK0);
200 #define HAS_SCK0
201 #endif
202 #if SPI_SCK0_P0_10
203         lpc_ioconf.pio0_10 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO0_10_SCK0);
204 #define HAS_SCK0
205 #endif
206 #if SPI_SCK0_P1_29
207         lpc_ioconf.pio1_29 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_29_SCK0);
208 #define HAS_SCK0
209 #endif
210 #ifndef HAS_SCK0
211 #error "No pin specified for SCK0"
212 #endif
213         lpc_ioconf.pio0_8 = ao_lpc_alternate(LPC_IOCONF_FUNC_MISO0);
214         lpc_ioconf.pio0_9 = ao_lpc_alternate(LPC_IOCONF_FUNC_MOSI0);
215
216         /* Enable the device */
217         lpc_scb.sysahbclkctrl |= (1 << LPC_SCB_SYSAHBCLKCTRL_SSP0);
218
219         /* Turn on the clock */
220         lpc_scb.ssp0clkdiv = 1;
221
222         /* Reset the device */
223         lpc_scb.presetctrl &= ~(1 << LPC_SCB_PRESETCTRL_SSP0_RST_N);
224         lpc_scb.presetctrl |= (1 << LPC_SCB_PRESETCTRL_SSP0_RST_N);
225         ao_spi_channel_init(0);
226
227         /* Configure NVIC */
228         lpc_nvic_set_enable(LPC_ISR_SSP0_POS);
229         lpc_nvic_set_priority(LPC_ISR_SSP0_POS, 0);
230 #endif
231
232 #if HAS_SPI_1
233
234 #if SPI_SCK1_P1_15
235         lpc_ioconf.pio1_15 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_15_SCK1);
236 #define HAS_SCK1
237 #endif
238 #if SPI_SCK1_P1_20
239         lpc_ioconf.pio1_20 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_20_SCK1);
240 #define HAS_SCK1
241 #endif
242 #ifndef HAS_SCK1
243 #error "No pin specified for SCK1"
244 #endif
245
246 #if SPI_MISO1_P0_22
247         lpc_ioconf.pio0_22 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO0_22_MISO1);
248 #define HAS_MISO1
249 #endif
250 #if SPI_MISO1_P1_21
251         lpc_ioconf.pio1_21 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_21_MISO1);
252 #define HAS_MISO1
253 #endif
254 #ifndef HAS_MISO1
255 #error "No pin specified for MISO1"
256 #endif
257
258 #if SPI_MOSI1_P0_21
259         lpc_ioconf.pio0_21 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO0_21_MOSI1);
260 #define HAS_MOSI1
261 #endif
262 #if SPI_MOSI1_P1_22
263         lpc_ioconf.pio1_22 = ao_lpc_alternate(LPC_IOCONF_FUNC_PIO1_22_MOSI1);
264 #define HAS_MOSI1
265 #endif
266 #ifndef HAS_MOSI1
267 #error "No pin specified for MOSI1"
268 #endif
269
270         /* Enable the device */
271         lpc_scb.sysahbclkctrl |= (1 << LPC_SCB_SYSAHBCLKCTRL_SSP1);
272
273         /* Turn on the clock */
274         lpc_scb.ssp1clkdiv = 1;
275
276         /* Reset the device */
277         lpc_scb.presetctrl &= ~(1 << LPC_SCB_PRESETCTRL_SSP1_RST_N);
278         lpc_scb.presetctrl |= (1 << LPC_SCB_PRESETCTRL_SSP1_RST_N);
279         ao_spi_channel_init(1);
280
281         /* Configure NVIC */
282         lpc_nvic_set_enable(LPC_ISR_SSP1_POS);
283         lpc_nvic_set_priority(LPC_ISR_SSP1_POS, 0);
284
285 #endif /* HAS_SPI_1 */
286 }