往期内容
I2C子系统专栏:
- I2C(IIC)协议讲解-CSDN博客
- SMBus 协议详解-CSDN博客
- I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
- 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
- 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
- 设备驱动与设备树匹配机制详解
总线和设备树专栏:
1.I2C总线驱动
I2C Core就是I2C核心层,它的作用:
- 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
- 实现
I2C总线-设备-驱动模型
,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)
2.框图
在上一章节已经讲过,只要把i2c_client(一个i2c设备的抽象)当作platform_device就行,其实原理都差不多。具体直接看下图就行:
除了i2c client结构体,其它的结构体在之前的章节也全都介绍过:
I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
3.结构体
3.1 i2c_driver
在 Linux 内核中,I2C (Inter-Integrated Circuit) 驱动框架用于处理连接在 I2C 总线上的设备。驱动程序通过 i2c_driver
结构体来表示,而每个 I2C 设备则通过 i2c_client
结构体来表示。
i2c_driver
是 Linux 内核中的结构体,用于描述 I2C 驱动。它包含与设备匹配的规则和驱动的核心操作函数,如 probe
和 remove
。I2C 驱动可通过以下两种方式与设备匹配:
使用
of_match_table
来判断(第二种匹配方式)
设备树匹配:设备树 (Device Tree) 是硬件的描述文件,I2C 驱动程序通过
of_match_table
来与设备树中的设备节点匹配。
- 当设备树中某个 I2C 控制器节点下的 I2C 设备节点的
compatible
属性与i2c_driver
中的of_match_table
表中的compatible[128]相同时,匹配成功。使用
name
匹配:(第四种匹配方式)
- 当
i2c_client
结构体中的name
字段与i2c_driver.device_driver.name
的值相同,也会匹配成功。使用
id_table
来判断(第三种匹配方式)
i2c_client.name
匹配:i2c_client
中的name
字段与id_table
中的某一项匹配时,也会匹配成功。当
i2c_driver
和i2c_client
成功匹配后,驱动的probe
函数会被调用,开始初始化设备。
其实和上一章节讲的的平台设备匹配,道理是几乎相同的,具体看下面链接:
3.2 i2c_client
i2c_client表示一个I2C设备,创建i2c_client的方法有4种:
方法1
- 通过I2C bus number来创建
- 函数
i2c_register_board_info()
允许通过 I2C 总线编号创建i2c_client
。常用于板级文件中提前注册设备信息int i2c_register_board_info(int busnum, struct i2c_board_info, const *info, unsigned len);
- 通过设备树来创建
i2c1: i2c@400a0000 { /* ... master properties skipped ... */ clock-frequency = <100000>; flash@50 { compatible = "atmel,24c256"; reg = <0x50>; }; pca9532: gpio@60 { compatible = "nxp,pca9532"; gpio-controller; #gpio-cells = <2>; reg = <0x60>; }; };
方法2
当设备的 I2C 总线编号不明确时,可以通过其他方式获取对应的
i2c_adapter
,然后通过以下两种函数创建
i2c_client
:
i2c_new_device()
:即使设备并不存在,该函数仍然可以创建i2c_client
可以使用下面两个函数来创建i2c_client:static struct i2c_board_info sfe4001_hwmon_info = { I2C_BOARD_INFO("max6647", 0x4e), }; int sfe4001_init(struct efx_nic *efx) { (...) efx->board_info.hwmon_client = i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info); (...) }
i2c_new_probed_device()
:设备的地址可能会发生变化,如某些设备的地址可通过引脚配置。这种情况下,可以列出一组可能的设备地址,i2c_new_probed_device 会根据地址逐一检测设备是否存在。static const unsigned short normal_i2c[] = { 0x2c, 0x2d, >I2C_CLIENT_END }; static int usb_hcd_nxp_probe(struct platform_device *pdev) { (...) struct i2c_adapter *i2c_adap; struct i2c_board_info i2c_info; (...) i2c_adap = i2c_get_adapter(2); memset(&i2c_info, 0, sizeof(struct i2c_board_info)); strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type)); isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, normal_i2c, NULL); i2c_put_adapter(i2c_adap); (...) }
方法3(不推荐): 通过 i2c_driver.detect
函数可以检测到设备并生成 i2c_client
,但这种方法较少使用。
方法4: 可以通过用户空间命令手动生成 i2c_client
,例如在调试过程中或不方便通过代码生成时:
// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
// 删除一个i2c_client
# echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device
4.通用模板编写
4.1 代码
驱动程序:
#include "linux/i2c.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
/* Global variables */
static int major = 0;
static struct class *my_i2c_class;
struct i2c_client *g_client;
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
struct fasync_struct *i2c_fasync;
/* Implementing open/read/write functions for the file_operations structure */
static ssize_t i2c_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
struct i2c_msg msgs[2];
/* Initialize i2c_msg for reading */
/* Assuming two i2c messages: One for writing register address, the other for reading data */
err = i2c_transfer(g_client->adapter, msgs, 2);
if (err < 0)
return err;
/* Copy data to user space */
/* Handle buffer size and offset accordingly */
if (copy_to_user(buf, msgs[1].buf, size)) {
return -EFAULT;
}
return size;
}
static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
struct i2c_msg msgs[2];
/* Allocate buffer and copy data from user space */
char *kbuf = kmalloc(size, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
if (copy_from_user(kbuf, buf, size)) {
kfree(kbuf);
return -EFAULT;
}
/* Initialize i2c_msg for writing */
/* First message: the register address, Second message: data */
err = i2c_transfer(g_client->adapter, msgs, 2);
kfree(kbuf);
return (err < 0) ? err : size;
}
static unsigned int i2c_drv_poll(struct file *fp, poll_table *wait)
{
poll_wait(fp, &gpio_wait, wait);
/* Returning available read/write events */
// return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
return 0;
}
static int i2c_drv_fasync(int fd, struct file *file, int on)
{
return fasync_helper(fd, file, on, &i2c_fasync) >= 0 ? 0 : -EIO;
}
/* Define file_operations structure */
static struct file_operations i2c_drv_fops = {
.owner = THIS_MODULE,
.read = i2c_drv_read,
.write = i2c_drv_write,
.poll = i2c_drv_poll,
.fasync = i2c_drv_fasync,
};
/* Probe function called when a compatible I2C device is found */
static int i2c_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* Save the client handle for later use */
g_client = client;
/* Register the character device */
major = register_chrdev(0, "my_i2c_device", &i2c_drv_fops);
if (major < 0)
return major;
/* Create device class and device file node */
my_i2c_class = class_create(THIS_MODULE, "my_i2c_class");
if (IS_ERR(my_i2c_class)) {
unregister_chrdev(major, "my_i2c_device");
return PTR_ERR(my_i2c_class);
}
device_create(my_i2c_class, NULL, MKDEV(major, 0), NULL, "myi2c");
return 0;
}
/* Remove function to cleanup on driver removal */
static int i2c_drv_remove(struct i2c_client *client)
{
/* Destroy the device and class */
device_destroy(my_i2c_class, MKDEV(major, 0));
class_destroy(my_i2c_class);
unregister_chrdev(major, "my_i2c_device");
return 0;
}
/* Device tree match table */
static const struct of_device_id my_i2c_dt_match[] = {
{
.compatible = "myvendor,my_i2c_device" },
{
},
};
/* I2C driver structure */
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_driver",
.owner = THIS_MODULE,
.of_match_table = my_i2c_dt_match,
},
.probe = i2c_drv_probe,
.remove = i2c_drv_remove,
};
/* Module initialization function */
static int __init i2c_drv_init(void)
{
return i2c_add_driver(&my_i2c_driver);
}
/* Module exit function */
static void __exit i2c_drv_exit(void)
{
i2c_del_driver(&my_i2c_driver);
}
/* Module macros */
module_init(i2c_drv_init);
module_exit(i2c_drv_exit);
MODULE_LICENSE("GPL");
测试:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
static int fd;
int main(int argc, char **argv)
{
int val;
struct pollfd fds[1];
int timeout_ms = 5000;
int ret;
int flags;
int i;
if (argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR | O_NONBLOCK);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
for (i = 0; i < 10; i++)
{
if (read(fd, &val, 4) == 4)
printf("get button: 0x%x\n", val);
else
printf("get button: -1\n");
}
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
while (1)
{
if (read(fd, &val, 4) == 4)
printf("get button: 0x%x\n", val);
else
printf("while get button: -1\n");
}
close(fd);
return 0;
}
4.2 分析
i2c_add_driver()
当作platform_driver_register吧
可以在上面编写的init中看到,调用了i2c_add_driver:
i2c_add_driver
是一个 Linux 内核函数,用于注册一个 I2C 驱动程序。当你有一个新的 I2C 设备驱动程序时,可以使用这个函数将其注册到内核中,以便在相应的 I2C 总线上匹配到设备时调用该驱动程序。
/* Device tree match table */
static const struct of_device_id my_i2c_dt_match[] = {
{
.compatible = "myvendor,my_i2c_device" },
{
},
};
/* I2C driver structure */
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_driver",
.owner = THIS_MODULE,
.of_match_table = my_i2c_dt_match,
},
.probe = i2c_drv_probe,
.remove = i2c_drv_remove,
};
/* Module initialization function */
static int __init i2c_drv_init(void)
{
return i2c_add_driver(&my_i2c_driver);
}
之前不是在内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇-CSDN博客讲解了关于内核提供的i2c-dev.c设备驱动程序吗,它里面的init的函数是这样的:
static int __init i2c_dev_init(void)
{
int res; // 用于存储函数调用的返回结果
// 打印内核信息,通知已启动 I²C 字符设备驱动
printk(KERN_INFO "i2c /dev entries driver\n");
// 注册字符设备区域,指定主设备号和次设备号范围
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
if (res)
goto out; // 如果注册失败,跳转到错误处理
// 创建一个设备类,用于 sysfs 中设备的表示和设备节点的自动创建
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class); // 获取错误码
goto out_unreg_chrdev; // 跳转到字符设备取消注册
}
i2c_dev_class->dev_groups = i2c_groups; // 设置设备类的属性组
// 注册 I²C 总线的通知器,用于追踪总线上的设备添加和移除事件
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class; // 如果通知器注册失败,跳转到类注销
// 绑定当前系统中已存在的 I²C 适配器
i2c_for_each_dev(NULL, i2cdev_attach_adapter); //---- (1)进入i2cdev_attach_adapter函数看下
// 成功返回 0 表示初始化完成
return 0;
out_unreg_class:
// 如果类创建失败,销毁之前创建的类
class_destroy(i2c_dev_class);
out_unreg_chrdev:
// 如果字符设备区域注册失败,取消设备号注册
unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
// 打印错误信息,表明驱动初始化失败
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res; // 返回错误码
}
可以看到并没有像我们所编写的驱动框架那样去调用到i2c_add_driver()
,其实来看一下这个函数内部实现,我们可以发现它和i2c-dev.c驱动模板的init函数其实是差不多的:
\Linux-4.9.88\include\linux\i2c.h
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver) //进入看
\Linux-4.9.88\drivers\i2c\i2c-core.c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (WARN_ON(!is_registered))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
INIT_LIST_HEAD(&driver->clients);
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver); //诶,注册驱动
if (res)
return res;
pr_debug("driver [%s] registered\n", driver->driver.name);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver); //猜猜,没错,这里就是找到adapter进行绑定
/*
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return 0;
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
*/
return 0;
无非就是注册驱动绑定该i2c设备的adapter等,至于register_chrdev 注册、初始化字符设备等操作,使其在/dev下显示出来,则放在了probe中实现,其实道理是一样的。
i2c_transfer()
i2c_transfer
是 Linux 内核中用于在 I2C 总线上进行数据传输的函数之一。它允许驱动程序向设备发送数据或从设备接收数据。
以下是 i2c_transfer
函数的原型:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
参数解释:
adap
:指向表示特定 I2C 适配器的struct i2c_adapter
结构的指针。这个结构提供了对 I2C 总线的访问方法。msgs
:一个指向struct i2c_msg
结构数组的指针,其中每个元素描述了一条 I2C 消息(包含了要发送的数据、接收的数据、目标设备地址等信息)。num
:指定了消息数组的元素数量。
struct i2c_msg
结构包含了一条 I2C 消息的描述信息,包括以下字段:
addr
:目标设备的 I2C 地址。flags
:标志位,指示是读操作、写操作还是特殊控制信息。len
:要传输的数据的字节数。buf
:指向包含要传输数据的缓冲区的指针。
调用 i2c_transfer
函数后,内核将按照消息数组中的顺序执行每一条消息,通过相应的 I2C 适配器在 I2C 总线上进行数据传输。根据每条消息的属性,数据可以从设备读取到缓冲区中,也可以从缓冲区写入到设备中。
这个函数通常由 I2C 设备驱动程序使用,用于与连接在 I2C 总线上的设备进行通信。
这个就不多讲了,主要是去配置i2c_msg的相关成员,然后就可以实现传输,要想深入了解可以看以往的内容:
内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇-CSDN博客
I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
需要特别注意的是,这个i2c_transfer函数(i2c_core.c提供的接口)是将app要传输的数据 借助i2c device driver传输给i2c控制器,其内部是调用了adapter->algo->master_xfer的,也就是i2c控制器的驱动(比如i2c-imx.c)中定义的函数(i2c_imx_xfer,下一文章将讲解)去传输给对应的实际的i2c硬件设备: