linux设备驱动-I2C驱动详解1

1 I2C的总体架构框图如下:

1.1 包括三大部分:I2C核心层、I2C总线驱动和I2C设备驱动。

(1)I2C核心层(i2c-core)提供了I2C总线驱动(适配器)和设备驱动的注册、注销方法,I2C通信方法(”algorithm”)上层的,与具体硬件无关的代码以及探测设备检测设备地址的上层代码等。

(2)I2C总线驱动(I2C adapter):I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。I2C总线驱动由i2c_adapter和i2c_algorithm来描述。 I2C适配器是SoC中内置i2c控制器的软件抽象,可以理解为它所代表的是一个I2C主机。

(3)I2C设备驱动(I2C client driver):包括两部分:设备的注册和设备驱动的注册

1.2 I2C子系统的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,让内核统一管理I2C设备,从而可以更容易的在linux下驱动自己的I2C接口硬件。

1.3 I2C子系统提供的两种驱动实现方法(源码中I2C相关的驱动均位于:drivers/i2c目录下)

(1)第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2Cmaster,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实现对硬件的操作,因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做“应用层驱动”,这种方式并不主流,它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。

(2)第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。

2 相关数据结构

I2C 总线驱动(I2C adapter)

2.1 struct i2c_adapter

定义位于:include\linux\i2c.h

struct i2c_adapter是用来描述一个I2C适配器,在SoC中的指的就是内部外设I2C控制器,当向I2C核心层注册一个I2C适配器时就需要提供这样的一个结构体变量。

 1 struct i2c_adapter {
 2     struct module *owner;             // 所有者
 3     unsigned int id;
 4     unsigned int class;               // 该适配器支持的从设备的类型
 5     const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法
 6     void *algo_data;
 7 
 8     /* data fields that are valid for all devices    */
 9     struct rt_mutex bus_lock;
10 
11     int timeout;              // 超时时间
12     int retries;
13     struct device dev;        // 该适配器设备对应的device
14 
15     int nr;                   // 适配器的编号
16     char name[48];            // 适配器的名字
17     struct completion dev_released;
18 
19     struct mutex userspace_clients_lock;
20     struct list_head userspace_clients;  // 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
21 
22     struct i2c_bus_recovery_info *bus_recovery_info;
23 };

2.2 struct i2c_algorithm

i2c_adapter是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型。struct i2c_algorithm结构体代表的是适配器的通信算法,在构建i2c_adapter结构体变量的时候会去填充这个元素。

 1 /*
 2  * The following structs are for those who like to implement new bus drivers:
 3  * i2c_algorithm is the interface to a class of hardware solutions which can
 4  * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 5  * to name two of the most common.
 6  */
 7 struct i2c_algorithm {
 8     /* If an adapter algorithm can't do I2C-level access, set master_xfer
 9        to NULL. If an adapter algorithm can do SMBus access, set
10        smbus_xfer. If set to NULL, the SMBus protocol is simulated
11        using common I2C messages */
12     /* master_xfer should return the number of messages successfully
13        processed, or a negative value on error */
14     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
15                int num);
16     int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
17                unsigned short flags, char read_write,
18                u8 command, int size, union i2c_smbus_data *data);
19 
20     /* To determine what the adapter supports */
21     u32 (*functionality) (struct i2c_adapter *);
22 }

如果一个I2C适配器不支持I2C通道,那么就将master_xfer成员设为NULL。如果适配器支持SMBUS协议,那么需要去实现smbus_xfer,如果smbus_xfer指针被设为NULL,那么当使用SMBUS协议的时候将会通过I2C通道进行仿真。master_xfer指向的函数的返回值应该是已经成功处理的消息数,或者返回负数表示出错了。functionality指针很简单,告诉询问着这个I2C主控器都支持什么功能。

在内核的drivers/i2c/i2c-stub.c中实现了一个i2c adapter的例子,其中实现的是更为复杂的smbus。

注意:smbus协议是从I2C协议的基础上发展而来的,他们之间有很大的相似度,smbus与I2C总线之间在时序特性上存在一些差别,应用于移动PC和桌面PC系统中的低速率通讯。

2.3 struct i2c_client

表示I2C的从设备,i2c_client标识连接到i2c总线的单个设备(即芯片)。暴露给Linux的行为是由管理设备的驱动程序定义的。I2C设备的注册一般在板级代码中。

 1 /**
 2  * struct i2c_client - represent an I2C slave device
 3  * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
 4  *    I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
 5  * @addr: Address used on the I2C bus connected to the parent adapter.
 6  * @name: Indicates the type of the device, usually a chip name that's
 7  *    generic enough to hide second-sourcing and compatible revisions.
 8  * @adapter: manages the bus segment hosting this I2C device
 9  * @driver: device's driver, hence pointer to access routines
10  * @dev: Driver model device node for the slave.
11  * @irq: indicates the IRQ generated by this device (if any)
12  * @detected: member of an i2c_driver.clients list or i2c-core's
13  *    userspace_devices list
14  *
15  * An i2c_client identifies a single device (i.e. chip) connected to an
16  * i2c bus. The behaviour exposed to Linux is defined by the driver
17  * managing the device.
18  */
19 struct i2c_client {    //  用来描述一个i2c次设备
20     unsigned short flags;        //  描述i2c次设备特性的标志位
21     unsigned short addr;         //  i2c 次设备的地址
22 
23     char name[I2C_NAME_SIZE];    //  i2c次设备的名字
24     struct i2c_adapter *adapter; //  指向与次设备匹配成功的适配器
25     struct i2c_driver *driver;   //  指向与次设备匹配成功的设备驱动
26     struct device dev;           //  该次设备对应的device
27     int irq;                     //  次设备的中断引脚
28     struct list_head detected;   //  作为一个链表节点挂接到与他匹配成功的i2c_driver 相应的链表头上
29 };

2.4 i2c_driver

具体的I2C设备驱动,如相机、传感器、触摸屏、背光控制器常见硬件设备大多都有或都是通过I2C协议与主机进行数据传输、控制。结构体如下:

 1 struct i2c_driver {
 2     unsigned int class;
 3 
 4     /* Notifies the driver that a new bus has appeared or is about to be
 5      * removed. You should avoid using this, it will be removed in a
 6      * near future.
 7      */
 8     int (*attach_adapter)(struct i2c_adapter *) __deprecated;  //旧的与设备进行绑定的接口函数
 9     int (*detach_adapter)(struct i2c_adapter *) __deprecated;  //旧的与设备进行解绑的接口函数
10 
11     /* Standard driver model interfaces */
12     int (*probe)(struct i2c_client *, const struct i2c_device_id *); //现行通用的与对应设备进行绑定的接口函数
13     int (*remove)(struct i2c_client *);  //现行通用与对应设备进行解绑的接口函数
14 
15     /* driver model interfaces that don't relate to enumeration  */
16     void (*shutdown)(struct i2c_client *);  //关闭设备
17     int (*suspend)(struct i2c_client *, pm_message_t mesg); //挂起设备,与电源管理有关,为省电
18     int (*resume)(struct i2c_client *); //从挂起状态恢复
19 
20     /* Alert callback, for example for the SMBus alert protocol.
21      * The format and meaning of the data value depends on the protocol.
22      * For the SMBus alert protocol, there is a single bit of data passed
23      * as the alert response's low bit ("event flag").
24      */
25     void (*alert)(struct i2c_client *, unsigned int data);
26 
27     /* a ioctl like command that can be used to perform specific functions
28      * with the device.
29      */
30     int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
31 
32     struct device_driver driver;  //I2C设备的驱动模型
33     const struct i2c_device_id *id_table;  //匹配设备列表
34 
35     /* Device detection callback for automatic device creation */
36     int (*detect)(struct i2c_client *, struct i2c_board_info *);
37     const unsigned short *address_list;
38     struct list_head clients;
39 };

一般编写驱动过程中对象常是driver类型,可以通过to_i2c_driver找到其父类型i2c_driver

#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)

如同普通设备的驱动能够驱动多个设备一样,一个I2C driver也可以对应多个I2C client。

以重力传感器AXLL34X为例,其实现的I2C驱动为:

 1 static const struct i2c_device_id adxl34x_id[] = { 
 2      { "adxl34x", 0 },  //匹配i2c client名为adxl34x的设备
 3      { } 
 4  };
 5 
 6  MODULE_DEVICE_TABLE(i2c, adxl34x_id);
 7 
 8  static struct i2c_driver adxl34x_driver = { 
 9      .driver = { 
10          .name = "adxl34x",
11          .owner = THIS_MODULE,
12          .pm = &adxl34x_i2c_pm,  //指定设备驱动的电源管理接口,包含suspend、resume
13      },  
14      .probe    = adxl34x_i2c_probe,  //组装设备匹配时候的匹配动作
15      .remove   = adxl34x_i2c_remove,  //组装设备移除接口
16      .id_table = adxl34x_id,  //制定匹配设备列表
17  };
18 
19  module_i2c_driver(adxl34x_driver);

这里要说明一下module_i2c_driver,宏的定义位于i2c.h:

 1 #define module_i2c_driver(__i2c_driver) \
 2     module_driver(__i2c_driver, i2c_add_driver, \
 3                      i2c_del_driver)
 4 
 5 #define i2c_add_driver(driver) \
 6         i2c_register_driver(THIS_MODULE, driver)
 7 module_driver():
 8 
 9 #define module_driver(__driver, __register, __unregister, ...) \
10 static int __init __driver##_init(void) \
11 { \
12         return __register(&(__driver) , ##__VA_ARGS__); \
13 } \
14 module_init(__driver##_init); \
15 static void __exit __driver##_exit(void) \
16 { \
17         __unregister(&(__driver) , ##__VA_ARGS__); \
18 } \
19 module_exit(__driver##_exit);

将module_i2c_driver(adxl34x_driver)展开就可以得到:

 1 static int __int adxl34x_driver_init(void)
 2 {
 3     return i2c_register_driver(&adxl34x_driver);
 4 }
 5 module_init(adxl34x_driver_init);
 6 static void __exit adxl34x_driver_exit(void)
 7 {
 8     return i2c_del_driver(&adxl34x_driver);
 9 }
10 module_exit(adxl34x_driver_exit);

这一句宏就解决了模块module安装卸载的复杂代码。这样驱动开发者在实现I2C驱动时只要将i2c_driver结构体填充进来就可以了,无需关心设备的注册与反注册过程。

2.4 struct i2c_board_info 

 1 struct i2c_board_info {      //  这个结构体是用来描述板子上的一个i2c设备的信息
 2     char        type[I2C_NAME_SIZE];    //  i2c 设备的名字,用来初始化i2c_client.name,最长20个字符
 3     unsigned short    flags;            //  用来初始化i2c_client.flags
 4     unsigned short    addr;             //  设备从地址,用来初始化 i2c_client.addr
 5     void        *platform_data;         //  设备数据,用来初始化 i2c_client.dev.platform_data
 6     struct dev_archdata    *archdata;   //  用来初始化i2c_client.dev.archdata
 7     struct device_node *of_node;
 8     struct acpi_dev_node acpi_node;
 9     int        irq;                     //  设备采用的中断号,用来初始化i2c_client.irq
10 };

可以看到,i2c_board_info基本是与i2c_client对应的。

通过这个宏定义可以方便的定义I2C设备的名称和从地址:

 1 /**
 2  * I2C_BOARD_INFO - macro used to list an i2c device and its address
 3  * @dev_type: identifies the device type
 4  * @dev_addr: the device's address on the bus.
 5  *
 6  * This macro initializes essential fields of a struct i2c_board_info,
 7  * declaring what has been provided on a particular board.  Optional
 8  * fields (such as associated irq, or device-specific platform_data)
 9  * are provided using conventional syntax.
10  */
11 #define I2C_BOARD_INFO(dev_type, dev_addr) \
12     .type = dev_type, .addr = (dev_addr)

下面还是以adxl34x为例:

 1 static struct i2c_board_info i2c0_devices[] = { 
 2     {   
 3         I2C_BOARD_INFO("ak4648", 0x12),
 4     },  
 5     {   
 6         I2C_BOARD_INFO("r2025sd", 0x32),
 7     },  
 8     {   
 9         I2C_BOARD_INFO("ak8975", 0x0c),
10         .irq = intcs_evt2irq(0x3380), /* IRQ28 */
11     },  
12     {   
13         I2C_BOARD_INFO("adxl34x", 0x1d),
14         .irq = intcs_evt2irq(0x3340), /* IRQ26 */
15     },  
16 };
17 ...
18 i2c_register_board_info(0, i2c0_devices, ARRAY_SIZE(i2c0_devices));

这样ADXL34X的i2c设备就被注册到了系统中,当名字与i2c_driver中的id_table中的成员匹配时就能够出发probe匹配函数了。

2.5 i2c_devinfo 

定义位于:drivers\i2c\i2c-core.h

1 struct i2c_devinfo {
2     struct list_head    list;            // 作为一个链表节点挂接到__i2c_board_list 链表上去
3     int            busnum;               // 适配器的编号
4     struct i2c_board_info    board_info; //  内置的i2c_board_info 结构体
5 };

 3 I2C文件分布(drivers\i2c)

(1)i2c-core.c:  i2c核心层

(2)busses目录:这个文件中是已经编写好的各种向i2c核心层注册的适配器

(3)algos目录:这个目录里面是一些I2C通信算法

参考博文:https://blog.csdn.net/ahza3195/article/details/101694558

https://www.cnblogs.com/biglucky/p/4059576.html

posted @ 2020-06-09 22:09  Action_er  阅读(1085)  评论(0编辑  收藏  举报