Yuhang Zheng

第三十节、i2c总线实现client设备和驱动

N 人看过

本节用于介绍i2c总线实现client设备和驱动

一、Linuxl2C驱动框架简介

Linux中的l2C也是按照平台总线模型设计的,既然也是按照平台总线模型设计的,是不是也分为一个device 和一个driver呢?但是I2C这里的device 不叫 device,而是叫client。在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于l2C而言,不需要虚拟出一条总线,直接使用l2C总线即可。

同样,我们也是先从非设备树开始,先来看一下,在没有设备树之前我们是怎么实现的l2C的device部分,也就是client部分。然后在学习有了设备树之后,我们的client是怎么编写的。按照Linux的发展路径来学习。

在没有使用设备树之前,我们使用的是i2c_board_info这个结构体来描述一个I2C设备的,i2c_board_info这个结构体如下:

文件位置路径:include/uapi/linux/i2c-dev.h

struct i2c_board_info {
        char            type[I2C_NAME_SIZE];    //I2C设备的名字
        unsigned short  flags;                    //标志
        unsigned short  addr;                    //I2C器件地址
        void            *platform_data;
        struct dev_archdata     *archdata;
        struct device_node *of_node;
        struct fwnode_handle *fwnode;
        const struct property_entry *properties;
        const struct resource *resources;
        unsigned int    num_resources;
        int             irq;
};

在这个结构体里面,type和addr这两个成员变量是必须要设置的,一个是l2C设备的名字,,这个名字就是用来进行匹配用的,一个是l2C设备的器件地址。也可以使用宏:

#define I2C_BOARD_INFO(dev_type, dev_addr) \
        .type = dev_type, .addr = (dev_addr)

可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的type和addr这两个成员变量。

I2C设备和驱动的匹配过程是由I2C核心来完成的,在Linux源码的 drivers/i2c/i2c-core-base.c就是l2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数,如下:

1、i2c_get_adapter函数

作用:获得一个l2C适配器

文件位置路径:include/linux/i2c.h

实际函数位置路径:drivers/i2c/i2c-core-base.c

struct i2c_adapter *i2c_get_adapter(int nr)
{
    struct i2c_adapter *adapter;

    mutex_lock(&core_lock);
    adapter = idr_find(&i2c_adapter_idr, nr);
    if (!adapter)
        goto exit;

    if (try_module_get(adapter->owner))
        get_device(&adapter->dev);
    else
        adapter = NULL;

 exit:
    mutex_unlock(&core_lock);
    return adapter;
}
//参数:
//nr:要获得的哪个I2C适配器的编号
//返回值:
//失败返回NULL

2、i2c_new_device函数

作用:把I2C适配器和I2C器件关联起来。

文件位置路径:include/linux/i2c.h

实际函数位置路径:drivers/i2c/i2c-core-base.c

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
//参数:
//adap:I2C适配器
//info:i2c_board_info结构体的指针
//返回值:
//失败返回NULL

3、i2c_put_adapter函数

作用:释放l2C适配器

文件位置路径:include/linux/i2c.h

实际函数位置路径:drivers/i2c/i2c-core-base.c

void i2c_put_adapter(struct i2c_adapter *adap)
{
    if (!adap)
        return;

    put_device(&adap->dev);
    module_put(adap->owner);
}
//参数:
//adap:要释放的I2C适配器

4、i2c_unregister_device函数

作用:注销一个client

文件位置路径:include/linux/i2c.h

实际函数位置路径:drivers/i2c/i2c-core-base.c

void i2c_unregister_device(struct i2c_client *client)
//参数:
//client:i2c_client的指针

在使用了设备树以后,就不用这么复杂了,使用设备树的时候只要在对应的I2C节点下创建相应设备的节点即可,比如我想添加一个RTC的设备,我就可以在对应的I2C的节点下这样写,如下图所示:

&i2c0 {
        status = "okay";

        rtc1:rtc@32 {
                compatible = "rx8010";
                reg = <0x32>;
                status = "okay";
        };
};

这些设备我们可以在文件系统的/sys/bus/i2c/devices/路径下看到我们所有的I2C设备的节点

root@localhost:~# ls /sys/bus/i2c/devices/
0-0052  0-0053  1-0032  i2c-0  i2c-1  i2c-2

然后我们再来看driver部分。不管是使用设备树还是非设备树,driver部分就比较复杂了。和注册一个杂项设备或者是字符设备的套路一样,我们也是要先定一个一个i2c_driver的结构体,然后在对他进行初始化,我们来看一下这个结构体的定义,如下图所示:

文件位置路径:include/linux/i2c.h

struct i2c_driver {
        unsigned int class;

        /* Notifies the driver that a new bus has appeared. You should avoid
         * using this, it will be removed in a near future.
         */
        int (*attach_adapter)(struct i2c_adapter *) __deprecated;

        /* Standard driver model interfaces */
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);

        /* New driver model interface to aid the seamless removal of the
         * current probe()'s, more commonly unused than used second parameter.
         */
        int (*probe_new)(struct i2c_client *);

        /* driver model interfaces that don't relate to enumeration  */
        void (*shutdown)(struct i2c_client *);

        /* Alert callback, for example for the SMBus alert protocol.
         * The format and meaning of the data value depends on the protocol.
         * For the SMBus alert protocol, there is a single bit of data passed
         * as the alert response's low bit ("event flag").
         * For the SMBus Host Notify protocol, the data corresponds to the
         * 16-bit payload data reported by the slave device acting as master.
         */
        void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
                      unsigned int data);

        /* a ioctl like command that can be used to perform specific functions
         * with the device.
         */
        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;

        bool disable_i2c_core_irq_mapping;
};
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)

在驱动注册之前i2c_driver结构体需要被正确地初始化,有4个成员要求必须被初始化,其中id_table不管用不用设备树都要被初始化,否则不能匹配成功:

static struct i2c_driver xxx_driver = {
    .driver = {
        .name ="xxx", 
    },
    .probe        = xxx_probe,
    .remove        = xxx_remove,
    .id_table    = xxx_table,
};

初始化完成以后就是把i2c_driver注册进内核,注册进内核我们使用是的是i2c_add_driver

1、i2c_add_driver函数

作用:注册一个i2c驱动

函数原型:

文件位置路径:include/linux/i2c.h

实际函数位置路径:drivers/i2c/i2c-core-base.c

#define i2c_add_driver(driver) \
        i2c_register_driver(THIS_MODULE, driver)

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
//参数:
//driver:struct i2c_driver的指针
//返回值:
//失败返回负值

2、i2c_del_driver函数

作用:删除一个i2c驱动

函数原型:

文件位置路径:include/linux/i2c.h

实际函数位置路径:drivers/i2c/i2c-core-base.c

void i2c_del_driver(struct i2c_driver *driver)
{
        i2c_for_each_dev(driver, __process_removed_driver);

        driver_unregister(&driver->driver);
        pr_debug("driver [%s] unregistered\n", driver->driver.name);
}
EXPORT_SYMBOL(i2c_del_driver);
//参数:
//driver:struct i2c_driver的指针
//返回值:
//失败返回负值