linux内核的总线-设备-驱动模型,当总线上的设备与总线上的驱动匹配时,就会调用驱动的probe函数,完成一系列的操作
I2C也是内核的一种总线
一、I2C设备的4种构建方法
1、静态注册设备
(1)定义一个 i2c_board_info 结构体,有名字,和设备地址
static struct i2c_board_info my_i2c_dev_info = {
I2C_BOARD_INFO("my_i2c_dev", 0x2d),
};
(2)注册设备
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n)
* busnum:哪一条总线,也就是选择哪一个i2c控制器
* info:i2c设备信息
* n:info中有几个设备
i2c_register_board_info做了什么?
i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
devinfo->busnum = busnum; /* 选择i2c总线 */
devinfo->board_info = *info; /* 绑定设备信息 */
list_add_tail(&devinfo->list, &__i2c_board_list); /* 将设备信息添加进链表中 */
}
在哪里使用了 __i2c_board_list 这个链表?
i2c_scan_static_board_info(struct i2c_adapter *adapter)
list_for_each_entry(devinfo, &__i2c_board_list, list) /* 遍历i2c设备链表 */
i2c_new_device(adapter, &devinfo->board_info); /* 创建一个i2c_client设备 */
struct i2c_client *client;
在哪里调用了 i2c_scan_static_board_info 这个函数?
int i2c_register_adapter(struct i2c_adapter *adap)
{
i2c_scan_static_board_info(adap);
}
adapter是i2c设备器,也就是i2c主机控制器,在启动内核时已经被注册了
调用关系如下
内核启动
int i2c_register_adapter(struct i2c_adapter *adap)
i2c_scan_static_board_info(adap)
list_for_each_entry(devinfo, &__i2c_board_list, list)
所以使用这个方法添加i2c设备,每次都要重新编译内核,无法动态注册i2c设备,不适合我们动态加载insmod
2、直接i2c_new_device,i2c_new_probe_device
i2c_new_device:
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
* adap:指定i2c设备器,以后访问设备的时候,使用过哪一个设备器(i2c主机控制器)去访问
* info:指定i2c设备信息
i2c_new_device注册i2c设备的代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>
static struct i2c_board_info my_i2c_dev_info = {
I2C_BOARD_INFO("my_i2c_dev", 0x50), /* 这个名字和地址很重要 */
};
static struct i2c_client *my_i2c_client;
static int i2c_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0); /* 得到i2c设备器,i2c设备在哪条总线上 */
my_i2c_client = i2c_new_device(i2c_adap, &my_i2c_dev_info);
i2c_put_adapter(i2c_adap);
return 0;
}
static void i2c_dev_exit(void)
{
i2c_unregister_device(my_i2c_client);
}
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
MODULE_LICENSE("GPL");
i2c_new_device与i2c_new_probed_device的区别
i2c_new_device:默认设备已经存在
i2c_new_probed_device:对于能识别的设备才会去创建设备
i2c_new_probed_device:
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:设配器,设备在哪一条总线上
* info:设备的名字信息
* addr_list:设备的地址列表
* probe:会调用这个函数去判断设备是否真正存在,如果不提供此函数的话,有默认的函数
i2c_new_probed_device
{
/* 检测每一个地址列表中的每一个地址 */
for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
/* Test address responsiveness */
if (probe(adap, addr_list[i])) /* 测试每个地址是否有回应 */
break;
}
/* 只有地址存在才来创建 */
return i2c_new_device(adap, info); /* 注册i2c client设备 */
}
i2c_new_probe_device注册i2c设备的代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
static struct i2c_client *my_i2c_client;
static const unsigned short addr_list[] = { 0x55, 0x50, I2C_CLIENT_END };
static int my_i2c_dev_init(void)
{
struct i2c_adapter *i2c_adap;
struct i2c_board_info my_i2c_dev_info;
memset(&my_i2c_dev_info, 0, sizeof(struct i2c_board_info));
strlcpy(my_i2c_dev_info.type, "my_i2c_dev", I2C_NAME_SIZE);
i2c_adap = i2c_get_adapter(0);
my_i2c_client = i2c_new_probed_device(i2c_adap, &my_i2c_dev_info, addr_list, NULL);
i2c_put_adapter(i2c_adap);
if (my_i2c_client )
return 0;
else
return -ENODEV;
}
static void my_i2c_dev_exit(void)
{
i2c_unregister_device(my_i2c_client );
}
module_init(my_i2c_dev_init);
module_exit(my_i2c_dev_exit);
MODULE_LICENSE("GPL");
3、从用户空间创建设备
创建设备
echo my_i2c_dev 0x55 > sys/devices/platform/s3c2440-i2c.0/i2c-0/new_device
会导致i2c_new_device被调用
删除设备
echo 0x55 > sys/devices/platform/s3c2440-i2c.0/i2c-0/delete_device
导致i2c_unregister_dev被调用
4、上面三种方法,都需要指定设配器(i2c主机控制器),现在如果不知道设备在哪个设配器上要怎么办呢?因此就有了第四种方法。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
static int __devinit my_i2c_drv_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int __devexit my_i2c_drv_remove(struct i2c_client *client)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static const struct i2c_device_id my_dev_id_table[] = {
{ "my_i2c_dev", 0 },
{}
};
static int my_i2c_drv_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
/* 能运行到这里, 表示该addr的设备是存在的
* 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
* 还需要进一步读写I2C设备来分辨是哪款芯片
* detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
*/
printk("my_i2c_drv_detect: addr = 0x%x\n", client->addr);
/* 进一步判断是哪一款 */
strlcpy(info->type, "my_i2c_dev", I2C_NAME_SIZE);
return 0;
}
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
/* 1. 分配/设置i2c_driver */
static struct i2c_driver my_i2c_driver = {
.class = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
.driver = {
.name = "my_i2c_dev",
.owner = THIS_MODULE,
},
.probe = my_i2c_drv_probe,
.remove = __devexit_p(my_i2c_drv_remove),
.id_table = my_dev_id_table,
.detect = my_i2c_drv_detect, /* 用这个函数来检测设备确实存在 */
.address_list = addr_list, /* 这些设备的地址 */
};
static int my_i2c_drv_init(void)
{
/* 2. 注册i2c_driver */
i2c_add_driver(&my_i2c_driver);
return 0;
}
static void my_i2c_drv_exit(void)
{
i2c_del_driver(&my_i2cc_driver);
}
module_init(my_i2c_drv_init);
module_exit(my_i2c_drv_exit);
MODULE_LICENSE("GPL");
上面的代码回去.class指定的这一类i2c设配器中,查找.address_list这些地址,如果设备存在,就会调用.detect函数,确认是哪一个i2c设备,然后调用i2c_new_device
下面来分析这一个过程
i2c_add_driver
i2c_register_driver
driver_register(&driver->driver); /* 会去dev链表中找到匹配的设备,调用probe函数 */
/* 对于每个设配器调用__process_new_driver */
i2c_for_each_dev(driver, __process_new_driver);
__process_new_driver
i2c_do_add_adapter
i2c_detect
address_list = driver->address_list; /* 得到要检测的所有地址 */
i2c_detect_address
i2c_check_addr_validity /* 检测地址是否存在 */
driver->detect(temp_client, &info); /* 如果地址存在,调用detect */
i2c_new_device /* 创建设备 */
二、i2c总线驱动
1、设备一个i2c_driver结构体
static const struct i2c_device_id my_i2c_dev_id[] = {
{ "my_i2c_dev", 0}, /* 设备名字 */
{ }
};
static struct i2c_driver my_i2c_drv = {
.driver = {
.name = "no", /* 这个名字不重要 */
.owner = THIS_MODULE,
},
.probe = my_i2c_drv_probe, /* 当匹配到i2c设备时调用 */
.remove = my_i2c_drv_remove, /* 当卸载i2c设备或驱动时调用 */
.id_table = my_i2c_dev_id, /* 这个结构体中的名字很重要 */
};
id_tabel中的名字很重要,i2c总线就是靠这个名字来匹配i2c驱动和设备的,一旦匹配上就会调用probe函数
2、注册
static int __init my_i2c_drv_init(void)
{
i2c_add_driver(&my_i2c_drv_drv);
return 0;
}
module_init(my_i2c_drv_init);
3、实现probe函数和remove函数
static struct i2c_client *my_i2c_client;
static int my_i2c_drv_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 记录下i2c设备client */
my_i2c_client = client;
/* 在这里注册字符设备,创建设备节点 */
/* 这里为了简单,只是打印一句话而已 */
printk("my_i2c_drv_probe\n");
return 0;
}
static int my_i2c_drv_remove(struct i2c_client *client)
{
/* 在这里卸载字符设备 */
/* 这里为了简单,只打印一句话 */
printk("my_i2c_drv_remove\n")
return 0;
}
完整的代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/earlysuspend.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/ioc4.h>
#include <linux/io.h>
#include <linux/proc_fs.h>
#include <mach/gpio.h>
#include <mach/hardware.h>
#include <plat/gpio-cfg.h>
#include <plat/irqs.h>
static struct i2c_client *my_i2c_client;
static const struct i2c_device_id my_i2c_dev_id[] = {
{ "my_i2c_dev", 0 },
{ }
};
static int my_i2c_drv_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 记录下i2c设备client */
my_i2c_client = client;
/* 在这里注册字符设备,创建设备节点 */
/* 这里为了简单,只是打印一句话而已 */
printk("my_i2c_ddrv_probe\n");
return 0;
}
static int my_i2c_drv_remove(struct i2c_client *client)
{
/* 在这里卸载字符设备 */
/* 这里为了简单,只打印一句话 */
printk("my_i2c_dev_remove\n")
return 0;
}
static struct i2c_driver my_i2c_drv = {
.driver = {
.name = "jt_ts",
.owner = THIS_MODULE,
},
.probe = my_i2c_drv_probe,
.remove = my_i2c_drv_remove,
.id_table = my_i2c_dev_id,
};
static int __init my_i2c_drv_init(void)
{
i2c_add_driver(&my_i2c_drv);
return 0;
}
static void __exit my_i2c_drv_exit(void)
{
i2c_del_driver(&my_i2c_drv);
return;
}
module_init(my_i2c_drv_init);
module_exit(my_i2c_drv_exit);
MODULE_LICENSE("GPL");
三、怎么使用i2c总线传输数据?
/* 定义 i2c_msg 结构体 */
struct i2c_msg msg[1];
char val[4]
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[0].flags = 0; /* 0表示写,1表示读 */
msg[0].buf = val; /* 写:要发送的数据地址,读:读取到的数据存放的地址 */
msg[0].len = 2; /* 想要传输的字节数 */
/* 传输数据 */
i2c_transfer(my_i2c_client->adapter, msg, 1); /* msg:消息,1:传输msg个数 */
举个栗子,完成以下时序的读写
下面是实现代码
/* 定义 i2c_msg 结构体 */
struct i2c_msg msg[2];
char val[10]
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[0].flags = 0; /* 0表示写,1表示读 */
msg[0].buf = 0x80; /* 写:要发送的数据地址,读:读取到的数据存放的地址 */
msg[0].len = 1; /* 想要传输的字节数 */
/* 填充msg */
msg[1].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[1].flags = 1; /* 1表示读 */
msg[1].buf = val; /* 读到的数据存在这里 */
msg[1].len = 4; /* 想要读取的字节数 */
/* 传输数据 */
i2c_transfer(my_i2c_client->adapter, msg, 2); /* 有两个msg */
读取到的数据会存储val[]数组中