Linux I2C驱动框架(超详细)-程序员宅基地

技术标签: 驱动  linux  i2c  

Linux I2C驱动框架

一、几个重要的对象

在讨论I2C驱动框架前,先讨论几个重要的概念

1、I2C总线

struct bus_type i2c_bus_type = {
    
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};

I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备I2C驱动匹配删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动probe函数

特别提示:i2c_device_match会管理I2C设备I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关

2、I2C驱动

struct i2c_driver {
    
	int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
	struct device_driver driver; //表明这是一个驱动
	const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
	int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
	const unsigned short *address_list; //设备地址
	struct list_head clients; //设备链表
};

对应的是I2C驱动程序

3、I2C设备

struct i2c_client {
    
	unsigned short addr; //设备地址
	char name[I2C_NAME_SIZE]; //设备名称
	struct i2c_adapter *adapter; //设配器,值I2C控制器
	struct i2c_driver *driver; //设备对应的驱动
	struct device dev; //表明这是一个设备
	int irq; //中断号
	struct list_head detected; //节点
};

对应的是I2C设备

4、I2C设配器

I2C设配器是什么?

经过上面的介绍,知道有I2C驱动I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C设配器I2C设配器对应的就是SOC上的I2C控制器

struct i2c_adapter {
    
	unsigned int id; //设备器的编号
	const struct i2c_algorithm *algo; //算法,发送时序
	struct device dev; //表明这是一个设备
};

其中的i2c_algorithm是算法的意思,对应的就是如何发送I2C时序

struct i2c_algorithm {
    
    /* 作为主设备时的发送函数 */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);

    /* 作为从设备时的发送函数 */
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);
};

小结

I2C驱动有4个重要的东西,I2C总线I2C驱动I2C设备I2C设备器

  • I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备I2C驱动的匹配和删除等
  • I2C驱动:对应的就是I2C设备的驱动程序
  • I2C设备:是具体硬件设备的一个抽象
  • I2C设配器:用于I2C驱动和I2C设备间的通用,是SOC上I2C控制器的一个抽象

以注册I2C驱动为例,简单讲解I2C总线的运行机制(I2C设备道理相同)

  • 1、注册I2C驱动
  • 2、将I2C驱动添加到I2C总线的驱动链表中
  • 3、遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数
  • 4、i2c_device_probe函数会调用I2C驱动probe函数

经过上面的讲解,对I2C驱动框架应该有了基本的了解了,下面通过分析内核源码来深入学习I2C驱动框架

二、内核源码分析

1、注册I2C驱动

编写I2C驱动程序时,通过调用i2c_add_driver函数来注册驱动,下面来分析这个函数发生了什么

static inline int i2c_add_driver(struct i2c_driver *driver)
{
    
	return i2c_register_driver(THIS_MODULE, driver);
}
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    
    /*
    	struct bus_type i2c_bus_type = {
			.name		= "i2c",
			.match		= i2c_device_match,
			.probe		= i2c_device_probe,
			.remove		= i2c_device_remove,
			.shutdown	= i2c_device_shutdown,
			.pm		= &i2c_device_pm_ops,
		};
    */
    driver->driver.bus = &i2c_bus_type; // 绑定总线
    
    driver_register(&driver->driver); // 向总线注册驱动
    
    /* 遍历I2C总线上所有设备,调用__process_new_driver函数 */
    bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver);
}

上面的程序可以看到,在调用i2c_add_driver后做了三件事,第一绑定总线,要记住这个总线结构体,第二向总线注册驱动,这算是I2C驱动框架的重点,第三遍历总线的设备,调用__process_new_driver函数

乍一看,会绝对第三件事比较重要,好像匹配规则在这里面,其实这里面有一部分匹配规则,但并不是最重要的,最重要的是在driver_register函数中

下面将重点分析driver_register函数

int driver_register(struct device_driver *drv)
{
    
    bus_add_driver(drv); // 将驱动添加到总线上
}
int bus_add_driver(struct device_driver *drv)
{
    
    driver_attach(drv);
    
    /* 将驱动添加到总线的驱动链表(bus->p->klist_drivers) */
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); 
}

接下来分析driver_attach(drv)这个函数,这个函数是重头

int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

来看看bus_for_each_dev函数做了什么

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
    
    /* 遍历总线的所有设备链表(bus->p->klist_devices)的所有设备,执行fn函数 */
	while ((dev = next_device(&i)) && !error)
		error = fn(dev, data);
}

会到上一个函数,我们知道bus_for_each_dev对应的fn函数是__driver_attach函数,也就是说,driver_attach函数会遍历总线上的所有设备执行__driver_attach函数,接下面来分析__driver_attach函数

static int __driver_attach(struct device *dev, void *data)
{
    
    /* 判断驱动和设备是否匹配 */
	if (!driver_match_device(drv, dev))
		return 0;
    
    /* 如果匹配的话,调用driver_probe_device函数 */
    if (!dev->driver)
		driver_probe_device(drv, dev);
}

首先来看一看driver_match_device函数,通过这个函数我们可以知道匹配规则

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
    
    /* 调用总线的macth函数 */
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

请不要忘了现在的总线式i2c总线,对应的结构体上面已经给出,如下

struct bus_type i2c_bus_type = {
    
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};

drv->bus->match对应的就是i2c_bus_type.match

下面来分析i2c_device_match函数

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    
    struct i2c_client	*client = i2c_verify_client(dev); // 有device获得i2c_client
    struct i2c_driver	*driver = to_i2c_driver(drv); // 有device_driver获得i2c_driver
    
    /* 调用i2c_match_id进行匹配 */
    return i2c_match_id(driver->id_table, client) != NULL;
}

driver->id_table是什么,来看一看id_table的定义

struct i2c_device_id {
    
	char name[I2C_NAME_SIZE];
};

其实就是一个字符串,表示设备的名字

i2c_driver中有一个我们自己i2c_device_id数组,用来匹配i2c设备

来看看怎么匹配

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
    
    /* 字符串匹配 */
	strcmp(client->name, id->name);   
}

可以知道匹配规则是

strcmp(i2c_client->name, i2c_driver->i2c_device_id->name); 

就是匹配驱动和设备中的名字

好了,现在弄清楚匹配规则了,下面来看一看如果匹配成功后要干嘛

回到下面这个函数

static int __driver_attach(struct device *dev, void *data)
{
    
    /* 判断驱动和设备是否匹配 */
	if (!driver_match_device(drv, dev))
		return 0;
    
    /* 如果匹配的话,调用driver_probe_device函数 */
    if (!dev->driver)
		driver_probe_device(drv, dev);
}

可以看到匹配成功后,会调用driver_probe_device函数,下面看一看这个函数

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    
	really_probe(dev, drv);
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
    
    /* 调用了总线的probe函数 */
    dev->bus->probe(dev);
}
struct bus_type i2c_bus_type = {
    
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};

可以知道,总线的probe函数是i2c_device_probe

static int i2c_device_probe(struct device *dev)
{
    
    /* 调用i2c驱动的probe函数 */
    driver->probe(client, i2c_match_id(driver->id_table, client));
}

通过上面的分析,可以知道i2c总线进制,当向内核注册i2c驱动时,会将i2c驱动添加到总线的链表中,遍历总线上所有设备,通过i2c_client->name, i2c_driver->i2c_device_id->name进行字符串匹配,如果匹配,就调用驱动程序的probe函数

继续回到上面的程序

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    
    /*
    	struct bus_type i2c_bus_type = {
			.name		= "i2c",
			.match		= i2c_device_match,
			.probe		= i2c_device_probe,
			.remove		= i2c_device_remove,
			.shutdown	= i2c_device_shutdown,
			.pm		= &i2c_device_pm_ops,
		};
    */
    driver->driver.bus = &i2c_bus_type; // 绑定总线
    
    driver_register(&driver->driver); // 向总线注册驱动
    
    /* 遍历I2C总线上所有设备,调用__process_new_driver函数 */
    bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver);
}

刚刚我们分析了driver_register,下面来分析一下bus_for_each_dev

其实bus_for_each_dev蕴含着i2c总线的另外一种匹配规则,不过不经常使用

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
    
    /* 遍历总线上的设备链表(bus->p->klist_devices)的所有设备,调用fn函数 */
	while ((dev = next_device(&i)) && !error)
		error = fn(dev, data);	
}

由上面可知,fn函数对应的是__process_new_driver函数

static int __process_new_driver(struct device *dev, void *data)
{
    
    /* data对应驱动,to_i2c_adapter(dev)对应设备对应的适配器 */
	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
			      struct i2c_adapter *adap)
{
    
	i2c_detect(adap, driver);
}
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
    
    address_list = driver->address_list; // 获取驱动指定的地址
    
    temp_client->adapter = adapter; // 绑定好设配器(I2C控制器)
    
    /* 使用该是适配器,去探测所有地址 */
    for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1)
        i2c_detect_address(temp_client, driver);
}
static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
    
    i2c_check_addr_busy(adapter, addr);
        
    i2c_default_probe(adapter, addr);
    
    /* 如果能到达这里,就说明该i2c控制器对应的总线上,该地址存在,调用驱动的detect函数进一步确认 */
    driver->detect(temp_client, &info);
    
    /* 如果驱动程序确认的话,生成i2c设备 */
    client = i2c_new_device(adapter, &info);
    list_add_tail(&client->detected, &driver->clients); // 将该设备添加到驱动程序的链表中
}

以上的做法是,遍历总线上的所有设备,拿到设备对应的设备器(I2C控制器),去给总线发送驱动指定好的地址,如果地址存在的话,就调用驱动的detect函数

为什么会有这种方式?

如果在不知道i2c设备在哪一条总线的情况下,这种方式就发挥了作用

上面分析了注册I2C驱动,下面来分析注册I2C设备

2、注册I2C设备

内核通过i2c_new_device注册i2c设备

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

需要指定i2c设配器和设备信息

i2c_adapter可以通过i2c_get_adapter来获取

struct i2c_adapter *i2c_get_adapter(int id)

i2c_get_adapter可以获取哪个i2c控制器,获取哪一个i2c控制器取决于你的i2c设备接在哪条i2c总线上

i2c_board_info结构体描述了设备的硬件信息

struct i2c_board_info {
    
	char		type[I2C_NAME_SIZE]; // 设备名称,用于与驱动匹配
	unsigned short	addr; // 设备地址
	int		irq; // 中断号
};

下面来详细分析i2c_new_device函数

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    
    struct i2c_client	*client;
    
    client->adapter = adap; // 设定设备的设配器
    client->addr = info->addr; // 地址
    client->irq = info->irq; // 中断号
    
    client->dev.bus = &i2c_bus_type; // 绑定总线
    
    device_register(&client->dev); // 向总线注册设备
    
    return client;
}

下面分析device_register的过程

int device_register(struct device *dev)
{
    
	device_initialize(dev);
	return device_add(dev);
}
int device_add(struct device *dev)
{
    
    bus_add_device(dev);
    
    bus_probe_device(dev);
}
int bus_add_device(struct device *dev)
{
    
    /* 将设备添加到总线的设备链表中(bus->p->klist_devices) */
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
void bus_probe_device(struct device *dev)
{
    
	device_attach(dev);
}
int device_attach(struct device *dev)
{
    
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
		     void *data, int (*fn)(struct device_driver *, void *))
{
    
    /* 遍历总线的驱动链表上的所有驱动,调用fn函数 */
	while ((drv = next_driver(&i)) && !error)
		error = fn(drv, data);
}

这里的fn函数指的是__device_attach函数

static int __device_attach(struct device_driver *drv, void *data)
{
    
    /* 判断是够匹配 */
	if (!driver_match_device(drv, dev))
		return 0;

	return driver_probe_device(drv, dev);
}

driver_match_devicedriver_probe_device函数跟上面分析的完全相同,这里不再累赘

至此注册i2c设备已经分析完,流程为,注册i2c设备,将i2c加入总线的设备链表中,调用总线的匹配函数判断是够匹配,如果匹配,就调用驱动的probe函数

内核还有静态一种注册i2c设备的方法

通过i2c_register_board_info注册,起始最后还是调用了i2c_new_device,这里简单分析一下

int __init
i2c_register_board_info(int busnum,
	struct i2c_board_info const *info, unsigned len)
{
    
    list_add_tail(&devinfo->list, &__i2c_board_list); // 将设备信息添加到链表中
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    
	list_for_each_entry(devinfo, &__i2c_board_list, list) {
    
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
	}
}

3、驱动如果使用设配器给设备发送数据

通过上面的分析,我们已经知道了i2c总线的工作机制,下面来看看当i2c设备和i2c驱动匹配之后,驱动程序要怎么去和设备通讯

驱动程序一般调用i2c_transfer来发送信息

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

需要指定i2c设配器

上面说过驱动程序是 i2c设配器向设备发送数据的,那么i2c_transferv底层肯定是通过i2c_adapter发送数据的

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    
    adap->algo->master_xfer(adap, msgs, num); // 通过设配器发送数据
}

三、总结

i2c总线维护着两个链表,一个驱动链表,一个设备链表,每当注册进一个驱动(或设备),就会将其添加到总线上相应的链表上,然后遍历总线的设备(或驱动)链表的所有设备(或驱动),通过总线的匹配函数判断是够匹配,如果匹配,则调用驱动的probe函数,然后我们就可以在probe函数注册字符设备,创建设备节点,实现fops集等等

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42462202/article/details/91128914

智能推荐

Aubo机械臂初学(愁)——1、gazebo和Rviz联合仿真_aubo机械臂仿真-程序员宅基地

文章浏览阅读1.7k次,点赞5次,收藏33次。auboi5机械臂初学者遇到的各种问题合集_aubo机械臂仿真

POJ_1064_Cable master【二分】_poj - 1064 二分枚举答案 floor向下取整函数 原创-程序员宅基地

文章浏览阅读1.7k次。/*Cable masterTime Limit: 1000MS Memory Limit: 10000KTotal Submissions: 43878 Accepted: 9409DescriptionInhabitants of the Wonderland have decided to hold a regional programmi_poj - 1064 二分枚举答案 floor向下取整函数 原创

【前端学习】HTML学习笔记-table_table前端心得-程序员宅基地

文章浏览阅读88次。<table><colgroup><col bgcolor='red' width=200></colgroup><thead><tr><th></th></tr><tbody><tr><td></td></t..._table前端心得

CSS 之 line-height 实现单行文字垂直居中的原理_css height=line-height 可以垂直居中-程序员宅基地

文章浏览阅读1.5k次,点赞3次,收藏12次。基础知识line-height 与 font-size 的计算值之差(在 CSS 中成为“行间距”)分为两半,分别加到一个文本行内容的顶部和底部。我们暂且称之为顶部距离和底部距离,就是上图中的蓝色区域。也就是说: line-height = 顶部距离 + 内容高度(顶线和底线之间的距离) + 底部距离;顶部距离 = 底部距离;示例一: 当line-height 等于 height 时,文字垂直居中文本默认大小16px。结果:文字垂直居中。顶部距离 = 底部距离 = (line-heig_css height=line-height 可以垂直居中

uniapp实战——实现详情其他部分的结构_uniapp 实现关系图谱-程序员宅基地

文章浏览阅读241次。QQ 1274510382Wechat JNZ_aming商业联盟 QQ群538250800技术搞事 QQ群599020441解决方案 QQ群152889761加入我们 QQ群649347320共享学习 QQ群674240731纪年科技aming网络安全 ,深度学习,嵌入式,机器强化,生物智能,生命科学。叮叮叮:产品已上线 —>关注 官方认证-微信公众号——济南纪年信息科技有限公司民生项目:商城加盟/娱乐交友/创业商圈/外包兼职开发-项目发布/安全项目:态.._uniapp 实现关系图谱

如何查看其他人的ABAP authorization check log_查看authorization-程序员宅基地

文章浏览阅读375次。Created by Jerry Wang on Jul 29, 2014 Go to start of metadata在做middleware相关的scenario操作时,有时候需要evaluate其他user的authorization check log,例如在CRM tcode SMW01里发现BDoc state为validation error,点击show error butto..._查看authorization

随便推点

I.MX6 eMMC分区挂载-程序员宅基地

文章浏览阅读244次。/********************************************************************* * I.MX6 eMMC分区挂载 * 说明: * 如果想要修改分区的挂载情况,可以修改fstab.freescale文件。 * * ..._imx6 分区挂载

【opencv-python】霍夫圆检测_霍夫圆圆心检测python-程序员宅基地

文章浏览阅读6.7k次,点赞10次,收藏55次。霍夫变换检测直线的原理是利用累加器找到最大的(ρ,θ)(ρ,θ)(ρ,θ)数对,如文章所述。圆形的数学表达式为(x−xcenter)2+(y−ycenter)2=r2(x-x_{center})^2+(y-y_{center})^2=r^2(x−xcenter​)2+(y−ycenter​)2=r2,其中(xcenter,ycenter)(x_{center},y_{center})(xcenter​,ycenter​)为圆心坐标,rrr为圆的直径。因此可知一个圆需要xcenter,ycenter,rx_{_霍夫圆圆心检测python

码仔精选,Android面试题-程序员宅基地

文章浏览阅读171次。码个蛋(codeegg) 第 822次推文码妞看世界1.Java创建对象的几种方式使用new关键字使用Class类的newInstance方法使用Constructor类的newIn..._码个蛋 《每日一道面试题》 第一期

Milking Time (poj 3616 简单DP)_poj milking time-程序员宅基地

文章浏览阅读2.5k次,点赞3次,收藏5次。题意:给个时间长度n,m个工作时间段和每个时间段能完成的工作量,一次只能做一个工作并且一旦开始做就要把它做完,要求选择的两个工作时间段之间至少相差r时间(中间需要休息嘛)求选择那些工作n时间内能完成的最大工作量。输出最大值。思路:先按工作的结束时间从小到大排序,再动态规划。dp[i]表示从头开始取到第i段所获得的最大值。二重循环,如果第i段之前的某个段的结束时间加上r小于等于第i段的开始时间,则更新dp[i]。_poj milking time

GDCM:gdcm::Global的测试程序_gbcm main show main screen-程序员宅基地

文章浏览阅读333次。GDCM:gdcm::Global的测试程序GDCM:gdcm::Global的测试程序GDCM:gdcm::Global的测试程序#include "gdcmGlobal.h"#include "gdcmDicts.h"#include "gdcmDict.h"#include "gdcmDefs.h"int TestGlobal(int, char *[]){ // case 1 // Get the global singleton: gdcm::Trace::DebugOn_gbcm main show main screen

理解 OAuth 2.0_shanks user-agent-程序员宅基地

文章浏览阅读278次。转载自http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html作者:阮一峰日期:2014年5月12日OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749。更新:我后来又写了一组三篇的《OAuth 2.0 教程》,更加通俗,并带有代码实例,欢迎阅读。一、应用场景..._shanks user-agent