Linux 内核驱动加载机制
一、概述
Linux 内核采用分层的设备驱动模型,通过总线(Bus)、设备(Device)和驱动(Driver)三者的匹配机制来实现硬件的管理和控制。本文将详细介绍驱动加载流程、Platform 总线机制、以及 Input 和 IIO 子系统的工作原理。
二、Linux 设备驱动模型基础
2.1 核心概念
Linux 设备驱动模型基于以下三个核心组件:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Device │───────▶│ Bus │◀───────│ Driver │
│ (设备) │ 注册 │ (总线) │ 注册 │ (驱动) │
└──────────┘ └──────────┘ └──────────┘
│
▼
匹配机制
(match/probe)
关键数据结构:
c
// 设备结构
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name;
const struct device_type *type;
struct bus_type *bus; // 所属总线
struct device_driver *driver; // 匹配的驱动
void *platform_data;
void *driver_data;
// ...
};
// 驱动结构
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const struct of_device_id *of_match_table; // 设备树匹配表
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
// ...
};
// 总线结构
struct bus_type {
const char *name;
struct device *dev_root;
int (*match)(struct device *dev, struct device_driver *drv);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
// ...
};
2.2 驱动加载流程
是 否 模块初始化 module_init 驱动注册 driver_register 加入总线驱动链表 遍历总线上的设备 match 匹配? 调用 probe 函数 设备初始化 创建设备节点 驱动加载完成
代码示例:
c
// 驱动模块初始化
static int __init my_driver_init(void)
{
printk(KERN_INFO "My driver init\n");
return platform_driver_register(&my_platform_driver);
}
module_init(my_driver_init);
// 驱动退出
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_platform_driver);
printk(KERN_INFO "My driver exit\n");
}
module_exit(my_driver_exit);
三、Platform 总线机制
3.1 Platform 总线简介
Platform 总线是一种虚拟总线,用于管理集成在 SoC 内部的设备(如 GPIO、I2C 控制器、SPI 控制器等),这些设备无法通过标准总线(如 PCI、USB)自动发现。
特点:
- 不依赖物理总线
- 设备信息通过设备树(DTS)或平台数据传递
- 自动匹配设备和驱动
3.2 Platform 设备与驱动
3.2.1 Platform 设备
c
struct platform_device {
const char *name; // 设备名称
int id; // 设备 ID
struct device dev; // 内嵌的 device 结构
u32 num_resources; // 资源数量
struct resource *resource; // 资源数组(内存、中断等)
const struct platform_device_id *id_entry;
// ...
};
// 资源定义
struct resource {
resource_size_t start; // 起始地址
resource_size_t end; // 结束地址
const char *name;
unsigned long flags; // IORESOURCE_MEM, IORESOURCE_IRQ 等
};
3.2.2 Platform 驱动
c
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
3.3 完整示例
3.3.1 设备树定义 (DTS)
dts
// 设备树节点
my_device: my_device@40010000 {
compatible = "vendor,my-device";
reg = <0x40010000 0x1000>;
interrupts = <0 25 4>;
clocks = <&clk_gate 10>;
status = "okay";
};
3.3.2 驱动实现
c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
// 匹配表 - 通过 compatible 匹配
static const struct of_device_id my_device_of_match[] = {
{ .compatible = "vendor,my-device", },
{ },
};
MODULE_DEVICE_TABLE(of, my_device_of_match);
// probe 函数 - 设备匹配成功后调用
static int my_device_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
printk(KERN_INFO "My device probe\n");
// 获取内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
}
// 映射寄存器地址
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return irq;
}
// 设备特定初始化
// ...
dev_info(&pdev->dev, "Device probed successfully, IRQ=%d\n", irq);
return 0;
}
// remove 函数
static int my_device_remove(struct platform_device *pdev)
{
printk(KERN_INFO "My device remove\n");
// 清理资源
return 0;
}
// Platform 驱动结构
static struct platform_driver my_platform_driver = {
.probe = my_device_probe,
.remove = my_device_remove,
.driver = {
.name = "my-device",
.of_match_table = my_device_of_match,
},
};
// 模块初始化和退出
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My Platform Device Driver");
3.4 Platform 总线匹配机制
c
// platform 总线的 match 函数
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
// 1. 优先通过设备树 compatible 匹配
if (of_driver_match_device(dev, drv))
return 1;
// 2. 通过 ACPI 匹配
if (acpi_driver_match_device(dev, drv))
return 1;
// 3. 通过 platform_device_id 匹配
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
// 4. 通过设备名称匹配
return (strcmp(pdev->name, drv->name) == 0);
}
匹配优先级:
- 设备树
compatible属性 - ACPI 匹配
platform_device_id表- 设备名称
四、Input 子系统
4.1 Input 子系统架构
┌─────────────────────────────────────────────┐
│ User Space (用户空间) │
│ /dev/input/event0 /dev/input/mouse0 etc. │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Event Handler (事件处理层) │
│ evdev mousedev joydev tsdev │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Input Core (输入核心层) │
│ 事件分发、设备注册、事件过滤 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Device Driver (设备驱动层) │
│ keyboard mouse touchscreen sensor etc. │
└─────────────────────────────────────────────┘
4.2 Input 设备注册
4.2.1 核心数据结构
c
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 支持的事件类型
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 支持的按键
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 支持的相对坐标
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 支持的绝对坐标
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
// ...
};
4.2.2 驱动实现示例(按键驱动)
c
#include <linux/module.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
struct button_dev {
struct input_dev *input;
int gpio;
int irq;
int key_code;
};
// 中断处理函数
static irqreturn_t button_irq_handler(int irq, void *dev_id)
{
struct button_dev *button = dev_id;
int state;
// 读取 GPIO 状态
state = gpio_get_value(button->gpio);
// 上报按键事件
input_report_key(button->input, button->key_code, !state);
input_sync(button->input); // 同步事件
return IRQ_HANDLED;
}
static int button_probe(struct platform_device *pdev)
{
struct button_dev *button;
struct device_node *np = pdev->dev.of_node;
int ret;
// 分配内存
button = devm_kzalloc(&pdev->dev, sizeof(*button), GFP_KERNEL);
if (!button)
return -ENOMEM;
// 分配 input 设备
button->input = devm_input_allocate_device(&pdev->dev);
if (!button->input)
return -ENOMEM;
// 从设备树获取 GPIO
button->gpio = of_get_named_gpio(np, "gpios", 0);
if (!gpio_is_valid(button->gpio))
return -EINVAL;
// 从设备树获取按键码
of_property_read_u32(np, "linux,code", &button->key_code);
// 配置 input 设备
button->input->name = "GPIO Button";
button->input->phys = "gpio-button/input0";
button->input->id.bustype = BUS_HOST;
// 设置支持的事件类型和按键
set_bit(EV_KEY, button->input->evbit);
set_bit(button->key_code, button->input->keybit);
// 请求 GPIO
ret = devm_gpio_request_one(&pdev->dev, button->gpio,
GPIOF_IN, "button-gpio");
if (ret)
return ret;
// 获取中断号
button->irq = gpio_to_irq(button->gpio);
// 请求中断
ret = devm_request_irq(&pdev->dev, button->irq,
button_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"button-irq", button);
if (ret)
return ret;
// 注册 input 设备
ret = input_register_device(button->input);
if (ret)
return ret;
platform_set_drvdata(pdev, button);
dev_info(&pdev->dev, "Button device registered\n");
return 0;
}
static int button_remove(struct platform_device *pdev)
{
struct button_dev *button = platform_get_drvdata(pdev);
input_unregister_device(button->input);
return 0;
}
static const struct of_device_id button_of_match[] = {
{ .compatible = "gpio-button", },
{ },
};
MODULE_DEVICE_TABLE(of, button_of_match);
static struct platform_driver button_driver = {
.probe = button_probe,
.remove = button_remove,
.driver = {
.name = "gpio-button",
.of_match_table = button_of_match,
},
};
module_platform_driver(button_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("GPIO Button Input Driver");
4.3 Input 事件类型
| 事件类型 | 宏定义 | 说明 |
|---|---|---|
| EV_SYN | 0x00 | 同步事件 |
| EV_KEY | 0x01 | 按键事件 |
| EV_REL | 0x02 | 相对坐标(鼠标) |
| EV_ABS | 0x03 | 绝对坐标(触摸屏) |
| EV_MSC | 0x04 | 杂项事件 |
| EV_LED | 0x11 | LED 事件 |
4.4 与 Platform 总线关联
c
// 设备树定义
gpio-button {
compatible = "gpio-button";
gpios = <&gpio4 5 GPIO_ACTIVE_LOW>;
linux,code = <KEY_ENTER>;
status = "okay";
};
关联关系:
- Input 设备驱动通过 Platform 总线注册
- 使用 Platform 框架的资源管理(devm_* 系列函数)
- 通过设备树匹配并获取硬件配置
五、IIO (Industrial I/O) 子系统
5.1 IIO 子系统架构
┌─────────────────────────────────────────────┐
│ User Space │
│ /sys/bus/iio/devices/iio:device0/ │
│ /dev/iio:device0 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ IIO Core (IIO 核心层) │
│ 设备注册、通道管理、触发器、缓冲区 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ IIO Device Driver (IIO 驱动层) │
│ ADC DAC 加速度计 陀螺仪 磁力计 温度计 │
└─────────────────────────────────────────────┘
5.2 IIO 核心概念
5.2.1 IIO 设备和通道
c
struct iio_dev {
int modes; // 工作模式
int currentmode;
struct device dev;
struct iio_buffer *buffer; // 数据缓冲区
int scan_bytes;
const unsigned long *available_scan_masks;
const unsigned long *active_scan_mask;
struct iio_trigger *trig; // 触发器
int num_channels; // 通道数量
const struct iio_chan_spec *channels; // 通道描述
const char *name;
const struct iio_info *info; // 操作函数
// ...
};
// 通道描述
struct iio_chan_spec {
enum iio_chan_type type; // 通道类型: IIO_VOLTAGE, IIO_TEMP 等
int channel; // 通道索引
int channel2;
unsigned long address;
int scan_index;
struct {
char sign; // 's'=有符号, 'u'=无符号
u8 realbits; // 有效位数
u8 storagebits; // 存储位数
u8 shift;
enum iio_endian endianness;
} scan_type;
long info_mask_separate; // 单独的信息掩码
long info_mask_shared_by_type; // 按类型共享的信息掩码
// ...
};
5.2.2 IIO 信息结构
c
struct iio_info {
int (*read_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask);
int (*write_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask);
int (*read_event_config)(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir);
// ...
};
5.3 IIO 驱动示例(ADC 驱动)
c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/iio/iio.h>
#include <linux/io.h>
#define ADC_CHANNELS 4
struct my_adc {
void __iomem *base;
struct iio_dev *indio_dev;
};
// 读取原始数据
static int my_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct my_adc *adc = iio_priv(indio_dev);
u32 raw_value;
switch (mask) {
case IIO_CHAN_INFO_RAW:
// 读取 ADC 寄存器
raw_value = readl(adc->base + chan->channel * 4);
*val = raw_value & 0xFFF; // 12-bit ADC
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
// 返回缩放因子: 3.3V / 4096 = 0.0008056640625
*val = 3300; // mV
*val2 = 12; // 2^12
return IIO_VAL_FRACTIONAL_LOG2;
default:
return -EINVAL;
}
}
static const struct iio_info my_adc_info = {
.read_raw = my_adc_read_raw,
};
// 定义通道
#define MY_ADC_CHANNEL(idx) { \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = idx, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
.scan_index = idx, \
.scan_type = { \
.sign = 'u', \
.realbits = 12, \
.storagebits = 16, \
.shift = 0, \
}, \
}
static const struct iio_chan_spec my_adc_channels[] = {
MY_ADC_CHANNEL(0),
MY_ADC_CHANNEL(1),
MY_ADC_CHANNEL(2),
MY_ADC_CHANNEL(3),
};
static int my_adc_probe(struct platform_device *pdev)
{
struct iio_dev *indio_dev;
struct my_adc *adc;
struct resource *res;
int ret;
// 分配 IIO 设备
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
if (!indio_dev)
return -ENOMEM;
adc = iio_priv(indio_dev);
// 获取寄存器地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
adc->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(adc->base))
return PTR_ERR(adc->base);
// 配置 IIO 设备
indio_dev->dev.parent = &pdev->dev;
indio_dev->name = "my-adc";
indio_dev->info = &my_adc_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = my_adc_channels;
indio_dev->num_channels = ARRAY_SIZE(my_adc_channels);
// 注册 IIO 设备
ret = devm_iio_device_register(&pdev->dev, indio_dev);
if (ret) {
dev_err(&pdev->dev, "Failed to register IIO device\n");
return ret;
}
platform_set_drvdata(pdev, indio_dev);
dev_info(&pdev->dev, "ADC device registered\n");
return 0;
}
static const struct of_device_id my_adc_of_match[] = {
{ .compatible = "vendor,my-adc", },
{ },
};
MODULE_DEVICE_TABLE(of, my_adc_of_match);
static struct platform_driver my_adc_driver = {
.probe = my_adc_probe,
.driver = {
.name = "my-adc",
.of_match_table = my_adc_of_match,
},
};
module_platform_driver(my_adc_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My ADC IIO Driver");
5.4 IIO 用户空间接口
5.4.1 Sysfs 接口
bash
# IIO 设备目录
/sys/bus/iio/devices/iio:device0/
├── in_voltage0_raw # 通道 0 原始值
├── in_voltage1_raw # 通道 1 原始值
├── in_voltage_scale # 缩放因子
├── name # 设备名称
├── sampling_frequency # 采样频率
└── buffer/
├── enable # 使能缓冲区
└── length # 缓冲区长度
# 读取 ADC 值
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
# 输出: 2048
cat /sys/bus/iio/devices/iio:device0/in_voltage_scale
# 输出: 0.000805664
# 实际电压 = raw * scale = 2048 * 0.000805664 ≈ 1.65V
5.4.2 字符设备接口
c
// 用户空间程序读取 IIO 数据
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
int main() {
int fd;
uint16_t data[4]; // 4 个通道
// 使能缓冲区
system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_en");
system("echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable");
// 打开字符设备
fd = open("/dev/iio:device0", O_RDONLY);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 读取数据
if (read(fd, data, sizeof(data)) > 0) {
printf("ADC Value: %u\n", data[0]);
}
close(fd);
return 0;
}
5.5 与 Platform 总线关联
dts
// 设备树定义
adc: adc@40012000 {
compatible = "vendor,my-adc";
reg = <0x40012000 0x400>;
clocks = <&rcc 0 28>;
vref-supply = <®_vref>;
status = "okay";
};
关联关系:
- IIO 驱动通过 Platform 总线框架注册
- 使用 Platform 框架获取硬件资源(寄存器、时钟等)
- 通过设备树传递硬件配置参数
六、三者关系总结
6.1 层次关系图
┌─────────────────────────────────────────────────┐
│ User Space Application │
│ /dev/input/event0 /sys/bus/iio/devices/... │
└─────────────┬───────────────────────────────────┘
│
┌─────────────▼───────────────────────────────────┐
│ Subsystems (子系统层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Input │ │ IIO │ │ Other │ │
│ │ Subsystem│ │ Subsystem│ │Subsystems│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
└───────┼─────────────┼─────────────┼─────────────┘
│ │ │
┌───────▼─────────────▼─────────────▼─────────────┐
│ Device Driver Model (设备驱动模型) │
│ ┌──────────────────────────────────────┐ │
│ │ Platform Bus Framework │ │
│ │ (Platform 总线框架 - 核心基础) │ │
│ └───┬──────────────────────────────┬───┘ │
│ │ Device Match & Probe │ │
└──────┼──────────────────────────────┼───────────┘
│ │
┌──────▼──────┐ ┌────────▼──────┐
│ Device │ │ Driver │
│ (设备树) │ │ (驱动代码) │
└─────────────┘ └───────────────┘
6.2 关联性分析
6.2.1 Platform 总线作为基础
c
// Input 设备驱动基于 Platform
static struct platform_driver input_button_driver = {
.probe = button_probe,
.driver = {
.name = "gpio-button",
.of_match_table = button_of_match,
},
};
// IIO 设备驱动基于 Platform
static struct platform_driver iio_adc_driver = {
.probe = adc_probe,
.driver = {
.name = "my-adc",
.of_match_table = adc_of_match,
},
};
6.2.2 数据流向
硬件中断/数据
↓
Platform Driver (底层驱动)
↓
Input/IIO Subsystem (子系统处理)
↓
Device Node (/dev/input/*, /sys/bus/iio/*)
↓
User Space Application
6.3 对比表
| 特性 | Platform 总线 | Input 子系统 | IIO 子系统 |
|---|---|---|---|
| 定位 | 基础设备框架 | 人机交互设备 | 工业传感器 |
| 设备类型 | SoC 内部设备 | 键盘、鼠标、触摸屏 | ADC、DAC、传感器 |
| 用户接口 | sysfs | /dev/input/* | /sys/bus/iio/* |
| 数据特点 | - | 事件驱动 | 连续采样/触发 |
| 依赖关系 | 独立基础框架 | 依赖 Platform | 依赖 Platform |
| 注册函数 | platform_driver_register | input_register_device | iio_device_register |
七、实战案例:触摸屏驱动
7.1 设备树定义
dts
touchscreen: touchscreen@48 {
compatible = "edt,edt-ft5406";
reg = <0x48>;
interrupt-parent = <&gpio2>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <480>;
status = "okay";
};
7.2 驱动实现要点
c
// 1. Platform 层 - I2C 设备注册
static int ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct input_dev *input;
int error;
// 2. Input 层 - 分配 input 设备
input = devm_input_allocate_device(&client->dev);
// 3. 配置 input 属性
input->name = "EDT-FT5406";
set_bit(EV_ABS, input->evbit);
set_bit(EV_KEY, input->evbit);
set_bit(BTN_TOUCH, input->keybit);
input_set_abs_params(input, ABS_X, 0, 800, 0, 0);
input_set_abs_params(input, ABS_Y, 0, 480, 0, 0);
// 4. 注册 input 设备
error = input_register_device(input);
// 5. 请求中断
devm_request_threaded_irq(&client->dev, client->irq,
NULL, ts_isr_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, tsdata);
return 0;
}
// 中断处理 - 上报触摸事件
static irqreturn_t ts_isr_handler(int irq, void *dev_id)
{
struct ts_data *tsdata = dev_id;
// 读取触摸点坐标
ts_read_touchdata(tsdata, &x, &y);
// 上报绝对坐标事件
input_report_abs(tsdata->input, ABS_X, x);
input_report_abs(tsdata->input, ABS_Y, y);
input_report_key(tsdata->input, BTN_TOUCH, 1);
input_sync(tsdata->input);
return IRQ_HANDLED;
}
八、调试技巧
8.1 Platform 设备调试
bash
# 查看已注册的 platform 设备
ls /sys/bus/platform/devices/
# 查看设备树信息
ls /proc/device-tree/
# 查看驱动绑定情况
cat /sys/bus/platform/drivers/my-driver/bind
# 查看内核日志
dmesg | grep platform
8.2 Input 设备调试
bash
# 查看 input 设备列表
cat /proc/bus/input/devices
# 实时查看事件
hexdump /dev/input/event0
# 使用 evtest 工具
evtest /dev/input/event0
8.3 IIO 设备调试
bash
# 查看 IIO 设备
ls /sys/bus/iio/devices/
# 读取通道值
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
# 查看设备信息
cat /sys/bus/iio/devices/iio:device0/name
# 使用 iio_info 工具
iio_info
I2C 子系统与 Input 子系统配合详解
一、整体架构设计
1.1 分层协作关系
┌─────────────────────────────────────────────────┐
│ User Space (用户空间) │
│ /dev/input/event0 evdev libinput │
└─────────────────┬───────────────────────────────┘
│ ioctl/read
┌─────────────────▼───────────────────────────────┐
│ Input Event Handler Layer (事件处理层) │
│ evdev.c mousedev.c joydev.c │
└─────────────────┬───────────────────────────────┘
│ input_event()
┌─────────────────▼───────────────────────────────┐
│ Input Core Layer (输入核心层) │
│ input.c 事件分发、过滤、同步 │
└─────────────────┬───────────────────────────────┘
│ input_report_*()
┌─────────────────▼───────────────────────────────┐
│ Touch Driver Layer (触摸屏驱动层) │
│ edt-ft5x06.c goodix.c 等 │
└─────────────────┬───────────────────────────────┘
│ i2c_transfer()
┌─────────────────▼───────────────────────────────┐
│ I2C Bus Layer (I2C总线层) │
│ i2c-core.c i2c-dev.c │
└─────────────────┬───────────────────────────────┘
│ 硬件操作
┌─────────────────▼───────────────────────────────┐
│ I2C Controller Driver (I2C控制器驱动) │
│ i2c-imx.c i2c-s3c2410.c 等 │
└─────────────────┬───────────────────────────────┘
│
[触摸屏芯片 IC]
二、关键数据结构
2.1 I2C 客户端结构
c
struct i2c_client {
unsigned short flags; // I2C_CLIENT_* 标志
unsigned short addr; // 芯片地址(7位或10位)
char name[I2C_NAME_SIZE]; // 设备名称
struct i2c_adapter *adapter; // 所属适配器
struct device dev; // 设备模型
int irq; // 中断号
struct list_head detected;
// ...
};
2.2 触摸屏驱动私有数据
c
struct edt_ft5x06_ts_data {
struct i2c_client *client; // I2C 客户端
struct input_dev *input; // Input 设备
u16 num_x; // X 轴分辨率
u16 num_y; // Y 轴分辨率
struct gpio_desc *reset_gpio; // 复位 GPIO
struct gpio_desc *wake_gpio; // 唤醒 GPIO
struct regulator *vcc; // 电源
struct touchscreen_properties prop; // 触摸屏属性
int threshold; // 触摸阈值
int gain; // 增益
int offset; // 偏移
enum edt_ver version; // 芯片版本
// 多点触控支持
struct edt_ft5x06_point points[MAX_SUPPORT_POINTS];
};
// 触摸点数据
struct edt_ft5x06_point {
u16 x;
u16 y;
u8 id;
u8 event; // 按下/抬起/移动
};
三、详细工作流程
3.1 驱动注册流程
c
// 1. I2C 驱动定义
static struct i2c_driver edt_ft5x06_ts_driver = {
.driver = {
.name = "edt_ft5x06",
.of_match_table = edt_ft5x06_of_match, // 设备树匹配
.pm = &edt_ft5x06_ts_pm_ops, // 电源管理
},
.id_table = edt_ft5x06_ts_id, // I2C ID 表
.probe = edt_ft5x06_ts_probe, // 探测函数
.remove = edt_ft5x06_ts_remove, // 移除函数
};
// 设备树匹配表
static const struct of_device_id edt_ft5x06_of_match[] = {
{ .compatible = "edt,edt-ft5206", .data = &edt_ft5x06_data },
{ .compatible = "edt,edt-ft5406", .data = &edt_ft5x06_data },
{ .compatible = "focaltech,ft6236", .data = &edt_ft6236_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, edt_ft5x06_of_match);
// I2C 设备 ID 表
static const struct i2c_device_id edt_ft5x06_ts_id[] = {
{ "edt-ft5x06", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, edt_ft5x06_ts_id);
// 注册 I2C 驱动
module_i2c_driver(edt_ft5x06_ts_driver);
3.2 Probe 函数详解
c
static int edt_ft5x06_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct edt_ft5x06_ts_data *tsdata;
struct input_dev *input;
unsigned long irq_flags;
int error;
// ========== 第一步: 分配私有数据 ==========
tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
if (!tsdata)
return -ENOMEM;
// ========== 第二步: 初始化 I2C 通信 ==========
// 检查 I2C 功能性
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "I2C functionality not supported\n");
return -ENODEV;
}
tsdata->client = client;
i2c_set_clientdata(client, tsdata); // 保存私有数据
// ========== 第三步: 硬件资源初始化 ==========
// 1. 获取电源
tsdata->vcc = devm_regulator_get(&client->dev, "vcc");
if (IS_ERR(tsdata->vcc)) {
error = PTR_ERR(tsdata->vcc);
if (error != -EPROBE_DEFER)
dev_err(&client->dev, "Failed to get vcc regulator: %d\n", error);
return error;
}
// 2. 获取 GPIO
tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev,
"reset", GPIOD_OUT_HIGH);
if (IS_ERR(tsdata->reset_gpio))
return PTR_ERR(tsdata->reset_gpio);
tsdata->wake_gpio = devm_gpiod_get_optional(&client->dev,
"wake", GPIOD_OUT_LOW);
if (IS_ERR(tsdata->wake_gpio))
return PTR_ERR(tsdata->wake_gpio);
// 3. 上电时序
error = regulator_enable(tsdata->vcc);
if (error) {
dev_err(&client->dev, "Failed to enable vcc: %d\n", error);
return error;
}
msleep(10); // 等待电源稳定
// 4. 复位芯片
if (tsdata->reset_gpio) {
gpiod_set_value_cansleep(tsdata->reset_gpio, 0); // 拉低
msleep(5);
gpiod_set_value_cansleep(tsdata->reset_gpio, 1); // 拉高
msleep(300); // 等待芯片启动
}
// ========== 第四步: 识别芯片型号 ==========
error = edt_ft5x06_ts_identify(client, tsdata);
if (error) {
dev_err(&client->dev, "Failed to identify chip\n");
goto err_disable_regulator;
}
// ========== 第五步: 创建 Input 设备 ==========
input = devm_input_allocate_device(&client->dev);
if (!input) {
dev_err(&client->dev, "Failed to allocate input device\n");
error = -ENOMEM;
goto err_disable_regulator;
}
tsdata->input = input;
input->name = "EDT-FT5x06 Touchscreen";
input->id.bustype = BUS_I2C; // 标识为 I2C 设备
input->dev.parent = &client->dev;
// ========== 第六步: 配置 Input 能力 ==========
// 1. 设置支持的事件类型
set_bit(EV_SYN, input->evbit); // 同步事件
set_bit(EV_KEY, input->evbit); // 按键事件
set_bit(EV_ABS, input->evbit); // 绝对坐标事件
// 2. 设置触摸按键
set_bit(BTN_TOUCH, input->keybit);
// 3. 配置绝对坐标参数
input_set_abs_params(input, ABS_X, 0, tsdata->num_x - 1, 0, 0);
input_set_abs_params(input, ABS_Y, 0, tsdata->num_y - 1, 0, 0);
// 4. 多点触控支持(MT协议)
input_mt_init_slots(input, MAX_SUPPORT_POINTS, INPUT_MT_DIRECT);
input_set_abs_params(input, ABS_MT_POSITION_X, 0, tsdata->num_x - 1, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, tsdata->num_y - 1, 0, 0);
// ========== 第七步: 触摸屏属性解析 ==========
touchscreen_parse_properties(input, true, &tsdata->prop);
// ========== 第八步: 配置芯片参数 ==========
error = edt_ft5x06_ts_set_regs(tsdata);
if (error)
goto err_disable_regulator;
// ========== 第九步: 注册 Input 设备 ==========
error = input_register_device(input);
if (error) {
dev_err(&client->dev, "Failed to register input device: %d\n", error);
goto err_disable_regulator;
}
// ========== 第十步: 注册中断处理 ==========
irq_flags = irq_get_trigger_type(client->irq);
if (irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING; // 默认下降沿触发
irq_flags |= IRQF_ONESHOT; // 单次触发模式
error = devm_request_threaded_irq(&client->dev, client->irq,
NULL,
edt_ft5x06_ts_isr,
irq_flags,
client->name,
tsdata);
if (error) {
dev_err(&client->dev, "Failed to request IRQ: %d\n", error);
goto err_unregister_device;
}
// ========== 第十一步: 创建 sysfs 属性 ==========
error = sysfs_create_group(&client->dev.kobj, &edt_ft5x06_attr_group);
if (error)
goto err_unregister_device;
dev_info(&client->dev,
"EDT FT5x06 initialized: IRQ %d, Reset pin %d, %dx%d\n",
client->irq,
tsdata->reset_gpio ? desc_to_gpio(tsdata->reset_gpio) : -1,
tsdata->num_x, tsdata->num_y);
return 0;
err_unregister_device:
input_unregister_device(input);
err_disable_regulator:
regulator_disable(tsdata->vcc);
return error;
}
3.3 I2C 通信函数
c
// 读取触摸数据
static int edt_ft5x06_ts_readwrite(struct i2c_client *client,
u16 wr_len, u8 *wr_buf,
u16 rd_len, u8 *rd_buf)
{
struct i2c_msg msgs[2];
int ret;
// 写消息
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = wr_len;
msgs[0].buf = wr_buf;
// 读消息
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = rd_len;
msgs[1].buf = rd_buf;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret != 2) {
dev_err(&client->dev, "I2C transfer failed: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
// 读取寄存器
static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata,
u8 addr)
{
u8 wrbuf[1], rdbuf[1];
int error;
wrbuf[0] = addr;
error = edt_ft5x06_ts_readwrite(tsdata->client,
1, wrbuf,
1, rdbuf);
if (error)
return error;
return rdbuf[0];
}
// 写入寄存器
static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata,
u8 addr, u8 value)
{
u8 wrbuf[2];
wrbuf[0] = addr;
wrbuf[1] = value;
return i2c_master_send(tsdata->client, wrbuf, 2);
}
3.4 中断处理详解
c
static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
{
struct edt_ft5x06_ts_data *tsdata = dev_id;
struct device *dev = &tsdata->client->dev;
u8 rdbuf[63]; // 最大读取63字节
int i, type, x, y, id;
int offset, tplen, datalen, crclen;
int error;
// ========== 第一步: 从芯片读取触摸数据 ==========
// FT5x06 寄存器布局:
// 0x00: 设备模式
// 0x01: 手势ID
// 0x02: 触摸点数量 (TD_STATUS)
// 0x03-0x08: 第1个触摸点数据
// 0x09-0x0E: 第2个触摸点数据
// ...
memset(rdbuf, 0, sizeof(rdbuf));
// 计算需要读取的字节数
tplen = 6; // 每个触摸点6字节
crclen = 1; // CRC校验1字节
switch (tsdata->version) {
case EDT_M06:
datalen = tplen * MAX_SUPPORT_POINTS + crclen;
offset = 5; // 数据起始偏移
break;
case EDT_M09:
case EDT_M12:
case GENERIC_FT:
datalen = tplen * MAX_SUPPORT_POINTS;
offset = 3; // TD_STATUS 寄存器偏移
break;
default:
goto out;
}
// 读取触摸数据
error = edt_ft5x06_ts_readwrite(tsdata->client,
1, &offset,
datalen, &rdbuf[offset]);
if (error) {
dev_err_ratelimited(dev, "Unable to fetch data: %d\n", error);
goto out;
}
// ========== 第二步: 解析触摸点数量 ==========
int num_points;
switch (tsdata->version) {
case EDT_M06:
num_points = rdbuf[2] & 0x0F; // 低4位表示触摸点数
break;
case EDT_M09:
case EDT_M12:
case GENERIC_FT:
num_points = rdbuf[2] & 0x0F;
if (num_points > MAX_SUPPORT_POINTS) {
dev_warn_ratelimited(dev, "Invalid number of points: %d\n", num_points);
goto out;
}
break;
default:
goto out;
}
// ========== 第三步: 解析每个触摸点 ==========
for (i = 0; i < num_points; i++) {
u8 *buf = &rdbuf[offset + i * tplen];
// 解析触摸事件类型
// buf[0]: XH (高4位:事件类型, 低4位:X坐标高4位)
// buf[1]: XL (X坐标低8位)
// buf[2]: YH (高4位:触摸点ID, 低4位:Y坐标高4位)
// buf[3]: YL (Y坐标低8位)
// buf[4]: 压力值
// buf[5]: 触摸面积
type = (buf[0] >> 6) & 0x03;
// 0: 按下 (Touch Down)
// 1: 抬起 (Touch Up)
// 2: 接触 (Touch Contact)
// 3: 保留
// 解析坐标
x = ((buf[0] & 0x0F) << 8) | buf[1];
y = ((buf[2] & 0x0F) << 8) | buf[3];
// 解析触摸点ID
id = (buf[2] >> 4) & 0x0F;
// 坐标转换(考虑旋转、镜像等)
touchscreen_report_pos(tsdata->input, &tsdata->prop, x, y, true);
// ========== 第四步: 上报 MT 协议事件 ==========
// MT 协议 B (Slot-based)
input_mt_slot(tsdata->input, id); // 选择槽位
if (type == 0x01) {
// 抬起事件
input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, false);
} else {
// 按下/移动事件
input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, true);
input_report_abs(tsdata->input, ABS_MT_POSITION_X, x);
input_report_abs(tsdata->input, ABS_MT_POSITION_Y, y);
}
}
// ========== 第五步: 上报传统单点事件(向后兼容) ==========
input_mt_report_pointer_emulation(tsdata->input, true);
// ========== 第六步: 同步事件 ==========
input_sync(tsdata->input); // 发送 EV_SYN 事件
out:
return IRQ_HANDLED;
}
四、关键技术点
4.1 多点触控协议
c
// MT 协议 A (已废弃)
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
input_mt_sync(input); // 触摸点分隔
input_sync(input); // 数据帧结束
// MT 协议 B (推荐使用)
input_mt_slot(input, slot); // 指定槽位
input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
input_sync(input);
4.2 坐标变换
c
// 处理旋转、镜像、交换轴
void touchscreen_report_pos(struct input_dev *input,
struct touchscreen_properties *prop,
unsigned int x, unsigned int y,
bool multitouch)
{
// 交换 X/Y 轴
if (prop->swap_x_y)
swap(x, y);
// X 轴镜像
if (prop->invert_x)
x = prop->max_x - x;
// Y 轴镜像
if (prop->invert_y)
y = prop->max_y - y;
// 上报坐标
if (multitouch) {
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
} else {
input_report_abs(input, ABS_X, x);
input_report_abs(input, ABS_Y, y);
}
}
4.3 电源管理
c
static int edt_ft5x06_ts_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
disable_irq(client->irq);
// 进入睡眠模式
edt_ft5x06_register_write(tsdata, 0xA5, 0x03);
// 关闭电源
if (tsdata->vcc)
regulator_disable(tsdata->vcc);
return 0;
}
static int edt_ft5x06_ts_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
// 上电
if (tsdata->vcc)
regulator_enable(tsdata->vcc);
msleep(10);
// 复位芯片
if (tsdata->reset_gpio) {
gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
msleep(5);
gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
msleep(300);
}
// 恢复寄存器配置
edt_ft5x06_ts_set_regs(tsdata);
enable_irq(client->irq);
return 0;
}
static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
edt_ft5x06_ts_suspend,
edt_ft5x06_ts_resume);
五、数据流向图
触摸屏芯片 (FT5406)
↓ [硬件中断]
GPIO 中断控制器
↓ [IRQ]
中断处理函数 (ts_isr_handler)
↓ [I2C读取]
i2c_transfer() → I2C 控制器驱动
↓ [返回触摸数据]
解析坐标、事件类型
↓ [调用 Input 函数]
input_report_abs(ABS_MT_POSITION_X/Y)
input_mt_slot() / input_mt_report_slot_state()
↓ [事件缓冲]
input_sync() → Input Core
↓ [事件分发]
evdev / mousedev / joydev
↓ [字符设备]
/dev/input/event0
↓ [read/poll]
用户空间应用 (Qt/Android/X11)
参考资料:
- Linux Device Drivers (LDD3)
- Linux 内核文档: Documentation/driver-api/
- Input 子系统文档: Documentation/input/
- IIO 子系统文档: Documentation/iio/
- 设备树规范: Devicetree Specification