Linux I2C驱动入门知识科普

电子说

1.2w人已加入

描述

Linux内核将 I2C 驱动分为两部分:

I2C 总线驱动, I2C总线驱动就是SOC的 I2C控制器驱动,也叫做 I2C适配器驱动。

I2C 设备驱动, I2C设备驱动就是针对具体的 I2C设备而编写的驱动。

I2C框架下的几个重要成员

1. I2C总线

I2C总线结构体在driversi2ci2c-core.c中定义如下:

 

struct bus_type i2c_bus_type = {
    .name  = "i2c",
    .match  = i2c_device_match,
    .probe  = i2c_device_probe,
    .remove  = i2c_device_remove,
    .shutdown = i2c_device_shutdown,
};

 

I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动的probe函数。

形如:

I2C总线

i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关。

2. I2C驱动

i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容, 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 *);


    /* 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").
     */
    void (*alert)(struct i2c_client *, 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;
};

 

重点成员如下:

 

int (*probe)(struct i2c_client *, const struct i2c_device_id *) 

 

当 I2C设备和驱动匹配成功以后 probe函数就会执行。

 

struct device_driver driver device_driver 

 

驱动结构体,如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容(compatible)属性。

 

const struct i2c_device_id *id_table

 

id_table 是传统的、未使用设备树的设备匹配 ID表

3. I2C设备

I2C设备结构体i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:

 

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 device dev;  /* the device structure  */
    int irq;   /* irq issued by device  */
    struct list_head detected;
    #if IS_ENABLED(CONFIG_I2C_SLAVE)
    i2c_slave_cb_t slave_cb; /* callback for slave mode */
    #endif
};

 

重点成员如下:

flags:标志

addr:芯片地址,7 位,存在低 7 位

flagsname[I2C_NAME_SIZE]:名字

adapter:对应的 I2C 适配器

dev:设备结构体

irq:中断

一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

4. I2C适配器

经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C设配器,I2C设配器对应的就是SOC上的I2C控制器。

Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter, i2c_adapter 结构体定义在 include/linux/i2c.h 文件中,结构体内容如下:

 

/*
 * i2c_adapter is the structure used to identify a physical i2c bus along
 * with the access algorithms necessary to access it.
 */
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;

    struct i2c_bus_recovery_info *bus_recovery_info;
    const struct i2c_adapter_quirks *quirks;
};

 

重点成员如下:

 

const struct i2c_algorithm *algo

 

I2C 适配器与 IIC 设备进行通信的方法。

i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中,内容如下:

 

struct i2c_algorithm {
    /* If an adapter algorithm can't do I2C-level access, set master_xfer
       to NULL. If an adapter algorithm can do SMBus access, set
       smbus_xfer. If set to NULL, the SMBus protocol is simulated
       using common I2C messages */
    /* master_xfer should return the number of messages successfully
       processed, or a negative value on error */
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
               int num);
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
               unsigned short flags, char read_write,
               u8 command, int size, union i2c_smbus_data *data);

    /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);

    #if IS_ENABLED(CONFIG_I2C_SLAVE)
    int (*reg_slave)(struct i2c_client *client);
    int (*unreg_slave)(struct i2c_client *client);
    #endif
};

 

重点成员如下:

master_xfer:I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。

smbus_xfer:SMBUS 总线的传输函数

I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm中的master_xfer函数。完成以后通过 i2c_add_numbered_adapter或 i2c_add_adapter这两个函数向系统注册设置好的 i2c_adapter。

这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。

5. 小结

I2C驱动有4个重要的东西:I2C总线、I2C驱动、I2C设备、I2C设备器。

I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等。

I2C驱动:对应的就是I2C设备的驱动程序。

I2C设备:是具体硬件设备的一个抽象。

I2C适配器:用于I2C驱动和I2C设备间的通用,是SOC上I2C控制器的一个抽象。

Linux I2C总线的运行机制:

注册I2C驱动

将I2C驱动添加到I2C总线的驱动链表中

遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数

i2c_device_probe函数会调用I2C驱动的probe函数

I2C驱动简单编写流程

 一般 SOC 的 I2C总线驱动都是由半导体厂商编写的,这个不需要用户去编写。因此 I2C 总线驱动对于 SOC使用者来说是被屏蔽掉的,我们只要专注于 I2C 设备驱动即可。除非你是在半导体公司上班,工作内容就是写 I2C 适配器驱动。

i2c_driver类似platform_driver,是我们编写I2C设备驱动重点要处理的内容,i2c_driver在上面已经介绍了其结构体的具体内容。

对于我们 I2C 设备驱动编写人来说,重点工作就是构建i2c_driver,构建完成以后需要向Linux内核注册这个i2c_driver。

那么如何注册呢?

使用下面的这个函数:

 

int i2c_register_driver(struct module *owner,struct i2c_driver *driver)
函数参数和返回值含义如下:

 

owner:一般为 THIS_MODULE。

driver:要注册的 i2c_driver。

返回值:0,成功;负值,失败。

另外 i2c_add_driver 也常常用于注册 i2c_driver, i2c_add_driver 是一个宏,定义如下:

 

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

 

i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的 i2c_driver。

设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:

 

void i2c_del_driver(struct i2c_driver *driver);
函数参数和返回值含义如下:

 

driver:要注销的 i2c_driver。

返回值:无。

例程框架:

 

/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client,
{
    /* 函数具体程序 */
    return 0;
}

/* i2c 驱动的 remove 函数 */
static int xxx_remove(struct i2c_client *client)
{
    /* 函数具体程序 */
    return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
    {"xxx", 0},
    {}
};

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
    { .compatible = "xxx" },
    { /* Sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
    .probe = xxx_probe,
    .remove = xxx_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "xxx",
        .of_match_table = xxx_of_match,
    },
    .id_table = xxx_id,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    int ret = 0;
    ret = i2c_add_driver(&xxx_driver);
    return ret;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    i2c_del_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

 

当I2C设备和I2C驱动匹配成功以后probe函数就会执行,这些和platform驱动一样,probe函数里面基本就是标准的字符设备驱动那一套了。

   审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分