I2C系统驱动模型
参考资料
- Linux内核文档:
Documentation\i2c\instantiating-devices.rst
Documentation\i2c\writing-clients.rst
- Linux内核驱动程序示例:
drivers/eeprom/at24.c
1.I2C驱动程序的层次
I2C Core就是I2c核心层,它的作用:
- 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
- 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)
2. I2C总线 - 设备 - 驱动模型
平台总线说明
之前用过Linux的平台设备总线(platform_bus_type),对于上面的图左边,平台设备总线应该是platform_device。保存了的一些设备相关的信息。右边会是platform_driver,保存了一些代码,根据左边的信息操作。左边的platform_device是经常容易变化的部分, 而右边则是稳定的部分。
代码的稳定性
如果修改了硬件结构,那么只需要修改左边的platform_device就够了。新版本中platform_device会来自设备树,这样的话,比如修改了引脚,只需要修改设备树就好了。内核启动时根据设备树的信息生成device。整个修改过程不需要改内核,不需要改驱动。
设备匹配
platform_driver通过probe函数、device_table表明支持哪些设备。当platform_device注册时,回去platform_driver一一比较,如果匹配成功就会去调用probe函数。匹配函数就是platform_bus提供的match函数。
返过来,如果已经有了platform_device,而新增加了platform_driver。同样会调用match函数,去一个一个比较platform_device。匹配的话就会调用probe函数。
I2C总线
对于平台总线,它是一条抽象的虚拟总线,而I2C总线则时一条真实的总线。在i2c_bus_type的两边也有两个链表,一个链表会挂有一个或多个i2c_client(I2C设备),另一个链表会挂有一个或多个i2c_driver (I2C驱动)。i2c_bus_type里面有match函数,用来检测左右两边是否匹配。
左边的i2c_client用来描述硬件信息,比如i2c的地址、名字、对应的adapter(i2c总线)。右边有各种操作函数。如果左右两边匹配的话,那么就会调用probe_new函数来注册。
//i2c-core.c
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
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 (!client)
return 0;
/* Attempt an OF style match 进行of风格(设备树)的类型比较 */
if (of_driver_match_device(dev, drv)) //这里把drv->of_match_table做匹配
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv)) //匹配acpi_device_id
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one 对于i2c_client无论时设备树还是代码里生成的,都会有一个name用来比较 */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL; //匹配id_table和i2c_client的name
return 0;
}
//drivers/misc/eeprom/at24.c
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.of_match_table = at24_of_match,
.acpi_match_table = ACPI_PTR(at24_acpi_ids),
},
.probe = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};
static const struct of_device_id at24_of_match[] = {
{ .compatible = "atmel,24c00", .data = &at24_data_24c00 },
{ .compatible = "atmel,24c01", .data = &at24_data_24c01 },
{ .compatible = "atmel,24cs01", .data = &at24_data_24cs01 },
//... 此处省略了一部分
{ .compatible = "atmel,24c256", .data = &at24_data_24c256 },
{ .compatible = "atmel,24c512", .data = &at24_data_24c512 },
{ .compatible = "atmel,24c1024", .data = &at24_data_24c1024 },
{ /* END OF LIST */ },
};
static const struct acpi_device_id at24_acpi_ids[] = {
{ "INT3499", AT24_DEVICE_MAGIC(8192 / 8, 0) },
{ }
};
static const struct i2c_device_id at24_ids[] = {
/* needs 8 addresses as A0-A2 are ignored */
{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
/* old variants can't be handled with this generic entry! */
{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
{ "24cs01", AT24_DEVICE_MAGIC(16,
AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
//... 此处省略了一部分
{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
{ "at24", 0 },
{ /* END OF LIST */ }
};
设备树中如何配置i2c
//Example:
i2c1: i2c@400a0000 { /* 第一条i2c总线 */
/* ... master properties skipped ... */
clock-frequency = <100000>; //
flash@50 { //第一个设备,会生成一个i2c_client
compatible = "atmel,24c256"; //这个compatible会和i2c_driver里的of_match_table来比较
reg = <0x50>;
};
pca9532: gpio@60 { //第二个设备,会生成一个i2c_client
compatible = "nxp,pca9532";
gpio-controller;
#gpio-cells = <2>;
reg = <0x60>;
};
};
2.1 i2c_driver
i2c_driver在结构体中表明能支持哪些设备
- 使用of_match_table来判断:设备树中i2c设备节点会生成i2c_client结构体,其中有compatible属性和of_match_table的某项兼容,就匹配成功
- 使用id_table来判断:i2c_client的name属性会和id_table[i].name匹配比较,如果相同则匹配成功
2.2 i2c_client
i2c_client表示一个I2C设备,创建i2c_client的方法有4种
- 方法1
- 通过i2c bus number来创建
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
- 通过设备树来创建
i2c1: i2c@400a0000 {
/* ... master properties skipped ... */
clock-frequency = <100000>;
flash@50 {
compatible = "atmel,24c256";
reg = <0x50>;
};
pca9532: gpio@60 {
compatible = "nxp,pca9532";
gpio-controller;
#gpio-cells = <2>;
reg = <0x60>;
};
};
- 方法2
有时候无法知道设备在哪个i2c bus下,不知道对应的i2c bus number。但是可以通过其他方法知道对应的i2c_adapter结构体。
可以使用下面两个函数来创建i2c_client:
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_adapter创建i2c_client
(...)
}
//-------------------------------------------------------------------------------------------
static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
static int 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));
strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
normal_i2c, NULL); //使用i2c_new_probed_device来创建i2c_client
i2c_put_adapter(i2c_adap);
(...)
}
两个函数的区别:
i2c_new_device会创建i2c_client,即使该设备并不存在。
i2c_new_probed_device 它成功的话,会创建i2c_client,并且表示这个设备肯定存在。I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样。可以罗列出可能的地址,i2c_new_probed_device使用这些地址判断设备是否存在。
-
方法3(不推荐)
由i2c_driver.detect函数来判断是否有对应的i2c设备并生成i2c_client -
方法4
通过用户空间(user-space)生成调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。
// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
// 删除一个i2c_client
# echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device