I2C驱动
1、硬件
- 物理接线
I2C 总线在物理连接上非常简单,分别由 SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对 SCL 和 SDA 线高低电平时序的控制,来产生 I2C 总线协议所需要的信号进行数据的传递。在总线空闲状态时,一般被所接的上拉电阻拉高,保持着高电平。
- I2C总线特征
I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把 CPU 带 I2C 总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。I2C 总线上可挂接的设备数量受总线的最大电容 400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址的限制。I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达 400kbit/s,高速模式下可达 3.4Mbit/s。一般通过 I2C 总线接口可编程时钟来实现传输速率的调整。I2C 总线上的主设备与从设备之间以 8bit 为单位进行双向的数据传输。
- I2C器件地址
不同的I2C器件有不同的地址设置,可分为 7bit 地址与 10bit 地址
此处以7bit 地址 EEPROM 为例(查看datasheet、硬件原理图):
前 4bit (1010)为固定位,由厂家决定;之后 3bit (A2、A1、A0)由器件连接方式决定,由上面的硬件原理图可知为 001;第 8bit 为读写位,0为写,1为读。
7bit 地址为:1010 001
读地址:0xA3;写地址:0xA2
2、时序
- 基本概念
①总线空闲状态:SCL 和 SDA 都保持着高电平;
②起始条件:总线在空闲状态时,当 SCL 为高电平而 SDA 由高到低跳变,表示产生一个起始条件;
③停止条件:当 SCL 为高电平而 SDA 由低到高的跳变,表示产生一个停止条件;
④应答信号:每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为低,则表示一个应答信号;
⑤非应答信号:每个字节传输完成后的下一个时钟信号,在 SCL 高电平期间,SDA 为高,则表示一个非应答信号;
- 传输过程
不同器件可能存在些许差异,具体看datasheet
此处以 写 EEPROM为例:
产生起始信号–>发送设备地址0xA2–>等待EEPROM应答ACK–>发送寄存器高 8bit 地址–>等待EEPROM应答ACK–>发送寄存器低 8bit 地址–>等待EEPROM应答ACK–>发送要写入的 8bit 数据–>等待EEPROM应答ACK–>发送停止条件
3、I2C整体架构
- I2C架构概述
下面的框图完整的描述了Linux下 I2C 驱动架构
简化图:
I2C子系统由上到下可大致分为3层:
① I2C 设备驱动层:
由设备层代码 + 驱动层代码构成,可类比平台设备驱动模型
真正实现具体设备的时序的代码
② I2C 核心层:
linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动和硬件控制的实现细节(如操作i2c的寄存器);提供了 I2C 总线驱动和设备驱动的注册、注销方法、 I2C通信方法等;提供了设备驱动层和适配器驱动层需要的API接口,起到了承上启下的作用
主要包含文件:i2c-core.c
③ I2C 适配器驱动层:
一般由芯片厂商提供,对 I2C硬件体系结构中适配器端的实现
主要包含文件:busses文件夹下
- 相关的重要结构体
①i2c_driver :
定义在 i2c.h 中,内核使用 struct i2c_driver 结构描述一个 i2c 设备驱动,其中必须实现的有 :probe、remove。
struct i2c_driver {
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; //设备的ID表,匹配创建的client
};
②i2c_client :
内核使用 struct i2c_client 来描述一个设备信息。每一个 i2c slave device 都需要一个i2c_client 结构体来描述
在内核中通过设备树的方式创建
struct i2c_client {
unsigned short flags; //标志,比如说设备地址 10 位还是 7 位
unsigned short addr; //低 7 位为芯片地址
char name[I2C_NAME_SIZE]; //设备名称,用来与 i2_driver 匹配
struct i2c_adapter *adapter; //依附的 i2c_adapter,它表示一个 IIC 控制器
struct i2c_driver *driver; //依附的 i2c_driver
struct device dev; //设备结构体
int irq; //设备所使用的中断号
struct list_head detected; //设备链表
};
③i2c_adapter
i2c总线适配器其实就是一个i2c总线控制器,本质上是一个物理设备,硬件上每一对I2C总线都对应一个适配器来控制它,主要用来完成i2c总线控制器相关的数据通信,由芯片厂商提供
struct i2c_adapter {
struct module *owner;
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; /* 指向适配器的设备结构体 */
int nr; /*总线编号*/
char name[48]; /*适配器的名称*/
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list
}
④i2c_algorithm
用来描述适配器和设备之间的通信方法,由芯片厂商实现
struct i2c_algorithm {
//传输函数指针,指向实现IIC总线通信协议的函数
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
};
⑤i2c_msg
封装要发送的数据
struct i2c_msg {
__u16 addr; /* 目标地址 slave address */
__u16 flags; /* 消息额外的标志,可选择的值有以下宏*/
#define I2C_M_TEN 0x0010 /* 表示目标器件地址是 10 位的 */
#define I2C_M_RD 0x0001 /* 在从设备中读取数据 */
/* 没有专门定义一个写的标志,默认是写 */
__u16 len; /* 传输的数据字节数量 */
__u8 *buf; /* 接收/发送缓冲区 */
};
4、海思 I2C
1、i2c_device
在我们使用的Linux 4.x的版本中通过设备树的方式来描述设备信息
文件: linux-4.9.y-smp/arch/arm/boot/dts 目录下的hi3519av100.dtsi
i2c_bus1: i2c@04561000 {//节点名
compatible = "hisilicon,hibvt-i2c"; //与 driver 中相同,进行匹配
reg = <0x04561000 0x1000>; //寄存器地址
clocks = <&clock HI3519AV100_I2C1_CLK>;
clock-frequency = <100000>;
status = "disabled";
dmas = <&hiedmacv310_1 2 16>, <&hiedmacv310_1 3 17>;
dma-names = "tx","rx";
};
最终内核会将这个设备树节点解析为一个i2c_client结构体与i2c_driver结构体进行匹配。
2、i2c_driver
文件:linux-4.9.y-smp/drivers/i2c/busses 目录下的 i2c-hibvt.c
//与设备树相匹配
static const struct of_device_id hibvt_i2c_match[] = {
{ .compatible = "hisilicon,hibvt-i2c"},
{ .compatible = "hisilicon,hi3516cv300-i2c"},
{ .compatible = "hisilicon,hi3536dv100-i2c"},
{},
};
//封装 i2c_driver 结构体
static struct platform_driver hibvt_i2c_driver = {
.driver = {
.name = "hibvt-i2c",
.of_match_table = hibvt_i2c_match,
.pm = &hibvt_i2c_dev_pm,
},
.probe = hibvt_i2c_probe,
.remove = hibvt_i2c_remove,
};
//如果设备树匹配成功,那么将会调用 probe 函数
在此文件中不仅实现了匹配设备,还实现了对i2c总线的时序操作
有些文件使用情况与模拟 i2c 相同,具体在下面分析
5、模拟 I2C
1、设置内核支持模拟 i2c
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
Device Drivers --->
I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
2、i2c_device
文件: linux-4.9.y-smp/arch/arm/boot/dts 目录下的hi3519av100.dtsi
添加如下代码
i2c-gpio {
compatible = "i2c-gpio"; //匹配 driver
gpios = <&gpio_chip3 1 0 //sda:gpio3_1
&gpio_chip3 0 0 //scl:gpio3_0
>;
i2c-gpio,sda-close-drain;
i2c-gpio,scl-open-drain; //设置开漏
i2c-gpio,delay-us = <5>; //设置延时时间
#address-cells = <1>;
#size-cells = <0>;
};
3、i2c_driver
文件:linux-4.9.y-smp/drivers/i2c/busses 目录下的 i2c-gpio.c
//与设备树相匹配
static const struct of_device_id i2c_gpio_dt_ids[] = {
{ .compatible = "i2c-gpio", },
}
static struct platform_driver i2c_gpio_driver = {
.driver = {
.name = "i2c-gpio",
.of_match_table = of_match_ptr(i2c_gpio_dt_ids),
},
.probe = i2c_gpio_probe,
.remove = i2c_gpio_remove,
};
//初始化
static int __init i2c_gpio_init(void)
{
int ret;
//将gpio模拟i2c总线 做为平台设备驱动注册
ret = platform_driver_register(&i2c_gpio_driver);
if (ret)
printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
return ret;
}
i2c_gpio_probe函数
static int i2c_gpio_probe(struct platform_device *pdev)
{
struct i2c_gpio_private_data *priv;
struct i2c_gpio_platform_data *pdata;
struct i2c_algo_bit_data *bit_data;
struct i2c_adapter *adap;
unsigned int sda_pin, scl_pin;
int ret;
/* First get the GPIO pins; if it fails, we'll defer the probe. */
if (pdev->dev.of_node) {//在设备树中定义了节点,进入
ret = of_i2c_gpio_get_pins(pdev->dev.of_node, &sda_pin, &scl_pin);
if (ret)
return ret;
} else {
if (!dev_get_platdata(&pdev->dev))
return -ENXIO;
pdata = dev_get_platdata(&pdev->dev);
sda_pin = pdata->sda_pin;
scl_pin = pdata->scl_pin;
}
//申请相应的gpio pin
ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* Try again later */
return ret;
}
ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* Try again later */
return ret;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
adap = &priv->adap;
bit_data = &priv->bit_data;
pdata = &priv->pdata;
if (pdev->dev.of_node) {
pdata->sda_pin = sda_pin;
pdata->scl_pin = scl_pin;
//依据设备树中的定义 赋值结构体pdata
of_i2c_gpio_get_props(pdev->dev.of_node, pdata);
} else {
memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));
}
if (pdata->sda_is_open_drain) {//我们在设备树中设置的是非开漏,执行else
gpio_direction_output(pdata->sda_pin, 1);
bit_data->setsda = i2c_gpio_setsda_val;
} else {
gpio_direction_input(pdata->sda_pin);//设置 sda 输入
bit_data->setsda = i2c_gpio_setsda_dir;//赋值函数指针 控制方向
}
if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
gpio_direction_output(pdata->scl_pin, 1);//设置 scl 输出高电平
bit_data->setscl = i2c_gpio_setscl_val; //赋值函数指针 设置高低电平
} else {
gpio_direction_input(pdata->scl_pin);
bit_data->setscl = i2c_gpio_setscl_dir;
}
if (!pdata->scl_is_output_only)
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda;//赋值函数指针 获取 sda 的值
//设置延时时间
if (pdata->udelay)
bit_data->udelay = pdata->udelay;
else if (pdata->scl_is_output_only)
bit_data->udelay = 50; /* 10 kHz */
else
bit_data->udelay = 5; /* 100 kHz */
//设置超时时间
if (pdata->timeout)
bit_data->timeout = pdata->timeout;
else
bit_data->timeout = HZ / 10; /* 100 ms */
bit_data->data = pdata;
//以下就是封装适配器结构体i2c_adapter
adap->owner = THIS_MODULE;
if (pdev->dev.of_node)
strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
else
snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
adap->nr = pdev->id;
ret = i2c_bit_add_numbered_bus(adap);
if (ret)
return ret;
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
pdata->sda_pin, pdata->scl_pin,
pdata->scl_is_output_only
? ", no clock stretching" : "");
return 0;
}
4、i2c_dev.c
该文件在 /drivers/i2c 目录下
实现了 I2C 适配器设备文件的功能,每一个 I2C 适配器都被分配一个设备。通过适配器访设备时的主设备号都为 89,次设备号为 0-255。I2c-dev.c 并没有针对特定的设备而设计,只是提供了通用的 read(),write(),和 ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的 I2C 设备的存储空间或寄存器,并控制 I2C 设备的工作方式
可以查看dev下的设备信息
~ # ls -l /dev/i2c-*
crw------- 1 root root 89, 0 Jan 11 05:44 /dev/i2c-0
crw------- 1 root root 89, 1 Jan 11 05:44 /dev/i2c-1
crw------- 1 root root 89, 10 Jan 11 05:44 /dev/i2c-10
crw------- 1 root root 89, 2 Jan 11 05:44 /dev/i2c-2
crw------- 1 root root 89, 3 Jan 11 05:44 /dev/i2c-3
crw------- 1 root root 89, 4 Jan 11 05:44 /dev/i2c-4
crw------- 1 root root 89, 5 Jan 11 05:44 /dev/i2c-5
crw------- 1 root root 89, 6 Jan 11 05:44 /dev/i2c-6
crw------- 1 root root 89, 7 Jan 11 05:44 /dev/i2c-7
crw------- 1 root root 89, 8 Jan 11 05:44 /dev/i2c-8
crw------- 1 root root 89, 9 Jan 11 05:44 /dev/i2c-9
5、i2c-algo-bit.c
该文件在 /driver/i2c/algos 目录下
定义了 i2c 通信的时序代码,控制 gpio 口输入输出、高低电平、等待ACK等都是在这个文件中实现的
6、对i2c从设备进行读写操作流程:
①应用层将要发送的数据打包成i2c_msg结构,打开 /dev下的i2c设备,通过ioctl函数将封装好的 i2c_msg 传入驱动层;
②进入 i2c-dev.c,内核中使用核心层 i2c_core.c 中提供的i2c_transfer()函数,传输 i2c_msg,这个函数最终会回调相应的i2c_adapter->i2c_algorithm->master_xfer()接口,该接口在i2c-algo-bit.c中赋值
③最终在i2c-algo-bit.c文件中完成一系列的时序控制