实现 I2C 设备驱动程序

这是一份小指南,适用于那些希望使用 Linux 作为协议主机/主设备(而不是从设备)为 I2C 或 SMBus 设备编写内核驱动程序的人。

要设置驱动程序,您需要执行几个操作。 有些是可选的,有些事情可以略有或完全不同地完成。 将其用作指南,而不是规则手册!

一般说明

尽量保持内核命名空间尽可能干净。 最好的方法是对所有全局符号使用唯一前缀。 这对于导出的符号尤其重要,但对于未导出的符号也是一个好主意。 在本教程中,我们将使用前缀 foo_

驱动程序结构

通常,您将实现单个驱动程序结构,并从中实例化所有客户端。 请记住,驱动程序结构包含通用访问例程,并且应进行零初始化,除非您提供数据的字段。 客户端结构包含设备特定的信息,如驱动程序模型设备节点及其 I2C 地址。

static const struct i2c_device_id foo_idtable[] = {
      { "foo", my_id_for_foo },
      { "bar", my_id_for_bar },
      { }
};
MODULE_DEVICE_TABLE(i2c, foo_idtable);

static struct i2c_driver foo_driver = {
      .driver = {
              .name   = "foo",
              .pm     = &foo_pm_ops,  /* optional */
      },

      .id_table       = foo_idtable,
      .probe          = foo_probe,
      .remove         = foo_remove,

      .shutdown       = foo_shutdown, /* optional */
      .command        = foo_command,  /* optional, deprecated */
}

name 字段是驱动程序名称,不能包含空格。 它应该与模块名称匹配(如果驱动程序可以编译为模块),尽管您可以使用 MODULE_ALIAS(在本例中传递“foo”)为模块添加另一个名称。 如果驱动程序名称与模块名称不匹配,则不会自动加载该模块(热插拔/冷插拔)。

所有其他字段都是用于回调函数,这将在下面进行解释。

额外的客户端数据

每个客户端结构都有一个特殊的 data 字段,可以指向任何结构。 您应该使用它来保存特定于设备的数据。

/* store the value */
void i2c_set_clientdata(struct i2c_client *client, void *data);

/* retrieve the value */
void *i2c_get_clientdata(const struct i2c_client *client);

请注意,从内核 2.6.34 开始,您不必再在 remove() 或 probe() 失败时将 data 字段设置为 NULL。 i2c-core 会在这些情况下自动执行此操作。 这些也是核心将触及此字段的唯一时间。

访问客户端

假设我们有一个有效的客户端结构。 在某个时候,我们需要从客户端收集信息,或将新信息写入客户端。

我发现为此定义 foo_read 和 foo_write 函数很有用。 对于某些情况,直接调用 I2C 函数会更容易,但许多芯片都有某种可以轻松封装的寄存器值概念。

以下函数是简单的示例,不应逐字复制

int foo_read_value(struct i2c_client *client, u8 reg)
{
      if (reg < 0x10) /* byte-sized register */
              return i2c_smbus_read_byte_data(client, reg);
      else            /* word-sized register */
              return i2c_smbus_read_word_data(client, reg);
}

int foo_write_value(struct i2c_client *client, u8 reg, u16 value)
{
      if (reg == 0x10)        /* Impossible to write - driver error! */
              return -EINVAL;
      else if (reg < 0x10)    /* byte-sized register */
              return i2c_smbus_write_byte_data(client, reg, value);
      else                    /* word-sized register */
              return i2c_smbus_write_word_data(client, reg, value);
}

探测和附加

Linux I2C 堆栈最初是为支持访问 PC 主板上的硬件监控芯片而编写的,因此过去会嵌入一些更适合 SMBus(和 PC)而不是 I2C 的假设。 其中一个假设是,大多数适配器和设备驱动程序都支持 SMBUS_QUICK 协议来探测设备是否存在。 另一个假设是,可以使用这些探测原语充分配置设备及其驱动程序。

随着 Linux 及其 I2C 堆栈在嵌入式系统和 DVB 适配器等复杂组件中得到更广泛的使用,这些假设变得更加成问题。 发出中断的 I2C 设备驱动程序需要更多(和不同)的配置信息,处理无法通过协议探测区分的芯片变体的驱动程序也是如此,或者需要一些板特定的信息才能正确操作。

设备/驱动程序绑定

系统基础架构,通常是板特定的初始化代码或启动固件,报告存在哪些 I2C 设备。 例如,内核或启动加载程序中可能有一个表,用于识别 I2C 设备并将它们链接到关于 IRQ 和其他接线人为因素、芯片类型等的板特定配置信息。 这可以用于为每个 I2C 设备创建 i2c_client 对象。

使用此绑定模型的 I2C 设备驱动程序的工作方式与 Linux 中的任何其他类型的驱动程序一样:它们提供一个 probe() 方法来绑定到这些设备,并提供一个 remove() 方法来取消绑定。

static int foo_probe(struct i2c_client *client);
static void foo_remove(struct i2c_client *client);

请记住,i2c_driver 不会创建这些客户端句柄。 该句柄可以在 foo_probe() 期间使用。 如果 foo_probe() 报告成功(零而不是负状态代码),它可以保存该句柄并使用它直到 foo_remove() 返回。 大多数 Linux 驱动程序都使用此绑定模型。

当 id_table name 字段中的条目与设备的名称匹配时,将调用 probe 函数。 如果 probe 函数需要该条目,它可以检索它,使用

const struct i2c_device_id *id = i2c_match_id(foo_idtable, client);

设备创建

如果您确定 I2C 设备已连接到给定的 I2C 总线,则可以通过简单地使用设备地址和驱动程序名称填充 i2c_board_info 结构并调用 i2c_new_client_device() 来实例化该设备。 这将创建设备,然后驱动程序核心将负责查找正确的驱动程序并调用其 probe() 方法。 如果驱动程序支持不同的设备类型,您可以使用 type 字段指定所需的类型。 您还可以根据需要指定 IRQ 和平台数据。

有时您知道设备已连接到给定的 I2C 总线,但您不知道它使用的确切地址。 例如,这发生在电视适配器上,其中同一驱动程序支持数十种略有不同的型号,并且 I2C 设备地址因型号而异。 在这种情况下,您可以使用 i2c_new_scanned_device() 变体,它与 i2c_new_client_device() 类似,只不过它需要一个额外的可能 I2C 地址列表来探测。 为列表中第一个响应地址创建设备。 如果您期望地址范围内存在多个设备,只需多次调用 i2c_new_scanned_device() 即可。

i2c_new_client_device() 或 i2c_new_scanned_device() 的调用通常发生在 I2C 总线驱动程序中。 您可能希望保存返回的 i2c_client 引用以供以后使用。

设备检测

设备检测机制存在许多缺点。 您需要一些可靠的方法来识别受支持的设备(通常使用特定于设备的专用识别寄存器),否则可能会发生误检测,并且事情很快就会出错。 请记住,I2C 协议不包含任何检测芯片是否以给定地址存在的标准方法,更不用说识别设备的标准方法了。 更糟糕的是,缺少与总线传输相关的语义,这意味着同一个传输可以被一个芯片视为读取操作,而被另一个芯片视为写入操作。 由于这些原因,设备检测被认为是遗留机制,不应在新代码中使用。

设备删除

可以使用 i2c_new_client_device() 或 i2c_new_scanned_device() 创建的每个 I2C 设备可以通过调用 i2c_unregister_device() 来注销。 如果您没有显式调用它,则会在删除底层 I2C 总线本身之前自动调用它,因为设备无法在其父设备在设备驱动程序模型中存在的情况下继续存在。

初始化驱动程序

当内核启动时,或者当您的 foo 驱动程序模块插入时,您必须进行一些初始化。 幸运的是,通常只需注册驱动程序模块就足够了。

static int __init foo_init(void)
{
      return i2c_add_driver(&foo_driver);
}
module_init(foo_init);

static void __exit foo_cleanup(void)
{
      i2c_del_driver(&foo_driver);
}
module_exit(foo_cleanup);

The module_i2c_driver() macro can be used to reduce above code.

module_i2c_driver(foo_driver);

请注意,某些函数标有 __init。 这些函数可以在内核启动(或模块加载)完成后删除。 同样,标有 __exit 的函数在代码构建到内核中时会被编译器丢弃,因为它们永远不会被调用。

驱动程序信息

/* Substitute your own name and email address */
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"
MODULE_DESCRIPTION("Driver for Barf Inc. Foo I2C devices");

/* a few non-GPL license types are also allowed */
MODULE_LICENSE("GPL");

电源管理

如果您的 I2C 设备在进入系统低功耗状态时需要特殊处理 - 例如将收发器置于低功耗模式或激活系统唤醒机制 - 请通过为驱动程序的 dev_pm_ops 实现适当的回调(如 suspend 和 resume)来实现。

这些是标准驱动程序模型调用,它们的工作方式与任何其他驱动程序堆栈一样。 这些调用可以休眠,并且可以使用 I2C 消息传递到正在挂起或恢复的设备(因为当发出这些调用时,它们的父 I2C 适配器处于活动状态,并且 IRQ 仍然启用)。

系统关机

如果您的 I2C 设备在系统关闭或重新启动(包括 kexec)时需要特殊处理 - 例如关闭某些东西 - 请使用 shutdown() 方法。

同样,这是一个标准驱动程序模型调用,其工作方式与任何其他驱动程序堆栈一样:这些调用可以休眠,并且可以使用 I2C 消息传递。

命令函数

支持通用的 ioctl 类函数回调。 您很少需要它,并且它的使用已被弃用,因此较新的设计不应使用它。

发送和接收

如果您想与您的设备通信,有几个函数可以执行此操作。 您可以在 <linux/i2c.h> 中找到所有这些函数。

如果您可以在普通的 I2C 通信和 SMBus 级别通信之间进行选择,请使用后者。 所有适配器都理解 SMBus 级别命令,但只有部分适配器理解普通的 I2C!

普通的 I2C 通信

int i2c_master_send(struct i2c_client *client, const char *buf,
                    int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);

这些例程从客户端读取/向客户端写入一些字节。 客户端包含 I2C 地址,因此您不必包含它。 第二个参数包含要读取/写入的字节,第三个参数是要读取/写入的字节数(必须小于缓冲区的长度,也应小于 64k,因为 msg.len 是 u16)。 返回的是实际读取/写入的字节数。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg,
                 int num);

这会发送一系列消息。 每个消息可以是读取或写入,并且它们可以以任何方式混合。 事务是组合的:在事务之间不发出停止条件。 i2c_msg 结构包含每个消息的客户端地址、消息的字节数和消息数据本身。

您可以阅读文件 I2C 协议 以获取有关实际 I2C 协议的更多信息。

SMBus 通信

s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
                   unsigned short flags, char read_write, u8 command,
                   int size, union i2c_smbus_data *data);

这是通用的 SMBus 函数。 以下所有函数都是用它实现的。 永远不要直接使用此函数!

s32 i2c_smbus_read_byte(struct i2c_client *client);
s32 i2c_smbus_write_byte(struct i2c_client *client, u8 value);
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(struct i2c_client *client,
                              u8 command, u8 value);
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(struct i2c_client *client,
                              u8 command, u16 value);
s32 i2c_smbus_read_block_data(struct i2c_client *client,
                              u8 command, u8 *values);
s32 i2c_smbus_write_block_data(struct i2c_client *client,
                               u8 command, u8 length, const u8 *values);
s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client,
                                  u8 command, u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(struct i2c_client *client,
                                   u8 command, u8 length,
                                   const u8 *values);

这些已从 i2c-core 中删除,因为它们没有用户,但如果需要,以后可以重新添加

s32 i2c_smbus_write_quick(struct i2c_client *client, u8 value);
s32 i2c_smbus_process_call(struct i2c_client *client,
                           u8 command, u16 value);
s32 i2c_smbus_block_process_call(struct i2c_client *client,
                                 u8 command, u8 length, u8 *values);

所有这些事务在失败时都会返回一个负的 errno 值。 “write”事务在成功时返回 0; “read”事务返回读取的值,但块事务除外,块事务返回读取的值的数量。 块缓冲区不必超过 32 字节。

您可以阅读文件 SMBus 协议 以获取有关实际 SMBus 协议的更多信息。

通用例程

下面列出了所有通用例程,这些例程之前未提及

/* Return the adapter number for a specific adapter */
int i2c_adapter_id(struct i2c_adapter *adap);