1.i2c 框架源码分析
文章基于
-
内核版本
- linux-3.2.0 CPU
- TI 的 am3352
- 源码路径 kernel-3.2\drivers\i2c\busses\i2c-omap.c i2c从机设备
- Philips PCF8563 RTC
- 源码路径 kernel-3.2\drivers\rtc\rtc-pcf8563.c
2.linux i2c 代码目录结构
源码路径 kernel-3.2\drivers\i2c
i2c/
|-- busses
| |-- i2c-gpio.c
| `-- i2c-omap.c
|-- algos
| |-- i2c-algo-bit.c
| |-- i2c-algo-pca.c
| |-- i2c-algo-pcf.c
| `-- i2c-algo-pcf.h
|-- muxes
| `-- gpio-i2cmux.c
|-- i2c-boardinfo.c
|-- i2c-core.c
|-- i2c-core.h
|-- i2c-dev.c
|-- i2c-mux.c
`-- i2c-smbus.c
-
busses
- 适配器驱动,对应不同的cpu有不同的适配器驱动,通过对 cpu i2c 寄存器控制 i2c 外设进行协议收发
- 目录下的 i2c-omap.c 就是 ti335x 系列芯片的适配器驱动,而 i2c-gpio.c 就是通过 gpio 实现的模拟 i2c 协议收发的一个适配器驱动 algos
- 一般 i2c 算法(如开始/结束/应答/发送)由适配器驱动自行实现,这个目录下提供一些特殊的 i2c 收发算法
- 像 i2c-algo-bit.c 提供了 i2c-gpio.c 所需的一些算法 muxes
- i2c 多路复用的适配器驱动
- 实际使用上可能有些 i2c 需要多路复用的情况,例如同一个总线上接了多个 i2c 地址相同的 i2c 设备,这时候就需要用到 i2c 多路复用芯片
- 这个目录提供了 i2c 多路复用芯片的适配器驱动,像 gpio-i2cmux.c 就是利用 gpio 进行片选的 i2c 多路复用芯片的适配器驱动 i2c-boardinfo.c
- 提供注册 boardinfo 的接口代码 i2c-core.c i2c-core.h
- i2c 核心层代码 i2c-dev.c
- 为注册好的 i2c 总线注册生成设备,将在路径 /dev/ 下生成 i2c-1/i2c-2/i2c-3… 等 i2c 总线设备
- 为应用提供直接控制总线的方法 i2c-mux.c
- i2c 多路复用的核心层代码 i2c-smbus.c
- smbus 协议(跟 i2c 协议类似)代码
要了解 i2c 框架,重点看 busses 目录下的适配器驱动和核心层代码 i2c-core.c
其他只是对于框架的一些补充和扩展
3.linux i2c 框架
linux i2c 框架分为三层,分别是:
- i2c核心层
- i2c适配器层
- i2c驱动层
i2c读写的一次流程简单来说包括以下步骤:
- i2c驱动层组 i2c 消息包,调用i2c核心层接口,进i2c入核心层
- i2c核心层回调i2c适配器层已经注册好的 i2c 发送接口,进入i2c适配器层
- i2c适配器层通过读写寄存器完成一次与外接i2c设备的读写
3.1.i2c核心层
i2c核心层提供一下功能:
- 提供适配器注册接口
- 提供驱动注册接口
- 提供数据收发接口
- 管理适配器,对应结构体是 struct i2c_adapter
- 管理驱动,对应结构体是 struct i2c_client
3.2.i2c适配器层
- 根据cpu,提供i2c的收发函数,供i2c核心层调用
- 填充适配器结构体 struct i2c_adapter,调用i2c核心层接口注册进i2c总线
i2c适配器层需要提供 struct i2c_adapter 结构体的 struct i2c_algorithm *algo
字段
该字段是操作i2c总线的算法,源码如下:
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 *);
};
其中
-
master_xfer 函数
- 是 i2c协议收发函数.核心层的 i2c_transfer() 接口回调了这个函数 smbus_xfer 函数
- 是 smbus协议收发函数,smbus协议跟i2c类似,如果芯片要驱动支持smbus协议的外接芯片,则需要实现这个函数.核心层的 i2c_smbus_xfer() 等接口回调了这个函数 functionality 函数
-
返回该适配器支持的功能,例如如果支持十位地址,则返回
I2C_FUNC_10BIT_ADDR
3.3.i2c驱动层
- 根据外接i2c设备需求,实现读写操作,提供write、read、ioctl接口供应用程序调用
- 读写操作中调用i2c核心层
数据收发接口
收发数据 - 调用i2c核心层接口注册驱动进i2c总线
4.适配器层
4.1.平台设备 platform_device
源码: kernel-3.2\arch\arm\mach-omap2\board-am335xevm.c
将 board_info 注册到一条 i2c bus 上
一条 bus 对应一个平台设备
一般会调用几次将 cpu 的全部 i2c 总线都生成对应的适配器平台设备
/*外设I2C设备信息
包含一个与驱动层的平台驱动匹配的设备名称
和一个设备对应的I2C地址*/
static struct i2c_board_info i2c0_boardinfo[] = {
{
I2C_BOARD_INFO("tps65217", TPS65217_I2C_ID),
.platform_data = &beaglebone_tps65217_info,
},
{
I2C_BOARD_INFO("24c256", 0x50),
},
{
I2C_BOARD_INFO("pcf8563", 0x51),
},
};
static void __init am335x_evm_i2c_init(void)
omap_register_i2c_bus(1, 100, i2c0_boardinfo,ARRAY_SIZE(i2c0_boardinfo)); /*将一个/多个I2C外设挂在1号总线(I2C0)*/
->进入 int omap_register_i2c_bus(int bus_id, u32 clkrate, struct i2c_board_info const *info, unsigned len)
i2c_register_board_info(bus_id, info, len); /*注册 board_info*/
->进入 int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
list_add_tail(&devinfo->list, &__i2c_board_list); /*将 board_info 加到 __i2c_board_list 队列,在驱动层的平台设备/驱动匹配时有用到*/
omap_i2c_add_bus(bus_id);
omap2_i2c_add_bus(bus_id);
->进入 static inline int omap2_i2c_add_bus(int bus_id)
/*根据 bus_id 找到相关硬件信息 omap_hwmod*/
...
static const char name[] = "omap_i2c";
omap_device_build(name, ...); /*创建平台设备, name 为 omap_i2c*/
4.2.平台驱动 platform_driver
一个适配器对应一条总线
源码: kernel-3.2\drivers\i2c\busses\i2c-omap.c
通过omap_i2c_init_driver()函数注册一个平台驱动
omap_i2c_init_driver(void) /*适配器平台驱动初始化*/
platform_driver_register(&omap_i2c_driver); /*注册适配器平台驱动*/
struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.driver = {
.name = "omap_i2c",
},
};
适配器层的平台驱动与平台设备名字都是"omap_i2c",匹配后进入probe函数.
omap_i2c_probe(struct platform_device *pdev)
/*获取适配器平台设备资源,如i2c寄存器地址等*/
...
dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL); /*申请驱动结构体 omap_i2c_dev 的内存*/
omap_i2c_init(dev); /*i2c 总线 0/1/2/3... 的初始化,如时钟初始化*/
isr = (dev->rev < OMAP_I2C_OMAP1_REV_2) ? omap_i2c_omap1_isr :map_i2c_isr; /*设置中断回调,中断里面实现 i2c 协议的收发*/
request_irq(dev->irq, isr, IRQF_NO_SUSPEND, pdev->name, dev); /*申请中断*/
adap->class = I2C_CLASS_HWMON; /*适配器的 class 设置为 I2C_CLASS_HWMON*/
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name))