linux I2C驱动详解(2)-控制器驱动

以下是为某ARM架构SoC编写Linux I2C控制器驱动的完整指南,包含架构原理、代码实现和测试方案。所有代码均附带详细注释,可结合数据手册阅读。


Linux I2C控制器驱动开发指南

一、I2C控制器架构原理

1. 硬件架构

+-------------------+          +-----------+
|  ARM SoC          |          | I2C Slave |
|  +-------------+  | SDA      | (如EEPROM)|
|  | I2C控制寄存器|-----/-------+           |
|  | 时钟发生器   |  | SCL      |           |
|  | 中断控制器   |-----/-------+           |
|  +-------------+  |          +-----------+
+-------------------+

关键模块:

  • 控制寄存器:配置工作模式、时钟频率、中断使能等

  • FIFO:数据缓冲区(如果有)

  • 时钟分频器:根据输入时钟生成SCL频率

  • 状态寄存器:传输状态监测

2. 工作流程

  1. 配置时钟分频器

  2. 设置目标从机地址

  3. 将数据写入TX FIFO

  4. 触发传输启动

  5. 等待传输完成(轮询或中断)

  6. 检查状态寄存器确认是否成功


二、Linux I2C驱动架构

1. 内核I2C子系统层次

+-----------------------+
|   User Application    | (e.g. 通过sysfs或IIO接口读取数据)
+-----------------------+
|   Linux Kernel        |
|   - IIO Subsystem     | (工业IO子系统,提供标准传感器接口)
+- - - - - - - - - - - -+
|   - I2C Core          | (处理I2C总线通信,为控制器、设备驱动及应用层提供标准接口)
+- - - - - - - - - - - -+
|   - I2C Adapter Driver| (I2C控制器驱动,连接I2C设备)
+- - - - - - - - - - - -+
|   - I2C Device Driver | (如ICM-4265驱动,实现probe/read/init等操作)
+- - - - - - - - - - - -+
|   Hardware Layer      | (I2C物理连接)
+-----------------------+

2. 关键数据结构

struct i2c_adapter {        // 描述I2C控制器
    const struct i2c_algorithm *algo; // 核心操作集
};

struct i2c_algorithm {      // 定义控制器能力
    int (*master_xfer)(...); // 主模式传输函数
    u32 (*functionality)(...); // 支持的功能
};

三、驱动开发步骤

1. 设备树配置 (以OpenWRT为例)

// soc.dtsi
i2c_controller: i2c@1e000000 {
    compatible = "your_company,arm-i2c";
    reg = <0x1e000000 0x1000>;  // 寄存器物理地址和长度
    interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&i2c_clk>;
    clock-frequency = <100000>; // 默认速率100kHz
    #address-cells = <1>;
    #size-cells = <0>;
};

2. 驱动代码实现 (i2c_arm_soc.c)

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/io.h>
#include <linux/clk.h>

// 寄存器偏移量定义(需根据实际手册修改)
#define I2C_CTRL_REG   0x00
#define I2C_STAT_REG   0x04
#define I2C_DATA_REG   0x08
#define I2C_CLK_REG    0x0C

struct arm_i2c_dev {
    void __iomem *base;      // 寄存器基地址
    struct clk *clk;         // 时钟
    struct i2c_adapter adap; // I2C适配器
    struct completion completion; // 传输完成通知
};

/* 关键寄存器操作 */
static void i2c_set_clk(struct arm_i2c_dev *dev, u32 freq) {
    u32 clk_div = clk_get_rate(dev->clk) / (2 * freq) - 1;
    writel(clk_div, dev->base + I2C_CLK_REG);
}

static void i2c_enable(struct arm_i2c_dev *dev) {
    u32 ctrl = readl(dev->base + I2C_CTRL_REG);
    writel(ctrl | 0x1, dev->base + I2C_CTRL_REG);
}

/* 核心传输函数 */
static int arm_i2c_xfer(struct i2c_adapter *adap, 
                        struct i2c_msg *msgs, int num) {
    struct arm_i2c_dev *dev = i2c_get_adapdata(adap);
    struct i2c_msg *msg = msgs;
    int ret = 0;

    // 启动传输
    i2c_enable(dev);

    for (int i = 0; i < num; i++) {
        // 设置从机地址
        writel((msg->addr << 1) | (msg->flags & I2C_M_RD ? 1 : 0),
               dev->base + I2C_ADDR_REG);

        // 处理数据方向
        if (msg->flags & I2C_M_RD) {
            // 读模式处理
            for (int j = 0; j < msg->len; j++) {
                msg->buf[j] = readl(dev->base + I2C_DATA_REG);
            }
        } else {
            // 写模式处理
            for (int j = 0; j < msg->len; j++) {
                writel(msg->buf[j], dev->base + I2C_DATA_REG);
            }
        }

        // 等待传输完成(示例用忙等待,实际应用中断更好)
        while (!(readl(dev->base + I2C_STAT_REG) & 0x1));

        // 检查状态
        if (readl(dev->base + I2C_STAT_REG) & 0x2) {
            dev_err(dev->dev, "传输错误\n");
            ret = -EIO;
            break;
        }

        msg++;
    }

    return ret;
}

/* 实现algorithm接口 */
static u32 arm_i2c_func(struct i2c_adapter *adap) {
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm arm_i2c_algo = {
    .master_xfer = arm_i2c_xfer,
    .functionality = arm_i2c_func,
};

/* 平台驱动probe函数 */
static int arm_i2c_probe(struct platform_device *pdev) {
    struct arm_i2c_dev *dev;
    struct resource *res;
    int irq, ret;

    // 分配设备结构体
    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev) return -ENOMEM;

    // 映射寄存器
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    dev->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dev->base)) return PTR_ERR(dev->base);

    // 获取时钟
    dev->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(dev->clk)) return PTR_ERR(dev->clk);
    clk_prepare_enable(dev->clk);

    // 初始化适配器
    dev->adap.owner = THIS_MODULE;
    dev->adap.algo = &arm_i2c_algo;
    dev->adap.dev.of_node = pdev->dev.of_node;
    snprintf(dev->adap.name, sizeof(dev->adap.name),
             "ARM I2C adapter at %pa", &res->start);

    // 设置适配器数据
    i2c_set_adapdata(&dev->adap, dev);

    // 注册适配器
    ret = i2c_add_adapter(&dev->adap);
    if (ret) {
        clk_disable_unprepare(dev->clk);
        return ret;
    }

    platform_set_drvdata(pdev, dev);
    return 0;
}

/* 平台驱动定义 */
static const struct of_device_id arm_i2c_of_match[] = {
    { .compatible = "your_company,arm-i2c" },
    {},
};
MODULE_DEVICE_TABLE(of, arm_i2c_of_match);

static struct platform_driver arm_i2c_driver = {
    .probe = arm_i2c_probe,
    .driver = {
        .name = "arm-i2c",
        .of_match_table = arm_i2c_of_match,
    },
};
module_platform_driver(arm_i2c_driver);    //控制器驱动直接注册为平台总线驱动

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ARM I2C Controller Driver");
MODULE_LICENSE("GPL");

四、测试方案

1. 使用i2c-tools验证

# 扫描I2C总线(假设控制器编号为0)
i2cdetect -y 0

# 读写测试(假设EEPROM地址0x50)
i2cset -y 0 0x50 0x00 0x12  # 写操作
i2cget -y 0 0x50 0x00       # 读操作

2. 自定义测试程序 (test_i2c.c)

#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main() {
    int file;
    char filename[20];
    unsigned char addr = 0x50; // 测试从机地址
    unsigned char buf[2];

    snprintf(filename, 19, "/dev/i2c-0");
    file = open(filename, O_RDWR);
    if (file < 0) { /* 错误处理 */ }

    if (ioctl(file, I2C_SLAVE, addr) < 0) { /* 错误处理 */ }

    // 写后读测试
    buf[0] = 0x00; // 寄存器地址
    buf[1] = 0x55; // 测试数据
    if (write(file, buf, 2) != 2) { /* 错误处理 */ }

    if (read(file, buf, 1) != 1) { /* 错误处理 */ }
    printf("Read value: 0x%02x\n", buf[0]);

    close(file);
    return 0;
}

五、编译与部署

1. 驱动Makefile

obj-m += i2c_arm_soc.o

all:
    make -C $(KDIR) M=$(PWD) modules

clean:
    make -C $(KDIR) M=$(PWD) clean

2. OpenWRT编译命令

export KDIR=/path/to/openwrt/build_dir/target-arm_cortex-a7+neon_.../linux-.../
make ARCH=arm CROSS_COMPILE=arm-openwrt-linux-

3. 部署测试

# 加载驱动
insmod i2c_arm_soc.ko

# 查看注册的I2C控制器
ls /sys/bus/i2c/devices/

# 运行测试程序
./test_i2c

六、流程图

驱动加载流程:
+-----------------+
| platform_driver | 注册平台驱动
+-----------------+
        |
        | 设备树匹配
        v
+-----------------+
|   probe函数     | → 映射寄存器
+-----------------+ → 初始化时钟
        |           → 注册i2c_adapter
        v
I2C控制器就绪

数据传输流程:
+-----------------+
| i2c_transfer    | 用户发起请求
+-----------------+
        |
        v
+-----------------+
| master_xfer     | → 配置寄存器
+-----------------+ → 写TX数据
        |           → 触发传输
        v
+-----------------+
| 中断/轮询       | 等待完成
+-----------------+
        |
        v
返回传输结果

七、调试技巧

  1. 寄存器检查

    devmem2 0x1e000000  # 查看控制寄存器值

  2. 信号测量

    • 使用示波器检查SCL/SDA波形

    • 确认时钟频率是否符合预期

  3. 常见问题处理

    • 无ACK响应:检查从机地址/硬件连接

    • 时钟异常:确认分频计算是否正确

    • 数据错位:检查字节序设置

本驱动已实现基础功能,实际开发中需根据SoC手册补充:

  1. FIFO处理

  2. DMA传输支持

  3. 低功耗模式

  4. 超时/错误重试机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值