I2C裝置的4種添加方法
分為靜态注冊、動态注冊、使用者空間注冊及i2c驅動掃描注冊
1.靜态注冊
靜态注冊就是在架構闆級檔案或初始化檔案中添加i2c裝置資訊,并注冊到特定位置(__i2c_board_list連結清單)上就可以了,如arm架構下board-xxx-yyy.c檔案,x86架構下xxx-yyy-init-zzz.c檔案。當系統靜态注冊i2c控制器(adapter)時,将會去查找這個連結清單,并執行個體化i2c裝置添加到i2c總線上。注意:一定要趕在i2c控制器注冊前将i2c裝置資訊添加到連結清單上。
1)定義一個 i2c_board_info 結構體
必須要有名字和裝置位址,其他如中斷号、私有資料非必須。
static struct i2c_board_info my_tmp75_info = {
I2C_BOARD_INFO("my_tmp75", 0x48),
};
@my_tmp75是裝置名字,用于比對i2c驅動。
@0x48是i2c裝置的基位址。
如果有多個裝置,可以定義成結構數組,一次添加多個裝置資訊。
2)注冊裝置
使用i2c_register_board_info函數将i2c裝置資訊添加到特定連結清單,函數原型如下
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n)
{
devinfo->busnum = busnum; /* 組裝i2c總線 */
devinfo->board_info = *info; /* 綁定裝置資訊 */
list_add_tail(&devinfo->list, &__i2c_board_list); /* 将裝置資訊添加進連結清單中 */
}
@busnum:哪一條總線,也就是選擇哪一個i2c控制器(adapter)
@info:i2c裝置資訊,就是上面的結構體
@n:info中有幾個裝置
将在i2c_register_adapter函數中使用到
static int i2c_register_adapter(struct i2c_adapter *adap)
{
…
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
…
}
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);
}
而調用i2c_register_adapter函數的有兩個地方,分别是i2c_add_adapter函數和i2c_add_numbered_adapter函數,但i2c_add_adapter函數中是動态配置設定的總線号,adap->nr一定比__i2c_first_dynamic_bus_num變量大,是以不會進入到i2c_scan_static_board_info函數,是以隻有i2c_add_numbered_adapter最終使用到,而這個函數是i2c控制器靜态注冊時調用的,是以靜态注冊i2c裝置必須趕在i2c控制器注冊前添加。
2.動态注冊
動态注冊i2c裝置可以使用兩個函數,分别為i2c_new_device函數與i2c_new_probed_device函數,它們兩差別是:
- i2c_new_device:不管i2c裝置是否真的存在,都執行個體化i2c_client。
- i2c_new_probed_device:調用probe函數去探測i2c位址是否有回應,存在則執行個體化i2c_clien。如果自己不提供probe函數的話,使用預設的i2c_default_probe函數。
1)使用i2c_new_device注冊裝置
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
static struct i2c_board_info my_tmp75_info = {
I2C_BOARD_INFO("my_tmp75", 0x48),//這個名字很重要,用于比對I2C驅動
};
static struct i2c_client *my_tmp75_client;
static int my_tmp75_init(void)
{
struct i2c_adapter *i2c_adapt;
int ret = 0;
i2c_adapt = i2c_get_adapter(6);
if (i2c_adapt == NULL)
{
printk("get adapter fail!\n");
ret = -ENODEV;
}
my_tmp75_client = i2c_new_device(i2c_adapt, &my_tmp75_info);
if (my_tmp75_client == NULL)
{
printk("i2c new fail!\n");
ret = -ENODEV;
}
i2c_put_adapter(i2c_adapt);
return ret;
}
static void my_tmp75_exit(void)
{
i2c_unregister_device(my_tmp75_client);
}
module_init(my_tmp75_init);
module_exit(my_tmp75_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This my i2c device for tmp75");
2)使用i2c_new_probed_device注冊裝置
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
static struct i2c_client *my_tmp75_client;
static const unsigned short addr_list[] = { 0x46, 0x48, I2C_CLIENT_END };//必須以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_tmp75", I2C_NAME_SIZE);
i2c_adap = i2c_get_adapter(0);
my_tmp75_client = i2c_new_probed_device(i2c_adap, &my_i2c_dev_info, addr_list, NULL);//隻會比對到0x48位址
i2c_put_adapter(i2c_adap);
if (my_tmp75_client)
return 0;
else
return -ENODEV;
}
static void my_i2c_dev_exit(void)
{
i2c_unregister_device(my_tmp75_client);
}
module_init(my_i2c_dev_init);
module_exit(my_i2c_dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This my i2c device for tmp75");
3.使用者空間注冊
1)建立i2c裝置
echo i2c_test 0x48 > /sys/bus/i2c/devices/i2c-6/new_device
使用這種方法建立的i2c裝置會挂在i2c_adapter的連結清單上,為了友善使用者空間删除i2c裝置!
2)删除裝置
echo 0x48 > /sys/bus/i2c/devices/i2c-6/delete_device
删除裝置隻能删除在使用者空間建立的i2c裝置!
在i2c控制器注冊時,會在/sys/bus/i2c/devices/目錄下建立i2c-x裝置檔案,并且設定它的屬性,而new_device和delete_device均是它的屬性,寫new_device時會調用i2c_sysfs_new_device函數,内部再調用i2c_new_device函數
寫delete_device時會調用i2c_sysfs_delete_device函數,内部再調用i2c_unregister_device函數
4.i2c驅動掃描注冊
在《i2c裝置與驅動比對過程》中說到,i2c驅動注冊時會使用兩種比對方法去尋找i2c裝置,代碼如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
driver->driver.bus = &i2c_bus_type;//添加總線
res = driver_register(&driver->driver);//驅動注冊核心函數,注意隻傳入了driver成員
/* 周遊所有挂在總線上的iic擴充卡,用它們去探測driver中指定的iic裝置位址清單 */
i2c_for_each_dev(driver, __process_new_driver);
}
driver_register函數已将講解過,現在來分析i2c_for_each_dev函數,
int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{
int res;
mutex_lock(&core_lock);
res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
mutex_unlock(&core_lock);
return res;
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus || !bus->p)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));
while (!error && (dev = next_device(&i)))
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}
最終調用__process_new_driver函數,使用i2c總線上所有i2c擴充卡去探測i2c驅動中的裝置位址數組!
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return 0;
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
入口先判斷傳入的裝置是不是i2c擴充卡(i2c控制器),因為在《i2c裝置與驅動比對過程》中說到,i2c擴充卡和i2c裝置一樣,都會挂在i2c總線上,它們是通過dev->type項區分的!
static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{
/* Detect supported devices on that bus, and instantiate them */
i2c_detect(adap, driver);
…
}
最終調用i2c_detect函數,函數簡化後如下:
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
int adap_id = i2c_adapter_id(adapter);
address_list = driver->address_list;
if (!driver->detect || !address_list)
return 0;
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
itemp_client->adapter = adapter;
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1)
{
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver);
if (unlikely(err))
break;
}
}
如果i2c驅動的裝置位址數組為空或detect函數不存在,則結束傳回,否則臨時執行個體化一個temp_client裝置指派adapter為目前i2c控制器,然後在使用該i2c控制器去探測i2c驅動裝置位址數組中的所有位址,關鍵函數是 i2c_detect_address如下(簡化後):
static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
struct i2c_board_info info;
struct i2c_adapter *adapter = temp_client->adapter;
int addr = temp_client->addr;
int err;
err = i2c_check_7bit_addr_validity_strict(addr);//檢查位址是否有效,即7位有效位址
if (err) {
return err;
}
if (i2c_check_addr_busy(adapter, addr))//跳過已經使用的i2c裝置
return 0;
if (!i2c_default_probe(adapter, addr))//檢查這個位址是否有回應
return 0;
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, &info);
if (err) {
return err == -ENODEV ? 0 : err;
}
if (info.type[0] == '\0')
{
}
else
{
struct i2c_client *client;
client = i2c_new_device(adapter, &info);
if (client)
list_add_tail(&client->detected, &driver->clients);
}
}
首先檢查有效性、是否有裝置回應、是否被使用,之後初始化了i2c_board_info結構,注意隻初始化了位址(執行個體化裝置必須還要名字),然後調用了i2c驅動中的detect函數,如果成功則調用 i2c_new_device函數真正執行個體化i2c裝置,并且将i2c裝置挂在i2c驅動的連結清單上!注意:隻有這種方式添加的i2c裝置才會挂在驅動的連結清單上!
仔細思考上面就能發現,i2c驅動中的detect函數必須要填寫i2c_board_info結構體中name,i2c_new_device才能執行個體化i2c裝置。
是以,使用i2c驅動掃描注冊裝置時,需要按如下格式編寫驅動!
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
static int __devinit my_i2c_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return 0;
}
static int __devexit my_i2c_drv_remove(struct i2c_client *client)
{
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[] = { 0x46, 0x48, I2C_CLIENT_END };//必須使用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");