# I2C in Linux
###### tags: `Linux kernel`
## [Access i2c in user space](https://docs.kernel.org/i2c/dev-interface.html)
```c
uint8_t read_reg(int fd, uint8_t address)
{
#ifdef USE_LIB
return i2c_smbus_read_byte_data(fd, address);
#else
uint8_t buf = address;
write(fd, &buf, 1);
read(fd, &buf, 1);
return buf;
#endif
}
void write_reg(int fd, uint8_t address, uint8_t value)
{
#ifdef USE_LIB
i2c_smbus_write_byte_data(fd, address, value);
#else
char buf[2];
buf[0] = address;
buf[1] = value;
write(fd, buf, 2);
#endif
}
```
#### [i2cdev](https://packages.debian.org/sid/libi2c-dev)(在 github 上也能找到類似的 [wrapper](https://github.com/costad2/i2cdev))
```c
__s32 i2c_smbus_read_byte_data(int file, __u8 command)
{
union i2c_smbus_data data;
int err;
err = i2c_smbus_access(file, I2C_SMBUS_READ, command,
I2C_SMBUS_BYTE_DATA, &data);
if (err < 0)
return err;
return 0x0FF & data.byte;
}
__s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value)
{
union i2c_smbus_data data;
data.byte = value;
return i2c_smbus_access(file, I2C_SMBUS_WRITE, command,
I2C_SMBUS_BYTE_DATA, &data);
}
__s32 i2c_smbus_access(int file, char read_write, __u8 command,
int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args = {
.read_write = read_write,
.command = command,
.size = size,
.data = data,
};
__s32 err;
err = ioctl(file, I2C_SMBUS, &args);
if (err == -1)
err = -errno;
return err;
}
```
#### [I2C_Tools](https://i2c.wiki.kernel.org/index.php/I2C_Tools)
一系列的工具可以對 i2c 做一些操作,其中 i2cdetect 可以掃描目前已有的裝置,如果在 device tree 裡有宣告 slave id,會特別標示。
```c
static int scan_i2c_bus(int file, int mode, unsigned long funcs,
int first, int last)
{
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
for (i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for(j = 0; j < 16; j++) {
switch (mode) {
default:
cmd = mode;
break;
case MODE_AUTO:
if ((i+j >= 0x30 && i+j <= 0x37)
|| (i+j >= 0x50 && i+j <= 0x5F))
cmd = MODE_READ;
else
cmd = MODE_QUICK;
break;
}
if (unwanted_address) {
printf(" ");
continue;
}
/* Set slave address */
if (ioctl(file, I2C_SLAVE, i+j) < 0) {
if (errno == EBUSY) {
printf("UU ");
continue;
}
}
/* Probe this address */
switch (cmd) {
default: /* MODE_QUICK */
res = i2c_smbus_write_quick(file,
I2C_SMBUS_WRITE);
break;
case MODE_READ:
res = i2c_smbus_read_byte(file);
break;
}
if (res < 0)
printf("-- ");
else
printf("%02x ", i+j);
}
printf("\n");
}
return 0;
}
```
* 怎麼知道哪個 address 是有在 device tree 中宣告?
* if set slave id return EBUSY
* `write_quick` 是什麼?
* [runs an SMBus "Quick command" transaction](https://docs.kernel.org/i2c/smbus-protocol.html#smbus-quick-command)
* MODE_AUTO 的判斷邏輯是什麼?
* https://stackoverflow.com/questions/75116783/explain-i2cdetect-tool-mode-auto-logic?noredirect=1#comment132558088_75116783
## Trace code in kernel
[Driver Architecture](https://i2c.wiki.kernel.org/index.php/Driver_Architecture)

節錄這個[文件](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/i2c/summary.rst)的重點
* SMBus is mostly a subset of the generalized I2C bus
* A master chip is called adapter(drivers/i2c/busses/)
* Algorithm contains general code that can be used to implement a whole class of I2C adapters(drivers/i2c/algos/)
* A slave chip is called client
https://elixir.bootlin.com/linux/latest/source/drivers/i2c/i2c-dev.c#L311
```c
// /drivers/i2c/i2c-dev.c
static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
u8 read_write, u8 command, u32 size,
union i2c_smbus_data __user *data)
{
res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
read_write, command, size, &temp);
}
// drivers/i2c/i2c-core-smbus.c
s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
unsigned short flags, char read_write,
u8 command, int protocol, union i2c_smbus_data *data)
{
int (*xfer_func)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB;
xfer_func = adapter->algo->smbus_xfer;
if (xfer_func) {
/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (res = 0, try = 0; try <= adapter->retries; try++) {
res = xfer_func(adapter, addr, flags, read_write,
command, protocol, data);
if (res != -EAGAIN)
break;
if (time_after(jiffies,
orig_jiffies + adapter->timeout))
break;
}
if (res != -EOPNOTSUPP || !adapter->algo->master_xfer)
goto trace;
/*
* Fall back to i2c_smbus_xfer_emulated if the adapter doesn't
* implement native support for the SMBus operation.
*/
}
res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
command, protocol, data);
trace:
return res;
}
```
如果 driver 沒有實做 `smbus_xfer` 就會用 `master_xfer` 去模擬 SMBus 的行為。
執行時會對 i2c arbitration loss 做 retry。
```c
/*
* Simulate a SMBus command using the I2C protocol.
* No checking of parameters is done!
*/
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data *data)
{
/*
* So we need to generate a series of msgs. In the case of writing, we
* need to use only one message; when reading, we need two. We
* initialize most things with sane defaults, to keep the code below
* somewhat simpler.
*/
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
int nmsgs = read_write == I2C_SMBUS_READ ? 2 : 1;
u8 partial_pec = 0;
int status;
struct i2c_msg msg[2] = {
{
.addr = addr,
.flags = flags,
.len = 1,
.buf = msgbuf0,
}, {
.addr = addr,
.flags = flags | I2C_M_RD,
.len = 0,
.buf = msgbuf1,
},
};
bool wants_pec = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
&& size != I2C_SMBUS_I2C_BLOCK_DATA);
msgbuf0[0] = command;
switch (size) {
case I2C_SMBUS_QUICK:
msg[0].len = 0;
/* Special case: The read/write field is used as data */
msg[0].flags = flags | (read_write == I2C_SMBUS_READ ?
I2C_M_RD : 0);
nmsgs = 1;
break;
case I2C_SMBUS_BYTE:
if (read_write == I2C_SMBUS_READ) {
/* Special case: only a read! */
msg[0].flags = I2C_M_RD | flags;
nmsgs = 1;
}
break;
case I2C_SMBUS_BYTE_DATA:
if (read_write == I2C_SMBUS_READ)
msg[1].len = 1;
else {
msg[0].len = 2;
msgbuf0[1] = data->byte;
}
break;
}
if (wants_pec) {
/* Compute PEC if first message is a write */
if (!(msg[0].flags & I2C_M_RD)) {
if (nmsgs == 1) /* Write only */
i2c_smbus_add_pec(&msg[0]);
else /* Write followed by read */
partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
}
/* Ask for PEC if last message is a read */
if (msg[nmsgs - 1].flags & I2C_M_RD)
msg[nmsgs - 1].len++;
}
status = __i2c_transfer(adapter, msg, nmsgs);
/* Check PEC if last message is a read */
if (wants_pec && (msg[nmsgs - 1].flags & I2C_M_RD)) {
status = i2c_smbus_check_pec(partial_pec, &msg[nmsgs - 1]);
}
if (read_write == I2C_SMBUS_READ)
switch (size) {
case I2C_SMBUS_BYTE:
data->byte = msgbuf0[0];
break;
case I2C_SMBUS_BYTE_DATA:
data->byte = msgbuf1[0];
break;
}
return status;
}
// drivers/i2c/i2c-core-base.c
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
if (i2c_in_atomic_xfer_mode() && adap->algo->master_xfer_atomic)
ret = adap->algo->master_xfer_atomic(adap, msgs, num);
else
ret = adap->algo->master_xfer(adap, msgs, num);
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
return ret;
}
```
PEC 是 packet error checking,是 SMBus 的東西。
以 RP4 為例,[driver](https://github.com/raspberrypi/linux/blob/rpi-3.6.y/drivers/i2c/busses/i2c-bcm2708.c) 在這。
```c
static int __devinit bcm2708_i2c_probe(struct platform_device *pdev)
{
platform_set_drvdata(pdev, bi);
adap = &bi->adapter;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_DDC;
adap->algo = &bcm2708_i2c_algorithm;
adap->algo_data = bi;
adap->dev.parent = &pdev->dev;
adap->nr = pdev->id;
err = i2c_add_numbered_adapter(adap);
return 0;
}
static struct i2c_algorithm bcm2708_i2c_algorithm = {
.master_xfer = bcm2708_i2c_master_xfer,
.functionality = bcm2708_i2c_functionality,
};
```
Device driver 要實做 `algo` 並註冊包含它的 `adapter`。
[`i2c-dev`](https://elixir.bootlin.com/linux/latest/source/drivers/i2c/i2c-dev.c#L626) 則是包裝成 character device 給 user space 使用。所以 app 也可以用 `read`/`write` 來做基本的 i2c 操作。
## Todo
https://elixir.bootlin.com/linux/latest/source/drivers/i2c/algos/i2c-algo-pca.c
## Virtual i2c device
[i2c-stub](https://github.com/torvalds/linux/blob/master/drivers/i2c/i2c-stub.c)
## Reference
https://hackmd.io/@RinHizakura/BJDTZnUsF
## Code
https://github.com/mingpepe/sample-code/tree/main/linux/i2c
{"metaMigratedAt":"2023-06-17T18:29:51.342Z","metaMigratedFrom":"Content","title":"I2C in Linux","breaks":true,"description":"一系列的工具可以對 i2c 做一些操作,其中 i2cdetect 可以掃描目前已有的裝置,如果在 device tree 裡有宣告 slave id,會特別標示。","contributors":"[{\"id\":\"759af10a-4e79-47a3-8d1a-73cbdca2c9ed\",\"add\":11451,\"del\":2194}]"}