- Linux内核文档:
Documentation\i2c\instantiating-devices.rst
Documentation\i2c\writing-clients.rst
- Linux内核驱动程序示例:
- Linux-5.4/drivers/misc/eeprom/at24.c
待完善
无需编写驱动直接访问设备\_I2C-Tools介绍
I2C-Tools:
// 列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)
i2cdetect -l
// 打印某个I2C Adapter的Functionalities, I2CBUS为0、1、2等整数
i2cdetect -F I2CBUS
// 看看有哪些I2C设备, I2CBUS为0、1、2等整数
i2cdetect -y -a I2CBUS
……
一、 I2C 协议核心时序
I2C 通信由基本的操作序列构成:
- 写操作: [S] [Slave Addr + W] [A] [Reg Addr] [A] [Data] [A] ... [P]
- 读操作: [S] [Slave Addr + W] [A] [Reg Addr] [A] [Sr] [Slave Addr + R] [A] [Data] [A] ... [Data] [N] [P]
- S: 起始信号 (Start Condition)
- Sr: 重复起始信号 (Repeated Start)
- Slave Addr: 7 位从机地址
- W/R: 读写位 (0为写, 1为读)
- A/N: 应答/非应答信号 (ACK/NACK)
- P: 停止信号 (Stop Condition)
二、 驱动模型:设备与驱动的分离
Linux 的核心思想是将“硬件的描述”与“硬件的驱动程序”分离开。
- I2C Device (设备描述):
- 信息来源: 设备树 (Device Tree) /struct i2c_board_info
- 核心属性:
- compatible: “我是谁?” (e.g., "lite-on,ap3216c") - 这是驱动匹配的关键。
- reg: “我的地址是多少?” (e.g., <0x1e>) - I2C 从机地址。
- 挂载点: 它在设备树中的位置决定了它连接到哪个 I2C 控制器 (e.g., &i2c1)。
如何确认地址:
写四复位,写三开启 ALS PS IR模块
ALS 配置寄存器地址(八位)(不用设置)
- I2C Driver (驱动程序):
- 核心结构体: struct i2c_driver。
- 核心成员:
- .driver.of_match_table: “我能驱动谁?” - 一个 of_device_id 数组,包含了它所支持设备的 compatible 字符串列表。
- .probe: 当设备与驱动成功匹配时,内核调用的初始化函数。
- .remove: 当设备被移除或驱动卸载时,内核调用的清理函数。
- .id_table: (可选,用于非设备树系统和模块自动加载)
三、 驱动程序的生命周期与核心函数
- 注册 (init):
- 驱动模块加载时,调用 i2c_add_driver() 将 i2c_driver 结构体注册到内核的 I2C 核心子系统中。
- 匹配与探测 (Probe):
- I2C 核心将设备树中的设备与所有已注册的驱动进行 compatible 字符串匹配。
- 匹配成功后,内核创建 i2c_client 结构体,并调用驱动的 probe 函数。
- probe 函数的核心职责:
- 获取设备句柄: 保存内核传入的 i2c_client 指针,它是后续所有 I2C 通信的凭证。
- 硬件初始化: 通过 I2C 通信,对设备芯片进行复位、配置等初始化操作。
- 申请资源: 如申请中断、GPIO 等。
- 向上层提供接口: 创建字符设备节点 (/dev/xxx),这是驱动与用户空间交互的桥梁。
- register_chrdev(): 注册一个主设备号,并关联 file_operations 结构体。
- class_create() / device_create(): 让 udev 自动在 /dev 目录下创建设备文件。
- 交互 (file_operations):
- 当用户程序 open, read, write, ioctl 设备文件时,内核通过 file_operations 表,调用驱动中对应的实现函数。
- 这些函数是驱动的“业务逻辑”:
- open: 初始化设备,准备开始通信。
- read: 从 i2c_client 读取数据,并通过 copy_to_user 返回给用户。
- write: 从用户空间 copy_from_user 接收数据,解析后通过 i2c_client 写入设备。
- close (release): 关闭设备,释放资源。
- 移除 (remove):
- 当设备断开或驱动卸载时,内核调用 remove 函数。
- 职责: probe 函数的反向操作。释放所有申请的资源(销毁字符设备、注销中断、关闭硬件等)。
- 卸载 (exit):
- 驱动模块卸载时,调用 i2c_del_driver() 将驱动从 I2C 核心中注销。
框架
设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数,SMBus协议之后再说
四、 重要结构体精解
- i2c_adapter:
- 代表一个 I2C 控制器 (I2C Bus/Controller),比如 i2c-0, i2c-1。它是 I2C 通信的物理通道。
- 它内部包含一个 i2c_algorithm,定义了如何在该总线上产生起始/停止信号、收发字节等底层硬件操作。驱动开发者通常不直接操作它。
- i2c_client:
- 代表一个挂载在 i2c_adapter 上的从设备 (I2C Slave Device)。
- 它绑定了一个 i2c_adapter 和一个从机地址。
- 这是驱动开发者最常使用的“句柄”。所有 I2C 通信函数,如 i2c_smbus_read_byte_data,第一个参数都是 i2c_client。
- i2c_msg:
- 用于构建原始的、更复杂的 I2C 传输序列(比如一次读操作中的先写后读)。
- 当你需要进行 smbus 接口无法满足的复杂通信时,就需要手动填充 i2c_msg 数组,然后调用 i2c_transfer()。
完善:
init:添加一个driver
static int __init i2c_driver_ap3216c_init(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return i2c_add_driver(&i2c_ap3216c_driver);
}
probe函数:
static int ap3216c_probe(struct i2c_client *client)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
ap3216c_client = client;
/* register_chrdev */
major = register_chrdev(0, "ap3216c", &ap3216c_ops);
ap3216c_class = class_create(THIS_MODULE, "ap3216c_class"); //device_class
device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */
return 0;
}
1.注册字符类设备,设备号,名字
2.创建设备类
3.创建节点
file_operation
open:
static int ap3216c_open (struct inode *node, struct file *file)
{
i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
/* delay for reset */
mdelay(20);
i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
mdelay(250);
return 0;
}
向寄存器复位
读写:
u16 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);
i2c_smbus_write_word_data(const struct i2c_client *client, u8 command, u16 value);
读数据需要用到 copy_to_user
GPIO模拟I2C:以后再说
(以后再说)
阅读 Linux-5.4/drivers/misc/eeprom/at24.c