以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考博客
I2C总线驱动框架详解
linux内核I2C子系统详解
一、I2C总线的物理特征
这部分内容的简介可见博客:SPI、I2C、UART(即串口)三种串行总线详解。
(1)通信线数目:两根,即SCL、SDA。
(2)通信的特点:同步、串行、电平、低速(几百k)、近距离。
(3)总线式结构:支持多个设备挂接在同一条总线上。
(4)主从式结构:通信双方必须一个为主设备,一个为从设备,主设备掌握通信的主动权,从设备按照主设备的节奏被动响应。每个从设备在总线中有唯一的地址,主设备通过从地址找到自己要通信的从设备(本质是广播)。
(5)I2C总线的用途:用于主SoC和外围设备之间的通信。常见的各种物联网传感器芯片(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)均使用I2C接口和主SoC进行连接。
(6)I2C总线的最大优势:支持在总线上扩展多个外围设备。
(7)举例,电容触摸屏芯片的I2C接口:电容触摸屏芯片的多个引脚构成2个接口。其中一个接口是I2C接口,电容触摸屏芯片通过该接口与主SoC连接,此时触摸屏作为从设备。主SoC通过该接口初始化及控制电容触摸屏芯片,而芯片通过该接口向SoC汇报触摸事件的信息。我们使用电容触摸屏时重点关注的是这个接口。另一个接口是电容触摸屏的管理接口,电容触摸屏芯片通过该接口来控制触摸板硬件。该接口是电容触摸屏公司关心的,触摸屏芯片内部固件编程要处理这部分。我们使用电容触摸屏的人不需要关心这些内容。
二、I2C总线设备的驱动框架
I2C总线设备的驱动框架,或者说“I2C子系统”,其设计目的是让驱动开发者可以在内核中方便地添加自己的I2C设备的驱动程序,从而可以更容易地在linux下驱动I2C设备。
I2C总线设备的驱动框架内部关系如下:
1、驱动框架的组成部分
(1)I2C设备驱动层(I2C_client,I2C_driver)
和具体I2C接口的外设相关,每种外设都有自己的专属I2C驱动代码。
包括两部分:I2C从设备的注册、I2C设备驱动的注册。
(2)I2C核心层(I2C_core)
I2C核心提供了I2C总线驱动和I2C设备驱动的注册、匹配与注销方法,I2C通信方法,上层中与具体硬件无关的代码,以及探测设备检测设备地址的上层代码等。换言之,管理I2C驱动和I2C设备的注册、匹配,实现I2C的通信方法,是对I2C通信的抽象框架,与具体硬件无关。
(3)I2C总线驱动层(I2C_adapter、I2C_algorithm)
或者说I2C适配器层。这里说的I2C适配器,也就是I2C主设备,对应着Soc的I2C控制器,把I2C控制器看做一个设备,实现I2C控制器的驱动代码,和具体的Soc相关。
I2C总线驱动是I2C适配器的软件实现,或者说是SoC中内置的I2C控制器的软件抽象,可以将其理解为I2C主设备,它提供了I2C适配器与从设备间数据通信的功能。
I2C总线驱动用 I2C_adapter 和 I2C_algorithm 来描述I2C适配器。
2、I2C相关的驱动文件
源码中与I2C相关的驱动均位于drivers/i2c目录,包括的内容如下:
(1)I2C设备驱动层相关的文件
x210开发板的电容触摸屏gslX680采用I2C接口,因此以这个I2C设备为例进行分析。其对应的驱动源代码文件是x210_kernel\drivers\input\touchscreen\gslX680.c文件。它由触摸屏IC原厂工程师提供,代码中涉及到很多触摸屏专业方面的知识,但无需理会。
gslX680.c文件进行了I2C设备驱动的注册,相应的I2C设备注册是在mach-x210.c文件中。
(2)I2C核心层相关的文件
I2C核心层相关的文件是i2c-core.c文件,它由内核开发者提供,和具体的硬件操作无关。
(3)I2C总线驱动层相关的文件
algos目录:存放的是I2C通信的算法(主要是时序等内容)。
busses目录:存放的是各种已经编写好的、将来要向i2c核心层注册的适配器文件。
我们主要分析drivers\i2c\busses\i2c-s3c2410.c文件。
3、驱动实现的两种方式
I2C驱动有两种实现方法:
(1)第一种方式对应于drivers/i2c/i2c-dev.c文件
这种方法只是对主设备(一般是SoC中内置的I2C控制器)的I2C基本操作进行封装,并且向应用层提供相应的操作接口,应用层需要编写代码来实现对从设备的控制和操作。
这种I2C驱动,只是给应用层提供了可以访问从设备的接口,本身没有对硬件做任何操作。应用需要实现对硬件的操作,因此写应用的人必须对硬件非常了解。其实实这就相当于在应用层完成传统的驱动的工作内容,所以这种I2C驱动又叫做“应用层驱动”。
它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用修改驱动,只需要修改应用就可以实现对各种设备的驱动。这种驱动在驱动层很简单,但并不主流,这里不进行分析。
(2)第二种方式将I2C驱动的所有代码都放在驱动层实现(直接向应用层提供最终结果)
这种实现方法中,应用层不需要知道这里面有I2C存在。比如电容式触摸屏驱动,直接向应用层提供了/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到I2C。这种实现方法是我们后续分析的重点。
4、相关的结构体
(1)struct i2c_adapter(I2C适配器)
struct i2c_adapter结构体用来描述一个 I2C适配器,在SoC中指的就是I2C控制器。
当向I2C核心层注册一个I2C适配器时,就需要提供这样的一个结构体变量,换另外一颗SoC则需要修改该结构体变量。
struct i2c_adapter {struct module *owner; // 所有者unsigned int id;unsigned int class; // 该适配器支持的从设备的类型const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法void *algo_data;/* data fields that are valid for all devices */struct rt_mutex bus_lock;int timeout; // 超时时间int retries;struct device dev; // 该适配器设备对应的deviceint nr; // 适配器的编号char name[48]; // 适配器的名字struct completion dev_released;//用来挂接(与适配器匹配成功的)从设备i2c_client的一个链表头struct list_head userspace_clients; };
(2)struct i2c_algorithm结构体
struct i2c_algorithm结构体用来描述主设备和从设备通信的算法(主要是时序等内容),它与主设备、从设备都有关系。在构建i2c_adapter结构体变量的时候会去填充这个元素。
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality) (struct i2c_adapter *); };
注意,smbus协议是从I2C协议的基础上发展而来的,他们之间有很大的相似度,SMBus与I2C总线之间在时序特性上存在一些差别,应用于移动PC和桌面PC系统中的低速率通讯。
(3)struct i2c_client结构体
该结构体用来描述一个I2C从设备。
struct i2c_client { // 用来描述一个i2c从设备unsigned short flags; // 描述i2c从设备特性的标志位unsigned short addr; // i2c 从设备的地址char name[I2C_NAME_SIZE]; // i2c从设备的名字struct i2c_adapter *adapter; // 指向与从设备匹配成功的适配器struct i2c_driver *driver; // 指向与从设备匹配成功的设备驱动struct device dev; // 该从设备对应的deviceint irq; // 从设备的中断引脚struct list_head detected; // 作为一个链表节点挂接到(与它匹配成功的)i2c_driver 相应的链表头上 };
(4)struct i2c_driver结构体
该结构体代表一个I2C从设备的驱动。
struct i2c_driver { // 代表一个i2c设备驱动unsigned int class; // i2c设备驱动所支持的i2c设备的类型/* Notifies the driver that a new bus has appeared or is about to be* removed. You should avoid using this if you can, it will probably* be removed in a near future.*/int (*attach_adapter)(struct i2c_adapter *); // 用来匹配适配器的函数int (*detach_adapter)(struct i2c_adapter *);/* Standard driver model interfaces */ // 设备驱动层的probe函数int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); // 设备驱动层卸载函数/* driver model interfaces that don't relate to enumeration */void (*shutdown)(struct i2c_client *);int (*suspend)(struct i2c_client *, pm_message_t mesg);int (*resume)(struct i2c_client *);/* Alert callback, for example for the SMBus alert protocol.* The format and meaning of the data value depends on the protocol.* For the SMBus alert protocol, there is a single bit of data passed* as the alert response's low bit ("event flag").*/void (*alert)(struct i2c_client *, unsigned int data);/* a ioctl like command that can be used to perform specific functions* with the device.*/int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver; // 该i2c设备驱动所对应的device_driverconst struct i2c_device_id *id_table; //设备驱动层用来匹配设备的id_table/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list; // 该设备驱动支持的所有次设备的地址数组// 用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头struct list_head clients; };
(5)struct i2c_board_info结构体
这个结构体用来描述板子上的一个i2c设备的信息,一般用来对struct i2c_client结构体变量的成员进行赋值?
struct i2c_board_info { // 这个结构体是用来描述板子上的一个i2c设备的信息char type[I2C_NAME_SIZE]; // i2c 设备的名字,用来初始化i2c_client.nameunsigned short flags; // 用来初始化i2c_client.flagsunsigned short addr; // 用来初始化 i2c_client.addrvoid *platform_data; // 用来初始化 i2c_client.dev.platform_datastruct dev_archdata *archdata; // 用来初始化i2c_client.dev.archdata #ifdef CONFIG_OFstruct device_node *of_node; #endifint irq; // 用来初始化i2c_client.irq };
(6)struct i2c_devinfo结构体
struct i2c_devinfo {struct list_head list; // 作为一个链表节点挂接到__i2c_board_list 链表上去int busnum; // 适配器的编号struct i2c_board_info board_info; // 内置的i2c_board_info 结构体 };