首页 > 其他分享 >编写一个通用的i2c设备驱动框架

编写一个通用的i2c设备驱动框架

时间:2024-10-19 22:18:41浏览次数:3  
标签:I2C struct 框架 driver client 编写 i2c 设备

往期内容

I2C子系统专栏:

  1. I2C(IIC)协议讲解-CSDN博客
  2. SMBus 协议详解-CSDN博客
  3. I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
  4. 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
  5. 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
  6. 设备驱动与设备树匹配机制详解

总线和设备树专栏:

  1. 总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客

1.I2C总线驱动

img
I2C Core就是I2C核心层,它的作用:

  • 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
  • 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)

2.框图

在上一章节已经讲过,只要把i2c_client(一个i2c设备的抽象)当作platform_device就行,其实原理都差不多。具体直接看下图就行:

img

除了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 驱动。它包含与设备匹配的规则和驱动的核心操作函数,如 proberemove。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_driveri2c_client 成功匹配后,驱动的 probe 函数会被调用,开始初始化设备。

其实和上一章节讲的的平台设备匹配,道理是几乎相同的,具体看下面链接:

img

img

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()

img

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(IIC)协议讲解-CSDN博客

需要特别注意的是,这个i2c_transfer函数(i2c_core.c提供的接口)是将app要传输的数据 借助i2c device driver传输给i2c控制器,其内部是调用了adapter->algo->master_xfer的,也就是i2c控制器的驱动(比如i2c-imx.c)中定义的函数(i2c_imx_xfer,下一文章将讲解)去传输给对应的实际的i2c硬件设备:

img

标签:I2C,struct,框架,driver,client,编写,i2c,设备
From: https://blog.csdn.net/caiji0169/article/details/143029740

相关文章

  • dify 大模型开源应用框架使用案例,api调用
    参看:https://github.com/langgenius/dify1、安装下载安装:https://docs.dify.ai/getting-started/install-self-hosted/docker-composegitclonehttps://github.com/langgenius/dify.gitcddify##docker安装cddockercp.env.example.envdockercomposeup-d......
  • 网站模板的logo框架修改?后台修改网站内容?
    确定Logo位置打开网站模板的HTML文件,找到放置Logo的HTML元素。通常这个元素会有一个特定的类名或ID,如<divid="logo">或<divclass="logo">。调整CSS样式在CSS文件中找到与Logo相关的样式规则。这些规则可能包括宽度、高度、背景图像、边距等。根据需要调整这些样式。......
  • GoFly框架可以快速且更容易的完成信息及软件开发相关专业同学的毕业论文设计
    前言随着gofly开始开发框架的不断宣传,这段时间有很多软件开发相关专业同学问我们框架是否可以拿来做毕业论文设计技术框架。借助本文给正在选择毕业设计技术或者为将来毕业设计准备的同学介绍一下GoFly框架如何用于毕业设计。介绍之前可以肯定的回答,GoFly框架是完全可以用于毕......
  • GoFly快速开发框架集成ZincSearch全文搜索引擎-ZincSearch是ElasticSearch轻量级替代
    前言我们在项目开发中会遇到如下业务场景:1. 电子商务:实现商品搜索与推荐、价格监控。2. 日志分析:进行系统日志分析和网络流量监控。3. 社交媒体:内容搜索与发现以及用户行为分析。4. 企业知识管理:进行知识搜索与共享和文档版本管理。5. 新闻媒体:实现新闻搜索与推荐以......
  • (系列八).net8 webApi后端框架轮子,欢迎下载。
    说明  该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。   该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。   说明:OverallAuth2.0是一个简单、易懂、功能强大的权限+可视化流程管理系统。友情提醒:本篇文章是属于系......
  • HTTP客户端框架之UniHttp讲解
    目录1UniHttp1.1简介1.1.1前言1.1.2简介1.2简单使用1.2.1引入依赖1.2.2对接接口1.2.3声明定义HttpAPI包扫描路径1.2.4依赖注入使用即可1.3说明介绍1.3.1@HttpApi注解1.3.2@HttpInterface注解1.3.3@Par注解1.3.3.1@QueryPar注解1.3.3.2@PathPar注解1.3.3.3@Heade......
  • ssm基于vue框架的点餐系统的设计与实现+vue
    系统包含:源码+论文所用技术:SpringBoot+Vue+SSM+Mybatis+Mysql免费提供给大家参考或者学习,获取源码请私聊我需要定制请私聊目录摘要 IAbstract II1绪论 11.1研究背景与意义 11.1.1研究背景 11.1.2研究意义 11.2国内外研究现状 21.2.1国外研究现状 21.2.2......
  • ssm基于SSM框架的成绩管理系统的设计与实现+vue
    系统包含:源码+论文所用技术:SpringBoot+Vue+SSM+Mybatis+Mysql免费提供给大家参考或者学习,获取源码请私聊我需要定制请私聊目录1绪论 11.1选题背景 11.2选题意义 11.3研究内容 22系统开发技术 32.1MySQL数据库 32.2IDEA简介 32.3SSM框架 42.4Vue......
  • 【LVGL快速入门(二)】LVGL开源框架入门教程之框架使用(UI界面设计)
    零.前置篇章本篇前置文章为【LVGL快速入门(一)】LVGL开源框架入门教程之框架移植一.UI设计        介绍使用之前,我们要学习一款LVGL官方的UI设计工具SquareLineStudio,使用图形化设计方式设计出我们想要的界面,然后生成对应源文件导入工程使用。详情参考这篇文章:【......
  • ssm640基于ssm框架的博客系统的开发+vuepf
    ......