Linux的I2C子系统驱动框架简析

一、Linux的I2C子系统框架介绍

  1. I2C总线和I2C协议、SMBus协议
  2. 标准模式:100kbps、快速模式:400kbps、高速模式:3.4Mbps
  3. I2C接上拉电阻的作用:保证总线空闲处于高电平(开漏/开极输出输出0时总线处于高阻态)、实现线与功能(一个设备拉低总线的时候其他设备也能感知到)
  4. I2C上拉电阻的常规值:100kbps选10k电阻、400kbps选4.7k电阻、3.4Mbps选2.2k电阻
  5. I2C协议:起始信号(SCL为高电平期间SDA由高变低)、结束信号(SCL为高电平期间SDA由低变高)、传输以字节为单位、每个字节后必须加一个应答位、位序为先MSB,目标设备可拉低SCL设置控制器等待
  6. 目标地址是7bit,后跟读写方向位(0为写、1位读)组成一个字节

二、I2C子系统的分层介绍

  1. I2C设备驱动,包括一般的I2C设备驱动和i2c-dev.c,形成两种节点,/dev/xxx和/dev/i2c-N
  2. I2C核心层,如i2c-core-smbus.c、i2c-core-base.c,给上层提供各种API
  3. I2C控制器驱动,包含一般的I2C控制器驱动和i2c-gpio.c,后者使用GPIO模拟I2C的传输过程
  4. i2c-dev.c是通用的I2C设备驱动,i2c-gpio.c为GPIO模拟I2C的实现

三、典型的设备树文件

// 普通的I2C设备,挂在I2C0总线下,地址是0x50
&i2c0{
  i2c_dev@50 {
    compatible = "i2c_dev_compatible";
    reg = <0x50>;
  };
};
// 一般的I2C控制器
i2c_adap_virt: virtual_i2c_adapter {
  compatible = "user,virtual_i2c_adapter";
  #address-cells = <1>;
  #size-cells = <0>;
  ...      // 其他信息
};
// 使用GPIO模拟的I2C控制器
i2c-gpio-0 {
  compatible = "i2c-gpio";
  pinctrl-names = "default";
  pinctrl-0 = <&pinctrl_i2c_bitbang>;
  gpios = <&GPIOA 7 GPIO_ACTIVE_HIGH>,  //SCL
          <&GPIOA 8 GPIO_ACTIVE_HIGH>;  //SDA
  i2c-gpio,sda-open-drain;
  i2c-gpio,scl-open-drain;
  i2c-gpio,delay-us=<5>;  //100K
  #address-cells = <1>;
  #size-cells = <0>;
  ...      // 其他信息
};

四、I2C的重要结构体

struct i2c_adapter {    // 描述I2C控制器
  const struct i2c_algorithm *algo;      // I2C具体的传输实现
  int nr;              // 第几个I2C控制器
  ...
};
struct i2c_algorithm {  // 描述I2C的传输
  int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);    // 传输函数
  ...
  u32 (*functionality)(struct i2c_adapter *adap);  // 控制器支持哪些功能
};
struct i2c_msg {        // 描述I2C传递的消息
  __u16 addr;      // 从机地址
  __u16 flag;      // 标志位
  __u16 len;       // 长度
  __u8 *buf;       // 指向msg数据的指针
};
struct i2c_driver {    // 描述I2C驱动,类似platform_driver
  int (*probe_new)(struct i2c_client *client);
  int (*remove)(struct i2c_client *client);
  struct device_driver driver;  // compatible成员在这里
  const struct i2c_device_id *id_table;
  ...
};
struct i2c_client {    // 描述I2C设备,类似platform_device
  unsigned short flags;      // 标志
  unsigned short addr;      // I2C设备地址
  struct i2c_adapter *adapter;  // 控制器
  struct device dev;        // 必备的device结构体
};

五、驱动程序的流程

// I2C设备驱动
1. 入口函数中,使用i2c_add_driver注册一个i2c_driver,出口函数中,使用i2c_del_driver注销一个i2c_driver
2. I2C-BUS和Platform-BUS类似,入口函数注册相应driver,出口函数注销driver,compatible属性匹配时,调用probe函数
3. probe函数中,类似其他字符设备。使用alloc_chrdev_region、cdev_init、cdev_add、class_create、device_create
4. 关键的file_operations结构体中的读写函数,可以借助i2c_smbus_xx或者i2c_transfer函数实现
// I2C控制器驱动
1. 使用平台设备驱动程序的框架,注册I2C控制器驱动
2. probe函数中,分配、设置、注册一个i2c_adapter结构体,使用i2c_add_adapter注册一个控制器的结构体
3. 关键的成员i2c_algorithm需要最少实现master_xfer和functionality函数

六、i2c-dev.c介绍

1. 入口函数
  register_chrdev_region   // 主设备号89,名称为i2c
  class_create             // 创建类,辅助创建节点
  i2c_for_each_dev(NULL, i2cdev_attach_adapter);    // 绑定存在的I2C控制器,形成的/dev/i2c-N表示每个控制器
    cdev_init
    cdev_device_add
 2. 字符设备对应的fops
  open
    adap = i2c_get_adapter(minor);      // 得到控制器
    kzalloc                             // 分配一个i2c_client表示I2C设备
    设置I2C设备所属的控制器
  release
    i2c_put_adapter(client->adapter);    // 释放控制器
    kfree                                // 释放i2c_client的空间
  read
    i2c_master_recv
  write
    i2c_master_send
  ioctl
    I2C_SLAVE/I2C_SLAVE_FORCE        // 设置client的addr
    I2C_TENBIT                       // 设置client的flag
    I2C_PEC                          // 设置client的flag
    I2C_FUNCS                        // 调用i2c_algorithm的functionality函数获取能力
    I2C_RDWR                         // 构建msg,执行i2c_transfer
    I2C_SMBUS                        // 构建msg,执行i2c_smbus_xfer
    ...
3. 应用程序如何使用/dev/i2c-x,构建i2c_rdwr_ioctl_data结构体,用i2c_msg进行填充,并使用I2C_RDWR的ioctl实现I2C读写

七、i2c-gpio.c介绍

1. 使用平台设备驱动程序的框架,注册该platform_driver
2. probe函数
    解析设备树,获得时钟频率、SCL、SDA是否上拉等信息
    构建SCL和SDA对应的gpio_desc结构体
    构建adapter结构体对象
    i2c_bit_add_numbered_bus(adap)
      注册i2c_bit_algo,实现transfer函数和functionality函数
      调用add_adapter,注册一个adapter

八、总结

  1. 可以看到一般的I2C设备驱动和i2c-dev.c的实现类似,只不过两者生成的节点不同,后者对控制器生成了一个单独的节点,同样可以调用i2c协议或smbus协议的xfer函数
  2. i2c-gpio.c和一般的I2C控制器驱动类似,都使用add_adapter实现控制器的注册,关键之处在于xfer函数,前者使用GPIO做模拟实现I2C协议。

九、关键函数

  1. i2c_add_driver实际为i2c_register_driver,函数中将i2c_driver结构体中的driver成员,调用driver_register进行注册,最后调用i2c_for_each_dev(driver, __process_new_driver);
  2. i2c_del_driver先执行i2c_for_each_dev(driver, __process_removed_driver);,再执行driver_unregister
  3. i2c_get_adapter通过索引值得到注册i2c_adapter,之后调用get_device引用计数值加1
  4. i2c_put_adapter调用put_deivce引用计数值减1,之后调用module_put减少i2c_adapter模块计数
  5. i2c_new_client_device传入i2c_adapter的指针和i2c_board_info指针,得到一个i2c_client,并调用driver_register进行注册
  6. i2c_board_info的关键成员有type表示类型名称、flags表示标志位、addr表示I2C设备地址、platform_data表示平台数据、resources表示资源、num_resources表示资源的数量,of_node表示设备节点指针
  7. i2c_transfer是I2C核心层提供的I2C传输函数,传输的单位是i2c_msg,传输的个数是参数num,核心实现是调用对应的adapter的algo成员的master_xfer函数实现I2C数据传输,当i2c_msg的flag为0表示写、为1表示读,还有一些其他的宏控制I2C的操作
  8. i2c_client是什么时候创建的?因此i2c_client需要绑定一个i2c_adapter,因此必定是在i2c_adapter注册完之后才会创建i2c_client,在i2c控制器的驱动程序的probe函数的末尾,调用i2c_add_adapter注册一个i2c_adapter,其中调用i2c_register_adapter注册一个i2c_adapter,其中通过of_i2c_register_devices解析设备树并从设备树节点中提取出i2c_board_info,最后调用i2c_new_client_device注册了一个i2c_client

十、工具

  1. i2c-tools工具
  2. i2cdetect -l列出所有的I2C总线
  3. i2cdetect -F 1 列出I2C1上的功能集
  4. i2cdetect -a -y 1 扫描I2C1上的所有I2C地址
  5. i2cdump -f -a 1 0x38 读I2C1上的0x38的所有寄存器的值
  6. i2cdump -f -r 0x80-0xff 1 0x38 读I2C1上的0x38的指定范围寄存器的值
  7. i2cset -f -r 1 0x38 0x80 0x11 强制向I2C1的0x38的寄存器0x80写入值0x11
  8. i2cget -f 1 0x38 0x80 强制从I2C1的0x38地址的寄存器0x80读数
  9. i2ctransfer 1 w2@0x38 0x80 0x22 向I2C1的0x38地址的寄存器0x80写两个字节0x22
  10. i2ctransfer 1 w1@0x38 0x80 r1 向I2C1的0x38地址的寄存器0x80读一个字节
posted @ 2025-07-22 16:13  gramming  阅读(123)  评论(0)    收藏  举报