当前位置:   article > 正文

【正点原子Linux连载】二十八章 Linux I2C驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南_linux iic驱动正点原子

linux iic驱动正点原子

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第二十八章 Linux I2C驱动实验

对于 I2C 我相信大家都很熟悉,基本上做过单片机开发的朋友都接触过,在电子产品硬件设计当中,I2C 是一种很常见的同步、串行、低速、近距离通信接口,用于连接各种 IC、传感器等器件,它们都会提供 I2C 接口与 SoC 主控相连,比如陀螺仪、加速度计、触摸屏等,其最大优势在于可以在总线上扩展多个外围设备的支持。
Linux 内核开发者为了让驱动开发工程师在内核中方便的添加自己的 I2C 设备驱动程序,更容易的在 linux 下驱动自己的 I2C 接口硬件,进而引入了 I2C 总线框架。与 Linux 下的 platform 虚拟总线不同的是,I2C 是实际的物理总线,所以 I2C 总线框架也是Linux 下总线、设备、驱动模型的产物。
本章我们来学习一下如何在 Linux 下的 I2C 总线框架,以及如何使用 I2C 总线框架编写一个 I2C 接口的外设驱动程序;本章重点是学习 Linux 下的 I2C 总线框架。

28.1 I2C& AP3216C简介
28.1.1 I2C简介
I2C是很常见的一种总线协议,I2C是NXP公司设计的,I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条是SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100Kb/S,快速模式下可以达到400Kb/S。I2C总线工作是按照一定的协议来运行的,接下来就看一下I2C协议。
I2C是支持多从机的,也就是一个I2C控制器下可以挂多个I2C从设备,这些不同的I2C从设备有不同的器件地址,这样I2C主控制器就可以通过I2C设备的器件地址访问指定的I2C设备了,一个I2C总线连接多个I2C设备如图28.1.1.1所示:
在这里插入图片描述

图28.1.1.1 I2C多个设备连接结构图
图28.1.1.1中SDA和SCL这两根线必须要接一个上拉电阻,一般是4.7K。其余的I2C从器件都挂接到SDA和SCL这两根线上,这样就可以通过SDA和SCL这两根线来访问多个I2C设备。
接下来看一下I2C协议有关的术语:
1、起始位
顾名思义,也就是I2C通信起始标志,通过这个起始位就可以告诉I2C从机,“我”要开始进行I2C通信了。在SCL为高电平的时候,SDA出现下降沿就表示为起始位,如图28.1.1.2所示:
在这里插入图片描述

图28.1.1.1.2 I2C通信起始位
2、停止位
停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL位高电平的时候,SDA出现上升沿就表示为停止位,如图28.1.1.3所示:
在这里插入图片描述

图28.1.1.3 I2C通信停止位
3、数据传输
I2C总线在数据传输的时候要保证在SCL高电平期间,SDA上的数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生,如图28.1.1.4所示:
在这里插入图片描述

图28.1.1.4 I2C数据传输
4、应答信号
当I2C主机发送完8位数据以后会将SDA设置为输入状态,等待I2C从机应答,也就是等待I2C从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将SDA拉低来表示发出应答信号,表示通信成功,否则表示通信失败。
5、I2C写时序
主机通过I2C总线与从机之间进行通信不外乎两个操作:写和读,I2C总线单字节写时序如图28.1.5所示:
在这里插入图片描述

图28.1.1.5 I2C写时序
图28.1.1.5就是I2C写时序,我们来看一下写时序的具体步骤:
1)、开始信号。
2)、发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个I2C器件。这是一个8位的数据,其中高7位是设备地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示这是一个写操作。
3)、 I2C器件地址后面跟着一个读写位,为0表示写操作,为1表示读操作。
4)、从机发送的ACK应答信号。
5)、重新发送开始信号。
6)、发送要写写入数据的寄存器地址。
7)、从机发送的ACK应答信号。
8)、发送要写入寄存器的数据。
9)、从机发送的ACK应答信号。
10)、停止信号。
6、I2C读时序
I2C总线单字节读时序如图28.1.1.6所示:
在这里插入图片描述

图28.1.1.6 I2C单字节读时序
I2C单字节读时序比写时序要复杂一点,读时序分为4大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是I2C从器件输出要读取的寄存器值,我们具体来看一下这步。
1)、主机发送起始信号。
2)、主机发送要读取的I2C从设备地址。
3)、读写控制位,因为是向I2C从设备发送数据,因此是写信号。
4)、从机发送的ACK应答信号。
5)、重新发送START信号。
6)、主机发送要读取的寄存器地址。
7)、从机发送的ACK应答信号。
8)、重新发送START信号。
9)、重新发送要读取的I2C从设备地址。
10)、读写控制位,这里是读信号,表示接下来是从I2C从设备里面读取数据。
11)、从机发送的ACK应答信号。
12)、从I2C器件里面读取到的数据。
13)、主机发出NO ACK信号,表示读取完成,不需要从机再发送ACK信号了。
14)、主机发出STOP信号,停止I2C通信。
7、I2C多字节读写时序
有时候我们需要读写多个字节,多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,其他的控制时序都是和单字节一样的。

28.1.2 RK3568 I2C简介
RK3568支持6个独立I2C: I2C0、I2C1、I2C2、I2C3、I2C4、I2C5。I2C控制器支持以下特性:
①兼容i2c总线
②AMBA APB从接口
③支持I2C总线主模式
④软件可编程时钟频率和传输速率高达400Kbit/sec
⑤支持7位和10位寻址模式
⑥中断或轮询驱动的多字节数据传输
⑦时钟拉伸和等待状态生成
⑧过滤掉SCL和SDA上的故障
关于RK3568 IIC更多详细的介绍,路径:开发板光盘 03、核心板资料核心板板载芯片资料Rockchip RK3568 TRM Part1 V1.1-20210301.pdf(RK3568参考手册1).pdf和Rockchip RK3568 TRM Part2 V1.1-20210301(RK3568参考手册2).pdf。
在这里插入图片描述

28.1.3 AP3216C简介
ATK-DLRK3568开发板上通过I2C5连接了一个三合一环境传感器:AP3216C,AP3216C是由敦南科技推出的一款传感器,其支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。该芯片可以通过IIC接口与主控制相连,并且支持中断,AP3216C的特点如下:
①、I2C接口,快速模式下波特率可以到400Kbit/S
②、多种工作模式选择:ALS、PS+IR、ALS+PS+IR、PD等等。
③、内建温度补偿电路。
④、宽工作温度范围(-30°C ~ +80°C)。
⑤、超小封装,4.1mm x 2.4mm x 1.35mm
⑥、环境光传感器具有16位分辨率。
⑦、接近传感器和红外传感器具有10位分辨率。
AP3216C常被用于手机、平板、导航设备等,其内置的接近传感器可以用于检测是否有物体接近,比如手机上用来检测耳朵是否接触听筒,如果检测到的话就表示正在打电话,手机就会关闭手机屏幕以省电。也可以使用环境光传感器检测光照强度,可以实现自动背光亮度调节。
AP3216C结构如图28.1.3.1所示:
在这里插入图片描述

图28.1.3.1 AP3216C结构图
AP3216的设备地址为0X1E,同几乎所有的I2C从器件一样,AP3216C内部也有一些寄存器,通过这些寄存器我们可以配置AP3216C的工作模式,并且读取相应的数据。AP3216C我们用的寄存器如表28.1.3.1所示:
寄存器地址 位 寄存器功能 描述
0X00 2:0 系统模式 000:掉电模式(默认)。
001:使能ALS。
010:使能PS+IR。
011:使能ALS+PS+IR。
100:软复位。
101:ALS单次模式。
110:PS+IR单次模式。
111:ALS+PS+IR单次模式。
0X0A 7 IR低位数据 0:IR&PS数据有效,1:无效
1:0 IR最低2位数据。
0X0B 7:0 IR高位数据 IR高8位数据。
0X0C 7:0 ALS低位数据 ALS低8位数据。
0X0D 7:0 ALS高位数据 ALS高8位数据。
0X0E 7 PS低位数据 0,物体在远离;1,物体在接近。
6 0,IR&PS数据有效;1,IR&PS数据无效
3:0 PS最低4位数据。
0X0F 7 PS高位数据 0,物体在远离;1,物体在接近。
6 0,IR&PS数据有效;1,IR&PS数据无效
5:0 PS最低6位数据。
表28.1.3.1 本章使用的AP3216C寄存器表
在表28.1.3.1中,0X00这个寄存器是模式控制寄存器,用来设置AP3216C的工作模式,一般开始先将其设置为0X04,也就是先软件复位一次AP3216C。接下来根据实际使用情况选择合适的工作模式,比如设置为0X03,也就是开启ALS+PS+IR。0X0A~0X0F这6个寄存器就是数据寄存器,保存着ALS、PS和IR这三个传感器获取到的数据值。如果同时打开ALS、PS和IR的读取间隔最少要112.5ms,因为AP3216C完成一次转换需要112.5ms。关于AP3216C的介绍就到这里,如果要想详细的研究此芯片的话,请大家自行查阅其数据手册。
28.2 Linux I2C总线框架简介
使用裸机的方式编写一个 I2C 器件的驱动程序,我们一般需要实现两部分:
①、I2C 主机驱动。
②、I2C 设备驱动。
I2C 主机驱动也就是 SoC 的 I2C 控制器对应的驱动程序,I2C 设备驱动其实就是挂在 I2C总线下的具体设备对应的驱动程序,例如 eeprom、触摸屏 IC、传感器 IC 等;对于主机驱动来说,一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动提供的 API 函数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想,因此 Linux 内核也将
I2C 驱动分为两部分。
Linux内核开发者为了让驱动开发工程师在内核中方便的添加自己的I2C设备驱动程序,方便大家更容易的在linux下驱动自己的I2C接口硬件,进而引入了I2C总线框架,我们一般也叫作I2C子系统,Linux下I2C子系统总体框架如下所示:
在这里插入图片描述

图28.2.3.1 I2C子系统框架图
从图28.2.3.1可以知道,I2C子系统分为三大组成部分:
1、I2C核心(I2C-core)
I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册、注销方法,I2C通信方法(algorithm)与具体硬件无关的代码,以及探测设备地址的上层代码等;
2、I2C总线驱动(I2C adapter)
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。I2C总线驱动由i2c_adapter和i2c_algorithm来描述。I2C适配器是SoC中内置i2c控制器的软件抽象,可以理解为他所代表的是一个I2C主机;
3、I2C设备驱动(I2C client driver)
包括两部分:设备的注册和驱动的注册。
I2C子系统帮助内核统一管理I2C设备,让驱动开发工程师在内核中可以更加容易地添加自己的I2C设备驱动程序。
28.2.1 I2C 总线驱动
首先来看一下I2C总线,在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用I2C总线即可。I2C总线驱动重点是I2C适配器(也就是SoC的I2C接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapter和i2c_algorithm,I2C子系统将SoC的I2C适配器(控制器)抽象成一个i2c_adapter结构体,i2c_adapter结构体定义在include/linux/i2c.h文件中,结构体内容如下:
示例代码28.2.1 i2c_adapter结构体

672 struct i2c_adapter {
673     struct module *owner;
674     unsigned int class;       /* classes to allow probing for */
675     const struct i2c_algorithm *algo; /* the algorithm to access the bus */
676     void *algo_data;
677
678     /* data fields that are valid for all devices   */
679     const struct i2c_lock_operations *lock_ops;
680     struct rt_mutex bus_lock;
681     struct rt_mutex mux_lock;
682
683     int timeout;            /* in jiffies */
684     int retries;
685     struct device dev;      /* the adapter device */
686
687     int nr;
688     char name[48];
689     struct completion dev_released;
690
691     struct mutex userspace_clients_lock;
692     struct list_head userspace_clients;
693
694     struct i2c_bus_recovery_info *bus_recovery_info;
695     const struct i2c_adapter_quirks *quirks;
696
697     struct irq_domain *host_notify_domain;
698 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

第 675 行,i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。
i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中,内容如下:
示例代码28.2.1.2 i2c_algorithm结构体

526 struct i2c_algorithm {
527     /*
528      * If an adapter algorithm can't do I2C-level access, set 
529      * master_xfer to NULL. If an adapter algorithm can do SMBus 
530      * access, set smbus_xfer. If set to NULL, the SMBus protocol is 
531      * simulated using common I2C messages.
532      *
533      * master_xfer should return the number of messages successfully
534      * processed, or a negative value on error
535      */
536     int (*master_xfer)(struct i2c_adapter *adap, 
struct i2c_msg *msgs,
537                int num);
538     int (*master_xfer_atomic)(struct i2c_adapter *adap,
539                    struct i2c_msg *msgs, int num);
540     int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
541               unsigned short flags, char read_write,
542               u8 command, int size, union i2c_smbus_data *data);
543     int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
544                  unsigned short flags, char read_write,
545                  u8 command, int size, union i2c_smbus_data *data);
546
547     /* To determine what the adapter supports */
548     u32 (*functionality)(struct i2c_adapter *adap);
549
550 #if IS_ENABLED(CONFIG_I2C_SLAVE)
551     int (*reg_slave)(struct i2c_client *client);
552     int (*unreg_slave)(struct i2c_client *client);
553 #endif
554 };

519 struct i2c_algorithm {
520     /* If an adapter algorithm can't do I2C-level access, set master_xfer
521        to NULL. If an adapter algorithm can do SMBus access, set
522        smbus_xfer. If set to NULL, the SMBus protocol is simulated
523        using common I2C messages */
524     /* master_xfer should return the number of messages successfully
525        processed, or a negative value on error */
526     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
527                int num);
528     int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
529                unsigned short flags, char read_write,
530                u8 command, int size, union i2c_smbus_data *data);
531
532     /* To determine what the adapter supports */
533     u32 (*functionality) (struct i2c_adapter *);
534
535 #if IS_ENABLED(CONFIG_I2C_SLAVE)
536     int (*reg_slave)(struct i2c_client *client);
537     int (*unreg_slave)(struct i2c_client *client);
538 #endif
539 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

第526行,master_xfer就是I2C适配器的传输函数,可以通过此函数来完成与IIC设备之间的通信。
第528行,smbus_xfer就是SMBUS总线的传输函数。smbus协议是从I2C协议的基础上发展而来的,他们之间有很大的相似度,SMBus与I2C总线之间在时序特性上存在一些差别,应用于移动PC和桌面PC系统中的低速率通讯。
综上所述,I2C总线驱动,或者说I2C适配器驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数。完成以后通过i2c_add_numbered_adapter或i2c_add_adapter这两个函数向I2C子系统注册设置好的i2c_adapter,这两个函数的原型如下:
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
这两个函数的区别在于i2c_add_adapter会动态分配一个总线编号,而i2c_add_numbered_adapter函数则指定一个静态的总线编号。函数参数和返回值含义如下:
adapter或adap:要添加到Linux内核中的i2c_adapter,也就是I2C适配器。
返回值:0,成功;负值,失败。
如果要删除I2C适配器的话使用i2c_del_adapter函数即可,函数原型如下:
void i2c_del_adapter(struct i2c_adapter * adap)
函数参数和返回值含义如下:
adap:要删除的I2C适配器。
返回值:无。
关于I2C的总线(控制器或适配器)驱动就讲解到这里,一般SoC的I2C总线驱动都是由半导体厂商编写的,比如RK3568的I2C适配器驱动RK官方已经编写好了,这个不需要用户去编写。因此I2C总线驱动对我们这些SoC使用者来说是被屏蔽掉的,我们只要专注于I2C设备驱动即可,除非你是在半导体公司上班,工作内容就是写I2C适配器驱动。
28.2.2 I2C总线设备
I2C设备驱动重点关注两个数据结构:i2c_client和i2c_driver,根据总线、设备和驱动模型,I2C总线上一小节已经讲了。还剩下设备和驱动,i2c_client用于描述I2C总线下的设备,i2c_driver则用于描述I2C总线下的设备驱动,类似于platform总线下的platform_device和platform_driver。
1、i2c_client结构体
i2c_client结构体定义在include/linux/i2c.h文件中,内容如下:
示例代码28.2.2.1 i2c_client结构体

328 struct i2c_client {
329     unsigned short flags;       /* div., see below      */
330     unsigned short addr;        /* chip address - NOTE: 7bit    */
331                     /* addresses are stored in the  */
332                     /* _LOWER_ 7 bits       */
333     char name[I2C_NAME_SIZE];
334     struct i2c_adapter *adapter;    /* the adapter we sit on    */
335     struct device dev;      /* the device structure     */
336     int init_irq;           /* irq set at initialization    */
337     int irq;            /* irq issued by device     */
338     struct list_head detected;
339 #if IS_ENABLED(CONFIG_I2C_SLAVE)
340     i2c_slave_cb_t slave_cb;    /* callback for slave mode  */
341 #endif
342 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一个I2C设备对应一个i2c_client结构体变量,系统每检测到一个I2C从设备就会给这个设备分配一个i2c_client。
2、i2c_driver结构体
i2c_driver类似platform_driver,是我们编写I2C设备驱动重点要处理的内容,i2c_driver结构体定义在include/linux/i2c.h文件中,内容如下:
示例代码28.2.2.2 i2c_driver结构体

267 struct i2c_driver {
268     unsigned int class;
269
270     /* Standard driver model interfaces */
271     int (*probe)(struct i2c_client *, const struct i2c_device_id *);
272     int (*remove)(struct i2c_client *);
273
274     /* New driver model interface to aid the seamless removal of the
275      * current probe()'s, more commonly unused than used second parameter.
276      */
277     int (*probe_new)(struct i2c_client *);
278
279     /* driver model interfaces that don't relate to enumeration  */
280     void (*shutdown)(struct i2c_client *);
281
282     /* Alert callback, for example for the SMBus alert protocol.
283      * The format and meaning of the data value depends on the protocol.
284      * For the SMBus alert protocol, there is a single bit of data passed
285      * as the alert response's low bit ("event flag").
286      * For the SMBus Host Notify protocol, the data corresponds to the
287      * 16-bit payload data reported by the slave device acting as master.
288      */
289     void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
290               unsigned int data);
291
292     /* a ioctl like command that can be used to perform specific functions
293      * with the device.
294      */
295     int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
296
297     struct device_driver driver;
298     const struct i2c_device_id *id_table;
299
300     /* Device detection callback for automatic device creation */
301     int (*detect)(struct i2c_client *, struct i2c_board_info *);
302     const unsigned short *address_list;
303     struct list_head clients;
304
305     bool disable_i2c_core_irq_mapping;
306 };
307 #define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

第271行,当I2C设备和驱动匹配成功以后probe函数就会执行,和platform驱动一样。
第297行,device_driver驱动结构体,如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容(compatible)属性。
第298行,id_table是传统的、未使用设备树的设备匹配ID表。
对于我们I2C设备驱动编写人来说,重点工作就是构建i2c_driver,构建完成以后需要向I2C子系统注册这个i2c_driver。i2c_driver注册函数为int i2c_register_driver,此函数原型如下:
int i2c_register_driver(struct module *owner,
struct i2c_driver *driver)
函数参数和返回值含义如下:
owner:一般为THIS_MODULE。
driver:要注册的i2c_driver。
返回值:0,成功;负值,失败。
另外i2c_add_driver也常常用于注册i2c_driver,i2c_add_driver是一个宏,定义如下:
示例代码28.2.2.3 i2c_add_driver宏

806     #define i2c_add_driver(driver) \
807         i2c_register_driver(THIS_MODULE, driver)
  • 1
  • 2

i2c_add_driver就是对i2c_register_driver做了一个简单的封装,只有一个参数,就是要注册的i2c_driver。
注销I2C设备驱动的时候需要将前面注册的i2c_driver从I2C子系统中注销掉,需要用到i2c_del_driver函数,此函数原型如下:
void i2c_del_driver(struct i2c_driver *driver)
函数参数和返回值含义如下:
driver:要注销的i2c_driver。
返回值:无。
i2c_driver的注册示例代码如下:
示例代码28.2.2.4 i2c_driver注册流程

1  /* i2c驱动的probe函数 */
2  static int xxx_probe(struct i2c_client *client, 
const struct i2c_device_id *id)
3  {
4   	/* 函数具体程序 */
5   	return 0;
6  }
7  
8  /* i2c驱动的remove函数 */
9  static int ap3216c_remove(struct i2c_client *client)
10 {
11  	/* 函数具体程序 */
12  	return 0;
13 }
14 
15 /* 传统匹配方式ID列表 */
16 static const struct i2c_device_id xxx_id[] = {
17  	{"xxx", 0},  
18  	{}
19 };
20 
21 /* 设备树匹配列表 */
22 static const struct of_device_id xxx_of_match[] = {
23  	{ .compatible = "xxx" },
24  	{ /* Sentinel */ }
25 };
26 
27 /* i2c驱动结构体 */ 
28 static struct i2c_driver xxx_driver = {
29  	.probe = xxx_probe,
30  	.remove = xxx_remove,
31  	.driver = {
32          	.owner = THIS_MODULE,
33          	.name = "xxx",
34          	.of_match_table = xxx_of_match, 
35         	},
36  		.id_table = xxx_id,
37 		};
38         
39 /* 驱动入口函数 */
40 static int __init xxx_init(void)
41 {
42  	int ret = 0;
43 
44  	ret = i2c_add_driver(&xxx_driver);
45  	return ret;
46 }
47 
48 /* 驱动出口函数 */
49 static void __exit xxx_exit(void)
50 {
51  	i2c_del_driver(&xxx_driver);
52 }
53 
54 module_init(xxx_init);
55 module_exit(xxx_exit);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
第16~19行,i2c_device_id,无设备树的时候匹配ID表。
第22~25行,of_device_id,设备树所使用的匹配表。
第28~37行,i2c_driver,当I2C设备和I2C驱动匹配成功以后probe函数就会执行,这些和platform驱动一样,probe函数里面基本就是标准的字符设备驱动那一套了。
  • 1
  • 2
  • 3

28.2.3 I2C 设备和驱动匹配过程
I2C设备和驱动的匹配过程是由I2C子系统核心层来完成的,drivers/i2c/i2c-core-base.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数,比如前面讲过的:
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总线的数据结构为i2c_bus_type,定义在drivers/i2c/i2c-core-base.c文件,i2c_bus_type内容如下:
示例代码28.2.3.1 i2c_bus_type结构体

505 struct bus_type i2c_bus_type = {
506     .name     	= "i2c",
507     .match    	= i2c_device_match,
508     .probe    	= i2c_device_probe,
509     .remove   	= i2c_device_remove,
510     .shutdown 	= i2c_device_shutdown,
511 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

.match就是I2C总线的设备和驱动匹配函数,在这里就是i2c_device_match这个函数,此函数内容如下:
示例代码28.2.3.2 i2c_device_match函数
103 static int i2c_device_match(struct device *dev,
struct device_driver *drv)

104  {
105      struct i2c_client   *client = i2c_verify_client(dev);
106      struct i2c_driver   *driver;
107
108 
109      /* Attempt an OF style match */
110     if (i2c_of_match_device(drv->of_match_table, client))
111         return 1;
112
113     /* Then ACPI style match */
114     if (acpi_driver_match_device(dev, drv))
115         return 1;
116
117     driver = to_i2c_driver(drv);
118
119     /* Finally an I2C match */
120     if (i2c_match_id(driver->id_table, client))
121         return 1;
122
123     return 0;
124 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

第110行,i2c_of_match_device函数用于完成设备树中定义的设备与驱动匹配过程。比较I2C设备节点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示I2C设备和驱动匹配。
第114行,acpi_driver_match_device函数用于ACPI形式的匹配。
第120行,i2c_match_id函数用于传统的、无设备树的I2C设备和驱动匹配过程。比较I2C设备名字和i2c_device_id的name字段是否相等,相等的话就说明I2C设备和驱动匹配成功。
28.3 RK3568 I2C 适配器驱动分析
上一小节我们讲解了Linux下的I2C子系统,重点分为I2C适配器驱动和I2C设备驱动,其中I2C适配器驱动就是SoC的I2C控制器驱动。I2C设备驱动是需要用户根据不同的I2C从设备去编写,而I2C适配器驱动一般都是SoC厂商去编写的,比如RK就已经提供了RK3568的I2C适配器驱动程序。在内核源码arch/arm64/boot/dts/rockchip/rk3568.dtsi设备树文件中找到RK3568的I2C控制器节点,节点内容如下所示:
示例代码41.3.1 I2C1控制器节点

2900    i2c1: i2c@fe5a0000 {
2901            compatible = "rockchip,rk3399-i2c";
2902            reg = <0x0 0xfe5a0000 0x0 0x1000>;
2903            clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
2904            clock-names = "i2c", "pclk";
2905            interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
2906            pinctrl-names = "default";
2907            pinctrl-0 = <&i2c1_xfer>;
2908            #address-cells = <1>;
2909            #size-cells = <0>;
2910            status = "disabled";
2911    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

重点关注i2c1节点的compatible属性值,因为通过compatible属性值可以在Linux源码里面找到对应的驱动文件。这里i2c1节点的compatible属性值“rockchip,rk3399-i2c”,在Linux源码中搜索这个字符串即可找到对应的驱动文件。RK3568的I2C适配器驱动驱动文件为drivers/i2c/busses/i2c-rk3x.c,在此文件中有如下内容:
示例代码28.3.2 i2c-rk3x.c文件代码段

1258 static const struct of_device_id rk3x_i2c_match[] = {
1259         {
1260                 .compatible = "rockchip,rv1108-i2c",
1261                 .data = &rv1108_soc_data
1262         },
1263         {
1264                 .compatible = "rockchip,rv1126-i2c",
1265                 .data = &rv1126_soc_data
1266         },
1267         {
1268                 .compatible = "rockchip,rk3066-i2c",
1269                 .data = &rk3066_soc_data
1270         },
1271         {
1272                 .compatible = "rockchip,rk3188-i2c",
1273                 .data = &rk3188_soc_data
1274         },
1275         {
1276                 .compatible = "rockchip,rk3228-i2c",
1277                 .data = &rk3228_soc_data
1278         },
1279         {
1280                 .compatible = "rockchip,rk3288-i2c",
1281                 .data = &rk3288_soc_data
1282         },
1283         {
1284                 .compatible = "rockchip,rk3399-i2c",
1285                 .data = &rk3399_soc_data
1286         },
1287         {},
1288 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

从示例代码28.3.2可以看出,RK3568的I2C适配器驱动是个标准的platform驱动,由此可以看出,虽然I2C总线为别的设备提供了一种总线驱动框架,但是I2C适配器却是platform驱动。就像你的部门老大是你的领导,你是他的下属,但是放到整个公司,你的部门老大却也是老板的下属。
当设备和驱动匹配成功以后rk3x_i2c_probe函数就会执行,rk3x_i2c_probe函数就会完成I2C适配器初始化工作。
rk3x_i2c_probe函数内容如下所示(有省略):
示例代码28.3.3 rk3x_i2c_probe函数代码段

1291 static int rk3x_i2c_probe(struct platform_device *pdev)
1292 {
1293         struct device_node *np = pdev->dev.of_node;
1294         const struct of_device_id *match;
1295         struct rk3x_i2c *i2c;
1296         struct resource *mem;
1297         int ret = 0;
1298         u32 value;
1299         int irq;
1300         unsigned long clk_rate;
1301 
1302         i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
1303         if (!i2c)
1304                 return -ENOMEM;
1305 
1306         match = of_match_node(rk3x_i2c_match, np);
1307         i2c->soc_data = match->data;
1308 
1309         /* use common interface to get I2C timing properties */
1310         i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
1311 
1312         strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
1313         i2c->adap.owner = THIS_MODULE;
1314         i2c->adap.algo = &rk3x_i2c_algorithm;
1315         i2c->adap.retries = 3;
1316         i2c->adap.dev.of_node = np;
1317         i2c->adap.algo_data = i2c;
1318         i2c->adap.dev.parent = &pdev->dev;
1319 
1320         i2c->dev = &pdev->dev;
1321 
1322         spin_lock_init(&i2c->lock);
1323         init_waitqueue_head(&i2c->wait);
1324 
1325         i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
1326         i2c->i2c_restart_nb.priority = 128;
1327         ret = register_pre_restart_handler(&i2c->i2c_restart_nb);
1328         if (ret) {
1329                 dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
1330                 return ret;
1331         }
1332 
1333         mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1334         i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
1335         if (IS_ERR(i2c->regs))
1336                 return PTR_ERR(i2c->regs);
1337 
......
1376 
1377         /* IRQ setup */
1378         irq = platform_get_irq(pdev, 0);
1379         if (irq < 0) {
1380                 dev_err(&pdev->dev, "cannot find rk3x IRQ\n");
1381                 return irq;
1382         }
1383 
1384         ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
1385                                0, dev_name(&pdev->dev), i2c);
1386         if (ret < 0) {
1387                 dev_err(&pdev->dev, "cannot request IRQ\n");
1388                 return ret;
1389         }
1390 
1391         platform_set_drvdata(pdev, i2c);
1392 
1393         if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
1394                 /* Only one clock to use for bus clock and peripheral clock */
1395                 i2c->clk = devm_clk_get(&pdev->dev, NULL);
1396                 i2c->pclk = i2c->clk;
1397         } else {
1398                 i2c->clk = devm_clk_get(&pdev->dev, "i2c");
1399                 i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
1400         }
1401 
1402         if (IS_ERR(i2c->clk)) {
1403                 ret = PTR_ERR(i2c->clk);
1404                 if (ret != -EPROBE_DEFER)
1405                         dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
1406                 return ret;
1407         }
1408         if (IS_ERR(i2c->pclk)) {
1409                 ret = PTR_ERR(i2c->pclk);
1410                 if (ret != -EPROBE_DEFER)
1411                         dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
1412                 return ret;
1413         }
1414 
1415         ret = clk_prepare(i2c->clk);
1416         if (ret < 0) {
1417                 dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
1418                 return ret;
1419         }
1420         ret = clk_prepare(i2c->pclk);
1421         if (ret < 0) {
1422                 dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
1423                 goto err_clk;
1424         }
1425 
1426         i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
1427         ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
1428         if (ret != 0) {
1429                 dev_err(&pdev->dev, "Unable to register clock notifier\n");
1430                 goto err_pclk;
1431         }
1432 
1433         clk_rate = clk_get_rate(i2c->clk);
1434         rk3x_i2c_adapt_div(i2c, clk_rate);
1435 
1436         ret = i2c_add_adapter(&i2c->adap);
1437         if (ret < 0)
......
1449 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
第1302行,RK使用rk3x_i2c结构体来表示RK系列SOC的I2C控制器,这里使用devm_kzalloc函数来申请内存。
  • 1

第1310行,调用drivers/i2c/i2c-core-base.c下的i2c_parse_fw_timings函数设置I2C频率,如果不设置“clock-frequency”则使用默认值100KHZ,如果设备树节点设置了“clock-frequency”属性的话 I2C 频率就使用 clock-frequency 属性值。
第1314行,rk3x_i2c结构体有个adap的成员变量,adap就是i2c_adapter,这里初始化i2c_adapter。同时设置i2c_adapter的algo成员变量为rk3x_i2c_algorithm,也就是设置i2c_algorithm。
第1333~1334行,调用platform_get_resource函数从设备树中获取I2C1控制器寄存器物理基地址,也就是0xfe5a0000。获取到寄存器基地址以后使用devm_ioremap_resource函数对其进行内存映射,得到可以在Linux中使用的虚拟地址。
第1378行,调用platform_get_irq函数获取中断号。
第1384行,注册I2C控制器的中断。
第1436行,调用i2c_add_adapter函数向Linux内核注册i2c_adapter。
rk3x_i2c_probe函数主要的工作就是一下两点:
①、初始化 i2c_adapter,设置 i2c_algorithm 为 rk3x_i2c_algorithm,最后向 Linux 内核注册i2c_adapter。
②、初始化 I2C1 控制器的相关寄存器。rk3x_i2c_algorithm包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer,rk3x_i2c_algorithm结构体定义如下:
示例代码28.3.4 rk3x_i2c_algorithm结构体

1218 static const struct i2c_algorithm rk3x_i2c_algorithm = {
1219         .master_xfer            = rk3x_i2c_xfer,
1220         .functionality          = rk3x_i2c_func,
1221 };
  • 1
  • 2
  • 3
  • 4

我们先来看一下. functionality,functionality用于返回此I2C适配器支持什么样的通信协议,在这里functionality就是rk3x_i2c_func函数,rk3x_i2c_func函数内容如下:
示例代码28.3.5 rk3x_i2c_func函数

1213 static u32 rk3x_i2c_func(struct i2c_adapter *adap)
1214 {
1215         return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
1216 }
  • 1
  • 2
  • 3
  • 4
重点来看一下rk3x_i2c_xfer函数,因为最终就是通过此函数来完成与I2C设备通信的,此函数内容如下:
  • 1

示例代码28.3.6 rk3x_i2c_xfer函数

1071 static int rk3x_i2c_xfer(struct i2c_adapter *adap,
1072                          struct i2c_msg *msgs, int num)
1073 {
1074         struct rk3x_i2c *i2c = (struct rk3x_i2c *)adap->algo_data;
1075         unsigned long timeout, flags;
1076         u32 val;
1077         int ret = 0;
1078         int i;
1079 
1080         if (i2c->suspended)
1081                 return -EACCES;
1082 
1083         spin_lock_irqsave(&i2c->lock, flags);
1084 
1085         clk_enable(i2c->clk);
1086         clk_enable(i2c->pclk);
1087 
1088         i2c->is_last_msg = false;
1089 
1090         /*
1091          * Process msgs. We can handle more than one message at once (see
1092          * rk3x_i2c_setup()).
1093          */
1094         for (i = 0; i < num; i += ret) {
1095                 ret = rk3x_i2c_setup(i2c, msgs + i, num - i);
1096 
1097                 if (ret < 0) {
1098                         dev_err(i2c->dev, "rk3x_i2c_setup() failed\n");
1099                         break;
1100                 }
1101 
1102                 if (i + ret >= num)
1103                         i2c->is_last_msg = true;
1104 
1105                 rk3x_i2c_start(i2c);
1106 
1107                 spin_unlock_irqrestore(&i2c->lock, flags);
1108 
1109                 timeout = wait_event_timeout(i2c->wait, !i2c->busy,
1110                                              msecs_to_jiffies(WAIT_TIMEOUT));
1111 
1112                 spin_lock_irqsave(&i2c->lock, flags);
1113 
1114                 if (timeout == 0) {
1115                         dev_err(i2c->dev, "timeout, ipd: 0x%02x, state: %d\n",
1116                                 i2c_readl(i2c, REG_IPD), i2c->state);
1117 
1118                         /* Force a STOP condition without interrupt */
1119                         rk3x_i2c_disable_irq(i2c);
1120                         val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK;
1121                         val |= REG_CON_EN | REG_CON_STOP;
1122                         i2c_writel(i2c, val, REG_CON);
1123 
1124                         i2c->state = STATE_IDLE;
1125 
1126                         ret = -ETIMEDOUT;
1127                         break;
1128                 }
1129 
1130                 if (i2c->error) {
1131                         ret = i2c->error;
1132                         break;
1133                 }
1134         }
1135 
1136         rk3x_i2c_disable_irq(i2c);
1137         rk3x_i2c_disable(i2c);
1138 
1139         clk_disable(i2c->pclk);
1140         clk_disable(i2c->clk);
1141 
1142         spin_unlock_irqrestore(&i2c->lock, flags);
1143 
1144         return ret < 0 ? ret : num;
1145 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
第1095行调用 rk3x_i2c_setup 函数来进行 I2C 相关的配置和准备工作。如果返回值 ret 小于 0,表示设置失败,函数会打印错误消息并跳出循环。
第1105行,启动函数来启动 I2C 传输,具体内容这里就不分析了,大家感兴趣可以自己阅读代码。
  • 1
  • 2
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/664422
推荐阅读
相关标签
  

闽ICP备14008679号