前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux I2C驱动入门,建议收藏!

Linux I2C驱动入门,建议收藏!

作者头像
混说Linux
发布2022-07-14 20:09:00
2.9K0
发布2022-07-14 20:09:00
举报
文章被收录于专栏:混说Linux混说Linux

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

  1. I2C 总线驱动, I2C总线驱动就是SOC的 I2C控制器驱动,也叫做 I2C适配器驱动。
  2. I2C 设备驱动, I2C设备驱动就是针对具体的 I2C设备而编写的驱动。

I2C总线协议详解请参考:IIC通信协议,搞懂这篇就够了

I2C框架下的几个重要成员

1. I2C总线

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

代码语言:javascript
复制
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_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关。

2. I2C驱动

i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容, i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:

代码语言:javascript
复制
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;
};

重点成员如下:

代码语言:javascript
复制
int (*probe)(struct i2c_client *, const struct i2c_device_id *) 

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

代码语言:javascript
复制
struct device_driver driver device_driver 

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

代码语言:javascript
复制
const struct i2c_device_id *id_table

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

3. I2C设备

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

代码语言:javascript
复制
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 文件中,结构体内容如下:

代码语言:javascript
复制
/*
 * 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;
};

重点成员如下:

代码语言:javascript
复制
const struct i2c_algorithm *algo

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

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

代码语言:javascript
复制
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总线的运行机制:

  1. 注册I2C驱动
  2. 将I2C驱动添加到I2C总线的驱动链表中
  3. 遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数
  4. 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。

那么如何注册呢?

使用下面的这个函数:

代码语言:javascript
复制
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 是一个宏,定义如下:

代码语言:javascript
复制
#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 函数,此函数原型如下:

代码语言:javascript
复制
void i2c_del_driver(struct i2c_driver *driver);

函数参数和返回值含义如下:

  • driver:要注销的 i2c_driver。
  • 返回值:无。

例程框架:

代码语言:javascript
复制
/* 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函数里面基本就是标准的字符设备驱动那一套了。

作者:一只青木呀

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 混说Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档