以下是为某ARM架构SoC编写Linux I2C控制器驱动的完整指南,包含架构原理、代码实现和测试方案。所有代码均附带详细注释,可结合数据手册阅读。
Linux I2C控制器驱动开发指南
一、I2C控制器架构原理
1. 硬件架构
+-------------------+ +-----------+ | ARM SoC | | I2C Slave | | +-------------+ | SDA | (如EEPROM)| | | I2C控制寄存器|-----/-------+ | | | 时钟发生器 | | SCL | | | | 中断控制器 |-----/-------+ | | +-------------+ | +-----------+ +-------------------+
关键模块:
-
控制寄存器:配置工作模式、时钟频率、中断使能等
-
FIFO:数据缓冲区(如果有)
-
时钟分频器:根据输入时钟生成SCL频率
-
状态寄存器:传输状态监测
2. 工作流程
-
配置时钟分频器
-
设置目标从机地址
-
将数据写入TX FIFO
-
触发传输启动
-
等待传输完成(轮询或中断)
-
检查状态寄存器确认是否成功
二、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 返回传输结果
七、调试技巧
-
寄存器检查:
devmem2 0x1e000000 # 查看控制寄存器值
-
信号测量:
-
使用示波器检查SCL/SDA波形
-
确认时钟频率是否符合预期
-
-
常见问题处理:
-
无ACK响应:检查从机地址/硬件连接
-
时钟异常:确认分频计算是否正确
-
数据错位:检查字节序设置
-
本驱动已实现基础功能,实际开发中需根据SoC手册补充:
-
FIFO处理
-
DMA传输支持
-
低功耗模式
-
超时/错误重试机制