blob: 28cedf6b538854c57f1cc54127fe27d3f68f4de0 [file] [log] [blame]
#include "i2c.hpp"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <format>
extern "C"
{
#include <i2c/smbus.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
}
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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint16_t>(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<uint8_t>(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<uint16_t>(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<I2CInterface> I2CDevice::create(
uint8_t busId, uint8_t devAddr, InitialState initialState, int maxRetries)
{
std::unique_ptr<I2CDevice> dev(
new I2CDevice(busId, devAddr, initialState, maxRetries));
return dev;
}
std::unique_ptr<I2CInterface>
create(uint8_t busId, uint8_t devAddr,
I2CInterface::InitialState initialState, int maxRetries)
{
return I2CDevice::create(busId, devAddr, initialState, maxRetries);
}
} // namespace i2c