# 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://i.imgur.com/tLr094t.png) 節錄這個[文件](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}]"}
Expand menu