Jimmy Chen

A Programmer

(原创)Linux下I2C框架分析

  在上一篇I2C协议的简单介绍后,我们马上结合Linux源码来了解下Linux中的I2C框架是如何的。

1. 基本框架了解

《(原创)Linux下I2C框架分析》

  如上图显示的,要讨论的东西包括driver、client、i2c-dev、i2c-core、Algorithm和adapter。在上层的位置中,Client可以简单的理解为具体的物理设备,而driver就是驱动物理设备的驱动程序,i2c-dev则是为不同的client设备提供统一访问接口的作用。i2c-core在中间的位置,起到承上启下的作用,为上层驱动提供client与driver匹配接口以及上层driver和下层的adapter匹配接口,对下层起到将adapter添加到内核中以及adapter和Algorithm进行匹配的作用。下层中的adapter可以理解为芯片内的I2C驱动器,正是因为不同的芯片拥有不同的架构,而且其数据传输适中要求和芯片引脚不同,所以需要搭配不同的Algorithm来使用。

2. 几个重要的结构体

2.1 i2c_driver

struct i2c_driver {
    unsigned int class;

    // 指向依附i2c_adapter的函数指针
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;
    // 指向脱离i2c_adapter的函数指针
    int (*detach_adapter)(struct i2c_adapter *) __deprecated;

    // 标准驱动接口,probe用于初始化,remove用于注销
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    // 指向驱动状态控制的函数指针
    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);

    // 类似于ioctl的函数
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    // 设备驱动结构体
    struct device_driver driver;
    // 适配该驱动的设备TABLE
    const struct i2c_device_id *id_table;

    // 检测i2c_client设备
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    
    const unsigned short *address_list;
    // client设备链表头
    struct list_head clients;
};

2.2 i2c_client

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;
};

如上面的英文注解,addr的底七位记录client的地址,adapter记录client依附的adapter,而driver记录client使用的设备驱动i2c_driver。

2.3 i2c_algorithm

struct i2c_algorithm {
    // i2c传输函数指针
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
               int num);
    // smbus传输函数指针
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
               unsigned short flags, char read_write,
               u8 command, int size, union i2c_smbus_data *data);

    // 返回i2c_adapter支持的功能
    u32 (*functionality) (struct i2c_adapter *);
};

2.4 i2c_adapter

struct i2c_adapter {
    struct module *owner;
    unsigned int class;       /* classes to allow probing for */
    // 适配的Algorithm
    const struct i2c_algorithm *algo; /* the algorithm to access the bus */
    // Algorithm数据
    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;
    // adapter的名称
    char name[48];
    struct completion dev_released;

    struct mutex userspace_clients_lock;
    // client链表头
    struct list_head userspace_clients;
};

2.5 i2c_msg

struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN      0x0010  /* this is a ten bit chip address */
#define I2C_M_RD       0x0001  /* read data, from slave to master */
#define I2C_M_NOSTART      0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK   0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK        0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN     0x0400  /* length will be first received byte */
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};

2.6 总结

  上面给出了5个常用到的结构体,i2c_msg用于I2C通信的消息传递,这里主要总结下i2c_client、i2c_driver、i2c_adapter和i2c_algorithm之间的关系。

i2c_driver和i2c_client的关系

  正如之前所说的,i2c_client代表的是设备上具体的物理设备,例如EEPROM。那么在i2c_client的结构体内存在这i2c_adapter,主要是用于记录client所依附的adapter,当对设备读写时也就能找到对应的adapter以及读写设备的Algorithm了,其次还有一个i2c_driver,主要用于记录该client所使用的i2c_driver。i2c_driver是一个抽象的驱动,不对应任何的物理实体内容。博主目前认为如此设计,主要可能是因为driver和client是一对多的关系,即i2c_client对应的物理设备所使用驱动方法可能是相同的,那么抽象出来就能够服用了。i2c_driver内有一个client链表,就是用来记录以来该抽象i2c_driver的client的。同时i2c_driver提供了client用于注册到和注销相应adapter的方法。

  按照这里的说法,在添加新的设备时,肯定是需要填写对应的i2c_client结构体的,至于i2c_driver结构体,如果没有适合的i2c_driver可以复用的话,那么也是需要工程师进行添加的。

i2c_adapter和i2c_algorithm的关系

  i2c_adapter对应的是物理上的适配器,一般这个适配器会集成在芯片内,而i2c_algorithm对应的是一套通信的方法。i2c_algorithm就是为特定的i2c_adapter提供通信方法的,如果没有为i2c_adapter提供对应的i2c_algorithm的话,那么这个i2c_adapter就什么都做不了。所以我们会发现在i2c_adapter结构体内会存在一个记录i2c_algorithm的指针。而i2c_algorithm中的master_xfer和smbus_xfer指针就是记录对应的通信方法的。在执行I2C通信的时候会调用到。

  那么对于工程师来说,如果如果没有提供对应的适配器方法就需要自己填写i2c_adapter结构体,既然适配器的内容是自行添加的,那么对应的i2c通信方法也需要根据具体的时序和芯片手册标注的寄存器进行编程。这一部分也是需要工程师干预的。

i2c_adapter和i2c_client的关系

  结合上面介绍的内容,很容易就能理解i2c_adapter和i2c_client之间的关系了,无非就是物理适配器和物理设备之间的对应关系。i2c_client要依附于i2c_adapter。由于一个适配器上可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。

所以,可能需要工程师干预的部分如下:

  • 根据I2C适配器的特性,提供I2C适配器的硬件驱动、探测和初始化适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信息以及处理I2C中断
  • 提供与I2C适配器协同运作的Algorithm,用具体适配器的xxx_xfer()方法填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的alog指针
  • 实现I2C设备的i2c_driver接口,使用具体xxx_attach_adapter()函数指针、xxx_detach_client()函数指针和xxx_command()函数指针赋给i2c_driver的对应指针域
  • 实现具体I2C设备的文件操作接口,如open、write、read接口。

上面说到的前两点和后两点是可以分开进行的。毕竟可以将i2c_adapter可看做是一个驱动来向内核注册。

《(原创)Linux下I2C框架分析》

I2C驱动中个结构体之间的关系

3. i2c-core简单分析

  i2c-core中存在着许多有用的函数,所以这里只列出部分函数的代码,有兴趣的可以自行查看i2c-core.c文件进行查看。

3.1 i2c_device_match

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;
}

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;
}

  i2c_device_match就是用来匹配设备device和设备driver的函数,看上面的代码可以知道,匹配过程首先尝试通过设备树的方法,如果设备树的方式匹配失败了在调用i2c_match_id函数进行匹配,i2c_match_id方法是通过名字进行匹配的。所以,我们编写的i2c_client和i2c_driver结构体的时候,其名字字段必须是相同的。

3.2 i2c_add_adapter

static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0;

    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p))) {
        res = -EAGAIN;
        goto out_list;
    }

    // adapter的名字不能为空
    if (unlikely(adap->name[0] == '\0')) {
        pr_err("i2c-core: Attempt to register an adapter with "
               "no name!\n");
        return -EINVAL;
    }
    // adapter的algorithm不能为空
    if (unlikely(!adap->algo)) {
        pr_err("i2c-core: Attempt to register adapter '%s' with "
               "no algo!\n", adap->name);
        return -EINVAL;
    }

    // 初始化互斥锁和client链表
    rt_mutex_init(&adap->bus_lock);
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);

    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;

    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
    // 调用device_register注册adapter设备
    res = device_register(&adap->dev);
    if (res)
        goto out_list;

    dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

#ifdef CONFIG_I2C_COMPAT
    res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                       adap->dev.parent);
    if (res)
        dev_warn(&adap->dev,
             "Failed to create compatibility class link\n");
#endif

    /* create pre-declared device nodes */
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    // 扫描设备,触发匹配过程
    /* Notify drivers */
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    mutex_unlock(&core_lock);

    return 0;

out_list:
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    return res;
}

int i2c_add_adapter(struct i2c_adapter *adapter)
{
    int id, res = 0;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh */
    res = idr_get_new_above(&i2c_adapter_idr, adapter,
                __i2c_first_dynamic_bus_num, &id);
    mutex_unlock(&core_lock);

    if (res < 0) {
        if (res == -EAGAIN)
            goto retry;
        return res;
    }

    adapter->nr = id;
    return i2c_register_adapter(adapter);
}
EXPORT_SYMBOL(i2c_add_adapter);

  i2c_add_adapter会调用到i2c_register_adapter进行adapter添加。那么i2c_del_adapter是完成的工作理应和上面的相反的,这里就不列出i2c_del_adapter的代码了。

3.3 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;

    // 设置driver的内容
    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.
     */
    // 如注释说,会调用driver core的probe()方法尽心设备匹配
    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);
    // 扫描设备,触发匹配过程
    i2c_for_each_dev(driver, __process_new_driver);

    return 0;
}
EXPORT_SYMBOL(i2c_register_driver);

3.4 i2c_master_send && i2c_master_recv

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
    int ret;
    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.len = count;
    msg.buf = (char *)buf;

    ret = i2c_transfer(adap, &msg, 1);

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */
    return (ret == 1) ? count : ret;
}
EXPORT_SYMBOL(i2c_master_send);

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;
    int ret;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.flags |= I2C_M_RD;
    msg.len = count;
    msg.buf = buf;

    ret = i2c_transfer(adap, &msg, 1);

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */
    return (ret == 1) ? count : ret;
}
EXPORT_SYMBOL(i2c_master_recv);

  这里的i2c_master_xxxx操作都是先填充对应的i2c_msg结构体的内容,然后调用i2c_transfer进行实际的通信操作。同时应该注意到,i2c_master_send和i2c_master_recv方法只能够发送或接收一条i2c_msg信息,如果要一次发送或读取多条i2c_msg信息的话,就需要另寻方法了。

3.5 i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    unsigned long orig_jiffies;
    int ret, try;

    if (adap->algo->master_xfer) {
#ifdef DEBUG
        for (ret = 0; ret < num; ret++) {
            dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
                "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
                ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
        }
#endif

        if (in_atomic() || irqs_disabled()) {
            ret = i2c_trylock_adapter(adap);
            if (!ret)
                /* I2C activity is ongoing. */
                return -EAGAIN;
        } else {
            i2c_lock_adapter(adap);
        }

        /* Retry automatically on arbitration loss */
        orig_jiffies = jiffies;
        // 正如前面所说,这里会调用到adapter的algorithm的master_xfer方法
        for (ret = 0, try = 0; try <= adap->retries; try++) {
            ret = adap->algo->master_xfer(adap, msgs, num);
            if (ret != -EAGAIN)
                break;
            if (time_after(jiffies, orig_jiffies + adap->timeout))
                break;
        }
        i2c_unlock_adapter(adap);

        return ret;
    } else {
        dev_dbg(&adap->dev, "I2C level transfers not supported\n");
        return -EOPNOTSUPP;
    }
}
EXPORT_SYMBOL(i2c_transfer);

i2c-core.c文件中还有很多函数方法,这里就不一一列出了,毕竟暂时能力有限,理解不对反而祸害了读者,哈哈!

4. i2c-dev.c简单分析

  i2c-dev.c可以看做一个I2C设备驱动。在我看来,i2c-dev是为软件访问I2C设备提供统一的接口。这样,我们的i2c_driver就不用编写过多的read、write方法了。可以通过i2c-dev内的方法,加上i2c_adapter中的algorithm,就能完成I2C的传输。

下面稍微看几个i2c-dev中的函数

4.1 i2cdev_read

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
        loff_t *offset)
{
    char *tmp;
    int ret;

    // 获取client的私有数据
    struct i2c_client *client = file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = kmalloc(count, GFP_KERNEL);
    if (tmp == NULL)
        return -ENOMEM;

    pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
        iminor(file->f_path.dentry->d_inode), count);

    // 调用i2c-core中的i2c_master_recv方法
    ret = i2c_master_recv(client, tmp, count);
    if (ret >= 0)
        ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
    kfree(tmp);
    return ret;
}

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
    // 获取client中对应的adapter
    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;
    int ret;

    // 构造i2c_msg结构体
    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.flags |= I2C_M_RD;
    msg.len = count;
    msg.buf = buf;

    // 调用i2c_transfer方法,该方法中最后会调用algorithm中的实际传输方法
    ret = i2c_transfer(adap, &msg, 1);

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */
    return (ret == 1) ? count : ret;
}

  上面可以看出来,i2cdev_read也是会调用到i2c-core中的方法。而且看i2cdev_read中的代码就能感觉到该方法是通用的。

4.2 i2cdev_write

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
        size_t count, loff_t *offset)
{
    int ret;
    char *tmp;
    struct i2c_client *client = file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = memdup_user(buf, count);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
        iminor(file->f_path.dentry->d_inode), count);

    ret = i2c_master_send(client, tmp, count);
    kfree(tmp);
    return ret;
}

  i2cdev_write方法和read方法基本类似。

当然还有IOCTL方法了,这里就不贴代码了,大家想了解的还是看看源码比较好。之后找个机会和时间实际使用i2c-dev中的方法来编写一个应用来访问I2C好了,那样应该能够更好的理解和使用i2c-dev的作用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: