I2C子系统详解2——I2C核心层源码分析

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、前言

由I2C总线设备的驱动框架可知,I2C总线设备驱动框架涉及的文件如下:

(1)I2C设备驱动层相关的文件
x210开发板的电容触摸屏gslX680采用I2C接口,因此以这个I2C设备为例进行分析。

其对应的驱动源代码文件是x210_kernel\drivers\input\touchscreen\gslX680.c文件。

它由触摸屏IC原厂工程师提供,代码中涉及到很多触摸屏专业方面的知识,但无需理会。

gslX680.c文件进行了I2C设备驱动的注册,相应的I2C设备注册是在mach-x210.c文件中。

(2)I2C核心层相关的文件
I2C核心层相关的文件是i2c-core.c文件,它由内核开发者提供,和具体的硬件操作无关。

(3)I2C总线驱动层相关的文件
algos目录:存放的是I2C通信的算法(主要是时序等内容)。

busses目录:存放的是各种已经编写好的、将来要向i2c核心层注册的适配器文件。

我们主要分析drivers\i2c\busses\i2c_s3c2410.c文件。


下面我们将对I2C核心层相关的文件进行分析,主要分析i2c-core.c文件

二、I2C核心层源码分析

1、I2C子系统的注册函数:i2c_init()

i2c子系统实现为一个模块,在内核配置的时候可以进行动态的加载和卸载。

struct bus_type i2c_bus_type = {.name        = "i2c",                 //  总线的名字.match        = i2c_device_match,     //  总线下设备与设备驱动的匹配函数.probe        = i2c_device_probe,     //  总线层的probr函数.remove        = i2c_device_remove,   //  总线卸载时执行的函数.shutdown    = i2c_device_shutdown,.pm        = &i2c_device_pm_ops,      //  电源管理
};
static int __init i2c_init(void)
{int retval;retval = bus_register(&i2c_bus_type);   // 注册i2c总线  /sys/bus/i2cif (retval)return retval;
#ifdef CONFIG_I2C_COMPATi2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;}
#endifretval = i2c_add_driver(&dummy_driver);  //注册一个空设备驱动/sys/bus/i2c/driver/dummyif (retval)goto class_err;return 0;class_err:
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endifbus_unregister(&i2c_bus_type);return retval;
}/* We must initialize early, because some subsystems register i2c drivers* in subsys_initcall() code, but are linked (and initialized) before i2c.*/
postcore_initcall(i2c_init);

2、略过smbus代码

因为smbus是基于I2C总线发展出来的。

3、i2c_device_match函数分析

函数内容如下:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{   //  通过dev指针获取到对应的i2c_client指针struct i2c_client    *client = i2c_verify_client(dev);   struct i2c_driver    *driver;   // 定义一个i2c_driver 指针if (!client)return 0;//  通过device_driver指针获取到对应的i2c_driver指针driver = to_i2c_driver(drv);  /* match on an id table if there is one *///  如果设备驱动中存在id_table表,则通过这个来进行与设备的匹配//  匹配的方式就是比较 id_table指向的i2c_device_id数组中各个元素的名字if (driver->id_table)           //  如果匹配成功就会返回这个 i2c_device_id 元素项地址                      return i2c_match_id(driver->id_table, client) != NULL;  return 0;//  匹配失败返回 0
}

由i2c_match_id函数的分析可知,在i2c总线下的设备与驱动的匹配,是将设备的名字和驱动的i2c_device_id表中的各个表项的名字依次进行对比,只要有一个匹配成功,则表示设备和驱动匹配成功,如果都没有匹配成功则表明匹配失败。

对比平台总线的匹配方法,两者基本一致,但是平台总线的匹配方法多了一个步骤,即还会将设备的名字和驱动的名字进行匹配,如果这个也没有成功才表明设备与设备驱动匹配过程失败。

4、i2c_device_probe函数分析

函数内容如下:

static int i2c_device_probe(struct device *dev)
{    //  通过device指针获取到对应的i2c_client指针struct i2c_client    *client = i2c_verify_client(dev);  struct i2c_driver    *driver;int status;if (!client)return 0;//  通过device->driver指针获取到对应的i2c_driver指针driver = to_i2c_driver(dev->driver);                    if (!driver->probe || !driver->id_table)return -ENODEV;
//  i2c设备通过i2c_client->driver指针去指向与他匹配成功的设备驱动i2c_driverclient->driver = driver;                           if (!device_can_wakeup(&client->dev))device_init_wakeup(&client->dev,client->flags & I2C_CLIENT_WAKE);dev_dbg(dev, "probe\n");
// 调用设备驱动层的probe函数status = driver->probe(client, i2c_match_id(driver->id_table, client));  if (status) {client->driver = NULL;i2c_set_clientdata(client, NULL);}return status;
}

总结:I2C总线上有2条分支,即i2c_client链和i2c_driver链。当注册任何一个driver或者client时,I2C总线调用match函数去对client.name和driver.id_table.name进行循环匹配。若driver.id_table中所有的id都匹配不上,则说明client没有找到对应的driver;如果匹配上则表明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。

5、核心层开放的注册接口

(1)i2c_add_adapter函数 / i2c_add_numbered_adapter函数

这两个函数用来向核心层注册一个适配器。两者的区别在于,i2c_add_adapter函数自动分配适配器编号,而i2c_add_numbered_adapter函数需要手动指定一个适配器编号。

分析可知,这两个函数都调用了i2c_register_adapter函数,而i2c_register_adapter函数是I2C子系统核心层提供给I2C总线驱动层的、用来向核心层注册一个适配器的接口函数。

1)i2c_register_adapter函数及分析

//  向i2c总线注册适配器adapter
static int i2c_register_adapter(struct i2c_adapter *adap)   
{int res = 0, dummy;/* Can't register until after driver model init */if (unlikely(WARN_ON(!i2c_bus_type.p))) {res = -EAGAIN;goto out_list;}rt_mutex_init(&adap->bus_lock);//  初始化i2c_adapter->userspace_clients链表INIT_LIST_HEAD(&adap->userspace_clients);   /* Set default timeout to 1 second if not already set */if (adap->timeout == 0)adap->timeout = HZ;//  设置适配器设备的名字   i2c-%d(nr)dev_set_name(&adap->dev, "i2c-%d", adap->nr);   adap->dev.bus = &i2c_bus_type;              //  设置设备的总线类型adap->dev.type = &i2c_adapter_type;     //  设置设备的设备类型
//  注册设备,如果前面没有指定父设备那么创建的设备文件是: /sys/devices/i2c-%dres = device_register(&adap->dev);     if (res)
//  samsung在注册适配器的时候是指定了父设备的,所以他创建的设备是: 
//  /sys/devices/platform/s3c2410-i2cn/i2c-%dgoto out_list;                //  为什么是这个会在后面说到dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);#ifdef CONFIG_I2C_COMPATres = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,adap->dev.parent);if (res)dev_warn(&adap->dev,"Failed to create compatibility class link\n");
#endif/* create pre-declared device nodes */if (adap->nr < __i2c_first_dynamic_bus_num)
//   扫描__i2c_board_list链表上挂接的所有的i2c次设备信息并与适配器进行匹配,
//   匹配成功创建i2c次设备i2c_scan_static_board_info(adap);          /* Notify drivers */mutex_lock(&core_lock);dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,__process_new_adapter);mutex_unlock(&core_lock);return 0;out_list:mutex_lock(&core_lock);idr_remove(&i2c_adapter_idr, adap->nr);mutex_unlock(&core_lock);return res;
}

2)i2c_scan_static_board_info函数及分析

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{struct i2c_devinfo    *devinfo; //   定义一个i2c_devinfo 结构体指针down_read(&__i2c_board_lock);
//  遍历 __i2c_board_list 链表上的所有i2c_devinfo 结构体
//  比较 i2c_devinfo->busnum 与 适配器的编号是否匹配
//  如果匹配就会调用 i2c_new_device 函数进行注册添加新的次设备 i2c_clientlist_for_each_entry(devinfo, &__i2c_board_list, list) {   if (devinfo->busnum == adapter->nr                   && !i2c_new_device(adapter,                   &devinfo->board_info))dev_err(&adapter->dev,"Can't create device at 0x%02x\n",devinfo->board_info.addr);}up_read(&__i2c_board_lock);
}

3)i2c_new_device函数及分析

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{struct i2c_client    *client;                  //  定义一个  i2c_client  指针int            status;client = kzalloc(sizeof *client, GFP_KERNEL);  //  申请分配if (!client)return NULL;//   对i2c_client结构体变量进行填充client->adapter = adap;                               //  i2c次设备通过i2c_client->adapter指针去指向与它匹配成功的适配器i2c_adapterclient->dev.platform_data = info->platform_data;  //  将传进来的i2c_board_info结构体作为i2c次设备的platform平台数据if (info->archdata)client->dev.archdata = *info->archdata;client->flags = info->flags;   //  标志位client->addr = info->addr;     //  i2c次设备的地址client->irq = info->irq;       //  中断号strlcpy(client->name, info->type, sizeof(client->name));  //  名字/* Check for address validity */status = i2c_check_client_addr_validity(client);          //   次设备地址校验if (status) {dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);goto out_err_silent;}/* Check for address business */status = i2c_check_addr_busy(adap, client->addr);if (status)goto out_err;client->dev.parent = &client->adapter->dev;   //  指定i2c 次设备的父设备是与它匹配成功的适配器对应的设备client->dev.bus = &i2c_bus_type;              //  指定次设备的总线类型client->dev.type = &i2c_client_type;          //  指定次设备的设备类型
#ifdef CONFIG_OFclient->dev.of_node = info->of_node;
#endifdev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), //  设置次设备的名字    %d-%04xclient->addr);status = device_register(&client->dev);                     //  注册次设备:   /sys/devices/platform/s3c2410-i2cn/i2c-%d/%d-%04xif (status)goto out_err;dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",client->name, dev_name(&client->dev));return client;out_err:dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, status);
out_err_silent:kfree(client);return NULL;
}

i2c_new_device函数是I2C核心层提供给设备驱动层的、用来注册i2c设备的接口函数,我们可以在直接调用这个函数来注册I2C设备。

在这里I2C核心层还提供了另外一种机制来实现自动注册。其原理是,在系统启动时,在硬件初始化函数中(例如 smdkc110_machine_init 函数里)注册板子的I2C从设备(实际上就是构建i2c_devinfo结构体变量,挂接到__i2c_board_list链表上),当我们向I2C总线核心层注册适配器时就会去扫描该链表,然后根据相关的信息去注册I2C从设备。这也就是上面那个函数的意义。  

smdkc110_machine_init
{
//省略部分代码platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));    /* 这个是设置的是SoC中的i2c控制器(适配器)作为平台设备的私有数据 */s3c_i2c1_set_platdata(NULL);  // 通过这个函数注册板子上的i2c次设备的信息i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs0));   //省略部分代码
}

(2)i2c_add_driver函数分析

在i2c_add_driver函数的内部,调用了I2C核心层的i2c_register_driver函数,来注册一个I2C总线下的设备驱动。i2c_register_driver函数内容如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{int res;/* Can't register until after driver model init */if (unlikely(WARN_ON(!i2c_bus_type.p)))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;            //  指定该设备驱动的总线类型  i2c/* When registration returns, the driver core* will have called probe() for all matching-but-unbound devices.*/res = driver_register(&driver->driver);          //  注册设备驱动   /sys/bus/i2c/drivers/dummy     dummy就是一个设备驱动文件if (res)return res;pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);INIT_LIST_HEAD(&driver->clients);                //   初始化i2c_driver -> clients 链表/* Walk the adapters that are already present */mutex_lock(&core_lock);bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver);   //  可以忽略这条语句的执行效果mutex_unlock(&core_lock);return 0;
}

6、总结

从上面的分析可知,i2c子系统内部存在着2个匹配过程:

(1)i2c总线下的设备与设备驱动之间的匹配(通过设备驱动的id_table)。

(2)适配器与设备之间的匹配(通过适配器编号)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考复习之路—组成原理

计算机系统的基础知识应该是作为一个编程人员必备的一门课程&#xff0c;仅仅有了解了计算机的组成&#xff0c;程序在计算 机中的存储状态&#xff0c;运算等基本内容&#xff0c;我们才干继续对计算机有更深层次的认识&#xff0c;更easy学习与上手。比方说要 想学习操作系统…

python内存管理机制错误_Python内存管理机制和垃圾回收机制的简单理解

一、内存管理机制1.由c开发出来的cpython2.include / objests3.需要下载python源码包4.Pyobject&#xff1a;floatPyVarObject&#xff1a;5.在python中所有东西创建对象的时候&#xff0c;内部都会存储一个数据// 维护双向链表struct _object *_ob_next;struct _object *_ob_p…

C#中几种数据库的大数据批量插入

C#语言中对SqlServer、Oracle、SQLite和MySql中的数据批量插入是支持的&#xff0c;不过Oracle需要使用Orace.DataAccess驱动。 IProvider里有一个用于实现批量插入的插件服务接口IBatcherProvider。批量插入的实现可以通过实现该接口来实现。 /// <summary>/// 提供数据…

求背包问题所有解(C++实现)

这是我学习数据结构时的一道上机作业&#xff0c;那时还没养成写注释的习惯&#xff0c;所以各位得受点苦了。 只是简易背包问题。 代码&#xff1a; 展开 1 // 背包问题所有解2 // 作者:王锦 3 // 邮箱:jinkswvip.qq.com4 5 #include "stdafx.h"6 #include <iost…

I2C子系统详解3——I2C总线驱动层代码分析

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、前言 由I2C总线设备的驱动框架可知&#xff0c;I2C总线设备的驱动框架涉及的文件如下&#xff1a; &#xff08;1&#xff09;I2C设备驱动层相关的文件 x210开发板的电容触摸屏gslX680采用I2C接…

java join 异常_Java:守护进程:thread.join()没有完成,当在一个线程中抛出异常时...

我写了一个Java守护进程(一个实现守护进程和Runnable的类)&#xff0c;现在我遇到了以下问题&#xff1a;在init()中&#xff0c;我创建了一个新线程 . Thread thread new Thread(this); 在start()中我启动新线程 . thread.start() . 在运行中我做了很多不同的事情......然后发…

硬链接与符号链接的比较?

今天就说说硬链接&#xff08;实体链接&#xff09;与符号链接&#xff08;类似Windows的快捷方式&#xff09;的不同?首先我们应知道&#xff1a;每个档案都会占用一个inode ,档案内容由 inode记录来指向;想要读取该档案&#xff0c;必须要经过目录记录的文件名来指向正确的i…

JAVA--自制斐波那契数列输出

累了&#xff0c;写点简单的。 1 public class hello {2 3 /**4 * param args5 */6 public static void main(String[] args) {7 int Fabnum 10;8 int sum 0;9 System.out.print("Serial:\t"); 10 for(int i 1…

I2C子系统详解4——I2C设备驱动层代码分析

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、前言 由I2C总线设备的驱动框架可知&#xff0c;I2C总线设备的驱动框架涉及的文件如下&#xff1a; &#xff08;1&#xff09;I2C设备驱动层相关的文件 x210开发板的电容触摸屏gslX680采用I2C接…

golang java rpc_golang两种调用rpc的方法

本文实例讲述了golang两种调用rpc的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;golang的rpc有两种方法进行调用&#xff0c;一种是rpc例子中给的&#xff1a;package mainimport ("net/rpc""net/http""log""net"&quo…

9、C语言 —— 指针的用处

为什么80%的码农都做不了架构师&#xff1f;>>> 1、用函数实现两个数的交换 ‍‍在没用函数之前&#xff0c;可以这样实现‍‍#include <stdio.h>int main() {int a 3;int b 7;int c;printf("交换前&#xff0c;a%d&#xff0c;b%d\n", a, b); …

内核中的竞争状态和互斥(简述)

以下内容源于朱有鹏《物联网大讲堂》课程的学习&#xff0c;如有侵权&#xff0c;请告知删除。 一、概念 &#xff08;1&#xff09;竞争状态&#xff08;简称竟态&#xff09;&#xff1b; &#xff08;2&#xff09;临界段&#xff08;某一段代码&#xff0c;该代码有可能…

MenuetOS

MenuetOS由芬兰人Ville Turjanmaa开发&#xff0c;是一个操作系统&#xff0c;用于和IBM PC兼容的电脑。 它由汇编语言写成&#xff0c;可以存入一只1.44MB的软盘中。 Menuet OS 的32位版本Menuet32使用GNU通用公共许可证发放&#xff0c;但64位版本Menuet64使用自己的协议发放…

php是一种,PHP是一种什么型的语言:()

案例分析一&#xff1a;假定CPU的主频是500MHz。硬盘采用DMA方式进行数据传送&#xff0c;其数据传输率为4MB/s, 每次DMA传输的数据量为8KB, 要求没有任何数据传输被错过。如果CPU在DMA初始化设置和启动硬盘操作等方面用了1000个时钟周期&#xff0c;并且在DMA传送完成后的中断…

java动态代理二cglib

2019独角兽企业重金招聘Python工程师标准>>> java动态代理 转载于:https://my.oschina.net/u/1430510/blog/290215

spring心得6--自动装配知识点讲解及案例分析

1.自动装配&#xff1a; spring3.2以上版本有四种自动装配类型&#xff1a; 1&#xff09;.byName:寻找和属性名相同的bean,若找不到&#xff0c;则装不上。 2&#xff09;.byType:寻找和属性类型相同的bean,找不到,装不上,找到多个抛异常。 3&#xff09;.constructor:按照参数…

中断的上下半部

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 前言 因为输入类设备的输入都是异步事件&#xff0c;因此一般使用中断来处理和响应。 中断处理程序处于中断上下文中&#xff0c;不能和用户空间数据交互&#xff08;不能使用copy_to(from)_usr函数…

arrayPointer

1,分别使用指针加减 int wages[2] {100000000,20000000}; int *pw wages or int *pw &wages[0] 表示指针指向数组的首地址; pw表示地址,*pw表示取值,new分配的动态数组时 指针名称当数组名称使用eg pw[0],pw[2]分别表示指向数组wages的2个数组的元素值; 我们已知pw表示此…

php抓取动态数据,php+ajax实现无刷新动态加载数据技术

我们浏览有些网页的时候&#xff0c;当拉动浏览器的滚动条时到页底时&#xff0c;页面会继续自动加载更多内容供用户浏览。这种技术我暂且称它为滚屏加载技术。我们发现很多网站用到这种技术&#xff0c;必应图片搜索、新浪微博、QQ空间等将该技术应用得淋漓尽致。滚屏加载技术…

图片播放器小项目(详解)

以下内容源于朱有鹏《物联网大讲堂》课程的学习整理&#xff0c;如有侵权&#xff0c;请告知删除。一、开始动手写代码 1、Makefile介绍 &#xff08;1&#xff09;这是一个通用的项目管理的Makefile体系&#xff0c;自己写的&#xff08;有子文件夹组织的&#xff09;项目可以…