summaryrefslogtreecommitdiff
path: root/src/cc1111/ao_spi.c
blob: 124e09cb7f95479cff34472ebe555ebab452fab8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
 * Copyright © 2010 Keith Packard <keithp@keithp.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#include "ao.h"

/* Default pin usage for existing Altus Metrum devices */

#ifndef SPI_CONST
#define SPI_CONST	0xff
#endif

/*
 * USART0 SPI config alt 1
 * 
 *	MO	P0_3
 *	MI	P0_2
 *	CLK	P0_5
 *	SS	P0_4
 *
 * USART0 SPI config alt 2
 *
 * 	MO	P1_5
 * 	MI	P1_4
 * 	CLK	P1_3
 *	CSS	P1_2
 *
 * USART1 SPI config alt 1
 *
 *	MO	P0_4
 *	MI	P0_5
 *	CLK	P0_3
 *	SS	P0_2
 *
 * USART1 SPI config alt 2
 *
 *	MO	P1_6
 *	MI	P1_7
 *	CLK	P1_5
 *	SS	P1_4
 *
 *
 * Chip select is the responsibility of the caller in master mode
 */

#if HAS_SPI_0
#define SPI_BUF_0	&U0DBUFXADDR
#define SPI_CSR_0	U0CSR
#define SPI_BAUD_0	U0BAUD
#define SPI_GCR_0	U0GCR
#define SPI_CFG_MASK_0	PERCFG_U0CFG_ALT_MASK
#define SPI_DMA_TX_0	DMA_CFG0_TRIGGER_UTX0
#define SPI_DMA_RX_0	DMA_CFG0_TRIGGER_URX0

#if SPI_0_ALT_1
#define SPI_CFG_0	PERCFG_U0CFG_ALT_1
#define SPI_SEL_0	P0SEL
#define SPI_BITS_0	(1 << 3) | (1 << 2) | (1 << 5)
#define SPI_CSS_BIT_0	(1 << 4)
#endif

#if SPI_0_ALT_2
#define SPI_CFG_0	PERCFG_U0CFG_ALT_2
#define SPI_SEL_0	P1SEL
#define SPI_PRI_0	P2SEL_PRI3P1_USART0
#define SPI_BITS_0	(1 << 5) | (1 << 4) | (1 << 3)
#define SPI_CSS_BIT_0	(1 << 2)
#endif

#endif

#if HAS_SPI_1
#define SPI_BUF_1	&U1DBUFXADDR
#define SPI_CSR_1	U1CSR
#define SPI_BAUD_1	U1BAUD
#define SPI_GCR_1	U1GCR
#define SPI_CFG_MASK_1	PERCFG_U1CFG_ALT_MASK
#define SPI_DMA_TX_1	DMA_CFG0_TRIGGER_UTX1
#define SPI_DMA_RX_1	DMA_CFG0_TRIGGER_URX1

#if SPI_1_ALT_1
#define SPI_CFG_1	PERCFG_U1CFG_ALT_1
#define SPI_SEL_1	P0SEL
#define SPI_BITS_1	(1 << 4) | (1 << 5) | (1 << 3)
#define SPI_CSS_BIT_1	(1 << 2)
#endif

#if SPI_1_ALT_2
#define SPI_CFG_1	PERCFG_U1CFG_ALT_2
#define SPI_SEL_1	P1SEL
#define SPI_PRI_1	P2SEL_PRI3P1_USART1
#define SPI_BITS_1	(1 << 6) | (1 << 7) | (1 << 5)
#define SPI_CSS_BIT_1	(1 << 4)
#endif

#endif

#if MULTI_SPI

#define SPI_BUF(bus)		((bus) ? SPI_BUF_1 : SPI_BUF_0)
#define SPI_CSR(bus)		((bus) ? SPI_CSR_1 : SPI_CSR_0)
#define SPI_BAUD(bus)		((bus) ? SPI_BAUD_1 : SPI_BAUD_0)
#define SPI_GCR(bus)		((bus) ? SPI_GCR_1 : SPI_GCR_0)
#define SPI_CFG_MASK(bus)	((bus) ? SPI_CFG_MASK_1 : SPI_CFG_MASK_0)
#define SPI_DMA_TX(bus)		((bus) ? SPI_DMA_TX_1 : SPI_DMA_TX_0)
#define SPI_DMA_RX(bus)		((bus) ? SPI_DMA_RX_1 : SPI_DMA_RX_0)
#define SPI_CFG(bus)		((bus) ? SPI_CFG_1 : SPI_CFG_0)
#define SPI_SEL(bus)		((bus) ? SPI_SEL_1 : SPI_SEL_0)
#define SPI_BITS(bus)		((bus) ? SPI_BITS_1 : SPI_BITS_0)
#define SPI_CSS_BIT(bus)	((bus) ? SPI_CSS_BIT_1 : SPI_CSS_BIT_0)

#else

#if HAS_SPI_0
#define SPI_BUF(bus)		SPI_BUF_0
#define SPI_CSR(bus)		SPI_CSR_0
#define SPI_BAUD(bus)		SPI_BAUD_0
#define SPI_GCR(bus)		SPI_GCR_0
#define SPI_CFG_MASK(bus)	SPI_CFG_MASK_0
#define SPI_DMA_TX(bus)		SPI_DMA_TX_0
#define SPI_DMA_RX(bus)		SPI_DMA_RX_0
#define SPI_CFG(bus)		SPI_CFG_0
#define SPI_SEL(bus)		SPI_SEL_0
#define SPI_BITS(bus)		SPI_BITS_0
#define SPI_CSS_BIT(bus)	SPI_CSS_BIT_0
#endif
#if HAS_SPI_1
#define SPI_BUF(bus)		SPI_BUF_1
#define SPI_CSR(bus)		SPI_CSR_1
#define SPI_BAUD(bus)		SPI_BAUD_1
#define SPI_GCR(bus)		SPI_GCR_1
#define SPI_CFG_MASK(bus)	SPI_CFG_MASK_1
#define SPI_DMA_TX(bus)		SPI_DMA_TX_1
#define SPI_DMA_RX(bus)		SPI_DMA_RX_1
#define SPI_CFG(bus)		SPI_CFG_1
#define SPI_SEL(bus)		SPI_SEL_1
#define SPI_BITS(bus)		SPI_BITS_1
#define SPI_CSS_BIT(bus)	SPI_CSS_BIT_1
#endif

#endif /* MULTI_SPI */

#if AO_SPI_SLAVE
#define CSS(bus)		SPI_CSS_BIT(bus)
#define UxCSR_DIRECTION	UxCSR_SLAVE
#else
#define CSS(bus)		0
#define UxCSR_DIRECTION	UxCSR_MASTER
#endif

/* Shared mutex to protect SPI bus, must cover the entire
 * operation, from CS low to CS high. This means that any SPI
 * user must protect the SPI bus with this mutex
 */
__xdata uint8_t	ao_spi_mutex[N_SPI];
__xdata uint8_t ao_spi_dma_in_done[N_SPI];
__xdata uint8_t ao_spi_dma_out_done[N_SPI];

uint8_t	ao_spi_dma_out_id[N_SPI];
uint8_t ao_spi_dma_in_id[N_SPI];

static __xdata uint8_t ao_spi_const;


/* Send bytes over SPI.
 *
 * This sets up two DMA engines, one writing the data and another reading
 * bytes coming back.  We use the bytes coming back to tell when the transfer
 * is complete, as the transmit register is double buffered and hence signals
 * completion one byte before the transfer is actually complete
 */
#if MULTI_SPI
void
ao_spi_send(void __xdata *block, uint16_t len, uint8_t bus) __reentrant
#else
void
ao_spi_send_bus(void __xdata *block, uint16_t len) __reentrant
#define bus	0
#endif
{
	ao_dma_set_transfer(ao_spi_dma_in_id[bus],
			    SPI_BUF(bus),
			    &ao_spi_const,
			    len,
			    DMA_CFG0_WORDSIZE_8 |
			    DMA_CFG0_TMODE_SINGLE |
			    SPI_DMA_RX(bus),
			    DMA_CFG1_SRCINC_0 |
			    DMA_CFG1_DESTINC_0 |
			    DMA_CFG1_PRIORITY_NORMAL);
	ao_dma_set_transfer(ao_spi_dma_out_id[bus],
			    block,
			    SPI_BUF(bus),
			    len,
			    DMA_CFG0_WORDSIZE_8 |
			    DMA_CFG0_TMODE_SINGLE |
			    SPI_DMA_TX(bus),
			    DMA_CFG1_SRCINC_1 |
			    DMA_CFG1_DESTINC_0 |
			    DMA_CFG1_PRIORITY_NORMAL);

	ao_dma_start(ao_spi_dma_in_id[bus]);
	ao_dma_start(ao_spi_dma_out_id[bus]);
	ao_dma_trigger(ao_spi_dma_out_id[bus]);
#if !AO_SPI_SLAVE
	__critical while (!ao_spi_dma_in_done[bus])
		ao_sleep(&ao_spi_dma_in_done[bus]);
#endif
#undef bus
}

#if AO_SPI_SLAVE
void
ao_spi_send_wait(void)
{
	__critical while (!ao_spi_dma_in_done[0])
		ao_sleep(&ao_spi_dma_in_done[0]);
}
#endif

/* Receive bytes over SPI.
 *
 * This sets up tow DMA engines, one reading the data and another
 * writing constant values to the SPI transmitter as that is what
 * clocks the data coming in.
 */
#if MULTI_SPI
void
ao_spi_recv(void __xdata *block, uint16_t len, uint8_t bus) __reentrant
#else
void
ao_spi_recv_bus(void __xdata *block, uint16_t len) __reentrant
#define bus 0
#endif
{
	ao_dma_set_transfer(ao_spi_dma_in_id[bus],
			    SPI_BUF(bus),
			    block,
			    len,
			    DMA_CFG0_WORDSIZE_8 |
			    DMA_CFG0_TMODE_SINGLE |
			    SPI_DMA_RX(bus),
			    DMA_CFG1_SRCINC_0 |
			    DMA_CFG1_DESTINC_1 |
			    DMA_CFG1_PRIORITY_NORMAL);

	ao_spi_const = SPI_CONST;

#if !AO_SPI_SLAVE
	ao_dma_set_transfer(ao_spi_dma_out_id[bus],
			    &ao_spi_const,
			    SPI_BUF(bus),
			    len,
			    DMA_CFG0_WORDSIZE_8 |
			    DMA_CFG0_TMODE_SINGLE |
			    SPI_DMA_TX(bus),
			    DMA_CFG1_SRCINC_0 |
			    DMA_CFG1_DESTINC_0 |
			    DMA_CFG1_PRIORITY_NORMAL);
#endif

	ao_dma_start(ao_spi_dma_in_id[bus]);
#if !AO_SPI_SLAVE
	ao_dma_start(ao_spi_dma_out_id[bus]);
	ao_dma_trigger(ao_spi_dma_out_id[bus]);
	__critical while (!ao_spi_dma_in_done[bus])
		ao_sleep(&ao_spi_dma_in_done[bus]);
#endif
}

#if AO_SPI_SLAVE
void
ao_spi_recv_wait(void)
{
	__critical while (!ao_spi_dma_in_done[0])
		ao_sleep(&ao_spi_dma_in_done[0]);
}
#endif

/* Set up the USART.
 *
 * SPI master/slave mode
 */
/* Set the baud rate and signal parameters
 *
 * The cc1111 is limited to a 24/8 MHz SPI clock.
 * Every peripheral I've ever seen goes faster than that,
 * so set the clock to 3MHz (BAUD_E 17, BAUD_M 0)
 */
#define SPI_INIT(bus,o)	do {						\
		/* Set up the USART pin assignment */			\
		PERCFG = (PERCFG & ~SPI_CFG_MASK(bus)) | SPI_CFG(bus);	\
									\
		/* Make the SPI pins be controlled by the USART peripheral */ \
		SPI_SEL(bus) |= SPI_BITS(bus) | CSS(bus);		\
		SPI_CSR(bus) = (UxCSR_MODE_SPI | UxCSR_RE | UxCSR_DIRECTION); \
		SPI_BAUD(bus) = 0;					\
		SPI_GCR(bus) = (UxGCR_CPOL_NEGATIVE |			\
				UxGCR_CPHA_FIRST_EDGE |			\
				UxGCR_ORDER_MSB |			\
				(17 << UxGCR_BAUD_E_SHIFT));		\
		/* Set up OUT DMA */					\
		ao_spi_dma_out_id[o] = ao_dma_alloc(&ao_spi_dma_out_done[o]); \
									\
		/* Set up IN DMA */					\
		ao_spi_dma_in_id[o] = ao_dma_alloc(&ao_spi_dma_in_done[o]);	\
	} while (0)

void
ao_spi_init(void)
{
	/* Ensure that SPI USART takes precidence over the other USART
	 * for pins that they share
	 */
#ifdef SPI_PRI
	P2SEL = (P2SEL & ~P2SEL_PRI3P1_MASK) | SPI_PRI;
#endif

#if HAS_SPI_0
	SPI_INIT(0, 0);
#endif
#if HAS_SPI_1
	SPI_INIT(1, MULTI_SPI);
#endif
}