Linux驱动开发05:【I2C驱动】I2C总线驱动和设备驱动
介绍
这里实现一种不涉及硬件的I2C总线和设备驱动,然后分析I2C驱动在系统中的注册过程。这里实现的驱动类似于之前的FIFO字符设备驱动,与之不同的是这里使用了I2C驱动框架,设备文件写入和读取将通过i2c_client传递到指定的i2c_adaper,然后在i2c_adapter中操作一段内存。这个驱动非常简单,甚至没有使用FIFO这种硬件结构,只能进行读和写,毕竟内存的操作不是重点,重点是熟悉Linux系统的I2C驱动框架。
首先需要注意的是I2C驱动分为总线驱动和设备驱动,不能混为一谈。I2C总线驱动主要针对i2c_adapter和i2c_algorithm进行设置,一个i2c_adapter对应一个SOC中的I2C适配器。比如全志H3芯片有三个I2C接口,那么就对应三个I2C适配器,在I2C总线驱动中,主要完成对I2C适配器的配置工作,如写哪个寄存器配置I2C时钟,写哪个寄存器准备发送,接收到数据后从哪个寄存器读取等。可以看出这都是和平台相关的代码,主要由移植操作系统工程师实现。
I2C设备驱动是针对外设的驱动,这是在默认系统的I2C适配器已经能够正常工作情况下,指定调用哪个I2C适配器发送相应的数据给外设芯片。比如i2c-0上接了一个加速度传感器,那么我就可以调用i2c-0的适配器来向该外设发送相应的数据来操作它的寄存器,完成传感器的初始化、读取等操作。
相关结构体
- i2c_adapter对应一个I2C适配器,其中的
i2c_algorithm决定了该适配器是如何工作的。
struct i2c_adapter {struct module *owner;const struct i2c_algorithm *algo; /* the algorithm to access the bus */void *algo_data;...struct device dev; /* the adapter device */int nr;char name[48];...struct list_head userspace_clients;...
};
- i2c_algorithm指定了i2c_adapter具体的传输方法,一个i2c_adapter绑定一个i2c_algorithm。
struct i2c_algorithm{int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);u32 (*functionality) (struct i2c_adapter *);
}
- i2c_client对应一个I2C的外设,属于I2C设备驱动部分,其中包括了外设的名称、从机地址、挂接的适配器等信息。
struct i2c_client {unsigned short flags; /* div., see below */unsigned short addr; /* chip address - NOTE: 7bit *//* addresses are stored in the *//* _LOWER_ 7 bits */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter; /* the adapter we sit on */struct device dev; /* the device structure */int irq; /* irq issued by device */struct list_head detected;...
};
- i2c_driver主要包含一个probe函数,当i2c_client和i2c_driver匹配时,probe函数就会执行,这一点和platform驱动是相似的。
struct i2c_driver {unsigned int class;/* Notifies the driver that a new bus has appeared. You should avoid* using this, it will be removed in a near future.*/int (*attach_adapter)(struct i2c_adapter *) __deprecated;/* Standard driver model interfaces */int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);int (*probe_new)(struct i2c_client *);void (*shutdown)(struct i2c_client *);void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsigned int data);int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver;const struct i2c_device_id *id_table;/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;
};
I2C总线驱动
1. 驱动代码
由于这里使用了platform总线框架,因此这里只贴出platform_driver部分,platform_device部分只包含一个简单的注册并且在第2节有相关代码参考,所以不再贴出来了。
#include
#include
#include
#include
#include
#include
#include
#include #define PAGE_LEN 4096struct myi2c_dev {char *mem;struct i2c_adapter adapter;struct i2c_client *client;
};// 返回-EAGAIN重试发送
static int i2c_test_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,int num)
{struct myi2c_dev *dev = i2c_get_adapdata(adap);int i;for (i=0; iif (msgs[i].len > PAGE_LEN)msgs[i].len = PAGE_LEN;if (msgs[i].flags & I2C_M_RD) {// 读数据printk(KERN_INFO "read data from %p to %p\n", dev->mem, msgs[i].buf);if (copy_to_user(msgs[i].buf, dev->mem, msgs[i].len)) {printk(KERN_INFO "i2c_adapter: fail to read\n");return -EFAULT;}} else {// 写数据printk(KERN_INFO "write data from %p to %p\n", msgs[i].buf, dev->mem);if (copy_from_user(dev->mem, msgs[i].buf, msgs[i].len)) {printk(KERN_INFO "i2c_adapter: fail to write\n");return -EFAULT;}}}return 0;
}static u32 i2c_test_func(struct i2c_adapter *adap)
{return I2C_FUNC_I2C;
}static const struct i2c_algorithm i2c_test_algo = {.master_xfer = i2c_test_xfer,.functionality = i2c_test_func // 决定适配器支持什么类型的i2c器件
};static struct i2c_board_info i2c_mem_dev = {//I2C_BOARD_INFO("24c08", 0x50),//.platform_data = &at24c08.type = "i2c_mem",.addr = 0x23
};static int i2c_test_probe(struct platform_device *pdev)
{struct myi2c_dev *dev;int ret;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev)return -ENOMEM;dev->mem = kzalloc(PAGE_LEN, GFP_KERNEL);if (!dev->mem)return -ENOMEM;// 设置适配器私有数据i2c_set_adapdata(&dev->adapter, dev);// 添加i2c适配器, 使用指定的总线号dev->adapter.owner = THIS_MODULE;//dev->adapter.class = I2C_CLASS_DEPRECATED;strcpy(dev->adapter.name, "i2c test adapter");dev->adapter.algo = &i2c_test_algo;dev->adapter.dev.parent = &pdev->dev;dev->adapter.nr = pdev->id;ret = i2c_add_numbered_adapter(&dev->adapter);// 手动添加i2c_client,本来应该在板级文件或设备树中添加dev->client = i2c_new_device(&dev->adapter, &i2c_mem_dev);if (!dev->client) {printk(KERN_INFO "fail to register i2c_client\n");return -ENOMEM;}platform_set_drvdata(pdev, dev);printk(KERN_INFO "test probe!\n");return 0;
}static int i2c_test_remove(struct platform_device *pdev)
{struct myi2c_dev *dev = platform_get_drvdata(pdev);i2c_unregister_device(dev->client);i2c_del_adapter(&dev->adapter);kfree(dev->mem);kfree(dev);printk(KERN_INFO "i2c test remove!\n");return 0;
}static struct platform_driver i2c_test_driver = {.probe = i2c_test_probe,.remove = i2c_test_remove,.driver = {.name = "i2c_test_module",.owner = THIS_MODULE}
};
module_platform_driver(i2c_test_driver);MODULE_AUTHOR("colourfate " );
MODULE_LICENSE("GPL v2");
2. 内核实现分析
i2c_adapter的添加:
这里使用了platform驱动框架,我们直接看probe函数。这里首先使用i2c_set_adapdata()函数设置i2c_adapter的私有数据,可以看到i2c_adapter实际上是device的子类,而这个私有数据也是设置在device中的:
static inline void i2c_set_adapdata(struct i2c_adapter *dev, void *data)
{dev_set_drvdata(&dev->dev, data);
}
接下来对i2c_adapter进行配置,其中配置了dev->adapter.algo = &i2c_test_algo,这很重要,因为它决定了i2c适配器是如何工作的,同时还配置了dev->adapter.nr = pdev->id,这是i2c适配器的编号,通过编号可以找到唯一指定的适配器。
接下来使用i2c_add_numbered_adapter()函数将适配器添加到系统。该函数指定了i2c_adapter()的总线号,同时可以使用i2c_add_adapter()函数让系统自动分配总线号。
--- drivers --- i2c --- i2c_core.c --- i2c_add_numbered_adapter( --- if (adap->nr == -1)| | struct i2c_adapter *adap) | i2c_add_adapter(adap);| | |- __i2c_add_numbered_adapter(adap);| |- __i2c_add_numbered_adapter( --- id = idr_alloc(&i2c_adapter_idr,adap,adap->nr,adap->nr+1,GFP_KERNEL)| | struct i2c_adapter *adap) |- i2c_register_adapter(adap)| |- i2c_register_adapter( --- INIT_LIST_HEAD(&adap->userspace_clients)| | 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| | |- device_register(&adap->dev)| | |- bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);| |- __process_new_adapter( --- i2c_do_add_adapter(to_i2c_driver(d), data)| | struct device_driver *d,| | void *data)| |- i2c_do_add_adapter( --- i2c_detect(adap, driver)| | struct i2c_driver *driver, |- 这里是废弃的driver->attach_adapter方法| | struct i2c_adapter *adap)| |- i2c_detect( --- address_list = driver->address_list| | struct i2c_adapter *adapter, |- if (!driver->detect || !address_list) | | struct i2c_driver *driver) | return 0;| | |- temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL)| | |- temp_client->adapter = adapter| | |- for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {| | | temp_client->addr = address_list[i];| | | err = i2c_detect_address(temp_client, driver);| | | err不是错误码,则break;| | | }| | |- kfree(temp_client)| |- i2c_detect_address( --- struct i2c_board_info info| | struct i2c_client *temp_client, |- adapter = temp_client->adapter| | struct i2c_driver *driver) |- addr = temp_client->addr| | |- info.addr = addr| | |- driver->detect(temp_client, &info)| | |- if (info.type[0] == '\0')| | | 报错;| | | else {| | | struct i2c_client *client;| | | client = i2c_new_device(adapter, &info);| | | list_add_tail(&client->detected, &driver->clients);| | | }| |- base --- bus.c --- bus_for_each_drv( --- while ((drv = next_driver(&i)) && !error)struct bus_type *bus, |- error = fn(drv, data);struct device_driver *start,void *data,int (*fn)(struct device_driver *, void *))
i2c_add_numbered_adapter展开后涉及很多函数,首先可以追到i2c_register_adapter()函数,该函数首先使用dev_set_name()设定了总线名称为i2c-%d,其中%d为总线号,然后设定了总线名称和从总线类型,并用device_register()函数注册设备,该函数在platform驱动中已经详细分析,这里不展开,最后遍历总线上的所有驱动,并执行__process_new_adapter()函数。
从__process_new_adapter()函数直接调用了i2c_do_add_adapter()函数,其中to_i2c_driver宏是由device_driver父类得到i2c_driver子类。
i2c_do_add_adapter()函数实际上就是一个i2c_detect()函数,下面废弃的函数我们不分析。
i2c_detect()中,如果没有定义driver->address_list和driver->detect,则直接跳出,i2c_driver是下面讲到的外设驱动部分,这里没有定义这两个变量,所以到这里添加适配器就完成,由于我们没有在驱动中定义address_list和detect,所以不会有i2c_client添加进来,所以这里是手动添加的i2c_client。我们也可以继续分析。
接下来申请一个temp_client,然后将适配器与之绑定,然后遍历address_list,并将address_list[i]绑定到temp_client上,然后执行i2c_detect_address
进入i2c_detect_address()函数,该函数实际上是声明了一个struct i2c_board_info变量,然后使用driver->detect填充该变量,然后使用i2c_new_device()新申请了一个i2c_client,将adapter与之绑定,并使用info的信息初始化它,这个函数下来会分析。最后使用list_add_tail()添加到链表完成client的添加工作。
i2c_client的添加:
这里实际就用了一个i2c_new_device()函数完成client的添加
--- drivers --- i2c --- i2c_core.c --- i2c_new_device( --- client = kzalloc(sizeof *client, GFP_KERNEL)struct i2c_adapter *adap, |- client->adapter = adapstruct i2c_board_info const *info) |- client->dev.platform_data = info->platform_data|- client->flags = info->flags|- client->addr = info->addr|- client->irq = info->irq|- strlcpy(client->name, info->type, sizeof(client->name))|- client->dev.bus = &i2c_bus_type|- client->dev.type = &i2c_client_type|- device_register(&client->dev)
我不准备具体分析接下来的代码了,因为代码树列出了函数调用中的关键部分,再结合linux内核源码应该能很容易看懂(毕竟内核源码非常规范),之前的分析只是照着源码复述了一遍,意义不大。当然如果有容易搞错的关键部分我还是会说明。引用linus名言:
Read the fucking source code!
I2C设备驱动
1. 驱动代码
#include
#include
#include
#include
#include
#include
#include
#include struct i2c_memery_dev {struct miscdevice miscdev;struct i2c_client *client;
};ssize_t i2c_mem_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{// misc注册时自动将miscdevice的指针放到private_data中,这就是// 为什么在i2c_memery_dev中要使用miscdevice的实体,而不是指针struct i2c_memery_dev *dev = container_of(filp->private_data,struct i2c_memery_dev, miscdev);struct i2c_msg msg;int ret;// 构建i2c_msgmsg.addr = dev->client->addr;msg.flags = I2C_M_RD;msg.buf = buf;msg.len = count;// 调用适配器发送, 返回值为client->adapter->algo->master_xfer函数的// 返回值ret = i2c_transfer(dev->client->adapter, &msg, 1);if (ret < 0) {printk(KERN_INFO "fail to transfer\n");return 0;}printk(KERN_INFO "count=%d\n", count);return count;
}ssize_t i2c_mem_write (struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{struct i2c_memery_dev *dev = container_of(filp->private_data,struct i2c_memery_dev, miscdev);struct i2c_msg msg;int ret;// 构建i2c_msgmsg.addr = dev->client->addr;msg.flags = 0;msg.buf = buf;msg.len = count;// 调用适配器发送ret = i2c_transfer(dev->client->adapter, &msg, 1);if (ret < 0) { printk(KERN_INFO "fail to transfer\n");return 0;}printk(KERN_INFO "count=%d\n", count);return count;
}static int i2c_mem_release(struct inode *inode, struct file *filp)
{printk(KERN_INFO "i2c_mem release\n");return 0;
}static int i2c_mem_open(struct inode *inode, struct file *filp)
{printk(KERN_INFO "i2c_mem open\n");return 0;
}static const struct file_operations i2c_mem_fops = {.write = i2c_mem_write,.read = i2c_mem_read,.open = i2c_mem_open,.release = i2c_mem_release
};static int i2c_mem_probe(struct i2c_client *client, const struct i2c_device_id *id)
{struct i2c_memery_dev *dev;int ret;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev) {printk(KERN_INFO "i2c_mem_probe:fail to kzalloc\n");return -ENOMEM;}// 设置杂散类设备驱动dev->miscdev.minor = MISC_DYNAMIC_MINOR;dev->miscdev.name = "i2c_mem";dev->miscdev.fops = &i2c_mem_fops;// 设置私有数据dev->client = client;dev_set_drvdata(&client->dev, dev);ret = misc_register(&dev->miscdev);if (ret < 0) {return ret;}return 0;
}static int i2c_mem_remove(struct i2c_client *client)
{struct i2c_memery_dev *dev = dev_get_drvdata(&client->dev);misc_deregister(&dev->miscdev);return 0;
}static const struct i2c_device_id i2c_mem_ids[] = {{"i2c_mem", 0}
};static struct i2c_driver i2c_mem_driver = {.driver = {.name = "i2c_mem",.owner = THIS_MODULE,},.probe = i2c_mem_probe,.remove = i2c_mem_remove,.id_table = i2c_mem_ids,
};static int __init i2c_mem_test_init(void)
{return i2c_add_driver(&i2c_mem_driver);
}
module_init(i2c_mem_test_init);static void __exit i2c_mem_test_exit(void)
{i2c_del_driver(&i2c_mem_driver);
}
module_exit(i2c_mem_test_exit);MODULE_AUTHOR("colourfate " );
MODULE_LICENSE("GPL v2");
2. 内核实现分析
设备驱动主要是向系统注册i2c_driver这个结构,然后在总线驱动中,我们注册了一个i2c_client,此时注册驱动就会遍历总线所有设备,然后对所有设备和驱动调用bus->match这个函数来判断设备和驱动是否匹配,这个地方和platform驱动非常相似(毕竟它们都是总线驱动),只是这个地方的bus->match是i2c_bus_type的匹配函数。
匹配过后就可以执行probe函数进行初始化,这里设备驱动使用了杂散类设备驱动框架,只实现了其中的read和write函数
i2c_driver的注册:
注册主要调用的是i2c_add_driver()宏,这个宏调用了i2c_register_driver()函数。
#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)
接下来分析i2c_register_driver函数
--- drivers --- i2c --- i2c_core.c --- i2c_register_driver( --- driver->driver.bus = &i2c_bus_type| struct module *owner, |- INIT_LIST_HEAD(&driver->clients)| struct i2c_driver *driver) |- driver_register(&driver->driver)| |- i2c_for_each_dev(driver, __process_new_driver)|- i2c_for_each_dev( --- bus_for_each_dev(&i2c_bus_type, NULL, data, fn)| void *data,| int (*fn)(struct device *, void *))|- __process_new_driver( --- if (dev->type != &i2c_adapter_type)struct device *dev, | return 0;void *data) |- i2c_do_add_adapter(data, to_i2c_adapter(dev));
可以看到这里面有两个关键函数都已经分析过了,一个是在platform驱动中分析过的driver_register()函数,一个是刚刚分析过的i2c_do_add_adapter()函数。这里其实就是向系统注册驱动,并遍历i2c_bus上的所有设备,然后再对所有设备执行i2c_do_add_adapter,尝试生成i2c_client,由于这里没有定义detect函数,而是手动添加的i2c_client,所以全部返回0.
在driver_register()函数中完成了i2c_client和i2c_driver的匹配,但是这里调用的是i2c_bus_type->match函数,下面看看它们是如何匹配的。先找到其定义,再找到定义总绑定的函数。
// driver/i2c/i2c_core.c
struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,
};static int i2c_device_match(struct device *dev, struct device_driver *drv)
{struct i2c_client *client = i2c_verify_client(dev);struct i2c_driver *driver;/* Attempt an OF style match */if (i2c_of_match_device(drv->of_match_table, client))return 1;/* Then ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;driver = to_i2c_driver(drv);/* Finally an I2C match */if (i2c_match_id(driver->id_table, client))return 1;return 0;
}
这个函数实际上和platform_bus_type->match大同小异,先匹配设备树,然后匹配id,就是说如果i2c_client和i2c_driver的名字设置相同的话就能够完成匹配。
I2C驱动框架分析
如果认真分析了以上I2C驱动实现的具体代码,我们会发现Linux的I2C驱动框架实际上分三层,分别是:I2C设备驱动、I2C核心、I2C总线驱动。
I2C设备驱动定义了外设的交互方式,对与不同的I2C外设需要不同的设备驱动。I2C设备驱动对上和用户应用程序打交道,对下和I2C核心对接。
I2C核心是I2C驱动框架的主体,它定义了一系列API供设备驱动和总线驱动使用,相当于一个标准的函数调用规范。
I2C总线驱动定义了平台上I2C的工作方式,只要平台不变这部分代码就可以通用。I2C总线驱动是最底层的驱动,它上面是I2C核心,下面是SOC中的I2C适配器硬件,I2C适配器硬件上面连接了的我们的I2C外设。
总体来看这个框架如图所示:
驱动测试
分别编译好三个驱动模块,然后插入到系统,可以看到/dev/目录下出现了i2c_mem设备文件。写两个读和写app来测试驱动,这里写的app是通用的,以后也会使用这些它们来测试其他驱动。
首先是read.c:
#include
#include
#include
#include
#include
#include int main(int argc, char **argv)
{int fd, count, ret;char buf[128] = {0};if (argc != 3) {printf("usage: ./read \n" );return -1;}fd = open(argv[1], O_RDWR);if (fd < 0) {perror("open");return -1;}count = atoi(argv[2]);if (count > 128) {count = 128;printf("warn: count > 128\n");} else if (count < 1) {count = 1;printf("warn: count < 1\n");}ret = read(fd, buf, count);if (ret < 0) {perror("read");return -1;}printf("read %d byte: %s\n", ret, buf);close(fd);return 0;
}
然后是write.c:
#include
#include
#include
#include
#include
#include int main(int argc, char **argv)
{int fd, count, ret;if (argc != 3) {printf("usage: ./write \n" );return -1;}fd = open(argv[1], O_RDWR);if (fd < 0) {perror("open");return -1;}ret = write(fd, argv[2], strlen(argv[2]));if (ret < 0) {perror("write");return -1;}printf("write %d byte\n", ret);close(fd);return 0;
}
编译好之后执行./write /dev/i2c_mem helloworld写入数据,然后使用./read /dev/i2c_mem 10读出数据。我这里是直接在开发板上测试的。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
