以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、前言
由I2C总线设备的驱动框架可知,I2C总线设备驱动框架涉及的文件如下:
(1)I2C设备驱动层相关的文件
x210开发板的电容触摸屏gslX680采用I2C接口,因此以这个I2C设备为例进行分析。其对应的驱动源代码文件是x210_kernel\drivers\input\touchscreen\gslX680.c文件。
它由触摸屏IC原厂工程师提供,代码中涉及到很多触摸屏专业方面的知识,但无需理会。
gslX680.c文件进行了I2C设备驱动的注册,相应的I2C设备注册是在mach-x210.c文件中。
(2)I2C核心层相关的文件
I2C核心层相关的文件是i2c-core.c文件,它由内核开发者提供,和具体的硬件操作无关。(3)I2C总线驱动层相关的文件
algos目录:存放的是I2C通信的算法(主要是时序等内容)。busses目录:存放的是各种已经编写好的、将来要向i2c核心层注册的适配器文件。
我们主要分析drivers\i2c\busses\i2c_s3c2410.c文件。
下面我们将对I2C核心层相关的文件进行分析,主要分析i2c-core.c文件。
二、I2C核心层源码分析
1、I2C子系统的注册函数:i2c_init()
i2c子系统实现为一个模块,在内核配置的时候可以进行动态的加载和卸载。
struct bus_type i2c_bus_type = {.name = "i2c", // 总线的名字.match = i2c_device_match, // 总线下设备与设备驱动的匹配函数.probe = i2c_device_probe, // 总线层的probr函数.remove = i2c_device_remove, // 总线卸载时执行的函数.shutdown = i2c_device_shutdown,.pm = &i2c_device_pm_ops, // 电源管理 };
static int __init i2c_init(void) {int retval;retval = bus_register(&i2c_bus_type); // 注册i2c总线 /sys/bus/i2cif (retval)return retval; #ifdef CONFIG_I2C_COMPATi2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;} #endifretval = i2c_add_driver(&dummy_driver); //注册一个空设备驱动/sys/bus/i2c/driver/dummyif (retval)goto class_err;return 0;class_err: #ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class); bus_err: #endifbus_unregister(&i2c_bus_type);return retval; }/* We must initialize early, because some subsystems register i2c drivers* in subsys_initcall() code, but are linked (and initialized) before i2c.*/ postcore_initcall(i2c_init);
2、略过smbus代码
因为smbus是基于I2C总线发展出来的。
3、i2c_device_match函数分析
函数内容如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv) { // 通过dev指针获取到对应的i2c_client指针struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; // 定义一个i2c_driver 指针if (!client)return 0;// 通过device_driver指针获取到对应的i2c_driver指针driver = to_i2c_driver(drv); /* match on an id table if there is one */// 如果设备驱动中存在id_table表,则通过这个来进行与设备的匹配// 匹配的方式就是比较 id_table指向的i2c_device_id数组中各个元素的名字if (driver->id_table) // 如果匹配成功就会返回这个 i2c_device_id 元素项地址 return i2c_match_id(driver->id_table, client) != NULL; return 0;// 匹配失败返回 0 }
由i2c_match_id函数的分析可知,在i2c总线下的设备与驱动的匹配,是将设备的名字和驱动的i2c_device_id表中的各个表项的名字依次进行对比,只要有一个匹配成功,则表示设备和驱动匹配成功,如果都没有匹配成功则表明匹配失败。
对比平台总线的匹配方法,两者基本一致,但是平台总线的匹配方法多了一个步骤,即还会将设备的名字和驱动的名字进行匹配,如果这个也没有成功才表明设备与设备驱动匹配过程失败。
4、i2c_device_probe函数分析
函数内容如下:
static int i2c_device_probe(struct device *dev) { // 通过device指针获取到对应的i2c_client指针struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver;int status;if (!client)return 0;// 通过device->driver指针获取到对应的i2c_driver指针driver = to_i2c_driver(dev->driver); if (!driver->probe || !driver->id_table)return -ENODEV; // i2c设备通过i2c_client->driver指针去指向与他匹配成功的设备驱动i2c_driverclient->driver = driver; if (!device_can_wakeup(&client->dev))device_init_wakeup(&client->dev,client->flags & I2C_CLIENT_WAKE);dev_dbg(dev, "probe\n"); // 调用设备驱动层的probe函数status = driver->probe(client, i2c_match_id(driver->id_table, client)); if (status) {client->driver = NULL;i2c_set_clientdata(client, NULL);}return status; }
总结:I2C总线上有2条分支,即i2c_client链和i2c_driver链。当注册任何一个driver或者client时,I2C总线调用match函数去对client.name和driver.id_table.name进行循环匹配。若driver.id_table中所有的id都匹配不上,则说明client没有找到对应的driver;如果匹配上则表明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。
5、核心层开放的注册接口
(1)i2c_add_adapter函数 / i2c_add_numbered_adapter函数
这两个函数用来向核心层注册一个适配器。两者的区别在于,i2c_add_adapter函数自动分配适配器编号,而i2c_add_numbered_adapter函数需要手动指定一个适配器编号。
分析可知,这两个函数都调用了i2c_register_adapter函数,而i2c_register_adapter函数是I2C子系统核心层提供给I2C总线驱动层的、用来向核心层注册一个适配器的接口函数。
1)i2c_register_adapter函数及分析
// 向i2c总线注册适配器adapter static int i2c_register_adapter(struct i2c_adapter *adap) {int res = 0, dummy;/* Can't register until after driver model init */if (unlikely(WARN_ON(!i2c_bus_type.p))) {res = -EAGAIN;goto out_list;}rt_mutex_init(&adap->bus_lock);// 初始化i2c_adapter->userspace_clients链表INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */if (adap->timeout == 0)adap->timeout = HZ;// 设置适配器设备的名字 i2c-%d(nr)dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; // 设置设备的总线类型adap->dev.type = &i2c_adapter_type; // 设置设备的设备类型 // 注册设备,如果前面没有指定父设备那么创建的设备文件是: /sys/devices/i2c-%dres = device_register(&adap->dev); if (res) // samsung在注册适配器的时候是指定了父设备的,所以他创建的设备是: // /sys/devices/platform/s3c2410-i2cn/i2c-%dgoto out_list; // 为什么是这个会在后面说到dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);#ifdef CONFIG_I2C_COMPATres = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,adap->dev.parent);if (res)dev_warn(&adap->dev,"Failed to create compatibility class link\n"); #endif/* create pre-declared device nodes */if (adap->nr < __i2c_first_dynamic_bus_num) // 扫描__i2c_board_list链表上挂接的所有的i2c次设备信息并与适配器进行匹配, // 匹配成功创建i2c次设备i2c_scan_static_board_info(adap); /* Notify drivers */mutex_lock(&core_lock);dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,__process_new_adapter);mutex_unlock(&core_lock);return 0;out_list:mutex_lock(&core_lock);idr_remove(&i2c_adapter_idr, adap->nr);mutex_unlock(&core_lock);return res; }
2)i2c_scan_static_board_info函数及分析
static void i2c_scan_static_board_info(struct i2c_adapter *adapter) {struct i2c_devinfo *devinfo; // 定义一个i2c_devinfo 结构体指针down_read(&__i2c_board_lock); // 遍历 __i2c_board_list 链表上的所有i2c_devinfo 结构体 // 比较 i2c_devinfo->busnum 与 适配器的编号是否匹配 // 如果匹配就会调用 i2c_new_device 函数进行注册添加新的次设备 i2c_clientlist_for_each_entry(devinfo, &__i2c_board_list, list) { if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter, &devinfo->board_info))dev_err(&adapter->dev,"Can't create device at 0x%02x\n",devinfo->board_info.addr);}up_read(&__i2c_board_lock); }
3)i2c_new_device函数及分析
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) {struct i2c_client *client; // 定义一个 i2c_client 指针int status;client = kzalloc(sizeof *client, GFP_KERNEL); // 申请分配if (!client)return NULL;// 对i2c_client结构体变量进行填充client->adapter = adap; // i2c次设备通过i2c_client->adapter指针去指向与它匹配成功的适配器i2c_adapterclient->dev.platform_data = info->platform_data; // 将传进来的i2c_board_info结构体作为i2c次设备的platform平台数据if (info->archdata)client->dev.archdata = *info->archdata;client->flags = info->flags; // 标志位client->addr = info->addr; // i2c次设备的地址client->irq = info->irq; // 中断号strlcpy(client->name, info->type, sizeof(client->name)); // 名字/* Check for address validity */status = i2c_check_client_addr_validity(client); // 次设备地址校验if (status) {dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);goto out_err_silent;}/* Check for address business */status = i2c_check_addr_busy(adap, client->addr);if (status)goto out_err;client->dev.parent = &client->adapter->dev; // 指定i2c 次设备的父设备是与它匹配成功的适配器对应的设备client->dev.bus = &i2c_bus_type; // 指定次设备的总线类型client->dev.type = &i2c_client_type; // 指定次设备的设备类型 #ifdef CONFIG_OFclient->dev.of_node = info->of_node; #endifdev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), // 设置次设备的名字 %d-%04xclient->addr);status = device_register(&client->dev); // 注册次设备: /sys/devices/platform/s3c2410-i2cn/i2c-%d/%d-%04xif (status)goto out_err;dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",client->name, dev_name(&client->dev));return client;out_err:dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, status); out_err_silent:kfree(client);return NULL; }
i2c_new_device函数是I2C核心层提供给设备驱动层的、用来注册i2c设备的接口函数,我们可以在直接调用这个函数来注册I2C设备。
在这里I2C核心层还提供了另外一种机制来实现自动注册。其原理是,在系统启动时,在硬件初始化函数中(例如 smdkc110_machine_init 函数里)注册板子的I2C从设备(实际上就是构建i2c_devinfo结构体变量,挂接到__i2c_board_list链表上),当我们向I2C总线核心层注册适配器时就会去扫描该链表,然后根据相关的信息去注册I2C从设备。这也就是上面那个函数的意义。
smdkc110_machine_init { //省略部分代码platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices)); /* 这个是设置的是SoC中的i2c控制器(适配器)作为平台设备的私有数据 */s3c_i2c1_set_platdata(NULL); // 通过这个函数注册板子上的i2c次设备的信息i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs0)); //省略部分代码 }
(2)i2c_add_driver函数分析
在i2c_add_driver函数的内部,调用了I2C核心层的i2c_register_driver函数,来注册一个I2C总线下的设备驱动。i2c_register_driver函数内容如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) {int res;/* Can't register until after driver model init */if (unlikely(WARN_ON(!i2c_bus_type.p)))return -EAGAIN;/* add the driver to the list of i2c drivers in the driver core */driver->driver.owner = owner;driver->driver.bus = &i2c_bus_type; // 指定该设备驱动的总线类型 i2c/* When registration returns, the driver core* will have called probe() for all matching-but-unbound devices.*/res = driver_register(&driver->driver); // 注册设备驱动 /sys/bus/i2c/drivers/dummy dummy就是一个设备驱动文件if (res)return res;pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);INIT_LIST_HEAD(&driver->clients); // 初始化i2c_driver -> clients 链表/* Walk the adapters that are already present */mutex_lock(&core_lock);bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver); // 可以忽略这条语句的执行效果mutex_unlock(&core_lock);return 0; }
6、总结
从上面的分析可知,i2c子系统内部存在着2个匹配过程:
(1)i2c总线下的设备与设备驱动之间的匹配(通过设备驱动的id_table)。
(2)适配器与设备之间的匹配(通过适配器编号)。