I2C总线、设备、驱动

框架

I2C驱动框架可分为3个部分,分别是:I2C核心层、I2C总线驱动层(适配器层)以及I2C设备驱动层;

  • I2C核心层

    提供了统一的I2C操作函数,主要有两套函数smbus(system manager bus)和i2c_transfer;

    其中smbus是i2c_transfer的一小部分,有一些适配器只支持smbus,当驱动中只是用部分I2C协议功能时可使用smbus,具体使用可参考内核源码目录下:kernel/Documentation/i2c/smbus-protocol文档;

    在进行I2C设备的读写操作时,smbus提供了一系列的接口函数,可直接调用,而i2c_transfer需要构建i2c_msg,之后调用i2c_transfer函数进行操作;

  • I2C总线驱动层(适配器层)

    是对I2C硬件结构中(即主控芯片中I2C控制器)适配器的实现,主要包括I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm以及控制I2C适配器产生通信信号的函数;

  • I2C设备驱动层

    主要指的是对从设备硬件体系设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,驱动部分主要包括了数据结构i2c_driver和i2c_client;

对于I2C设备驱动,在内核中有个i2c_bus_type结构体,定义如下:

  1. struct bus_type i2c_bus_type = {
  2. .name = "i2c",
  3. .match = i2c_device_match,
  4. .probe = i2c_device_probe,
  5. .remove = i2c_device_remove,
  6. .shutdown = i2c_device_shutdown,
  7. .pm = &i2c_device_pm_ops,
  8. };

在I2C总线中有两个链表,这两个链表分别存储着不同的“i2c_client”和“i2c_driver”;

当注册一个i2c_client时,会从i2c_driver所在的链表中将i2c-driver挨个取出与i2c_client进行比对,如果匹配,就调用对应i2c_driver中的probe函数(probe函数内部做什么事情自己随意定);

当注册一个i2c_driver时,会从i2c_client所在的链表中将i2c_client挨个取出与i2c_driver进行比对,如果匹配,就调用对应i2c_driver中的probe函数(probe函数内部做什么事情自己随意定);

i2c_client和i2c_driver的进行匹配判断时调用的函数时i2c_bus_type中的.match,匹配判断的规则是driver->id_table中的名字与client的名字是否相同,代码如下:

  1. if (driver->id_table)
  2. return i2c_match_id(driver->id_table, client) != NULL;
  3. /* i2c_match_id函数如下 */
  4. static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
  5. const struct i2c_client *client)
  6. {
  7. while (id->name[0]) {
  8. if (strcmp(client->name, id->name) == 0) /* 比对的关键所在name */
  9. return id;
  10. id++;
  11. }
  12. return NULL;
  13. }

构建一个I2C设备

总共有四种方式,可参考文档:linux源码目录下/Documentation/i2c/instantiating-devices;

  • 第一种方法

    定义一个i2c_board_info结构体,例如:

    1. static struct i2c_board_info i2c_devs[] __initdata = {
    2. {
    3. I2C_BOARD_INFO("xxx", (0x74>>1)),
    4. .platform_data = &xxx_pdata,
    5. },
    6. };

    里面还有名字,设备地址;

    然后调用函数i2c_register_board_info()注册这个结构体,之后的函数调用处理如下:

    1. i2c_register_board_info
    2. /* 会在函数内部分配一个devinfo,然后根据i2c_register_board_info传入的参数
    3. * 来设置devinfo,最终添加到__i2c_board_list链表中
    4. */
    5. list_add_tail(&devinfo->list, &__i2c_board_list);

    搜索__i2c_board_list这个链表在什么地方用,发现是i2c_scan_static_board_info函数在调用,内部处理如下:

    1. /* 在函数i2c_scan_static_board_info中使用链表
    2. * 对链表中的每一个成员都调用i2c_new_device()
    3. */
    4. list_for_each_entry(devinfo, &__i2c_board_list, list) {
    5. if (devinfo->busnum == adapter->nr
    6. && !i2c_new_device(adapter,
    7. &devinfo->board_info))
    8. }

    其中调用的i2c_new_device()函数的作用是:根据i2c_board_info信息构造一个client,然后调用device_register()进行注册;

    那么,i2c_scan_static_board_info()函数是谁在调用?

    是i2c_register_adapter()函数在调用;

    总结:

    函数调用依次是:i2c_register_adapter() -> i2c_scan_static_board_info() -> i2c_new_device();

    当调用i2c_register_adapter()时,就会扫描挂在__i2c_board_list这个链表上的每个成员,并调用i2c_new_device()构造client,最终添加到i2c总线对应的设备链表中,并将i2c_driver中的每一项取出来比对,匹配上之后就调用i2c_driver中的probe函数;

    使用限制:

    必须在i2c_register_adapter之前就i2c_register_board_info()已经构造了i2c_board_info,所以不适合动态加载insmod;

  • 第二种方法

    直接使用i2c_new_device()或者i2c_new_probed_device();

    区别:

    i2c_new_device()不管设备地址对还是不对,都认为设备肯定存在;

    参考代码如下:

    1. /* at24cxx_dev.c 使用i2c_new_device */
    2. #include <linux/kernel.h>
    3. #include <linux/module.h>
    4. #include <linux/platform_device.h>
    5. #include <linux/i2c.h>
    6. #include <linux/err.h>
    7. #include <linux/regmap.h>
    8. #include <linux/slab.h>
    9. static struct i2c_board_info at24cxx_info = {
    10. I2C_BOARD_INFO("at24c08", 0x50),
    11. };
    12. static struct i2c_client *at24cxx_client;
    13. static int at24cxx_dev_init(void)
    14. {
    15. struct i2c_adapter *i2c_adap;
    16. i2c_adap = i2c_get_adapter(0);
    17. at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
    18. i2c_put_adapter(i2c_adap);
    19. return 0;
    20. }
    21. static void at24cxx_dev_exit(void)
    22. {
    23. i2c_unregister_device(at24cxx_client);
    24. }
    25. module_init(at24cxx_dev_init);
    26. module_exit(at24cxx_dev_exit);
    27. MODULE_LICENSE("GPL");

    i2c_new_probed_device()先通过传入的或者默认的probe函数发送一个数据看设备有没有回应,来确认设备是否真实存在,之后才会调用i2c_new_device();

    1. /* at24cxx_dev.c 使用i2c_new_probed_device */
    2. #include <linux/kernel.h>
    3. #include <linux/module.h>
    4. #include <linux/platform_device.h>
    5. #include <linux/i2c.h>
    6. #include <linux/err.h>
    7. #include <linux/regmap.h>
    8. #include <linux/slab.h>
    9. static struct i2c_client *at24cxx_client;
    10. static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
    11. static int at24cxx_dev_init(void)

{

struct i2c_adapter *i2c_adap;

struct i2c_board_info at24cxx_info;

  1. memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));
  2. strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
  3. i2c_adap = i2c_get_adapter(0);
  4. at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
  5. i2c_put_adapter(i2c_adap);
  6. if (at24cxx_client)
  7. return 0;
  8. else
  9. return -ENODEV;

}

static void at24cxx_dev_exit(void)

{

i2c_unregister_device(at24cxx_client);

}

module_init(at24cxx_dev_init);

module_exit(at24cxx_dev_exit);

MODULE_LICENSE("GPL");


  1. - 第三种方法
  2. 从用户空间创建设备;
  3. 创建设备:echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
  4. 其中“eeprom ”为设备名字,“0x50 ”为设备地址,最终会构造一个i2c_board_info,再调用i2c_new_device();
  5. 删除设备:echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device
  6. 可参考内核源码目录下kernel/Documentation/i2c/dev-interface文档;
  7. 通常,i2c设备由内核驱动程序控制,但它也是可以从用户空间访问适配器上的所有设备,通过/dev接口,这需要i2c-tools这个包的支持;
  8. 注意:如果内核中已经有了(安装了)该设备的驱动程序,那么这种方法就不能使用了(在使用i2cdev_ioctl接口时,内部会调用i2cdev_check_addr()进行校验,如果这个设备地址已经有驱动绑定了,就会返回-EBUSY);
  9. 具体参考代码如下:
  10. ```C
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <sys/types.h>
  15. #include <sys/stat.h>
  16. #include <fcntl.h>
  17. #include "i2c-dev.h" /* 需要该文件支持,文件来自i2c-tools包,可网上下载 */
  18. /* i2c_usr_test </dev/i2c-0> <dev_addr> r addr
  19. * i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
  20. */
  21. void print_usage(char *file)
  22. {
  23. printf("%s </dev/i2c-0> <dev_addr> r addr\n", file);
  24. printf("%s </dev/i2c-0> <dev_addr> w addr val\n", file);
  25. }
  26. int main(int argc, char **argv)
  27. {
  28. int fd;
  29. unsigned char addr, data;
  30. int dev_addr;
  31. if ((argc != 5) && (argc != 6))
  32. {
  33. print_usage(argv[0]);
  34. return -1;
  35. }
  36. fd = open(argv[1], O_RDWR);
  37. if (fd < 0)
  38. {
  39. printf("can't open %s\n", argv[1]);
  40. return -1;
  41. }
  42. dev_addr = strtoul(argv[2], NULL, 0);
  43. if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)
  44. {
  45. /* ERROR HANDLING; you can check errno to see what went wrong */
  46. printf("set addr error!\n");
  47. return -1;
  48. }
  49. if (strcmp(argv[3], "r") == 0)
  50. {
  51. addr = strtoul(argv[4], NULL, 0);
  52. data = i2c_smbus_read_word_data(fd, addr);
  53. printf("data: %c, %d, 0x%2x\n", data, data, data);
  54. }
  55. else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
  56. {
  57. addr = strtoul(argv[4], NULL, 0);
  58. data = strtoul(argv[5], NULL, 0);
  59. i2c_smbus_write_byte_data(fd, addr, data);
  60. }
  61. else
  62. {
  63. print_usage(argv[0]);
  64. return -1;
  65. }
  66. return 0;
  67. }
  • 第四种方法

    前面三种方法都要事先确定好适配器(I2C控制器),同时也推荐使用前三种方法;

    第四种方法的参考代码如下:

    1. /* at24cxx_drv.c */
    2. #include <linux/kernel.h>
    3. #include <linux/module.h>
    4. #include <linux/platform_device.h>
    5. #include <linux/i2c.h>
    6. #include <linux/err.h>
    7. #include <linux/regmap.h>
    8. #include <linux/slab.h>
    9. static int __devinit at24cxx_probe(struct i2c_client *client,
    10. const struct i2c_device_id *id)
    11. {
    12. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    13. return 0;
    14. }
    15. static int __devexit at24cxx_remove(struct i2c_client *client)
    16. {
    17. printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    18. return 0;
    19. }
    20. static const struct i2c_device_id at24cxx_id_table[] = {
    21. { "at24c08", 0 },
    22. {}
    23. };
    24. static int at24cxx_detect(struct i2c_client *client,
    25. struct i2c_board_info *info)
    26. {
    27. /* 能运行到这里, 表示该addr的设备是存在的
    28. * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
    29. * 还需要进一步读写I2C设备来分辨是哪款芯片
    30. * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
    31. */
    32. printk("at24cxx_detect : addr = 0x%x\n", client->addr);
    33. /* 进一步判断是哪一款 */
    34. strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
    35. return 0;
    36. }
    37. static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
    38. /* 1. 分配/设置i2c_driver */
    39. static struct i2c_driver at24cxx_driver = {
    40. .class = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
    41. .driver = {
    42. .name = "100ask",
    43. .owner = THIS_MODULE,
    44. },
    45. .probe = at24cxx_probe,
    46. .remove = __devexit_p(at24cxx_remove),
    47. .id_table = at24cxx_id_table,
    48. .detect = at24cxx_detect, /* 用这个函数来检测设备确实存在 */
    49. .address_list = addr_list, /* 这些设备的地址 */
    50. };
    51. static int at24cxx_drv_init(void)
    52. {
    53. /* 2. 注册i2c_driver */
    54. i2c_add_driver(&at24cxx_driver);
    55. return 0;
    56. }
    57. static void at24cxx_drv_exit(void)
    58. {
    59. i2c_del_driver(&at24cxx_driver);
    60. }
    61. module_init(at24cxx_drv_init);
    62. module_exit(at24cxx_drv_exit);
    63. MODULE_LICENSE("GPL");

注册I2C驱动

参考代码如下:

  1. /* at24cxx_drv.c */
  2. #include <linux/kernel.h>
  3. #include <linux/module.h>
  4. #include <linux/platform_device.h>
  5. #include <linux/i2c.h>
  6. #include <linux/err.h>
  7. #include <linux/regmap.h>
  8. #include <linux/slab.h>
  9. #include <linux/fs.h>
  10. #include <asm/uaccess.h>
  11. static int major;
  12. static struct class *class;
  13. static struct i2c_client *at24cxx_client;
  14. /* 传入: buf[0] : addr
  15. * 输出: buf[0] : data
  16. */
  17. static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off)
  18. {
  19. unsigned char addr, data;
  20. copy_from_user(&addr, buf, 1);
  21. data = i2c_smbus_read_byte_data(at24cxx_client, addr);
  22. copy_to_user(buf, &data, 1);
  23. return 1;
  24. }
  25. /* buf[0] : addr
  26. * buf[1] : data
  27. */
  28. static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
  29. {
  30. unsigned char ker_buf[2];
  31. unsigned char addr, data;
  32. copy_from_user(ker_buf, buf, 2);
  33. addr = ker_buf[0];
  34. data = ker_buf[1];
  35. printk("addr = 0x%02x, data = 0x%02x\n", addr, data);
  36. if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data))
  37. return 2;
  38. else
  39. return -EIO;
  40. }
  41. static struct file_operations at24cxx_fops = {
  42. .owner = THIS_MODULE,
  43. .read = at24cxx_read,
  44. .write = at24cxx_write,
  45. };
  46. static int __devinit at24cxx_probe(struct i2c_client *client,
  47. const struct i2c_device_id *id)
  48. {
  49. at24cxx_client = client;
  50. //printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  51. major = register_chrdev(0, "at24cxx", &at24cxx_fops);
  52. class = class_create(THIS_MODULE, "at24cxx");
  53. device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
  54. return 0;
  55. }
  56. static int __devexit at24cxx_remove(struct i2c_client *client)
  57. {
  58. //printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  59. device_destroy(class, MKDEV(major, 0));
  60. class_destroy(class);
  61. unregister_chrdev(major, "at24cxx");
  62. return 0;
  63. }
  64. static const struct i2c_device_id at24cxx_id_table[] = {
  65. { "at24c08", 0 },
  66. {}
  67. };
  68. /* 1. 分配/设置i2c_driver */
  69. static struct i2c_driver at24cxx_driver = {
  70. .driver = {
  71. .name = "100ask",
  72. .owner = THIS_MODULE,
  73. },
  74. .probe = at24cxx_probe,
  75. .remove = __devexit_p(at24cxx_remove),
  76. .id_table = at24cxx_id_table,
  77. };
  78. static int at24cxx_drv_init(void)
  79. {
  80. /* 2. 注册i2c_driver */
  81. i2c_add_driver(&at24cxx_driver);
  82. return 0;
  83. }
  84. static void at24cxx_drv_exit(void)
  85. {
  86. i2c_del_driver(&at24cxx_driver);
  87. }
  88. module_init(at24cxx_drv_init);
  89. module_exit(at24cxx_drv_exit);
  90. MODULE_LICENSE("GPL");

I2C总线、设备、驱动的更多相关文章

  1. Linux I2C总线设备驱动模型分析(ov7740)

    1. 框架1.1 硬件协议简介1.2 驱动框架1.3 bus-drv-dev模型及写程序a. 设备的4种构建方法a.1 定义一个i2c_board_info, 里面有:名字, 设备地址 然后i2c_r ...

  2. Linux驱动之I2C总线设备以及驱动

    [ 导读] 本文通过阅读内核代码,来梳理一下I2C子系统的整体视图.在开发I2C设备驱动程序时,往往缺乏对于系统整体的认识,导致没有一个清晰的思路.所以从高层级来分析一下I2C系统的设计思路,将有助于 ...

  3. Linux学习 : 总线-设备-驱动模型

    platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...

  4. Linux的总线设备驱动模型

    裸机编写驱动比较自由,按照手册实现其功能即可,每个人写出来都有很大不同: 而Linux中还需要按照Linux的驱动模型来编写,也就是需要按照"模板"来写,写出来的驱动就比较统一. ...

  5. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  6. usb驱动开发4之总线设备驱动模型

    在上文说usb_init函数,却给我们留下了很多岔路口.这次就来好好聊聊关于总线设备驱动模型.这节只讲理论,不讲其中的函数方法,关于函数方法使用参考其他资料. 总线.设备.驱动对应内核结构体分别为bu ...

  7. MPU6050带字符驱动的i2c从设备驱动1

    开干: 1.闲言碎语 这个驱动,越写觉的越简单,入门难,入门之后感觉还好.Linux开发还是比较友好的. 2.编写MPU6050带字符驱动的i2c从设备驱动 要实现的功能就是,将MPU6050作为字符 ...

  8. 总线设备驱动模型---platform篇

    总线设备驱动模型----驱动篇 http://blog.chinaunix.net/uid-27664726-id-3334923.html http://blog.chinaunix.net/uid ...

  9. Linux I2C总线控制器驱动(S3C2440)

    s3c2440的i2c控制器驱动(精简DIY),直接上代码,注释很详细: #include <linux/kernel.h> #include <linux/module.h> ...

  10. 芯灵思SinlinxA33开发板 Linux平台总线设备驱动

    1.什么是platform(平台)总线? 相对于USB.PCI.I2C.SPI等物理总线来说,platform总线是一种虚拟.抽象出来的总线,实际中并不存在这样的总线. 那为什么需要platform总 ...

随机推荐

  1. CEIWEI CheckSum CRC校验精灵v2.1 CRC3/CRC4/CRC5/CRC6/CRC8CRC10/CRC11/CRC16/CRC24/CRC32/CRC40/CRC64/CRC82/Adler32

    CEIWEI CheckSum CRC校验精灵 是一款通用的循环冗余校验码CRC(Cyclic Redundancy Check).MD5.SHA1.SHA2.SHA3.HAVAL.SHAKE.TIG ...

  2. 安卓计数器类APP推荐

    1. Thing Counter - Google Play 上的应用 可添加多个计数器,同一页面展示,一行一个,操作直观方便: 可以更改计数器颜色,使页面更美观. 每个计数器有详情和历史记录 详情: ...

  3. XMemcached的基本使用

    XMemcached是memcached的一个java客户端,基于java nio,支持memcached的所有协议.本文简要介绍XMemcached的基本使用. 一.添加依赖 <depende ...

  4. [学习笔记] Blender 常用工具 移动与旋转,缩放, 变换

    点击上面的移动图标之后,可在X,Y,Z轴移动物体. shift+S 之后,可有更多的移动选项. 旋转:可沿X,Y, Z 进行旋转 缩放 还可输入缩放的具体数值,更精确. 变换 可同时做移动.旋转.缩放 ...

  5. cent8安装postgres

    postgres是一款免费.开源的对象型关系数据库,其在cent8的安装方式与cent7的不太一样,特此记录. 步骤: 1 安装postgres server dnf install postgres ...

  6. CNN-4: GoogLeNet 卷积神经网络模型

    1.GoogLeNet 模型简介 GoogLeNet 是2014年Christian Szegedy提出的一种全新的深度学习结构,该模型获得了ImageNet挑战赛的冠军. 2.GoogLeNet 模 ...

  7. openlayers集成到vue开发

    openlayer初步加载地图 vue项目搭好后,直接用node js安装ol, 安装好后可以看看安装的版本:我用的是5..2的版本 然后看官网相对应版本的文档,都是英文文档; 官方文档:    ht ...

  8. 【转帖】循序渐进Oracle:数据库的字符集和字符集文件

    循序渐进Oracle:数据库的字符集和字符集文件 https://blog.csdn.net/Enmotech/article/details/100869162 2019年09月15日 18:23: ...

  9. MySQL之mysqldump数据备份还原

    一 mysqldump指令实现数据备份.mysql指令实现数据还原 经常有朋友问我,DBA到底是做什么的,百科上说:数据库管理员(Database Administrator,简称DBA),是从事管理 ...

  10. Python习题005

    作业一 :任意一个数字列表,然后进行排序(冒泡排序) 方法一: def test1(): list1 = [1,23,4,6,8,55,2,9,90,35] list1.sort() # sort() ...