/*----------------------------------------------------------------------------*/ /* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ /*----------------------------------------------------------------------------*/ #include "hal/SPI.h" #include #include #include #include #include #include #include #include #include #include "DigitalInternal.h" #include "HALInitializer.h" #include "hal/DIO.h" #include "hal/HAL.h" #include "hal/handles/HandlesInternal.h" using namespace hal; static int32_t m_spiCS0Handle{0}; static int32_t m_spiCS1Handle{0}; static int32_t m_spiCS2Handle{0}; static int32_t m_spiCS3Handle{0}; static int32_t m_spiMXPHandle{0}; static constexpr int32_t kSpiMaxHandles = 5; // Indices 0-3 are for onboard CS0-CS2. Index 4 is for MXP. static std::array spiHandleMutexes; static std::array spiApiMutexes; static std::array spiAccumulatorMutexes; // MXP SPI does not count towards this static std::atomic spiPortCount{0}; static HAL_DigitalHandle digitalHandles[9]{HAL_kInvalidHandle}; static wpi::mutex spiAutoMutex; static int32_t spiAutoPort = kSpiMaxHandles; static std::atomic_bool spiAutoRunning{false}; static std::unique_ptr spiAutoDMA; static bool SPIInUseByAuto(HAL_SPIPort port) { // SPI engine conflicts with any other chip selects on the same SPI device. // There are two SPI devices: one for ports 0-3 (onboard), the other for port // 4 (MXP). if (!spiAutoRunning) return false; std::lock_guard lock(spiAutoMutex); return (spiAutoPort >= 0 && spiAutoPort <= 3 && port >= 0 && port <= 3) || (spiAutoPort == 4 && port == 4); } namespace hal { namespace init { void InitializeSPI() {} } // namespace init } // namespace hal extern "C" { static void CommonSPIPortInit(int32_t* status) { // All false cases will set if (spiPortCount.fetch_add(1) == 0) { // Have not been initialized yet initializeDigital(status); if (*status != 0) return; // MISO if ((digitalHandles[3] = HAL_InitializeDIOPort(createPortHandleForSPI(29), false, status)) == HAL_kInvalidHandle) { std::printf("Failed to allocate DIO 29 (MISO)\n"); return; } // MOSI if ((digitalHandles[4] = HAL_InitializeDIOPort(createPortHandleForSPI(30), false, status)) == HAL_kInvalidHandle) { std::printf("Failed to allocate DIO 30 (MOSI)\n"); HAL_FreeDIOPort(digitalHandles[3]); // free the first port allocated return; } } } static void CommonSPIPortFree(void) { if (spiPortCount.fetch_sub(1) == 1) { // Clean up SPI Handles HAL_FreeDIOPort(digitalHandles[3]); HAL_FreeDIOPort(digitalHandles[4]); } } void HAL_InitializeSPI(HAL_SPIPort port, int32_t* status) { hal::init::CheckInit(); if (port < 0 || port >= kSpiMaxHandles) { *status = PARAMETER_OUT_OF_RANGE; return; } int handle; if (HAL_GetSPIHandle(port) != 0) return; switch (port) { case HAL_SPI_kOnboardCS0: CommonSPIPortInit(status); if (*status != 0) return; // CS0 is not a DIO port, so nothing to allocate handle = open("/dev/spidev0.0", O_RDWR); if (handle < 0) { std::printf("Failed to open SPI port %d: %s\n", port, std::strerror(errno)); CommonSPIPortFree(); return; } HAL_SetSPIHandle(HAL_SPI_kOnboardCS0, handle); break; case HAL_SPI_kOnboardCS1: CommonSPIPortInit(status); if (*status != 0) return; // CS1, Allocate if ((digitalHandles[0] = HAL_InitializeDIOPort(createPortHandleForSPI(26), false, status)) == HAL_kInvalidHandle) { std::printf("Failed to allocate DIO 26 (CS1)\n"); CommonSPIPortFree(); return; } handle = open("/dev/spidev0.1", O_RDWR); if (handle < 0) { std::printf("Failed to open SPI port %d: %s\n", port, std::strerror(errno)); CommonSPIPortFree(); HAL_FreeDIOPort(digitalHandles[0]); return; } HAL_SetSPIHandle(HAL_SPI_kOnboardCS1, handle); break; case HAL_SPI_kOnboardCS2: CommonSPIPortInit(status); if (*status != 0) return; // CS2, Allocate if ((digitalHandles[1] = HAL_InitializeDIOPort(createPortHandleForSPI(27), false, status)) == HAL_kInvalidHandle) { std::printf("Failed to allocate DIO 27 (CS2)\n"); CommonSPIPortFree(); return; } handle = open("/dev/spidev0.2", O_RDWR); if (handle < 0) { std::printf("Failed to open SPI port %d: %s\n", port, std::strerror(errno)); CommonSPIPortFree(); HAL_FreeDIOPort(digitalHandles[1]); return; } HAL_SetSPIHandle(HAL_SPI_kOnboardCS2, handle); break; case HAL_SPI_kOnboardCS3: CommonSPIPortInit(status); if (*status != 0) return; // CS3, Allocate if ((digitalHandles[2] = HAL_InitializeDIOPort(createPortHandleForSPI(28), false, status)) == HAL_kInvalidHandle) { std::printf("Failed to allocate DIO 28 (CS3)\n"); CommonSPIPortFree(); return; } handle = open("/dev/spidev0.3", O_RDWR); if (handle < 0) { std::printf("Failed to open SPI port %d: %s\n", port, std::strerror(errno)); CommonSPIPortFree(); HAL_FreeDIOPort(digitalHandles[2]); return; } HAL_SetSPIHandle(HAL_SPI_kOnboardCS3, handle); break; case HAL_SPI_kMXP: initializeDigital(status); if (*status != 0) return; if ((digitalHandles[5] = HAL_InitializeDIOPort(createPortHandleForSPI(14), false, status)) == HAL_kInvalidHandle) { wpi::outs() << "Failed to allocate DIO 14\n"; return; } if ((digitalHandles[6] = HAL_InitializeDIOPort(createPortHandleForSPI(15), false, status)) == HAL_kInvalidHandle) { wpi::outs() << "Failed to allocate DIO 15\n"; HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated return; } if ((digitalHandles[7] = HAL_InitializeDIOPort(createPortHandleForSPI(16), false, status)) == HAL_kInvalidHandle) { wpi::outs() << "Failed to allocate DIO 16\n"; HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated return; } if ((digitalHandles[8] = HAL_InitializeDIOPort(createPortHandleForSPI(17), false, status)) == HAL_kInvalidHandle) { wpi::outs() << "Failed to allocate DIO 17\n"; HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated HAL_FreeDIOPort(digitalHandles[7]); // free the third port allocated return; } digitalSystem->writeEnableMXPSpecialFunction( digitalSystem->readEnableMXPSpecialFunction(status) | 0x00F0, status); handle = open("/dev/spidev1.0", O_RDWR); if (handle < 0) { std::printf("Failed to open SPI port %d: %s\n", port, std::strerror(errno)); HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated HAL_FreeDIOPort(digitalHandles[7]); // free the third port allocated HAL_FreeDIOPort(digitalHandles[8]); // free the fourth port allocated return; } HAL_SetSPIHandle(HAL_SPI_kMXP, handle); break; default: *status = PARAMETER_OUT_OF_RANGE; break; } } int32_t HAL_TransactionSPI(HAL_SPIPort port, const uint8_t* dataToSend, uint8_t* dataReceived, int32_t size) { if (port < 0 || port >= kSpiMaxHandles) { return -1; } if (SPIInUseByAuto(port)) return -1; struct spi_ioc_transfer xfer; std::memset(&xfer, 0, sizeof(xfer)); xfer.tx_buf = (__u64)dataToSend; xfer.rx_buf = (__u64)dataReceived; xfer.len = size; std::lock_guard lock(spiApiMutexes[port]); return ioctl(HAL_GetSPIHandle(port), SPI_IOC_MESSAGE(1), &xfer); } int32_t HAL_WriteSPI(HAL_SPIPort port, const uint8_t* dataToSend, int32_t sendSize) { if (port < 0 || port >= kSpiMaxHandles) { return -1; } if (SPIInUseByAuto(port)) return -1; struct spi_ioc_transfer xfer; std::memset(&xfer, 0, sizeof(xfer)); xfer.tx_buf = (__u64)dataToSend; xfer.len = sendSize; std::lock_guard lock(spiApiMutexes[port]); return ioctl(HAL_GetSPIHandle(port), SPI_IOC_MESSAGE(1), &xfer); } int32_t HAL_ReadSPI(HAL_SPIPort port, uint8_t* buffer, int32_t count) { if (port < 0 || port >= kSpiMaxHandles) { return -1; } if (SPIInUseByAuto(port)) return -1; struct spi_ioc_transfer xfer; std::memset(&xfer, 0, sizeof(xfer)); xfer.rx_buf = (__u64)buffer; xfer.len = count; std::lock_guard lock(spiApiMutexes[port]); return ioctl(HAL_GetSPIHandle(port), SPI_IOC_MESSAGE(1), &xfer); } void HAL_CloseSPI(HAL_SPIPort port) { if (port < 0 || port >= kSpiMaxHandles) { return; } int32_t status = 0; HAL_FreeSPIAuto(port, &status); { std::lock_guard lock(spiApiMutexes[port]); close(HAL_GetSPIHandle(port)); } HAL_SetSPIHandle(port, 0); if (port < 4) { CommonSPIPortFree(); } switch (port) { // Case 0 does not need to do anything case 1: HAL_FreeDIOPort(digitalHandles[0]); break; case 2: HAL_FreeDIOPort(digitalHandles[1]); break; case 3: HAL_FreeDIOPort(digitalHandles[2]); break; case 4: HAL_FreeDIOPort(digitalHandles[5]); HAL_FreeDIOPort(digitalHandles[6]); HAL_FreeDIOPort(digitalHandles[7]); HAL_FreeDIOPort(digitalHandles[8]); break; default: break; } } void HAL_SetSPISpeed(HAL_SPIPort port, int32_t speed) { if (port < 0 || port >= kSpiMaxHandles) { return; } std::lock_guard lock(spiApiMutexes[port]); ioctl(HAL_GetSPIHandle(port), SPI_IOC_WR_MAX_SPEED_HZ, &speed); } void HAL_SetSPIOpts(HAL_SPIPort port, HAL_Bool msbFirst, HAL_Bool sampleOnTrailing, HAL_Bool clkIdleHigh) { if (port < 0 || port >= kSpiMaxHandles) { return; } uint8_t mode = 0; mode |= (!msbFirst ? 8 : 0); mode |= (clkIdleHigh ? 2 : 0); mode |= (sampleOnTrailing ? 1 : 0); std::lock_guard lock(spiApiMutexes[port]); ioctl(HAL_GetSPIHandle(port), SPI_IOC_WR_MODE, &mode); } void HAL_SetSPIChipSelectActiveHigh(HAL_SPIPort port, int32_t* status) { if (port < 0 || port >= kSpiMaxHandles) { *status = PARAMETER_OUT_OF_RANGE; return; } std::lock_guard lock(spiApiMutexes[port]); if (port < 4) { spiSystem->writeChipSelectActiveHigh_Hdr( spiSystem->readChipSelectActiveHigh_Hdr(status) | (1 << port), status); } else { spiSystem->writeChipSelectActiveHigh_MXP(1, status); } } void HAL_SetSPIChipSelectActiveLow(HAL_SPIPort port, int32_t* status) { if (port < 0 || port >= kSpiMaxHandles) { *status = PARAMETER_OUT_OF_RANGE; return; } std::lock_guard lock(spiApiMutexes[port]); if (port < 4) { spiSystem->writeChipSelectActiveHigh_Hdr( spiSystem->readChipSelectActiveHigh_Hdr(status) & ~(1 << port), status); } else { spiSystem->writeChipSelectActiveHigh_MXP(0, status); } } int32_t HAL_GetSPIHandle(HAL_SPIPort port) { if (port < 0 || port >= kSpiMaxHandles) { return 0; } std::lock_guard lock(spiHandleMutexes[port]); switch (port) { case 0: return m_spiCS0Handle; case 1: return m_spiCS1Handle; case 2: return m_spiCS2Handle; case 3: return m_spiCS3Handle; case 4: return m_spiMXPHandle; default: return 0; } } void HAL_SetSPIHandle(HAL_SPIPort port, int32_t handle) { if (port < 0 || port >= kSpiMaxHandles) { return; } std::lock_guard lock(spiHandleMutexes[port]); switch (port) { case 0: m_spiCS0Handle = handle; break; case 1: m_spiCS1Handle = handle; break; case 2: m_spiCS2Handle = handle; break; case 3: m_spiCS3Handle = handle; break; case 4: m_spiMXPHandle = handle; break; default: break; } } void HAL_InitSPIAuto(HAL_SPIPort port, int32_t bufferSize, int32_t* status) { if (port < 0 || port >= kSpiMaxHandles) { *status = PARAMETER_OUT_OF_RANGE; return; } std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (spiAutoPort != kSpiMaxHandles) { *status = RESOURCE_IS_ALLOCATED; return; } // remember the initialized port for other entry points spiAutoPort = port; // configure the correct chip select if (port < 4) { spiSystem->writeAutoSPI1Select(false, status); spiSystem->writeAutoChipSelect(port, status); } else { spiSystem->writeAutoSPI1Select(true, status); spiSystem->writeAutoChipSelect(0, status); } // configure DMA tDMAChannelDescriptor desc; spiSystem->getSystemInterface()->getDmaDescriptor(g_SpiAutoData_index, &desc); spiAutoDMA = std::make_unique(desc.channel, bufferSize, status); } void HAL_FreeSPIAuto(HAL_SPIPort port, int32_t* status) { if (port < 0 || port >= kSpiMaxHandles) { *status = PARAMETER_OUT_OF_RANGE; return; } std::lock_guard lock(spiAutoMutex); if (spiAutoPort != port) return; spiAutoPort = kSpiMaxHandles; // disable by setting to internal clock and setting rate=0 spiSystem->writeAutoRate(0, status); spiSystem->writeAutoTriggerConfig_ExternalClock(false, status); // stop the DMA spiAutoDMA->stop(status); spiAutoDMA.reset(nullptr); spiAutoRunning = false; } void HAL_StartSPIAutoRate(HAL_SPIPort port, double period, int32_t* status) { std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return; } spiAutoRunning = true; // start the DMA spiAutoDMA->start(status); // auto rate is in microseconds spiSystem->writeAutoRate(period * 1000000, status); // disable the external clock spiSystem->writeAutoTriggerConfig_ExternalClock(false, status); } void HAL_StartSPIAutoTrigger(HAL_SPIPort port, HAL_Handle digitalSourceHandle, HAL_AnalogTriggerType analogTriggerType, HAL_Bool triggerRising, HAL_Bool triggerFalling, int32_t* status) { std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return; } spiAutoRunning = true; // start the DMA spiAutoDMA->start(status); // get channel routing bool routingAnalogTrigger = false; uint8_t routingChannel = 0; uint8_t routingModule = 0; if (!remapDigitalSource(digitalSourceHandle, analogTriggerType, routingChannel, routingModule, routingAnalogTrigger)) { *status = HAL_HANDLE_ERROR; return; } // configure external trigger and enable it tSPI::tAutoTriggerConfig config; config.ExternalClock = 1; config.FallingEdge = triggerFalling ? 1 : 0; config.RisingEdge = triggerRising ? 1 : 0; config.ExternalClockSource_AnalogTrigger = routingAnalogTrigger ? 1 : 0; config.ExternalClockSource_Module = routingModule; config.ExternalClockSource_Channel = routingChannel; spiSystem->writeAutoTriggerConfig(config, status); } void HAL_StopSPIAuto(HAL_SPIPort port, int32_t* status) { std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return; } // disable by setting to internal clock and setting rate=0 spiSystem->writeAutoRate(0, status); spiSystem->writeAutoTriggerConfig_ExternalClock(false, status); // stop the DMA spiAutoDMA->stop(status); spiAutoRunning = false; } void HAL_SetSPIAutoTransmitData(HAL_SPIPort port, const uint8_t* dataToSend, int32_t dataSize, int32_t zeroSize, int32_t* status) { if (dataSize < 0 || dataSize > 16) { *status = PARAMETER_OUT_OF_RANGE; return; } if (zeroSize < 0 || zeroSize > 127) { *status = PARAMETER_OUT_OF_RANGE; return; } std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return; } // set tx data registers for (int32_t i = 0; i < dataSize; ++i) spiSystem->writeAutoTx(i >> 2, i & 3, dataToSend[i], status); // set byte counts tSPI::tAutoByteCount config; config.ZeroByteCount = static_cast(zeroSize) & 0x7f; config.TxByteCount = static_cast(dataSize) & 0xf; spiSystem->writeAutoByteCount(config, status); } void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) { std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return; } spiSystem->strobeAutoForceOne(status); } int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer, int32_t numToRead, double timeout, int32_t* status) { std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return 0; } size_t numRemaining = 0; // timeout is in ms spiAutoDMA->read(buffer, numToRead, timeout * 1000, &numRemaining, status); return numRemaining; } int32_t HAL_GetSPIAutoDroppedCount(HAL_SPIPort port, int32_t* status) { std::lock_guard lock(spiAutoMutex); // FPGA only has one auto SPI engine if (port != spiAutoPort) { *status = INCOMPATIBLE_STATE; return 0; } return spiSystem->readTransferSkippedFullCount(status); } } // extern "C"