如何实例化 I2C 设备

与 PCI 或 USB 设备不同,I2C 设备在硬件级别不会被枚举。相反,软件必须知道每个 I2C 总线段上连接了哪些设备,以及这些设备正在使用什么地址。因此,内核代码必须显式地实例化 I2C 设备。根据上下文和要求,有几种方法可以实现这一点。

方法 1:静态声明 I2C 设备

当 I2C 总线是系统总线时,此方法是合适的,许多嵌入式系统就是这种情况。在这样的系统上,每个 I2C 总线都有一个预先知道的编号。因此,可以预先声明位于该总线上的 I2C 设备。

此信息以不同的方式提供给不同架构上的内核:设备树、ACPI 或板级文件。

当相关的 I2C 总线被注册时,I2C 设备将由 i2c-core 自动实例化。当它们所在的 I2C 总线消失时(如果有的话),这些设备将被自动解除绑定并销毁。

通过设备树声明 I2C 设备

在使用设备树的平台上,I2C 设备的声明在主控制器的子节点中完成。

示例

i2c1: i2c@400a0000 {
        /* ... master properties skipped ... */
        clock-frequency = <100000>;

        flash@50 {
                compatible = "atmel,24c256";
                reg = <0x50>;
        };

        pca9532: gpio@60 {
                compatible = "nxp,pca9532";
                gpio-controller;
                #gpio-cells = <2>;
                reg = <0x60>;
        };
};

在此,两个设备以 100kHz 的速度连接到总线。有关设置设备可能需要的其他属性,请参阅 Documentation/devicetree/bindings/ 中的设备树文档。

通过 ACPI 声明 I2C 设备

ACPI 也可以描述 I2C 设备。目前在基于 ACPI 的设备枚举有关于此的特殊文档。

在板级文件中声明 I2C 设备

在许多嵌入式架构中,设备树已经取代了基于板级文件的旧硬件描述,但后者仍然在旧代码中使用。通过板级文件实例化 I2C 设备是通过调用i2c_register_board_info()注册一个struct i2c_board_info数组来完成的。

示例(来自 omap2 h4)

static struct i2c_board_info h4_i2c_board_info[] __initdata = {
      {
              I2C_BOARD_INFO("isp1301_omap", 0x2d),
              .irq            = OMAP_GPIO_IRQ(125),
      },
      {       /* EEPROM on mainboard */
              I2C_BOARD_INFO("24c01", 0x52),
              .platform_data  = &m24c01,
      },
      {       /* EEPROM on cpu card */
              I2C_BOARD_INFO("24c01", 0x57),
              .platform_data  = &m24c01,
      },
};

static void __init omap_h4_init(void)
{
      (...)
      i2c_register_board_info(1, h4_i2c_board_info,
                      ARRAY_SIZE(h4_i2c_board_info));
      (...)
}

上面的代码在 I2C 总线 1 上声明了 3 个设备,包括它们各自的地址和驱动程序所需的自定义数据。

方法 2:显式实例化设备

当较大的设备使用 I2C 总线进行内部通信时,此方法是合适的。典型的例子是电视适配器。这些适配器通常通过 I2C 总线将调谐器、视频解码器、音频解码器等连接到主芯片。您不会预先知道 I2C 总线的编号,因此无法使用上面描述的方法 1。相反,您可以显式实例化 I2C 设备。这是通过填写struct i2c_board_info 并调用 i2c_new_client_device() 来完成的。

示例(来自 sfe4001 网络驱动程序)

static struct i2c_board_info sfe4001_hwmon_info = {
      I2C_BOARD_INFO("max6647", 0x4e),
};

int sfe4001_init(struct efx_nic *efx)
{
      (...)
      efx->board_info.hwmon_client =
              i2c_new_client_device(&efx->i2c_adap, &sfe4001_hwmon_info);

      (...)
}

上面的代码在相关的网络适配器上的 I2C 总线上实例化了 1 个 I2C 设备。

当您不确定 I2C 设备是否存在时(例如,对于主板的廉价版本上不存在的可选功能,但您无法区分它们),或者它在不同的主板上可能具有不同的地址(制造商在不通知的情况下更改其设计)时,会出现这种情况的变体。在这种情况下,您可以调用 i2c_new_scanned_device() 而不是 i2c_new_client_device()

示例(来自 nxp OHCI 驱动程序)

static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

static int usb_hcd_nxp_probe(struct platform_device *pdev)
{
      (...)
      struct i2c_adapter *i2c_adap;
      struct i2c_board_info i2c_info;

      (...)
      i2c_adap = i2c_get_adapter(2);
      memset(&i2c_info, 0, sizeof(struct i2c_board_info));
      strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
      isp1301_i2c_client = i2c_new_scanned_device(i2c_adap, &i2c_info,
                                                  normal_i2c, NULL);
      i2c_put_adapter(i2c_adap);
      (...)
}

上面的代码在相关的 OHCI 适配器上的 I2C 总线上实例化了最多 1 个 I2C 设备。它首先尝试地址 0x2c,如果在那里找不到任何东西,它会尝试地址 0x2d,如果仍然找不到任何东西,它就会放弃。

实例化 I2C 设备的驱动程序负责在清理时销毁它。这是通过对 i2c_new_client_device() 或 i2c_new_scanned_device() 先前返回的指针调用 i2c_unregister_device() 来完成的。

方法 3:探测 I2C 总线上是否存在某些设备

有时您没有关于 I2C 设备的足够信息,甚至无法调用 i2c_new_scanned_device()。典型的例子是 PC 主板上的硬件监控芯片。有几十个型号,可以位于 25 个不同的地址。考虑到存在大量的主板,几乎不可能构建一个正在使用的硬件监控芯片的详尽列表。幸运的是,大多数这些芯片都有制造商和设备 ID 寄存器,因此可以通过探测来识别它们。

在这种情况下,I2C 设备既不声明也不显式实例化。相反,i2c-core 会在加载驱动程序后立即探测此类设备,如果找到任何设备,将自动实例化 I2C 设备。为了防止此机制的任何行为不当,适用以下限制:

  • I2C 设备驱动程序必须实现 detect() 方法,该方法通过读取任意寄存器来识别支持的设备。

  • 只会探测可能具有支持的设备并同意被探测的总线。例如,这避免了在电视适配器上探测硬件监控芯片。

示例:请参阅 drivers/hwmon/lm90.c 中的 lm90_driver 和 lm90_detect()

由于成功探测而实例化的 I2C 设备,将在检测到它们的驱动程序被删除或底层 I2C 总线本身被销毁时(以较早发生者为准)自动销毁。

那些熟悉 2.4 内核和早期 2.6 内核的 I2C 子系统的人会发现,此方法 3 本质上与当时所做的工作类似。两个显着的区别是:

  • 探测现在只是实例化 I2C 设备的一种方式,而当时它是唯一的方法。在可能的情况下,应首选方法 1 和方法 2。只有在没有其他方法时才应使用方法 3,因为它可能会产生不良的副作用。

  • I2C 总线现在必须显式说明哪些 I2C 驱动程序类可以探测它们(通过 class 位字段),而当时默认情况下会探测所有 I2C 总线。默认值是一个空类,这意味着不会发生探测。class 位字段的目的是限制上述不良副作用。

再次说明,应尽可能避免使用方法 3。显式设备实例化(方法 1 和 2)是更好的选择,因为它更安全、更快速。

方法 4:从用户空间实例化

一般来说,内核应该知道连接了哪些 I2C 设备以及它们位于哪些地址。但是,在某些情况下,它不知道,因此添加了一个 sysfs 接口来让用户提供信息。此接口由在每个 I2C 总线目录中创建的 2 个属性文件组成:new_devicedelete_device。这两个文件都是只写的,您必须向它们写入正确的参数才能正确地实例化和删除 I2C 设备。

new_device 文件接受 2 个参数:I2C 设备的名称(字符串)和 I2C 设备的地址(一个数字,通常以 0x 开头的十六进制表示,但也可以用十进制表示)。

delete_device 文件接受一个参数:I2C 设备的地址。由于在给定的 I2C 段上,没有两个设备可以位于同一地址,因此该地址足以唯一标识要删除的设备。

示例

# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device

虽然此接口只应在无法进行内核设备声明时使用,但在许多情况下它可以提供帮助:

  • I2C 驱动程序通常会检测设备(上面的方法 3),但您的设备所在的总线段没有设置正确的类位,因此不会触发检测。

  • I2C 驱动程序通常会检测设备,但您的设备位于意外的地址。

  • I2C 驱动程序通常会检测设备,但您的设备未被检测到,原因是检测例程过于严格,或者您的设备尚未正式支持,但您知道它是兼容的。

  • 您正在测试板上开发驱动程序,您自己焊接了 I2C 设备。

此接口是某些 I2C 驱动程序实现的 force_* 模块参数的替代品。它在 i2c-core 中实现,而不是在每个设备驱动程序中单独实现,因此效率更高,而且它还具有无需重新加载驱动程序即可更改设置的优点。您还可以在加载驱动程序甚至可用之前实例化设备,而且您无需知道设备需要什么驱动程序。