I2C控制器驱动好后,由i2c_adapter对象来描述, 我们只需通过函数调用控制器来实现数据传输就可以了。
但i2c控制器上接的i2c设备,需要我们来声明才可以.
在linux内核里,每个i2c_client对象表示一个i2c硬件设备
"include/linux/i2c.h"
struct i2c_client {
unsigned short flags; //没用. 如设备地址为10位,则设I2C_CLIENT_TEN
unsigned short addr; //i2c设备地址
char name[I2C_NAME_SIZE]; //设备的名字
struct i2c_adapter *adapter; //此指针指向此设备在具体i2c控制器的i2c_adapter对象
struct i2c_driver *driver; //指向匹配上的i2c设备驱动
struct device dev; //基于device结构体扩展面来, 驱动模型. 也可以用dev.platform_data来给设备驱动提供硬件资源
int irq; //中断号
struct list_head detected;
};
但比较坑的事情是通常情况下不会直接用i2c_client结构体声明对象来描述设备,因描述设备时它的adapter成员根本没法指定具体的i2c_adapter对象的地址.所以内核里的做法是,先用struct i2c_board_info来描述设备, 等控制器驱动好后,也就是i2c_adapter对象都搞好后,再根据i2c_board_info对象的内容生成i2c_client对象,并指定i2c_client对象的adapter指向对应的i2c_adapter对象地址.
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //就是i2c_client的name
unsigned short flags; // i2c_client的flags
unsigned short addr; // i2c_client的addr
void *platform_data; // i2c_client的dev.platform_data
struct dev_archdata *archdata;
struct device_node *of_node; //用于设备树
int irq; // i2c_client的irq
};
/ i2c设备的描述方法
可参考内核源码目录下Documentation/i2c/instantiating-devices文档
1) 通过指定控制器号来声明i2c设备(最常用的方法)
先声明一个i2c_board_info对象表示一个i2c硬件设备.
struct i2c_board_info info = {
I2C_BOARD_INFO("24c02", 0x50), //名为24c02, 设备地址为0x50
.platform_data = NULL,
};
如此i2c设备是接着第9个控制器(即i2c_adapter的nr成员值为9):
i2c_register_board_info(9, &info, 1); //注册i2c_board_info对象
///根据i2c_board_info对象生成i2c_client对象的过程
int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
...
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo; //注册i2c_board_info对象后,再用i2c_devinfo对象来记录i2c_board_info对象的地址
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
...
devinfo->busnum = busnum; //指定i2c_board_info对象是用于哪个控制器的
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list); //再把i2c_devinfo对象加入链表__i2c_board_list里。 也就是可通过此链表获取出所有的i2c_board_info对象的内容
...
}
}
//然后查看在什么地址遍历链表__i2c_board_list, 可发现在"drivers/i2c/i2c-core.c"
783 static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
784 {
785 struct i2c_devinfo *devinfo;
...
//遍历链表,并根据指定的控制器号调用i2c_new_device创建i2c_client对象
788 list_for_each_entry(devinfo, &__i2c_board_list, list) {
789 if (devinfo->busnum == adapter->nr
790 && !i2c_new_device(adapter,
791 &devinfo->board_info))
...
797 }
506 struct i2c_client *
507 i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
508 {
509 struct i2c_client *client;
510 int status;
511
512 client = kzalloc(sizeof *client, GFP_KERNEL);
...
516 client->adapter = adap;//指定对应的i2c_adapter对象的地址
517
518 client->dev.platform_data = info->platform_data;
519
520 if (info->archdata)
521 client->dev.archdata = *info->archdata;
522
523 client->flags = info->flags;
524 client->addr = info->addr;
525 client->irq = info->irq;
526
527 strlcpy(client->name, info->type, sizeof(client->name));
...
542 client->dev.parent = &client->adapter->dev;
543 client->dev.bus = &i2c_bus_type; // i2c_client对象是挂载到i2c总线下的, 所以每个i2c设备描述好后可在"/sys/bus/i2c/devices"目录下查看到
544 client->dev.type = &i2c_client_type;
545 client->dev.of_node = info->of_node;
...
551 status = device_register(&client->dev); //注册设备,驱动模型.
552 if (status)
//i2c_scan_static_board_info函数是用于根据i2c_board_info生成i2c_client对象,但这个函数是在i2c控制器注册时被调用的. 所以必须在i2c控制器注册前描述好i2c_board_info并i2c_register_board_info注册好i2c_board_info对象才可以
822 static int i2c_register_adapter(struct i2c_adapter *adap)
823 {
...
870 if (adap->nr < __i2c_first_dynamic_bus_num)
871 i2c_scan_static_board_info(adap);
///
"arch/arm/mach-sunxi/sun8i.c"
524 MACHINE_START(SUNXI, "sun8i")
525 .atag_offset = 0x100,
526 .init_machine = sunxi_dev_init, //这个是板上设备初始化函数, 可以sunxi_dev_init函数里实现i2c_board_info对象的准备
527 .init_early = sunxi_init_early, //是更前面的初始化函数
//在第9个控制器里增加三个设备
451 struct i2c_board_info infos[] = {
452 {I2C_BOARD_INFO("haha", 0x33)},
453 {I2C_BOARD_INFO("nono", 0x44)},
454 {I2C_BOARD_INFO("what", 0x55)},
455 };
456
457 static void __init sunxi_dev_init(void)
458 {
459 i2c_register_board_info(9, infos, ARRAY_SIZE(infos));
//重编内核镜像好,控制器驱动好后,在"/sys/bus/i2c/device"目录下应用9-0033, 9-0044等子目录的出现
///
2) 直接使用i2c_new_device函数来创建出i2c_client对象
struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
//调用此函数需要控制器对应的i2c_adapter对象的地址, 还需要一个i2c_board_info对象
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(2); //可调用此函数获取第2个控制器的i2c_adapter对象的地址
此方法可以写成模块,动态加载
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
struct i2c_board_info info = {
.type = "myi2c_dev",
.addr = 0x67,
};
struct i2c_client *cli;
static int __init test_init(void)
{
struct i2c_adapter *adap;
adap = i2c_get_adapter(9);
cli = i2c_new_device(adap, &info);
return 0;
}
static void __exit test_exit(void)
{
i2c_unregister_device(cli); //反注册i2c设备
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
///
3) 使用i2c_new_probed_device函数来创建存在的i2c设备对象
此方法会让控制器发设备地址,检查应答信号确认设备是否存在,如存在,则创建i2c_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))
//adap为要检查设备的控制器的i2c_adapter对象地址, info为创建设备对象时准备的信息,
// addr_list是一个要检查的设备地址数组,以I2C_CLIENT_END结尾. 控制器会按数组里的顺序发出设备地址,直到有应答信号时,创建i2c_client对象,并返回对象的地址.
// probe函数指针为NULL.当为NULL时会使用默认的检查方法
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
struct i2c_board_info info = {
.type = "myi2c_dev",
.addr = 0x67,
};
struct i2c_client *cli = NULL;
unsigned short addrs[] = {0x11, 0x22, 0x50, 0x66, 0x77, I2C_CLIENT_END};
static int __init test_init(void)
{
struct i2c_adapter *adap;
adap = i2c_get_adapter(9);
cli = i2c_new_probed_device(adap, &info, addrs, NULL);
if (NULL == cli)
return -ENODEV;
printk("cli->addr = %x\n", cli->addr);
return 0;
}
static void __exit test_exit(void)
{
i2c_unregister_device(cli); //反注册i2c设备
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
//
利用此方法还可以检查出控制器上接的设备,输出存在的设备的地址
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
unsigned short addrs[129];
static int __init test_init(void)
{
struct i2c_adapter *adapter;
struct i2c_board_info myinfo;
struct i2c_client *cli = NULL;
int i;
unsigned short *addr;
//准备好从0~128的设备地址
for (i = 0; i < ARRAY_SIZE(addrs); i++)
addrs[i] = i;
addrs[i] = I2C_CLIENT_END;
adapter = i2c_get_adapter(9);
memset(&myinfo, 0, sizeof(myinfo));
strcpy(myinfo.type , "mydev"); //准备i2c_board_info信息
addr = addrs;
while (1)
{
cli = i2c_new_probed_device(adapter, &myinfo, addr, NULL);
if (cli)
{
printk("addr: %x found\n", cli->addr);
addr += cli->addr;
i2c_unregister_device(cli);
continue;
}
break;
}
return 0;
}
static void __exit test_exit(void)
{
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
///
4) 使用”/sys/bus/i2c/devices/i2c-0/new_device”的属性文件接口
echo "aaa 0x50" > /sys/bus/i2c/devices/i2c-0/new_device
//这种方法只适用于简单的设备。这类型设备不会有中断号,也不需要提供额外的硬件资源.