-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
bus_spi_stdperiph.c
366 lines (302 loc) · 11.6 KB
/
bus_spi_stdperiph.c
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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/*
* This file is part of Cleanflight and Betaflight.
*
* Cleanflight and Betaflight are free software. You can redistribute
* this software and/or modify this software under the terms of the
* GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Cleanflight and Betaflight are distributed in the hope that they
* 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 software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
#ifdef USE_SPI
// STM32F405 can't DMA to/from FASTRAM (CCM SRAM)
#define IS_CCM(p) (((uint32_t)p & 0xffff0000) == 0x10000000)
#include "common/maths.h"
#include "drivers/bus.h"
#include "drivers/bus_spi.h"
#include "drivers/bus_spi_impl.h"
#include "drivers/exti.h"
#include "drivers/io.h"
#include "drivers/rcc.h"
static SPI_InitTypeDef defaultInit = {
.SPI_Mode = SPI_Mode_Master,
.SPI_Direction = SPI_Direction_2Lines_FullDuplex,
.SPI_DataSize = SPI_DataSize_8b,
.SPI_NSS = SPI_NSS_Soft,
.SPI_FirstBit = SPI_FirstBit_MSB,
.SPI_CRCPolynomial = 7,
.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8,
.SPI_CPOL = SPI_CPOL_High,
.SPI_CPHA = SPI_CPHA_2Edge
};
static uint16_t spiDivisorToBRbits(SPI_TypeDef *instance, uint16_t divisor)
{
// SPI2 and SPI3 are on APB1/AHB1 which PCLK is half that of APB2/AHB2.
#if defined(STM32F410xx) || defined(STM32F411xE)
UNUSED(instance);
#else
if (instance == SPI2 || instance == SPI3) {
divisor /= 2; // Safe for divisor == 0 or 1
}
#endif
divisor = constrain(divisor, 2, 256);
return (ffs(divisor) - 2) << 3; // SPI_CR1_BR_Pos
}
static void spiSetDivisorBRreg(SPI_TypeDef *instance, uint16_t divisor)
{
#define BR_BITS ((BIT(5) | BIT(4) | BIT(3)))
const uint16_t tempRegister = (instance->CR1 & ~BR_BITS);
instance->CR1 = tempRegister | spiDivisorToBRbits(instance, divisor);
#undef BR_BITS
}
void spiInitDevice(SPIDevice device)
{
spiDevice_t *spi = &(spiDevice[device]);
if (!spi->dev) {
return;
}
// Enable SPI clock
RCC_ClockCmd(spi->rcc, ENABLE);
RCC_ResetCmd(spi->rcc, ENABLE);
IOInit(IOGetByTag(spi->sck), OWNER_SPI_SCK, RESOURCE_INDEX(device));
IOInit(IOGetByTag(spi->miso), OWNER_SPI_MISO, RESOURCE_INDEX(device));
IOInit(IOGetByTag(spi->mosi), OWNER_SPI_MOSI, RESOURCE_INDEX(device));
IOConfigGPIOAF(IOGetByTag(spi->sck), SPI_IO_AF_SCK_CFG, spi->af);
IOConfigGPIOAF(IOGetByTag(spi->miso), SPI_IO_AF_MISO_CFG, spi->af);
IOConfigGPIOAF(IOGetByTag(spi->mosi), SPI_IO_AF_CFG, spi->af);
// Init SPI hardware
SPI_I2S_DeInit(spi->dev);
SPI_I2S_DMACmd(spi->dev, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, DISABLE);
SPI_Init(spi->dev, &defaultInit);
SPI_Cmd(spi->dev, ENABLE);
}
void spiInternalResetDescriptors(busDevice_t *bus)
{
DMA_InitTypeDef *initTx = bus->initTx;
DMA_InitTypeDef *initRx = bus->initRx;
DMA_StructInit(initTx);
initTx->DMA_Channel = bus->dmaTxChannel;
initTx->DMA_DIR = DMA_DIR_MemoryToPeripheral;
initTx->DMA_Mode = DMA_Mode_Normal;
initTx->DMA_PeripheralBaseAddr = (uint32_t)&bus->busType_u.spi.instance->DR;
initTx->DMA_Priority = DMA_Priority_Low;
initTx->DMA_PeripheralInc = DMA_PeripheralInc_Disable;
initTx->DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
initTx->DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_StructInit(initRx);
initRx->DMA_Channel = bus->dmaRxChannel;
initRx->DMA_DIR = DMA_DIR_PeripheralToMemory;
initRx->DMA_Mode = DMA_Mode_Normal;
initRx->DMA_PeripheralBaseAddr = (uint32_t)&bus->busType_u.spi.instance->DR;
initRx->DMA_Priority = DMA_Priority_Low;
initRx->DMA_PeripheralInc = DMA_PeripheralInc_Disable;
initRx->DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
}
void spiInternalResetStream(dmaChannelDescriptor_t *descriptor)
{
DMA_Stream_TypeDef *streamRegs = (DMA_Stream_TypeDef *)descriptor->ref;
// Disable the stream
streamRegs->CR = 0U;
// Clear any pending interrupt flags
DMA_CLEAR_FLAG(descriptor, DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_TCIF);
}
static bool spiInternalReadWriteBufPolled(SPI_TypeDef *instance, const uint8_t *txData, uint8_t *rxData, int len)
{
uint8_t b;
while (len--) {
b = txData ? *(txData++) : 0xFF;
while (SPI_I2S_GetFlagStatus(instance, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(instance, b);
while (SPI_I2S_GetFlagStatus(instance, SPI_I2S_FLAG_RXNE) == RESET);
b = SPI_I2S_ReceiveData(instance);
if (rxData) {
*(rxData++) = b;
}
}
return true;
}
void spiInternalInitStream(const extDevice_t *dev, bool preInit)
{
static uint8_t dummyTxByte = 0xff;
static uint8_t dummyRxByte;
busDevice_t *bus = dev->bus;
volatile busSegment_t *segment = bus->curSegment;
if (preInit) {
// Prepare the init structure for the next segment to reduce inter-segment interval
segment++;
if(segment->len == 0) {
// There's no following segment
return;
}
}
uint8_t *txData = segment->txData;
uint8_t *rxData = segment->rxData;
int len = segment->len;
DMA_InitTypeDef *initTx = bus->initTx;
DMA_InitTypeDef *initRx = bus->initRx;
if (txData) {
initTx->DMA_Memory0BaseAddr = (uint32_t)txData;
initTx->DMA_MemoryInc = DMA_MemoryInc_Enable;
} else {
dummyTxByte = 0xff;
initTx->DMA_Memory0BaseAddr = (uint32_t)&dummyTxByte;
initTx->DMA_MemoryInc = DMA_MemoryInc_Disable;
}
initTx->DMA_BufferSize = len;
if (rxData) {
initRx->DMA_Memory0BaseAddr = (uint32_t)rxData;
initRx->DMA_MemoryInc = DMA_MemoryInc_Enable;
} else {
initRx->DMA_Memory0BaseAddr = (uint32_t)&dummyRxByte;
initRx->DMA_MemoryInc = DMA_MemoryInc_Disable;
}
// If possible use 16 bit memory writes to prevent atomic access issues on gyro data
if ((initRx->DMA_Memory0BaseAddr & 0x1) || (len & 0x1))
{
initRx->DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
} else {
initRx->DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
}
initRx->DMA_BufferSize = len;
}
void spiInternalStartDMA(const extDevice_t *dev)
{
// Assert Chip Select
IOLo(dev->busType_u.spi.csnPin);
dmaChannelDescriptor_t *dmaTx = dev->bus->dmaTx;
dmaChannelDescriptor_t *dmaRx = dev->bus->dmaRx;
DMA_Stream_TypeDef *streamRegsTx = (DMA_Stream_TypeDef *)dmaTx->ref;
DMA_Stream_TypeDef *streamRegsRx = (DMA_Stream_TypeDef *)dmaRx->ref;
// Use the correct callback argument
dmaRx->userParam = (uint32_t)dev;
// Clear transfer flags
DMA_CLEAR_FLAG(dmaTx, DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_TCIF);
DMA_CLEAR_FLAG(dmaRx, DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_TCIF);
// Disable streams to enable update
streamRegsTx->CR = 0U;
streamRegsRx->CR = 0U;
/* Use the Rx interrupt as this occurs once the SPI operation is complete whereas the Tx interrupt
* occurs earlier when the Tx FIFO is empty, but the SPI operation is still in progress
*/
DMA_ITConfig(streamRegsRx, DMA_IT_TC, ENABLE);
// Update streams
DMA_Init(streamRegsTx, dev->bus->initTx);
DMA_Init(streamRegsRx, dev->bus->initRx);
/* Note from AN4031
*
* If the user enables the used peripheral before the corresponding DMA stream, a “FEIF”
* (FIFO Error Interrupt Flag) may be set due to the fact the DMA is not ready to provide
* the first required data to the peripheral (in case of memory-to-peripheral transfer).
*/
// Enable streams
DMA_Cmd(streamRegsTx, ENABLE);
DMA_Cmd(streamRegsRx, ENABLE);
/* Enable the SPI DMA Tx & Rx requests */
SPI_I2S_DMACmd(dev->bus->busType_u.spi.instance, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE);
}
void spiInternalStopDMA (const extDevice_t *dev)
{
dmaChannelDescriptor_t *dmaTx = dev->bus->dmaTx;
dmaChannelDescriptor_t *dmaRx = dev->bus->dmaRx;
DMA_Stream_TypeDef *streamRegsTx = (DMA_Stream_TypeDef *)dmaTx->ref;
DMA_Stream_TypeDef *streamRegsRx = (DMA_Stream_TypeDef *)dmaRx->ref;
SPI_TypeDef *instance = dev->bus->busType_u.spi.instance;
// Disable streams
streamRegsTx->CR = 0U;
streamRegsRx->CR = 0U;
SPI_I2S_DMACmd(instance, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, DISABLE);
}
// DMA transfer setup and start
void spiSequence(const extDevice_t *dev, busSegment_t *segments)
{
busDevice_t *bus = dev->bus;
SPI_TypeDef *instance = bus->busType_u.spi.instance;
bool dmaSafe = dev->useDMA;
uint32_t xferLen = 0;
uint32_t segmentCount = 0;
dev->bus->initSegment = true;
dev->bus->curSegment = segments;
SPI_Cmd(instance, DISABLE);
// Switch bus speed
if (dev->busType_u.spi.speed != bus->busType_u.spi.speed) {
spiSetDivisorBRreg(bus->busType_u.spi.instance, dev->busType_u.spi.speed);
bus->busType_u.spi.speed = dev->busType_u.spi.speed;
}
if (dev->busType_u.spi.leadingEdge != bus->busType_u.spi.leadingEdge) {
// Switch SPI clock polarity/phase
instance->CR1 &= ~(SPI_CPOL_High | SPI_CPHA_2Edge);
// Apply setting
if (dev->busType_u.spi.leadingEdge) {
instance->CR1 |= SPI_CPOL_Low | SPI_CPHA_1Edge;
} else
{
instance->CR1 |= SPI_CPOL_High | SPI_CPHA_2Edge;
}
bus->busType_u.spi.leadingEdge = dev->busType_u.spi.leadingEdge;
}
SPI_Cmd(instance, ENABLE);
// Check that any there are no attempts to DMA to/from CCD SRAM
for (busSegment_t *checkSegment = bus->curSegment; checkSegment->len; checkSegment++) {
if (((checkSegment->rxData) && IS_CCM(checkSegment->rxData)) ||
((checkSegment->txData) && IS_CCM(checkSegment->txData))) {
dmaSafe = false;
break;
}
// Note that these counts are only valid if dmaSafe is true
segmentCount++;
xferLen += checkSegment->len;
}
// Use DMA if possible
if (bus->useDMA && dmaSafe && ((segmentCount > 1) || (xferLen > 8))) {
// Intialise the init structures for the first transfer
spiInternalInitStream(dev, false);
// Start the transfers
spiInternalStartDMA(dev);
} else {
// Manually work through the segment list performing a transfer for each
while (bus->curSegment->len) {
// Assert Chip Select
IOLo(dev->busType_u.spi.csnPin);
spiInternalReadWriteBufPolled(
bus->busType_u.spi.instance,
bus->curSegment->txData,
bus->curSegment->rxData,
bus->curSegment->len);
if (bus->curSegment->negateCS) {
// Negate Chip Select
IOHi(dev->busType_u.spi.csnPin);
}
if (bus->curSegment->callback) {
switch(bus->curSegment->callback(dev->callbackArg)) {
case BUS_BUSY:
// Repeat the last DMA segment
bus->curSegment--;
break;
case BUS_ABORT:
bus->curSegment = (busSegment_t *)NULL;
return;
case BUS_READY:
default:
// Advance to the next DMA segment
break;
}
}
bus->curSegment++;
}
bus->curSegment = (busSegment_t *)NULL;
}
}
#endif