I2C子系统框架,i2c子系统


1  I2C子系统框架

Linux I2C子系统分成三部分:I2C核心层、I2C总线驱动和I2C设备驱动。

 

(1)I2C核心层

I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。核心层的代码在drivers/i2c/i2c-core.c中实现。

(2)I2C总线驱动

I2C总线驱动是对I2C硬件适配器端的实现,适配器可由CPU控制。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。

经由I2C总线驱动的代码,我们可以控制I2C适配器以主控产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

Linux在drivers/i2c/下建立了busses目录用于存放各种已实现的I2C总线驱动,其中包括samsung S3C系列芯片的I2C总线驱动实现i2c-s3c2410.c。

(3)I2C设备驱动

I2C设备驱动是对I2C硬件设备端的实现,设备一般挂接在受CPU控制的I2C控制器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。

【温馨提示】i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。i2c-dev.c并不是针对特定的设备而设计的,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或者寄存器,并控制I2C设备的工作方式。

2  I2C子系统数据结构

(1)i2c_adapter结构

I2C子系统用struct i2c_adapter来描述一个物理的适配器,适配器的具体通信方法由struct i2c_adapter的一个类型为struct i2c_algorithm的成员来描述。定义在include/linux/i2c.h。

 1 struct i2c_adapter {
 2     struct module *owner;
 3     unsigned int id;    // 适配器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     u8 level;           /* nesting level for lockdep */
10     struct mutex bus_lock;
11 
12     int timeout;    // 传输超时时间
13     int retries;    // 重试次数
14     struct device dev;    // 内嵌的device结构
15 
16     int nr;    // 总线编号(也是适配器编号),同时对应设备节点/dev/i2c-x(x=0,1,2...)来访问
17     char name[48];    // 适配器名称,这个名称可以通过/sys/bus/i2c/devices/i2c-x/name(x=0,1,2...)来访问
18     struct completion dev_released;
19 };

(2)i2c_algorithm结构

struct i2c_adapter中用于描述通信方法的成员i2c_algorithm定义如下,该成员及其内核方法的实现是开发I2C总线驱动的核心任务。此结构体定义在include/linux/i2c.h。

 1 struct i2c_algorithm {
 2     /* 指向具体的I2C传输函数的指针,对应的传输一般会通过直接操作适配器硬件来发起。这个
 3        函数的传入参数分别是使用该传输方法的适配器adap,待传输的消息msgs和消息数量num。 */
 4     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
 5                int num);
 6     /* 指向具体的SMBus传输函数的指针。SMBus协议大部分基于I2C总线规范,并在基础上做了扩展,在访问
 7        时序上由一些差异。如果这个指针置NULL,基于SMBus协议的通信将通过I2C传输来模拟 */
 8     int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
 9                unsigned short flags, char read_write,
10                u8 command, int size, union i2c_smbus_data *data);
11 
12     /* 指向返回适配器支持功能的函数的指针,这些功能定义在incude/linux/i2c.h中以I2C_FUNC开头
13        的宏表示,常用的有I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL、I2C_FUNC_PROTOCOL_MANGLING */
14     u32 (*functionality) (struct i2c_adapter *);
15 };

(3)i2c_msg结构

i2c_msg结构用来描述一个用于传输I2C的消息。此结构体定义在include/linux/i2c.h

 1 struct i2c_msg {
 2     __u16 addr;    // 从机地址
 3     __u16 flags;    // 反映消息特定的标志,当I2C_M_RD位被设置时,表示消息方向是主机从从机去读,如果
 4                     // 不设置该位(0),则消息方向是主机向从机写入。I2C_M_RD可以被所有适配器处理。I2C_M_NOSTART等
 5                     // 几个消息则需要适配器支持I2C_FUNC_PROTOCOL_MANGLING功能。
 6 #define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
 7 #define I2C_M_RD        0x0001  /* read data, from slave to master */
 8 #define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
 9 #define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
10 #define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
11 #define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
12 #define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
13     __u16 len;    // 消息数据长度,单位是字节
14     __u8 *buf;    // 指向存放消息数据缓冲区
15 };

一个i2c_msg对象代表一个底层I2C传输单元。在驱动程序中,i2c_msg对象通过函数i2c_transfer()进行处理,而i2c_transfer()则调用适配器通信方法的master_xfer()来传输消息数据。

(4)i2c_driver结构

i2c_driver结构定义在include/linux/i2c.h中。它对应于一套驱动方法,其主要成员函数是probe()remove()suspend()resume()等,另外,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表。

 1 struct i2c_driver {
 2     unsigned int class;
 3 
 4     /* 分别是依附和脱离i2c_adapter的函数指针,驱动注册函数会遍历适配器设备类i2c_adapter_class中的所有设备
 5        并调用该驱动的attach_adapter方法进行依附。相应的,在添加i2c_adapter时,适配器注册函数会遍历总线i2c_bus_type
 6        上所有的驱动,如果驱动定义了attach_adapter方法,他也将得到调用 */
 7     int (*attach_adapter)(struct i2c_adapter *) __deprecated;
 8     int (*detach_adapter)(struct i2c_adapter *) __deprecated;
 9 
10     /* 在设备驱动中,当总线i2c_bus_type上的设备与设备驱动匹配后被调用。 */
11     int (*probe)(struct i2c_client *, const struct i2c_device_id *);
12     int (*remove)(struct i2c_client *);
13 
14     /* driver model interfaces that don't relate to enumeration  */
15     void (*shutdown)(struct i2c_client *);
16     int (*suspend)(struct i2c_client *, pm_message_t mesg);
17     int (*resume)(struct i2c_client *);
18 
19     void (*alert)(struct i2c_client *, unsigned int data);
20 
21     int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
22 
23     /* 内嵌的driver结构。在注册i2c_driver对象时,i2c_driver->driver的总线类型被指定为i2c_bus_type */
24     struct device_driver driver;
25     /* 存放该驱动支持的设备列表。i2c_device id结构在include/linux/mod_devicetable.h
26        中定义。包括一个char name[I2C_NAME_SIZE]和一个kernel_ulong_t driver_data的成员。
27        name用于匹配设备和驱动,i2c_bus_type的match()方法会遍历驱动id_table中的每一项,通过
28        比较设备名称和这个name成员,找到与设备匹配的驱动。*/
29     const struct i2c_device_id *id_table;
30 
31     int (*detect)(struct i2c_client *, struct i2c_board_info *);
32     const unsigned short *address_list;
33     struct list_head clients;
34 };

(5)i2c_client结构

i2c_client结构定义在inlcude/linux/i2c.h中。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client描述。i2c_client的信息通常在BSP的板级文件中通过i2c_board_info填充或者设备树填充。

 1 struct i2c_client {
 2     unsigned short flags;    // 两个主要的标识是:I2C_CLIENT_TEN表示设备使用10-bit地址;
 3                              // I2C_CLIENT_PEC表示设备使用SMBus包错误检查
 4     unsigned short addr;    // 设备地址,7-bit地址格式下,地址存放该成员的低7位
 5     char name[I2C_NAME_SIZE];    // 设备名
 6     struct i2c_adapter *adapter;    // 依附的适配器
 7     struct i2c_driver *driver;    // 设备绑定的驱动
 8     struct device dev;    // 内嵌device结构。
 9     int irq;    
10     struct list_head detected;
11 };

1BSP板级文件的i2c_board_info填充:

I2C设备ID为“ad7142_joystick”、地址为0x2C、中断号为IRQ_PF5

1 static struct i2c_board_info __initdata xxx_i2c_board_info[] = {
2     {
3         I2C_BOARD_INFO("ad7142_joystick", 0x2C);
4         .irq = IRQ_PF5.
5     },
6     ...
7 }

2)设备树填充:

I2C设备ID为“invensense,mpu6050”、地址为0x68、中断号为3。

i2c@138B0000 {
    samsung,i2c-sda-delay = <100>;
    samsung,i2c-max-bus-freq = <20000>;
    pinctrl-0 = <&i2c5_bus>;
    pinctrl-names = "default";
    status = "okay";
    
    mpu6050-3-axis@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
        interrupt-parent = <&gpx3>;
        interrupts = <3 2>;
    };
};

I2C总线驱动i2c_bus_typematch()函数i2c_device_match()中,会调用i2c_match_id()函数匹配板级文件中定义的IDi2c_driver所支持的ID表。

3  I2C子系统接口

1i2c_add_adapter()

i2c_add_adapter()向系统注册一个i2c_adapter对象,不必指定其nr成员,总线编号自动在i2c_add_adapter中分配并赋值给nr

函数原形

int i2c_add_adapter(struct i2c_adapter *adapter);

函数参数

adaperi2c_adapter结构指针

2i2c_del_adaper()

i2c_del_adapter()向系统注销一个i2c_adapter对象。

函数原形

int i2c_del_adapter(struct i2c_adapter *adapter);

函数参数

adaperi2c_adapter结构指针

3i2c_add_numbered_adapter()

i2c_add_numbered_adapter()向系统注册一个i2c_adapter结构。此函数必须静态指定一个总线编号给nr成员。

函数原形

int i2c_add_numbered_adapter(struct i2c_adapter *adapter);

函数参数

adaperi2c_adapter结构指针

4i2c_add_driver()

i2c_add_driver()向系统添加一个i2c_driver对象。

函数原形

int i2c_add_driver(struct i2c_driver *driver);

函数参数

driveri2c_driver结构指针

5i2c_del_driver()

i2c_del_driver()向系统注销一个i2c_driver对象。

函数原形

int i2c_del_driver(struct i2c_driver *driver);

函数参数

driveri2c_driver结构指针

6i2c_master_send()

i2c_master_send()函数用来主机向i2c_client设备对象发送数据。在使用它们收发数据时,必须先设置设备地址。

函数原形

int i2c_master_send(const struct i2c_client *client, const char *buf, int count);

函数参数

clienti2c_client结构指针

buf:存放发送数据的缓冲区

count:发送数据的大小

7i2c_master_recv()

i2c_master_recv()函数用来主机获取从机的数据。在它们收发数据时,必须先设置设备节点。

函数原形

int i2c_master_recv(const struct i2c_client *client, const char *buf, int count);

函数参数

clienti2c_client结构指针

buf:存放接收数据的缓冲区

count:接收数据的大小

8i2c_transfer()

i2c_transfer()函数用于执行单个或组合的MSG消息。

函数原形

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

函数参数

adapterI2C适配器的结构指针

msgsi2c_msg结构,存放发送和接收的数据

nummsg的数量

【温馨提示】i2c_transfer()一次可以传输多个i2c_msg(考虑到很多外设的读写波形比较复杂)。而对于时序比较简单的外设,i2c_master_send()函数和i2c_master_recv()函数内部调用i2c_transfer()函数分别完成一条写消息和一条读消息。

相关内容