Linux 内核驱动加载机制

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);
}

匹配优先级

  1. 设备树 compatible 属性
  2. ACPI 匹配
  3. platform_device_id
  4. 设备名称

四、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 = <&reg_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
相关推荐
炸膛坦客2 小时前
FreeRTOS 学习:(十七)“外部中断”和“内核中断”的差异,引入 FreeRTOS 中断管理
stm32·freertos·实时操作系统
奋斗的牛马2 小时前
FPGA--zynq学习 PS与PL交互(二) HP接口
单片机·嵌入式硬件·学习·fpga开发·信息与通信
好记忆不如烂笔头abc2 小时前
Oracle19c rac两节点实例test,在节点1查看监听状态没有test1,但在节点2可以看到test2
运维·服务器
牢七2 小时前
Linux新
linux
C.咖.2 小时前
Linux环境下——Git 与 GitHub
linux·git·github·远程仓库
阿猿收手吧!3 小时前
【环境配置】vscode远程连接云服务器死机问题
运维·服务器
SSL店小二4 小时前
IP SSL证书申请全过程及注意事项
服务器·网络·网络协议·https·ssl
Empty_7774 小时前
Ansible进行Nginx编译安装的详细步骤
linux·nginx·ansible
ACP广源盛139246256734 小时前
GSV1016/ACP#HDMI2.0 HDCP1.4 发射器(TTL/LVDS 输入 + 音频插入)技术解析
单片机·嵌入式硬件·音视频