I2C驱动层级
一张图整理,可以看完后面的具体内容再回来看这张图:
接下来,会按照从上到下的顺序介绍整个驱动架构。
内核自带的通用I2C驱动程序i2c-dev
1.i2c-dev.c注册过程
入口函数中,请求字符设备号,并注册已存在adapters下面的所有i2c设备
同时也会生成对应的设备节点i2c-X,以后只要打开这个设备节点,就是访问该设备,并且该设备的次设备号也绑定了对应的adapter。
2.file_operations函数分析
回忆我们在I2C-TOOLS中调用open、ioctl,最终就会调用到以下驱动结构体的函数。
所以我们就查看open、ioctl。(read、write提供了i2c简易写,读写一个字节)
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.compat_ioctl = compat_i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
2.1 i2cdev_open
open函数里面可以看到上面,入口函数把adap和次设备号绑定,这里就可以使用次设备号访问对应的i2c_adapter;
然后分配一个i2c_client结构体,把它放入file的私有数据
2.2 i2cdev_ioctl
-
设置从机地址I2C_SLAVE/I2C_SLAVE_FORCE
通过file的私有数据就可以获得open函数里面放入的i2c_client
-
读写I2C_RDWR/I2C_SMBUS:最终就是调用i2c-core提供的函数
3.总结
来自韦东山课程
编写一个设备驱动程序
参考资料:
- Linux内核文档:
Documentation\i2c\instantiating-devices.rst
Documentation\i2c\writing-clients.rst
- Linux内核驱动程序示例:
drivers/eeprom/at24.c
1.I2C总线-设备-驱动模型
类似于通用字符设备总线-设备-驱动模型,I2C设备也有一套I2C总线-设备-驱动模型。整体结构如下图:
2.i2c_driver设备驱动
分配、设置、注册一个i2c_driver结构体,类似drivers/eeprom/at24.c
:
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.of_match_table = of_match_ids_example,
.acpi_match_table = ACPI_PTR(at24_acpi_ids),
},
.probe = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};
- 在probe_new函数中,分配、设置、注册file_operations结构体。
- 在file_operations的函数中,使用i2c_transfer等函数发起I2C传输。
参考at24.c的代码,可以给出一个i2c_driver的模板:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
static const struct of_device_id of_match_ids_example[] = {
{
.compatible = "com_name,chip_name", .data = NULL}, // data is private data
{
/* END OF LIST */},
};
static const struct i2c_device_id example_ids[] = {
{
"chip_name", (kernel_ulong_t)NULL},
{
/* END OF LIST */},
};
static int i2c_driver_example_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return 0;
}
static int i2c_driver_example_remove(struct i2c_client *client)
{
return 0;
}
static struct i2c_driver i2c_example_driver = {
.driver = {
.name = "example",
.of_match_table = of_match_ids_example,
},
.probe = i2c_driver_example_probe,
.remove = i2c_driver_example_remove,
.id_table = example_ids,
};
static int __init i2c_driver_example_init(void)
{
return i2c_add_driver(&i2c_example_driver);
}
module_init(i2c_driver_example_init);
static void __exit i2c_driver_example_exit(void)
{
i2c_del_driver(&i2c_example_driver);
}
module_exit(i2c_driver_example_exit);
MODULE_AUTHOR("wanghaicheng.online");
MODULE_LICENSE("GPL");
3.编写I2C设备-AP3216C传感器的i2c_driver设备驱动
AP3216C是红外、光强、距离三合一的传感器,设备地址是0x1E。
以读出光强、距离值为例,步骤如下:
- 复位:往寄存器0写入0x4
- 使能:往寄存器0写入0x3
- 读红外:读寄存器0xA、0xB得到2字节的红外数据
- 读光强:读寄存器0xC、0xD得到2字节的光强
- 读距离:读寄存器0xE、0xF得到2字节的距离值
(1)利用上面的i2c驱动框架,先实现入口与出口函数:入口函数里面添加总线驱动i2c_driver,出口函数里面删除。同时提供of_match_ids_ap3216c匹配i2c_client。
static const struct of_device_id of_match_ids_ap3216c[] = {
{
.compatible = "lite-on,ap3216c", .data = NULL}, // data is private data
{
/* END OF LIST */},
};
static const struct i2c_device_id ap3216c_ids[] = {
{
"ap3216c", (kernel_ulong_t)NULL},
{
/* END OF LIST */},
};
static struct i2c_driver ap3216c_driver = {
.driver =
{
.name = "ap3216c",
.of_match_table = of_match_ids_ap3216c,
},
.probe = i2c_ap3216c_probe,
.remove = i2c_ap3216c_remove,
.id_table = ap3216c_ids,
};
static int __init ap3216c_driver_init(void) {
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return i2c_add_driver(&ap3216c_driver);
}
module_init(ap3216c_driver_init);
static void __exit ap3216c_driver_exit(void) {
i2c_del_driver(&ap3216c_driver);
}
module_exit(ap3216c_driver_exit);
MODULE_AUTHOR("wanghaicheng.online");
MODULE_LICENSE("GPL");
(2)在i2c_driver
的probe
函数里面,注册一个字符设备,使用字符设备的file_operations操作ap3126c。
static int i2c_ap3216c_probe(struct i2c_client *client,
const struct i2c_device_id *id) {
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
ap3216c_client = client;
/* 注册字符设备 */
major = register_chrdev(major, "ap3216c_drv", &ap3216c_fops);
/* 创建设备节点 */
ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c_dev");
return 0;
}
(3)实现字符设备的file_operations
static int major = 0;
static struct class *ap3216c_class;