linux I2C驱动详解(3)-I2C Core

I2C Core(I2C 核心层)是 Linux 内核中 I2C 驱动架构的重要组成部分,它为 I2C 适配器驱动和 I2C 设备驱动提供了一系列标准接口,以实现统一的管理和操作。

I2C Core 提供的一些主要标准接口:

1. 适配器注册与注销接口

i2c_add_adapter
  • 功能:向 I2C 核心层注册一个 I2C 适配器。注册成功后,该适配器就可以被系统识别和使用。
  • 原型
int i2c_add_adapter(struct i2c_adapter *adapter);
  • 参数
    • adapter:指向要注册的 struct i2c_adapter 结构体的指针。
  • 返回值:成功返回 0,失败返回负数错误码。
i2c_del_adapter
  • 功能:从 I2C 核心层注销一个已注册的 I2C 适配器。注销后,该适配器将不再被系统使用。
  • 原型
void i2c_del_adapter(struct i2c_adapter *adapter);
  • 参数
    • adapter:指向要注销的 struct i2c_adapter 结构体的指针。

2. I2C 传输接口

i2c_transfer
  • 功能:发起一系列的 I2C 消息传输。它是 I2C 核心层提供的核心传输接口,会调用适配器驱动中实现的 master_xfer 回调函数来完成实际的传输操作。
  • 原型
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
  • 参数
    • adap:指向要使用的 I2C 适配器的指针。
    • msgs:指向 struct i2c_msg 结构体数组的指针,每个 i2c_msg 结构体表示一个 I2C 消息。
    • numi2c_msg 数组的元素个数。
  • 返回值:成功返回实际传输的消息数量,失败返回负数错误码。

3. I2C 设备注册与注销接口

i2c_new_device
  • 功能:在指定的 I2C 适配器上注册一个新的 I2C 设备。
  • 原型
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
  • 参数
    • adap:指向要使用的 I2C 适配器的指针。
    • info:指向 struct i2c_board_info 结构体的指针,该结构体包含了 I2C 设备的相关信息,如设备名称、从设备地址等。
  • 返回值:成功返回指向新注册的 struct i2c_client 结构体的指针,失败返回 NULL
i2c_unregister_device
  • 功能:注销一个已注册的 I2C 设备。
  • 原型
void i2c_unregister_device(struct i2c_client *client);
  • 参数
    • client:指向要注销的 struct i2c_client 结构体的指针。

4. SMBus 操作接口

i2c_smbus_xfer
  • 功能:执行 SMBus(System Management Bus)操作。SMBus 是 I2C 总线的一个子集,提供了一些标准的读写操作命令。
  • 原型
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
                   u16 flags, char read_write, u8 command,
                   int size, union i2c_smbus_data *data);
  • 参数
    • adapter:指向要使用的 I2C 适配器的指针。
    • addr:I2C 从设备的地址。
    • flags:操作标志。
    • read_write:读写标志,I2C_SMBUS_READ 表示读操作,I2C_SMBUS_WRITE 表示写操作。
    • command:SMBus 命令。
    • size:数据传输的大小。
    • data:指向 union i2c_smbus_data 结构体的指针,用于存储传输的数据。
  • 返回值:成功返回传输的数据,失败返回负数错误码。

5. 设备树相关接口

i2c_of_register_devices
  • 功能:根据设备树中的信息,自动注册 I2C 设备。
  • 原型
void i2c_of_register_devices(struct i2c_adapter *adap);
  • 参数
    • adap:指向要使用的 I2C 适配器的指针。

这些接口为 I2C 驱动的开发提供了便利,使得开发者可以专注于具体的硬件操作和功能实现,而无需关心 I2C 核心层的底层细节。

核心通信函数i2c_transfer详解 

在 Linux 内核的 I2C 驱动架构中,I2C 核心层、i2c_transfer 函数以及 master_xfer 回调函数之间存在着紧密的调用关系,下面为你详细解释它们之间的调用流程。

1. 概述

本节说明下I2C 核心层->i2c_transfer->适配器驱动master_xfer调用栈

2. I2C 核心层调用 i2c_transfer 的过程

2.1 应用层发起 I2C 操作请求

当应用程序需要进行 I2C 设备的读写操作时,通常会通过系统调用(如 ioctl)来请求内核进行相应的 I2C 传输。例如,使用 I2C_RDWR 命令发起一次读写操作。

2.2 内核层接收请求

在内核层,设备文件系统会将应用层的请求传递给 I2C 核心层。I2C 核心层会根据请求的内容,调用 i2c_transfer 函数来完成具体的 I2C 传输。

2.3 i2c_transfer 函数的定义和作用

i2c_transfer 是 I2C 核心层提供的一个通用函数,用于发起一系列的 I2C 消息传输。其原型如下:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
  • adap:指向要使用的 I2C 适配器的指针。
  • msgs:指向 i2c_msg 结构体数组的指针,每个 i2c_msg 结构体表示一个 I2C 消息。
  • numi2c_msg 数组的元素个数。
2.4 调用示例

在 I2C 核心层的代码中,当接收到应用层的 I2C 操作请求时,会调用 i2c_transfer 函数,示例代码如下:

// 假设已经获取到了 i2c_adapter 指针 adap 和 i2c_msg 数组 msgs 以及消息数量 num
int ret = i2c_transfer(adap, msgs, num);
if (ret < 0) {
    // 处理传输错误
}

3. i2c_transfer 调用 master_xfer 的过程

3.1 i2c_algorithm 结构体

每个 I2C 适配器都有一个 i2c_algorithm 结构体,该结构体定义了该适配器支持的 I2C 操作方法。其中,master_xfer 是一个回调函数指针,指向具体的 I2C 传输实现函数。

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
    u32 (*functionality)(struct i2c_adapter *adap);
    // 其他成员...
};
3.2 i2c_adapter 结构体

i2c_adapter 结构体表示一个 I2C 适配器,其中包含了指向 i2c_algorithm 结构体的指针。

struct i2c_adapter {
    struct module *owner;
    unsigned int id;
    unsigned int class;
    const struct i2c_algorithm *algo;
    // 其他成员...
};
3.3 i2c_transfer 内部调用 master_xfer

在 i2c_transfer 函数的实现中,会通过 i2c_adapter 结构体获取到对应的 i2c_algorithm 结构体,然后调用其中的 master_xfer 回调函数来完成实际的 I2C 传输。以下是简化的 i2c_transfer 函数实现示例:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    const struct i2c_algorithm *algo = adap->algo;
    if (algo->master_xfer) {
        return algo->master_xfer(adap, msgs, num);
    } else {
        // 处理不支持的情况
        return -ENOTSUPP;
    }
}
3.4 适配器驱动实现 master_xfer

I2C 适配器驱动需要实现 master_xfer 回调函数,该函数负责与具体的 I2C 控制器硬件进行交互,完成 I2C 数据的读写操作。例如,在之前提供的代码中,arm_i2c_xfer 函数就是 master_xfer 的具体实现:

static int arm_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    int i, ret;

    for (i = 0; i < num; i++) {
        struct i2c_msg *msg = &msgs[i];

        // 发送起始信号
        // 这里需要根据具体硬件设置寄存器来产生起始信号
        // 示例代码:
        // writel(START_BIT_MASK, i2c_con_reg);

        // 发送设备地址
        // 这里需要将设备地址写入数据寄存器并发送
        // 示例代码:
        // writel(msg->addr << 1 | (msg->flags & I2C_M_RD), i2c_data_reg);

        if (msg->flags & I2C_M_RD) {
            // 读取数据
            for (int j = 0; j < msg->len; j++) {
                // 等待数据准备好
                // 示例代码:
                // while (!(readl(i2c_stat_reg) & DATA_READY_MASK));
                // 读取数据
                // msg->buf[j] = readl(i2c_data_reg);
            }
        } else {
            // 写入数据
            for (int j = 0; j < msg->len; j++) {
                // 写入数据到寄存器
                // 示例代码:
                // writel(msg->buf[j], i2c_data_reg);
                // 等待写入完成
                // 示例代码:
                // while (!(readl(i2c_stat_reg) & WRITE_COMPLETE_MASK));
            }
        }

        // 发送停止信号
        // 这里需要根据具体硬件设置寄存器来产生停止信号
        // 示例代码:
        // writel(STOP_BIT_MASK, i2c_con_reg);
    }

    return num;
}

总结

I2C 核心层在接收到应用层的 I2C 操作请求后,会调用 i2c_transfer 函数。i2c_transfer 函数通过 i2c_adapter 结构体获取到对应的 i2c_algorithm 结构体,然后调用其中的 master_xfer 回调函数,最终由适配器驱动的 master_xfer 实现完成与 I2C 控制器硬件的交互,从而完成 I2C 数据的传输。

应用层到I2C核心层调用栈

下面详细介绍应用程序从发起 I2C 操作请求到调用 i2c_transfer 的完整流程以及代码调用栈。

完整流程

1. 应用程序层面

应用程序通常会使用 Linux 系统提供的文件操作接口(如 openioctlreadwrite 等)来与 I2C 设备进行交互。一般使用 ioctl 函数结合 I2C_RDWR 命令来发起一次完整的 I2C 读写操作。

2. 内核层面
  • 设备文件系统:应用程序的系统调用会通过设备文件系统传递到内核中对应的 I2C 设备驱动。
  • I2C 核心层:I2C 核心层负责接收这些请求,并调用 i2c_transfer 函数来完成实际的 I2C 传输。
  • I2C 适配器驱动i2c_transfer 函数会进一步调用 I2C 适配器驱动中实现的 master_xfer 回调函数,该函数负责与具体的 I2C 控制器硬件进行交互,完成数据的读写。

代码调用栈

应用程序代码示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

#define I2C_DEV_PATH "/dev/i2c-0"
#define I2C_SLAVE_ADDR 0x50

int main()
{
    int fd;
    struct i2c_rdwr_ioctl_data msgset;
    struct i2c_msg msgs[2];
    unsigned char buf[2];

    // 打开 I2C 设备文件
    fd = open(I2C_DEV_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open I2C device");
        return -1;
    }

    // 设置从设备地址
    if (ioctl(fd, I2C_SLAVE, I2C_SLAVE_ADDR) < 0) {
        perror("Failed to set I2C slave address");
        close(fd);
        return -1;
    }

    // 准备写入数据
    buf[0] = 0x00; // 寄存器地址
    buf[1] = 0xAA; // 写入的数据
    msgs[0].addr = I2C_SLAVE_ADDR;
    msgs[0].flags = 0;
    msgs[0].len = 2;
    msgs[0].buf = buf;

    // 准备读取数据
    msgs[1].addr = I2C_SLAVE_ADDR;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = 1;
    msgs[1].buf = buf;

    msgset.msgs = msgs;
    msgset.nmsgs = 2;

    // 执行 I2C 传输
    if (ioctl(fd, I2C_RDWR, &msgset) < 0) {
        perror("Failed to perform I2C transfer");
        close(fd);
        return -1;
    }

    // 打印读取的数据
    printf("Read data: 0x%02x\n", buf[0]);

    // 关闭 I2C 设备文件
    close(fd);

    return 0;
}
内核层面代码调用栈
1. ioctl 系统调用

应用程序调用 ioctl(fd, I2C_RDWR, &msgset) 时,会触发内核中的 vfs_ioctl 函数,该函数是内核中处理 ioctl 系统调用的通用函数。

用户空间: main() -> ioctl(fd, I2C_RDWR, &msgset)
内核空间: vfs_ioctl()
2. I2C 设备文件操作

vfs_ioctl 会根据文件描述符 fd 找到对应的 I2C 设备文件操作结构体 file_operations,并调用其中的 unlocked_ioctl 或 compat_ioctl 函数(取决于内核配置)。对于 I2C 设备,这些函数会进一步处理 I2C_RDWR 命令。

内核空间: vfs_ioctl() -> i2c_dev_ioctl()
3. 解析 I2C_RDWR 命令

i2c_dev_ioctl 函数会解析 I2C_RDWR 命令,提取出 i2c_rdwr_ioctl_data 结构体中的 i2c_msg 数组和消息数量,然后调用 i2c_transfer 函数。

内核空间: i2c_dev_ioctl() -> i2c_transfer()
4. 调用 master_xfer

i2c_transfer 函数会通过 i2c_adapter 结构体获取到对应的 i2c_algorithm 结构体,然后调用其中的 master_xfer 回调函数,该函数由 I2C 适配器驱动实现,负责与具体的 I2C 控制器硬件进行交互。

内核空间: i2c_transfer() -> algo->master_xfer()

完整代码调用栈总结

用户空间: main()
    -> ioctl(fd, I2C_RDWR, &msgset)
内核空间: vfs_ioctl()
    -> i2c_dev_ioctl()
        -> i2c_transfer()
            -> algo->master_xfer()

详细代码解释

i2c_dev_ioctl 函数(简化示例)
static long i2c_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct i2c_client *client = file->private_data;
    struct i2c_adapter *adap = client->adapter;
    struct i2c_rdwr_ioctl_data *msgset;
    struct i2c_msg *msgs;
    int ret;

    switch (cmd) {
    case I2C_RDWR:
        msgset = (struct i2c_rdwr_ioctl_data *)arg;
        msgs = msgset->msgs;
        ret = i2c_transfer(adap, msgs, msgset->nmsgs);
        return ret;
    // 其他命令处理...
    default:
        return -ENOTTY;
    }
}

i2c_transfer 函数(简化示例)

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    const struct i2c_algorithm *algo = adap->algo;
    if (algo->master_xfer) {
        return algo->master_xfer(adap, msgs, num);
    } else {
        return -ENOTSUPP;
    }
}

通过以上流程和代码调用栈,应用程序可以通过系统调用发起 I2C 操作请求,最终由 I2C 适配器驱动完成与硬件的交互。

I2C-DEV模块

在 Linux 系统中,I2C-DEV模块是I2C Core的重要组成部分,/dev/i2c-0 这样的设备节点,以及i2c_dev_ioctl()这类供上层调用的API皆在其中实现。

1. I2C 驱动框架概述

Linux 内核的 I2C 驱动框架主要由三部分组成:I2C 核心层、I2C 适配器驱动层和 I2C 设备驱动层。/dev/i2c-0 设备节点的产生主要与 I2C 适配器驱动的注册和设备文件系统的操作有关。

2. I2C 适配器驱动的加载与注册

2.1 设备树描述(可选)

现代 Linux 系统通常使用设备树(Device Tree)来描述硬件设备信息。在设备树文件(一般以 .dts 或 .dtb 为扩展名)中,会对 I2C 控制器进行描述,例如:

i2c0: i2c@12345678 {
    compatible = "vendor,i2c-controller";
    reg = <0x12345678 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    clock-frequency = <100000>;
};

上述代码描述了一个 I2C 控制器,compatible 属性指定了设备的兼容性,reg 属性指定了寄存器的基地址和大小,clock - frequency 指定了 I2C 总线的时钟频率。

2.2 适配器驱动的加载

内核启动时,会根据设备树信息加载相应的 I2C 适配器驱动。适配器驱动通常是一个内核模块,包含了对特定 I2C 控制器硬件的操作函数。例如,对于 ARM 架构的 I2C 控制器,可能有类似以下的驱动代码:

#include <linux/module.h>
#include <linux/i2c.h>

// 适配器操作函数实现
static int my_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    // 实现 I2C 数据传输的具体逻辑
    return num;
}

static u32 my_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C;
}

// 适配器算法结构体
static const struct i2c_algorithm my_i2c_algo = {
    .master_xfer = my_i2c_xfer,
    .functionality = my_i2c_func,
};

// 适配器结构体
static struct i2c_adapter my_i2c_adapter = {
    .owner = THIS_MODULE,
    .class = I2C_CLASS_HWMON,
    .algo = &my_i2c_algo,
    .name = "my_i2c_adapter",
};

// 驱动初始化函数
static int __init my_i2c_init(void)
{
    int ret;
    ret = i2c_add_adapter(&my_i2c_adapter);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add I2C adapter\n");
        return ret;
    }
    printk(KERN_INFO "I2C adapter registered\n");
    return 0;
}

// 驱动退出函数
static void __exit my_i2c_exit(void)
{
    i2c_del_adapter(&my_i2c_adapter);
    printk(KERN_INFO "I2C adapter unregistered\n");
}

module_init(my_i2c_init);
module_exit(my_i2c_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My I2C Adapter Driver");

在上述代码中,my_i2c_init 函数调用 i2c_add_adapter 函数将 my_i2c_adapter 注册到 I2C 核心层。

3. I2C 设备文件系统的处理

3.1 i2c-dev 模块的加载

i2c - dev 是一个内核模块,它为 I2C 适配器提供了用户空间的访问接口。当 i2c - dev 模块加载时,会遍历系统中已注册的 I2C 适配器,并为每个适配器创建一个对应的设备节点。

3.2 设备节点的创建

i2c - dev 模块中的代码会调用 misc_register 或 cdev_add 等函数来创建字符设备节点。例如,i2c - dev 模块中的部分代码如下:

static int __init i2c_dev_init(void)
{
    int res;

    printk(KERN_INFO "i2c /dev entries driver\n");

    res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
    if (res)
        goto out;

    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
    if (IS_ERR(i2c_dev_class)) {
        res = PTR_ERR(i2c_dev_class);
        goto out_unreg_chrdev;
    }

    i2c_register_adapter_notifier(&i2c_dev_notifier);
    i2c_for_each_dev(NULL, i2c_dev_attach_adapter);

    return 0;

out_unreg_chrdev:
    unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
    return res;
}

上述代码中,register_chrdev_region 函数用于分配字符设备的主设备号和次设备号,class_create 函数用于创建一个设备类,i2c_for_each_dev 函数会遍历所有已注册的 I2C 适配器,并调用 i2c_dev_attach_adapter 函数为每个适配器创建设备节点。

3.3 设备节点的命名

对于第一个 I2C 适配器,i2c - dev 模块会创建名为 /dev/i2c - 0 的设备节点;对于第二个 I2C 适配器,会创建 /dev/i2c - 1 节点,依此类推。设备节点的命名规则是 /dev/i2c - <适配器编号>

4. 总结

综上所述,/dev/i2c - 0 设备节点的产生过程如下:

  1. 内核启动时,根据设备树信息加载 I2C 适配器驱动,并将适配器注册到 I2C 核心层。
  2. 加载 i2c - dev 内核模块,该模块遍历已注册的 I2C 适配器。
  3. i2c - dev 模块为每个 I2C 适配器分配主设备号和次设备号,创建设备类,并为每个适配器创建对应的字符设备节点,命名为 /dev/i2c - <适配器编号>

这样,用户空间的应用程序就可以通过操作 /dev/i2c - 0 等设备节点来与 I2C 设备进行通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值