Linux设备驱动之I2C架构分析

一:前言

I2c是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而I2C是基于multi master机制.一同总线上可允许多个master.关于I2C协议的知识,这里不再赘述.可自行下载spec阅读即可.

二:I2C架构概述

在linux中,I2C驱动架构如下所示:

如上图所示,每一条I2C对应一个adapter.在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作(struct i2c_adapter).再通过i2c core层将i2c设备与i2c adapter关联起来.

这个图只是提供了一个大概的框架.在下面的代码分析中,从下至上的来分析这个框架图.以下的代码分析是基于linux 2.6.26.分析的代码基本位于: linux-2.6.26.3/drivers/i2c/位置.

三:adapter注册

在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.

对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.

分别来看一下这两个函数的代码:

int i2c_add_adapter(struct i2c_adapter *adapter)

{

int id, res = 0;

retry:

if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)

return -ENOMEM;

mutex_lock(&core_lock);

/* "above" here means "above or equal to", sigh */

res = idr_get_new_above(&i2c_adapter_idr, adapter,

__i2c_first_dynamic_bus_num, &id);

mutex_unlock(&core_lock);

if (res < 0) {

if (res == -EAGAIN)

goto retry;

return res;

}

adapter->nr = id;

return i2c_register_adapter(adapter);

}

在这里涉及到一个idr结构.idr结构本来是为了配合page cache中的radix tree而设计的.在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存.避免在内存不够的时候出现问题.所在,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id.以后凭这个id就可以在idr中找到相对应的结构了.对这个数据结构操作不太理解的可以查阅本站<< linux文件系统之文件的读写>>中有关radix tree的分析.

注意一下idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的参数的含义,它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于__i2c_first_dynamic_bus_num,

然后将对应的id号存放在adapter->nr中.调用i2c_register_adapter(adapter)对这个adapter进行进一步注册.

看一下另外一人注册函数: i2c_add_numbered_adapter( ),如下所示:

int i2c_add_numbered_adapter(struct i2c_adapter *adap)

{

int id;

int status;

if (adap->nr & ~MAX_ID_MASK)

return -EINVAL;

retry:

if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)

return -ENOMEM;

mutex_lock(&core_lock);

/* "above" here means "above or equal to", sigh;

* we need the "equal to" result to force the result

*/

status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);

if (status == 0 && id != adap->nr) {

status = -EBUSY;

idr_remove(&i2c_adapter_idr, id);

}

mutex_unlock(&core_lock);

if (status == -EAGAIN)

goto retry;

if (status == 0)

status = i2c_register_adapter(adap);

return status;

}

对比一下就知道差别了,在这里它已经指定好了adapter->nr了.如果分配的id不和指定的相等,便返回错误.

过一步跟踪i2c_register_adapter().代码如下:

static int i2c_register_adapter(struct i2c_adapter *adap)

{

int res = 0, dummy;

mutex_init(&adap->bus_lock);

mutex_init(&adap->clist_lock);

INIT_LIST_HEAD(&adap->clients);

mutex_lock(&core_lock);

/* Add the adapter to the driver core.

* If the parent pointer is not set up,

* we add this adapter to the host bus.

*/

if (adap->dev.parent == NULL) {

adap->dev.parent = &platform_bus;

pr_debug("I2C adapter driver [%s] forgot to specify "

"physical device ", adap->name);

}

sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);

adap->dev.release = &i2c_adapter_dev_release;

adap->dev.class = &i2c_adapter_class;

res = device_register(&adap->dev);

if (res)

goto out_list;

dev_dbg(&adap->dev, "adapter [%s] registered ", adap->name);

/* create pre-declared device nodes for new-style drivers */

if (adap->nr < __i2c_first_dynamic_bus_num)

i2c_scan_static_board_info(adap);

/* let legacy drivers scan this bus for matching devices */

dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,

i2c_do_add_adapter);

out_unlock:

mutex_unlock(&core_lock);

return res;

out_list:

idr_remove(&i2c_adapter_idr, adap->nr);

goto out_unlock;

}

首先对adapter和adapter中内嵌的struct device结构进行必须的初始化.之后将adapter内嵌的struct device注册.

在这里注意一下adapter->dev的初始化.它的类别为i2c_adapter_class,如果没有父结点,则将其父结点设为platform_bus.adapter->dev的名字为i2c + 总线号.

测试一下:

[eric@mochow i2c]$ cd /sys/class/i2c-adapter/

[eric@mochow i2c-adapter]$ ls

i2c-0

可以看到,在我的PC上,有一个I2C adapter,看下详细信息:

[eric@mochow i2c-adapter]$ tree

.

`-- i2c-0

|-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0

|-- name

|-- subsystem -> ../../../class/i2c-adapter

`-- uevent

3 directories, 2 files

可以看到,该adapter是一个PCI设备.

继续往下看:

之后,在注释中看到,有两种类型的driver,一种是new-style drivers,另外一种是legacy drivers

New-style drivers是在2.6近版的kernel加入的.它们最主要的区别是在adapter和i2c driver的匹配上.

3.1: new-style 形式的adapter注册

对于第一种,也就是new-style drivers,将相关代码再次列出如下:

if (adap->nr < __i2c_first_dynamic_bus_num)

i2c_scan_static_board_info(adap);

如果adap->nr 小于__i2c_first_dynamic_bus_num的话,就会进入到i2c_scan_static_board_info().

结合我们之前分析的adapter的两种注册分式: i2c_add_adapter()所分得的总线号肯会不会小于__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()才有可能满足:

(adap->nr < __i2c_first_dynamic_bus_num)

而且必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册时才会更改__i2c_first_dynamic_bus_num的值.在x86上只没有使用i2c_register_board_info()的.因此,x86平台上的分析可以忽略掉new-style driver的方式.不过,还是详细分析这种情况下.

首先看一下i2c_register_board_info(),如下:

int __init

i2c_register_board_info(int busnum,

struct i2c_board_info const *info, unsigned len)

{

int status;

mutex_lock(&__i2c_board_lock);

/* dynamic bus numbers will be assigned after the last static one */

if (busnum >= __i2c_first_dynamic_bus_num)

__i2c_first_dynamic_bus_num = busnum + 1;

for (status = 0; len; len--, info++) {

struct i2c_devinfo  *devinfo;

devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);

if (!devinfo) {

pr_debug("i2c-core: can't register boardinfo! ");

status = -ENOMEM;

break;

}

devinfo->busnum = busnum;

devinfo->board_info = *info;

list_add_tail(&devinfo->list, &__i2c_board_list);

}

mutex_unlock(&__i2c_board_lock);

return status;

}

这个函数比较简单, struct i2c_board_info用来表示I2C设备的一些情况,比如所在的总线.名称,地址,中断号等.最后,这些信息会被存放到__i2c_board_list链表.

跟踪i2c_scan_static_board_info():代码如下:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)

{

struct i2c_devinfo  *devinfo;

mutex_lock(&__i2c_board_lock);

list_for_each_entry(devinfo, &__i2c_board_list, list) {

if (devinfo->busnum == adapter->nr

&& !i2c_new_device(adapter,

&devinfo->board_info))

printk(KERN_ERR "i2c-core: can't create i2c%d-%04x ",

i2c_adapter_id(adapter),

devinfo->board_info.addr);

}

mutex_unlock(&__i2c_board_lock);

}

该函数遍历挂在__i2c_board_list链表上面的i2c设备的信息,也就是我们在启动的时候指出的i2c设备的信息.

如果指定设备是位于adapter所在的I2C总线上,那么,就调用i2c_new_device().代码如下:

struct i2c_client *

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

{

struct i2c_client   *client;

int         status;

client = kzalloc(sizeof *client, GFP_KERNEL);

if (!client)

return NULL;

client->adapter = adap;

client->dev.platform_data = info->platform_data;

device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);

client->flags = info->flags & ~I2C_CLIENT_WAKE;

client->addr = info->addr;

client->irq = info->irq;

strlcpy(client->name, info->type, sizeof(client->name));

/* a new style driver may be bound to this device when we

* return from this function, or any later moment (e.g. maybe

* hotplugging will load the driver module).  and the device

* refcount model is the standard driver model one.

*/

status = i2c_attach_client(client);

if (status < 0) {

kfree(client);

client = NULL;

}

return client;

}

我们又遇到了一个新的结构:struct i2c_client,不要被这个结构吓倒了,其实它就是一个嵌入struct device的I2C设备的封装.它和我们之前遇到的struct usb_device结构的作用是一样的.

首先,在clinet里保存该设备的相关消息.特别的, client->adapter指向了它所在的adapter.

特别的,clinet->name为info->name.也是指定好了的.

一切初始化完成之后,便会调用i2c_attach_client( ).看这个函数的字面意思,是将clinet关联起来.到底怎么样关联呢?继续往下看:

int i2c_attach_client(struct i2c_client *client)

{

struct i2c_adapter *adapter = client->adapter;

int res = 0;

//初始化client内嵌的dev结构

//父结点为所在的adapter,所在bus为i2c_bus_type

client->dev.parent = &client->adapter->dev;

client->dev.bus = &i2c_bus_type;

//如果client已经指定了driver,将driver和内嵌的dev关联起来

if (client->driver)

client->dev.driver = &client->driver->driver;

//指定了driver, 但不是newstyle的

if (client->driver && !is_newstyle_driver(client->driver)) {

client->dev.release = i2c_client_release;

client->dev.uevent_suppress = 1;

} else

client->dev.release = i2c_client_dev_release;

//clinet->dev的名称

snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),

"%d-%04x", i2c_adapter_id(adapter), client->addr);

//将内嵌的dev注册

res = device_register(&client->dev);

if (res)

goto out_err;

//将clinet链到adapter->clients中

mutex_lock(&adapter->clist_lock);

list_add_tail(&client->list, &adapter->clients);

mutex_unlock(&adapter->clist_lock);

dev_dbg(&adapter->dev, "client [%s] registered with bus id %s ",

client->name, client->dev.bus_id);

//如果adapter->cleinet_reqister存在,就调用它

if (adapter->client_register)  {

if (adapter->client_register(client)) {

dev_dbg(&adapter->dev, "client_register "

"failed for client [%s] at 0x%02x ",

client->name, client->addr);

}

}

return 0;

out_err:

dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "

"(%d) ", client->name, client->addr, res);

return res;

}

参考上面添加的注释,应该很容易理解这段代码了,就不加详细分析了.这个函数的名字不是i2c_attach_client()么?怎么没看到它的关系过程呢?

这是因为:在代码中设置了client->dev所在的bus为i2c_bus_type .以为只需要有bus为i2c_bus_type的driver注册,就会产生probe了.这个过程呆后面分析i2c driver的时候再来详细分析.

3.2: legacy形式的adapter注册

Legacy形式的adapter注册代码片段如下:

dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,

i2c_do_add_adapter);

这段代码遍历挂在i2c_bus_type上的驱动,然后对每一个驱动和adapter调用i2c_do_add_adapter().

代码如下:

static int i2c_do_add_adapter(struct device_driver *d, void *data)

{

struct i2c_driver *driver = to_i2c_driver(d);

struct i2c_adapter *adap = data;

if (driver->attach_adapter) {

/* We ignore the return code; if it fails, too bad */

driver->attach_adapter(adap);

}

return 0;

}

该函数很简单,就是调用driver的attach_adapter()接口.

到此为止,adapter的注册已经分析完了.

四:i2c driver注册

在分析i2c driver的时候,有必要先分析一下i2c架构的初始化

代码如下:

static int __init i2c_init(void)

{

int retval;

retval = bus_register(&i2c_bus_type);

if (retval)

return retval;

retval = class_register(&i2c_adapter_class);

if (retval)

goto bus_err;

retval = i2c_add_driver(&dummy_driver);

if (retval)

goto class_err;

return 0;

class_err:

class_unregister(&i2c_adapter_class);

bus_err:

bus_unregister(&i2c_bus_type);

return retval;

}

subsys_initcall(i2c_init);

很明显,i2c_init()会在系统初始化的时候被调用.

在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.

I2c_bus_type结构如下:

static struct bus_type i2c_bus_type = {

.name       = "i2c",

.dev_attrs  = i2c_dev_attrs,

.match      = i2c_device_match,

.uevent     = i2c_device_uevent,

.probe      = i2c_device_probe,

.remove     = i2c_device_remove,

.shutdown   = i2c_device_shutdown,

.suspend    = i2c_device_suspend,

.resume     = i2c_device_resume,

};

这个结构先放在这里吧,以后还会用到里面的信息的.

从上面的初始化函数里也看到了,注册i2c driver的接口为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)

{

int res;

/* new style driver methods can't mix with legacy ones */

//如果是一个newstyle的driver.但又定义了attach_adapter/detach_adapter.非法

if (is_newstyle_driver(driver)) {

if (driver->attach_adapter || driver->detach_adapter

|| driver->detach_client) {

printk(KERN_WARNING

"i2c-core: driver [%s] is confused ",

driver->driver.name);

return -EINVAL;

}

}

/* add the driver to the list of i2c drivers in the driver core */

//关联到i2c_bus_types

driver->driver.owner = owner;

driver->driver.bus = &i2c_bus_type;

/* for new style drivers, when registration returns the driver core

* will have called probe() for all matching-but-unbound devices.

*/

//注册内嵌的driver

res = driver_register(&driver->driver);

if (res)

return res;

mutex_lock(&core_lock);

pr_debug("i2c-core: driver [%s] registered ", driver->driver.name);

/* legacy drivers scan i2c busses directly */

//遍历所有的adapter,对其都调用driver->attach_adapter

if (driver->attach_adapter) {

struct i2c_adapter *adapter;

down(&i2c_adapter_class.sem);

list_for_each_entry(adapter, &i2c_adapter_class.devices,

dev.node) {

driver->attach_adapter(adapter);

}

up(&i2c_adapter_class.sem);

}

mutex_unlock(&core_lock);

return 0;

}

这里也有两种形式的区分,对于第一种,只需要将内嵌的driver注册就可以了,对于legacy的情况,对每一个adapter都调用driver->attach_adapter().

现在,我们可以将adapter和i2c driver关联起来考虑一下了:

1:如果是news style形式的,在注册adapter的时候,将它上面的i2c 设备转换成了struct client.struct client->dev->bus又指定了和i2c driver同一个bus.因为,它们可以发生probe.

2:如果是legacy形式,就直接找到对应的对象,调用driver->attach_adapter().

linux iic设备中断,Linux设备驱动之I2C架构分析(一)相关推荐

  1. NVIDIA GPU开源驱动编译学习架构分析

    2022年5月,社区终于等到了这一天,NVIDIA开源了他们的LINUX GPU 内核驱动, Linux 内核总设计师 Linus Torvalds 十年前说过的一句话,大概意思是英伟达是LINUX开 ...

  2. linux iic总线驱动程序,linux总线驱动之初识i2c驱动数据传输流程

    吃个晚饭,画个流程图,没想到已经这么晚了.还是速度把这篇文章搞定,收拾回去了. 先看下linux中的i2c的数据流程图吧.这里主要是用gpio模拟的i2c的. 还是具体看下代码吧,流程只是个大概,和i ...

  3. linux内核nmi中断,LINUX AM335X NMI中断使用方法

    首先高清楚你要用什么中断,如果是用普通的GPIO去申请中断,那在网上百度就可以了,这里主要讲一下如何使用NMI中断(不可屏蔽中断),自己在这里走了很多弯路,希望可以帮助大家理解,有其他看法的大家在这里 ...

  4. linux iic 设备驱动,linux IIC设备驱动.doc

    linux IIC设备驱动 //---------------IIC的linux驱动------------------#include #include #include #include #inc ...

  5. linux内核模块编程(六)----字符设备驱动中断开发

    先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题 一 why 字符设备驱动在我们 ...

  6. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  7. 【linux iic子系统】i2c设备的添加方法(四)

    文章目录 前言 一.静态注册 二.动态注册 三.用户空间注册 四.i2c驱动扫描注册 前言 I2C设备的4种添加方法: 1)静态注册 2)动态注册 3)用户空间注册 4)i2c驱动扫描注册 一.静态注 ...

  8. 从需求的角度去理解Linux系列:总线、设备和驱动

    <从需求的角度去理解Linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文章,也是从需求的角度去理解Linux系统软件的开篇.这是作者精心撰写的经验总结,希望嵌 ...

  9. linux底层硬件需求,Linux系统的硬件设备驱动的底层结构讲解

    什么是驱动?最通俗的解释就是"驱使硬件设备行动" 作用?设备驱动与底层硬件直接打交道,按照硬件设备的具体工作方式读写设备寄存器,完成设备的轮询.中断处理.DMA通信,进行物理内存向 ...

最新文章

  1. c# 自定义文件关联程序
  2. swift UI专项训练4 场景过渡-转场
  3. 宜信创始人唐宁:技术到处都是,关键看怎么用
  4. NULL 值处理遇到的错误问题.
  5. java面向对象编程的三大基本特性--封装,继承,多态
  6. 鸿蒙专属ota升级真机,鸿蒙OS手机开发者Beta版发布,华为P40、Mate 30系列优先公测...
  7. 数百款惠普打印机易受严重RCE漏洞影响
  8. Redis 实现队列优先级
  9. Mac OS下安装MangoDB及其使用配置
  10. 原子结构示意图全部_原子结构示意图规则
  11. 高速硬盘和固态硬盘的区别
  12. 统计闰年2月29日天数
  13. 基于STM32的DDS信号发生器
  14. 生成对抗网络理论模型和应用综述
  15. word打开文档很久很慢_解决直接打开Word、Excel文档很慢,而通过先打开WORD、EXCEL程序再打开文档则很快的问题...
  16. 你有没有见过5GB大小的图片?
  17. 剑指offer做题记录
  18. 单片机p1口亮灯c语言代码,单片机闪烁灯汇编语言源代码大全(四款闪烁灯的汇编语言源代码)...
  19. Linux - 权限管理(用户)
  20. ISLR统计学习导论之R语言应用(五):R语言实现交叉验证和bootstrap

热门文章

  1. 致网友Wonderfei的一封信(如何选择自动化框架的几点拙见)
  2. ogc是一个非营利性组织_非营利组织的16个最佳WordPress插件
  3. ceph常用命令及其使用、ceph集群定位常用命令说明【如ceph osd set norebalance】、ceph官方文档查询地址
  4. 【附源码】计算机毕业设计java长途汽车订票系统设计与实现
  5. Mobile Computing
  6. 8张图理解Java---importnew---programcreek
  7. 【华人学者风采】纪秀磊 美国俄勒冈州立大学
  8. 最适合运动的蓝牙耳机、运动型蓝牙耳机推荐
  9. 各位8090后,可以分享下真实收入吗
  10. #input框#默认样式#:怎么修改点击之后的边框的样式