1. 项目概述
I2C总线作为嵌入式系统中最常用的串行通信协议之一,其重要性不言而喻。但在实际开发中,很多工程师对Linux内核中的I2C子系统理解不够深入,导致开发效率低下、代码耦合度高、维护困难等问题。本文将带你从原理到实战,彻底掌握Linux I2C子系统的核心机制,并实现设备与驱动分离式开发。
我从事嵌入式Linux开发已有8年时间,处理过各种I2C设备驱动问题。在这个过程中,我发现很多开发者对I2C子系统的理解停留在表面,只会照搬示例代码,一旦遇到复杂场景就束手无策。本文将分享我在实际项目中总结的I2C子系统开发经验,特别是如何实现设备与驱动的解耦,让代码更易维护和扩展。
2. I2C子系统核心架构解析
2.1 Linux I2C子系统分层设计
Linux内核中的I2C子系统采用经典的分层架构设计,从上到下主要分为以下几个层次:
-
用户空间接口层:通过/dev/i2c-X字符设备文件提供用户空间访问接口,支持ioctl和read/write操作。这个接口通常用于调试和简单设备控制。
-
I2C核心层:负责I2C总线、设备和驱动的注册与管理,提供核心API供其他层调用。这是整个子系统的中枢。
-
I2C适配器层(也称为I2C控制器驱动层):实现具体SoC或芯片的I2C控制器驱动,负责底层硬件操作。
-
I2C设备驱动层:实现具体I2C设备的驱动逻辑,如传感器、EEPROM等。
这种分层设计的关键优势在于:
- 各层职责明确,耦合度低
- 便于扩展新的I2C控制器和设备
- 用户可以根据需要选择合适层次的API
2.2 设备与驱动分离的关键机制
实现设备与驱动分离主要依赖以下内核机制:
-
设备树(Device Tree):用于描述硬件配置,将设备信息从驱动代码中分离出来。I2C设备通常定义在对应的I2C控制器节点下。
-
platform_device/platform_driver:平台设备和驱动机制,用于非即插即用设备的注册和匹配。
-
i2c_client/i2c_driver:I2C子系统的设备和驱动结构体,通过设备树或ACPI进行匹配。
-
sysfs接口:通过/sys/bus/i2c目录提供设备和驱动的信息查询与配置接口。
分离式开发的核心思想是:设备信息(如I2C地址、寄存器配置等)由设备树描述,驱动代码只关注设备的功能实现,不包含具体的硬件配置信息。这样同一驱动可以支持不同配置的同类设备。
3. I2C设备驱动开发实战
3.1 环境准备与基础配置
在开始开发前,需要确保内核已正确配置I2C子系统支持:
bash复制# 检查内核配置
zcat /proc/config.gz | grep I2C
# 或
make menuconfig # 进入配置界面
关键配置选项:
- CONFIG_I2C:启用I2C子系统支持
- CONFIG_I2C_CHARDEV:启用用户空间I2C接口
- CONFIG_I2C_DEBUG_CORE:调试支持(可选)
对于嵌入式开发,还需要正确配置设备树。以常见的i.MX6平台为例:
dts复制&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
/* 示例:添加一个I2C温度传感器 */
temp_sensor: lm75@48 {
compatible = "national,lm75";
reg = <0x48>;
};
};
3.2 实现设备与驱动分离的关键步骤
3.2.1 定义设备树节点
首先在设备树中定义I2C设备节点。以常见的AT24C系列EEPROM为例:
dts复制&i2c2 {
eeprom: at24c512@50 {
compatible = "atmel,24c512";
reg = <0x50>;
pagesize = <128>;
};
};
关键属性说明:
- compatible:用于匹配驱动程序的字符串
- reg:设备的I2C地址
- pagesize:设备特定的参数(页大小)
3.2.2 编写I2C设备驱动
驱动代码主要实现i2c_driver结构体和对应的probe/remove函数:
c复制#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#define DRIVER_NAME "at24c512"
static int at24c512_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
dev_info(dev, "AT24C512 EEPROM detected at address 0x%02x\n",
client->addr);
// 初始化设备相关资源
// ...
return 0;
}
static int at24c512_remove(struct i2c_client *client)
{
// 清理资源
// ...
return 0;
}
static const struct of_device_id at24c512_of_match[] = {
{ .compatible = "atmel,24c512" },
{ }
};
MODULE_DEVICE_TABLE(of, at24c512_of_match);
static struct i2c_driver at24c512_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = at24c512_of_match,
},
.probe = at24c512_probe,
.remove = at24c512_remove,
};
module_i2c_driver(at24c512_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("AT24C512 EEPROM Driver");
3.2.3 实现设备操作接口
为了提供用户空间访问接口,通常还需要实现file_operations或使用sysfs:
c复制static ssize_t eeprom_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct i2c_client *client = filp->private_data;
u8 reg_addr = *f_pos;
u8 *tmp_buf;
int ret;
tmp_buf = kmalloc(count + 1, GFP_KERNEL);
if (!tmp_buf)
return -ENOMEM;
tmp_buf[0] = reg_addr;
ret = i2c_master_send(client, tmp_buf, 1);
if (ret < 0)
goto out;
ret = i2c_master_recv(client, tmp_buf, count);
if (ret < 0)
goto out;
if (copy_to_user(buf, tmp_buf, count)) {
ret = -EFAULT;
goto out;
}
*f_pos += count;
ret = count;
out:
kfree(tmp_buf);
return ret;
}
static const struct file_operations eeprom_fops = {
.owner = THIS_MODULE,
.read = eeprom_read,
// 实现其他操作...
};
3.3 调试与测试技巧
3.3.1 用户空间I2C工具
Linux提供了i2c-tools工具包,可用于快速测试I2C设备:
bash复制# 扫描I2C总线上的设备
i2cdetect -y 1
# 读取设备寄存器
i2cget -y 1 0x50 0x00
# 写入设备寄存器
i2cset -y 1 0x50 0x00 0x12
3.3.2 内核调试技巧
- 启用I2C调试信息:
bash复制echo 1 > /sys/module/i2c_core/parameters/debug
- 使用dev_dbg输出调试信息:
c复制dev_dbg(&client->dev, "Register 0x%02x value: 0x%02x\n", reg, val);
- 通过sysfs查看I2C设备信息:
bash复制ls /sys/bus/i2c/devices/
cat /sys/bus/i2c/devices/i2c-1/name
4. 高级话题与性能优化
4.1 I2C传输模式选择
I2C支持多种传输模式,合理选择可以提升性能:
- 标准模式(Standard Mode):100 kHz
- 快速模式(Fast Mode):400 kHz
- 快速模式+(Fast Mode Plus):1 MHz
- 高速模式(High Speed Mode):3.4 MHz
在设备树中指定时钟频率:
dts复制&i2c1 {
clock-frequency = <400000>; // 400 kHz
};
4.2 I2C多设备管理
当一条I2C总线上挂载多个设备时,需要注意:
- 确保每个设备有唯一的I2C地址
- 考虑总线负载能力,可能需要使用I2C缓冲器
- 合理设置重试次数和超时时间
c复制static struct i2c_adapter *adapter;
static struct i2c_client *client1, *client2;
adapter = i2c_get_adapter(1); // 获取I2C1适配器
// 动态创建多个I2C客户端
client1 = i2c_new_client_device(adapter, &board_info1);
client2 = i2c_new_client_device(adapter, &board_info2);
4.3 电源管理与唤醒机制
对于低功耗设备,需要实现电源管理:
c复制static int at24c512_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
// 进入低功耗模式
i2c_smbus_write_byte_data(client, REG_POWER, POWER_DOWN);
return 0;
}
static int at24c512_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
// 恢复正常工作模式
i2c_smbus_write_byte_data(client, REG_POWER, POWER_UP);
return 0;
}
static const struct dev_pm_ops at24c512_pm_ops = {
.suspend = at24c512_suspend,
.resume = at24c512_resume,
};
static struct i2c_driver at24c512_driver = {
.driver = {
.pm = &at24c512_pm_ops,
},
};
5. 常见问题与解决方案
5.1 I2C通信失败排查步骤
-
检查物理连接:
- 确认SDA/SCL线连接正确
- 检查上拉电阻是否合适(通常4.7kΩ)
- 测量信号波形是否正常
-
内核层面检查:
bash复制dmesg | grep i2c cat /sys/kernel/debug/gpio # 检查GPIO复用是否正确 -
设备树检查:
bash复制
dtc -I fs /proc/device-tree | less
5.2 典型错误与修复
-
Probe函数未被调用:
- 检查compatible字符串是否匹配
- 确认设备树节点状态为"okay"
- 检查I2C控制器驱动是否加载
-
I2C传输超时:
c复制// 调整超时时间 client->adapter->timeout = 500; // ms client->adapter->retries = 3; -
SMBus兼容性问题:
- 优先使用i2c_smbus_* API
- 对于不支持SMBus的设备,使用i2c_master_send/receive
5.3 性能优化技巧
-
批量读写优化:
c复制// 单次传输多个字节 i2c_smbus_read_i2c_block_data(client, reg, len, buf); i2c_smbus_write_i2c_block_data(client, reg, len, buf); -
减少重复初始化:
- 缓存常用寄存器值
- 实现寄存器位操作函数
-
中断驱动设计:
c复制// 注册中断处理函数 devm_request_threaded_irq(&client->dev, client->irq, NULL, at24c512_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "at24c512", private_data);
6. 实战案例:多功能传感器驱动开发
6.1 复合设备驱动设计
对于集成多个功能的传感器(如温湿度+气压),推荐采用以下设计模式:
c复制struct multi_sensor_data {
struct i2c_client *client;
struct mutex lock;
struct device *hwmon_dev;
struct delayed_work work;
};
static int multi_sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct multi_sensor_data *data;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
// 初始化各传感器
init_temperature_sensor(client);
init_humidity_sensor(client);
// 设置定期轮询
INIT_DELAYED_WORK(&data->work, sensor_polling_work);
schedule_delayed_work(&data->work, msecs_to_jiffies(1000));
return 0;
}
6.2 实现sysfs接口
为用户空间提供方便的访问接口:
c复制static ssize_t temp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct multi_sensor_data *data = dev_get_drvdata(dev);
int temp;
mutex_lock(&data->lock);
temp = read_temperature(data->client);
mutex_unlock(&data->lock);
return sprintf(buf, "%d\n", temp);
}
static DEVICE_ATTR_RO(temp);
static struct attribute *multi_sensor_attrs[] = {
&dev_attr_temp.attr,
NULL
};
static const struct attribute_group multi_sensor_group = {
.attrs = multi_sensor_attrs,
};
static int multi_sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
// ...
sysfs_create_group(&client->dev.kobj, &multi_sensor_group);
// ...
}
6.3 设备树配置示例
dts复制&i2c1 {
multi_sensor: bme680@76 {
compatible = "bosch,bme680";
reg = <0x76>;
interrupt-parent = <&gpio3>;
interrupts = <21 IRQ_TYPE_EDGE_FALLING>;
bosch,oversample-temp = <8>;
bosch,oversample-hum = <2>;
bosch,oversample-press = <4>;
};
};
7. 进阶话题:动态加载与热插拔支持
7.1 动态设备添加与移除
c复制// 动态添加设备
struct i2c_board_info board_info = {
I2C_BOARD_INFO("at24c256", 0x50),
};
struct i2c_client *client = i2c_new_device(adapter, &board_info);
// 动态移除设备
i2c_unregister_device(client);
7.2 I2C多路复用器支持
对于I2C多路复用器(如PCA954x系列),需要特殊处理:
dts复制i2c-mux@70 {
compatible = "nxp,pca9548";
reg = <0x70>;
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
i2c@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
};
7.3 用户空间驱动开发
对于快速原型开发,可以在用户空间实现I2C驱动:
c复制#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int file;
char filename[20];
unsigned char buf[10];
snprintf(filename, 19, "/dev/i2c-%d", 1);
file = open(filename, O_RDWR);
if (file < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
int addr = 0x50;
if (ioctl(file, I2C_SLAVE, addr) < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
/* Using SMBus commands */
buf[0] = 0x00; // Register address
if (write(file, buf, 1) != 1) {
/* ERROR HANDLING: i2c transaction failed */
}
if (read(file, buf, 2) != 2) {
/* ERROR HANDLING: i2c transaction failed */
} else {
printf("Data: %02x %02x\n", buf[0], buf[1]);
}
close(file);
return 0;
}
8. 测试与验证策略
8.1 单元测试框架
使用内核的KUnit框架进行驱动单元测试:
c复制#include <kunit/test.h>
static void test_i2c_read_reg(struct kunit *test)
{
struct i2c_client *mock_client;
int ret;
mock_client = i2c_new_dummy_device(NULL, 0x50);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mock_client);
ret = i2c_smbus_read_byte_data(mock_client, 0x00);
KUNIT_EXPECT_EQ(test, ret, 0xAB);
i2c_unregister_device(mock_client);
}
static struct kunit_case i2c_test_cases[] = {
KUNIT_CASE(test_i2c_read_reg),
{}
};
static struct kunit_suite i2c_test_suite = {
.name = "i2c-driver-tests",
.test_cases = i2c_test_cases,
};
kunit_test_suite(i2c_test_suite);
8.2 硬件在环测试
构建自动化测试系统:
- 使用I2C控制器模拟器(如Total Phase Aardvark)
- 开发Python测试脚本:
python复制import pyaardvark
# 配置Aardvark适配器
aa = pyaardvark.aardvark_open(0)
pyaardvark.aardvark_configure(aa, pyaardvark.AA_CONFIG_SPI_I2C)
# 设置I2C模式
pyaardvark.aardvark_i2c_slave_enable(
aa, 0x50, 0, 0
)
# 模拟设备响应
def callback(data):
if data[0] == 0x00: # 寄存器地址
return bytes([0x12, 0x34]) # 模拟返回值
return bytes([0x00])
pyaardvark.aardvark_i2c_slave_set_response(
aa, callback
)
8.3 性能测试与优化
使用内核性能分析工具:
bash复制# 记录I2C传输耗时
perf probe -a 'i2c_transfer'
perf stat -e 'probe:i2c_transfer*' -a sleep 10
# 生成火焰图分析性能瓶颈
perf record -g -a -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > i2c.svg
9. 维护与升级策略
9.1 驱动版本管理
- 使用内核的版本宏:
c复制#define DRIVER_VERSION "1.0.2"
MODULE_VERSION(DRIVER_VERSION);
- 通过sysfs查看驱动版本:
bash复制cat /sys/module/your_driver/version
9.2 兼容性处理
处理不同硬件版本或厂商变种:
c复制static int sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct of_device_id *match;
struct device *dev = &client->dev;
match = of_match_device(sensor_of_match, dev);
if (!match)
return -ENODEV;
switch ((uintptr_t)match->data) {
case MODEL_A:
// 处理A型号
break;
case MODEL_B:
// 处理B型号
break;
default:
return -ENODEV;
}
}
9.3 贡献上游内核
- 遵循内核编码风格:
bash复制./scripts/checkpatch.pl --file your_driver.c
- 编写完整的文档:
text复制Documentation/i2c/your-driver.txt
- 提交补丁:
bash复制git format-patch -1
./scripts/get_maintainer.pl your_driver.c
10. 实战经验与技巧分享
10.1 调试I2C通信问题
-
逻辑分析仪抓包:
- 使用Saleae Logic或PulseView分析I2C波形
- 检查START/STOP条件、ACK/NACK响应
-
内核调试技巧:
c复制// 在关键位置添加调试打印
dev_dbg(&client->dev, "Transfer %d messages\n", num);
// 启用I2C核心调试
echo 0xff > /sys/module/i2c_core/parameters/debug
- 模拟设备测试:
python复制# 使用Python smbus2模拟设备
from smbus2 import SMBus, i2c_msg
with SMBus(1) as bus:
# 模拟设备响应
def read_callback(addr, cmd, length):
if cmd == 0x00:
return [0x12, 0x34]
return [0x00]*length
bus.enable_pec = True
bus.set_i2c_callback(read_callback)
10.2 电源管理实战
- 运行时电源管理:
c复制static int sensor_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
// 进入低功耗模式
i2c_smbus_write_byte_data(client, REG_POWER, 0x01);
regulator_disable(data->vdd);
return 0;
}
static int sensor_runtime_resume(struct device *dev)
{
// 恢复供电
regulator_enable(data->vdd);
usleep_range(1000, 2000); // 等待电源稳定
// 初始化设备
i2c_smbus_write_byte_data(client, REG_CONFIG, 0x00);
return 0;
}
static const struct dev_pm_ops sensor_pm_ops = {
SET_RUNTIME_PM_OPS(sensor_runtime_suspend,
sensor_runtime_resume,
NULL)
};
10.3 多线程安全处理
- 互斥锁保护共享资源:
c复制struct sensor_data {
struct mutex lock;
int calibration_data;
};
static ssize_t calibrate_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct sensor_data *data = dev_get_drvdata(dev);
int val;
if (kstrtoint(buf, 0, &val))
return -EINVAL;
mutex_lock(&data->lock);
data->calibration_data = val;
update_calibration(data->client, val);
mutex_unlock(&data->lock);
return count;
}
- 工作队列处理耗时操作:
c复制static void sensor_work_handler(struct work_struct *work)
{
struct sensor_data *data = container_of(work,
struct sensor_data,
work.work);
mutex_lock(&data->lock);
int val = read_sensor_value(data->client);
data->last_value = val;
mutex_unlock(&data->lock);
// 重新调度
schedule_delayed_work(&data->work,
msecs_to_jiffies(INTERVAL_MS));
}
10.4 固件升级支持
实现I2C设备的固件升级功能:
c复制static int update_firmware(struct i2c_client *client,
const struct firmware *fw)
{
int ret;
u8 buf[32];
size_t offset = 0;
// 进入bootloader模式
ret = i2c_smbus_write_byte_data(client, REG_BOOT, 0x01);
if (ret < 0)
return ret;
msleep(100); // 等待设备重启
// 分段写入固件
while (offset < fw->size) {
size_t chunk = min(sizeof(buf), fw->size - offset);
memcpy(buf, fw->data + offset, chunk);
ret = i2c_master_send(client, buf, chunk);
if (ret < 0)
return ret;
offset += chunk;
}
// 验证固件
ret = i2c_smbus_write_byte_data(client, REG_VERIFY, 0x01);
if (ret < 0)
return ret;
// 退出bootloader模式
return i2c_smbus_write_byte_data(client, REG_BOOT, 0x00);
}
11. 工具链与开发环境
11.1 推荐开发工具
-
硬件工具:
- 逻辑分析仪(Saleae Logic Pro 16)
- I2C协议分析仪(Total Phase Aardvark)
- 多通道示波器
-
软件工具:
- Linux内核源码(含I2C子系统)
- i2c-tools工具包
- Python smbus2库(用于快速原型开发)
-
调试工具:
- J-Link调试器(配合OpenOCD)
- kgdb内核调试
- Trace32(商业调试工具)
11.2 交叉编译环境配置
嵌入式开发通常需要配置交叉编译工具链:
bash复制# 安装交叉编译工具链
sudo apt install gcc-arm-linux-gnueabihf
# 编译驱动模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C /path/to/kernel M=$(pwd) modules
# 编译设备树
dtc -I dts -O dtb -o my-board.dtb my-board.dts
11.3 持续集成方案
使用GitLab CI自动化构建和测试:
yaml复制stages:
- build
- test
build_driver:
stage: build
script:
- make ARCH=arm CROSS_COMPILE=${CROSS_COMPILE} -C ${KERNEL_DIR} M=${CI_PROJECT_DIR} modules
artifacts:
paths:
- *.ko
test_on_target:
stage: test
script:
- scp *.ko target:/tmp/
- ssh target "insmod /tmp/your_driver.ko && dmesg | grep your_driver"
needs:
- build_driver
12. 安全与可靠性设计
12.1 I2C通信安全
- 数据校验:
- 使用CRC校验重要数据
- 实现重传机制
c复制static int i2c_smbus_read_word_crc(struct i2c_client *client,
u8 command, u16 *value)
{
u8 buf[4]; // command + data + crc
int ret;
ret = i2c_smbus_read_i2c_block_data(client, command,
sizeof(buf), buf);
if (ret != sizeof(buf))
return -EIO;
if (crc8(buf, 3, 0x07) != buf[3]) {
dev_err(&client->dev, "CRC mismatch\n");
return -EIO;
}
*value = (buf[1] << 8) | buf[2];
return 0;
}
12.2 错误恢复机制
实现健壮的错误处理:
c复制static int sensor_read_with_retry(struct i2c_client *client,
u8 reg, u8 *value, int max_retries)
{
int ret, retries = 0;
do {
ret = i2c_smbus_read_byte_data(client, reg);
if (ret >= 0) {
*value = ret;
return 0;
}
msleep(10);
retries++;
} while (retries < max_retries);
return ret;
}
12.3 看门狗与健康监测
c复制static void sensor_watchdog_work(struct work_struct *work)
{
struct sensor_data *data = container_of(work,
struct sensor_data,
watchdog_work.work);
if (!i2c_check_functionality(data->client->adapter,
I2C_FUNC_SMBUS_READ_BYTE)) {
dev_err(&data->client->dev, "Adapter lost functionality\n");
return;
}
// 检查设备响应
int ret = i2c_smbus_read_byte(data->client);
if (ret < 0) {
dev_err(&data->client->dev, "Device not responding\n");
// 尝试复位设备
reset_device(data->client);
}
// 重新调度
schedule_delayed_work(&data->watchdog_work,
msecs_to_jiffies(WATCHDOG_INTERVAL));
}
13. 性能调优实战
13.1 I2C总线负载优化
-
减少总线占用时间:
- 合并多个寄存器读写
- 使用块传输代替单字节传输
-
优化传输模式:
c复制// 使用复合消息减少START/STOP条件
struct i2c_msg msg[2];
u8 reg_addr = 0x10;
u8 buf[16];
msg[0].addr = client->addr;
msg[0].flags = 0; // 写
msg[0].len = 1;
msg[0].buf = ®_addr;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; // 读
msg[1].len = sizeof(buf);
msg[1].buf = buf;
ret = i2c_transfer(client->adapter, msg, 2);
13.2 中断驱动优化
对于支持中断的设备:
c复制static irqreturn_t sensor_irq_handler(int irq, void *dev_id)
{
struct sensor_data *data = dev_id;
// 读取中断状态寄存器
int status = i2c_smbus_read_byte_data(data->client,
REG_INTERRUPT_STATUS);
if (status & DATA_READY_MASK) {
// 读取数据
int value = i2c_smbus_read_word_data(data->client,
REG_DATA);
// 推送到输入子系统或其它处理
input_report_abs(data->input, ABS_X, value);
input_sync(data->input);
}
return IRQ_HANDLED;
}
13.3 DMA传输支持
对于大量数据传输,考虑使用DMA:
c复制static int sensor_read_dma(struct i2c_client *client,
u8 reg, void *buf, size_t len)
{
struct i2c_msg msg[2];
dma_addr_t dma_addr;
u8 *dma_buf;
dma_buf = dma_alloc_coherent(&client->dev, len + 1,
&dma_addr, GFP_KERNEL);
if (!dma_buf)
return -ENOMEM;
dma_buf[0] = reg;
msg[0].addr = client->addr;
msg[0].flags = 0; // 写
msg[0].len = 1;
msg[0].buf = dma_buf;
msg[0].flags |= I2C_M_DMA_SAFE;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; // 读
msg[1].len = len;
msg[1].buf = dma_buf + 1;
msg[1].flags |= I2C_M_DMA_SAFE;
int ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2) {
memcpy(buf, dma_buf + 1, len);
ret = 0;
} else {
ret = -EIO;
}
dma_free_coherent(&client->dev, len + 1, dma_buf, dma_addr);
return ret;
}
14. 兼容性与可移植性
14.1 多平台支持策略
- 使用设备树抽象硬件差异:
dts复制sensor@48 {
compatible = "vendor,sensor";
reg = <0x48>;
vdd-supply = <&sensor_vdd>;
interrupts-extended = <&gpio1 15 IRQ_TYPE_EDGE_FALLING>;
vendor,config-param = <3>;
};
- 平台特定代码隔离:
c复制#ifdef CONFIG_ARCH_IMX6
static int platform_specific_init(struct i2c_client *client)
{
// i.MX6特定初始化
}
#elif defined(CONFIG_ARCH_BCM2835)
static int platform_specific_init(struct i2c_client *client)
{
// Raspberry Pi特定初始化
}
#endif
14.2 内核版本兼容
处理不同内核版本的API变化:
c复制#if LINUX_VERSION_CODE < KERNEL_VERSION(5,3,0)
static inline void *devm_platform_ioremap_resource(struct platform_device *pdev,
unsigned int index)
{