blob: ddced41b9a8b4ec7478752ba9012cb2cd5f30625 [file] [log] [blame]
Brian Silverman41cdd3e2019-01-19 19:48:58 -08001/*----------------------------------------------------------------------------*/
2/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
8#include "hal/SPI.h"
9
10#include <fcntl.h>
11#include <linux/spi/spidev.h>
12#include <sys/ioctl.h>
13#include <unistd.h>
14
15#include <array>
16#include <atomic>
17#include <cstring>
18
19#include <wpi/mutex.h>
20#include <wpi/raw_ostream.h>
21
22#include "DigitalInternal.h"
23#include "HALInitializer.h"
24#include "hal/DIO.h"
25#include "hal/HAL.h"
26#include "hal/handles/HandlesInternal.h"
27
28using namespace hal;
29
30static int32_t m_spiCS0Handle{0};
31static int32_t m_spiCS1Handle{0};
32static int32_t m_spiCS2Handle{0};
33static int32_t m_spiCS3Handle{0};
34static int32_t m_spiMXPHandle{0};
35
36static constexpr int32_t kSpiMaxHandles = 5;
37
38// Indices 0-3 are for onboard CS0-CS2. Index 4 is for MXP.
39static std::array<wpi::mutex, kSpiMaxHandles> spiHandleMutexes;
40static std::array<wpi::mutex, kSpiMaxHandles> spiApiMutexes;
41static std::array<wpi::mutex, kSpiMaxHandles> spiAccumulatorMutexes;
42
43// MXP SPI does not count towards this
44static std::atomic<int32_t> spiPortCount{0};
45
46static HAL_DigitalHandle digitalHandles[9]{HAL_kInvalidHandle};
47
48static wpi::mutex spiAutoMutex;
49static int32_t spiAutoPort = kSpiMaxHandles;
50static std::atomic_bool spiAutoRunning{false};
51static std::unique_ptr<tDMAManager> spiAutoDMA;
52
53static bool SPIInUseByAuto(HAL_SPIPort port) {
54 // SPI engine conflicts with any other chip selects on the same SPI device.
55 // There are two SPI devices: one for ports 0-3 (onboard), the other for port
56 // 4 (MXP).
57 if (!spiAutoRunning) return false;
58 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
59 return (spiAutoPort >= 0 && spiAutoPort <= 3 && port >= 0 && port <= 3) ||
60 (spiAutoPort == 4 && port == 4);
61}
62
63namespace hal {
64namespace init {
65void InitializeSPI() {}
66} // namespace init
67} // namespace hal
68
69extern "C" {
70
71static void CommonSPIPortInit(int32_t* status) {
72 // All false cases will set
73 if (spiPortCount.fetch_add(1) == 0) {
74 // Have not been initialized yet
75 initializeDigital(status);
76 if (*status != 0) return;
77 // MISO
78 if ((digitalHandles[3] = HAL_InitializeDIOPort(createPortHandleForSPI(29),
79 false, status)) ==
80 HAL_kInvalidHandle) {
81 std::printf("Failed to allocate DIO 29 (MISO)\n");
82 return;
83 }
84 // MOSI
85 if ((digitalHandles[4] = HAL_InitializeDIOPort(createPortHandleForSPI(30),
86 false, status)) ==
87 HAL_kInvalidHandle) {
88 std::printf("Failed to allocate DIO 30 (MOSI)\n");
89 HAL_FreeDIOPort(digitalHandles[3]); // free the first port allocated
90 return;
91 }
92 }
93}
94
95static void CommonSPIPortFree(void) {
96 if (spiPortCount.fetch_sub(1) == 1) {
97 // Clean up SPI Handles
98 HAL_FreeDIOPort(digitalHandles[3]);
99 HAL_FreeDIOPort(digitalHandles[4]);
100 }
101}
102
103void HAL_InitializeSPI(HAL_SPIPort port, int32_t* status) {
104 hal::init::CheckInit();
105 if (port < 0 || port >= kSpiMaxHandles) {
106 *status = PARAMETER_OUT_OF_RANGE;
107 return;
108 }
109
110 int handle;
111 if (HAL_GetSPIHandle(port) != 0) return;
112 switch (port) {
113 case HAL_SPI_kOnboardCS0:
114 CommonSPIPortInit(status);
115 if (*status != 0) return;
116 // CS0 is not a DIO port, so nothing to allocate
117 handle = open("/dev/spidev0.0", O_RDWR);
118 if (handle < 0) {
119 std::printf("Failed to open SPI port %d: %s\n", port,
120 std::strerror(errno));
121 CommonSPIPortFree();
122 return;
123 }
124 HAL_SetSPIHandle(HAL_SPI_kOnboardCS0, handle);
125 break;
126 case HAL_SPI_kOnboardCS1:
127 CommonSPIPortInit(status);
128 if (*status != 0) return;
129 // CS1, Allocate
130 if ((digitalHandles[0] = HAL_InitializeDIOPort(createPortHandleForSPI(26),
131 false, status)) ==
132 HAL_kInvalidHandle) {
133 std::printf("Failed to allocate DIO 26 (CS1)\n");
134 CommonSPIPortFree();
135 return;
136 }
137 handle = open("/dev/spidev0.1", O_RDWR);
138 if (handle < 0) {
139 std::printf("Failed to open SPI port %d: %s\n", port,
140 std::strerror(errno));
141 CommonSPIPortFree();
142 HAL_FreeDIOPort(digitalHandles[0]);
143 return;
144 }
145 HAL_SetSPIHandle(HAL_SPI_kOnboardCS1, handle);
146 break;
147 case HAL_SPI_kOnboardCS2:
148 CommonSPIPortInit(status);
149 if (*status != 0) return;
150 // CS2, Allocate
151 if ((digitalHandles[1] = HAL_InitializeDIOPort(createPortHandleForSPI(27),
152 false, status)) ==
153 HAL_kInvalidHandle) {
154 std::printf("Failed to allocate DIO 27 (CS2)\n");
155 CommonSPIPortFree();
156 return;
157 }
158 handle = open("/dev/spidev0.2", O_RDWR);
159 if (handle < 0) {
160 std::printf("Failed to open SPI port %d: %s\n", port,
161 std::strerror(errno));
162 CommonSPIPortFree();
163 HAL_FreeDIOPort(digitalHandles[1]);
164 return;
165 }
166 HAL_SetSPIHandle(HAL_SPI_kOnboardCS2, handle);
167 break;
168 case HAL_SPI_kOnboardCS3:
169 CommonSPIPortInit(status);
170 if (*status != 0) return;
171 // CS3, Allocate
172 if ((digitalHandles[2] = HAL_InitializeDIOPort(createPortHandleForSPI(28),
173 false, status)) ==
174 HAL_kInvalidHandle) {
175 std::printf("Failed to allocate DIO 28 (CS3)\n");
176 CommonSPIPortFree();
177 return;
178 }
179 handle = open("/dev/spidev0.3", O_RDWR);
180 if (handle < 0) {
181 std::printf("Failed to open SPI port %d: %s\n", port,
182 std::strerror(errno));
183 CommonSPIPortFree();
184 HAL_FreeDIOPort(digitalHandles[2]);
185 return;
186 }
187 HAL_SetSPIHandle(HAL_SPI_kOnboardCS3, handle);
188 break;
189 case HAL_SPI_kMXP:
190 initializeDigital(status);
191 if (*status != 0) return;
192 if ((digitalHandles[5] = HAL_InitializeDIOPort(createPortHandleForSPI(14),
193 false, status)) ==
194 HAL_kInvalidHandle) {
195 wpi::outs() << "Failed to allocate DIO 14\n";
196 return;
197 }
198 if ((digitalHandles[6] = HAL_InitializeDIOPort(createPortHandleForSPI(15),
199 false, status)) ==
200 HAL_kInvalidHandle) {
201 wpi::outs() << "Failed to allocate DIO 15\n";
202 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
203 return;
204 }
205 if ((digitalHandles[7] = HAL_InitializeDIOPort(createPortHandleForSPI(16),
206 false, status)) ==
207 HAL_kInvalidHandle) {
208 wpi::outs() << "Failed to allocate DIO 16\n";
209 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
210 HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated
211 return;
212 }
213 if ((digitalHandles[8] = HAL_InitializeDIOPort(createPortHandleForSPI(17),
214 false, status)) ==
215 HAL_kInvalidHandle) {
216 wpi::outs() << "Failed to allocate DIO 17\n";
217 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
218 HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated
219 HAL_FreeDIOPort(digitalHandles[7]); // free the third port allocated
220 return;
221 }
222 digitalSystem->writeEnableMXPSpecialFunction(
223 digitalSystem->readEnableMXPSpecialFunction(status) | 0x00F0, status);
224 handle = open("/dev/spidev1.0", O_RDWR);
225 if (handle < 0) {
226 std::printf("Failed to open SPI port %d: %s\n", port,
227 std::strerror(errno));
228 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
229 HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated
230 HAL_FreeDIOPort(digitalHandles[7]); // free the third port allocated
231 HAL_FreeDIOPort(digitalHandles[8]); // free the fourth port allocated
232 return;
233 }
234 HAL_SetSPIHandle(HAL_SPI_kMXP, handle);
235 break;
236 default:
237 *status = PARAMETER_OUT_OF_RANGE;
238 break;
239 }
240}
241
242int32_t HAL_TransactionSPI(HAL_SPIPort port, const uint8_t* dataToSend,
243 uint8_t* dataReceived, int32_t size) {
244 if (port < 0 || port >= kSpiMaxHandles) {
245 return -1;
246 }
247
248 if (SPIInUseByAuto(port)) return -1;
249
250 struct spi_ioc_transfer xfer;
251 std::memset(&xfer, 0, sizeof(xfer));
252 xfer.tx_buf = (__u64)dataToSend;
253 xfer.rx_buf = (__u64)dataReceived;
254 xfer.len = size;
255
256 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
257 return ioctl(HAL_GetSPIHandle(port), SPI_IOC_MESSAGE(1), &xfer);
258}
259
260int32_t HAL_WriteSPI(HAL_SPIPort port, const uint8_t* dataToSend,
261 int32_t sendSize) {
262 if (port < 0 || port >= kSpiMaxHandles) {
263 return -1;
264 }
265
266 if (SPIInUseByAuto(port)) return -1;
267
268 struct spi_ioc_transfer xfer;
269 std::memset(&xfer, 0, sizeof(xfer));
270 xfer.tx_buf = (__u64)dataToSend;
271 xfer.len = sendSize;
272
273 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
274 return ioctl(HAL_GetSPIHandle(port), SPI_IOC_MESSAGE(1), &xfer);
275}
276
277int32_t HAL_ReadSPI(HAL_SPIPort port, uint8_t* buffer, int32_t count) {
278 if (port < 0 || port >= kSpiMaxHandles) {
279 return -1;
280 }
281
282 if (SPIInUseByAuto(port)) return -1;
283
284 struct spi_ioc_transfer xfer;
285 std::memset(&xfer, 0, sizeof(xfer));
286 xfer.rx_buf = (__u64)buffer;
287 xfer.len = count;
288
289 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
290 return ioctl(HAL_GetSPIHandle(port), SPI_IOC_MESSAGE(1), &xfer);
291}
292
293void HAL_CloseSPI(HAL_SPIPort port) {
294 if (port < 0 || port >= kSpiMaxHandles) {
295 return;
296 }
297
298 int32_t status = 0;
299 HAL_FreeSPIAuto(port, &status);
300
301 {
302 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
303 close(HAL_GetSPIHandle(port));
304 }
305
306 HAL_SetSPIHandle(port, 0);
307 if (port < 4) {
308 CommonSPIPortFree();
309 }
310
311 switch (port) {
312 // Case 0 does not need to do anything
313 case 1:
314 HAL_FreeDIOPort(digitalHandles[0]);
315 break;
316 case 2:
317 HAL_FreeDIOPort(digitalHandles[1]);
318 break;
319 case 3:
320 HAL_FreeDIOPort(digitalHandles[2]);
321 break;
322 case 4:
323 HAL_FreeDIOPort(digitalHandles[5]);
324 HAL_FreeDIOPort(digitalHandles[6]);
325 HAL_FreeDIOPort(digitalHandles[7]);
326 HAL_FreeDIOPort(digitalHandles[8]);
327 break;
328 default:
329 break;
330 }
331}
332
333void HAL_SetSPISpeed(HAL_SPIPort port, int32_t speed) {
334 if (port < 0 || port >= kSpiMaxHandles) {
335 return;
336 }
337
338 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
339 ioctl(HAL_GetSPIHandle(port), SPI_IOC_WR_MAX_SPEED_HZ, &speed);
340}
341
342void HAL_SetSPIOpts(HAL_SPIPort port, HAL_Bool msbFirst,
343 HAL_Bool sampleOnTrailing, HAL_Bool clkIdleHigh) {
344 if (port < 0 || port >= kSpiMaxHandles) {
345 return;
346 }
347
348 uint8_t mode = 0;
349 mode |= (!msbFirst ? 8 : 0);
350 mode |= (clkIdleHigh ? 2 : 0);
351 mode |= (sampleOnTrailing ? 1 : 0);
352
353 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
354 ioctl(HAL_GetSPIHandle(port), SPI_IOC_WR_MODE, &mode);
355}
356
357void HAL_SetSPIChipSelectActiveHigh(HAL_SPIPort port, int32_t* status) {
358 if (port < 0 || port >= kSpiMaxHandles) {
359 *status = PARAMETER_OUT_OF_RANGE;
360 return;
361 }
362
363 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
364 if (port < 4) {
365 spiSystem->writeChipSelectActiveHigh_Hdr(
366 spiSystem->readChipSelectActiveHigh_Hdr(status) | (1 << port), status);
367 } else {
368 spiSystem->writeChipSelectActiveHigh_MXP(1, status);
369 }
370}
371
372void HAL_SetSPIChipSelectActiveLow(HAL_SPIPort port, int32_t* status) {
373 if (port < 0 || port >= kSpiMaxHandles) {
374 *status = PARAMETER_OUT_OF_RANGE;
375 return;
376 }
377
378 std::lock_guard<wpi::mutex> lock(spiApiMutexes[port]);
379 if (port < 4) {
380 spiSystem->writeChipSelectActiveHigh_Hdr(
381 spiSystem->readChipSelectActiveHigh_Hdr(status) & ~(1 << port), status);
382 } else {
383 spiSystem->writeChipSelectActiveHigh_MXP(0, status);
384 }
385}
386
387int32_t HAL_GetSPIHandle(HAL_SPIPort port) {
388 if (port < 0 || port >= kSpiMaxHandles) {
389 return 0;
390 }
391
392 std::lock_guard<wpi::mutex> lock(spiHandleMutexes[port]);
393 switch (port) {
394 case 0:
395 return m_spiCS0Handle;
396 case 1:
397 return m_spiCS1Handle;
398 case 2:
399 return m_spiCS2Handle;
400 case 3:
401 return m_spiCS3Handle;
402 case 4:
403 return m_spiMXPHandle;
404 default:
405 return 0;
406 }
407}
408
409void HAL_SetSPIHandle(HAL_SPIPort port, int32_t handle) {
410 if (port < 0 || port >= kSpiMaxHandles) {
411 return;
412 }
413
414 std::lock_guard<wpi::mutex> lock(spiHandleMutexes[port]);
415 switch (port) {
416 case 0:
417 m_spiCS0Handle = handle;
418 break;
419 case 1:
420 m_spiCS1Handle = handle;
421 break;
422 case 2:
423 m_spiCS2Handle = handle;
424 break;
425 case 3:
426 m_spiCS3Handle = handle;
427 break;
428 case 4:
429 m_spiMXPHandle = handle;
430 break;
431 default:
432 break;
433 }
434}
435
436void HAL_InitSPIAuto(HAL_SPIPort port, int32_t bufferSize, int32_t* status) {
437 if (port < 0 || port >= kSpiMaxHandles) {
438 *status = PARAMETER_OUT_OF_RANGE;
439 return;
440 }
441
442 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
443 // FPGA only has one auto SPI engine
444 if (spiAutoPort != kSpiMaxHandles) {
445 *status = RESOURCE_IS_ALLOCATED;
446 return;
447 }
448
449 // remember the initialized port for other entry points
450 spiAutoPort = port;
451
452 // configure the correct chip select
453 if (port < 4) {
454 spiSystem->writeAutoSPI1Select(false, status);
455 spiSystem->writeAutoChipSelect(port, status);
456 } else {
457 spiSystem->writeAutoSPI1Select(true, status);
458 spiSystem->writeAutoChipSelect(0, status);
459 }
460
461 // configure DMA
462 tDMAChannelDescriptor desc;
463 spiSystem->getSystemInterface()->getDmaDescriptor(g_SpiAutoData_index, &desc);
464 spiAutoDMA = std::make_unique<tDMAManager>(desc.channel, bufferSize, status);
465}
466
467void HAL_FreeSPIAuto(HAL_SPIPort port, int32_t* status) {
468 if (port < 0 || port >= kSpiMaxHandles) {
469 *status = PARAMETER_OUT_OF_RANGE;
470 return;
471 }
472
473 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
474 if (spiAutoPort != port) return;
475 spiAutoPort = kSpiMaxHandles;
476
477 // disable by setting to internal clock and setting rate=0
478 spiSystem->writeAutoRate(0, status);
479 spiSystem->writeAutoTriggerConfig_ExternalClock(false, status);
480
481 // stop the DMA
482 spiAutoDMA->stop(status);
483
484 spiAutoDMA.reset(nullptr);
485
486 spiAutoRunning = false;
487}
488
489void HAL_StartSPIAutoRate(HAL_SPIPort port, double period, int32_t* status) {
490 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
491 // FPGA only has one auto SPI engine
492 if (port != spiAutoPort) {
493 *status = INCOMPATIBLE_STATE;
494 return;
495 }
496
497 spiAutoRunning = true;
498
499 // start the DMA
500 spiAutoDMA->start(status);
501
502 // auto rate is in microseconds
503 spiSystem->writeAutoRate(period * 1000000, status);
504
505 // disable the external clock
506 spiSystem->writeAutoTriggerConfig_ExternalClock(false, status);
507}
508
509void HAL_StartSPIAutoTrigger(HAL_SPIPort port, HAL_Handle digitalSourceHandle,
510 HAL_AnalogTriggerType analogTriggerType,
511 HAL_Bool triggerRising, HAL_Bool triggerFalling,
512 int32_t* status) {
513 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
514 // FPGA only has one auto SPI engine
515 if (port != spiAutoPort) {
516 *status = INCOMPATIBLE_STATE;
517 return;
518 }
519
520 spiAutoRunning = true;
521
522 // start the DMA
523 spiAutoDMA->start(status);
524
525 // get channel routing
526 bool routingAnalogTrigger = false;
527 uint8_t routingChannel = 0;
528 uint8_t routingModule = 0;
529 if (!remapDigitalSource(digitalSourceHandle, analogTriggerType,
530 routingChannel, routingModule,
531 routingAnalogTrigger)) {
532 *status = HAL_HANDLE_ERROR;
533 return;
534 }
535
536 // configure external trigger and enable it
537 tSPI::tAutoTriggerConfig config;
538 config.ExternalClock = 1;
539 config.FallingEdge = triggerFalling ? 1 : 0;
540 config.RisingEdge = triggerRising ? 1 : 0;
541 config.ExternalClockSource_AnalogTrigger = routingAnalogTrigger ? 1 : 0;
542 config.ExternalClockSource_Module = routingModule;
543 config.ExternalClockSource_Channel = routingChannel;
544 spiSystem->writeAutoTriggerConfig(config, status);
545}
546
547void HAL_StopSPIAuto(HAL_SPIPort port, int32_t* status) {
548 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
549 // FPGA only has one auto SPI engine
550 if (port != spiAutoPort) {
551 *status = INCOMPATIBLE_STATE;
552 return;
553 }
554
555 // disable by setting to internal clock and setting rate=0
556 spiSystem->writeAutoRate(0, status);
557 spiSystem->writeAutoTriggerConfig_ExternalClock(false, status);
558
559 // stop the DMA
560 spiAutoDMA->stop(status);
561
562 spiAutoRunning = false;
563}
564
565void HAL_SetSPIAutoTransmitData(HAL_SPIPort port, const uint8_t* dataToSend,
566 int32_t dataSize, int32_t zeroSize,
567 int32_t* status) {
568 if (dataSize < 0 || dataSize > 16) {
569 *status = PARAMETER_OUT_OF_RANGE;
570 return;
571 }
572
573 if (zeroSize < 0 || zeroSize > 127) {
574 *status = PARAMETER_OUT_OF_RANGE;
575 return;
576 }
577
578 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
579 // FPGA only has one auto SPI engine
580 if (port != spiAutoPort) {
581 *status = INCOMPATIBLE_STATE;
582 return;
583 }
584
585 // set tx data registers
586 for (int32_t i = 0; i < dataSize; ++i)
587 spiSystem->writeAutoTx(i >> 2, i & 3, dataToSend[i], status);
588
589 // set byte counts
590 tSPI::tAutoByteCount config;
591 config.ZeroByteCount = static_cast<unsigned>(zeroSize) & 0x7f;
592 config.TxByteCount = static_cast<unsigned>(dataSize) & 0xf;
593 spiSystem->writeAutoByteCount(config, status);
594}
595
596void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) {
597 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
598 // FPGA only has one auto SPI engine
599 if (port != spiAutoPort) {
600 *status = INCOMPATIBLE_STATE;
601 return;
602 }
603
604 spiSystem->strobeAutoForceOne(status);
605}
606
607int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
608 int32_t numToRead, double timeout,
609 int32_t* status) {
610 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
611 // FPGA only has one auto SPI engine
612 if (port != spiAutoPort) {
613 *status = INCOMPATIBLE_STATE;
614 return 0;
615 }
616
617 size_t numRemaining = 0;
618 // timeout is in ms
619 spiAutoDMA->read(buffer, numToRead, timeout * 1000, &numRemaining, status);
620 return numRemaining;
621}
622
623int32_t HAL_GetSPIAutoDroppedCount(HAL_SPIPort port, int32_t* status) {
624 std::lock_guard<wpi::mutex> lock(spiAutoMutex);
625 // FPGA only has one auto SPI engine
626 if (port != spiAutoPort) {
627 *status = INCOMPATIBLE_STATE;
628 return 0;
629 }
630
631 return spiSystem->readTransferSkippedFullCount(status);
632}
633
634} // extern "C"