LV10-11-I2C驱动-01-Linux中的I2C驱动

本文主要是Linux中I2C驱动模型的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
Linux开发板 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2
u-boot 2013.01
点击查看本文参考资料
参考方向参考原文
------
点击查看相关文件下载
文件下载链接
------

学习 platform 总线的时候就说过, platform 是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C 总线即可。

一、 I2C 原理

1. I2C 协议及原理

这一部分可以看 学习单片机的时候的相关笔记。

2. 几个概念

00_SOC架构图

SOC 芯片平台的外设分为:

  • 一级外设:外设控制器集成在 SOC 芯片内部。
  • 二级外设:外设控制器由另一块芯片负责,通过一些通讯总线与 SOC 芯片相连。

拿 I2C 来说, SOC 中的 I2C 控制器就是一级外设,外部的 MPU6050 等 I2C 器件就属于二级外设,这些 I2C 器件挂在一级外上。 I2C 控制器也被称为 I2C 适配器。

3. Linux 内核中的 I2C

3.1 四个对象

在 linux 内核中, I2C 驱动如下图所示:

image-20220903201758352

用户空间的就是用户访问 I2C 设备的一些接口,我们目前不用关心这个。

内核空间将 I2C 分为三层:

  • (1) I2C 从设备驱动层

就是上图中的 driver 驱动层,也简称为 I2C 设备驱动层,里边都是一些挂在 I2C 上的设备的驱动程序。这里边的 driver 和 client 都是需要我们自己来实现的。

这一层是挂载在I2C总线上的二级外设的驱动,也称客户(client)驱动,实现对二级外设的各种操作,二级外设的几乎所有操作全部依赖于对其自身内部寄存器的读写,对这些二级外设寄存器的读写又依赖于I2C总线的发送和接收。

  • (2) I2C 核心层

主要是承上启下,为 I2C 从设备驱动和 I2C 总线驱动开发提供接口,为 I2C 设备驱动层提供管理多个 i2c_driver 、 i2c_client 对象的数据结构,为 I2C 总线驱动层提供多个 i2c_algorithm 、 i2c_adapter 对象的数据结构。完成它们之间的匹配。

  • (3) I2C 总线驱动层

包括上图中的访问抽象层和硬件实现控制层,这一层是 I2C 总线自身控制器的驱动程序,每个 I2C 适配器都会有自己的驱动程序。一般 SOC 芯片都会提供多个 I2C 总线控制器,也叫I2C适配器( I2C Adaptor),每个 I2C 总线控制器提供一组 I2C 总线,每一组 I2C 总线被称为一个 I2C通道,这个通道会提供一组 SCL 时钟线和 SDA 数据线,在这两根线上会挂载不同的 I2C 设备。

Linux 内核里将 I2C 总线控制器叫做适配器( adapter ),适配器驱动主要工作就是提供通过本组 I2C 总线与二级外设进行数据传输的接口,每个二级外设驱动里必须能够获得其对应的 adapter 对象才能实现数据传输。

这一层的驱动会由 SOC 开发商在第一次移植的时候完成编写,一般我们是不需要编写的。这一层的驱动也会分两个模块编写,一个是 I2C_Adapter 模块,就很类似于 device 模块(因为总线控制器也就相当于一个设备),另一个是 Algorithm 算法模块,在这里会实现 I2C 数据的传输。

我们在进行 I2C 驱动编写的时候,需要编写两个内核模块,一个是 driver 模块,另一个是 client 模块(表示从设备),类比 platform 总线的话,这里的 Client 模块就相当于 platform 总线中的 device 设备模块。

  • I2C 的 driver 模块:提供二级外设(也就是从设备)驱动程序的逻辑代码。

  • I2C 的 client 模块:提供二级外设的一些资源信息。

经过上边的分析,在一个 I2C 驱动中,会有四个模块: client 、 driver 、 I2C_Adapter 和 Algorithm 这四个模块,其中 client 和 I2C_Adapter 就相当于设备模块, driver 和 Algorithm 是驱动模块。

3.2 四个对象关系

匹配的时候, client 模块与 driver 模块匹配,一个 driver 可以匹配多个 client , I2C_Adapter 和 Algorithm 匹配,一个 Algorithm 可以匹配多个 I2C_Adapter ,然后 client 还需要与 I2C_Adapter 模块进行一个一对一的匹配,这样才能完成数据的传输,他们四者之间的关系如下:

image-20220903202953443
  • i2c_client:提供i2c设备的资源信息
  • i2c_driver:提供i2c设备的驱动
  • i2c_adapter:提供i2c适配器的驱动
  • i2c_algorithm:提供i2c的通信方法

4. I2C 在系统的体现

我们进入根文件系统的 /sys/bus 目录,会发现有 i2c 的一个目录:

image-20220903204424953

我们进入这个目录,查看一下目录下的文件:

image-20220903204512981

会发现,目录的结构与我们之前学习 platform 总线的时候是很类似的。

二、基本数据结构

1. struct bus_type

Linux 系统内核使用 bus_type 结构体表示总线,这个结构体定义在 linux 内核源码的下边这个文件中:

1
include/linux/device.h

我们打开这个文件,找到 struct bus_type 结构体如下:

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
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);
int (*offline)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

struct iommu_ops *iommu_ops;

struct subsys_private *p;
struct lock_class_key lock_key;
};

成员中 match 指向的函数很重要,此函数指针指向的函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。 match 函数有两个参数: dev 和 drv ,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

2. I2C 总线相关结构体

2.1 总线定义

I2C 总线是 struct bus_type 定义的一个结构体变量,它定义在 linux 内核源码的下边这个文件中:

1
drivers/i2c/i2c-core.c

我们打开这个文件,可以看到这个结构体变量的定义如下:

1
2
3
4
5
6
7
8
9
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);

I2C 总线对应着 linux 根文件系统的 /sys/bus 下的一条总线,这个 I2C 总线结构体管理着 I2C 设备与 I2C 驱动的匹配,删除等操作, I2C 总线会调用 i2c_device_match 函数看 I2C 设备和 I2C 驱动是否匹配,如果匹配就调用 i2c_device_probe 函数,进而调用 I2C 驱动的 probe 函数。

2.2 匹配函数

I2C 匹配函数定义在 linux 内核源码的下边这个文件中:

1
drivers/i2c/i2c-core.c

我们打开这文件可以看到这个函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;

if (!client)
return 0;

/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;

/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;

return 0;
}

从函数视实现中,我们可以看到,驱动和设备的匹配有三种方法 :

  • OF 类型的匹配,也就是设备树采用的匹配方式

of_driver_match_device 函数定义在 linux 内核源码的下边这个文件中:

1
include/linux/of_device.h
  • ACPI 匹配方式

没怎么使用过,后续使用了再补充笔记。

  • id_table 匹配

2.3 “总线设备”

这里的总线设备,指的就是 I2C 控制器设备。

2.3.1 struct i2c_adapter

Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter ,这个结构体定义在 linux 内核源码的这个文件中:

1
include/linux/i2c.h

我们打开这个文件,可以看到这个结构体及其成员定义如下:

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
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;

/* data fields that are valid for all devices */
struct rt_mutex bus_lock;

int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */

int nr;
char name[48];
struct completion dev_released;

struct mutex userspace_clients_lock;
struct list_head userspace_clients;

struct i2c_bus_recovery_info *bus_recovery_info;
};

【成员介绍】

  • algo : struct i2c_algorithm * 类型,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。

2.4 “总线驱动”

总线驱动指的就是 I2C 控制器的驱动程序。

2.4.1 struct i2c_algorithm

这个结构体定义在 linux 内核源码的这个文件中:

1
include/linux/i2c.h

我们打开这个文件,可以看到这个结构体及其成员定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, 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 *);
};

【成员介绍】

  • master_xfer :函数指针,指向一个 I2C 适配器的传输函数,可以通过此函数来完成与 I2C 设备之间的通信。
  • smbus_xfer :函数指针,指向一个 SMBUS 总线的传输函数。

2.4.2 总线驱动的功能

I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter 或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter 。

3. I2C 设备驱动相关结构体

3.1 struct i2c_driver

i2c_driver 结构体表示 I2C 设备驱动,这个结构体定义在 linux 内核源码的这个文件中:

1
include/linux/i2c.h

我们打开这个文件,可以看到这个结构体及其成员定义如下:

1
2
3
4
5
6
7
8
9
10
struct i2c_driver {
// ... ...
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
// ... ...
struct device_driver driver;
const struct i2c_device_id *id_table;
// ... ...
};

【成员说明】

  • probe :函数指针,在 i2c_client 与 i2c_driver 匹配成功后执行该函数指针指向的函数。
  • remove :函数指针,在取消 i2c_client 与 i2c_driver 匹配绑定后后执行该函数指针指向的函数。
  • driver : struct device_driver 类型,这个成员类型在平台设备驱动层中也有,而且使用其中的 name 成员来实现平台设备匹配,但是 I2C 子系统中不使用其中的 name 进行匹配,这也是 I2C 设备驱动模型和平台设备模型匹配方法的一点区别。
  • id_table : struct i2c_device_id * 类型,用来实现 i2c_client 与 i2c_driver 匹配绑定,当 i2c_client 中的 name 成员和 i2c_driver 中 id_table 中 name 成员相同的时候,就匹配上了。未使用设备树的设备匹配 ID 表。
点击查看 i2c_client 与 i2c_driver 匹配问题
  • i2c_client 中的 name 成员和 i2c_driver 中 id_table 中 name 成员相同的时候。

  • i2c_client 指定的信息在物理上真实存放对应的硬件,并且工作是正常的才会绑定上,并执行其中的 probe 接口函数这第二点要求和平台模型匹配有区别,平台模型不要求设备层指定信息在物理上真实存在就能匹配。

【定义实例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 设备树匹配数组 */
struct of_device_id mpu6050_dt[] = {
{.compatible = "invensense,mpu6050"},
{},
};

/** 定义I2C驱动 */
struct i2c_driver mpu6050_driver = {
.driver = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(mpu6050_dt),
},
.probe = mpu6050_driver_probe, /* 设备和驱动匹配成功之后的调用函数 */
.remove = mpu6050_driver_remove, /* 卸载设备 */
};

3.2 struct device_driver

device_driver 结构体定义在 linux 内核源码的这个文件中:

1
include/linux/device.h

我们打开这个文件,可以看到这个结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct device_driver {
const char *name;
struct bus_type *bus;

struct module *owner;
const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;

int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;
};

【成员说明】

  • name : char * 类型,驱动名称,使用名称匹配的时候匹配 device 用,最后一个成员是必须要初始化的。
  • bus : struct bus_type * 类型,总线类型。
  • owner : struct module * 类型,一般填 THIS_MODULE 。
  • of_match_table : struct of_device_id * 类型(后边会介绍),用于设备树匹配 of_match_ptr (某 struct of_device_id 对象地址) 。 of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型。

3.3 struct i2c_device_id

在使用 ID 匹配和设备树匹配的时候都会用到,在驱动中程序中使用,需要定义一个数组。这个结构体定义在 linux 内核源码的下边这个文件中:

1
include/linux/mod_devicetable.h

我们打开这个文件,会看到这个结构体定义如下:

1
2
3
4
5
6
7
8
#define I2C_NAME_SIZE   20
#define I2C_MODULE_PREFIX "i2c:"

struct i2c_device_id {
char name[I2C_NAME_SIZE];
kernel_ulong_t driver_data; /* Data private to the driver */
};

【成员说明】

  • name : char 类型,表示匹配用的名称(按我自己的理解这个名称就是匹配的时候用的 ID )。
  • driver_data : kernel_ulong_t 类型,需要向驱动传输的其它数据。

【注意事项】一般是定义在驱动中,定义的是一个结构体数组,一般不指定大小,初始化时最后加 {} 表示数组结束。

【定义实例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 定义id匹配数组 */
struct i2c_device_id mpu6050_ids[] = {
{"mpu6050", 0},
{}, /* 表示结束 */
};
/** 定义I2C驱动 */
struct i2c_driver mpu6050_driver = {
.driver = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
.owner = THIS_MODULE,
},
.probe = mpu6050_driver_probe, /* 设备和驱动匹配成功之后的调用函数 */
.remove = mpu6050_driver_remove, /* 卸载设备 */
.id_table = mpu6050_ids,
};

3.4 struct of_device_id

上边提到 of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在 linux 内核源码的这个文件中:

1
include/linux/mod_devicetable.h

我们打开这个文件,可以看到这结构体定义如下:

1
2
3
4
5
6
7
8
9
10
/*
* Struct used for matching a device
*/
struct of_device_id
{
char name[32];
char type[32];
char compatible[128];
const void *data;
};

【成员说明】

  • name : char 类型数组,设备名。
  • type : char 类型数组,设备类型。
  • compatible : char 类型数组,对于设备树而言,通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

【注意事项】一般是定义在驱动中,定义的是一个结构体数组,一般不指定大小,初始化时最后加 {} 表示数组结束。

【定义实例】

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
/* 设备树结构 */
i2c@138B0000{
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay";
mpu6050-3-asix@68{
compatible = "invensense,mpu6050";
reg = <0x68>;
interrupt-parent = <&gpx3>;
interrupts = <3 2>;
};
};

/** 设备树匹配数组定义 */
struct of_device_id mpu6050_dt[] = {
{.compatible = "invensense,mpu6050"},
{},
};

/** 定义I2C驱动 */
struct i2c_driver mpu6050_driver = {
.driver = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
.owner = THIS_MODULE,
.of_match_table = mpu6050_dt,
},
.probe = mpu6050_driver_probe, /* 设备和驱动匹配成功之后的调用函数 */
.remove = mpu6050_driver_remove, /* 卸载设备 */
};

3.5 struct i2c_msg

I2C 设备驱动中实现 I2C 数据传输的数据结构体,寄存器地址,读取的数据,发送的数据等都用该结构体定义一个结构体数组来暂存。此结构体定义在 linux 内核源码的这个文件中:

1
include/uapi/linux/i2c.h

我们打开这个文件,可以看到这结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};

【成员说明】

  • addr : __u16 类型,要读写的二级外设地址
  • flags : __u16 类型,表示地址的长度,读写功能,使用下边定义的这几个宏定义。如果是 10 位地址必须设置 I2C_M_TEN ,如果是读操作必须设置有 I2C_M_RD ,可以使用或运算合成。
  • len : __u16 类型,读写数据的数据长度。
  • buf : __u8 * 类型,要读写的数据指针。写操作:数据源;读操作:指定存放数据的缓存区。

【注意事项】一般是定义在驱动中,定义的是一个结构体数组,主要用于 I2C 通信。

【定义实例】

1
2
3
4
5
6
7
8
9
10
11
int xxx_read_byte(struct i2c_client *pclt, unsigned char reg)
{
// ... ...
/* 1.定义发送数据用的变量和接收数据用的变量并初始化 */
struct i2c_msg msg[2] = { /* 该结构体定义在 linux/i2c.h */
{pclt->addr, 0, 1, txbuf},
{pclt->addr, I2C_M_RD, 1, rxbuf},
};
// ... ...
return rxbuf[0];
}

4. I2C 设备相关结构体

4.1 struct i2c_client

struct i2c_client 这个结构体定义在 linux 内核源码的这个文件中:

1
include/linux/i2c.h

我们打开这个文件,可以看到结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};

【成员说明】

  • flags : unsigned short 类型,表示地址长度,例如是 10 位还是 7 位地址,默认是 7 位地址。如果是 10 位地址器件,则设置为 I2C_CLIENT_TEN 。
  • addr : unsigned short 类型,表示具体 I2C 器件(如 AT24C02 )的设备地址的低 7 位(第 8 位为读或者写的标志)。
  • name : char 类型数组,表示设备名,用于和 i2c_driver 层匹配使用的,可以和平台模型中的平台设备层 platform_driver 中的 name 作用是一样的。
  • adapter : struct i2c_adapter * 类型,本设备所绑定的适配器结构( CPU 有很多 I2C 适配器,类似单片机有串口 1 、串口 2 等等,在 linux 中每个适配器都用一个结构描述),意思就是使用的哪一个 I2C 适配器。
  • irq : int 类型,设备需要使用到中断时,把中断编号传递给 i2c_driver 进行注册中断,如果没有就不需要填充。(有的 I2C 器件有中断引脚编号,与 CPU 相连)。

【定义实例】

1
2
3
static struct i2c_board_info mpu6050_info = {
I2C_BOARD_INFO("mpu6050", 0x68), /* 需要是driver文件中 struct i2c_device_id 定义的那个数组中的一个id相同,0x68表示从设备地址 */
};

4.2 struct i2c_board_info

I2C 设备在进行匹配的时候需要自己创建 client 对象,就需要用到 struct i2c_board_info 这个结构体了,它用于用来协助创建 i2c_client 对象,这个结构体定义在 linux 内核源码的这个文件中:

1
include/linux/i2c.h

我们打开这个文件,可以看到结构体定义如下:

1
2
3
4
5
6
7
8
9
10
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct acpi_dev_node acpi_node;
int irq;
};

【成员说明】

  • type : char 类型数组,用来初始化 i2c_client 结构中的 name 成员。
  • flags : unsigned short 类型,用来初始化 i2c_client 结构中的 flags 成员。
  • addr : unsigned short 类型,用来初始化 i2c_client 结构中的 addr 成员。
  • platform_data : void * 类型,用来初始化 i2c_client 结构中的 .dev.platform_data 成员。
  • archdata : struct dev_archdata *a 类型,用来初始化 i2c_client 结构中的 .dev.archdata 成员。
  • irq : int 类型,用来初始化 i2c_client 结构中的 irq 成员。

【注意事项】关键就是记住该结构和 i2c_client 结构成员的对应关系。在 i2c 子系统不直接创建 i2c_client 结构,只是提供 struct i2c_board_info 结构信息,让子系统动态创建,并且注册。

5. 结构体关系

图片

三、基本函数

1. I2C 总线相关函数

总线驱动相关函数是 SOC 厂商编写总线驱动会使用到的一些函数。

1.1 I2C 总线设备

1.1 i2c_add_adapter()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_add_adapter -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
int i2c_add_adapter(struct i2c_adapter *adapter);

【函数说明】该函数向 Linux 内核注册一个设置好的 i2c_adapter 适配器,该函数使用的是动态的总线号 。

【函数参数】

  • adapter : struct i2c_adapter * 类型,要添加到 Linux 内核中的 i2c_adapter ,也就是 I2C 适配器。

【返回值】 int 类型,成功返回 0 ,失败返回 一个负数。

【使用格式】 none

【注意事项】 none

1.2 i2c_add_numbered_adapter()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_add_numbered_adapter -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
int i2c_add_numbered_adapter(struct i2c_adapter *adap);

【函数说明】该函数向 Linux 内核注册一个设置好的 i2c_adapter 适配器,该函数使用的是静态的总线号 。

【函数参数】

  • adap : struct i2c_adapter * 类型,要添加到 Linux 内核中的 i2c_adapter ,也就是 I2C 适配器。

【返回值】 int 类型,成功返回 0 ,失败返回 一个负数。

【使用格式】 none

【注意事项】 none

1.3 i2c_del_adapter()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_del_adapter -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
void i2c_del_adapter(struct i2c_adapter * adap);

【函数说明】该函数向 Linux 内核删除一个 i2c_adapter 适配器,该函数使用的是静态的总线号 。

【函数参数】

  • adap : struct i2c_adapter * 类型,要添加到 Linux 内核中的 i2c_adapter ,也就是 I2C 适配器。

【返回值】 none

【使用格式】 none

【注意事项】 none

2. I2C 设备相关函数

I2C 设备相关的函数就是我们在 client 模块中需要用到的一些函数,需要我们自己调用。

2.1 i2c_get_adapter()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_get_adapter -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
struct i2c_adapter *i2c_get_adapter(int nr);

【函数说明】该函数通过 I2C 总线编号获得内核中的 i2c_adapter 结构地址,然后用户可以使用这个结构地址就可以给 i2c_client 结构使用,从而实现 i2c_client 进行总线绑定,从而增加适配器引用计数。

【函数参数】

  • int : int 类型, I2C 通道编号,比如 fs4412 开发板的 mpu6050 挂在 I2C5 上,这里就填 5 就可以了。

【返回值】 struct i2c_adapter * 类型,成功返回对应的 I2C 适配器结构内存地址,失败返回 NULL 。

【使用格式】 none

【注意事项】 none

2.2 i2c_put_adapter()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_put_adapter -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
void i2c_put_adapter(struct i2c_adapter *adap);

【函数说明】该函数当使用 i2c_get_adapter 后,需要使用该函数减少引用计数,当 i2c_client 创建完毕后,就可以调用该函数了。(如果适配器驱动不需要卸载,可以不使用这几个函数)。

【函数参数】

  • adap : struct i2c_adapter * 类型, I2C 适配器结构体地址。

【返回值】 none

【使用格式】 none

【注意事项】 none

2.3 i2c_new_probed_device()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_new_probed_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
/* If you don't know the exact address of an I2C device, use this variant
* instead, which can probe for device presence in a list of possible
* addresses. The "probe" callback function is optional. If it is provided,
* it must return 1 on successful probe, 0 otherwise. If it is not provided,
* a default probing method is used.
*/
extern struct i2c_client *
i2c_new_probed_device(struct i2c_adapter *adap,
struct i2c_board_info *info,
unsigned short const *addr_list,
int (*probe)(struct i2c_adapter *, unsigned short addr));

【函数说明】该函数根据参数 adap , info , addr , addr_list 动态创建 i2c_client 并且进行注册,不明确二级外设地址,但是知道是可能几个值之一的情况下可用

【函数参数】

  • adap : struct i2c_adapter * 类型, i2c_client 所依附的 I2C 适配器结构地址。
  • info : struct i2c_board_info * 类型, i2c_client 基本信息。
  • addt_list : unsigned short const * 类型, i2c_client 的地址(地址定义形式是固定的,一般是定义一个数组,数组必须以 I2C_CLIENT_END 结束,示例
1
2
3
4
5
unsigned short ft5x0x_i2c[] = {
0x38,
0x39,
I2C_CLIENT_END
};
  • probe :回调函数指针,当创建好 i2c_client 后,会调用该函数,一般没有什么特殊需求传递 NULL 。

【返回值】 struct i2c_client * 类型,成功返回创建好的 i2c_client 结构地址,失败返回 NULL 。

【使用格式】

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct i2c_adapter *ad;
struct i2c_board_info info= {""};

unsigned short addr_list[]= {
0x38,
0x39,
I2C_CLIENT_END
};

/* 假设设备挂在i2c-2总线上 */
ad=i2c_get_adapter(2);

/* 自己填充board_info */
strcpy(inf.type,"xxxxx");
info.flags=0;
/* 动态创建i2c_client并且注册 */
i2c_new_probed_device(ad,&info,addr_list,NULL);

i2c_put_adapter(ad);

【注意事项】 none

2.4 i2c_new_device()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_new_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
9
10
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
/* Add-on boards should register/unregister their devices; e.g. a board
* with integrated I2C, a config eeprom, sensors, and a codec that's
* used in conjunction with the primary hardware.
*/
extern struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

【函数说明】该函数根据参数 adap , info ,创建 i2c_client 并且进行注册,明确二级外设地址的情况下可用

【函数参数】

  • adap : struct i2c_adapter * 类型, i2c_client 所依附的 I2C 适配器结构地址。
  • info : struct i2c_board_info * 类型, i2c_client 基本信息。

【返回值】 struct i2c_client * 类型,成功返回创建好的 i2c_client 结构地址,失败返回 NULL 。

【使用格式】

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
struct i2c_adapter *ad;
struct i2c_board_info info={
I2C_BOARD_INFO(name, 二级外设地址)
};
/* 假设设备挂在i2c-2总线上 */
ad=i2c_get_adapter(2);

/* 动态创建i2c_client并且注册 */
i2c_new_device(ad,&info);

i2c_put_adapter(ad);

【注意事项】 none

2.5 i2c_unregister_device()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_unregister_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
void i2c_unregister_device(struct i2c_client *pclt);

【函数说明】该函数从 linux 内核注销一个 i2c_client 对象。

【函数参数】

  • adap : struct i2c_adapter * 类型, i2c_client 所依附的 I2C 适配器结构地址。
  • info : struct i2c_board_info * 类型, i2c_client 基本信息。

【返回值】 struct i2c_client * 类型,成功返回创建好的 i2c_client 结构地址,失败返回 NULL 。

【使用格式】 none

【注意事项】 none

3. I2C 设备驱动相关函数

3.1 i2c_register_driver()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_register_driver -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
int i2c_register_driver(struct module *owner,
struct i2c_driver *driver);

【函数说明】该函数向 Linux 内核注册一个 i2c_driver 驱动。

【函数参数】

  • owner : struct module * 类型,一般为 THIS_MODULE 。
  • driver : struct i2c_driver * 类型,要注册的 i2c_driver 。

【返回值】 int 类型,成功返回 0 ,失败返回 一个负数。

【使用格式】 none

【注意事项】 none

3.2 i2c_add_driver()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_add_driver -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
/* use a define to avoid include chaining to get THIS_MODULE */
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)

【函数说明】这其实是一个宏,它用于 Linux 内核注册一个 i2c_driver 驱动,只是我们可以少写一个参数,一般用这个会多一些。

【函数参数】

  • driver : struct i2c_driver * 类型,要注册的 i2c_driver 。

【返回值】 int 类型,成功返回 0 ,失败返回 一个负数。

【使用格式】 none

【注意事项】 none

3.3 i2c_del_driver()

注销 I2C 设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉, 我们使用以下命令查询一下函数所在头文件:

1
grep i2c_del_driver -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
void i2c_del_driver(struct i2c_driver *driver);

【函数说明】该函数用于从 Linux 内核注销一个 i2c_driver 驱动。

【函数参数】

  • driver : struct i2c_driver * 类型,要注销的 i2c_driver 。

【返回值】 int 类型,成功返回 0 ,失败返回 一个负数。

【使用格式】 none

【注意事项】 none

4. I2C 数据读写函数

这些函数用于 I2C 设备驱动中进行数据的发送与接收。

4.1 i2c_transfer()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_transfer -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

【函数说明】该函数是 i2c 收发一体化函数,收还是发由参数 msgs 的成员 flags 决定。

【函数参数】

  • adap : struct i2c_adapter * 类型,表示使用哪一个适配器发送信息,一般是取 i2c_client 结构中的 adapter 指针作为参数。
  • msgs : struct i2c_msg * 类型,具体发送消息指针,一般情况下是一个数组。
  • num : int 类型,表示前一个参数 msgs 数组有多少个消息要发送的。

【返回值】 int 类型,成功返回正数,表示成功发送的 msgs 数量,失败返回一个负数。

【使用格式】 none

【注意事项】 none

4.2 i2c_master_recv()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_master_recv -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);

【函数说明】该函数是 i2c 读取数据函数,实现标准的 I2C 读时序,数据可以是 N 个数据,这个函数调用时候默认已经包含发送从机地址 + 读方向这一环节了。

【函数参数】

  • client : struct i2c_client * 类型, I2C 设备对象的地址。
  • buf : char * 类型,读取数据存放缓冲区。
  • count : int 类型,读取数据大小 不大于 64k 。

【返回值】 int 类型,成功返回读取到的字节数,失败返回一个负数。

【使用格式】 none

【注意事项】 none

4.3 i2c_master_send()

我们使用以下命令查询一下函数所在头文件:

1
grep i2c_master_send -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/i2c.h>

/* 函数声明 */
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);

【函数说明】该函数是 i2c 发送数据函数,实现标准的 I2C 写时序,数据可以是 N 个数据,这个函数调用时候默认已经包含发送从机地址 + 写方向这一环节了。

【函数参数】

  • client : struct i2c_client * 类型, I2C 设备对象的地址。
  • buf : char * 类型,要大宋的数据存放缓冲区。
  • count : int 类型,写入数据大小 不大于 64k 。

【返回值】 int 类型,成功返回发送的字节数,失败返回一个负数。

【使用格式】 none

【注意事项】 none