RK3568休眠按键唤醒驱动(低功耗)

RK3568休眠按键唤醒驱动(低功耗)


1. 数据结构定义

1.1 gpio_keys_button — 按键属性结构体

  • 描述单个 GPIO 按键属性,包括按键代码、触发方式、唤醒能力、去抖时间等。

1.2 gpio_keys_platform_data — 平台数据

  • 包含所有按键数组,按键数目,是否支持重复按压等。

1.3 gpio_button_data — 按键运行时数据

  • 关联单个按键的 GPIO 描述符、输入设备、按键码、工作队列等信息。

1.4 gpio_keys_drvdata — 驱动私有数据

  • 包含平台数据、输入设备、按键映射表及所有按键数据数组。

2. 核心函数详解

2.1 gpio_keys_gpio_work_func — 按键事件工作处理函数

  • 读取 GPIO 值,触发输入事件,支持去抖;
  • 处理完后释放唤醒锁。

2.2 gpio_keys_gpio_isr — 中断服务函数

  • 按键中断触发,保持唤醒锁,调度延迟工作处理按键事件。

2.3 gpio_keys_get_data_from_devtree — 解析设备树数据

  • 从设备树获取按键节点数量及每个按键属性;
  • 支持读取 labelcodewakeupdebounce_intervalpress_type

2.4 gpio_keys_setup_key — 按键初始化设置

  • 获取 GPIO 描述符;
  • 计算 GPIO 号、分组,打印信息;
  • 获取中断号,注册中断处理函数;
  • 初始化输入设备能力。

2.5 gpio_keys_probe — 驱动 probe 函数

  • 驱动入口,调用设备树解析,分配驱动私有数据;
  • 分配输入设备,初始化按键,注册输入设备;
  • 支持唤醒初始化。

3. 电源管理相关

  • gpio_keys_button_enable_wakeup / gpio_keys_enable_wakeup:启用按键唤醒中断;
  • gpio_keys_button_disable_wakeup / gpio_keys_disable_wakeup:禁用按键唤醒中断;
  • gpio_keys_suspend / gpio_keys_resume:系统挂起和恢复时调用,管理唤醒功能。

4. 驱动注册与模块入口

  • 定义设备树匹配表,匹配 compatible = "my-keys"
  • 平台驱动结构体绑定 probe 和电源管理操作;
  • 驱动初始化注册和退出注销;
  • 使用 late_initcall_sync 延迟初始化驱动;
  • 声明模块 GPL 许可证。

5.完整驱动解析

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <dt-bindings/input/gpio-keys.h>
#include <linux/device.h>

// 定义打印信息的宏,方便调试时输出信息,格式统一,前缀为 "mytips: "
#define mytips(str, ...) printk("mytips: " str, ##__VA_ARGS__)

/* 
 * 按键的描述结构体,保存单个按键的相关信息 
 */
struct gpio_keys_button {
    unsigned int code;             // 按键对应的输入事件代码,如 KEY_VOLUMEUP
    int active_low;                // 按键是否为低电平有效,1表示低电平有效
    const char *label;             // 按键的标签名称
    unsigned int type;             // 输入事件类型,一般为 EV_KEY
    int wakeup;                   // 是否支持唤醒系统,1支持,0不支持
    int debounce_interval;         // 按键去抖延迟,单位毫秒
    int value;                     // 当前按键值(内部使用)
    /*unsigned int trigger;*/       // 触发类型,注释掉未使用
    unsigned int press_type;       // 按压类型,0为短按,>0为长按,单位秒
};

/* 
 * 平台数据结构,包含所有按键的信息 
 */
struct gpio_keys_platform_data {
    const struct gpio_keys_button *buttons;  // 按键数组指针
    int nbuttons;                            // 按键数量
    unsigned int rep:1;                      // 是否支持重复按键事件(autorepeat)
    const char *label;                       // 平台设备标签
};

/* 
 * 按键运行时数据结构,保存按键相关的工作队列、GPIO描述符等 
 */
struct gpio_button_data {
    const struct gpio_keys_button *button;  // 指向对应的按键描述结构体
    struct input_dev *input;                 // 输入设备指针
    struct gpio_desc *gpiod;                 // GPIO描述符,用于读取GPIO电平
    unsigned short *code;                    // 指向按键码映射的指针
    struct delayed_work work;                // 延迟工作,用于去抖处理
    unsigned int press;                      // 按键当前状态,1按下,0松开
    unsigned int irq;                        // GPIO对应的中断号
};

/* 
 * 驱动私有数据结构,包含平台数据、输入设备及所有按键数据 
 */
struct gpio_keys_drvdata {
    const struct gpio_keys_platform_data *pdata;  // 平台数据指针
    struct input_dev *input;                        // 输入设备指针
    unsigned short *keymap;                         // 按键码数组
    struct gpio_button_data data[];                 // 按键数据数组,长度为按键数
};

/* 函数声明,驱动内部使用 */
static int gpio_keys_enable_wakeup(struct gpio_keys_drvdata *ddata);
static int gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata);
static void gpio_keys_gpio_work_func(struct work_struct *work);
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id);
static struct gpio_keys_platform_data* gpio_keys_get_data_from_devtree(struct device *dev);
static int gpio_keys_setup_key(struct platform_device *pdev, struct input_dev *input,
                struct gpio_keys_drvdata *ddata, const struct gpio_keys_button *button,
                int idx, struct fwnode_handle *child);
static int gpio_keys_probe(struct platform_device *pdev);

/**
 * gpio_keys_gpio_work_func - 按键事件的延迟工作处理函数
 * @work: 延迟工作结构体指针
 *
 * 读取GPIO值,生成按键输入事件,并同步输入设备状态。
 * 处理完成后释放系统唤醒锁。
 */
static void gpio_keys_gpio_work_func(struct work_struct *work){
    // 通过容器宏获取 gpio_button_data 结构体
	struct gpio_button_data *bdata =
        container_of(work, struct gpio_button_data, work.work);

	struct input_dev *input = bdata->input;  // 输入设备指针
	int val;

	// 读取GPIO电平,支持睡眠状态下调用
	val = gpiod_get_value_cansleep(bdata->gpiod);
	if (val < 0) {
		mytips("err get gpio val: %d\n", val);
		return;
	}

	// 发送按键事件,EV_KEY事件,值为val转换为布尔值(0或1)
	input_event(input, EV_KEY, *bdata->code, !!val);
	input_sync(input);  // 同步输入设备,确保事件发送

	bdata->press = !!val;  // 记录按键当前状态

	// 如果按键支持唤醒,释放唤醒锁
	if (bdata->button->wakeup)
		pm_relax(bdata->input->dev.parent);
}

/**
 * gpio_keys_gpio_isr - GPIO中断处理函数
 * @irq: 中断号
 * @dev_id: 设备ID指针,指向 gpio_button_data 结构体
 *
 * 中断触发时调用,主要是启动延迟工作处理按键事件,并管理系统唤醒锁。
 */
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id){
	struct gpio_button_data *bdata = dev_id;

	// 如果该按键支持唤醒,则保持唤醒锁,防止系统睡眠
	if(bdata->button->wakeup) 
		pm_stay_awake(bdata->input->dev.parent);

	// 重新调度延迟工作,延迟时间为去抖时间加上按键按下时的长按时长(单位转换为jiffies)
	mod_delayed_work(system_wq,
             &bdata->work,
             msecs_to_jiffies(bdata->button->debounce_interval + !bdata->press * bdata->button->press_type * 1000));

	return IRQ_HANDLED;  // 中断已处理
}

/**
 * gpio_keys_get_data_from_devtree - 解析设备树,获取平台数据
 * @dev: 设备结构体指针
 *
 * 从设备树节点读取子节点数量及每个按键的属性,填充平台数据结构。
 */
static struct gpio_keys_platform_data*
gpio_keys_get_data_from_devtree(struct device *dev){
	int nbuttons = 0;
	struct gpio_keys_platform_data *pdata;
	struct gpio_keys_button *button;
	struct fwnode_handle *child;

	// 获取设备子节点数量,即按键数量
	nbuttons = device_get_child_node_count(dev);
	if(!nbuttons){
		mytips("no keys dev\n");
		return ERR_PTR(-ENODEV);
	}
	mytips("button number: %d\n", nbuttons);

	// 分配平台数据结构,包含按键数组空间
	pdata = devm_kzalloc(dev, sizeof(*pdata) + nbuttons * sizeof(*button), GFP_KERNEL);
	if(!pdata){
		mytips("data alloc failed\n");
		return ERR_PTR(-ENOMEM);
	}

	// 按键数组紧跟平台数据结构后面
	button = (struct gpio_keys_button*)(pdata + 1);

	pdata->buttons = button;
	pdata->nbuttons = nbuttons;

	// 读取平台设备标签
	device_property_read_string(dev, "label", &pdata->label);

	// 读取是否支持自动重复(autorepeat)
	pdata->rep = device_property_read_bool(dev, "autorepeat");

	// 遍历子节点,读取每个按键的属性
	device_for_each_child_node(dev, child){
		fwnode_property_read_string(child, "label", &button->label);  // 读取标签

		button->type = EV_KEY;  // 按键类型固定为EV_KEY

		// 读取按键代码,失败则使用默认值1
		if(fwnode_property_read_u32(child, "code", &button->code)){
			mytips("use default code : 1");
			button->code = 1;
		}
		mytips("code = %u\n", button->code);

		// 读取是否支持唤醒
		button->wakeup = fwnode_property_read_bool(child, "wakeup");
		mytips("wakeup=%d\n", button->wakeup);

		// 读取去抖时间,失败则默认10ms
		if(fwnode_property_read_u32(child, "debounce_interval", &button->debounce_interval)){
			button->debounce_interval = 10;
		}
		mytips("debounce interval=%d\n", button->debounce_interval);

		// 读取按压类型,失败则默认0(短按)
		if(fwnode_property_read_u32(child, "press_type", &button->press_type)){
			button->press_type = 0;
		}

		button ++;  // 指向下一个按键
	}

	return pdata;
}

/**
 * gpio_keys_setup_key - 初始化单个按键
 * @pdev: 平台设备指针
 * @input: 输入设备指针
 * @ddata: 驱动私有数据指针
 * @button: 按键描述结构体指针
 * @idx: 按键索引
 * @child: 设备树子节点句柄
 *
 * 获取GPIO、IRQ,初始化延迟工作,设置输入设备能力,申请中断等。
 */
static int gpio_keys_setup_key(struct platform_device *pdev,
                struct input_dev *input,
                struct gpio_keys_drvdata *ddata,
                const struct gpio_keys_button *button,
                int idx,
                struct fwnode_handle *child){

	const char *label = button->label ? button->label : "my_keys";  // 标签名称,默认my_keys
	struct device *dev = &pdev->dev;
	struct gpio_button_data *bdata = &ddata->data[idx];  // 当前按键的数据结构体
	irq_handler_t isr;
	unsigned long irqflags;
	int gpio = -1, bank = -1, group = -1;
	int irq;
	int error;
	bool active_low;
	char gpioname[10];

	bdata->input = input;
	bdata->button = button;

	// 获取GPIO描述符,方向为输入
	bdata->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child, GPIOD_IN, label);
	if(IS_ERR(bdata->gpiod)){
		mytips("failed to get gpio, errnum:%ld\n", PTR_ERR(bdata->gpiod));
		return PTR_ERR(bdata->gpiod);
	}

	gpio = desc_to_gpio(bdata->gpiod);  // 获取GPIO编号
	
	// 计算GPIO分组及银行编号,用于打印
	group = gpio / 32;
	bank = (gpio - (group * 32)) / 8;
	sprintf(gpioname, "GPIO%d%c%d", bank, 'A' + bank, gpio - group * 32 - bank * 8);

	mytips("gpio %d : %s\n", gpio, gpioname);

	active_low = gpiod_is_active_low(bdata->gpiod);  // 判断是否低电平有效
	mytips("active low : %d\n", active_low);
	
	irq = gpiod_to_irq(bdata->gpiod);  // 获取GPIO对应的中断号
	if(irq < 0){
		mytips("err get irq for gpio %s\n", gpioname);
		return irq;
	}
	bdata->irq = irq;
	mytips("irq %d\n attach %s\n", irq, gpioname);

	// 初始化延迟工作,绑定工作函数
	INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
	
	bdata->press = 0;  // 初始化按键状态为未按下

	// 关联keymap数组中对应位置的按键码
	bdata->code = &ddata->keymap[idx];
	*bdata->code = button->code;

	// 设置输入设备能力,支持按键事件和对应按键码
	input_set_capability(input, EV_KEY, *bdata->code);

	// 注册中断处理函数,支持上升沿和下降沿触发
	isr = gpio_keys_gpio_isr;
	irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
	error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags, label, bdata);
	if(error < 0) {
		mytips("request irq %d failed\n", bdata->irq);
		return error;
	}
	
	return 0;
}

/**
 * gpio_keys_probe - 驱动的probe函数,初始化设备
 * @pdev: 平台设备指针
 *
 * 1. 解析设备树获取按键平台数据;
 * 2. 分配驱动私有数据结构和输入设备;
 * 3. 初始化每个按键;
 * 4. 注册输入设备;
 * 5. 设置设备唤醒功能(如支持)。
 */
static int gpio_keys_probe(struct platform_device *pdev){
	struct device *dev = &pdev->dev;
	const struct gpio_keys_platform_data *pdata; 
	struct fwnode_handle *child = NULL;
	struct gpio_keys_drvdata *ddata;
	struct input_dev *input;
	size_t size;
	int i, error, wakeup = 0;
	
	// 从设备树解析平台数据
	pdata = gpio_keys_get_data_from_devtree(dev);
	if(IS_ERR(pdata))
		return PTR_ERR(pdata);

	// 计算分配驱动私有数据的大小,包含按键数据数组
	size = sizeof(struct gpio_keys_drvdata) + 
			pdata->nbuttons * sizeof(struct gpio_button_data);
	ddata = devm_kzalloc(dev, size, GFP_KERNEL);
	if(!ddata) {
		mytips("failed to allocate ddata\n");
		return -ENOMEM;
	}

	// 分配按键码数组
	ddata->keymap = devm_kcalloc(dev, pdata->nbuttons, sizeof(ddata->keymap[0]), GFP_KERNEL);
	if(!ddata->keymap)
		return -ENOMEM;

	// 分配输入设备
	input = devm_input_allocate_device(dev);
	if(!input) {
		mytips("failed to allocate input dev\n");
		return -ENOMEM;
	}

	ddata->pdata = pdata;
	ddata->input = input;
	
	input->name = pdev->name;          // 输入设备名称,使用平台设备名
	input->dev.parent = dev;           // 设备父设备指针

	input->keycode = ddata->keymap;   // 按键码映射
	input->keycodesize = sizeof(ddata->keymap[0]);
	input->keycodemax = pdata->nbuttons;

	// 如果支持重复按键事件,则设置EV_REP位
	if(pdata->rep)
		__set_bit(EV_REP, input->evbit);

	// 逐个初始化按键
	for(i = 0; i < pdata->nbuttons; i ++) {
		const struct gpio_keys_button *button = &pdata->buttons[i];

		child = device_get_next_child_node(dev, child);
		if(!child) {
			mytips("no child device node\n");
			return -EINVAL;
		}

		error = gpio_keys_setup_key(pdev, input, ddata, button, i, child);
		if(error) {
			fwnode_handle_put(child);
			return error;
		}

		// 判断是否存在支持唤醒的按键
		if(button->wakeup)
			wakeup = 1;
	}
	fwnode_handle_put(child);

	// 注册输入设备
	error = input_register_device(input);
	if(error) {
		mytips("unable to register input dev\n");
		return error;
	}

	// 保存驱动私有数据指针
	platform_set_drvdata(pdev, ddata);
    input_set_drvdata(input, ddata);

	// 初始化设备唤醒功能
	if(wakeup){
		error = device_init_wakeup(dev, wakeup);
		mytips("init wakeup,ret = %d\n", error);
		// gpio_keys_enable_wakeup(ddata); // 这里注释掉,可根据需求打开
	}

	return 0;
}

/**
 * gpio_keys_button_enable_wakeup - 使能单个按键的中断唤醒功能
 * @bdata: 按键数据结构体指针
 *
 * 使能中断唤醒并设置中断触发类型为双边沿。
 */
static int
gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata)
{
    int error;

    // 使能中断唤醒
    error = enable_irq_wake(bdata->irq);
    if (error) {
        mytips("failed setup wakeup source IRQ: %d by err: %d\n",
            bdata->irq, error);
        return error;
    }

    // 设置中断触发类型为上升沿和下降沿触发
    error = irq_set_irq_type(bdata->irq, IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING);
    if (error) {
        mytips("failed to set wakeup trigger for IRQ %d: %d\n", bdata->irq, error);
        disable_irq_wake(bdata->irq);  // 失败则撤销唤醒使能
        return error;
    }

    return 0;
}

/**
 * gpio_keys_enable_wakeup - 使能所有支持唤醒的按键
 * @ddata: 驱动私有数据指针
 *
 * 遍历所有按键,调用 gpio_keys_button_enable_wakeup 使能唤醒。
 */
static int 
gpio_keys_enable_wakeup(struct gpio_keys_drvdata *ddata)
{
    struct gpio_button_data *bdata;
    int error;
    int i;

    for (i = 0; i < ddata->pdata->nbuttons; i++) {
        bdata = &ddata->data[i];
        if (bdata->button->wakeup) {
            error = gpio_keys_button_enable_wakeup(bdata);
            if (error)
                return error;
        }
    }

    return 0;
}

/**
 * gpio_keys_button_disable_wakeup - 禁用单个按键的中断唤醒
 * @bdata: 按键数据结构体指针
 */
static void __maybe_unused
gpio_keys_button_disable_wakeup(struct gpio_button_data *bdata)
{
    int error;

    error = disable_irq_wake(bdata->irq);
    if (error)
        mytips("failed to disable wakeup src IRQ %d: %d\n", bdata->irq, error);
}

/**
 * gpio_keys_disable_wakeup - 禁用所有支持唤醒的按键
 * @ddata: 驱动私有数据指针
 */
static void __maybe_unused
gpio_keys_disable_wakeup(struct gpio_keys_drvdata *ddata)
{
    struct gpio_button_data *bdata;
    int i;

    for (i = 0; i < ddata->pdata->nbuttons; i++) {
        bdata = &ddata->data[i];
        if (irqd_is_wakeup_set(irq_get_irq_data(bdata->irq)))
            gpio_keys_button_disable_wakeup(bdata);
    }
}

/**
 * gpio_keys_suspend - 设备挂起回调
 * @dev: 设备指针
 *
 * 挂起时使能唤醒功能(如支持)。
 */
static int __maybe_unused gpio_keys_suspend(struct device *dev)
{
    struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
    int error;

    if (device_may_wakeup(dev)) {
        error = gpio_keys_enable_wakeup(ddata);
        if (error)
            return error;
    }
    return 0;
}

/**
 * gpio_keys_resume - 设备恢复回调
 * @dev: 设备指针
 *
 * 恢复时禁用唤醒功能。
 */
static int __maybe_unused gpio_keys_resume(struct device *dev)
{
    struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);

    if (device_may_wakeup(dev)) {
        gpio_keys_disable_wakeup(ddata);
    }

    return 0;
}

// 定义简单的电源管理操作结构体,绑定挂起和恢复回调函数
static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume);

// 设备树匹配表,匹配 compatible 字段为 "my-keys"
static const struct of_device_id gpio_keys_of_match[] = {
    { .compatible = "my-keys", },
    { },
};

MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

// 平台驱动结构体定义
static struct platform_driver gpio_keys_device_driver = {
    .probe      = gpio_keys_probe,      // probe 函数入口
    .driver     = {
        .name   = "my-keys",            // 驱动名称
        .of_match_table = gpio_keys_of_match,  // 设备树匹配表
		.pm = &gpio_keys_pm_ops,        // 电源管理操作
    }
};

// 驱动模块初始化函数
static int __init gpio_keys_init(void)
{
    return platform_driver_register(&gpio_keys_device_driver);
}

// 驱动模块退出函数
static void __exit gpio_keys_exit(void)
{
    platform_driver_unregister(&gpio_keys_device_driver);
}

// 延迟初始化模块,保证所有子系统初始化完成后再加载
late_initcall_sync(gpio_keys_init);
module_exit(gpio_keys_exit);

MODULE_LICENSE("GPL");  // 声明GPL许可,保证开源兼容性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值