一、I2C协议
I2C和SMBus简介
? I2C(发音:I2C,在内核文档中写成I2C)是由Philips开发的协议。它是一种缓慢的两线协议(可变速度,最高可达400 kHz),具有高速扩展(3.4 MHz)。它提供了一种廉价的总线,用于连接需要不频繁或低带宽通信的多种设备。I2C广泛应用于嵌入式系统中。一些系统使用不符合品牌要求的变体,因此不标榜为I2C,而是使用不同的名称,例如TWI(两线接口),IIC。
最新的I2C官方规范是NXP Semiconductors发布的《I2C总线规范与用户手册》(UM10204)。但是,您需要登录到该站点才能访问PDF。此规范的旧版本(版本6)存档于此。
SMBus(系统管理总线)是基于I2C协议的,主要是I2C协议和信令的子集。许多I2C设备将在SMBus上工作,但是一些SMBus协议添加了超出实现I2C品牌所需的语义。现代PC主板依赖SMBus。通过SMBus连接的最常见设备是使用I2C eeprom配置的RAM模块和硬件监控芯片。
因为SMBus主要是广义I2C总线的一个子集,所以我们可以在许多I2C系统上使用它的协议。然而,有些系统不能同时满足SMBus和I2C的电气约束;以及其他不能实现所有通用SMBus协议语义或消息的组件。
术语
使用官方文档中的术语,I2C总线连接一个或多个主芯片和一个或多个从芯片。
? 简单的I2C总线
? 主芯片是启动与从芯片通信的节点。在Linux内核实现中,它被称为适配器或总线。适配器驱动程序在drivers/i2c/busses/子目录中。
? 一个算法包含通用代码,可用于实现整个I2C适配器类。每个特定的适配器驱动程序要么依赖于drivers/i2c/algos/子目录中的一个算法驱动程序,要么包含它自己的实现。
? 从芯片是当主芯片寻址时响应通信的节点。在Linux中,它被称为客户端。客户端驱动程序被保存在特定于它们提供的特性的目录中,例如用于gpio扩展器的驱动程序/media/gpio/和用于视频相关芯片的驱动程序/media/i2c/。
对于图中的示例配置,您需要I2C适配器的驱动程序和I2C设备的驱动程序(通常每个设备一个驱动程序)。
关键符号
S | 起始条件 |
---|
P | 停止条件 | Rd/Wr (1 bit) | 读/写。Rd等于1 Wr等于0。 | A, NA (1 bit) | 确认(ACK)和不确认(NACK)位 | Addr (7 bits) | I2C 7位地址。注意,可以将其扩展为10位I2C地址。 | Data (8 bits) | 纯数据字节。 | […] | 由I2C设备发送的数据,而不是由主机适配器发送的数据。 |
简单的发送传输
由i2c_master_send实现():
S Addr Wr [A] Data [A] Data [A]…A .数据[A] P
简单的接收传输
由i2c_master_recv实现():
S Addr Rd [A] [Data] A [Data] A…A[数据]NA P
合并传输
由i2c_transfer实现()。
它们就像上面的传输一样,但是发送的不是停止条件P,而是开始条件S,传输继续进行。一个读字节,然后写字节的例子:
S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P
修改交互
通过为I2C消息设置这些标志,也可以对I2C协议进行以下修改。除了I2C_M_NOSTART,它们通常只需要解决设备问题:
I2C_M_IGNORE_NAK:
正常情况下,如果有[NA]来自客户端,消息会立即中断。设置该标志将任何[NA]视为[A],并发送所有消息。这些消息仍然可能在SCL lo->hi超时时失败。 I2C_M_NO_RD_ACK:
在已读消息中,跳过master a /NA位。 I2C_M_NOSTART:
在合并传输中,在某个点上没有生成“S Addr Wr/Rd [a]”。例如,在第二个部分消息上设置I2C_M_NOSTART会产生如下结果:
S Addr Rd [A] [Data] NA Data [A] P
如果您为第一个部分消息设置了I2C_M_NOSTART变量,我们不生成Addr,但是我们生成了启动条件s。这可能会混淆您总线上的所有其他客户端,所以不要尝试这样做。
这通常用于从系统内存中的多个数据缓冲区收集传输到I2C设备的单个传输,但也可以在一些罕见的设备的方向变化之间使用。 I2C_M_REV_DIR_ADDR:
这将切换Rd/Wr标志。也就是说,如果您想进行写入操作,但需要发出Rd而不是Wr,或者反之亦然,您可以设置此标志。例如:
数据[A]数据[A]…A .数据[A] P
I2C_M_STOP:
在消息之后强制一个停止条件§。一些I2C相关协议(如SCCB)需要这样做。通常,您不希望在一次传输的消息之间被中断。
如何实例化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的速度连接到总线上。关于设置设备可能需要的其他属性,请参阅文档/设备树/bindings/中的设备树文档。
通过ACPI声明I2C设备
? ACPI也可以描述I2C设备。有关于这个的特殊文档,目前位于基于ACPI的设备枚举。
在板文件中声明I2C设备
? 在许多嵌入式架构中,设备树已经取代了基于板卡文件的硬件描述,但板卡文件在旧代码中仍然使用。通过board文件实例化I2C设备是通过调用i2c_register_board_info() 来注册一个数组结构i2c_board_info 来完成的。
例如(来自omap2h4):
static struct i2c_board_info h4_i2c_board_info[] __initdata = {
{
I2C_BOARD_INFO("isp1301_omap", 0x2d),
.irq = OMAP_GPIO_IRQ(125),
},
{
I2C_BOARD_INFO("24c01", 0x52),
.platform_data = &m24c01,
},
{
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总线上的一个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总线目录下创建的两个属性文件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重要的驱动接口
1. i2c_driver
I2C设备驱动结构
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
driver.owner 字段应该设置为该驱动程序的模块所有者。driver.name 字段应该设置为该驱动程序的名称。
? 对于自动设备检测,必须同时定义@detect和@address_list。@class也应该设置,否则只有强制使用模块参数的设备才会被创建。detect函数必须至少填充成功检测时传递给它的i2c_board_info结构的name字段,也可能是flags字段。
? 如果@detect缺失,驱动程序仍然可以在枚举设备上正常工作。检测到的设备将不受支持。对于许多无法可靠检测的I2C/SMBus设备,以及在实践中总能列举出来的设备,这都是意料之中的。
传递给@detect回调的i2c_client结构并不是一个真正的i2c_client。它被初始化了,这样您就可以调用i2c_smbus_read_byte_data和它的友元函数。不要用它做任何其他事情。特别是,不允许在dev_dbg上调用dev_dbg和friends。
2. i2c_client :
? I2C从设备结构,i2c_client 用于标识连接到i2c总线的单个设备(即芯片,如:AP3216C)。暴露在Linux中的行为是由管理设备的驱动程序定义的。
struct i2c_client {
unsigned short flags;
unsigned short addr;
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;
struct device dev;
int irq;
struct list_head detected;
};
? i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client(),i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述,i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
3. i2c_adapter
i2c_adapter 是用于标识物理i2c总线以及访问它所需的访问算法的结构。
struct i2c_adapter {
struct module *owner;
unsigned int class;
const struct i2c_algorithm *algo;
void *algo_data;
struct rt_mutex bus_lock;
int timeout;
int retries;
struct device dev;
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
? i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
4. i2c_algorithm
? i2c_algorithm 是I2C传输方法的接口,可以使用相同的总线算法来解决,例如位碰撞或PCF8584,这是最常见的两种算法。@master_xfer字段的返回码应该表明在传输过程中发生的错误码的类型,这在内核文档文件Documentation/i2c/fault-codes中有记录。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
? i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
? i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体。
5. i2c_msg
? i2c_msg是I2C传输的一个段的底层表示。它对@i2c_transfer()过程中的驱动程序、i2c-dev中的用户空间以及@i2c_adapter.@master_xfer()方法中的I2C适配器驱动程序都是可见的。
? 除了使用I2C“protocol mangling”之外,所有的I2C适配器都实现了I2C传输的标准规则。每个传输都从START开始。其后是从机地址,以及位编码读和写。然后跟着所有数据字节,可能包括一个带有SMBus PEC的字节。传输以一个NAK终止,或者当所有这些字节都被传输并被ACK后终止。如果这是组中的最后一条消息,则它后面会跟着STOP。否则,它后面是下一个@i2c_msg传输段,以(重复的)START开头。
? 另外,当适配器支持I2C_FUNC_PROTOCOL_MANGLING时,传递某些@flags可能会改变那些标准协议行为。这些标志只用于断开/不符合从机设备,以及已知支持它们需要的特定mangling选项的适配器(一个或多个IGNORE_NAK, NO_RD_ACK, NOSTART和REV_DIR_ADDR)。
struct i2c_msg {
__u16 addr;
__u16 flags;
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
#define I2C_M_STOP 0x8000
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_RECV_LEN 0x0400
__u16 len;
__u8 *buf;
};
? 可以看出,linux内核对i2c架构抽象出了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为下面的硬件实现提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。
6. i2c_board_info
? 设备创建模板,i2c_board_info 用于构建列出现有I2C设备的信息表。此信息用于生成驱动模型树。 对于主板,这是使用i2c_register_board_info() 静态完成的; 总线号码标识了尚未可用的适配器。对于外接板,i2c_new_device() 使用已知的适配器动态执行此操作。
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
int irq;
};
? I2C实际上不支持硬件探测,尽管控制器和设备可能能够使用I2C_SMBUS_QUICK来判断给定地址是否有设备。驱动程序通常需要更多的信息,如芯片类型、配置、相关IRQ等。
三、I2C核心驱动调用流程
i2c_init() 函数主要工作有:创建./bus/devices 和./bus/drivers 目录;初始化klist_devices 设备链表和klist_drivers 驱动链表;在/sys/class/ 目录下创建i2c-adapter 兼容性子类目录(/sys/class/i2c-adapter) ;将i2c_driver 空驱动挂接到klist_drivers 驱动链表
static int __init i2c_init(void)
--> retval = of_alias_get_highest_id("i2c");
--> list_for_each_entry(app, &aliases_lookup, link)
for (app = list_head(&aliases_lookup, typeof(*app), link);
&app->link != (&aliases_lookup);
app = list_next(app, link)) {
if (strcmp(app->stem, stem) != 0)
continue;
if (app->id > id)
id = app->id;
}
--> retval = bus_register(&i2c_bus_type);
-->
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);
retval = bus_create_file(bus, &bus_attr_uevent);
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);
retval = bus_add_groups(bus, bus->bus_groups);
--> i2c_adapter_compat_class = class_compat_register("i2c-adapter");
--> struct class_compat *cls;
cls = kmalloc(sizeof(struct class_compat), GFP_KERNEL);
if (!cls)
return NULL;
cls->kobj = kobject_create_and_add(name, &class_kset->kobj);
-->
retval = i2c_add_driver(&dummy_driver);
-->
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
res = driver_register(&driver->driver);
--> struct device_driver *other;
other = driver_find(drv->name, drv->bus);
ret = bus_add_driver(drv);
ret = driver_add_groups(drv, drv->groups);
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
INIT_LIST_HEAD(&driver->clients);
i2c_for_each_dev(driver, __process_new_driver);
--> mutex_lock(&core_lock);
res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
--> struct klist_iter i;
struct device *dev;
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
klist_iter_exit(&i);
mutex_unlock(&core_lock);
--> of_reconfig_notifier_register(&i2c_of_notifier);
--> return blocking_notifier_chain_register(&of_reconfig_chain, nb);
--> if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
down_write(&nh->rwsem);
ret = notifier_chain_register(&nh->head, n);
--> while ((*nl) != NULL) {
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
up_write(&nh->rwsem);
i2c_init().【注1】
struct alias_prop {
struct list_head link;
const char *alias;
struct device_node *np;
int id;
char stem[0];
};
struct alias_prop *app;
i2c_init().【注2】
struct list_head aliases_lookup;
i2c_init().【注3】
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *class;
};
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
i2c_init().【注4】
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return 0;
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
-->
i2c_detect(adap, driver);
if (driver->attach_adapter) {
dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
driver->driver.name);
dev_warn(&adap->dev, "Please use another way to instantiate "
"your i2c_client\n");
driver->attach_adapter(adap);
}
return 0;
}
i2c_init().【注5】
static struct notifier_block i2c_of_notifier = {
.notifier_call = of_i2c_notify,
};
static int of_i2c_notify(struct notifier_block *nb, unsigned long action,
void *arg)
{
struct of_reconfig_data *rd = arg;
struct i2c_adapter *adap;
struct i2c_client *client;
switch (of_reconfig_get_state_change(action, rd)) {
case OF_RECONFIG_CHANGE_ADD:
adap = of_find_i2c_adapter_by_node(rd->dn->parent);
--> dev = bus_find_device(&i2c_bus_type, NULL, node, of_dev_node_match);
return i2c_verify_adapter(dev);
client = of_i2c_register_device(adap, rd->dn);
--> struct i2c_client *result;
struct i2c_board_info info = {};
struct dev_archdata dev_ad = {};
const __be32 *addr;
int len;
if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
return ERR_PTR(-EINVAL);
}
addr = of_get_property(node, "reg", &len);
if (!addr || (len < sizeof(int))) {
return ERR_PTR(-EINVAL);
}
info.addr = be32_to_cpup(addr);
if (info.addr > (1 << 10) - 1) {
return ERR_PTR(-EINVAL);
}
info.of_node = of_node_get(node);
info.archdata = &dev_ad;
if (of_get_property(node, "wakeup-source", NULL))
info.flags |= I2C_CLIENT_WAKE;
result = i2c_new_device(adap, &info);
--> struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL);
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
status = i2c_check_client_addr_validity(client);
if (status) {
goto out_err_silent;
}
status = i2c_check_addr_busy(adap, client->addr);
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client);
status = device_register(&client->dev);
return client;
if (result == NULL) {
of_node_put(node);
return ERR_PTR(-EINVAL);
}
put_device(&adap->dev);
break;
case OF_RECONFIG_CHANGE_REMOVE:
client = of_find_i2c_device_by_node(rd->dn);
i2c_unregister_device(client);
put_device(&client->dev);
break;
}
}
-
i2c_device_match() 每当为i2c总线添加新设备或驱动程序时调用此函数,可能会调用多次。如果给定的设备可以由给定的驱动程序处理,它应该返回一个非零值。
static int i2c_device_match(struct device *dev, struct device_driver *drv) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
--> return of_match_device(drv->of_match_table, dev) != NULL;
--> return of_match_node(drv->of_match_table, dev->of_node);
--> match = __of_match_node(drv->of_match_table, dev->of_node);
--> score = __of_device_is_compatible(dev->of_node,
drv->of_match_table->compatible,
drv->of_match_table->type,
drv->of_match_table->name);
--> /* 当驱动of_match_table有compatible成员且其值不为空时 */
if (drv->of_match_table->compatible &&
drv->of_match_table->compatible[0]) {
/* 遍历"设备节点"所有的compatible属性值, 直到找到compatible属性 */
prop = __of_find_property(dev->of_node, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
/* "设备节点"的compatible属性与驱动of_match_table中的
* compatible成员比较, 一致时退出比较 */
if (of_compat_cmp(cp, drv->of_match_table->compatible,
strlen(drv->of_match_table->compatible)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
}
/* 当驱动of_match_table有type成员且其值不为空时 */
if (drv->of_match_table->type && drv->of_match_table->type[0]) {
/* "设备节点"的type属性与驱动of_match_table中的type成员比较,
* 一致时退出比较 */
if (!dev->of_node->type ||
of_node_cmp(drv->of_match_table->type,
dev->of_node->type))
return 0;
score += 2;
}
/* 当驱动of_match_table有name成员且其值不为空时 */
if (drv->of_match_table->name && drv->of_match_table->name[0]) {
/* "设备节点"的name属性与驱动of_match_table中的name成员比较,
* 一致时退出比较 */
if (!dev->of_node->name ||
of_node_cmp(drv->of_match_table->name,
dev->of_node->name))
return 0;
score++;
}
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
/* 比较i2c_client->name与i2c_device_id->name是否一致 */
return i2c_match_id(driver->id_table, client) != NULL;
}
3. `i2c_device_probe()` 在新设备或驱动程序添加到i2c总线时调用此函数,并回调特定驱动程序的探测以初始化匹配的设备。
```c
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
if (!client->irq && dev->of_node) {
int irq = of_irq_get(dev->of_node, 0);
if (irq == -EPROBE_DEFER)
return irq;
if (irq < 0)
irq = 0;
client->irq = irq;
}
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n");
status = of_clk_set_defaults(dev->of_node, false);
if (status < 0)
return status;
status = dev_pm_domain_attach(&client->dev, true);
if (status != -EPROBE_DEFER) {
status = driver->probe(client, i2c_match_id(driver->id_table,
client));
if (status)
dev_pm_domain_detach(&client->dev, true);
}
return status;
}
|