zoukankan      html  css  js  c++  java
  • 转:I2C驱动分析

    实际上在较新的代码中(如笔者现在用的linux-2.6.30)里面其实己经有一个通用的I2C驱动了。所以在一些简单的场合,我们其实可以不用再去写驱动,只要会用就可以了,但是会用也不是一件很简单的事情,因为关于这方面的内容很少,有些时候,我们不得不去分析一下代码,才能明白如何去用。

    I2C的代码是比较少的,因为协议本身也不是很复杂。我们可以从他的代码目录开始讲起。

    Linux中,关于I2C的驱动代码C文件基本上都放在drivers/i2c目录下。

    此文件夹包含了linux系统里的i2c实现的主要代码。

    内容包括三个子文件夹,和三个.c文件。

    子文件夹:algos busses chips

    algos主要包含的是一个些总线传递数据时的时序算法。

    busses包含的是不同平台上的i2c总线低层驱动方法。

    chips包含的是一个些己知芯的驱动方法。

    .c文件

    i2c-core.c,这个文件是i2c驱动代码核心,用于沟通VFS与低层实现。

    i2c-dev.c这是一个通用的驱动,基本上大多数i2c驱动都可以通过调用这个操作。它在/dev下生成一个主设备号为89的i2c设备。它主要实现了与VFS中规定的操作。

    i2c-boardinfo.c包含一此板级信息。

    总共的代码不是很多。正上面引用的那段所说,我们要关心的代码实在是不多,

    从编译过的代码目录里,我们找到一些踪迹,我的代码树是针对mini2440进行编过的,通过查看.o文件,笔者发现,其实真正参加编译的只有如下几个文件:

    根目录下的:i2c-core.c    i2c-dev.c    i2c-boardinfo.c

    drivers/i2c/algos目录下的:i2c-algo-bit.c

    drivers/i2c/busses目录下的:i2c-s3c2410.c

    总共五个。文件虽少,所涉及的结构体嵌套却是十分复杂。笔者曾经试首整理,结果却发现头绪乱得很,终也没能整理出一个明白的线路来。

    以笔者的经验来说,看一看<Linux设备驱动开发详解>这本书,是非常有益于理解的,但是笔者也发现,其中所整理的图表也不是完善,但在笔者看来,能整理到那个程度,却己经是相当不容易的了。

    下面说一下上面所提到的五个文件,上面的三个在引用部他己有说明。下面的两个i2c-algo-bit.c主要是涉能一些算法相关的内容。而i2c-s3c2410.c则是平台相关的i2c适配器驱动,也即总线驱动。

    这里首先要明白一些概念:

    一、i2c-adapter,即i2c-适配器,这个东西对应s3c2440这块芯片面言,就是指片上的i2c-controller,就是i2c控制器。i2c adapter的作用的是产生总线时序,以读写i2c从设备。

    二、i2c-client,即i2c从设备,从机,这个东西代表的是接到s3c2440芯片上的设备,如一个at24c08的eeprom芯片,或是ov9650摄像头芯中的摄像头控制总线(注:这个摄像头芯片中,官方给的芯片手册上提到的是sccb总线,实际上是一种弱化的i2c总线,我们可以利用i2c驱动来读写它,稍后我们也会演示)。

    首先,我们先从i2c-dev.c这个文件开始分析,一一步步去看从上到下是怎样调用的。

    这是一个模块化的驱动代码,所以我们先从初始化代码开始看:

    static int __init i2c_dev_init(void)
    {
     int res;
     
     printk(KERN_INFO "i2c /dev entries driver\n");
     
     res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
     if (res)
      goto out;
     
     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;
     }
     
     res = i2c_add_driver(&i2cdev_driver);
     if (res)
      goto out_unreg_class;
     
     return 0;
     
    out_unreg_class:
     class_destroy(i2c_dev_class);
    out_unreg_chrdev:
     unregister_chrdev(I2C_MAJOR, "i2c");
    out:
     printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
     return res;
    }

    为了简便起见,我们把这个代码不太重要的东西先拿掉,只看一个重要的,就变成:

    static int __init i2c_dev_init(void)
    {
     int res;
     res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
     i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
     res = i2c_add_driver(&i2cdev_driver);
    }

    也就是说这个模块的初始化代码做了三件事:
    一、注册一个字符设备驱动。mark1
    二、向sysfs注册了一个类。
    三、向上添加了一个驱动结构。这第三个动作不是内核标准动作,而是来自于i2c-core.c中。这个我们之后再谈。mark2
    首先,我们来说这个字符设备。
    字符设备,我想如果写过led驱动的人都可以理解了。重要的就两个部分,主次设备号和file_operations结构。

    主设备号I2C_MAJOR定义于include/linux/i2c-dev.h

    原定义为:

    #define I2C_MAJOR       89              /* Device major number          */

    而次设备号则不在这里定义。

    file_operations结构原文件中初始化如下:

    static const struct file_operations i2cdev_fops = {
     .owner  = THIS_MODULE,
     .llseek  = no_llseek,
     .read  = i2cdev_read,
     .write  = i2cdev_write,
     .unlocked_ioctl = i2cdev_ioctl,
     .open  = i2cdev_open,
     .release = i2cdev_release,
    };

    下面我们则要简要说明一下各个函数的作用,并介绍一些结构体。

    通常使用一个设备的第一步,就是打开该设备的设备结点,我们来看看打开这个设备时会发生什么:

    这里我们还先把这个文件简化,只抽出重要的部分,大家可以在自己的代码中查看原文,也可以打开on.usr.cc,搜索这个函数名。

    static int i2cdev_open(struct inode *inode, struct file *file)
    {
     unsigned int minor = iminor(inode);
     struct i2c_client *client;
     struct i2c_adapter *adap;
     struct i2c_dev *i2c_dev;
     
     i2c_dev = i2c_dev_get_by_minor(minor)
     adap = i2c_get_adapter(i2c_dev->adap->nr);
     
     client = kzalloc(sizeof(*client), GFP_KERNEL);
      i2c_put_adapter(adap);
     
     client->driver = &i2cdev_driver;
     
     client->adapter = adap;
     file->private_data = client;
    }

    这里第一个动作是i2c_dev_get_by_minor.这个函数的作用是通过子设备号,得到一个i2c_dev结构。

    i2c_dev结构是这个文件的头部定义的一个结构体:

    struct i2c_dev {
     struct list_head list;
     struct i2c_adapter *adap;
     struct device *dev;
    };

    它有三个成员,list_head list,表明这个结构最终是要组成链表的。i2c_adapter *adap指向一个i2c_adapter结构,而i2c_adapter结构代表硬件上的i2c适配器,也就是说通过这个结构,我们还要能访问适配器。最后一个是device *dev指向一个device结构,但从目前的知识看,还不知到这个device是做什么用的。因此我们在这里标上mark3以备后查。

    i2c_dev_get_by_minor函数简化版本如下:

    static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
    {
     struct i2c_dev *i2c_dev;
     
     list_for_each_entry(i2c_dev, &i2c_dev_list, list)
      if (i2c_dev->adap->nr == index)
      return i2c_dev;
    }

    也就是说,这里查询了一个链表(看上面有一段同样颜色的字),这个链表的数据项是i2c_dev结构组成的。我们通过i2c_dev访问了adap,也就是适配器,并找到了这个适配器结构中保存的一个成员nr, 其实就是次设备号。如果某一个i2c_dev 项所找到的次设备号与传入的次设备号相同,就是找到了,返回这个i2c_dev结构。

    到现在为止我们讲得都是file_operations结构中的open函数的第一个动作,也就通过次设备号,找到对应的i2c_dev结构,而通过这个结构可以访问适配器和一个不知用处的device结构。次设备号是open时从设备节点得到的。适配器结构却还不知于何时何地初始化。

    下面我们来讲open函数中的第二个动作,i2c_get_adapter,这个函数动作另人不解,我们己经有了i2c_dev,他本来就指向一个adapter啊,为什么还调用一个函数去找呢?记得看过别人的文章,说是有什么讲究来着,不过不记得了。mark4.

    总之这里找到了一个i2c_adapter结构,也就是说可以用i2c_adapter了。

    open函数的第三个动作,生成一个i2c_client结构,第三个动作就是为它开辟一段内存区。

    open函数第四个动作,i2c_put_adapter这个函数中只是调用了module_put(),也就是说减少adapter的引用计数,但是为什么呢?mark5

    open函数第五、六个动作:

    client->driver = &i2cdev_driver;
    client->adapter = adap;

    这里面i2cdev_driver 是在文件头部定义的一个全局的i2c_driver结构体。而adap则是我们通子次设备节点找到的那个adapter.

    open的最后一个动作与我们写字符设备驱动时一样:

    file->private_data = client;

    总结一下:

    以上讲的是open中的动作,在里面声明了一个i2c_dev结构体指针,但是用过后就不要了。但我们却知道了,系统维护着一个i2c_dev的链表。而通过i2c_dev可以找到adapter和device(未明作用)。另外,这个驱动的中心结构体是i2c_client,系统还有一个全局变量i2c_dirver i2cdev_driver.

    书中暗表:这个i2cdev_driver可能是与i2c_client配套的。

    至此我们接解的结构体有:

    i2c_dev

    i2c_client

    i2c_driver

    i2c_adapter

    其中i2c_client就是在这里第一次进入历史舞台的。

    自此以后,我们可以在任意地点从fp获得client,并从而访问adapter和i2c_driver 了,但我们还不知其出处。

    下面我们将先不去讲file_operations中的其他函数,因为这样讲下去是没有意义的。我们先留着。所以

    我们还没有讲的有:

    file_operations中除open处的所有函数。

    好了,我们记下来了,然后我们先去看点别的。

    i2c_dev中除了一个基本字符设备之外,还有点别的东西。

    还记得我们在模块初始化函数里说的吗?那里面有三步动作,我们上面讲的是注册字符设备的那个动作的准备。

    下面我们要说说另外两个动作了。

    一、i2c_dev_class = class_create(THIS_MODULE, “i2c-dev”);

    注意哦,这里的i2c_dev_class也是一个全局变量呢!

    static struct class *i2c_dev_class;

    这一句在我的文件中的516行,其实所有另外两个动作的实现代码也都是从这里开始的。

    仔细读了代码之后,发现这个i2c_dev_class的第一次使用,就是在模板初始化代码中的第二个动作。也就是调用class_create函数,来填充这个指针。

    之后,i2c_dev_class还出现在两个函数里,紧挨着上面引用中的那句话后面是两个函数,和一个结构体的填充。之后便是模块初始化函数了。我把中间这段简化后帖出来:

    static int i2cdev_attach_adapter(struct i2c_adapter *adap)
    {
     struct i2c_dev *i2c_dev;
     i2c_dev = get_free_i2c_dev(adap);
     i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
             MKDEV(I2C_MAJOR, adap->nr), NULL,
             "i2c-%d", adap->nr);
     res = device_create_file(i2c_dev->dev, &dev_attr_name);
    }
     
    static int i2cdev_detach_adapter(struct i2c_adapter *adap)
    {
     struct i2c_dev *i2c_dev;
     i2c_dev = i2c_dev_get_by_minor(adap->nr);
     device_remove_file(i2c_dev->dev, &dev_attr_name);
     return_i2c_dev(i2c_dev);
    }
     
    static struct i2c_driver i2cdev_driver = {
     .driver = {
      .name = "dev_driver",
     },
     .attach_adapter = i2cdev_attach_adapter,
     .detach_adapter = i2cdev_detach_adapter,
    };

    看到了吧,从上到下思路很清晰,两个函数是为了下面的结构体而写的,而结构是为了模块初始化函数中的第三步动作而写的。
    我们先看第一个函数:i2cdev_attach_adapter
    第一个动作get_free_i2c_dev,记得上面我们讲open时说的第一步,是通次设备号找到一个相应的i2c_dev结构,也就是遍历一个i2c_dev的表,从中去找,但是我们还不知那个表是何时生成的,现在我们依然不知道,但己经可以见些端倪,就在get_free_i2c_dev这个函数中,其作用就是初始化一个i2c_dev结构,用传入的adapter去填充其adap成员,并且将这个i2c_dev加入到i2c_dev_list表中(详情请点击上面的链接)。

    现在我们知道了,i2c_dev_list成员的填加过程。但上面加入的i2c_dev只有一个adap成员被初始化了,还有一个device成员没有初始化,而这就是i2cdev_attach_adapter函数中第二个动作要做的:i2c_dev->dev = device_create(),这里调用一个函数,并用其返回值去初始化i2c_dev->dev, 也就是device 成员。第三步:device_create_file在sysfs下创建节点。这个函数是在这个driver被注册,或是adapter被注册时调用。

    总之,在这里i2cdev_driver被初始化,然后的事情就是在模块初始化时调用i2c_add_driver,这个i2c_add_driver实际上是转而调了i2c-core.c中的i2c_add_driver来完成些功能,

    static inline int i2c_add_driver(struct i2c_driver *driver)
    {
           return i2c_register_driver(THIS_MODULE, driver);
    }

    而这个函数在这里先不想仔细研究,只想说里面最后有一段代码,就是轮询所有driver 并调用其attach_adapter函数,这里也就是上面为什么说注册时会调用的原因了。

    好,文章己经太长,本文先结束,待下文继续分解。

  • 相关阅读:
    转--后台开发人员的技术栈
    hadoop +streaming 排序总结
    python 的tempfile学习
    hadoop学习日志
    shell sort 排序大讨论
    系统吞吐量、TPS(QPS)、用户并发量、性能测试概念和公式
    推荐系统评测指标--准确率(Precision)和召回率(Recall)、F值(F-Measure)
    shell 数组
    leecode第七百四十六题(使用最小花费爬楼梯)
    leecode第四百七十五题(供暖器)
  • 原文地址:https://www.cnblogs.com/shenhaocn/p/1996548.html
Copyright © 2011-2022 走看看