驱动之I2C驱动
总览
本文使用 linux-2.6.22.6 内核, 由于jz2440开发板没有板载I2C设备, 因此源码部分无法实际测试.
I2C驱动框架分析
I2C协议本身不是太复杂, 但Linux内核为了通用化, 搞了一套复杂的总线系统.
最要理解i2c框架, 尝试理解 i2c_add_adapter
和 i2c_add_driver
就行了.
i2c_adapter
对i2c主机的抽象概念, 与i2c_add_adapter
相关- 这部分的架构都是已经搭好的, 由CPU厂商完成.
- 针对特定的开发板, 一般情况只会有一个
i2c_adapter
. 会在/drivers/i2c/busses
下选取一个 - 但特殊情况, 如果需要用不同的数据预处理方式, 如
i2c-algo-bit
, 那么也可以抽象出多个i2c_adapter
i2c_driver
对i2c从机的抽象概念, 与i2c_add_driver
相关- Linux内核给了很多i2c芯片的驱动范例. 我们所说的开发i2c驱动, 是位于这一端的.
- 一个真实的i2c从机设备并非对应唯一的
i2c_driver
. - 譬如i2c芯片24cXX. 可以对APP端抽象出多种概念:
- linux内核为了让APP端能直接操作i2c, 通过
i2c-dev.c
实现了一个i2c_driver
- 系统里的
eeprom.c
, 帮我们实现了通用eeprom的操作. 就是另一个i2c_driver
- 我们自己也可以写一个驱动, 将24cXX认为是一块加密芯片. 就是第三个
i2c_driver
- 这样, i2c从机端的底层都是一样的, 但上层的抽象概念是不同的. 或许, 这也是将主机端取名为
i2c_adapter
的原因, 它只是一个通讯适配器. 将APP层的不同抽象概念适配到一个个具体的i2c芯片上.
- linux内核为了让APP端能直接操作i2c, 通过
i2c_adapter
和i2c_driver
的关联方式- 就是 platform 总线架构, 两个链表有新加内容后, 循环查找匹配.
- 是否匹配有两个要点:
- 一是
i2c_adapter.nr
和i2c_client_address_data
里的设置是否一样 - 这里基本都不用这个值去匹配的. 总线驱动也没去设置
i2c_adapter.nr
. 设备端驱动直接设置为ANY_I2C_BUS
即可. - 二是 i2c 的物理地址, 根据物理地址实际通讯一下, 来进行匹配.
- 如果用了
i2c_client_address_data.force
, 那么物理地址的检测过程也将被忽略.
- 一是
相关文件
./drivers/i2c/i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。- 此文件就是
i2c核心层
作用是承上启下: - 对上, 提供统一的调用接口, 屏蔽硬件差异. 如提供
read
write
函数. - 对下, 抽象出i2c操作通用的部分, 简化i2c的硬件驱动开发.
- 此文件就是
-
./drivers/i2c/busses
包含了各个芯片厂商的I2C总线的驱动- 如
i2c-s3c2410.c
针对S3C系列处理器的I2C控制器驱动.
- 如
./drivers/i2c/i2c-dev.c
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备.- 把这个文件理解为系统提供的一个i2c设备驱动程序即可. 需要手动加载.
- 此文件会调用
i2c_add_driver
, 系统默认注册的一个i2c设备, 可供app端直接调用. - i2c芯片另外需要自己的驱动程序, 去调用
i2c_add_driver
, 并注册设备.
./drivers/i2c/algos
文件夹实现了一些I2C总线适配器的algorithm.- algorithm 这个词让人容易误解. 我的理解是数据预处理方式的不同.
i2c-algo-pca.c
. 可参考 PCF8584 I2C-bus controlleri2c-algo-pcf.c
. 可参考 PCA9564 Parallel bus to I2C-bus controlleri2c-algo-sgi.c
. 应该针对给2款早已过时的PC机用的.I2C_ALGO_XXX
的宏定义可以在./include/linux/i2c-id.h
下找到
数据结构
struct i2c_driver
提供probe
remove
等函数接口. i2c从机设备驱动使用i2c_add_driver
函数使用. 与i2c_adapter
对应, 两者需要匹配.
struct i2c_adapter
适配器. 就是将多种多样的底层I2C硬件需求(不同地址, 不同通讯方法)给一个统一的方法接入到I2C核心层.- 指定通讯方式(i2c_algorithm)
- 指定i2c设备(i2c_client)
i2c_add_adapter
函数使用. 与i2c_driver
对应, 两者需要匹配.
struct i2c_client
描述了真实设备的所有必要信息, 如 i2c addr, 设备名称, 中断号等等.- 除了提供给
i2c_adapter
外, 还直接和i2c_driver
想关联. - 原因应该是内核层和应用层都需要方便的读取真实i2c设备的必要信息
- 除了提供给
struct i2c_algorithm
通讯方法. 其中两个函数指针是由底层硬件实现的. 相当于 i2c核心层和底层的接口- algorithm 这个词让人容易误解. 我的理解是数据预处理方式的不同.
- 只和
i2c_adapter
相关, 给i2c主机提供收发功能 .master_xfer
发送函数, 需要底层实现.struct i2c_msg
用于存放通讯时的地址, 数据buf, 长度等信息.functionality
驱动支持的功能, 需要底层明确.- 底层没有接收函数. 因为i2c通讯必须由主机发起并提供时钟, 发送的同时就会接收数据.
源码分析
// =========== 从 i2c_add_driver 看 ========== |
编写I2C设备驱动
一般的, I2C总线驱动也由芯片公司完成了.
因此, 当外接了某个i2c设备时, 只需要编写一下设备驱动就可以了.
linux内核还包含了常用的 i2c 设备如eeprom. 可以在 ./drivers/i2c/chips
下看看.
核心步骤如下:
- 分配一个i2c_driver结构体
- 设置:
- `attach_adapter`, 它直接调用 i2c_probe (adap, 设备地址, 发现这个设备后要调用的函数) - `detach_client`, 卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理
- 注册:
i2c_add_driver
- 注册为
字符设备
或其它. 如input系统
块设备
, 并实现对应的操作函数.
at24cxx.c
|
Makefile
TEST_FILE := i2c_test |
i2c_test.c
|
测试
# Ubuntu 主机端 |
参考资料
原创于 DRA&PHO