Linux设备驱动之I2C设备驱动

时间:2022-10-05 17:56:37

   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文档。