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 — 解析设备树数据
- 从设备树获取按键节点数量及每个按键属性;
- 支持读取
label
、code
、wakeup
、debounce_interval
和 press_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许可,保证开源兼容性