内核版本:linux-3.10.61
1、I2C总线硬件介绍
I2C总线在硬件上很简单,由两条线构成,分别是数据线SDA,时钟线SCL。在设备结构上它属于主从结构,一条总线上可以挂接多个设备,每个设备都有成为主机的潜力,但是在同一时刻只能有一个主机。对设备的读写操作都是由主设备发起的。在对从设备进行读写时,主设备会先发出从设备设备地址确定是对哪一个设备进行读写。
来分析一下I2C设备的时序图。第一步,发出Start信号,在高电平时将SDA拉低表示主设备将要进行读写操作。然后发出第一个字节的数据,在一个时钟周期内SCL为低电平时SDA根据数据变换电平高低,在SCL高电平时确定数据。第一个字节的前7位是设备地址,设备地址由设备手册和接线共同决定,最后一位是读写位,0表示写,1表示读。每发出8位数据后第9位就是一个ACK信号。在写操作时ACK由从设备发出,读设备时ACK由主设备发出。发出地址信号后接下来的数据就是需要读取或者写入的数据了。当数据传输结束后在SCL高电平时将SDA拉高,表示发出一个停止信号。这样一次I2C的数据传输就结束了。
2、I2C驱动框架分析
通常我们把I2C驱动框架分为3层,驱动层,核心层和适配器层。每个层级都有对应的结构体对象来表示。
2-1、I2C驱动框架分层
驱动层:具体设备的I2C驱动程序,主要实现数据的处理
核心层:提供各种操作函数并且实现I2C总线 i2c_bus_type
适配器层:实现读写设备函数。下面以三星的芯片s3c2410为例
2-2、I2C总线
看过平台总线驱动架构你就会发现,I2C总线也是用一个bus_type结构体来描述,实际上这是一个内核中很常用的总线驱动模型,后面专门出一章来细说。和platform总线一样,match函数会在设备和驱动匹配时被调用。probe函数会在匹配成功后被调用。
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match, /* 用于匹配device和driver */
.probe = i2c_device_probe, /* device和driver匹配成功后调用 */
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
2-3、I2C适配器
struct i2c_adapter的核心成员是struct i2c_algorithm *algo;这个结构体实现了I2C的发送和接收算法。
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
};
struct i2c_algorithm {
/* 用于和I2C通信 */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* 一种特殊的I2C通讯方式,也可以用master_xfer来实现 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
struct i2c_adapter使用函数 i2c_add_adapter()注册进系统。通过device_register()将i2c_adapter挂接到i2c_bus_type.p->klist_devices,i2c_client 也会挂接到i2c_bus_type.p->klist_devices,使用adap->dev.type可以区分i2c_client 和i2c_adapter。
int i2c_add_adapter(struct i2c_adapter *adapter)
{
return i2c_register_adapter(adapter);
}
static int i2c_register_adapter(struct i2c_adapter *adap)
{
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
}
2-4、驱动层i2c_client
struct i2c_client用来描述一个I2C设备。dev_type是一个字符串用于和driver的id_table进行匹配。dev_addr是i2c设备的硬件地址。i2c_client最终会被挂接到i2c_bus_type.p->klist_devices用于driver匹配。
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* I2C adapter */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
添加i2c_client 的方式总共有四种详情。不同的内核版本可能会有一些偏差,可以参考内核文档 "\Documentation\i2c\instantiating-devices"
第一种:静态声明
静态注册通过i2c_register_board_info()将struct i2c_board_info的信息添加到__i2c_board_list ,struct i2c_board_info一般使用宏I2C_BOARD_INFO(dev_type, dev_addr)来进行初始化。dev_type为设备类型,dev_addr是7位设备地址。
static struct i2c_board_info __initdata h4_i2c_board_info[] = {
{
I2C_BOARD_INFO("isp1301_omap", 0x2d),
.irq = OMAP_GPIO_IRQ(125),
},
{ /* EEPROM on mainboard */
I2C_BOARD_INFO("24c01", 0x52),
.platform_data = &m24c01,
},
{ /* EEPROM on cpu card */
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_board_list */
(...)
}
在注册i2c_adapter时,i2c_register_adapter()会调用i2c_scan_static_board_info()将__i2c_board_list 链表中的信息提取出来通过i2c_new_device()构建一个i2c_client。
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
第二种:显式声明
将struct i2c_board_info作为参数直接传递给i2c_new_device(),得到一个i2c_client,和第一种方法不同的是直接调用函数i2c_new_device()。
下面是内核文档中提供的实例代码
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_device(&efx->i2c_adap, &sfe4001_hwmon_info);
(...)
}
知道怎么用i2c_new_device()了就来分析一下实现该函数实现原理吧。分配一段空间给client,然后初始化它,最后调用device_register()将client挂接到i2c_bus_type.p->klist_devices,通过dev.type来区别这个device是一个client还是adapter。
struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) {
struct i2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL);
/* ... */
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type; /* 把device类型设置为client */
client->dev.of_node = info->of_node;
/* ... */
status = device_register(&client->dev);
/* ... */
}
对于显式声明还有第二种方式,得到一个i2c_client,实现方式如下所示。
static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
static int __devinit 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));
strlcpy(i2c_info.type, "isp1301_nxp", I2C_NAME_SIZE);
isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
normal_i2c, NULL);
i2c_put_adapter(i2c_adap);
(...)
}
同样来分析一下i2c_new_probed_device()的实现过程做了什么特殊处理,依次调用probe函数访问传入的设备地址,若设备有响应就跳出循环调用i2c_new_device()生成一个client。
struct i2c_client *
i2c_new_probed_device(struct i2c_adapter *adap,
struct i2c_board_info *info,
unsigned short const *addr_list,
int (*probe)(struct i2c_adapter *, unsigned short addr))
{
if (!probe)
probe = i2c_default_probe;
for (i = 0; addr_list[i] != I2C_CLIENT_END; i++){
/* Test address responsiveness */
if (probe(adap, addr_list[i]))
break;
}
if (addr_list[i] == I2C_CLIENT_END) {
dev_dbg(&adap->dev, "Probing failed, no device found\n");
return NULL;
}
info->addr = addr_list[i];
return i2c_new_device(adap, info);
}
第三种:用户空间创建
通过执行命令 "echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-*/new_device",第一个参数是名称,第二个参数是设备地址,其中"i2c-*" *是通配符根据你自己设备实际情况取值。可以生成一个client 。这种方法往往在测试时使用。
第四种方法在注册i2c_driver时介绍。
2-5、驱动层i2c_driver
i2c_driver用来描述IIC硬件驱动程序,实现对IIC数据的处理。id_table用于匹配设备。在设备匹配成功后会调用probe函数。
struct i2c_driver {
unsigned int class;
/* 比较老的内核使用的方法探测设备 */
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
int (*probe)(struct i2c_client *, const struct i2c_device_id *); /* 匹配到device后调用 */
int (*remove)(struct i2c_client *); /* 设备移除时调用 */
struct device_driver driver; /* 实现一个driver */
const struct i2c_device_id *id_table; /* 用于匹配device */
int (*detect)(struct i2c_client *, struct i2c_board_info *); /* 设置了这个方法可以生成一个i2c_client */
const unsigned short *address_list;
struct list_head clients;
};
i2c_driver通过i2c_register_driver()函数注册进内核。通过driver_register() i2c_driver会被挂接到i2c_bus_type.p->klist_drivers用于device匹配。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
driver->driver.bus = &i2c_bus_type;
res = driver_register(&driver->driver);
/*
* 取出i2c_bus上所有的adapter类型的device和driver进行匹配
* __process_new_driver()可以生成一个i2c_client
*/
i2c_for_each_dev(driver, __process_new_driver);
}
client的第四种生成方式就是在__process_new_driver()实现的,跟着这个入口看看来实现过程。
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type) /* 判断是一个adapter类型的device */
return 0;
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
struct i2c_adapter *adap)
{
i2c_detect(adap, driver); /* 重要过程在这里,往下看 */
if (driver->attach_adapter) {
driver->attach_adapter(adap);
}
}
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
address_list = driver->address_list;
if (!driver->detect || !address_list) /* 如果的i2c_driver.detect未实现或者地址列表为空则返回*/
return 0;
/* 为每一个地址调用 i2c_detect_address()*/
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver);
}
}
static int i2c_detect_address(struct i2c_client *temp_client,
struct i2c_driver *driver)
{
if (!i2c_default_probe(adapter, addr)) /* 检测总线上是否存在该设备地址 */
return 0;
err = driver->detect(temp_client, &info); /* 调用 driver->detect()设置info.type */
if (info.type[0] == '\0') {
;
} else {
client = i2c_new_device(adapter, &info); /* 得到一个i2c_client */
list_add_tail(&client->detected, &driver->clients); /* i2c_client 挂接到i2c_driver */
}
}
2-6、消息结构体
struct i2c_msg用来描述一个I2C消息,当有多条消息时一般定义为一个结构数组。i2c_transfer()用来接收/发送消息。这个发送函数也只是通过调用adap->algo->master_xfer()实现接收/发送功能。
struct i2c_msg {
__u16 addr; /* 7位设备地址 */
__u16 flags; /* 读写标志0表示写,1表示读 */
__u16 len; /* 消息长度 */
__u8 *buf; /* 存储数据的buffer */
};
3、i2c_client和i2c_driver的匹配
通过前面的分析我们得知注册i2c_client会调用register_device(),注册i2c_driver会调用到register_driver()。这两个函数最终都会调用i2c_bus的match函数来匹配driver,而后调用bus的probe函数。如果不清楚register_device()/register_driver()是如何调用到bus的match函数和probe函数请看我之前的文章Linux Platform 平台设备驱动模型简介。
分析i2c_device_match()我们可以得出结论:通过比较drv->id_table和i2c_client的name来匹配i2c_driver和i2c_client。
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 (of_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* 通过id_table进行匹配 */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0) /* 比较id_table和client的name */
return id;
id++;
}
return NULL;
}
其实i2c_bus的probe函数就会调用到i2c_driver中实现的probe函数
static int i2c_device_probe(struct device *dev){
struct i2c_driver *driver;
driver = to_i2c_driver(dev->driver);
status = driver->probe(client, i2c_match_id(driver->id_table, client));
}
4、总结
从总线上看实现上I2C设备是简单的,虽然在驱动框架上已经有大佬帮我们实现了,但是不同的设备也会有不同的操作方式需要在工作中去学习了解。感谢你的阅读,祝你早日找到自己的路,成为你想成为的人!
恭喜获得知识点+1
若有讲解不妥之处烦请在评论区指正!
如果有收获那就一键三连鼓励一下吧!