天天看點

【linux iic子系統】i2c裝置的添加方法(四)I2C裝置的4種添加方法

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函數

【linux iic子系統】i2c裝置的添加方法(四)I2C裝置的4種添加方法

寫delete_device時會調用i2c_sysfs_delete_device函數,内部再調用i2c_unregister_device函數

【linux iic子系統】i2c裝置的添加方法(四)I2C裝置的4種添加方法

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");
           

繼續閱讀