3. HDMI 热插拔检测(HPD)中断处理

HDMI 热插拔检测(HPD)中断处理 学习卡片

任务说明

本案例实现HDMI热插拔检测(HPD)中断处理的最小可复现驱动框架。目标:当HDMI线缆插入或拔出时,HPD引脚电平变化触发GPIO中断,驱动通过中断处理函数更新连接状态,并通过内核消息接口输出状态变更事件。本案例聚焦中断触发与状态管理核心逻辑,不涉及EDID读取、音频/视频数据通路配置。

硬件连接与中断原理

HDMI源设备(如SoC)的HPD引脚连接接收端(显示器)的HPD输出。显示器上电且线缆连接时,HPD为高电平;拔出或显示器断电时,HPD为低电平。将HPD引脚配置为双边沿触发GPIO中断,即可同时捕获插入与拔出事件。

SoC GPIO控制器 中断控制器 HDMI TX 显示器 HDMI RX HPD 驱动 HDMI 线缆 HPD 信号 GPIO 中断 → 内核中断子系统 插入: HPD=高电平 (1) | 拔出: HPD=低电平 (0)

驱动代码实现

以下代码实现HPD GPIO中断处理驱动,支持设备树配置中断触发方式,中断回调中更新连接状态并输出事件日志。

// hpd_interrupt.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/delay.h>

struct hpd_dev {
    struct gpio_desc *hpd_gpio;
    int irq;
    bool connected;
};

static irqreturn_t hpd_irq_handler(int irq, void *data)
{
    struct hpd_dev *dev = data;
    int value;
    
    /* 读取当前HPD电平 */
    value = gpiod_get_value(dev->hpd_gpio);
    
    /* 消抖处理:延时20ms后再次确认 */
    usleep_range(20000, 25000);
    value = gpiod_get_value(dev->hpd_gpio);
    
    /* 更新连接状态 */
    dev->connected = (value == 1);
    
    /* 输出事件信息 */
    if (dev->connected)
        pr_info("HPD: HDMI cable inserted\n");
    else
        pr_info("HPD: HDMI cable removed\n");
    
    return IRQ_HANDLED;
}

static int hpd_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct hpd_dev *hpd;
    int ret;
    
    hpd = devm_kzalloc(dev, sizeof(*hpd), GFP_KERNEL);
    if (!hpd)
        return -ENOMEM;
    
    /* 获取HPD GPIO */
    hpd->hpd_gpio = devm_gpiod_get(dev, "hpd", GPIOD_IN);
    if (IS_ERR(hpd->hpd_gpio)) {
        dev_err(dev, "Failed to get HPD GPIO\n");
        return PTR_ERR(hpd->hpd_gpio);
    }
    
    /* 获取GPIO对应的中断号 */
    hpd->irq = gpiod_to_irq(hpd->hpd_gpio);
    if (hpd->irq < 0) {
        dev_err(dev, "Failed to get IRQ for HPD GPIO\n");
        return hpd->irq;
    }
    
    /* 注册中断处理函数 */
    ret = devm_request_threaded_irq(dev, hpd->irq, NULL,
                                    hpd_irq_handler,
                                    IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
                                    IRQF_ONESHOT,
                                    "hpd_interrupt", hpd);
    if (ret) {
        dev_err(dev, "Failed to request IRQ %d\n", hpd->irq);
        return ret;
    }
    
    /* 初始化连接状态 */
    hpd->connected = gpiod_get_value(hpd->hpd_gpio) == 1;
    dev_info(dev, "HPD driver probed, initial state: %s\n",
             hpd->connected ? "connected" : "disconnected");
    
    platform_set_drvdata(pdev, hpd);
    return 0;
}

static int hpd_remove(struct platform_device *pdev)
{
    struct hpd_dev *hpd = platform_get_drvdata(pdev);
    
    if (hpd->connected)
        pr_info("HPD: cable removed during shutdown\n");
    
    return 0;
}

static const struct of_device_id hpd_of_match[] = {
    { .compatible = "example,hpd-interrupt" },
    { }
};
MODULE_DEVICE_TABLE(of, hpd_of_match);

static struct platform_driver hpd_driver = {
    .probe  = hpd_probe,
    .remove = hpd_remove,
    .driver = {
        .name = "hpd_interrupt",
        .of_match_table = hpd_of_match,
    },
};
module_platform_driver(hpd_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("HDMI HPD GPIO Interrupt Handler");
MODULE_AUTHOR("Driver Course");

设备树配置

/* 设备树节点示例 */
&soc {
    hpd_controller: hpd-controller@0 {
        compatible = "example,hpd-interrupt";
        hpd-gpios = &gpio2 5 GPIO_ACTIVE_HIGH;
        /* GPIO2控制器第5号引脚,高电平有效 */
    };
};

编译与加载

# 编译(内核源码树内)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules

# 加载模块
insmod hpd_interrupt.ko

# 查看内核日志
dmesg | tail -20

验证方法与判定标准

验证步骤:

  1. 加载驱动后,执行 dmesg 应看到 HPD driver probed 日志,且初始状态与当前实际连接一致。
  2. 物理插入HDMI线缆,执行 dmesg 应看到 HPD: HDMI cable inserted。
  3. 物理拔出HDMI线缆,执行 dmesg 应看到 HPD: HDMI cable removed。
  4. 反复插拔3次,每次均应有对应日志输出,且状态切换正确。

判定标准:上述4个步骤全部通过,且日志中无 Failed 错误信息,即判定驱动实现正确。若插入/拔出无日志,检查GPIO引脚编号、中断触发方式配置及硬件连接。

中断处理时序

关键实现说明:驱动使用 devm_request_threaded_irq 注册线程化中断,配合 IRQF_ONESHOT 标志确保中断处理期间电平变化不会丢失。消抖延时20ms可滤除机械抖动引起的误触发。GPIO读取使用 gpiod_get_value 返回逻辑电平值,与设备树中 GPIO_ACTIVE_HIGH 配置一致。