Open LQF376 opened 1 year ago
I2C 驱动分为了 总线驱动(SOC的I2C控制器驱动、适配器驱动) 和 设备驱动(针对具体 I2C 设备而编写的驱动)
从总体上来看,I2C 总线驱动是一个标准的 platform 驱动
重点在 i2c_adapter,内核将 SOC 的 I2C 适配器抽象成 i2c_adapter;其内部有一个结构体 i2c_algorithm 提供适配器对外读写数据的 API 接口
struct i2c_adapter{
...
const struct i2c_algorithm *algo; // 提供对外数据读写的 API
...
}
i2c_algorithm 中的 master_xfer,即 I2C 适配器的传输函数
struct i2c_algorithm{
...
/* I2C 适配器的传输函数 */
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);
...
}
设备树信息(imx6ull.dtsi)
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
驱动部分是一个标准的 platform 驱动类型;
i2c_imx_probe 主要工作内容:
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe, // 重点
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = i2c_imx_dt_ids,
.pm = IMX_I2C_PM,
},
.id_table = imx_i2c_devtype,
};
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);
static struct i2c_algorithm i2c_imx_alfo = {
.master_xfer = i2c_imx_xfer, //
.functionality = i2c_imx_func, // 此 I2C 适配器支持什么样的通信协议
};
/* I2C 通信接口;对外 API 接口(设备驱动会调用) */
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num);
设备驱动 是 I2C 总线驱动模型,重点在 i2c_client(描述设备信息)和 i2c_driver (描述驱动内容)
struct bus_type i2c_bus_type = { // 总线模型
.name = "i2c",
.match = i2c_device_match, // 设备和驱动的匹配函数
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client
i2c_driver 对于 I2C 设备驱动编写来说是重点,构建完 i2c_driver 后需要向内核进行注册
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
static int __init xxx_init(void)
{
return i2c_add_driver(&xxx_driver);
}
设备信息描述,i2c_board_info 结构体来描述一个具体的I2C设备(未使用设备树前提下)
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 fwnode_handle *fwnode;
int irq;
};
使用设备树情况下
&i2c1{
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110"; // 重点
reg = <0x0e>; // 重点
position = <2>;
};
}
内核使用 i2c_msg 结构体表示一个消息
struct i2c_msg {
__u16 addr; // 从机地址
__u16 flags; // 标志 (发送数据还是读数据)
__u16 len; // 消息长度
__u8 *buf; // 消息内容
}
构建好 i2c_msg,之后,便可以使用 i2c_transfer 进行 I2C 数据收发
struct xxx_dev {
...
void *private_data; // 一般设置为 i2c_client
};
/* 读多条数据 */
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
msg[0].addr = client->addr;
msg[0].flags = 0; // 发送数据
msg[0].buf = ® // 读取的首地址
msg[0].len = 1; // reg 的长度
msg[1].addr = client->addr;
msg[1].flag = I2C_M_RD;
msg[1].buf = val;
msg[1].len = len;
i2c_transfer(client->adapter, msg, 2);
}
/* 多个寄存器写入数据 */
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
b[0] = reg;
memcpy(&b[1], buf, len);
msg.addr = client->addr;
msg.flag = 0; // 写数据
msg.buf = b;
msg.len = len + 1;
return i2c_transfer(client->adapter, &msg, 1);
}
设备树添加 ap3216c
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
驱动部分
struct ap3216c_dev{
...
void *private_data;
unsigned short ir, als, ps;
};
static struct ap3216c_dev ap3216cdev;
/* 读取多个寄存器数据,底层调用 i2c_transfer 发送数据 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len);
/* 往多个寄存器写入数据,底层调用 i2c_transfer 发送数据 */
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len);
/* 读取一个寄存器的数据,底层调 ap3216c_read_regs(dev, reg, &data, 1); */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg);
/* 向指定寄存器写入数据,底层 ap3216c_write_regs(dev, reg, &buf, 1) */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data);
/* 读取 ir ps als 数据寄存器内的数据 */
void ap3216c_readdata(struct ap3216c_dev *dev);
/* 打开设备 file_operations 成员 */
static int ap3216c_open(struct inode *inode, struct file *filp);
/* file_operations 中的 read */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
ap3216c_readdata(dev);
copy_to_user(buf, data, sizeof(data));
}
/* file_opeartions 中的 release */
static int ap3216c_release(struct inode *inode, struct file *filp);
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 1. 构建设备号
// 2. 注册设备 (会绑定 file_operations)
// 3. 创建类
// 4. 创建设备
// 5. ap3216cdev.private_data = client
}
/* 移除函数,完成注销相关操作 */
static int ap3216c_remove(struct i2c_client *client);
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,
};
static int __init ap3216c_init(void)
{
return i2c_add_driver(&ap3216c_driver);
}
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
I2C Linux
1. I2C 简介
1.1 I2C 总线通信过程
1.2 I2C 总线寻址方式
1.3 I2C 信号的实现
起始信号和停止信号
字节传送与应答
同步信号
1.4 典型 I2C 时序
主机向从机发送数据
从机向主机发送数据
主机先向从机发送数据,然后从机再向主机发送数据
注:阴影部分表示数据由主机向从机传送;无阴影部分则表示数据由从机向主机发送
2. I.MX6U I2C 简介
2.1 I2Cx_IADR(x =1~4)I2C 地址寄存器
2.2 I2C_IFDR 分频寄存器
IC(bit5:0)这个位,用来设置 I2C 的波特率
2.3 I2Cx_I2CR 控制寄存器
2.4 I2Cx_I2SR 状态寄存器
2.5 I2Cx_I2DR 数据寄存器
3. I.MX6U 的 I2C 文件
4. AP3216C 简介
AP3216C 支持 环境光强度(ALS)、接近距离(PS) 和 红外线强度(IR) 这三个环境参数检测
原理图
5. 配置 AP3216C
6. Linux I2C 驱动框架
Linux 内核将 I2C 驱动分成两部分:
6.1 I2C 总线驱动(I2C 适配器)
I2C 总线驱动的重点是 I2C 适配器驱动(SOC 的 I2C 接口控制器)
两个重要结构体: i2c_adapter 和 i2c_algorithm
Linux 将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter (include/linux/i2c.h)
i2c_algorithm ,对外提供读写 API 函数,设备驱动程序使用这些 API 来完成读写操作, 即 I2C 适配器 和 I2C 设备进行通信的方法 (include/linux/i2c.h)
6.2 I2C 设备驱动
两个重要结构体:i2c_client (描述设备信息)和 i2c_driver(描述驱动内容)
i2c_client 用来描述设备信息;include/linux/i2c.h
一个设备对应一个 i2c_client 结构体
i2c_driver 用来描述驱动内容;编写 I2C 设备的重要处理内容;include/linux/i2c.h
I2C 设备驱动编写,重点就是构建 i2c_driver,然后向内核注册这个 i2c_driver
i2c_driver 注册示例:
6.3 I2C 设备和驱动匹配过程
I2C 设备和驱动匹配的过程由 I2C 核心来完成, drivers/i2c.i2c-core.c 就是 I2C 核心,提供了一套与硬件无关的 API
设备和驱动的匹配过程是由 I2C 总线来完成的,I2C总线的数据结构为 i2c_bus_type(类似于 platform,只不过其是虚拟总线);drivers/i2c/i2c-core.c
.match 用来完成 总线设备和驱动匹配函数
.probe 函数 在 设备和驱动匹配成功之后,就会执行;主要工作内容:
其中 i2c_imx_algo:
i2c_imx_xfer 完成与 I2C 设备通信的
7. I2C 设备驱动编写流程
7.1 I2C 设备信息描述
1. 未使用设备树
未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描述一个 I2C 设备
2. 使用设备树的时候
I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然后在这个子节点内描述 mag3110 这个芯片的相关信息
7.2 I2C 设备数据收发流程
i2c_transfer 用于对 I2C 设备寄存器进行读写操作;i2c_transfer 会调用 I2C 适配器中的 i2c_algorithm 里面的 master_xfer 函数(I.MX SOC来说是 i2c_imx_xfer)
Linux 内核使用 i2c_msg 结构体 来描述一个消息(include/uapi/linux/i2c.h)
两个封装 i2c_transfer 的 API
8. 以 AP3216C 为例,编写 I2C 设备驱动
8.0 电路原理图
8.1 修改设备树
端口复用, IO修改或添加
i2c1 节点追加 ap3216c 子节点
make dtbs 编译设备树后;可在 /sys/bus/i2c/devices/0-001e/ 看到设备名字
8.2 AP3216C 驱动编写