/* I2C driver * * Author: Niklas Eiling * SPDX-FileCopyrightText: 2023 Niklas Eiling * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include 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 &data) { int ret; if (!initDone) { throw RuntimeError("I2C not initialized"); } hwLock.lock(); ret = XIic_SetAddress(&xIic, XII_ADDR_TO_SEND_TYPE, static_cast(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 &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(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 &data, size_t max_read) { std::vector 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 data = {CHANNEL_MAP[channel]}; i2c->write(address, data); } uint8_t I2c::Switch::getChannel() { std::vector 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(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;