I2C适配器驱动就是前面说的I2C总线驱动,那么I2C总线驱动是怎么将适配器注册与匹配的呢?
以某平台i2c总线驱动框架为例:
static const struct of_device_id nvtim_i2c_ids[] = {
{ .compatible = "nvt,nvt_i2c" },
{},
};
static struct platform_driver nvtim_i2c_driver = {
.probe = nvtim_i2c_probe,
.remove = nvtim_i2c_remove,
.driver = {
.name = "nvt_i2c",
.owner = THIS_MODULE,
.pm = nvtim_i2c_pm_ops,
.of_match_table = of_match_ptr(nvtim_i2c_ids),
},
};
由以上可以看到其I2c驱动为platform_driver形式,由此可以看出,虽然 I2C 总线为别的设备提供了一种总线驱动框架,但是 I2C 适配器却是 platform驱动。就像你的部门老大是你的领导,你是他的下属,但是放到整个公司,你的部门老大却也是老板的下属。其中of_match_table填充的"nvt,nvt_i2c"字符就是与设备树的compatible匹配,之后probe函数就会执行。
static int nvtim_i2c_probe(struct platform_device *pdev)
{
//获取含有i2c控制器寄存器物理基地址的resource
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&pdev->dev, "no mem resource?\n");
return -ENODEV;
}
//获取中断号
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq) {
dev_err(&pdev->dev, "no irq resource?\n");
return -ENODEV;
}
i2c_nvtim_init(dev);
//物理地址映射成虚拟地址保存
dev->base = devm_ioremap_resource(&pdev->dev, mem);
//注册中断
r = devm_request_irq(&pdev->dev, dev->irq, i2c_nvtim_isr, 0,
pdev->name, dev);
if (r) {
dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq);
goto err_unuse_clocks;
}
//填充适配器结构体
adap = &dev->adapter;
//平台I2C数据结构设置为适配器
i2c_set_adapdata(adap, dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "nvtim I2C adapter", sizeof(adap->name));
adap->algo = &i2c_nvtim_algo; //通信算法
adap->dev.parent = &pdev->dev;
adap->timeout = NVTIM_I2C_TIMEOUT;
if (dtsi_i2c_id == -1) {
adap->nr = pdev->id;
} else {
//给适配器编号
adap->nr = dtsi_i2c_id;
}
#ifdef CONFIG_OF
adap->dev.of_node = pdev->dev.of_node;
#endif
//注册i2c控制器,也就是i2c_adapter
r = i2c_add_numbered_adapter(adap);
}
需要知道的是i2c_nvtim_algo 是i2c通信算法,其结构如下,后面会说明:
static struct i2c_algorithm i2c_nvtim_algo = {
.master_xfer = i2c_nvtim_xfer,
.functionality = i2c_nvtim_func,
};
i2c_add_numbered_adapter为适配器真正注册函数,其最终会调用i2c_register_adapter(adap),简要分析如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
of_i2c_register_devices(adap);
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
}
首先是设置适配的名称,后面会在/dev下生成相应的节点名称,接着设置适配器的总线为i2c总线,类型为i2c_adapter_type,接着调用device_register 函数将i2c适配注册到i2c总线上,然后调用of_i2c_register_devices 函数,查找并添加设备树中存在i2c设备,接着判断如果是静态设备(代码中添加,即i2c_board_info),就调用i2c_scan_static_board_info函数将__i2c_board_list链表上对应总线号的设备实例化注册进i2c总线中,最后调用bus_for_each_drv函数遍历i2c总线上所有i2c驱动,使用它们的i2c设备地址组中地址探测该i2c适配器上是否有响应设备,有的话便实例化后注册进i2c总线。
先简要分析device_register->device_add
int device_add(struct device *dev)
{
error = device_create_file(dev, &uevent_attr);//创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件
if (MAJOR(dev->devt)) {
//创建sys目录下设备的设备号属性,即major和minor
error = device_create_file(dev, &devt_attr); error = device_create_sys_dev_entry(dev);
devtmpfs_create_node(dev);
}
error = device_add_attrs(dev);//创建sys目录下设备其他属性文件
error = bus_add_device(dev);//将设备添加到对应总线,重要
if (dev->bus)//呼唤通知链表,通知注册监听该总线的设备,有新设备加入
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
bus_probe_device(dev);//在总线上寻找对应的driver
}
首先将i2c适配注册到I2c总线上,然后呼唤通知链,事件类型为BUS_NOTIFY_ADD_DEVICE,最后调用bus_probe_device设备匹配驱动函数,这个函数是用于i2c设备匹配i2c总线上驱动的,所以虽然i2c适配器也会调用这个函数,但在函数内部调用到i2c总线上的match函数后,因为其类型是i2c_adapter_type而不是i2c_client_type,因此直接结束。后面讲解i2c设备与驱动匹配的时候会详细分析这个函数。
接下来,继续分析of_i2c_register_devices(adap)-> of_i2c_register_device();
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
struct device_node *node)
{
struct i2c_board_info info;
ret = of_i2c_get_board_info(&adap->dev, node, &info);
client = i2c_new_device(adap, &info);
}
看见了吧,这里也是将设备树中i2c设备信息填充到i2c_board_info 中,之后调用i2c_new_device 函数添加i2c设备。i2c_new_device是i2c设备注册的重要函数,将在i2c设备与驱动匹配中说明。
接下来分析i2c_register_adapter函数中的i2c_scan_static_board_info,此函数是扫描静态添加到__i2c_board_list链表的i2c设备,如果扫描到即调用i2c_new_device函数注册。关于i2c静态添加的说明参考:【linux iic子系统】i2c设备的添加方法(四)_i2c_board_info-CSDN博客 。
可见i2c适配器的注册过程有多处添加i2c设备的过程。
- i2c通信方式
I2c通信过程或者说通信算法是通过i2c_algorithm注册的,其一般如下;
static struct i2c_algorithm i2c_nvtim_algo = {
.master_xfer = i2c_nvtim_xfer,
.functionality = i2c_nvtim_func,
};
而i2c_nvtim_xfer函数就是各平台厂家根据自己的i2c适配器实现的通信过程,i2c_nvtim_xfer会被i2c_transfer函数调用。
- i2c适配器的操作接口
与i2c设备通信一般可以通过两种方式,一种是通过i2c设备驱动(驱动中存在fops操作方法或者驱动开放接口供应用层通信),而另一种可以通过i2c适配器通信,操作/dev/i2c-0节点即可,现在重点说下后者的实现过程。
在i2c核心有一个文件叫i2c-dev.c,文件中向系统注册了此模块。
static int __init i2c_dev_init(void)
{
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
}
首先使用register_chrdev_region函数申请了设备号89,但并未实际注册字符驱动设备,bus_register_notifier向系统中注册了通知链,去监测i2c总线上设备注册!当i2c总线上设备注册/删除并呼唤通知链时,则会调用i2cdev_notifier结构体中的.notifier_call指向的函数!
static struct notifier_block i2cdev_notifier = {
.notifier_call = i2cdev_notifier_call,
};
从这里可以看到.notifier_call 指向的函数为i2cdev_notifier_call。
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
return i2cdev_attach_adapter(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE:
return i2cdev_detach_adapter(dev, NULL);
}
return 0;
}
如果总线上是添加设备,则调用i2cdev_attach_adapter函数,如果是删除设备则调用i2cdev_detach_adapter函数。
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
if (dev->type != &i2c_adapter_type)
return 0;
cdev_init(&i2c_dev->cdev, &i2cdev_fops);
i2c_dev->cdev.owner = THIS_MODULE;
res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
if (res)
goto error_cdev;
/* register this i2c device with the driver core */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
}
使用之前占用的设备号向系统中注册字符设备驱动,并提供fops操作方法集。并创建了名为i2c-x的设备节点。
通过上面所述可以知道,当i2c适配器注册到系统中并呼唤通知链后会触发i2cdev_attach_adapter函数执行,进而为i2c适配器注册一个字符驱动,那么这里就有一个疑问了,如果i2c适配器在i2c-dev.c模块注册之前提前注册到了系统怎么办?
模块注册时会调用i2c_for_each_dev!这个函数会去遍历i2c总线上所有设备,执行i2cdev_attach_adapter函数为提前注册到i2c总线上的i2c适配器注册字符驱动!
- I2c用户态使用
在用户态编程之前,先看一下一般i2c通信的读写流程:
I2C写流程
写寄存器的标准流程为:
- Master发起START
- Master发送I2C addr(7bit)和w操作0(1bit),等待ACK
- Slave发送ACK
- Master发送reg addr(8bit),等待ACK
- Slave发送ACK
- Master发送data(8bit),即要写入寄存器中的数据,等待ACK
- Slave发送ACK
- 第6步和第7步可以重复多次,即顺序写多个寄存器
- Master发起STOP
读寄存器的标准流程为:
- Master发送I2C addr(7bit)和w(写)操作0(1bit),等待ACK
- Slave发送ACK
- Master发送reg addr(8bit),等待ACK
- Slave发送ACK
- Master发起START
- Master发送I2C addr(7bit)和r(读)操作1(1bit),等待ACK
- Slave发送ACK
- Slave发送data(8bit),即寄存器里的值
- Master发送ACK
- 第7步和第8步可以重复多次,即顺序读多个寄存器
i2c波形:写数据波形解读:
需要注意的是:中间的8位数据0x00101110一般位寄存器地址。
i2c波形: 读写数据波形解读
用户态编程:
1.用户空间操作i2c,需要包含以下头文件:
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
2.i2c-dev设备节点的read、write操作
int i2c_write_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{
unsigned char *data_wr = NULL;
int ret = -1;
data_wr = malloc(len + 2);
if (!data_wr) {
printf("%s, malloc failed!\n", __func__);
return -1;
}
data_wr[0] = addr / 0xff;
data_wr[1] = addr % 0xff;
memcpy(&data_wr[2], data, len);
ioctl(fd, I2C_SLAVE, SLAVE_ADDR);
ioctl(fd, I2C_TIMEOUT, 1);
ioctl(fd, I2C_RETRIES, 1);
ret = write(fd, data_wr, len+2);
if (ret < 0) {
printf("%s, write failed, ret: 0x%x\n", __func__, ret);
return ret;
}
printf("%s, write ok, num: %d\n", __func__, ret);
if (data_wr != NULL) {
free(data_wr);
data_wr = NULL;
}
return ret;
}
int i2c_read_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{
unsigned char addr_slave[2] = { 0 };
int ret = -1;
ioctl(fd, I2C_SLAVE, SLAVE_ADDR);
ioctl(fd, I2C_TIMEOUT, 1);
ioctl(fd, I2C_RETRIES, 1);
addr_slave[0] = addr / 0xff;
addr_slave[1] = addr % 0xff;
ret = write(fd, addr_slave, 2);
if (ret < 0) {
printf("%s, write failed, ret: 0x%x\n", __func__, ret);
return ret;
}
ret = read(fd, data, len);
if (ret < 0) {
printf("%s, read failed, ret: 0x%x\n", __func__, ret);
return ret;
}
printf("%s, read ok, num: %d\n", __func__, ret);
return ret;
}
3.i2c-dev设备节点的ioctl操作
int i2c_write_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{
struct i2c_rdwr_ioctl_data data_wr;
int ret = -1;
data_wr.nmsgs = 1;
data_wr.msgs = malloc(sizeof(struct i2c_msg) * data_wr.nmsgs);
if (!data_wr.msgs) {
printf("%s, msgs malloc failed!\n", __func__);
return -1;
}
data_wr.msgs[0].addr = SLAVE_ADDR;
data_wr.msgs[0].flags = 0;
data_wr.msgs[0].len = len + 2;
data_wr.msgs[0].buf = malloc(data_wr.msgs[0].len + 2);
if (!data_wr.msgs[0].buf) {
printf("%s, msgs buf malloc failed!\n", __func__);
return -1;
}
data_wr.msgs[0].buf[0] = addr / 0xff;
data_wr.msgs[0].buf[1] = addr % 0xff;
memcpy(&data_wr.msgs[0].buf[2], data, len);
ret = ioctl(fd, I2C_RDWR, (unsigned long)&data_wr);
if (ret < 0) {
printf("%s, ioctl failed, ret: 0x%x\n", __func__, ret);
return ret;
}
if (data_wr.msgs[0].buf != NULL) {
free(data_wr.msgs[0].buf);
data_wr.msgs[0].buf = NULL;
}
if (data_wr.msgs != NULL) {
free(data_wr.msgs);
data_wr.msgs = NULL;
}
return ret;
}
int i2c_read_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{
struct i2c_rdwr_ioctl_data data_rd;
int ret = -1;
int i = 0;
data_rd.nmsgs = 2;
data_rd.msgs = malloc(sizeof(struct i2c_msg) * data_rd.nmsgs);
if (!data_rd.msgs) {
printf("%s, msgs malloc failed!\n", __func__);
return -1;
}
data_rd.msgs[0].addr = SLAVE_ADDR;
data_rd.msgs[0].flags = 0;
data_rd.msgs[0].len = 2;
data_rd.msgs[0].buf = malloc(data_rd.msgs[0].len);
if (!data_rd.msgs[0].buf) {
printf("%s, msgs buf malloc failed!\n", __func__);
return -1;
}
data_rd.msgs[0].buf[0] = addr / 0xff;
data_rd.msgs[0].buf[1] = addr % 0xff;
data_rd.msgs[1].addr = SLAVE_ADDR;
data_rd.msgs[1].flags = I2C_M_RD;
data_rd.msgs[1].len = len;
data_rd.msgs[1].buf = malloc(data_rd.msgs[1].len);
if (!data_rd.msgs[0].buf) {
printf("%s, msgs buf malloc failed!\n", __func__);
return -1;
}
memset(data_rd.msgs[1].buf, 0, data_rd.msgs[1].len);
ret = ioctl(fd, I2C_RDWR, (unsigned long)&data_rd);
if (ret < 0) {
printf("%s, ioctl failed, ret: 0x%x\n", __func__, ret);
return ret;
}
memcpy(data, data_rd.msgs[1].buf, len);
printf("%s, read ok, num: %d\n", __func__, ret);
if (data_rd.msgs[0].buf != NULL) {
free(data_rd.msgs[0].buf);
data_rd.msgs[0].buf = NULL;
}
if (data_rd.msgs[1].buf != NULL) {
free(data_rd.msgs[1].buf);
data_rd.msgs[1].buf = NULL;
}
if (data_rd.msgs != NULL) {
free(data_rd.msgs);
data_rd.msgs = NULL;
}
return ret;
}
main函数:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define SLAVE_ADDR 0x51
int arr_show(unsigned char *data, int len)
{
int i = 0;
for (i = 0; i < len; i++) {
printf("data[%d]: 0x%x\n", i, data[i]);
}
return 0;
}
void usage(void)
{
printf("xxx -r addr len\n");
printf("xxx -w addr data1 data2 ...\n");
}
int main(int argc, char *argv[])
{
int opt;
int fd = -1;
unsigned short addr;
unsigned char buf[256] = { 0 };
int len = 0;
int i = 0;
if (argc < 4) {
usage();
return -1;
}
fd = open("/dev/i2c-2", O_RDWR);
if (fd < 0) {
printf("%s, open failed!\n", __func__);
return -1;
}
while ((opt = getopt(argc, argv, "w:r:")) != -1) {
printf("optarg: %s\n", optarg);
printf("optind: %d\n", optind);
printf("argc: %d\n", argc);
printf("argv[optind]: %s\n", argv[optind]);
addr = (unsigned short)strtol(optarg, NULL, 0);
printf("addr: %d\n", addr);
switch(opt) {
case 'w':
for (len = 0; optind < argc; optind++, len++) {
buf[len] = (unsigned char)strtol(argv[optind], NULL, 0);
}
printf("len: %d\n", len);
i2c_write_bytes(fd, addr, buf, len);
break;
case 'r':
len = (unsigned int)strtol(argv[optind], NULL, 0);
printf("len: %d\n", len);
i2c_read_bytes(fd, addr, buf, len);
arr_show(buf, len);
break;
default:
printf("Invalid parameter!\n");
usage;
break;
}
}
close(fd);
return 0;
}
参考文档:
【linux iic子系统】i2c整体框图【精髓部分】(五)_bus_for_each_drv-CSDN博客
【linux iic子系统】i2c设备的添加方法(四)_i2c_board_info-CSDN博客
【linux iic子系统】i2c-designware框架分析(六)_【linuxiic子系统】i2c-designware框架分析(六)-CSDN博客
Linux驱动之i2c用户态函数调用_i2c ioctl-CSDN博客