3. HDMI 热插拔检测(HPD)中断处理
任务说明
本案例实现HDMI热插拔检测(HPD)中断处理的最小可复现驱动框架。目标:当HDMI线缆插入或拔出时,HPD引脚电平变化触发GPIO中断,驱动通过中断处理函数更新连接状态,并通过内核消息接口输出状态变更事件。本案例聚焦中断触发与状态管理核心逻辑,不涉及EDID读取、音频/视频数据通路配置。
硬件连接与中断原理
HDMI源设备(如SoC)的HPD引脚连接接收端(显示器)的HPD输出。显示器上电且线缆连接时,HPD为高电平;拔出或显示器断电时,HPD为低电平。将HPD引脚配置为双边沿触发GPIO中断,即可同时捕获插入与拔出事件。
驱动代码实现
以下代码实现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
验证方法与判定标准
验证步骤:
- 加载驱动后,执行
dmesg应看到HPD driver probed日志,且初始状态与当前实际连接一致。 - 物理插入HDMI线缆,执行
dmesg应看到HPD: HDMI cable inserted。 - 物理拔出HDMI线缆,执行
dmesg应看到HPD: HDMI cable removed。 - 反复插拔3次,每次均应有对应日志输出,且状态切换正确。
判定标准:上述4个步骤全部通过,且日志中无 Failed 错误信息,即判定驱动实现正确。若插入/拔出无日志,检查GPIO引脚编号、中断触发方式配置及硬件连接。
中断处理时序
关键实现说明:驱动使用 devm_request_threaded_irq 注册线程化中断,配合 IRQF_ONESHOT 标志确保中断处理期间电平变化不会丢失。消抖延时20ms可滤除机械抖动引起的误触发。GPIO读取使用 gpiod_get_value 返回逻辑电平值,与设备树中 GPIO_ACTIVE_HIGH 配置一致。