Linux驱动框架学习——I2C驱动体系

最近打算写一个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

I2C总线仅仅使用SCL、SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用。因此,I 2C总线非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中

Linux系统定义了I2C驱动体系结构,将I2C驱动分为三部分:

1.I2C核心,提供了I2C总线驱动和设备驱动的注册注销方法等代码;

2.I2C总线驱动,即SOC的I2C控制器驱动,也叫做I2C适配器驱动;

3.I2C设备驱动,即针对具体的I2C设备编写的驱动.

9.1 I2C核心

I2C核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I 2C总线驱动和设备驱动之间以I 2C核心作为纽带。

9.2 I2C总线驱动

对于I2C,不需要虚拟出一条总线,直接使用I2C总线(),主要包含I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数

I2C总线驱动重点

重点为I2C适配器驱动,重点:两个数据结构:i2c_adapteri2c_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

9.3 I2C设备驱动

介绍: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...函数

9.4 内核中I2C相关源码

9.4.1 drivers/i2c目录

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总线适配器的通信方法

9.4.2 include/linux/i2c.h文件

包含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总线的设备节点后,相关数据结构之间的逻辑组织关系

9.4.3 i2c-dev.c文件

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通信函数的调用,以完成消息的传输

9.5 I2C设备和驱动匹配

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控制器的相关寄存器

9.6 Linux I2C子系统驱动

一方面,适配器驱动可能是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驱动

9.7 I2C设备驱动编写

I2C适配器驱动SOC厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动

9.5.1 I2C设备信息描述

1.未使用设备树

  • i2c_board_info

描述一个具体的I2C设备,成员变量有:type(设备名字)和addr(I2C设备的器件地址),用I2C_BOARD_INFO宏初始化,即设置type和addr成员变量

2.使用设备树

I2C设备信息通过创建相应节点,I2C设备节点的创建重点是compatible属性和reg属性的设置,一个用于匹配驱动,一个用于设置器件地址

9.5.2 I2C设备数据收发处理流程

重点是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 };

9.5.3 设备结构体

/* 设备结构体 */
2 struct xxx_dev {
3 ......
4 void *private_data; /* 私有数据,一般会设置为 i2c_client */
5 };
  • 设备结构体

在设备结构体里面添加一个指向void的指针成员变量private_data,此成员变量用于保存设备的私有数据。在 I2C 设备驱动中我们一般将其指向 I2C 设备对应的i2c_client(在probe函数中实现:标准的字符设备注册代码+传递client参数)

你可能感兴趣的:(Linux驱动框架学习,linux,学习)