05、i2c_bus 驱动总线说明
下面用简单步骤说明I2C总线的工作流程:
注册总线:系统启动时,先把I2C总线添加到设备管理列表中
添加驱动:将对应的I2C设备驱动程序加入到总线的驱动集合里
匹配设备:系统自动扫描总线上所有设备,逐个检查:
- 通过简单对比(就像核对设备ID号)
- 如果发现驱动和设备能配对成功
激活设备:立即执行驱动的初始化操作(也就是驱动里的probe函数)
整个过程就像这样:系统先准备好通讯通道(总线),把可用的设备说明书(驱动)放进去,然后挨个检查每个设备,找到匹配的说明书后,就按照说明书指导完成设备的启用。
一、i2c 总线定义
I2C总线通过两个列表(驱动列表和设备列表)来管理设备和驱动。当设备被检测到时,系统会自动在驱动列表中寻找匹配的驱动程序并完成配对。当设备或驱动被移除时,系统会自动解除它们的连接并清理记录。这样就能让设备和驱动自动关联,同时方便地管理它们的添加和移除。
二、i2c 总线注册
linux 启动之后,默认执行 i2c_init。
在i2c_init接口中通过调用bus_register,进行i2c总线的注册。
i2c-core-base.c
static int __init i2c_init(void)
{
int retval;
retval = of_alias_get_highest_id("i2c");
down_write(&__i2c_board_lock);
if (retval >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = retval + 1;
up_write(&__i2c_board_lock);
retval = bus_register(&i2c_bus_type);
if (retval)
return retval;
is_registered = true;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver);
if (retval)
goto class_err;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
if (IS_ENABLED(CONFIG_ACPI))
WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
is_registered = false;
bus_unregister(&i2c_bus_type);
return retval;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- 使用 bus_register 函数来注册 I2C 总线类型(i2c_bus_type)。
- 通过 i2c_add_driver 函数注册名为 dummy_driver 的设备驱动程序。
三、i2c 设备和 i2c 驱动匹配规则
- of_driver_match_device 设备树匹配方法,检查I2C设备节点中的compatible属性是否与驱动里设定的匹配项一致。
- acpi_driver_match_device ACPI匹配方法,用于通过ACPI规范识别硬件设备。
- i2c_match_id 传统I2C匹配方法,直接对比I2C设备的名字和驱动中预设名称列表是否匹配。
四、i2c_device_probe接口分析
该接口为i2c总线的probe,该接口一般也就是调用driver的probe接口,实现探测操作。
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;
client->irq = client->init_irq;
if (!client->irq) {
int irq = -ENOENT;
if (client->flags & I2C_CLIENT_HOST_NOTIFY) {
dev_dbg(dev, "Using Host Notify IRQ\n");
/* Keep adapter active when Host Notify is required */
pm_runtime_get_sync(&client->adapter->dev);
irq = i2c_smbus_host_notify_to_irq(client);
} else if (dev->of_node) {
irq = of_irq_get_byname(dev->of_node, "irq");
if (irq == -EINVAL || irq == -ENODATA)
irq = of_irq_get(dev->of_node, 0);
} else if (ACPI_COMPANION(dev)) {
irq = i2c_acpi_get_irq(client);
}
if (irq == -EPROBE_DEFER) {
status = irq;
goto put_sync_adapter;
}
if (irq < 0)
irq = 0;
client->irq = irq;
}
driver = to_i2c_driver(dev->driver);
/*
* An I2C ID table is not mandatory, if and only if, a suitable OF
* or ACPI ID table is supplied for the probing device.
*/
if (!driver->id_table &&
!acpi_driver_match_device(dev, dev->driver) &&
!i2c_of_match_device(dev->driver->of_match_table, client)) {
status = -ENODEV;
goto put_sync_adapter;
}
if (client->flags & I2C_CLIENT_WAKE) {
int wakeirq;
wakeirq = of_irq_get_byname(dev->of_node, "wakeup");
if (wakeirq == -EPROBE_DEFER) {
status = wakeirq;
goto put_sync_adapter;
}
device_init_wakeup(&client->dev, true);
if (wakeirq > 0 && wakeirq != client->irq)
status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);
else if (client->irq > 0)
status = dev_pm_set_wake_irq(dev, client->irq);
else
status = 0;
if (status)
dev_warn(&client->dev, "failed to set up wakeup irq\n");
}
dev_dbg(dev, "probe\n");
status = of_clk_set_defaults(dev->of_node, false);
if (status < 0)
goto err_clear_wakeup_irq;
status = dev_pm_domain_attach(&client->dev, true);
if (status)
goto err_clear_wakeup_irq;
/*
* When there are no more users of probe(),
* rename probe_new to probe.
*/
if (driver->probe_new)
status = driver->probe_new(client);
else if (driver->probe)
status = driver->probe(client,
i2c_match_id(driver->id_table, client));
else
status = -EINVAL;
if (status)
goto err_detach_pm_domain;
return 0;
err_detach_pm_domain:
dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:
dev_pm_clear_wake_irq(&client->dev);
device_init_wakeup(&client->dev, false);
put_sync_adapter:
if (client->flags & I2C_CLIENT_HOST_NOTIFY)
pm_runtime_put_sync(&client->adapter->dev);
return status;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
在I2C模块实现中,流程主要分为两步:
- 首先判断设备类型:系统会检查设备类型是否为I2C客户端类型。因为I2C适配器和I2C客户端都会注册到I2C总线上,所以需要先区分设备类型。只有当设备是I2C客户端时,才会进行后续的驱动绑定操作。
- 直接调用专用初始化函数:确认是I2C客户端后,系统会直接调用I2C驱动的probe函数(i2c_driver->probe),而不是通用设备驱动的probe函数(device_driver->probe)。虽然大多数情况下这两种probe函数功能相同,但这里需要明确调用I2C专用的初始化流程来确保正确性。
这样处理的原因是:I2C总线上的设备需要特殊的初始化流程,直接使用对应的驱动接口能更精准地完成设备与驱动的匹配和配置。
五、注册 I2C 设备驱动
5.1、i2c_add_driver
I2C 设备的注册使用的函数为 i2c_add_driver, 被定义在内核源码的“include/linux/i2c.h”目录下, 具体内容如下所示:
i2c_add_driver是一个宏,它简化了注册I2C设备驱动的步骤。实际执行注册操作的是i2c_register_driver函数,该函数定义在drivers/i2c/i2c-core-base.c文件中。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* 在驱动模型初始化完成之前,无法注册驱动程序 */
if (WARN_ON(!is_registered))
return -EAGAIN;
/* 将驱动程序添加到驱动核心的 i2c 驱动列表中 */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
INIT_LIST_HEAD(&driver->clients);
/* 当注册返回时,驱动核心会为所有匹配但尚未绑定的设备调用 probe() 函数 */
res = driver_register(&driver->driver);
if (res)
return res;
pr_debug("driver [%s] registered\n", driver->driver.name);
/* 遍历所有已经存在的适配器 */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
该函数的主要功能是向系统注册I2C设备驱动,并初始化相关设置。在使用时需要传入一个i2c_driver类型的结构体,开发者需要在编写驱动时填写这个结构体的参数。这个结构体定义在头文件"include/linux/i2c.h"中。
struct i2c_driver {
unsigned int class; // 驱动程序所属的设备类型
int (*probe)(struct i2c_client *, const struct i2c_device_id *); // 探测并绑定设备的回调函数
int (*remove)(struct i2c_client *); // 从设备上解绑驱动程序的回调函数
int (*probe_new)(struct i2c_client *); // 新的探测设备并绑定的回调函数
void (*shutdown)(struct i2c_client *); // 设备关闭时调用的回调函数
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
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; // 与该驱动程序匹配的设备 ID 表
int (*detect)(struct i2c_client *, struct i2c_board_info *); // 用于自动创建设备的探测回调函数
const unsigned short *address_list; // 与该驱动程序匹配的设备地址列表
struct list_head clients; // 与该驱动程序绑定的 I2C 设备列表
bool disable_i2c_core_irq_mapping; // 禁用 I2C 核心中断映射的标志
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在调用 i2c_add_driver 函数注册 I2C 设备之前, 需要先填充 i2c_driver 结构体, 然后实现的各种回调函数, 跟前面讲解的平台总线内容相同。
5.2、module_i2c_driver
相当于这个宏:
i2c_add_driver(__i2c_driver);
i2c_del_driver(__i2c_driver)