#include "i2c.hpp" #include #include #include #include #include #include #include #include extern "C" { #include #include #include } namespace i2c { // Maximum number of data bytes in a block read/block write/block process call // in SMBus 3.0. The maximum was 32 data bytes in SMBus 2.0 and earlier. constexpr uint8_t I2C_SMBUS3_BLOCK_MAX = 255; unsigned long I2CDevice::getFuncs() { // If functionality has not been cached if (cachedFuncs == NO_FUNCS) { // Get functionality from adapter int ret = 0, retries = 0; do { ret = ioctl(fd, I2C_FUNCS, &cachedFuncs); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { cachedFuncs = NO_FUNCS; throw I2CException("Failed to get funcs", busStr, devAddr, errno); } } return cachedFuncs; } void I2CDevice::checkReadFuncs(int type) { unsigned long funcs = getFuncs(); switch (type) { case I2C_SMBUS_BYTE: if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE)) { throw I2CException("Missing SMBUS_READ_BYTE", busStr, devAddr); } break; case I2C_SMBUS_BYTE_DATA: if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA)) { throw I2CException("Missing SMBUS_READ_BYTE_DATA", busStr, devAddr); } break; case I2C_SMBUS_WORD_DATA: if (!(funcs & I2C_FUNC_SMBUS_READ_WORD_DATA)) { throw I2CException("Missing SMBUS_READ_WORD_DATA", busStr, devAddr); } break; case I2C_SMBUS_BLOCK_DATA: if (!(funcs & I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { throw I2CException("Missing SMBUS_READ_BLOCK_DATA", busStr, devAddr); } break; case I2C_SMBUS_I2C_BLOCK_DATA: if (!(funcs & I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { throw I2CException("Missing I2C_FUNC_SMBUS_READ_I2C_BLOCK", busStr, devAddr); } break; default: fprintf(stderr, "Unexpected read size type: %d\n", type); assert(false); break; } } void I2CDevice::checkWriteFuncs(int type) { unsigned long funcs = getFuncs(); switch (type) { case I2C_SMBUS_BYTE: if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE)) { throw I2CException("Missing SMBUS_WRITE_BYTE", busStr, devAddr); } break; case I2C_SMBUS_BYTE_DATA: if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { throw I2CException("Missing SMBUS_WRITE_BYTE_DATA", busStr, devAddr); } break; case I2C_SMBUS_WORD_DATA: if (!(funcs & I2C_FUNC_SMBUS_WRITE_WORD_DATA)) { throw I2CException("Missing SMBUS_WRITE_WORD_DATA", busStr, devAddr); } break; case I2C_SMBUS_BLOCK_DATA: if (!(funcs & I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) { throw I2CException("Missing SMBUS_WRITE_BLOCK_DATA", busStr, devAddr); } break; case I2C_SMBUS_I2C_BLOCK_DATA: if (!(funcs & I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { throw I2CException("Missing I2C_FUNC_SMBUS_WRITE_I2C_BLOCK", busStr, devAddr); } break; case I2C_SMBUS_PROC_CALL: if (!(funcs & I2C_FUNC_SMBUS_PROC_CALL)) { throw I2CException("Missing I2C_FUNC_SMBUS_PROC_CALL", busStr, devAddr); } break; case I2C_SMBUS_BLOCK_PROC_CALL: if (!(funcs & I2C_FUNC_SMBUS_BLOCK_PROC_CALL)) { throw I2CException("Missing I2C_FUNC_SMBUS_BLOCK_PROC_CALL", busStr, devAddr); } break; default: fprintf(stderr, "Unexpected write size type: %d\n", type); assert(false); } } void I2CDevice::processCallSMBus(uint8_t addr, uint8_t writeSize, const uint8_t* writeData, uint8_t& readSize, uint8_t* readData) { int ret = 0, retries = 0; uint8_t buffer[I2C_SMBUS_BLOCK_MAX]; do { // Copy write data to buffer. Buffer is also used by SMBus function to // store the data read from the device. std::memcpy(buffer, writeData, writeSize); ret = i2c_smbus_block_process_call(fd, addr, writeSize, buffer); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to execute block process call", busStr, devAddr, errno); } readSize = static_cast(ret); std::memcpy(readData, buffer, readSize); } void I2CDevice::processCallI2C(uint8_t addr, uint8_t writeSize, const uint8_t* writeData, uint8_t& readSize, uint8_t* readData) { // Buffer for block write. Linux supports SMBus 3.0 max size for block // write. Buffer will contain register address, byte count, and data bytes. constexpr uint16_t writeBufferSize = I2C_SMBUS3_BLOCK_MAX + 2; uint8_t writeBuffer[writeBufferSize]; // Buffer for block read. Linux supports smaller SMBus 2.0 max size for // block read. After ioctl() buffer will contain byte count and data bytes. constexpr uint16_t readBufferSize = I2C_SMBUS_BLOCK_MAX + 1; uint8_t readBuffer[readBufferSize]; // i2c_msg and i2c_rdwr_ioctl_data structs required for ioctl() constexpr unsigned int numMessages = 2; struct i2c_msg messages[numMessages]; struct i2c_rdwr_ioctl_data readWriteData; readWriteData.msgs = messages; readWriteData.nmsgs = numMessages; int ret = 0, retries = 0; do { // Initialize write buffer with reg addr, byte count, and data bytes writeBuffer[0] = addr; writeBuffer[1] = writeSize; std::memcpy(&(writeBuffer[2]), writeData, writeSize); // Initialize first i2c_msg to perform block write messages[0].addr = devAddr; messages[0].flags = 0; messages[0].len = writeSize + 2; // 2 == reg addr + byte count messages[0].buf = writeBuffer; // Initialize read buffer. Set first byte to number of "extra bytes" // that will be read in addition to data bytes. Set to 1 since only // extra byte is the byte count. readBuffer[0] = 1; // Initialize second i2c_msg to perform block read. Linux requires the // len field to be set to the buffer size. messages[1].addr = devAddr; messages[1].flags = I2C_M_RD | I2C_M_RECV_LEN; messages[1].len = readBufferSize; messages[1].buf = readBuffer; // Call ioctl() to send the I2C messages ret = ioctl(fd, I2C_RDWR, &readWriteData); } while ((ret != numMessages) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to execute I2C block process call", busStr, devAddr, errno); } else if (ret != numMessages) { throw I2CException( std::format( "Failed to execute I2C block process call: {} messages sent", ret), busStr, devAddr); } // Read size is in first byte; copy remaining data bytes to readData param readSize = readBuffer[0]; std::memcpy(readData, &(readBuffer[1]), readSize); } void I2CDevice::open() { if (isOpen()) { throw I2CException("Device already open", busStr, devAddr); } int retries = 0; do { fd = ::open(busStr.c_str(), O_RDWR); } while ((fd == -1) && (++retries <= maxRetries)); if (fd == -1) { throw I2CException("Failed to open", busStr, devAddr, errno); } retries = 0; int ret = 0; do { ret = ioctl(fd, I2C_SLAVE, devAddr); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { // Close device since setting slave address failed closeWithoutException(); throw I2CException("Failed to set I2C_SLAVE", busStr, devAddr, errno); } } void I2CDevice::close() { checkIsOpen(); int ret = 0, retries = 0; do { ret = ::close(fd); } while ((ret == -1) && (++retries <= maxRetries)); if (ret == -1) { throw I2CException("Failed to close", busStr, devAddr, errno); } fd = INVALID_FD; cachedFuncs = NO_FUNCS; } void I2CDevice::read(uint8_t& data) { checkIsOpen(); checkReadFuncs(I2C_SMBUS_BYTE); int ret = 0, retries = 0; do { ret = i2c_smbus_read_byte(fd); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to read byte", busStr, devAddr, errno); } data = static_cast(ret); } void I2CDevice::read(uint8_t addr, uint8_t& data) { checkIsOpen(); checkReadFuncs(I2C_SMBUS_BYTE_DATA); int ret = 0, retries = 0; do { ret = i2c_smbus_read_byte_data(fd, addr); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to read byte data", busStr, devAddr, errno); } data = static_cast(ret); } void I2CDevice::read(uint8_t addr, uint16_t& data) { checkIsOpen(); checkReadFuncs(I2C_SMBUS_WORD_DATA); int ret = 0, retries = 0; do { ret = i2c_smbus_read_word_data(fd, addr); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to read word data", busStr, devAddr, errno); } data = static_cast(ret); } void I2CDevice::read(uint8_t addr, uint8_t& size, uint8_t* data, Mode mode) { checkIsOpen(); int ret = -1, retries = 0; switch (mode) { case Mode::SMBUS: checkReadFuncs(I2C_SMBUS_BLOCK_DATA); do { ret = i2c_smbus_read_block_data(fd, addr, data); } while ((ret < 0) && (++retries <= maxRetries)); break; case Mode::I2C: checkReadFuncs(I2C_SMBUS_I2C_BLOCK_DATA); do { ret = i2c_smbus_read_i2c_block_data(fd, addr, size, data); } while ((ret < 0) && (++retries <= maxRetries)); if (ret != size) { throw I2CException("Failed to read i2c block data", busStr, devAddr, errno); } break; } if (ret < 0) { throw I2CException("Failed to read block data", busStr, devAddr, errno); } size = static_cast(ret); } void I2CDevice::write(uint8_t data) { checkIsOpen(); checkWriteFuncs(I2C_SMBUS_BYTE); int ret = 0, retries = 0; do { ret = i2c_smbus_write_byte(fd, data); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to write byte", busStr, devAddr, errno); } } void I2CDevice::write(uint8_t addr, uint8_t data) { checkIsOpen(); checkWriteFuncs(I2C_SMBUS_BYTE_DATA); int ret = 0, retries = 0; do { ret = i2c_smbus_write_byte_data(fd, addr, data); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to write byte data", busStr, devAddr, errno); } } void I2CDevice::write(uint8_t addr, uint16_t data) { checkIsOpen(); checkWriteFuncs(I2C_SMBUS_WORD_DATA); int ret = 0, retries = 0; do { ret = i2c_smbus_write_word_data(fd, addr, data); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to write word data", busStr, devAddr, errno); } } void I2CDevice::write(uint8_t addr, uint8_t size, const uint8_t* data, Mode mode) { checkIsOpen(); int ret = -1, retries = 0; switch (mode) { case Mode::SMBUS: checkWriteFuncs(I2C_SMBUS_BLOCK_DATA); do { ret = i2c_smbus_write_block_data(fd, addr, size, data); } while ((ret < 0) && (++retries <= maxRetries)); break; case Mode::I2C: checkWriteFuncs(I2C_SMBUS_I2C_BLOCK_DATA); do { ret = i2c_smbus_write_i2c_block_data(fd, addr, size, data); } while ((ret < 0) && (++retries <= maxRetries)); break; } if (ret < 0) { throw I2CException("Failed to write block data", busStr, devAddr, errno); } } void I2CDevice::processCall(uint8_t addr, uint16_t writeData, uint16_t& readData) { checkIsOpen(); checkWriteFuncs(I2C_SMBUS_PROC_CALL); int ret = 0, retries = 0; do { ret = i2c_smbus_process_call(fd, addr, writeData); } while ((ret < 0) && (++retries <= maxRetries)); if (ret < 0) { throw I2CException("Failed to execute process call", busStr, devAddr, errno); } readData = static_cast(ret); } void I2CDevice::processCall(uint8_t addr, uint8_t writeSize, const uint8_t* writeData, uint8_t& readSize, uint8_t* readData) { checkIsOpen(); unsigned long funcs = getFuncs(); if ((funcs & I2C_FUNC_SMBUS_BLOCK_PROC_CALL) && (writeSize <= I2C_SMBUS_BLOCK_MAX)) { // Use standard SMBus function which supports smaller SMBus 2.0 maximum processCallSMBus(addr, writeSize, writeData, readSize, readData); } else if (funcs & I2C_FUNC_I2C) { // Use lower level I2C ioctl which supports larger SMBus 3.0 maximum processCallI2C(addr, writeSize, writeData, readSize, readData); } else { throw I2CException( std::format( "Block process call unsupported: writeSize={:d}, funcs={}", writeSize, funcs), busStr, devAddr); } } std::unique_ptr I2CDevice::create( uint8_t busId, uint8_t devAddr, InitialState initialState, int maxRetries) { std::unique_ptr dev( new I2CDevice(busId, devAddr, initialState, maxRetries)); return dev; } std::unique_ptr create(uint8_t busId, uint8_t devAddr, I2CInterface::InitialState initialState, int maxRetries) { return I2CDevice::create(busId, devAddr, initialState, maxRetries); } } // namespace i2c