使用gpio子系统实现按键驱动(二)

news2024/10/5 20:25:05

一,gpio_keys.c介绍

Linux内核下的drivers/input/keyboard/gpio_keys.c实现了一个体系无关的GPIO按键驱动,使用此按键驱动,只需要在设备树gpio-key节点添加需要的按键子节点即可,适合于实现独立式按键驱动。

gpio-keys是基于input架构实现的一个通用gpio按键驱动,该驱动基于platform_driver架构,实现了驱动和设备分离,符合linux设备驱动模型的思想。

二,主要结构体及其关系

首先大致看下代码实现搞清楚结构体之间的关系,然后根据结构体之前的关系再看代码细节。

1,主要的结构体

struct gpio_keys_drvdata:

struct gpio_keys_drvdata {
    const struct gpio_keys_platform_data *pdata;
    struct input_dev *input;
    struct mutex disable_lock;
    unsigned short *keymap;
    struct gpio_button_data data[];
};

struct gpio_keys_platform_data:

struct gpio_keys_platform_data {
    const struct gpio_keys_button *buttons;
    int nbuttons;
    unsigned int poll_interval;
    unsigned int rep:1;
    int (*enable)(struct device *dev);
    void (*disable)(struct device *dev);
    const char *name;
};

struct gpio_button_data:

struct gpio_button_data {
    const struct gpio_keys_button *button;
    struct input_dev *input;
    struct gpio_desc *gpiod;

    unsigned short *code;

    struct timer_list release_timer;
    unsigned int release_delay;    /* in msecs, for IRQ-only buttons */

    struct delayed_work work;
    unsigned int software_debounce;    /* in msecs, for GPIO-driven buttons */

    unsigned int irq;
    unsigned int wakeup_trigger_type;
    spinlock_t lock;
    bool disabled;
    bool key_pressed;
    bool suspended;
};

struct gpio_keys_button:

struct gpio_keys_button {
    unsigned int code;
    int gpio;
    int active_low;
    const char *desc;
    unsigned int type;
    int wakeup;
    int wakeup_event_action;
    int debounce_interval;
    bool can_disable;
    int value;
    unsigned int irq;
};
2,结构体之间的关系

三,关键代码分析

以Android volumn up key为例。

1,设备树配置
gpio_keys {
    compatible = "gpio-keys";
    label = "gpio-keys";


    pinctrl-names = "default";
    pinctrl-0 = <&key_vol_up_default &google_key_default>;


    vol_up {
        label = "volume_up";
        gpios = <&pm7325_gpios 6 GPIO_ACTIVE_LOW>;
        linux,input-type = <1>;
        linux,code = <KEY_VOLUMEUP>;
        gpio-key,wakeup;
        debounce-interval = <15>;
        linux,can-disable;
    };

    google_key {
        label = "google_key";
        gpios = <&tlmm 41 GPIO_ACTIVE_LOW>;
        linux,input-type = <1>;
        linux,code = <KEY_SEARCH>;
        gpio-key,wakeup;
        debounce-interval = <15>;
        linux,can-disable;
    };
    ... ... ...
};
2,

probe函数

static int gpio_keys_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
    struct fwnode_handle *child = NULL;
    struct gpio_keys_drvdata *ddata;
    struct input_dev *input;
    int i, error;
    int wakeup = 0;

    if (!pdata) {
        //解析设备树配置
        pdata = gpio_keys_get_devtree_pdata(dev);
        if (IS_ERR(pdata))
            return PTR_ERR(pdata);
    }

    //给driver data分配内存,struct_size用来计算gpio_keys_drvdata结构体和nbuttons个gpio_button_data 所占内存大小
    ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nbuttons),
                 GFP_KERNEL);
    if (!ddata) {
        dev_err(dev, "failed to allocate state\n");
        return -ENOMEM;
    }
    dev_err(dev, "william debug gpio keys driver\n");

    ddata->keymap = devm_kcalloc(dev,
                     pdata->nbuttons, sizeof(ddata->keymap[0]),
                     GFP_KERNEL);
    if (!ddata->keymap)
        return -ENOMEM;
    
    //分配input设备
    input = devm_input_allocate_device(dev);
    if (!input) {
        dev_err(dev, "failed to allocate input device\n");
        return -ENOMEM;
    }

    ddata->pdata = pdata;
    ddata->input = input;
    mutex_init(&ddata->disable_lock);

    platform_set_drvdata(pdev, ddata);
    input_set_drvdata(input, ddata);

    input->name = pdata->name ? : pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = dev;
    input->open = gpio_keys_open;
    input->close = gpio_keys_close;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

    input->keycode = ddata->keymap;
    input->keycodesize = sizeof(ddata->keymap[0]);
    input->keycodemax = pdata->nbuttons;

    /* Enable auto repeat feature of Linux input subsystem */
    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
        if (!dev_get_platdata(dev)) {
            child = device_get_next_child_node(dev, child);
            if (!child) {
                dev_err(dev,
                    "missing child device node for entry %d\n",
                    i);
                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) {
        dev_err(dev, "Unable to register input device, error: %d\n",
            error);
        return error;
    }
    
    device_init_wakeup(dev, wakeup);

    return 0;
}

button设置函数gpio_keys_setup_key

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 *desc = button->desc ? button->desc : "gpio_keys";
    struct device *dev = &pdev->dev;
    //每一个bdata跟button对应,见以上结构体之间的关系
    struct gpio_button_data *bdata = &ddata->data[idx];
    irq_handler_t isr;
    unsigned long irqflags;
    int irq;
    int error;

    bdata->input = input;
    bdata->button = button;
    spin_lock_init(&bdata->lock);

    if (child) {
        //如果child节点不空,使用此函数获取gpio description
        bdata->gpiod = devm_fwnode_gpiod_get(dev, child,
                             NULL, GPIOD_IN, desc);
        if (IS_ERR(bdata->gpiod)) {
            error = PTR_ERR(bdata->gpiod);
            if (error == -ENOENT) {
                /*
                 * GPIO is optional, we may be dealing with
                 * purely interrupt-driven setup.
                 */
                bdata->gpiod = NULL;
            } else {
                if (error != -EPROBE_DEFER)
                    dev_err(dev, "failed to get gpio: %d\n",
                        error);
                return error;
            }
        }
    } else if (gpio_is_valid(button->gpio)) {
        /*
         * Legacy GPIO number, so request the GPIO here and
         * convert it to descriptor.
         */
        unsigned flags = GPIOF_IN;

        if (button->active_low)
            flags |= GPIOF_ACTIVE_LOW;

        error = devm_gpio_request_one(dev, button->gpio, flags, desc);
        if (error < 0) {
            dev_err(dev, "Failed to request GPIO %d, error %d\n",
                button->gpio, error);
            return error;
        }
        
        //将gpio转成gpio description,为了使用新的gpio控制接口,gpiod接口  
        bdata->gpiod = gpio_to_desc(button->gpio);
        if (!bdata->gpiod)
            return -EINVAL;
    }

    if (bdata->gpiod) {
        //GPIO_ACTIVE_LOW表示在低电平时触发某种操作,而GPIO_ACTIVE_HIGH表示在高电平时触发相同的操作,将逻辑电平与物理电平区分开
        bool active_low = gpiod_is_active_low(bdata->gpiod);

        if (button->debounce_interval) {
            error = gpiod_set_debounce(bdata->gpiod,
                    button->debounce_interval * 1000);
            /* use timer if gpiolib doesn't provide debounce */
            if (error < 0)
                //如果对应的gpio chip没有实现debounce的实现,使用software debounce
                bdata->software_debounce =
                        button->debounce_interval;
        }

        if (button->irq) {
            bdata->irq = button->irq;
        } else {
            irq = gpiod_to_irq(bdata->gpiod);
            if (irq < 0) {
                error = irq;
                dev_err(dev,
                    "Unable to get irq number for GPIO %d, error %d\n",
                    button->gpio, error);
                return error;
            }
            bdata->irq = irq;
        }
        
        //初始化一个delayed work,作为终端的下半部
        INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
        
        //中断服务函数,中断上半部
        isr = gpio_keys_gpio_isr;
        //触发中断的电平条件,上升沿或者下降沿触发
        irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

        switch (button->wakeup_event_action) {
        case EV_ACT_ASSERTED:
            bdata->wakeup_trigger_type = active_low ?
                IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
            break;
        case EV_ACT_DEASSERTED:
            bdata->wakeup_trigger_type = active_low ?
                IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING;
            break;
        case EV_ACT_ANY:
        default:
            /*
             * For other cases, we are OK letting suspend/resume
             * not reconfigure the trigger type.
             */
            break;
        }
    } else {
        if (!button->irq) {
            dev_err(dev, "Found button without gpio or irq\n");
            return -EINVAL;
        }

        bdata->irq = button->irq;

        if (button->type && button->type != EV_KEY) {
            dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
            return -EINVAL;
        }

        bdata->release_delay = button->debounce_interval;
        timer_setup(&bdata->release_timer, gpio_keys_irq_timer, 0);

        isr = gpio_keys_irq_isr;
        irqflags = 0;

        /*
         * For IRQ buttons, there is no interrupt for release.
         * So we don't need to reconfigure the trigger type for wakeup.
         */
    }

    bdata->code = &ddata->keymap[idx];
    *bdata->code = button->code;
    //设置该输入设备的能力,支持上报的事件类型
    input_set_capability(input, button->type ?: EV_KEY, *bdata->code);

    /*
     * Install custom action to cancel release timer and
     * workqueue item.
     */
    //用做软件防抖?当中断下半部触发之后,如果在debounce time时间之内,gpio口电平有变化,会执行gpio_keys_quiesce_key把上报键值的work cancel掉
    error = devm_add_action(dev, gpio_keys_quiesce_key, bdata);
    if (error) {
        dev_err(dev, "failed to register quiesce action, error: %d\n",
            error);
        return error;
    }

    /*
     * If platform has specified that the button can be disabled,
     * we don't want it to share the interrupt line.
     */
    if (!button->can_disable)
        irqflags |= IRQF_SHARED;

    //申请中断,中断服务函数isr
    error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags,
                         desc, bdata);
    if (error < 0) {
        dev_err(dev, "Unable to claim irq %d; error %d\n",
            bdata->irq, error);
        return error;
    }

    return 0;
}

中断服务函数isr

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
    struct gpio_button_data *bdata = dev_id;

    BUG_ON(irq != bdata->irq);

    if (bdata->button->wakeup) {
        const struct gpio_keys_button *button = bdata->button;
        
        //保持系统awake状态
        pm_stay_awake(bdata->input->dev.parent);
        if (bdata->suspended  &&
            (button->type == 0 || button->type == EV_KEY)) {
            /*
             * Simulate wakeup key press in case the key has
             * already released by the time we got interrupt
             * handler to run.
             */
            input_report_key(bdata->input, button->code, 1);
        }
    }
    
    //在防抖时间software_debounce之后,调度执行delayed work,在work中上报input event
    mod_delayed_work(system_wq,
             &bdata->work,
             msecs_to_jiffies(bdata->software_debounce));

    return IRQ_HANDLED;
}

退出delayed work的函数

static void gpio_keys_quiesce_key(void *data)
{
    struct gpio_button_data *bdata = data;

    if (bdata->gpiod)
        cancel_delayed_work_sync(&bdata->work);
    else
        del_timer_sync(&bdata->release_timer);
}

四,按键测试

volumn up:

[     611.497258] /dev/input/event0: EV_KEY       KEY_VOLUMEUP         DOWN

[     611.497258] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 0

[     611.643337] /dev/input/event0: EV_KEY       KEY_VOLUMEUP         UP

[     611.643337] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 6

google key:

[     731.598789] /dev/input/event0: EV_KEY       KEY_SEARCH           DOWN

[     731.598789] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 0

[     731.779700] /dev/input/event0: EV_KEY       KEY_SEARCH           UP

[     731.779700] /dev/input/event0: EV_SYN       SYN_REPORT           00000000             rate 5

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1077391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gma 2 教程(三)坐标参考系统:1.坐标系和坐标参考系统模块简介

安装 gma&#xff1a;pip install gma 坐标参考系统是地理空间数据表示和位置定位的基础&#xff0c;它是一种用于描述和测量地球表面位置的标准化框架。其定义了坐标系统、基准面和坐标单位等要素&#xff0c;以确保地球上不同地方的位置可以一致、准确地表示和比较。 本章以g…

开源协议介绍

文章目录 一、简介二、常见开源协议介绍2.1 BSD &#xff08;Berkeley Software Distribution license&#xff09;2.2 MIT&#xff08;Massachusetts Institute of Technology&#xff09;2.3 Apache Licence 2.02.4 GPL&#xff08;General Public License&#xff09;2.5 LG…

微电网单台并网逆变器PQ控制matlab仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 微电网运行在并网模式下且公共电网供应正常时&#xff0c;因为公共电网给定了电 压和频率的参考值&#xff0c;所有的逆变器可以使用PQ控制方式。 当系统频率为额定频率f0时&#xff0c;系统稳定在A点&#x…

【C语言进阶(13)】文件操作

文章目录 Ⅰ 什么是文件1. 文件分类2. 文件名 Ⅱ 文本文件和二进制文件Ⅲ 文件缓冲区Ⅳ 文件的打开和关闭1. 文件指针2. 文件的打开和关闭3. 文件打开方式 Ⅴ 文件的顺序读写1. fputc 写入一个字符2. fgetc 读取一个字符3. fputs 覆盖并写入一行数据4. fgets 读取指定长度的数据…

CTR特征建模:ContextNet MaskNet(Twitter在用的排序模型)

在之前的文章中 FiBiNet&FiBiNet模型&#xff0c;阐述了微博在CTR特征(Embedding)重要性建模方面的一些实践方向&#xff0c;今天再来学习下这个方面的两个相关研究&#xff1a;致力于特征和特征交互精炼(refine)的ContextNet和MaskNet&#xff0c;其中MaskNet也是Twitter(…

STM32 PA15/JTDI 用作普通IO,烧录口不能使用问题解决

我们一般用SW调试接口 所以DEBUG选择Serial Wire 这样PA15可以用作普通IO使用。 工程中默认加上&#xff1a; PA13(JTMS/SWDIO).ModeSerial_Wire PA13(JTMS/SWDIO).SignalDEBUG_JTMS-SWDIO PA14(JTCK/SWCLK).ModeSerial_Wire PA14(JTCK/SWCLK).SignalDEBUG_JTCK-SWCLK

Apple 推出全球开发者资源 —— 人人能编程

近日&#xff0c;Apple 宣布推出 Meet with Apple Experts 开发者资源&#xff0c;帮助全球 Apple 开发者社区发现并参与课程、研讨会、实验室、一对一咨询等体验。 Meet with Apple Experts 初期提供超过 50 个课程、研讨会和咨询项目&#xff0c;并将持续提供线上和线下活动&…

深入理解强化学习——强化学习的例子

分类目录&#xff1a;《深入理解强化学习》总目录 为什么我们关注强化学习&#xff0c;其中非常重要的一个原因就是强化学习得到的模型可以有超人类的表现。 有监督学习获取的监督数据&#xff0c;其实是人来标注的&#xff0c;比如ImageNet的图片的标签都是人类标注的。因此我…

JavaScript入门——(6)对象

1、什么是对象 对象&#xff08;object&#xff09;&#xff1a;JavaScript里的一种数据类型 可以理解为是一种无序的数据集合&#xff0c;注意数组是有序的数据集合 用来详细的描述某个事物&#xff0c;例如描述一个人 人有姓名、年龄、性别等信息、还有 吃饭睡觉打代码等…

Tomcat项目启动报错

java.io.IOException: java.lang.ClassCastException: Cannot cast org.springframework.web.SpringServletContainerInitializer to javax.servlet.ServletContainerInitializer解决办法&#xff1a;可能Tomcat版本不对&#xff0c;使用7.0.90版本启动报错&#xff0c;使用8.0…

关于hive的时间戳

unix_timestamp&#xff08;&#xff09;和 from_unixtime&#xff08;&#xff09;的2个都是格林威治时间 北京时间 格林威治时间8 from_unixtme 是可以进行自动时区转换的 (4.0新特性) 4.0之前可以通过from_utc_timestamp进行查询 如果时间戳为小数&#xff0c;是秒&#…

Python接口自动化搭建过程,含request请求封装!

开篇碎碎念 接口测试自动化好处 显而易见的好处就是解放双手&#x1f600;。 可以在短时间内自动执行大量的测试用例通过参数化和数据驱动的方式进行测试数据的变化&#xff0c;提高测试覆盖范围快速反馈测试执行结果和报告支持持续集成和持续交付的流程 使用Requestspytes…

移动 联通 电信 运营商大数据是如何采集访客信息的?什么是截流?

今天我们来谈手机采集获取流量&#xff0c;离开了持续不断的客户流量进来&#xff0c;再历害的转化手法也白搭。网络项目做生意&#xff0c;PC时代流量加转化&#xff0c;互联时代也一样不变。手机采集引流要么上量不精准&#xff0c;要么就是精准不上量的&#xff0c;我们不必…

AlmaLinux (兼容centos)安装Geant4与ROOT

AlmaLinux 介绍 AlmaLinux OS 是一个开源、社区驱动的 Linux 操作系统&#xff0c;它填补了因 CentOS 稳定版本停止维护而留下的空白&#xff0c;同时更加强大。 安装 AlmaLinux 这个我用的是 windows 子系统进行安装 首先打开微软商店&#xff0c;然后搜索AlmaLinux&#…

利用MobaXterm连接服务器的全程配置

一、服务器上的操作 1.1 保证openssh的安装 openssh安装命令如下 apt-get update apt install openssh-server1.2 保证SSH服务没有在相应端口上侦听连接 1确保本地 SSH 服务正在运行 可以尝试使用以下命令检查 SSH 服务的状态&#xff08;在大多数 Linux 系统上&#xff0…

信息系统项目管理师第四版学习笔记——项目立项管理

项目建议与立项申请 立项申请又称为项目建议书&#xff0c;是项目建设单位向上级主管部门提交项目申请时所必须的文件&#xff0c;项目建议书是项目发展周期的初始阶段&#xff0c;是国家或上级主管部门选择项目的依据&#xff0c;也是可行性研究的依据。 项目建议书应该包括…

Python生成ASCII艺术:将文字演绎成视觉盛宴

文章目录 介绍安装使用终端命令字体展示介绍 ASCII艺术也被称为“计算机文本艺术”,它涉及将特殊字符有智慧地放置于字母之间,以制作出分布在多行文本上的视觉形状。今天和大家分享一个Python库,它可以将文本转换为ASCII艺术,让文字看起来更花哨。 运用场景:我们可以将ASC…

【力扣每日一题】2023.10.10 移动机器人

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目比较复杂&#xff0c;我概括一下。给我们一个数组表示不同机器人在一维坐标轴上的初始位置&#xff0c;还有一个字符串表示每个机器人…

对于使用win32 API获取性能计数器的理解

微软提供了获取性能计数器的接口&#xff0c;如下 LSTATUS RegQueryValueExA([in] HKEY hKey,[in, optional] LPCSTR lpValueName,LPDWORD lpReserved,[out, optional] LPDWORD lpType,[out, optional] LPBYTE lpData,[in, out, optional] L…