Linux驱动之I2C驱动架构

简介: Linux驱动之I2C驱动架构

一、Linux的I2C体系结构

主要由三部分组成:
(1) I2C核心
提供I2C控制器和设备驱动的注册和注销方法,I2C通信方法,与适配器无关的代码以及探测设备等。
(2) I2C控制器驱动(适配器)
(3) I2C设备驱动
20190915104940280.png

二、重要的结构体

  • i2c_adapter
//i2c控制器(适配器)
struct i2c_adapter {
    struct module *owner;
    unsigned int class;          /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* 总线通信结构体指针 */
    void *algo_data; //algorithm数据

    /* data fields that are valid for all devices    */
    //并发同步,互斥锁
    const struct i2c_lock_operations *lock_ops;
    struct rt_mutex bus_lock;
    struct rt_mutex mux_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; //client链表

    struct i2c_bus_recovery_info *bus_recovery_info;
    const struct i2c_adapter_quirks *quirks;

    struct irq_domain *host_notify_domain;
};
  • i2c_algorithm
//I2C传输方法
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);

    //返回适配器支持的功能
    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
};

SMBus是基于I2C总线规范的,所以上面的传输函数要根据自己的总线来选择,选择其一就可以。

  • i2c_driver
//I2C驱动,和platform_driver,spi_driver类似
struct i2c_driver {
    unsigned int class;

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

    int (*probe_new)(struct i2c_client *);
    void (*shutdown)(struct i2c_client *);
    void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
              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; //该设备所支持的设备ID表

    /* 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; //client链表

    bool disable_i2c_core_irq_mapping;
};
  • i2c_client
//I2C设备
struct i2c_client {
    unsigned short flags;        //标志
    unsigned short addr;        //芯片地址,保存在addr低7位
    char name[I2C_NAME_SIZE];       //设备名称
    struct i2c_adapter *adapter;    //依附的i2c_adapter
    struct device dev;        //设备结构体
    int irq;            //设备使用的中断号
    struct list_head detected;      //client链表,和i2c_driver中clients的一样
#if IS_ENABLED(CONFIG_I2C_SLAVE)
    i2c_slave_cb_t slave_cb;    //从机模式回调
#endif
};
  • i2c_msg
//I2C传输数据结构体,代表一个消息数据
struct i2c_msg {
    __u16 addr;    //设备地址
    __u16 flags;    //标志
#define I2C_M_RD        0x0001    /* read data, from slave to master */
                    /* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN        0x0010    /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE    0x0200    /* the buffer of this message is DMA safe */
                    /* makes only sense in kernelspace */
                    /* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN    0x0400    /* length will be first received byte */
#define I2C_M_NO_RD_ACK    0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART    0x4000    /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP        0x8000    /* if I2C_FUNC_PROTOCOL_MANGLING */
    __u16 len;        //消息长度
    __u8 *buf;        //消息数据
};

总结上面结构体关系:

1. i2c_adapter和i2c_algorithm
    i2c_adapter对应物理上的一个适配器,而i2c_algorithm对应一套通信方法,适配器需要通过i2c_algorithm提供的通信函数来产生对应的访问时序。所以i2c_adapter中包含i2c_algorithm的指针。
    i2c_algorithm使用master_xfer()来产生I2C时序,以i2c_msg为单位,i2c_msg代表一次传输的数据。

2. i2c_driver和i2c_client
    i2c_driver对应一套驱动方法,包含probe,remove等方法。i2c_clent对应真实的物理设备,每个i2c设备都需要一个i2c_client来描述。i2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同类型的i2c_client。

3. i2c_adapter和i2c_client
    i2c_adapter与i2c_client的关系和硬件上适配器与设备的关系一致,即i2c_client依附于i2c_adapter。一个适配器可以连接多个设备,所以i2c_adapter中包含i2c_client的链表。

三、API函数

//增加/删除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
void i2c_del_adapter(struct i2c_adapter *adap)

//增加/删除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)
 
//i2c传输、发送和接收
//完成I2C总线和I2C设备之间的一定数目的I2C message交互
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
//通过封装i2c_transfer()完成一次I2c发送操作
int i2c_master_send(const struct i2c_client *client,
                  const char *buf, int count) 
//通过封装i2c_transfer()完成一次I2c接收操作      
int i2c_master_recv(const struct i2c_client *client,
                  char *buf, int count) 

i2c_transfer()函数本身不具备驱动适配器物理硬件以完成消息交互的能力,它只是寻找到与i2c_adapter对应的i2c_algorithm, 并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。

追踪i2c_transfer()的源码会发现下面的代码

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控制器通常是在内存上的,所以它本身也连接在platform总线上的,通过platform_driver和platform_device的匹配还执行。
(1) probe()完成如下工作:

  • 初始化I2C控制器所使用的硬件资源,如申请IO地址,中断号,时钟等。
  • 为特定I2C控制器实现通信方法,主要是实现i2c_algorithm的master_xfer()和functionality()函数。
  • 通过i2c_add_adapter()添加i2c_adapter的数据结构(i2c_adapter成员已被初始化)。

模板代码:

static const struct i2c_algorithm xxx_i2c_algo = {
    .master_xfer    = xxx_i2c_master_xfer,
    .functionality    = xxx_i2c_func,
};

static u32 xxx_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
        (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
        I2C_FUNC_SMBUS_BLOCK_DATA;
}

static int xxx_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
                int num)
{
    int ret, ;
    u32 reg;
    struct xxx_i2c *id = adap->algo_data;

    /* Process the msg one by one */
    for (i = 0; i < num; i++, msgs++) {
        i2c_adapter_xxx_start(); /*产生开始位*/
        /*是读消息*/
        if (msgs[i]->flags &I2C_M_RD)
        {
            i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*发送从设备读地址*/
            i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
            i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /*读取msgs[i]->len长的数据到msgs[i]->buf*/
        }
        else/*是写消息*/
        {
            i2c_adapter_xxx_setaddr(msg->addr << 1); /*发送从设备写地址*/
            i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
            i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /*读取msgs[i]->len长的数据到msgs[i]->buf*/
        }
    }
    i2c_adapter_xxx_stop(); /*产生停止位*/
    return num;
}

static int xxx_i2c_probe(struct platform_device *pdev)  // dts里的设备信息传递进来了
{
    struct resource *r_mem;
    struct xxx_i2c *id;
    int ret;

    id = devm_kzalloc(&pdev->dev, sizeof(*id), GFP_KERNEL);
    if (!id)
        return -ENOMEM;
    platform_set_drvdata(pdev, id);

   xxx_adapter_hw_init();    //通常初始化iic适配器使用的硬件资源,如申请IO地址、中断号、时钟等
    id->adap.dev.of_node = pdev->dev.of_node;
    id->adap.algo = &xxx_i2c_algo;  // 把altorithm连进来
    id->adap.timeout = XXX_I2C_TIMEOUT;
    id->adap.retries = 3;        /* Default retry value. */
    id->adap.algo_data = id;
    id->adap.dev.parent = &pdev->dev;

    ret = i2c_add_adapter(&id->adap);
    ...
}

static int xxx_i2c_remove(struct platform_device *pdev)
{
    struct xxx_i2c *id = platform_get_drvdata(pdev);

    i2c_del_adapter(&id->adap);
    xxx_adapter_hw_free();            // 硬件相关资源的free

    return 0;
}

static const struct of_device_id xxx_i2c_of_match[] = {
    { .compatible = "cdns,i2c-r1p10", },          
    { /* end of table */ }
};
MODULE_DEVICE_TABLE(of, xxx_i2c_of_match);

static struct platform_driver xxx_i2c_drv = {
    .driver = {
        .name  = DRIVER_NAME,
        .owner = THIS_MODULE,
        .of_match_table = xxx_i2c_of_match,    // dts匹配的依据
        .pm = &xxx_i2c_dev_pm_ops,
    },
    .probe  = xxx_i2c_probe,
    .remove = xxx_i2c_remove,
};

module_platform_driver(xxx_i2c_drv);

xxx_adapter_hw_init实现和具体的CPU和I2C控制器硬件相关的初始化。
functionality() 函数比较简单,返回支持的通信协议。
master_xfer() 函数在适配器上完成i2c_msg的数据传输。

五、设备(外设)驱动

i2c_dirver就是i2c标准总线设备驱动模型中的驱动部分,i2c_client可理解为i2c总线上挂的外设。

模板代码:

static struct i2c_driver xxx_driver = {
    .driver = {
        .name = "xxx",
        .of_match_table = xxx_of_match,
        .acpi_match_table = ACPI_PTR(xxx_acpi_ids),
    },
    .probe_new = xxx_probe,
    .remove = xxx_remove,
    .id_table = xxx_ids,
};

static int __init xxx_init(void)
{
    .......
    return i2c_add_driver(&xxx_driver);  // 匹配后,driver中的probe就能执行
}
static void __exit xxx_exit(void)
{
    i2c_del_driver(&xxx_driver);
}
module_exit(xxx_exit);
module_init(xxx_init);
相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
2月前
|
人工智能 运维 安全
配置驱动的动态 Agent 架构网络:实现高效编排、动态更新与智能治理
本文所阐述的配置驱动智能 Agent 架构,其核心价值在于为 Agent 开发领域提供了一套通用的、可落地的标准化范式。
607 49
|
2月前
|
人工智能 安全 数据可视化
配置驱动的动态Agent架构网络:实现高效编排、动态更新与智能治理
本文系统性地提出并阐述了一种配置驱动的独立运行时Agent架构,旨在解决当前低代码/平台化Agent方案在企业级落地时面临困难,为Agent开发领域提供了一套通用的、可落地的标准化范式。
365 18
配置驱动的动态Agent架构网络:实现高效编排、动态更新与智能治理
|
4月前
|
数据可视化 IDE Java
OneCode图生代码技术深度解析:从可视化设计到注解驱动实现的全链路架构
OneCode图生代码技术通过可视化设计与Java注解驱动,实现UI到代码的高效转换,支持设计即开发、组件复用与动态加载,提升企业应用开发效率与协作能力。
OneCode图生代码技术深度解析:从可视化设计到注解驱动实现的全链路架构
|
6月前
|
监控 Linux 应用服务中间件
Linux多节点多硬盘部署MinIO:分布式MinIO集群部署指南搭建高可用架构实践
通过以上步骤,已成功基于已有的 MinIO 服务,扩展为一个 MinIO 集群。该集群具有高可用性和容错性,适合生产环境使用。如果有任何问题,请检查日志或参考MinIO 官方文档。作者联系方式vx:2743642415。
2160 57
|
运维 监控 负载均衡
动态服务管理平台:驱动微服务架构的高效引擎
动态服务管理平台:驱动微服务架构的高效引擎
239 17
|
4月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
270 0
|
8月前
|
调度 决策智能 知识图谱
腾讯云大模型知识引擎驱动 DeepSeek 满血版能源革命大模型:架构、优势与产业变革
腾讯云大模型知识引擎驱动的DeepSeek满血版能源革命大模型,融合了超大规模知识、极致计算效能和深度行业理解,具备智能预测、优化调度、设备健康管理和能源安全预警等七大功能模块。该模型通过分布式计算和多模态融合,提供精准的能源市场分析与决策支持,广泛应用于智慧风电场管理、油气田开发、能源市场交易等十大场景,助力能源行业的数字化转型与可持续发展。
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
567 2