blob: 80cbf09bbef95ed3fa7c5a1d4d58f5223e45265d [file] [log] [blame]
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2019 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 <fcntl.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <array>
#include <atomic>
#include <cstring>
#include <wpi/mutex.h>
#include <wpi/raw_ostream.h>
#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<wpi::mutex, kSpiMaxHandles> spiHandleMutexes;
static std::array<wpi::mutex, kSpiMaxHandles> spiApiMutexes;
static std::array<wpi::mutex, kSpiMaxHandles> spiAccumulatorMutexes;
// MXP SPI does not count towards this
static std::atomic<int32_t> 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<tDMAManager> 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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<tDMAManager>(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::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock 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 > 32) {
*status = PARAMETER_OUT_OF_RANGE;
return;
}
if (zeroSize < 0 || zeroSize > 127) {
*status = PARAMETER_OUT_OF_RANGE;
return;
}
std::scoped_lock 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<unsigned>(zeroSize) & 0x7f;
config.TxByteCount = static_cast<unsigned>(dataSize) & 0x1f;
spiSystem->writeAutoByteCount(config, status);
}
void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) {
std::scoped_lock 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::scoped_lock 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::scoped_lock lock(spiAutoMutex);
// FPGA only has one auto SPI engine
if (port != spiAutoPort) {
*status = INCOMPATIBLE_STATE;
return 0;
}
return spiSystem->readTransferSkippedFullCount(status);
}
// These 2 functions are so the new stall functionality
// can be tested. How they're used is not very clear
// but I want them to be testable so we can add an impl.
// We will not be including these in the headers
void* HAL_GetSPIDMAManager() { return spiAutoDMA.get(); }
void* HAL_GetSPISystem() { return spiSystem.get(); }
} // extern "C"