RK3288的adc按键驱动rk_key.c

1、DTS修改
DTS 节点在 kernel/arch/arm64/boot/dts/rockchip/rk3288.dtsi 文件中定义,如下所示:

saradc: saradc@ff100000 {
	compatible = "rockchip,saradc";
	reg = <0x0 0xff100000 0x0 0x100>;
	interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
	#io-channel-cells = <1>;
	clocks = <&cru SCLK_SARADC>, <&cru PCLK_SARADC>;
	clock-names = "saradc", "apb_pclk";
	resets = <&cru SRST_SARADC>;
	reset-names = "saradc-apb";
	status = "disabled";
};

在DTS文件中添加自己ADC的资源描述:

rk_key{
		compatible = "rockchip,key";//"rockchip,key"该名字用来匹配rk_key.c这个驱动
		status = "okay";
		io-channels = <&saradc 1>;//根据硬件来选择adc 0/1/2   
		vol-up-key {//添加子节点
			linux,code = <114>;//驱动要上报的按键code 
			label = "volume up";
			rockchip,adc_value = <1>;//按键的adc对应电压值
		};  
		vol-down-key {
			linux,code = <115>;
			label = "volume down";
			rockchip,adc_value = <170>;
		};	  
		power-key {
			//gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
			linux,code = <116>;
			label = "power";
			gpio-key,wakeup;
			rockchip,adc_value = <509>;
		};	  
		menu-key {
			linux,code = <59>;
			label = "menu";
			rockchip,adc_value = <609>;
		};	  
		home-key {
			linux,code = <102>;
			label = "home";
			rockchip,adc_value = <842>;
		};
		back-key {
			linux,code = <158>;
			label = "back";
			rockchip,adc_value = <973>;
		};
	};

2、匹配驱动:
对应匹配的驱动:kernel\drivers\input\keyboard\rk_key.c
怎么匹配的呢??看驱动的这个函数:

static const struct of_device_id rk_key_match[] = {
    { .compatible = "rockchip,key", .data = NULL},
    {},
};

带有of_xxxxx开头的函数一般是用来解析dts的资源的,比如上面的这个函数中的.compatible = "rockchip,key"就是和dts匹配的,驱动的 "rockchip,key""rockchip,key"名字是一样的,具体的怎么匹配的,怎么实现的不用太深入,知道这个是用来匹配的就好。
2.1、驱动中解析dts设定的adc电压值的函数:
注:rk_key_type_get函数可判断是io口还是adc按键

static int rk_key_type_get(struct device_node *node,
			   struct rk_keys_button *button)
{
	u32 adc_value;

	if (!of_property_read_u32(node, "rockchip,adc_value", &adc_value))
		return TYPE_ADC;
	else if (of_get_gpio(node, 0) >= 0)
		return TYPE_GPIO;
	else
		return -1;
}
/*************************************************************/
> of_property_read_u32(node, "rockchip,adc_value", &adc_value)
>参数node:对于dts的节点
>参数 "rockchip,adc_value":进行匹配
>参数 adc_value:将dts设定的按键电压值赋给参数adc_value
/*************************************************************/

驱动中还有:of_property_read_u32(child_node, "linux,code", &code))of_get_property(child_node, "label", NULL);等是同一个道理分析的。
2.2、获取ADC通道
获取对应的通道 struct iio_channel *chan;
定义 IIO 通道结构体chan = iio_channel_get(&pdev->dev, NULL);
获取 IIO 通道结构体

static int rk_keys_parse_dt(struct rk_keys_drvdata *pdata,
			    struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct device_node *child_node;
	struct iio_channel *chan;
	int ret, gpio, i = 0;
	u32 code, adc_value, flags, drift;

	if (of_property_read_u32(node, "adc-drift", &drift))
		pdata->drift_advalue = DRIFT_DEFAULT_ADVALUE;
	else
		pdata->drift_advalue = (int)drift;

	chan = iio_channel_get(&pdev->dev, NULL);
	if (IS_ERR(chan)) {
		dev_info(&pdev->dev, "no io-channels defined\n");
		chan = NULL;
	}

2.3、获取ADC值
在adc_key polling调用了iio_read_channel_raw()函数读取 AD 采集的原始数据,并存入 val

static int rk_key_adc_iio_read(struct rk_keys_drvdata *data)
{
	struct iio_channel *channel = data->chan;
	int val, ret;

	if (!channel)
		return INVALID_ADVALUE;
	ret = iio_read_channel_raw(channel, &val);
    
	//printk("val=%d\n",val);//打印按键的实际电压值
	
	if (ret < 0) {
		pr_err("read channel() error: %d\n", ret);
		return ret;
	}
	return val;
}

3、驱动是通过内核input子系统来将keys注册供用户空间使用

static int keys_probe(struct platform_device *pdev)
{
    ***
    input = devm_input_allocate_device(dev);
    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;
    ***
    for (i = 0; i < ddata->nbuttons; i++) {
      struct rk_keys_button *button = &ddata->button[i];

      if (button->type == TYPE_GPIO) {
         int irq;
          //为io口申请这里的type即为TYPE_GPIO  而不是adc
         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;
         }
          //申请中断
         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;
         }
      }
   }
    ***
    
}

4、rk_keys_parse_dt()实现
每个key都会注册一个定时器函数来处理状态变化并通知用户空间。

   for (i = 0; i < ddata->nbuttons; i++) {
        if (button->code) {
            setup_timer(&button->timer,
                    keys_timer, (unsigned long)button);
        }
    }

keys_timer():

static void keys_timer(unsigned long _data)
{
    //普通gpio直接读取
    if (button->type == TYPE_GPIO)
        state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^
               button->active_low);
    else
        //adc转成bool状态值
        state = !!button->adc_state;
    //状态变化上报事件
    if (button->state != state) {
        button->state = state;
        input_event(input, EV_KEY, button->code, button->state);
        input_event(input, EV_KEY, button->code, button->state);
        input_sync(input);
    }
    //10ms后启动定时器
    if (state)
        mod_timer(&button->timer, jiffies + DEBOUNCE_JIFFIES);
}

定时器会处理普通gpio和adc两种类型的按键,当状态变化时,会向用户空间上报当前事件、键值、状态。默认开机时,定时器处理函数因为检测不到状态变化而关闭退出。定时器的开启有两个地方会被调用:
a.系统开机会启一个工作队列,每100ms周期性调用一次检测有没有按键触发

static void adc_key_poll(struct work_struct *work)
{
    if (!ddata->in_suspend) {
        //读取adc电压
        result = rk_key_adc_iio_read(ddata);
        for (i = 0; i < ddata->nbuttons; i++) {
            //允许值有一定范围的漂移
            if (result < button->adc_value + DRIFT_ADVALUE &&
                result > button->adc_value - DRIFT_ADVALUE)
                button->adc_state = 1;
            else
                button->adc_state = 0;
            if (button->state != button->adc_state)
                mod_timer(&button->timer,
                      jiffies + DEBOUNCE_JIFFIES);
        }
    }
    //周期性调用。ADC_SAMPLE_JIFFIES为100ms
    schedule_delayed_work(&ddata->adc_poll_work, ADC_SAMPLE_JIFFIES);
}

b、 power key唤醒时中断处理会被触发

static irqreturn_t keys_isr(int irq, void *dev_id)
{
    //上报power key事件
    if (button->wakeup && pdata->in_suspend) {
        button->state = 1;
        input_event(input, EV_KEY, button->code, button->state);
        input_sync(input);
    }
    if (button->wakeup)
        wake_lock_timeout(&pdata->wake_lock, WAKE_LOCK_JIFFIES);
    mod_timer(&button->timer, jiffies + DEBOUNCE_JIFFIES);

    return IRQ_HANDLED;
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那肯定是很多年以后!

你的鼓励就我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值