I2C 概述

I2C是philips提出的外设总线.

I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线。

因此,I2C总线被非常广泛地应用在EEPROM,实时钟,小型LCD等设备与CPU的接口中。


linux下的驱动思路

在linux系统下编写I2C驱动,目前主要 有两种方法 , 一种是把I2C设备当作一个普通的字符设备来处理, 另一种是利用linux下I2C驱动体系结构来完成。下面比较下这两种方法:
  第一种方法:
    优点:思路比较直接,不需要花很多时间去了解linux中复杂的I2C子系统的操作方法。
     缺点 :
       要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器(I2C控制器)操作。
       要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可以移植性差。
       对内核的资源无法直接使用,因为内核提供的所有I2C设备器以及设备驱动都是基于I2C子系统的格式。
第一种方法的优点就是第二种方法的缺点,
   第一种方法的缺点就是第二种方法的优点。

I2C架构概述

 Linux的I2C体系结构分为3个组成部分 :

   I2C核心 :I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。

   I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。

   I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。

linux驱动中i2c驱动架构

  

  上图完整的描述了Linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂。

  那么我们如何编写特定i2c接口器件的驱动程序?就是说上述架构中的那些部分需要我们完成,而哪些是linux内核已经完善的或者是芯片提供商已经提供的?


架构层次分类

  第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层

  第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xferf()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。覆盖图中的访问抽象层、i2c核心层

  第三层:实现i2c设备驱动中的i2c_driver接口,用具体的i2c device设备的attach_adapter()、detach_adapter()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的挂接。覆盖图中的driver驱动层

  第四层:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。覆盖图中的driver驱动层

  第一层和第二层又叫i2c总线驱动(bus),第三第四属于i2c设备驱动(device driver)。

  在linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为linux内核几乎集成所有总线bus,如usb、pci、i2c等等。并且总线bus中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c-2440平台i2c总线bus为/drivers/i2c/buses/i2c-s3c2410.c

  第三第四层与特定device相干的就需要驱动工程师来实现了。


Linux下I2C体系文件构架

  在Linux内核源代码中的driver目录下包含一个i2c目录

  

  i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
    i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
  busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c.
  algos文件夹实现了一些I2C总线适配器的algorithm.


重要的结构体

i2c_driver

  1. struct i2c_driver {
  2. unsigned int class;
  3. int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针
  4. int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针
  5. int (*probe)(struct i2c_client *, const struct i2c_device_id *);
  6. int (*remove)(struct i2c_client *);
  7. void (*shutdown)(struct i2c_client *);
  8. int (*suspend)(struct i2c_client *, pm_message_t mesg);
  9. int (*resume)(struct i2c_client *);
  10. void (*alert)(struct i2c_client *, unsigned int data);
  11. int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表
  12. struct device_driver driver;
  13. const struct i2c_device_id *id_table;//该驱动所支持的设备ID表
  14. int (*detect)(struct i2c_client *, struct i2c_board_info *);
  15. const unsigned short *address_list;
  16. struct list_head clients;
  17. };

i2c_client

  1. struct i2c_client {
  2. unsigned short flags;//标志
  3. unsigned short addr; //低7位为芯片地址
  4. char name[I2C_NAME_SIZE];//设备名称
  5. struct i2c_adapter *adapter;//依附的i2c_adapter
  6. struct i2c_driver *driver;//依附的i2c_driver
  7. struct device dev;//设备结构体
  8. int irq;//设备所使用的结构体
  9. struct list_head detected;//链表头
  10. };

i2c_adapter

  1. struct i2c_adapter {
  2. struct module *owner;//所属模块
  3. unsigned int id;//algorithm的类型,定义于i2c-id.h,
  4. unsigned int class;
  5. const struct i2c_algorithm *algo; //总线通信方法结构体指针
  6. void *algo_data;//algorithm数据
  7. struct rt_mutex bus_lock;//控制并发访问的自旋锁
  8. int timeout;
  9. int retries;//重试次数
  10. struct device dev; //适配器设备
  11. int nr;
  12. char name[48];//适配器名称
  13. struct completion dev_released;//用于同步
  14. struct list_head userspace_clients;//client链表头
  15. };

i2c_algorithm

  1. struct i2c_algorithm {
  2. int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);//I2C传输函数指针
  3. int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union
  4. i2c_smbus_data *data);//smbus传输函数指针
  5. u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能
  6. };

各结构体的作用与它们之间的关系

i2c_adapter与i2c_algorithm

  i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。

  i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。

  i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体

  1. struct i2c_msg {
  2. __u16 addr; /* slave address            */
  3. __u16 flags;
  4. __u16 len;      /* msg length               */
  5. __u8 *buf;      /* pointer to msg data          */
  6. };

i2c_driver和i2c_client

  i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client()

  i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述

  i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.

i2c_adapter和i2c_client

  i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。

  

  从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为小面的硬件事件提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。


具体分析

  先看一下i2c-core为外部提供的核心函数(选取部分),i2c-core对应的源文件为i2c-core.c,位于内核目录/driver/i2c/i2c-core.c

  1. EXPORT_SYMBOL(i2c_add_adapter);
  2. EXPORT_SYMBOL(i2c_del_adapter);
  3. EXPORT_SYMBOL(i2c_del_driver);
  4. EXPORT_SYMBOL(i2c_attach_client);
  5. EXPORT_SYMBOL(i2c_detach_client);
  6. EXPORT_SYMBOL(i2c_transfer);

  i2c_transfer()函数:i2c_transfer()函数本身并不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正的驱动硬件流程,代码清单如下,不重要的已删除。

  1. int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
  2. {
  3. int ret;
  4. if (adap->algo->master_xfer) {//如果master_xfer函数存在,则调用,否则返回错误
  5. ret = adap->algo->master_xfer(adap,msgs,num);//这个函数在硬件相关的代码中给algorithm赋值
  6. return ret;
  7. } else {
  8. return -ENOSYS;
  9. }
  10. }

当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。

  相反的,在client被取消关联的时候,sysfs文件和设备也被注销,驱动开发人员在开发i2c设备驱动时,需要调用下列函数。程序清单如下

  1. int i2c_attach_client(struct i2c_client *client)
  2. {
  3. ...
  4. device_register(&client->dev);
  5. device_create_file(&client->dev, &dev_attr_client_name);
  6. ...
  7. return 0;
  8. }
  9. [cpp] view plaincopy
  10. int i2c_detach_client(struct i2c_client *client)
  11. {
  12. ...
  13. device_remove_file(&client->dev, &dev_attr_client_name);
  14. device_unregister(&client->dev);
  15. ...
  16. return res;
  17. }

  i2c_add_adapter()函数和i2c_del_adapter()在i2c-davinci.c中有调用,稍后分析

  1. int i2c_add_adapter(struct i2c_adapter *adap)
  2. {
  3. ...
  4. device_register(&adap->dev);
  5. device_create_file(&adap->dev, &dev_attr_name);
  6. ...
  7. /* inform drivers of new adapters */
  8. list_for_each(item,&drivers) {
  9. driver = list_entry(item, struct i2c_driver, list);
  10. if (driver->attach_adapter)
  11. /* We ignore the return code; if it fails, too bad */
  12. driver->attach_adapter(adap);
  13. }
  14. ...
  15. }
  16. int i2c_del_adapter(struct i2c_adapter *adap)
  17. {
  18. ...
  19. list_for_each(item,&drivers) {
  20. driver = list_entry(item, struct i2c_driver, list);
  21. if (driver->detach_adapter)
  22. if ((res = driver->detach_adapter(adap))) {
  23. }
  24. }
  25. ...
  26. list_for_each_safe(item, _n, &adap->clients) {
  27. client = list_entry(item, struct i2c_client, list);
  28. if ((res=client->driver->detach_client(client))) {
  29. }
  30. }
  31. ...
  32. device_remove_file(&adap->dev, &dev_attr_name);
  33. device_unregister(&adap->dev);
  34. }

  i2c-davinci.c是实现与硬件相关功能的代码集合,这部分是与平台相关的,也叫做i2c总线驱动,这部分代码是这样添加到系统中的

  1. static struct platform_driver davinci_i2c_driver = {
  2. .probe      = davinci_i2c_probe,
  3. .remove     = davinci_i2c_remove,
  4. .driver     = {
  5. .name   = "i2c_davinci",
  6. .owner  = THIS_MODULE,
  7. },
  8. };
  9. /* I2C may be needed to bring up other drivers */
  10. static int __init davinci_i2c_init_driver(void)
  11. {
  12. return platform_driver_register(&davinci_i2c_driver);
  13. }
  14. subsys_initcall(davinci_i2c_init_driver);
  15. static void __exit davinci_i2c_exit_driver(void)
  16. {
  17. platform_driver_unregister(&davinci_i2c_driver);
  18. }
  19. module_exit(davinci_i2c_exit_driver);

  并且,i2c适配器控制硬件发送接收数据的函数在这里赋值给i2c-algorithm,i2c_davinci_xfer稍加修改就可以在裸机中控制i2c适配器

  1. static struct i2c_algorithm i2c_davinci_algo = {
  2. .master_xfer    = i2c_davinci_xfer,
  3. .functionality  = i2c_davinci_func,
  4. };

  然后在davinci_i2c_probe函数中,将i2c_davinci_algo添加到添加到algorithm系统中

  1. adap->algo = &i2c_davinci_algo;

适配器驱动程序分析

  在linux系统中,适配器驱动位于linux目录下的\drivers\i2c\busses下,不同的处理器的适配器驱动程序设计有差异,但是总体思路不变。

  在适配器的驱动中,实现两个结构体非常关键,也是整个适配器驱动的灵魂。

  下面以某个适配器的驱动程序为例进行说明:

  1. static struct platform_driver tcc_i2c_driver = {
  2. .probe   = tcc_i2c_probe,
  3. .remove   = tcc_i2c_remove,
  4. .suspend  = tcc_i2c_suspend_late,
  5. .resume   = tcc_i2c_resume_early,
  6. .driver   = {
  7. .owner  = THIS_MODULE,
  8. .name  = "tcc-i2c",
  9. },
  10. };

  以上说明这个驱动是基于平台总线的,这样实现的目的是与CPU紧紧联系起来。

  1. static const struct i2c_algorithm tcc_i2c_algorithm = {
  2. .master_xfer = tcc_i2c_xfer,
  3. .functionality = tcc_i2c_func,
  4. };

  这个结构体也是非常的关键,这个结构体里面的函数tcc_i2c_xfer是适配器算法的实现,这个函数实现了适配器与I2C CORE的连接。

  tcc_i2c_func是指该适配器所支持的功能。
  tcc_i2c_xfer这个函数实质是实现I2C数据的发送与接收的处理过程。不同的处理器实现的方法不同,主要表现在寄存器的设置与中断的处理方法上。
  把握上面的两点去分析适配器程序就简单多了。

I2C-core驱动程序分析

  在I2C-core.c这个函数中,把握下面的几个关键函数就可以了。

  1. //增加/删除i2c_adapter
  2. int i2c_add_adapter(struct i2c_adapter *adapter)
  3. int i2c_del_adapter(struct i2c_adapter *adap)
  4. //增加/删除i2c_driver
  5. int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
  6. void i2c_del_driver(struct i2c_driver *driver)
  7. //i2c_client依附/脱离
  8. int i2c_attach_client(struct i2c_client *client)
  9. //增加/删除i2c_driver
  10. int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
  11. void i2c_del_driver(struct i2c_driver *driver)
  12. //i2c_client依附/脱离
  13. int i2c_attach_client(struct i2c_client *client)
  14. int i2c_detach_client(struct i2c_client *client)
  15. //I2C传输,发送和接收
  16. int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
  17. int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
  18. int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

  I2c_transfer这个函数实现了core与adapter的联系。


代码调用层次图

  有时候代码比任何文字描述都来得直接,但是过多的代码展示反而让人觉得枯燥。这个时候,需要一幅图来梳理一下上面的内容

  

  上面这些代码的展示是告诉我们:linux内核和芯片提供商为我们的的驱动程序提供了 i2c驱动的框架,以及框架底层与硬件相关的代码的实现。

  剩下的就是针对挂载在i2c两线上的i2c设备了device,而编写的即具体设备驱动了,这里的设备就是硬件接口外挂载的设备,而非硬件接口本身(soc硬件接口本身的驱动可以理解为总线驱动)


编写驱动需要完成的工作

  编写具体的I2C驱动时,工程师需要处理的主要工作如下:
1).提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生。
2).提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针。
3).实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。
4).实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接。
上面的工作中前两个属于I2C总线驱动,后面两个属于I2C设备驱动。

linux系统下编写I2C驱动相关推荐

  1. OK6410开发板linux系统下的SPI驱动和测试

    OK6410下的SPI驱动是可以用的,但是飞凌把它作为其它用途了,我们修改一些代码才能在/dev目录下创建SPI的设备节点文件 Step1:打开arch/arm/mach_s3c64XX/mach_m ...

  2. linux系统能装什么显卡,Linux系统下安装显卡驱动

    第一次使用Linux遇到的不是硬盘分区而是显卡驱动问题,因为Linux在图形界面方面作的不是很好,以后你安其它软件时就能感受到.好多时候看到图形界面出错不一定是软件出问题. Linux下安装显卡驱动 ...

  3. linux系统下的打印机驱动下载,方法论:Linux下如何驱动主流品牌打印机

    薄荷站长浸淫 Linux 桌面领域十余年,一直致力于 Linux 桌面系统的推广.对于桌面用户办公中必需的打印功能,薄荷站长也是有所研究.为了使新手朋友们少走弯路,薄荷站长介绍一下常见品牌打印机的 L ...

  4. Linux 系统下 NVIDIA 显卡驱动,GNOME 桌面环境不兼容

    NVIDIA 显卡驱动在 Windows 系统下的安装非常方便,大部分第三方软件都能直接自动检测型号,并从 NVIDIA 官网上下载驱动安装.但是在 Linux 操作系统下,NVIDIA 显卡的驱动需 ...

  5. linux系统下的打印机驱动下载,总结各大常见打印机品牌在Linux下的驱动方法

    本文介绍常见打印机品牌在 Linux 系统中的驱动方法,这是一篇总结方向性的文章,不是教你在 Linux 下安装打印机驱动,而是看完本文后大致有所了解各大常见打印机品牌在 Linux 下的驱动方法,具 ...

  6. linux系统下安装2080ti驱动,CentOS 7.4 Nvidia GeForce RTX 2080 Ti 显卡驱动安装

    环境描述 系统版本:CentOS Linux release 7.4.1708 (Core) 内核版本:3.10.0-693.el7.x86_64 显卡驱动版本: NVIDIA-Linux-x86_6 ...

  7. linux CFI接口,高手进阶 Linux系统下MTD/CFI驱动介绍

    某些Intel的FLASH芯片(如StrataFlash系列)支持多分区,也就是各个分区可以同时进行操作.应该说这是不错的特性,但是也会带来些问题.记得当初移植Linux-2.4.21,挂JFFS2文 ...

  8. 如何检查Linux系统下的Virtio 驱动

    操作场景 云服务器系统内核需要支持 Virtio 驱动(包括块设备驱动 virtio_blk 和网卡驱动 virtio_net)才能在腾讯云上正常运行.为避免导入自定义镜像后,创建的云服务器实例无法启 ...

  9. linux下的can驱动测试,linux系统下can的驱动测试

    测试can需要ip,can-utils和libsocketcan库. 通过ip工具配置can,如速率,启用和禁用can等.不能用buildroot编译出来的ip,需要重新编译. 1. 编译ip: ip ...

最新文章

  1. Java并发编程:深入剖析ThreadLocal
  2. 使用onclick跳转到其他页面。使用button跳转到指定url
  3. java时间格式24小时制12小时制
  4. C# 与 C 和 C++ 比较
  5. sip账号服务器,如何解决“sip账号未登录” ?
  6. 经验分享——家长与学校应该怎样沟通
  7. python 英语分词_Python实现中英文分词
  8. 软件压力测试报告要怎么写,如何做接口压力测试?压力测试报告应该包含哪些结果?...
  9. Mybatis使用map参数查询
  10. 链接h5代码_H5是什么?今天给大家做一个全面的解释
  11. 中秋节快乐ooooo
  12. 【hdu 6045】Is Derek lying?
  13. 【excel技巧读书笔记005】快速隔行填充
  14. Canvas 画布中坐标系的位置问题(原点确定)
  15. 为什么你的工具类APP用户量不少,却难以找到变现模式?
  16. android电视如何打开adb调试模式,分享解密某Android电视adb后门方法
  17. Android双波浪自定义控件(DoubleWaveView)
  18. 体感游戏波斯王子:重生.
  19. Kali aircrack-ng 暴破wifi
  20. 微信接口开发报错invalid credential, access_token is invalid or not latest hint

热门文章

  1. 职业规划九—it行业几条不同的路
  2. 关于婚姻,你能事先回答这些问题吗?(来自网络)
  3. 电机模组发出噪音怎么办?
  4. Word2Vec原理与公式详细推导
  5. Windows 内核(WRK)编译
  6. 【Java 虚拟机原理】垃圾收集器 ( Serial | ParNew | Parallel Scavenge | CMS | Serial Old - MSC | Parallel Old )
  7. 超全面常用的数据库优化方案
  8. Git使用小技巧【修改commit注释, 超详细】
  9. 浅显易懂 SQLite3 笔记(03)— SQL数据查询(超级详细)
  10. 7.Hive性能优化及Hive3新特性