完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
需求
驱动实现 GPIO 的脉冲信号检测,当脉冲信号来临时和脉冲信号结束时通知系统。 实现原理 1、当脉冲来临时通过 GPIO 中断触发检测; 2、脉冲信号作为按键事件处理,并通过 input 子系统进行事件上报; 3、使用内核定时器进行 input 事件上报的控制。 具体实现 设备树配置为: &rk_key { status = "okay"; compatible = "rockchip,key"; io-channels = <&saradc 1>; vol-up-key { linux,code = <114>; label = "volume up"; rockchip,adc_value = <170>; }; vol-down-key { linux,code = <115>; label = "volume down"; rockchip,adc_value = <1>; }; power-key { gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; linux,code = <116>; label = "power"; gpio-key,wakeup; }; left-light { gpios = <&gpio4 RK_PC1 GPIO_ACTIVE_LOW>; linux,code = <87>; label = "left_light"; gpio-key,light; }; right-light { gpios = <&gpio4 RK_PC0 GPIO_ACTIVE_LOW>; linux,code = <88>; label = "right_light"; gpio-key,light; }; }; 具体实现是在 /kernel/drivers/input/keyboard/rk_keys.c 中,并参照其中的相关操作: keys_probe() 函数如下: static int keys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct rk_keys_drvdata *ddata = NULL; struct input_dev *input = NULL; int i, error = 0; int wakeup, key_num = 0; key_num = of_get_child_count(np); if (key_num == 0) dev_info(&pdev->dev, "no key definedn"); ddata = devm_kzalloc(dev, sizeof(struct rk_keys_drvdata) + key_num * sizeof(struct rk_keys_button), GFP_KERNEL); input = devm_input_allocate_device(dev); if (!ddata || !input) { error = -ENOMEM; return error; } platform_set_drvdata(pdev, ddata); dev_set_drvdata(&pdev->dev, ddata); input->name = "rk29-keypad"; /* pdev->name; */ input->phys = "gpio-keys/input0"; input->dev.parent = dev; input->id.bustype = BUS_HOST; input->id.vendor = 0x0001; input->id.product = 0x0001; input->id.version = 0x0100; ddata->input = input; /* parse info from dt */ ddata->nbuttons = key_num; error = rk_keys_parse_dt(ddata, pdev); if (error) goto fail0; /* Enable auto repeat feature of Linux input subsystem */ if (ddata->rep) __set_bit(EV_REP, input->evbit); error = input_register_device(input); if (error) { pr_err("gpio-keys: Unable to register input device, error: %dn", error); goto fail0; } sinput_dev = input; for (i = 0; i < ddata->nbuttons; i++) { struct rk_keys_button *button = &ddata->button; if (button->code) { if(button->light){ setup_timer(&button->timer, keys_light_timer, (unsigned long)button); }else{ setup_timer(&button->timer, keys_timer, (unsigned long)button); } } if (button->wakeup) wakeup = 1; input_set_capability(input, EV_KEY, button->code); } wake_lock_init(&ddata->wake_lock, WAKE_LOCK_SUSPEND, input->name); device_init_wakeup(dev, wakeup); for (i = 0; i < ddata->nbuttons; i++) { struct rk_keys_button *button = &ddata->button; button->dev = &pdev->dev; if (button->type == TYPE_GPIO) { int irq; error = devm_gpio_request(dev, button->gpio, button->desc ? : "keys"); if (error < 0) { pr_err("gpio-keys: failed to request GPIO %d, error %dn", button->gpio, error); goto fail1; } error = gpio_direction_input(button->gpio); if (error < 0) { pr_err("gpio-keys: failed to configure input direction for GPIO %d, error %dn", button->gpio, error); gpio_free(button->gpio); goto fail1; } irq = gpio_to_irq(button->gpio); if (irq < 0) { error = irq; pr_err("gpio-keys: Unable to get irq number for GPIO %d, error %dn", button->gpio, error); gpio_free(button->gpio); goto fail1; } if(button->light){ error = devm_request_irq(dev, irq, keys_light_isr, button->active_low ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING, button->desc ? button->desc : "keys", button); }else{ error = devm_request_irq(dev, irq, keys_isr, button->active_low ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING, button->desc ? button->desc : "keys", button); } if (error) { pr_err("gpio-keys: Unable to claim irq %d; error %dn", irq, error); gpio_free(button->gpio); goto fail1; } } } input_set_capability(input, EV_KEY, KEY_WAKEUP); /* adc polling work */ if (ddata->chan) { INIT_DELAYED_WORK(&ddata->adc_poll_work, adc_key_poll); schedule_delayed_work(&ddata->adc_poll_work, ADC_SAMPLE_JIFFIES); } return error; fail1: while (--i >= 0) del_timer_sync(&ddata->button.timer); device_init_wakeup(dev, 0); wake_lock_destroy(&ddata->wake_lock); fail0: platform_set_drvdata(pdev, NULL); return error; } keys_light_isr() 函数如下: static irqreturn_t keys_light_isr(int irq, void *dev_id) { struct rk_keys_button *button = (struct rk_keys_button *)dev_id; struct rk_keys_drvdata *pdata = dev_get_drvdata(button->dev); struct input_dev *input = pdata->input; BUG_ON(irq != gpio_to_irq(button->gpio)); if (!button->state){ button->state = 1; input_event(input, EV_KEY, button->code, button->state); input_sync(input); mod_timer(&button->timer, jiffies + DEBOUNCE_LIGHT_JIFFIES); }else{ mod_timer(&button->timer, jiffies +DEBOUNCE_LIGHT_JIFFIES); } return IRQ_HANDLED; } keys_light_timer() 函数如下: static void keys_light_timer(unsigned long _data) { struct rk_keys_button *button = (struct rk_keys_button *)_data; struct rk_keys_drvdata *pdata = dev_get_drvdata(button->dev); struct input_dev *input = pdata->input; if(button->state) { button->state = 0; input_event(input, EV_KEY, button->code, button->state); input_sync(input); }else{ } } 重点函数解析 1、devm_kzalloc() 和 devm_kfree(): 函数 devm_kzalloc() 和 kzalloc() 一样,都是内核内存分配函数。但是 devm_kzalloc() 是跟设备有关的,当设备被拆卸或者驱动卸载时,内存会被自动释放;另外,当内存不使用时,可以使用函数 devm_kfree() 释放。而 kzalloc() 则需要手动释放(使用 kfree()),但是如果工程师检查不仔细,则有可能造成内存泄漏。 注:也就是在驱动的 probe 函数中调用 devm_kzalloc() ,在除去函数中调用 devm_kfree()。 2、platform_set_drvdata() 和 platform_get_drvdata(): 驱动中常用到 platform_set_drvdata() 和 platform_get_drvdata() 这两个函数,用来保存局部变量。 函数原型如下: static inline void *platform_get_drvdata(const struct platform_device *pdev); static inline void platform_set_drvdata(struct platform_device *pdev, void* data); 就是把 data 赋值给 pdev->dev->driver_data,pdev 是平台总线设备,对于整个驱动是可见的,所以可以通过 platform_get_drvdata() 来获取 data。 内核模块一般在 probe() 函数中动态申请内存来使用,这种情况下,这个指针就得有个位置存储防止丢失,所以内核设计得在 platform_device 结构中,保留了一个指针,就是为了这样的驱动编写方式。 所以我们一般在 probe() 函数中动态申请设备结构体(或局部变量),并初始化它,然后使用 paltform_set_drvdata() 函数将其保存到 platform_device 中,在需要使用的时候再使用 platform_get_drvdata() 来获取它。但是这个指针肯定是需要我们自己释放内存的。 3、dev_set_drvdata() 和 dev_get_drvdata(): 用来设置 device 的私有数据和用来获取 device 的私有数据。 |
|
|
|
4、for_each_child_of_node:
遍历所有子节点。 5、of_get_gpio_flags(): 例:gpio = of_get_gpio_flags(device_node *node, 0, &flags); 从 node 中读取 GPIO 配置编号 gpio 和标志 flags,flags 代表 GPIO_ACTIVE_LOW、GPIO_ACTIVE_HIGHT。 6、of_get_property(): 函数原型为: const void *of_get_property(const struct device_node *np, const char *name, int *lenp); 根据 name 参数,在指定的设备节点 np 中查找匹配的 property,并返回这个 property 的属性值。 7、setup_timer() 和 mod_timer(): 内核定时器是内核用来控制在未来某个时间点(基于 jiffies)调度执行某个函数的一种机制,其实现位于 内核定时器的调度函数运行一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。 setup_timer() 函数用于初始化定时器并赋值其成员;要修改一个定时器的调度时间,可以通过调用 mod_timer() 函数,mod_timer() 函数会重新注册定时器到内核,而不管定时器函数是否被运行过。 8、input_event(): 上报新的 input 事件。 函数原型如下: input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
9、input_set_capability: 设置输入设备可以上报哪些输入事件。 函数原型如下: input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);
10、devm_input_allocate_device() 和 input_allocate_device(): 分配一个 input_dev 结构体。 11、set_bit(): set_bit(int nr, int *addr); 将 addr 的第 nr (nr 为 0-31)位置 1。 应用举例: set_bit(EV_REP, input->evbit); 12、input_set_abs_params(dev, axis, min, max, fuzz, flat): 该函数调用实际上也是 set_bit(),对于参数:fuzz 有滤波作用;min,max 代表范围;axis 表示了坐标轴;flat 暂不知道用途。 13、input_report_key() / input_report_rel() / input_report_abs(): 提交按键事件 / 提交相对坐标事件 / 提交绝对坐标事件。 14、input_sync(): 告知 input 子系统,设备驱动已经发出了一个完整的报告。 15、input_register_device(): 用于注册一个输入设备。 |
|
|
|
你正在撰写答案
如果你是对答案或其他答案精选点评或询问,请使用“评论”功能。
rk1126实现BT601输入,输入不带IIC接口的驱动程序
1609 浏览 0 评论
1422 浏览 1 评论
2534 浏览 3 评论
RK3568 Android11让系统ntp校准时间生效,需要设置些什么
3254 浏览 1 评论
5291 浏览 2 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-24 03:25 , Processed in 0.562530 second(s), Total 45, Slave 38 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191