[Android O] [RK3399] -- 脉冲信号检测

需求

驱动实现 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 defined\n");

	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: %d\n",
		       error);
		goto fail0;
	}
	sinput_dev = input;

	for (i = 0; i < ddata->nbuttons; i++) {
		struct rk_keys_button *button = &ddata->button[i];

		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[i];

		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 %d\n",
				       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 %d\n",
				       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 %d\n",
				       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 %d\n",
				       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[i].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)调度执行某个函数的一种机制,其实现位于 <linux/timer.h> 和 time.c 文件中。

内核定时器的调度函数运行一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。

setup_timer() 函数用于初始化定时器并赋值其成员;要修改一个定时器的调度时间,可以通过调用 mod_timer() 函数,mod_timer() 函数会重新注册定时器到内核,而不管定时器函数是否被运行过。

8、input_event():

上报新的 input 事件。

函数原型如下:

input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
  • dev:device that generated the event;
  • type:type of the event;
  • code:event code;
  • value:value of the event

9、input_set_capability:

设置输入设备可以上报哪些输入事件。

函数原型如下:

input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);
  • dev:就是设备的 input_dev 结构体变量;
  • type:表示设备可以上报的事件类型;
  • code:表示上报这类时间中的哪个事件。

注意:input_set_capability() 一次只能设置一个具体事件,如果设备可以上报多个事件,则需要重复调用这个函数来进行设置。

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():

用于注册一个输入设备。

 

知识点总结

1、input 子系统;

2、 中断系统;

3、内核定时器。

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值