Contents ...
udn網路城邦
Linux之I2C設備
2015/07/02 23:37
瀏覽2,983
迴響0
推薦0
引用0
I2C由三大部分組成:
1、I2C核心:I2C核心提供了總線驅動和設備驅動的註冊、註銷的方法,I2C通信方法,與具體適配器無關的代碼以及檢測設備地址的代碼等。
2、I2C總線驅動:對I2C硬件體系結構中適配器的實現,控制I2C總線驅動的代碼,控制I2C適配器以主控方式產生開始位,停止位,讀寫以及設備讀寫方式,產生ack等。
3、I2C客戶驅動程序:是對I2C硬件體系結構設備端得實現。
我們在分析其子系統之前,還是來看看其數據結構
i2c_adapter結構體表示一個物理的i2c總線控制器
struct i2c_adapter {
    struct module *owner;
    unsigned int class;         /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* the algorithm to access the bus */
    void *algo_data;
    /* data fields that are valid for all devices    */
    struct rt_mutex bus_lock;
    int timeout;            /* in jiffies */
    int retries;
    struct device dev;        /* the adapter device */
    int nr;
    char name[48];       //適配器名字
    struct completion dev_released;   //用於同步
    struct mutex userspace_clients_lock;
    struct list_head userspace_clients;
};
上面包含i2c_algorithm,其對應一套通信方法
struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
             int num);//I2C傳輸函數指針
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
             unsigned short flags, char read_write,
             u8 command, int size, union i2c_smbus_data *data);//SMBUS傳輸函數指針
    u32 (*functionality) (struct i2c_adapter *);//返回適配器支持的功能
};
一個i2c適配器需要i2c_algorithm中提供的通信函數來控制適配器產生特定的訪問函數。其中提供了關鍵的函數master_xfer()用於產生i2c訪問周期需要的信號,以i2c_msg為單位
struct i2c_msg {
    __u16 addr;    /* slave address            */
    __u16 flags;
    __u16 len;        /* msg length                */
    __u8 *buf;        /* pointer to msg data            */
};
i2c_driver代表i2c從設備驅動
struct i2c_driver {
    unsigned int class;
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;//適配器函數指針
    int (*detach_adapter)(struct i2c_adapter *) __deprecated;
    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);
    /* driver model interfaces that don't relate to enumeration */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
    void (*alert)(struct i2c_client *, unsigned int data);

    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    struct device_driver driver;
    const struct i2c_device_id *id_table;
    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};
i2c_client代表i2c從設備
struct i2c_client {
    unsigned short flags;        /* div., see below        */
    unsigned short addr;        /* chip address - NOTE: 7bit    */
                    /* addresses are stored in the    */
                    /* _LOWER_ 7 bits        */
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;    /* the adapter we sit on    */
    struct i2c_driver *driver;    /* and our access routines    */
    struct device dev;        /* the device structure        */
    int irq;            /* irq issued by device        */
    struct list_head detected;
};
     i2c_driver與i2c_client是一對多的關系,一個i2c_driver上可以支持多個同等類型的i2c_client。i2c_adapter與i2c_client的關系與i2c硬件體系中適配器和從設備的關系一致,i2c_client依附在i2c_adapter。
下面來看看內核提供任何去註冊一個i2c的設備驅動
1.直接使用i2c_register_board_unfo完成設備註冊,這種方法適合於i2c總線上預先已知設備,因此可以預先聲明i2c設備在哪條總線上,在通過數組結構i2c_board_info,內核實例為arch\arm\mach-omap2\board-h4.c。
static struct i2c_board_info __initdata h4_i2c_board_info[] = {
    {
        I2C_BOARD_INFO("isp1301_omap", 0x2d),
        .irq        = OMAP_GPIO_IRQ(125),
    },
    {    /* EEPROM on mainboard */
        I2C_BOARD_INFO("24c01", 0x52),
        .platform_data    = &m24c01,
    },
    {    /* EEPROM on cpu card */
        I2C_BOARD_INFO("24c01", 0x57),
        .platform_data    = &m24c01,
    },
};
static void __init omap_h4_init(void)
{
    (...)
    i2c_register_board_info(1, h4_i2c_board_info,
            ARRAY_SIZE(h4_i2c_board_info));
    (...)
}
那麼內核怎麼完成匹配呢?首先來看看i2c_register_board_info()
int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;
    down_write(&__i2c_board_lock);
    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)
        __i2c_first_dynamic_bus_num = busnum + 1;
    for (status = 0; len; len--, info++) {
        struct i2c_devinfo    *devinfo;
        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!\n");
            status = -ENOMEM;
            break;
        }
        devinfo->busnum = busnum;
        devinfo->board_info = *info;
        list_add_tail(&devinfo->list, &__i2c_board_list);
    }
    up_write(&__i2c_board_lock);
    return status;
}
這上面只做了一個非常重要的,將i2c_board_info放入到__i2c_board_list鏈表,而這個info中存放的是i2c通信非常重要的,設備名字和設備地址。那麼鏈表何時使用呢?這個在i2c_scan_static_board_info的時候會調用
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_board_list鏈表上面的i2c設備的信息,也就是我們在啟動的時候指出的i2c設備的信息,如果指定設備位於adapter所在的i2c總線上,那麼就調用i2c_new_device()。
2.直接使用2c_new_device, i2c_new_probed_device
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client    *client;
    int            status;
    client = kzalloc(sizeof *client, GFP_KERNEL);
    if (!client)
        return NULL;
    client->adapter = adap;
    client->dev.platform_data = info->platform_data;
    if (info->archdata)
        client->dev.archdata = *info->archdata;
    client->flags = info->flags;
    client->addr = info->addr;
    client->irq = info->irq;
    strlcpy(client->name, info->type, sizeof(client->name));
    /* Check for address validity */
    status = i2c_check_client_addr_validity(client);
    if (status) {
        dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
            client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
        goto out_err_silent;
    }
    /* Check for address business */
    status = i2c_check_addr_busy(adap, client->addr);
    if (status)
        goto out_err;
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
    client->dev.of_node = info->of_node;
    /* For 10-bit clients, add an arbitrary offset to avoid collisions */
    dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
         client->addr | ((client->flags & I2C_CLIENT_TEN)
                 ? 0xa000 : 0));
    status = device_register(&client->dev);
    if (status)
        goto out_err;
    dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
        client->name, dev_name(&client->dev));
    return client;
out_err:
    dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
        "(%d)\n", client->name, client->addr, status);
out_err_silent:
    kfree(client);
    return NULL;
}
該函數只是device_register,但是上面出現了一個新的結構,i2c_client,其實它就是一個struct device的i2c設備的封裝。在client裏保存該設備的相關信息,client->adapter指向了它所在的adapter。clent->dev所在的bus為i2c_bus_type,在device_register註冊的時候,會調用總線的match函數。
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 */
    if (of_driver_match_device(dev, drv))
        return 1;
    driver = to_i2c_driver(drv);
    /* match on an id table if there is one */
    if (driver->id_table)
        return i2c_match_id(driver->id_table, client) != NULL;
    return 0;
}
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}
上面是匹配driver的id_table的名字和client的名字是否相同,那麼會調用驅動的probe函數。
下面在分析下i2c_new_probed_device有啥不一樣,前面一個認為設備肯定存在,而後面的是對於已經識別出來的設備,才會創建。
for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
        /* Check address validity */
        if (i2c_check_addr_validity(addr_list[i]) < 0) {
            dev_warn(&adap->dev, "Invalid 7-bit address "
                 "0x%02x\n", addr_list[i]);
            continue;
        }
        /* Check address availability */
        if (i2c_check_addr_busy(adap, addr_list[i])) {
            dev_dbg(&adap->dev, "Address 0x%02x already in "
                "use, not probing\n", addr_list[i]);
            continue;
        }
        /* Test address responsiveness */
        if (probe(adap, addr_list[i]))
            break;
    }
3.直接i2c_add_driver
前面的2中方法,都要實現確定適配器,如果我們不知道這個i2c設備在那個適配器上,怎麼辦?內核提供了一種去class表示在所有的適配器上查找一些i2c設備的地址。
static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .owner = THIS_MODULE,
    },
    .probe = at24_probe,
    .remove = __devexit_p(at24_remove),
    .id_table = at24_ids,
};
static int __init at24_init(void)
{
    if (!io_limit) {
        pr_err("at24: io_limit must not be 0!\n");
        return -EINVAL;
    }
    io_limit = rounddown_pow_of_two(io_limit);
    return i2c_add_driver(&at24_driver);
}
而i2c_add_diver只是調用i2c_register_driver了
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    int res;
    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p)))
        return -EAGAIN;
    /* add the driver to the list of i2c drivers in the driver core */
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;
    /* When registration returns, the driver core
     * will have called probe() for all matching-but-unbound devices.
     */
    res = driver_register(&driver->driver);
    if (res)
        return res;
    /* Drivers should switch to dev_pm_ops instead. */
    if (driver->suspend)
        pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
            driver->driver.name);
    if (driver->resume)
        pr_warn("i2c-core: driver [%s] using legacy resume method\n",
            driver->driver.name);
    pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
    INIT_LIST_HEAD(&driver->clients);
    /* Walk the adapters that are already present */
    i2c_for_each_dev(driver, __process_new_driver);
    return 0;
}
上面比較主要的是driver_register,同時遍歷driver->client鏈表,會調用driver->detect的函數。

限會員,要發表迴響,請先登入