Linux I2C驱动体系结构主要由3部分组成,即I2C设备驱动,I2C核心层、I2C总线驱动。设备驱动层主要是针对不同的I2C硬件从设备编写的驱动程序,I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可以理解为软件上抽象出来的i2c接口,这个接口可以对应I2C总线控制器接口,也可以对应用用GPIO模拟的I2C控制器接口。I2C核心层是I2C总线驱动和I2C设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了I2C中设备与适配器的沟通。I2C总线驱动填充i2c_adapter和i2c_algorithm结构体,I2C设备驱动填充i2c_driver和i2c_client结构体。驱动工程师要做的就是总线驱动和设备驱动的编写。
I2C设备驱动
前面说了设备驱动主要是填充i2c_driver和i2c_client两个结构体,了解Linux总线,设备,驱动模型的话就知道,注册i2c_driver(i2c_client)结构体时,总线会从总线上寻找与其名字匹配的i2c_client(i2c_driver),并调用i2c_driver的probe函数,反之有一个被删除时,会调用i2c_driver的remove函数。我们可以在i2c_driver的probe函数里做想做的事情,比如创建设备节点等,在remove函数里在一些清除工作。那么i2c_driver该如何定义和注册呢,参考4.43内核其它的i2c设备驱动文件,我们先定义好i2c_driver结构体,并填充好几个关键成员driver{.name},probe,remove,id_table后,用module_i2c_driver()这个宏来注册i2c_driver,这个宏在/include/linux/i2c.h定义,注释上说用它来注册一个i2c_driver,并通过调用它代替module_init() and module_exit()。当然你也可以在入口函数里用i2c_add_driver,出口函数里用i2c_del_driver来注册和删除i2c_driver。这样,i2c_driver结构体就算完成了。
接下来我们来注册一个i2c_client结构体,内核文档Documentation/i2c/instantiating-devices中介绍了如何实例化一个i2c设备,总共有以下几种方法:
Method 1a: Declare the I2C devices by bus number。通过总线号来注册I2C设备,如下所示:
1 static struct i2c_board_info h4_i2c_board_info[] __initdata = { 2 { 3 I2C_BOARD_INFO("isp1301_omap", 0x2d), 4 .irq = OMAP_GPIO_IRQ(125), 5 }, 6 { /* EEPROM on mainboard */ 7 I2C_BOARD_INFO("24c01", 0x52), 8 .platform_data = &m24c01, 9 }, 10 { /* EEPROM on cpu card */ 11 I2C_BOARD_INFO("24c01", 0x57), 12 .platform_data = &m24c01, 13 }, 14 }; 15 16 static void __init omap_h4_init(void) 17 { 18 (...) 19 i2c_register_board_info(1, h4_i2c_board_info, 20 ARRAY_SIZE(h4_i2c_board_info)); 21 (...) 22 }
i2c_register_board_info的传统用法是在内核初始化时,在i2c_adapter注册之前。查看i2c_adapter的注册代码可以发现,i2c_adapter_register里会调用i2c_scan_static_board_info扫描board_info的链表,为每一个注册的信息调用i2c_new_device函数生成i2c_client,这样在i2c_driver注册的时候,设备和驱动就能匹配并调用probe.
如果想在adapter注册之后调用i2c_register_board_info,注册的信息没有机会生成i2c_client,从而无法与i2c_driver匹配。这时还是要使用i2c_new_device或i2c_new_probed_device。
Method 1b: Declare the I2C devices via devicetree,通过设备树来声明I2C设备,如下所示:
1 i2c1: i2c@400a0000 { 2 /* ... master properties skipped ... */ 3 clock-frequency = <100000>; 4 5 flash@50 { 6 compatible = "atmel,24c256"; 7 reg = <0x50>; 8 }; 9 10 pca9532: gpio@60 { 11 compatible = "nxp,pca9532"; 12 gpio-controller; 13 #gpio-cells = <2>; 14 reg = <0x60>; 15 }; 16 };
Method 1c: Declare the I2C devices via ACPI。通过ACPI来声明I2C设备,详见Documentation/acpi/enumeration.txt。
Method 2: Instantiate the devices explicitly(明确地)。主要有如下两种方法:
1 static struct i2c_board_info sfe4001_hwmon_info = { 2 I2C_BOARD_INFO("max6647", 0x4e), 3 }; 4 5 int sfe4001_init(struct efx_nic *efx) 6 { 7 (...) 8 efx->board_info.hwmon_client = 9 i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info); 10 11 (...) 12 }
1 static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END }; 2 3 static int usb_hcd_nxp_probe(struct platform_device *pdev) 4 { 5 (...) 6 struct i2c_adapter *i2c_adap; 7 struct i2c_board_info i2c_info; 8 9 (...) 10 i2c_adap = i2c_get_adapter(2); 11 memset(&i2c_info, 0, sizeof(struct i2c_board_info)); 12 strlcpy(i2c_info.type, "isp1301_nxp", I2C_NAME_SIZE); 13 isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, 14 normal_i2c, NULL); 15 i2c_put_adapter(i2c_adap); 16 (...) 17 }
这类方法适用于事先不知道I2C总线号的情况,前者用i2c_new_device来声明设备获得一个i2c_client结构体,后者通过i2c_new_probed_device来实现,两者的区别在于i2c_new_probed_device会依次探测normal_i2c数组中的地址,查看该地址的设备是否存在,如果存在,就实例化,而i2c_new_device不会管这么多,它会直接实例化某个固定地址的设备,不管它是否存在。两者都是用i2c_unregister_device()来删除实例化的设备。
Method 3: Probe(探测) an I2C bus for certain devices。某些时刻,我们不知道I2C设备的一些具体信息,以至于不能用上面的方法,当我们装载这类设备的驱动时,I2C核心层会为我们探测这些设备并自动实例化。这要求驱动必须有detect函数和address_list成员,address_list为要探测的地址序列。而且总线必须支持该设备,并同意检测。详见drivers/hwmon/lm90.c。一般来说这种方法不推荐,更推荐方法一和二。
Method 4: Instantiate from user-space:我们可以从用户空间实例化一个I2C设备和删除一个设备。示例如下:
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device //实例化一个名为eeprom地址为0x50的设备
这种方法只应该在内核中的设备声明无法使用时才用到,但它也有一些优势,你不用去重新载入驱动去更改设备的某些设置,你可以在驱动装载前就实例化它,不用管它需要哪个驱动。
以上就是实例化设备的几种方法,这样,我们就把i2c_client和i2c_driver填充完毕了,设备驱动的主要任务也就完成了,具体其它的工作不同的设备各有不同,这里就不介绍了。
通常来说,I2C设备都由内核驱动来控制,但我们也可以从用户空间使用一个适配器来使用所有设备,这通过/dev接口来实现,我们需要装载i2c-dev模块,我们可以把它理解为一个内核帮我们实现的一个通用I2C设备驱动。如果想要在一个C应用程序里使用这个adapter,需要#include <linux/i2c-dev.h>,这个头文件在i2c-tool里有,i2c-tool是一款I2C调试工具,将这个头文件拷入内核的/include/linux/目录下即可。具体如何在C应用程序里使用这个adapter详见/Documentation/i2c/dev-interface文档。