如何实例化I2C设备

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

方法一:静态声明I2C设备

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

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

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

通过设备树声明I2C设备

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

示例:

i2c1: i2c@400a0000 {
        /* ... master properties skipped ... */
        clock-frequency = <100000>;
     status = "ok"; 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 Based Device Enumeration

I2C总线控制器后面的从设备只需要添加ACPI id。一旦注册了适配器,I2C核心就会自动枚举控制器设备后面的任何从设备。

下面是如何将ACPI支持添加到现有的mpu3050输入驱动程序的示例:

#ifdef CONFIG_ACPI
static const struct acpi_device_id mpu3050_acpi_match[] = {
        { "MPU3050", 0 },
        { },
};
MODULE_DEVICE_TABLE(acpi, mpu3050_acpi_match);
#endif

static struct i2c_driver mpu3050_i2c_driver = {
        .driver = {
                .name   = "mpu3050",
                .owner  = THIS_MODULE,
                .pm     = &mpu3050_pm,
                .of_match_table = mpu3050_of_match,
                .acpi_match_table = ACPI_PTR(mpu3050_acpi_match),
        },
        .probe          = mpu3050_probe,
        .remove         = mpu3050_remove,
        .id_table       = mpu3050_ids,
};

在单板文件中声明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设备。这是通过填充结构体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驱动程序类可以探测它们(通过类位域的方式),而在当时默认情况下,所有I2C总线都被探测。默认值是一个空类,这意味着没有探测发生。类位字段的目的是限制上述不希望出现的副作用。

再次强调,应尽可能避免使用方法3。显式设备实例化(方法1和2)更可取,因为它更安全、更快。

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

一般来说,内核应该知道连接了哪些I2C设备以及它们所在的地址。但是,在某些情况下,它不会,所以添加了一个sysfs接口来让用户提供信息。这个接口由在每个I2C总线目录中创建的2个属性文件组成:new_device delete_device。这两个文件都是只写的,以便正确地实例化(分别删除)I2C设备。

文件new_device接受两个参数: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中实现而不是在每个设备驱动程序中单独实现,它更高效,而且还具有不必重新加载驱动程序来更改设置的优点。你也可以在驱动加载或可用之前实例化设备,你不需要知道设备需要什么驱动。

使用这种方法实例化的I2C设备,在设备驱动程序中可以通过别名匹配或者OF匹配表匹配。例如:

/* 如果使用OF匹配表匹配,i2c_driver.id_table必须存在,即使为空 */
static
const struct i2c_device_id i2c_id_table[] = {   {"eeprom", 0}, /* 若使用OF匹配表匹配,可注释掉此行 */
  {} }; MODULE_DEVICE_TABLE(i2c, i2c_id_table);
static const struct of_device_id eeprom_dt_ids[] = {   {.compatible = "xxx(制造商),eeprom",},   {} }; MODULE_DEVICE_TABLE(of, eeprom_dt_ids); static struct i2c_driver eeprom_drv = {
  .driver
= {
    .name
= "***",   .owner = THIS_MODULE,
    .of_match_table
= of_match_ptr(eeprom_dt_ids), /* 若使用别名匹配,可注释掉此行 */ }, .probe = eeprom_probe, .remove = eeprom_remove, .id_table = i2c_id_table, };

 

posted @ 2021-08-16 16:34  闹闹爸爸  阅读(680)  评论(0编辑  收藏  举报