最近打算写一个Camera项目,涉及调试内核、优化开机——从启动到获取第一帧图像的时间,因为在查看Camera相关内核源码后发现底层Camera总线是类似I2C的,所以结合《正点原子驱动开发指南》和《Linux驱动开发详解》简单地了解了一下Linux中的I2C驱动框架,在博客上作个笔记记录以下,好记性不如敲键盘哈哈
目录
I2C
9.1 I2C核心
9.2 I2C总线驱动
9.3 I2C设备驱动
9.4 内核中I2C相关源码
9.4.1 drivers/i2c目录
9.4.2 include/linux/i2c.h文件
9.4.3 i2c-dev.c文件
9.5 I2C设备和驱动匹配
9.6 Linux I2C子系统驱动
9.7 I2C设备驱动编写
9.5.1 I2C设备信息描述
9.5.2 I2C设备数据收发处理流程
9.5.3 设备结构体
I2C总线仅仅使用SCL、SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用。因此,I 2C总线非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中
Linux系统定义了I2C驱动体系结构,将I2C驱动分为三部分:
1.I2C核心,提供了I2C总线驱动和设备驱动的注册注销方法等代码;
2.I2C总线驱动,即SOC的I2C控制器驱动,也叫做I2C适配器驱动;
3.I2C设备驱动,即针对具体的I2C设备编写的驱动.
I2C核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I 2C总线驱动和设备驱动之间以I 2C核心作为纽带。
对于I2C,不需要虚拟出一条总线,直接使用I2C总线(),主要包含I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数
I2C总线驱动重点
重点为I2C适配器驱动,重点:两个数据结构:i2c_adapter和i2c_algorithm
i2c_adapter
Linux内核将SOC的I2C适配器(控制器)抽象成i2c_adapter结构体
成员变量包括I2C适配器与具体I2C设备的通信方法:i2c_algorithm(总线访问算法)
const struct i2c_algorithm *algo; /* 总线访问算法 */
i2c_algorithm
i2c_algorithm类型指针变量为其的指针变量,即I2C适配器与I2C设备进行通信的方法,master_xfer为I2C适配器的传输函数
示例代码 61.1.1.2 i2c_algorithm 结构体
391 struct i2c_algorithm {
......
398 int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
399 int num);
400 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
401 unsigned short flags, char read_write,
402 u8 command, int size, union i2c_smbus_data *data);
403
404 /* To determine what the adapter supports */
405 u32 (*functionality) (struct i2c_adapter *);
......
411 };
综上,I2C总线驱动(或I2C适配器驱动)的主要工作即:
初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数,完成后通过add...函数向系统注册好的i2c_adapter
介绍:I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据
重点:I2C设备驱动重点关注两个数据结构:i2c_client(描述设备信息)一般包含在涉笔的私有信息结构体中和i2c_driver(描述驱动内容,类似于platform_driver)适合被定义为全局变量并初始化:
i2c_client
一个设备对应于一个i2c_client,每检测到一个I2C设备就会给这个设备分配一个i2c_client,i2c_adapter即为该结构的成员变量
示例代码 61.1.2.1 i2c_client 结构体
217 struct i2c_client {
218 unsigned short flags; /* 标志 */
219 unsigned short addr; /* 芯片地址,7 位,存在低 7 位*/
......
222 char name[I2C_NAME_SIZE]; /* 名字 */
223 struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
224 struct device dev; /* 设备结构体 */
225 int irq; /* 中断 */
226 struct list_head detected;
......
230 };
i2c_driver
i2c_driver类似platform_driver
成员变量有:probe函数,device_driver驱动结构体(若使用设备树,需要设置of_match_table成员变量,即兼容属性),id_table(传统的设备匹配ID表)
对于I2C设备驱动编写,重点工作就是:
构建i2c_driver,构建完成以后需要向Linux内核注册这个i2c_driver,通过add...函数
drivers目录下有一个i2c目录,在i2c目录下包含如下文件和文件夹:
(1) i2c-core.c:实现了I2C核心的功能以及/proc/bus/i2c*接口;
(2) i2c-dev.c:实现了I2C适配器设备文件的功能,每一个适配器都被分配一个设备,主设备号为89,次设备号为0~255,并不针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口
应用层可以通过这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式
(3) busses文件夹:包含一些I2C主机控制器的驱动
(4) algos文件夹:实现了I2C总线适配器的通信方法
包含i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这四个数据结构定义
(1) i2c_adapter和i2c_algorithm:
i2c_adapter包含所使用的i2c_algorithm的指针,i2c_algorithm中的关键函数为master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(I2C信息)为单位,其中的成员表明I2C的传输地址、方向、缓冲区和缓冲区长度的信息
(2)i2c_driver和i2c_client:
i2c_driver对应于一套驱动方法,i2c_client对应于真实的物理设备,每个I 2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_driver可以支持多个同类型的i2c_client
(3)i2c_adpater与i2c_client:
i2c_adpater与i2c_client的关系与I 2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器可以连接多个I 2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表
假设I 2C总线适配器xxx上有两个使用相同驱动程序的yyy I 2C设备,在打开该I 2C总线的设备节点后,相关数据结构之间的逻辑组织关系
i2c-dev.c文件完全可以被看作是一个I2C设备驱动,不过,它实现的i2c_client是虚拟、临时的,主要是为了便于从用户空间操作I2C外设。i2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,因此i2c-dev.c的主体是“i2c_driver成员函数+字符设备驱动” 。
i2c-dev.c提供的i2cdev_read()、i2cdev_write()函数对应于用户空间要使用的read()和write()文件操作接口,这两个函数分别调用I 2C核心的i2c_master_recv()和i2c_master_send()函数来构造一条I2C消息并引发适配器Algorithm通信函数的调用,以完成消息的传输
I2C设备和驱动匹配由I2C核心完成,由I2C总线完成,I2C核心提供了一些与具体硬件无关的API函数,如有关I2C适配器和I2C驱动的注册注销函数:
1、i2c_adapter 注册/注销函数
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2、i2c_driver注册/注销函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
总线数据结构为i2c_bus_type:
i2c_bus_type
示例代码 61.1.2.5 i2c_bus_type 总线
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
I2C适配器驱动时一个标准的platform驱动,当设备和驱动匹配成功后i2c_imx_probe函数就会执行,完成I2C适配器初始化工作:
1.初始化i2c_adapter.设置i2c_algorithm为i2c_imx_algo,最后向Linux内核注册i2c_adapter; 2.初始化I2C1控制器的相关寄存器
一方面,适配器驱动可能是Linux内核本身还不包含的;另一方面,挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的。因此,工程师要实现的主要工作如下。
1.I2C总线驱动:
1.提供I2C适配器的硬件驱动,探测、初始化I 2C适配器(如申请I 2C的I/O地址和中断号)、驱动CPU控制的I 2C适配器从硬件上产生各种信号以及处理I 2C中断等。
2.提供I2C适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。
2.I2C设备驱动:
3.实现I2C设备驱动中的i2c_driver接口,用具体设备yyy的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、suspend、resume和id_table指针。
4.实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等;如果是声卡,就实现ALSA驱动
I2C适配器驱动SOC厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动
1.未使用设备树
i2c_board_info
描述一个具体的I2C设备,成员变量有:type(设备名字)和addr(I2C设备的器件地址),用I2C_BOARD_INFO宏初始化,即设置type和addr成员变量
2.使用设备树
I2C设备信息通过创建相应节点,I2C设备节点的创建重点是compatible属性和reg属性的设置,一个用于匹配驱动,一个用于设置器件地址
重点是i2c_msg的构建和i2c_transfer函数的调用
I2C 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核注册。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行,probe 函数里面所做的就是字符设备驱动那一套了。
一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里就要用到 i2c_transfer 函数了。i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,使用之前要先构建好i2c_msg
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
i2c_msg结构体
Linux内核使用i2c_msg结构体来描述一个信息
示例代码 61.3.2.1 i2c_msg 结构体
68 struct i2c_msg {
69 __u16 addr; /* 从机地址 */
70 __u16 flags; /* 标志 */
71 #define I2C_M_TEN 0x0010
72 #define I2C_M_RD 0x0001
73 #define I2C_M_STOP 0x8000
74 #define I2C_M_NOSTART 0x4000
75 #define I2C_M_REV_DIR_ADDR 0x2000
76 #define I2C_M_IGNORE_NAK 0x1000
77 #define I2C_M_NO_RD_ACK 0x0800
78 #define I2C_M_RECV_LEN 0x0400
79 __u16 len; /* 消息(本 msg)长度 */
80 __u8 *buf; /* 消息数据 */
81 };
/* 设备结构体 */
2 struct xxx_dev {
3 ......
4 void *private_data; /* 私有数据,一般会设置为 i2c_client */
5 };
设备结构体
在设备结构体里面添加一个指向void的指针成员变量private_data,此成员变量用于保存设备的私有数据。在 I2C 设备驱动中我们一般将其指向 I2C 设备对应的i2c_client(在probe函数中实现:标准的字符设备注册代码+传递client参数)