前言
分析传感器源码的时候,发现对 I2C 的驱动也有些忘记了,所以就再分析一下并形成这篇博文。
驱动架构
I2C 驱动架构
源码分析
1 | # alps\kernel-3.18\drivers\i2c |
重要的结构体
1 | # alps\kernel-3.18\include\linux\i2c.h |
关键路径
1 | # alps\kernel-3.18\arch\arm64\boot\dts\mt6797.dtsi |
总线驱动层
总线驱动层主要实现外设驱动部分,初始化硬件(i2c控制器)和提供操作硬件的方法,与 i2c-dev 相对应,其负责的部分通俗点讲就是:知道怎么发数据,但不知道发什么数据。
其关键流程如下:
- 获取资源
- 注册中断、使能时钟等初始化工作
- 构建 i2c_adapter
- 设置 i2c_adapter
- 注册 i2c_adapter
这部分源码就不在此文分析了,感兴趣的朋友可以参考外设系列
核心层(i2c-core)
构建一个 i2c 总线结构体,并且提供匹配方法和驱动用的结构体 ,如总线驱动层和设备驱动层的注册、注销等方法。此部分存在两个匹配过程:
- i2c 总线下的设备(i2c_client)与设备驱动(i2c_driver)之间的匹配。
- i2c控制器(i2c_adapter)与设备之间的匹配。
1
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# alps\kernel-3.18\drivers\i2c\i2c-core.c
struct bus_type i2c_bus_type = {
.name = "i2c", // 总线名
.match = i2c_device_match, // 匹配设备(i2c_client)与设备驱动(i2c_driver)
.probe = i2c_device_probe, // 注册挂载 i2c
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
__init i2c_init() // init 函数
# kernel-3.18/drivers/base/bus.c
bus_register(&i2c_bus_type) // 注册i2c总线 "/sys/bus/i2c"
i2c_add_driver(&dummy_driver) // // 注册 i2c 驱动创建“/sys/bus/i2c/driver/dummy”
i2c_register_driver() // 注册 i2c 驱动
driver_register(&driver->driver); // 注册设备驱动,创建上面的“dummy”设备文件
INIT_LIST_HEAD(&driver->clients)
i2c_device_probe()
i2c_verify_client(dev) // 获取 i2c_client
to_i2c_driver(dev->driver) // 获取 i2c_driver
driver->probe(client, i2c_match_id(driver->id_table,client)) // 调用设备驱动层 probe,查询外设(client)对应的 id
i2c_device_match()
client = i2c_verify_client(dev)// 通过 dev 获取 i2c_client
of_driver_match_device(dev, drv) // 通过 of 方式匹配
acpi_driver_match_device(dev, drv) // acpi 方式匹配
/* if 上述两种方式皆未成功 */
driver = to_i2c_driver(drv); //通过 drv 获取 i2c_driver
i2c_match_id(driver->id_table, client) // 通过查询 id_table 匹配
i2c_master_send() // 发送一个 i2c 数据包
// 构建 i2c_msg
i2c_transfer()
__i2c_transfer() // 发送数据包到总线驱动层
i2c_master_recv()
/* 通过动态获取|指定 bus number 注册 i2c 控制器 */
i2c_add_adapter()|i2c_add_numbered_adapter()
i2c_register_adapter(adapter)
dev_set_name(&adap->dev, "i2c-%d", adap->nr); // 设置 adapter 名字 “i2c-%d”
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
/*
* 注册设备, 默认创建的设备文件是: /sys/devices/i2c-%d
* 若注册 adapter 时指定了父设备,则为:/sys/devices/platform/xxx/i2c-%d
*/
device_register(&adap->dev);
/*
* 扫描 __i2c_board_list 匹配 adapter 与 i2c 次设备信息,匹配成功则创建 i2c 设备 (i2c_client)
*/
i2c_scan_static_board_info()
i2c_new_device() // 注册 i2c 设备
i2c_dev_set_name(adap, client) // 设置次设备名" %d-%04x"
device_register(&client->dev) // 注册次设备"/sys/devices/platform/xxx/i2c-%d/%d-%04x“
# alps\kernel-3.18\include\linux\i2c.h
i2c_add_driver(driver)
i2c_register_driver(THIS_MODULE, driver)注:主设备表示特定的驱动程序;次设备表示使用该设备驱动的设备
设备驱动层(i2c-dev)
设备驱动层主要是封装主机 i2c 的基本操作,给上层提供接口,与总线驱动层相对应,其:知道发什么数据,但不知道怎么发。
1 | # alps\kernel-3.18\drivers\i2c\i2c-dev.c (也可以是其他设备驱动文件,如:ov9650.c等。) |
用户空间
这里只看一种通用的通过 JNI 操作 i2c 设备的方案,i2c-dev 提供的接口通过 JNI 给 APP 使用,如下:
1 | # jni |