I2C子系统-内核视角

I2C驱动层级

内核自带的通用I2C驱动程序i2c-dev

编写一个设备驱动程序

控制器驱动程序I2C Adapter框架

GPIO模拟I2C,实现I2C_Adapter驱动

具体芯片下I2C_Adapter驱动

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_driverprobe函数里面,注册一个字符设备,使用字符设备的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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值