文章目录
- [1 I2C简介](#1 I2C简介)
-
- [1.1 I2C协议](#1.1 I2C协议)
-
- [1.1.1 起始位](#1.1.1 起始位)
- [1.1.2 停止位](#1.1.2 停止位)
- [1.1.3 数据传输](#1.1.3 数据传输)
- [1.1.4 应答信号](#1.1.4 应答信号)
- [1.1.5 I2C写时序](#1.1.5 I2C写时序)
- [1.1.6 I2C读时序](#1.1.6 I2C读时序)
- [1.1.7 I2C多字节读写时序](#1.1.7 I2C多字节读写时序)
- [2 I2C总线框架简介](#2 I2C总线框架简介)
-
- [2.1 I2C总线驱动](#2.1 I2C总线驱动)
- [2.2 I2C总线设备](#2.2 I2C总线设备)
-
- [2.2.1 i2c_client结构体](#2.2.1 i2c_client结构体)
- [2.2.2 i2c_driver结构体](#2.2.2 i2c_driver结构体)
- [2.2.3 i2c_driver注册](#2.2.3 i2c_driver注册)
- [2.3 I2C设备和驱动匹配过程](#2.3 I2C设备和驱动匹配过程)
- [3 I2C设备驱动编写流程](#3 I2C设备驱动编写流程)
-
- [3.1 设备树I2C设备信息描述](#3.1 设备树I2C设备信息描述)
-
- [3.1.1 AP3216C简介](#3.1.1 AP3216C简介)
- [3.2 I2C设备数据收发处理流程](#3.2 I2C设备数据收发处理流程)
-
- [3.2.1 i2c_transfer函数](#3.2.1 i2c_transfer函数)
-
- [3.2.1.1 使用i2c_transfer进行I2C数据收发](#3.2.1.1 使用i2c_transfer进行I2C数据收发)
- [3.3 辅助函数](#3.3 辅助函数)
- [4 I2C驱动的应用实例](#4 I2C驱动的应用实例)
-
- [4.1 硬件原理图分析](#4.1 硬件原理图分析)
- [4.2 修改设备树](#4.2 修改设备树)
- [4.3 驱动程序编写](#4.3 驱动程序编写)
- [4.4 用户侧测试程序编写](#4.4 用户侧测试程序编写)
1 I2C简介
I2C是一种很常见的同步、串行、低速、近距离通信接口。
Linux内核开发者为了让驱动开发工程师在内核中方便的添加自己的I2C设备驱动程序,更容易的在linux下驱动自己的I2C接口硬件,进而引入了I2C总线框架。与Linux下的platform虚拟总线不同的是,I2C是实际的物理总线,所以I2C总线框架也是Linux下总线、设备、驱动模型的产物。
I2C是很常见的一种总线协议,I2C是NXP公司设计的,I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条是SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100Kb/S,快速模式下可以达到400Kb/S。I2C总线工作时按照一定的协议来运行的。
1.1 I2C协议
I2C是支持多从机的,也就是一个I2C控制器下可以挂多个I2C从设备,这些不同的I2C从设备有不同的器件地址,这样I2C主控制器就可以通过I2C设备的器件地址访问指定的I2C设备了,一个I2C总线连接多个I2C设备如图所示:

图中SDA和SCL这两根线必须要接一个上拉电阻,一般是4.7K。
I2C为什么必须接上拉电阻?且必须配置为开漏输出?
- 必须接上拉电阻是为了线与(多设备共享总线),解决多设备竞争;
- 必须开漏输出是为了允许多个设备共用一根线,防止芯片烧毁;
- 绝对不能用推挽输出,推挽会短路烧芯片。
其余的I2C从器件都挂接到SDA和SCL这两根线上,这样就可以通过SDA和SCL这两根线来访问多个I2C设备。
1.1.1 起始位
I2C通信起始标志,通过这个起始位就可以告诉I2C从机,"我"要开始进行I2C通信了。在SCL为高电平的时候,SDA出现下降沿 就表示为起始位,如所示:

1.1.2 停止位
停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL为高电平的时候,SDA出现上升沿 就表示为停止位,如图所示:

1.1.3 数据传输
I2C总线在数据传输的时候要保证在SCL高电平期间,SDA上的数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生,如图所示:

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

写时序的具体步骤:
- 开发信号;
- 发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个I2C器件。这是一个8位的数据,其中高7位是设备地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示这个是写操作;
- I2C器件地址后面跟着一个读写为,为1是读操作,为0是写操作;
- 从机发送的ACK应答信号;
- 重新发送开始信号;
- 发送要写入数据的寄存器地址;
- 从机发送的ACK应答信号;
- 发送要写入寄存器的数据;
- 从机发送的ACK应答信号;
- 停止信号。
1.1.6 I2C读时序
I2C总线单字节读时序如图所示:

I2C单字节读时序比写时序要复杂一点,读时序分为4大步,
- 第一步是发送设备地址;
- 第二步是发送要读取的寄存器地址;
- 第三步重新发送设备地址;
- 最后一步就是I2C从器件输出要读取的寄存器值。
具体来看一下每一个步骤:
- 主机发送起始信号;
- 主机发送要读取的I2C从设备地址;
- 读写控制位,因为是向I2C从设备发送数据,因此是写信号;
- 从机发送的ACK应答信号;
- 重新发送START信号;
- 主机发送要读取数据的寄存器地址;
- 从机发送的ACK应答信号;
- 重新发送START信号;
- 重新发送要读取的I2C从设备地址;
- 读写控制位,这里是读信号,表示接下来是从I2C从设备里面读取数据;
- 从机发送的ACK应答信号;
- 从I2C器件里面读取到的数据;
- 主机发送NO ACK信号,表示读取完成,不需要从机再发送ACK信号;
- 主机发送STOP信号,停止I2C通信。
1.1.7 I2C多字节读写时序
需要读写多个字节,多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,其他的控制时序都是和单字节一样的。
2 I2C总线框架简介
Linux内核开发者为了让驱动开发工程师在内核中方便的添加自己的I2C设备驱动程序,方便大家更容易的在linux下驱动自己的I2C接口硬件,进而引入了I2C总线框架,我们一般也叫作I2C子系统,Linux下I2C子系统总体框架如下所示:

I2C子系统分为三大组成部分:
- I2C核心(I2C-core):I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册、注销方法,I2C通信方法(algorithm)与具体硬件无关的代码,以及探测设备地址的上层代码等;
- I2C总线驱动(I2C adapter):I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。I2C总线驱动由i2c_adapter和i2c_algorithm来描述。I2C适配器是SoC中内置i2c控制器的软件抽象,可以理解为他所代表的是一个I2C主机;
- I2C设备驱动(I2C client driver):包括两部分,设备注册和驱动注册。
I2C子系统帮助内核统一管理I2C设备,让驱动开发工程师在内核中可以更加容易添加自己的I2C设备驱动程序。
2.1 I2C总线驱动
一般由半导体厂商提供,驱动开发人员不关注。
2.2 I2C总线设备
I2C设备驱动重点关注两个数据结构:i2c_client和i2c_driver,根据总线、设备和驱动模型I2C总线由半导体厂商提供,还剩下设备和驱动。i2c_client用于描述I2C总线下的设备,i2c_driver则用于描述I2C总线下的设备驱动,类似于platform总线下的platform_device和platform_driver。
2.2.1 i2c_client结构体
i2c_client结构体定义在include/linux/i2c.h文件中,内容如下:
c
struct i2c_client {
/*
* 标志位(掩码),用于描述 I2C 设备的特性、工作模式
* 下面的宏定义都是 flags 的可用取值
*/
unsigned short flags;
#define I2C_CLIENT_PEC 0x04 /* 使用数据包错误校验(CRC校验) */
#define I2C_CLIENT_TEN 0x10 /* 设备使用 10位 芯片地址 */
/* 注意:该值必须与 I2C_M_TEN 保持一致 */
#define I2C_CLIENT_SLAVE 0x20 /* 当前设备工作在 I2C 从机模式 */
#define I2C_CLIENT_HOST_NOTIFY 0x40 /* 使能 I2C 主机通知功能(从机主动通知主机) */
#define I2C_CLIENT_WAKE 0x80 /* 用于 board_info 配置:标记该设备可唤醒系统 */
#define I2C_CLIENT_SCCB 0x9000 /* 使用 Omnivision(豪威)的 SCCB 协议(摄像头专用) */
/* 该值必须与 I2C_M_STOP | IGNORE_NAK 匹配 */
/*
* I2C 芯片地址
* 重要:这里存储的是 7位 地址
* 并且存放在 低7位(最高位不使用)
*/
unsigned short addr;
char name[I2C_NAME_SIZE]; /* 设备名称(驱动匹配、调试时使用) */
struct i2c_adapter *adapter; /* 指向该设备挂载的 I2C 适配器(控制器) */
struct device dev; /* 标准 Linux 设备结构体(继承核心设备属性) */
int init_irq; /* 初始化时配置的中断号(固定值) */
int irq; /* 设备产生的中断号(实际使用的中断) */
struct list_head detected; /* 用于链接所有已检测到的 I2C 设备的链表节点 */
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* I2C 从机模式回调函数(从机收到数据时触发) */
#endif
};
一个I2C设备对应一个i2c_client结构体变量,系统每检测到一个I2C从设备就会给这个设备分配一个i2c_client。
2.2.2 i2c_driver结构体
i2c_driver类似platform_driver,是编写I2C设备驱动重点要处理的内容,i2c_driver结构体定义在include/linux/i2c.h文件中,内容如下:
c
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
int (*probe_new)(struct i2c_client *client);
void (*shutdown)(struct i2c_client *client);
void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *client, struct i2c_board_info *info);
const unsigned short *address_list;
struct list_head clients;
bool disable_i2c_core_irq_mapping;
};
i2c_driver结构体分析:
- 当I2C设备和驱动匹配成功以后probe函数就会执行,和platform驱动一样;
- device_driver驱动结构体,如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容(compatible)属性;
- id_table是传统的、未使用设备树的设备匹配ID表。
对于I2C设备驱动来说,主要就是构建i2c_driver,构建完成以后需要向I2C子系统注册这个i2c_driver。注册和注销i2c所用的到函数原型如下:
c
///
/// \brief i2c_register_driver 注册 i2c_driver
/// \param owner 一般为THIS_MODULE
/// \param driver 要注册的 i2c_driver
/// \return 0,成功;负值,失败
///
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/// 注册 i2c_driver
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
///
/// \brief i2c_del_driver 注销 i2c_driver
/// \param driver 要注销的 i2c_driver
///
void i2c_del_driver(struct i2c_driver *driver);
2.2.3 i2c_driver注册
i2c_driver的注册示例代码如下:
c
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return 0;
}
static int xxx_remove(struct i2c_client *client)
{
return 0;
}
// 设备树匹配列表
static const struct of_device_id xxx_id[] = {
{ .compatible = "xxx" },
{ }
};
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_id,
},
};
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
2.3 I2C设备和驱动匹配过程
I2C设备和驱动的匹配过程是由I2C子系统核心层来完成的,drivers/i2c/i2c-core-base.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数。
设备和驱动的匹配过程也是由核心层完成的,I2C总线的数据结构为i2c_bus_type,定义在drivers/i2c/i2c-core-base.c文件,i2c_bus_type内容如下:
c
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
.match就是I2C总线的设备和驱动匹配函数,在这里就是i2c_device_match这个函数,此函数内容如下:
c
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
/* Attempt an OF style match */
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* Finally an I2C match */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
分析i2c_device_match函数,I2C设备和驱动匹配有三种方式:
- i2c_of_match_device函数用于完成设备树中定义的设备与驱动匹配过程。比较I2C设备节点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示I2C设备和驱动匹配;
- acpi_driver_match_device函数用于ACPI形式的匹配;
- i2c_match_id函数用于传统的、无设备树的I2C设备和驱动匹配过程。比较I2C设备名字和i2c_device_id的name字段是否相等,相等的话就说明I2C设备和驱动匹配成功。
3 I2C设备驱动编写流程
3.1 设备树I2C设备信息描述
使用设备树的时候I2C设备信息通过创建相应的节点就行了,比如在我们的STM32MP1的开发板上有一个I2C器件AP3216C,这是三合一的环境传感器,并且该器件挂在STM32MP1I2C5总线接口上,因此必须在i2c5节点下创建一个子节点来描述AP3216C设备,节点示例如下所示:
c
&i2c5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c5_pins_a>;
pinctrl-1 = <&i2c5_pins_sleep_a>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
i2c5节点分析:
- 设置了i2c5的pinmux的配置;
- 向i2c5添加ap3216c子节点,"ap3216c@1e"是子节点名字,"@"后面的"1e"就是ap3216c的I2C器件地址。设置compatible属性值为"alientek,ap3216c"。reg属性也是设置ap3216c的器件地址的,因此值为0x1e。
I2C设备节点的创建重点是compatible属性和reg属性的设置,一个用于匹配驱动,一个用于设置器件地址。
3.1.1 AP3216C简介
STM32MP1开发板上通过I2C5连接了一个三合一环境传感器:AP3216C。AP3216C是由敦南科技推出的一款传感器,其支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。该芯片可以通过I2C接口与主控制相连,并且支持中断,AP3216C的特点如下:
- I2C接口,快速模式下波特率可以到400Kbit/S;
- 多种工作模式选择:ALS、PS+IR、ALS+PS+IR、PD等等;
- 内建温度补偿电路;
- 宽工作温度范围(-30C~+80C);
- 超小封装,4.1mmx2.4mmx1.35mm;
- 环境光传感器具有16位分辨率;
- 接近传感器和红外传感器具有10位分辨率。
AP3216C的设备地址是0X1E,这个地址是设备手册中的。
AP3216C用到的寄存器地址如下所示:

3.2 I2C设备数据收发处理流程
3.2.1 i2c_transfer函数
I2C设备驱动首先要做的就是初始化i2c_driver并向Linux内核注册。当设备和驱动匹配以后i2c_driver里面的probe函数就会执行,probe函数里面所做的就是字符设备驱动那一套了。一般需要在probe函数里面初始化I2C设备,要初始化I2C设备就必须能够对I2C设备寄存器进行读写操作,这里就要用到i2c_transfer函数了。i2c_transfer函数最终会调用I2C适配器中i2c_algorithm里面的master_xfer函数,对于STM32MP1而言就是stm32f7_i2c_xfer这个函数。i2c_transfer函数原型如下:
c
///
/// \brief i2c_transfer 传递消息
/// \param adap 所使用的I2C适配器,i2c_client会保存其对应的i2c_adapter
/// \param msgs I2C要发送的一个或多个消息
/// \param num 消息数量,也就是msgs的数量
/// \return
///
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
着重看一下msgs这个参数,这是一个i2c_msg类型的指针参数,I2C进行数据收发说白了就是消息的传递,Linux内核使用i2c_msg结构体来描述一个消息。i2c_msg结构体定义在include/uapi/linux/i2c.h文件中,结构体内容如下:
c
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */
/* makes only sense in kernelspace */
/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
3.2.1.1 使用i2c_transfer进行I2C数据收发
使用i2c_transfer函数发送数据之前要先构建好i2c_msg,使用i2c_transfer进行I2C数据收发的示例代码如下:
c
struct xxx_dev {
// ......
void *private_data; /* 私有数据,一般会设置为i2c_client */
};
/*
* @description : 读取I2C设备多个寄存器数据
* @param - dev: I2C设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* I2C器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向I2C设备多个寄存器写入数据
* @param - dev: 要写入的设备结构体
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 ap3216c_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* I2C器件地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。
c
///
/// \brief i2c_master_send I2C数据发送函数
/// \param client I2C设备对应的 i2c_client
/// \param buf 要发送的数据
/// \param count 要发送的数据字节树,要小于64KB,因为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据
/// \return 负值,失败;其他非负值,发送的字节数
///
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
///
/// \brief i2c_master_recv I2C数据接收函数
/// \param client I2C设备对应的 i2c_client
/// \param buf 要接收的数据
/// \param count 要接收的数据字节数,要小于64KB,因为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据
/// \return 负值,失败;其他非负值,接收的字节数
///
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
I2C收发数据的关键就是i2c_msg的构建和i2c_transfer函数的调用。
3.3 辅助函数
c
///
/// \brief 托管式内存分配,自动初始化为 0,设备释放时自动回收
/// \param dev 关联的设备结构体指针,用于绑定生命周期
/// \param size 要分配的内存字节大小
/// \param gfp 内存分配标志位(如 GFP_KERNEL/GFP_ATOMIC)
/// \return 成功返回清零后的内存指针;失败返回 NULL
///
void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
///
/// \brief 向 i2c_client 结构体设置私有数据指针
/// \param client I2C 客户端设备结构体
/// \param data 要保存的私有数据指针(驱动自定义结构体)
/// \return 无返回值
///
void i2c_set_clientdata(struct i2c_client *client, void *data);
///
/// \brief 从 i2c_client 结构体获取之前设置的私有数据指针
/// \param client I2C 客户端设备结构体
/// \return 返回保存的私有数据指针;未设置时返回 NULL
///
void *i2c_get_clientdata(const struct i2c_client *client);
///
/// \brief 宏定义,通过结构体成员指针,反推出整个结构体的起始地址
/// \param ptr 结构体成员变量的指针
/// \param type 外层结构体类型
/// \param member 结构体中成员的名字
/// \return 外层结构体的起始地址指针
///
container_of(ptr, type, member)
4 I2C驱动的应用实例
4.1 硬件原理图分析

4.2 修改设备树
打开stm32mp157d-atk.dts文件,通过节点内容追加的方式,向i2c5节点中添加ap3216c@1e"子节点,节点如下所示
c
&i2c5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c5_pins_a>;
pinctrl-1 = <&i2c5_pins_sleep_a>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
4.3 驱动程序编写
c
#ifndef AP3216C_H
#define AP3216C_H
#define AP3216C_ADDR 0X1E /* AP3216C器件地址 */
/* AP3316C寄存器 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */
#endif
c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
struct ap3216c_dev {
struct i2c_client *client; /* i2c 设备 */
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
unsigned short ir, als, ps; /* 三个光传感器数据 */
};
/*
* @description : 从ap3216c读取多个寄存器数据
* @param - dev: ap3216c设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* ap3216c地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向ap3216c多个寄存器写入数据
* @param - dev: ap3216c设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* ap3216c地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 读取ap3216c指定寄存器值,读取一个寄存器
* @param - dev: ap3216c设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
}
/*
* @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器
* @param - dev: ap3216c设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param - ir : ir数据
* @param - ps : ps数据
* @param - ps : als数据
* @return : 无。
*/
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i =0;
unsigned char buf[6];
/* 循环读取所有传感器数据 */
for(i = 0; i < 6; i++) {
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
dev->ir = 0;
else /* 读取IR传感器的数据 */
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
dev->ps = 0;
else /* 读取PS传感器的数据 */
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{
/* 从file结构体获取cdev的指针,在根据cdev获取ap3216c_dev结构体的首地址 */
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct ap3216c_dev *ap3216cdev = container_of(cdev, struct ap3216c_dev, cdev);
/* 初始化AP3216C */
ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */
mdelay(50); /* AP3216C复位最少10ms */
ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[3];
long err = 0;
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev, cdev);
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
struct ap3216c_dev *ap3216cdev;
/* */
ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev), GFP_KERNEL);
if(!ap3216cdev)
return -ENOMEM;
/* 注册字符设备驱动 */
/* 1、创建设备号 */
ret = alloc_chrdev_region(&ap3216cdev->devid, 0, AP3216C_CNT, AP3216C_NAME);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", AP3216C_NAME, ret);
return -ENOMEM;
}
/* 2、初始化cdev */
ap3216cdev->cdev.owner = THIS_MODULE;
cdev_init(&ap3216cdev->cdev, &ap3216c_ops);
/* 3、添加一个cdev */
ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid, AP3216C_CNT);
if(ret < 0) {
goto del_unregister;
}
/* 4、创建类 */
ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);
if (IS_ERR(ap3216cdev->class)) {
goto del_cdev;
}
/* 5、创建设备 */
ap3216cdev->device = device_create(ap3216cdev->class, NULL, ap3216cdev->devid, NULL, AP3216C_NAME);
if (IS_ERR(ap3216cdev->device)) {
goto destroy_class;
}
ap3216cdev->client = client;
/* 保存ap3216cdev结构体 */
i2c_set_clientdata(client,ap3216cdev);
return 0;
destroy_class:
device_destroy(ap3216cdev->class, ap3216cdev->devid);
del_cdev:
cdev_del(&ap3216cdev->cdev);
del_unregister:
unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);
return -EIO;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_remove(struct i2c_client *client)
{
struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);
/* 注销字符设备驱动 */
/* 1、删除cdev */
cdev_del(&ap3216cdev->cdev);
/* 2、注销设备号 */
unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);
/* 3、注销设备 */
device_destroy(ap3216cdev->class, ap3216cdev->devid);
/* 4、注销类 */
class_destroy(ap3216cdev->class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c",
.of_match_table = ap3216c_of_match,
},
.id_table = ap3216c_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
/* module_i2c_driver(ap3216c_driver) */
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
4.4 用户侧测试程序编写
c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned short databuf[3];
unsigned short ir, als, ps;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* 数据读取成功 */
ir = databuf[0]; /* ir传感器数据 */
als = databuf[1]; /* als传感器数据 */
ps = databuf[2]; /* ps传感器数据 */
printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
}
usleep(200000); /* 200ms */
}
close(fd); /* 关闭文件 */
return 0;
}