mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-16 00:00:02 +01:00
342 lines
9.9 KiB
C++
342 lines
9.9 KiB
C++
/* I2C driver
|
|
*
|
|
* Author: Niklas Eiling <niklas.eiling@eonerc.rwth-aachen.de>
|
|
* SPDX-FileCopyrightText: 2023 Niklas Eiling <niklas.eiling@eonerc.rwth-aachen.de>
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include <xilinx/xiic.h>
|
|
|
|
#include <villas/fpga/ips/i2c.hpp>
|
|
#include <villas/fpga/ips/intc.hpp>
|
|
#include <villas/log.hpp>
|
|
|
|
using namespace villas::fpga::ip;
|
|
|
|
I2c::I2c()
|
|
: Node(), transmitIntrs(0), receiveIntrs(0), statusIntrs(0), xIic(),
|
|
xConfig(), hwLock(), configDone(false), initDone(false), polling(false),
|
|
switchInstance(nullptr) {}
|
|
|
|
I2c::~I2c() {}
|
|
|
|
static void SendHandler(I2c *i2c, __attribute__((unused)) int bytesSend) {
|
|
i2c->transmitIntrs++;
|
|
}
|
|
static void ReceiveHandler(I2c *i2c, __attribute__((unused)) int bytesSend) {
|
|
i2c->receiveIntrs++;
|
|
}
|
|
static void StatusHandler(I2c *i2c, __attribute__((unused)) int event) {
|
|
i2c->statusIntrs++;
|
|
}
|
|
|
|
bool I2c::init() {
|
|
int ret;
|
|
if (!configDone) {
|
|
throw RuntimeError("I2C configuration not done");
|
|
}
|
|
if (initDone) {
|
|
logger->warn("I2C already initialized");
|
|
return true;
|
|
}
|
|
xConfig.BaseAddress = getBaseAddr(registerMemory);
|
|
logger->debug("I2C base address: {:#x}", xConfig.BaseAddress);
|
|
hwLock.lock();
|
|
ret = XIic_CfgInitialize(&xIic, &xConfig, xConfig.BaseAddress);
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to initialize I2C");
|
|
}
|
|
|
|
XIic_SetSendHandler(&xIic, this, (XIic_Handler)SendHandler);
|
|
XIic_SetRecvHandler(&xIic, this, (XIic_Handler)ReceiveHandler);
|
|
XIic_SetStatusHandler(&xIic, this, (XIic_StatusHandler)StatusHandler);
|
|
|
|
irqs[i2cInterrupt].irqController->enableInterrupt(irqs[i2cInterrupt],
|
|
0); //polling);
|
|
hwLock.unlock();
|
|
initDone = true;
|
|
return true;
|
|
}
|
|
|
|
bool I2c::check() {
|
|
if (!initDone) {
|
|
throw RuntimeError("I2C not initialized");
|
|
}
|
|
// Note: While testing the I2C switch here would be great, there might not be a switch connected
|
|
// Then a call to getSwitch().selfTest() will fail even though the I2C might be working.
|
|
// The only reliable thing to do is to assume there is nothing connected to the I2C bus and
|
|
// always return true.
|
|
// In the future we might check the FMC EEPROM to determine whether the FMC is connected.
|
|
return 1;
|
|
}
|
|
|
|
bool I2c::stop() { return reset(); }
|
|
|
|
bool I2c::reset() {
|
|
logger->debug("I2C reset");
|
|
// we cannot lock here because this may be called in a destructor
|
|
XIic_Reset(&xIic);
|
|
irqs[i2cInterrupt].irqController->disableInterrupt(irqs[i2cInterrupt]);
|
|
initDone = false;
|
|
return true;
|
|
}
|
|
|
|
void I2c::driverWriteBlocking(u8 *dataPtr, size_t size) {
|
|
int ret;
|
|
int intrRetries;
|
|
int sendRetries = 10;
|
|
int bbRetries;
|
|
transmitIntrs = 0;
|
|
// We retry the entire transmission a few times before giving up.
|
|
while (transmitIntrs == 0 && sendRetries > 0) {
|
|
xIic.Stats.TxErrors = 0;
|
|
bbRetries = 1;
|
|
intrRetries = 10;
|
|
// We retry once when the bus is busy.
|
|
do {
|
|
ret = XIic_MasterSend(&xIic, dataPtr, size);
|
|
if (ret == XST_IIC_BUS_BUSY) {
|
|
waitForBusNotBusy();
|
|
} else if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to send I2C data. code: {}", ret);
|
|
}
|
|
} while (ret == XST_IIC_BUS_BUSY && --bbRetries >= 0);
|
|
// We need to wait for several interrupts as sending the stop condition involves generating
|
|
// multiple interrupts before the transmission is complete.
|
|
while (transmitIntrs == 0 && intrRetries > 0) {
|
|
ssize_t intrs = irqs[i2cInterrupt].irqController->waitForInterrupt(
|
|
irqs[i2cInterrupt].num);
|
|
if (intrs == -1) {
|
|
break;
|
|
}
|
|
// logger->trace("I2C write interrupt eventfd read: {}, ISR: {}", numIntr,
|
|
// irqStatus);
|
|
XIic_InterruptHandler(&xIic);
|
|
--intrRetries;
|
|
}
|
|
--sendRetries;
|
|
}
|
|
if (sendRetries == 0) {
|
|
throw RuntimeError(
|
|
"Failed to send I2C data: No transmit interrupt after 10 tries.");
|
|
}
|
|
if (xIic.Stats.TxErrors > 0) {
|
|
throw RuntimeError("Failed to send I2C data: {} TX errors",
|
|
xIic.Stats.TxErrors);
|
|
}
|
|
}
|
|
|
|
void I2c::driverReadBlocking(u8 *dataPtr, size_t max_read) {
|
|
int ret;
|
|
int intrRetries;
|
|
int readRetries = 10;
|
|
int bbRetries;
|
|
receiveIntrs = 0;
|
|
while (receiveIntrs == 0 && readRetries > 0) {
|
|
intrRetries = 10;
|
|
bbRetries = 1;
|
|
do {
|
|
ret = XIic_MasterRecv(&xIic, dataPtr, max_read);
|
|
if (ret == XST_IIC_BUS_BUSY) {
|
|
waitForBusNotBusy();
|
|
} else if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to receive I2C data: code {}", ret);
|
|
}
|
|
} while (ret == XST_IIC_BUS_BUSY && --bbRetries >= 0);
|
|
while (receiveIntrs == 0 && intrRetries > 0) {
|
|
ssize_t intrs = irqs[i2cInterrupt].irqController->waitForInterrupt(
|
|
irqs[i2cInterrupt].num);
|
|
if (intrs == -1) {
|
|
break;
|
|
}
|
|
XIic_InterruptHandler(&xIic);
|
|
--intrRetries;
|
|
}
|
|
--readRetries;
|
|
}
|
|
if (readRetries == 0) {
|
|
throw RuntimeError(
|
|
"Failed to receive I2C data: No receive interrupt after 10 tries.");
|
|
}
|
|
}
|
|
|
|
void I2c::waitForBusNotBusy() {
|
|
int retries = 10;
|
|
uint32_t irqStatus;
|
|
do {
|
|
irqs[i2cInterrupt].irqController->waitForInterrupt(irqs[i2cInterrupt].num);
|
|
irqStatus =
|
|
XIic_ReadIisr(xIic.BaseAddress) & XIic_ReadIier(xIic.BaseAddress);
|
|
} while (!(irqStatus & XIIC_INTR_BNB_MASK) && --retries > 0);
|
|
// logger->trace("I2C bus not busy after {} interrupts", 10 - retries);
|
|
//Deactivate BusNotBusy interrupt
|
|
XIic_WriteIier(xIic.BaseAddress,
|
|
XIic_ReadIier(xIic.BaseAddress) & ~(XIIC_INTR_BNB_MASK));
|
|
uint32_t clear = XIIC_INTR_BNB_MASK;
|
|
XIic_WriteIisr(xIic.BaseAddress, clear);
|
|
if (retries == 0) {
|
|
throw RuntimeError("I2C bus stayed busy after 10 interrupts");
|
|
}
|
|
}
|
|
|
|
bool I2c::write(u8 address, std::vector<u8> &data) {
|
|
int ret;
|
|
|
|
if (!initDone) {
|
|
throw RuntimeError("I2C not initialized");
|
|
}
|
|
|
|
hwLock.lock();
|
|
ret =
|
|
XIic_SetAddress(&xIic, XII_ADDR_TO_SEND_TYPE, static_cast<int>(address));
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to set I2C address");
|
|
}
|
|
|
|
ret = XIic_Start(&xIic);
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to start I2C");
|
|
}
|
|
|
|
driverWriteBlocking(data.data(), data.size());
|
|
|
|
ret = XIic_Stop(&xIic);
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to stop I2C");
|
|
}
|
|
hwLock.unlock();
|
|
return true;
|
|
}
|
|
|
|
bool I2c::read(u8 address, std::vector<u8> &data, size_t max_read) {
|
|
int ret;
|
|
|
|
if (!initDone) {
|
|
throw RuntimeError("I2C not initialized");
|
|
}
|
|
|
|
data.resize(data.size() + max_read);
|
|
u8 *dataPtr = data.data() + data.size() - max_read;
|
|
|
|
hwLock.lock();
|
|
ret =
|
|
XIic_SetAddress(&xIic, XII_ADDR_TO_SEND_TYPE, static_cast<int>(address));
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to set I2C address");
|
|
}
|
|
|
|
ret = XIic_Start(&xIic);
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to start I2C");
|
|
}
|
|
|
|
driverReadBlocking(dataPtr, max_read);
|
|
|
|
ret = XIic_Stop(&xIic);
|
|
if (ret != XST_SUCCESS) {
|
|
throw RuntimeError("Failed to stop I2C");
|
|
}
|
|
hwLock.unlock();
|
|
|
|
return XST_SUCCESS;
|
|
}
|
|
|
|
bool I2c::readRegister(u8 address, u8 reg, std::vector<u8> &data,
|
|
size_t max_read) {
|
|
|
|
std::vector<u8> regData = {reg};
|
|
bool ret;
|
|
ret = write(address, regData);
|
|
ret &= read(address, data, max_read);
|
|
return ret;
|
|
}
|
|
|
|
static const uint8_t CHANNEL_MAP[] = I2C_SWITCH_CHANNEL_MAP;
|
|
void I2c::Switch::setChannel(uint8_t channel) {
|
|
if (channel >= sizeof(CHANNEL_MAP) / sizeof(CHANNEL_MAP[0])) {
|
|
throw RuntimeError("Invalid channel number {}", channel);
|
|
}
|
|
this->channel = channel;
|
|
readOnce = true;
|
|
std::vector<u8> data = {CHANNEL_MAP[channel]};
|
|
i2c->write(address, data);
|
|
}
|
|
|
|
uint8_t I2c::Switch::getChannel() {
|
|
std::vector<u8> data;
|
|
int retries = 10;
|
|
do {
|
|
i2c->read(address, data, 1);
|
|
} while (readOnce && data[0] != CHANNEL_MAP[channel] && --retries >= 0);
|
|
if (retries == 0) {
|
|
throw RuntimeError(
|
|
"Invalid channel readback after 10 retries: {:x} != {:x}", data[0],
|
|
CHANNEL_MAP[channel]);
|
|
}
|
|
if (!readOnce) {
|
|
for (size_t i = 0; i < sizeof(CHANNEL_MAP) / sizeof(CHANNEL_MAP[0]); ++i) {
|
|
if (data[0] == CHANNEL_MAP[i]) {
|
|
channel = i;
|
|
break;
|
|
}
|
|
}
|
|
readOnce = true;
|
|
}
|
|
return channel;
|
|
}
|
|
|
|
bool I2c::Switch::selfTest() {
|
|
uint8_t readback;
|
|
logger->debug("I2c::Switch self test. Testing {} channels of device {:#x}",
|
|
sizeof(CHANNEL_MAP) / sizeof(CHANNEL_MAP[0]), address);
|
|
for (size_t i = 0; i < sizeof(CHANNEL_MAP) / sizeof(CHANNEL_MAP[0]); ++i) {
|
|
logger->debug("Setting switch to channel {}, data byte {:#x}", i,
|
|
CHANNEL_MAP[i]);
|
|
setAndLockChannel(i);
|
|
try {
|
|
readback = getChannel();
|
|
logger->debug("Readback channel: {}", readback);
|
|
} catch (const RuntimeError &e) {
|
|
logger->debug("Encoutered error on readback: {}", e.what());
|
|
return false;
|
|
}
|
|
unlockChannel();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void I2cFactory::parse(Core &ip, json_t *cfg) {
|
|
NodeFactory::parse(ip, cfg);
|
|
|
|
auto &i2c = dynamic_cast<I2c &>(ip);
|
|
|
|
int i2c_frequency = 0;
|
|
char *component_name = nullptr;
|
|
|
|
json_error_t err;
|
|
int ret = json_unpack_ex(cfg, &err, 0, "{ s: { s?: i, s?: i, s?: i, s?: s} }",
|
|
"parameters", "c_iic_freq", &i2c_frequency,
|
|
"c_ten_bit_adr", &i2c.xConfig.Has10BitAddr,
|
|
"c_gpo_width", &i2c.xConfig.GpOutWidth,
|
|
"component_name", &component_name);
|
|
if (ret != 0) {
|
|
throw ConfigError(cfg, err, "", "Failed to parse DMA configuration for {}",
|
|
ip.getInstanceName());
|
|
}
|
|
if (component_name != nullptr) {
|
|
char last_letter = component_name[strlen(component_name) - 1];
|
|
if (last_letter >= '0' && last_letter <= '9') {
|
|
i2c.xConfig.DeviceId = last_letter - '0';
|
|
} else {
|
|
throw RuntimeError("Invalid device ID in component name {} for {}",
|
|
component_name, ip.getInstanceName());
|
|
}
|
|
}
|
|
|
|
i2c.configDone = true;
|
|
}
|
|
|
|
static I2cFactory f;
|