【高通SDM660平台 Android 10.0】19 --- Camera_focus、Camera_snapshot、volume_up 按键工作原理分析

  • 一、 DTS代码配置
  • 二、 Kernel 代码解析
    • 2.1 按键初始化 gpio_keys_probe()
      • 2.1.1 解析gpio dts节点 gpio_keys_get_devtree_pdata()
    • 2.2 按键工作原理
      • 2.2.1 中断注册 gpio_keys_setup_key()
      • 2.2.2 按键工作流程
        • 2.2.2.1 gpio 中断工作流程 gpio_keys_gpio_isr()
        • 2.2.2.2 非gpio 的中断流程为 gpio_keys_irq_isr()


《【高通SDM660平台】(1) — Camera 驱动 Bringup Guide》
《【高通SDM660平台】(2) — Camera Kernel 驱动层代码逻辑分析》
《【高通SDM660平台】(3) — Camera V4L2 驱动层分析 》
《【高通SDM660平台】(4) — Camera Init 初始化流程 》
《【高通SDM660平台】(5) — Camera Open 流程》
《【高通SDM660平台】(6) — Camera getParameters 及 setParameters 流程》
《【高通SDM660平台】(7) — Camera onPreview 代码流程》
《【高通SDM660平台】(8) — Camera MetaData介绍》
《【高通SDM660平台 Android 10.0】(9) — Qcom Camera Daemon 代码分析》
《【高通SDM660平台 Android 10.0】(10) — Camera Sensor lib 与 Kernel Camera Probe 代码分析》
《【高通SDM660平台 Android 10.0】(11) — Eeprom lib 与 Kernel eeprom代码分析》
《【高通SDM660平台 Android 10.0】(12) — Camera Chromatix 代码分析》
《【高通SDM660平台 Android 10.0】(18) — Camera start_session() 过程分析》
《【高通SDM660平台 Android 10.0】(19) — Camera_focus、Camera_snapshot、volume_up 按键工作原理分析》
《【高通SDM660平台 Android 10.0】(20) — Actuator 与 Kernel Actuator代码分析》



一、 DTS代码配置

Camera_focus、Camera_snapshot、volume_up 这三个按键的Kernel 代码是在dts中配置的,
由Kernel 来实现处理按键中断 及 键值上报的工作,
所以如果发通过getevent 来看,按键后发现并没有事件上报,说明Kernel 配置有问题,
这个时候,你就要检查下Kernel dts 配置了。

至于键值上报后,如何进行键值转换,从而被上层接收到,
可以参考我之前写的文章《Android 中 KeyEvent keycode 配置 及 转换原理》

如下:

# msm-4.14/arch/arm64/boot/dts/qcom/sdm660.dtsi&soc {gpio_keys {status = "okay";compatible = "gpio-keys";input-name = "gpio-keys";pinctrl-names = "tlmm_gpio_key_active","tlmm_gpio_key_suspend","default";pinctrl-0 = <&gpio_key_active &key_vol_up_default>;pinctrl-1 = <&gpio_key_suspend>;camera_focus {label = "camera_focus";// Camera 对焦gpios = <&tlmm 64 0x1>;// 使用GPIO 64  , 上升沿触发linux,input-type = <1>;// <1>: EV_KEYlinux,code = <0x210>;// 按下时上报键值 0x210debounce-interval = <15>;// 消抖延时,15ms};camera_snapshot {label = "camera_snapshot";// Camera 拍照gpios = <&tlmm 113 0x1>;// 使用GPIO 113  , 上升沿触发linux,input-type = <1>;// <1>: EV_KEYlinux,code = <0x2fe>;// 按下时上报键值 0x2fedebounce-interval = <15>;// 消抖延时,15ms};vol_up {label = "volume_up";// 音量上键gpios = <&pm660l_gpios 7 0x1>;// 使用 PM660L 的 GPIO 7 , 上升沿触发linux,input-type = <1>;// <1>: EV_KEYlinux,code = <115>;// 按下时上报键值 115linux,can-disable;// 说明该按键中断是专用中断,可以通过禁用该中断,来禁止该中断事件gpio-key,wakeup;// 该按键中断可以唤醒系统debounce-interval = <15>;// 消抖延时 15ms};};};

触发类型包括如下四种:

  • 1: 上升沿触发
  • 2: 下降沿触发
  • 4: 高电平触发
  • 8: 低电平触发

有关中断的详细可参考《【高通SDM660平台】(1) — Camera 驱动 Bringup Guide — interrupts 中断节点解析》 中的第三节,


二、 Kernel 代码解析

我们先来找下 compatible = "gpio-keys"; 对应的代码,
其代码位于 msm-4.14/drivers/input/keyboard/gpio_keys.c


2.1 按键初始化 gpio_keys_probe()

# msm-4.14/drivers/input/keyboard/gpio_keys.cstatic const struct of_device_id gpio_keys_of_match[] = {{ .compatible = "gpio-keys", },{ },};MODULE_DEVICE_TABLE(of, gpio_keys_of_match);static struct platform_driver gpio_keys_device_driver = {.probe= gpio_keys_probe,.driver= {.name= "gpio-keys",.pm= &gpio_keys_pm_ops,.of_match_table = gpio_keys_of_match,}};

进入 gpio_keys_probe() 函数分析下:

  1. 解析 dts 节点中的内容,将dts 中所的信息,保存在 pdata 指向的内存中,pdata 指向 dev->platform_data
  2. 统计 gpio_keys_drvdata + gpio_button_data 的大小,并申请内存,指针保存在struct gpio_keys_drvdata *ddata
  3. 初始化输入设备,该输入设备的父dev 为 dev { input->dev.parent = dev; }
  4. 将输入设备input 及 dts信息pdata ,保存在 struct gpio_keys_drvdata *ddata 中。
  5. 将 ddata 指针 保存在平台设备 pdev->dev->driver_data
  6. 将 ddata指什保存在 input->dev->driver_data
  7. 配置input 输入设备的相关参数
  8. 如果支持 auto repeat 需求,则注册 EV_REP 事件
  9. 申请注册每个按键对应的GPIO,并申请gpio irq 中断
  10. 添加 gpio key 的sys attr 属性节点
  11. 注册输入设备
  12. 配置该输入设备,是否支持 wakeup 唤醒系统
# msm-4.14/drivers/input/keyboard/gpio_keys.cstatic int gpio_keys_probe(struct platform_device *pdev){struct input_dev *input;const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);  // pdata 指向 dev->platform_datastruct gpio_keys_drvdata *ddata;// 1. 解析 dts 节点中的内容,将dts 中所的信息,保存在 pdata 指向的内存中。pdata = gpio_keys_get_devtree_pdata(dev);// 2. 统计 gpio_keys_drvdata + gpio_button_data 的大小,并申请内存,指针保存在ddata中 size = sizeof(struct gpio_keys_drvdata) + pdata->nbuttons * sizeof(struct gpio_button_data);ddata = devm_kzalloc(dev, size, GFP_KERNEL);ddata->keymap = devm_kcalloc(dev, pdata->nbuttons, sizeof(ddata->keymap[0]),  GFP_KERNEL);// 3. 初始化输入设备,该输入设备的父dev 为 dev (input->dev.parent = dev;)input = devm_input_allocate_device(dev);// 4. 将输入设备input 及 dts信息pdata ,保存在 ddata 中。ddata->pdata = pdata;ddata->input = input;mutex_init(&ddata->disable_lock);// 5. 将 ddata指针 保存在平台设备 pdev->dev->driver_data  中platform_set_drvdata(pdev, ddata);// 6. 将 ddata指什保存在 input->dev->driver_data 中input_set_drvdata(input, ddata);// 7. 配置input 输入设备的相关参数 input->name = pdata->name ? : pdev->name; // input->name = "gpio-keys"input->phys = "gpio-keys/input0";input->dev.parent = dev;input->open = gpio_keys_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;// 8. 如果支持 auto repeat 需求,则注册 EV_REP 事件/* 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];// 9. 申请注册每个按键对应的GPIO,并申请gpio irq 中断error = gpio_keys_setup_key(pdev, input, ddata,  button, i, child);=====>irq = gpiod_to_irq(bdata->gpiod);INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);isr = gpio_keys_gpio_isr;irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;if (button->wakeup)wakeup = 1;}// 10. 添加 gpio key 的sys attr 属性节点error = devm_device_add_group(dev, &gpio_keys_attr_group);// 11. 注册输入设备error = input_register_device(input);// 12. 配置该输入设备,是否支持 wakeup 唤醒系统device_init_wakeup(dev, wakeup);return 0;}

2.1.1 解析gpio dts节点 gpio_keys_get_devtree_pdata()

主要工作如下:

  1. 获取 gpio_keys 主节点下 子节点的数据,此处包括 camera_focuscamera_snapshotvol_up 三个子节点
  2. 在kenel 中申请连续的内存,内存大小为 gpio_keys_platform_data + 按键数量 x gpio_keys_button
  3. 将button指针 指向对应的内存区域
  4. 获取gpio key 是否支持 autorepeat 功能。
  5. 开始遍历子节点,解析所有按键的信息
    (1)解析节点中的中断信息,如 interrupts-extended#interrupt-cells,自动配置中断,并映射好对应的中断号
    (2)解析 linux,code 用于上报键值
    (3)解析 label
    (4)解析 linux,input-type,默认为 1, 即EV_KEY
    (5)解析wakeup-sourcegpio-key,wakeup 是否支待 中断换醒系统 功能
    (6)解析 linux,can-disable,是否是专用中断
    (7)解析 debounce-interval,消抖延时,如果未定义,默认为 5ms
  6. 当所有的按键解析完毕后,所有的dts 信息,保存在 pdata 指向的内存中。
static struct gpio_keys_platform_data * gpio_keys_get_devtree_pdata(struct device *dev){struct gpio_keys_platform_data *pdata;struct gpio_keys_button *button;// 1. 获取 gpio_keys 主节点下 子节点的数据,此处包括 camera_focus、camera_snapshot、vol_up 三个子节点nbuttons = device_get_child_node_count(dev);// 2. 在kenel 中申请连续的内存,内存大小为 gpio_keys_platform_data + 按键数量 x gpio_keys_buttonpdata = devm_kzalloc(dev, sizeof(*pdata) + nbuttons * sizeof(*button), GFP_KERNEL);// 3. 将button指针 指向对应的内存区域button = (struct gpio_keys_button *)(pdata + 1);pdata->buttons = button;pdata->nbuttons = nbuttons;// 4. 获取gpio key 是否支持 autorepeat 功能。pdata->rep = device_property_read_bool(dev, "autorepeat");device_property_read_string(dev, "label", &pdata->name);// 5. 开始遍历子节点,解析所有按键的信息device_for_each_child_node(dev, child) {// 6. 解析节点中的中断信息,如 interrupts-extended、#interrupt-cells,接着配置中断,并映射好对应的中断号button->irq =irq_of_parse_and_map(to_of_node(child), 0);// 7. 解析 linux,code ,用于上报键值fwnode_property_read_u32(child, "linux,code", &button->code);// 8. 解析 labelfwnode_property_read_string(child, "label", &button->desc);// 9. linux,input-type,默认为1, 即EV_KEYif (fwnode_property_read_u32(child, "linux,input-type", &button->type))button->type = EV_KEY;// 10. 解析wakeup-source 、gpio-key,wakeup 是否支待 中断换醒系统 功能button->wakeup = fwnode_property_read_bool(child, "wakeup-source") ||/* legacy name */ fwnode_property_read_bool(child, "gpio-key,wakeup");// 11. 解析 linux,can-disable,是否是专用中断button->can_disable = fwnode_property_read_bool(child, "linux,can-disable");// 12. 解析 debounce-interval,消抖延时,如果未定义,默认为 5msif (fwnode_property_read_u32(child, "debounce-interval", &button->debounce_interval))button->debounce_interval = 5;button++;}// 13. 当所有的按键解析完毕后,所有的dts 信息,保存在 pdata 指向的内存中。return pdata;}

举例: dts 中的中断信息,包括如下:

# msm-4.14/arch/arm64/boot/dts/qcom/sdm660-mtp.dtsi&sdhc_2 {#address-cells = <0>;interrupt-parent = <&sdhc_2>;interrupts = <0 1 2>;#interrupt-cells = <1>;interrupt-map-mask = <0xffffffff>;interrupt-map = <0 &intc 0 0 125 01 &intc 0 0 221 02 &tlmm 54 0>;interrupt-names = "hc_irq", "pwr_irq", "status_irq";cd-gpios = <&tlmm 54 0x1>;};

2.2 按键工作原理

前面在 gpio_keys_probe() 过程中,我们分析到通过 gpio_keys_setup_key() 函数对每个gpio 进行中断注册。

2.2.1 中断注册 gpio_keys_setup_key()

  1. 申请 GPIO号
  2. 转换 gpio 为 gpio 描述符
  3. 配置 gpio 消抖延时,如果配置失败,则配置为软件消抖
  4. 保存 gpio 按键中断号
  5. 初始化一个 work ,保存在 bdata->work 中,函数为 gpio_keys_gpio_work_func()
  6. 配置按键中断函数 gpio_keys_gpio_isr(), 中断触发类型为 上升沿 或 下降沿
  7. 保存按键键值,并注册 keycode 键值到 EV_KEY 事件中
  8. 配置取消 gpio 定时器和 work 的函数
# msm-4.14/drivers/input/keyboard/gpio_keys.cstatic 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;struct gpio_button_data *bdata = &ddata->data[idx];  // 在ddata->data[idx]中包含了各个dts中各个gpio 的信息bdata->input = input;bdata->button = button;if (child) {bdata->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,  GPIOD_IN,  desc);} 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;// 1. 申请 GPIO号error = devm_gpio_request_one(dev, button->gpio, flags, desc);// 2. 转换 gpio 为 gpio 描述符bdata->gpiod = gpio_to_desc(button->gpio);}if (bdata->gpiod) {// 3. 配置 gpio 消抖延时,如果配置失败,则配置为软件消抖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)bdata->software_debounce = button->debounce_interval;}// 4. 保存 gpio 按键中断号if (button->irq) {bdata->irq = button->irq;} else {irq = gpiod_to_irq(bdata->gpiod);bdata->irq = irq;}// 5. 初始化一个 work ,保存在 bdata->work 中INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);// 6. 配置按键中断函数,中断触发类型为 上升沿 或 下降沿isr = gpio_keys_gpio_isr;irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;} else {bdata->irq = button->irq;bdata->release_delay = button->debounce_interval;setup_timer(&bdata->release_timer, gpio_keys_irq_timer, (unsigned long)bdata);isr = gpio_keys_irq_isr;irqflags = 0;}// 7. 保存按键键值,并注册keycode 键值到 EV_KEY  事件中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. */// 8. 配置取消 gpio 定时器和work 的函数error = devm_add_action(dev, gpio_keys_quiesce_key, bdata);/* * 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;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;}

2.2.2 按键工作流程


2.2.2.1 gpio 中断工作流程 gpio_keys_gpio_isr()

从前面代码分析,我们得知当 bdata->gpiod != 0 时,说明此时是gpio 中断,
注册中断过程中,配置了中断函数 gpio_keys_gpio_isr()

主要工作为:

  1. 获取当前gpio key的所有信息
  2. 判断当前 irq 是否为当前 gpio的irq
  3. 如果支待唤醒系统,则唤配系统,同步上报 wakeup key
  4. 运行延时 work 函数 gpio_keys_gpio_work_func()
  5. 在work 中 获取当前 gpio 的状态,然后 上报gpio 状态,并且同步 input 事件
# msm-4.14/drivers/input/keyboard/gpio_keys.cstatic irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id){// 1. 获取当前gpio key的所有信息struct gpio_button_data *bdata = dev_id;// 2. 判断当前 irq 是否为当前 gpio的irqBUG_ON(irq != bdata->irq);// 3. 如果支待唤醒系统,则唤配系统if (bdata->button->wakeup) {const struct gpio_keys_button *button = bdata->button;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. */ // 上报key codeinput_report_key(bdata->input, button->code, 1);}}// 4. 运行延时 work 函数 gpio_keys_gpio_work_func()mod_delayed_work(system_wq, &bdata->work, msecs_to_jiffies(bdata->software_debounce));return IRQ_HANDLED;}
static void gpio_keys_gpio_work_func(struct work_struct *work){struct gpio_button_data *bdata =container_of(work, struct gpio_button_data, work.work);// 1.上报 key eventgpio_keys_gpio_report_event(bdata);if (bdata->button->wakeup)pm_relax(bdata->input->dev.parent);}static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata){const struct gpio_keys_button *button = bdata->button;struct input_dev *input = bdata->input;unsigned int type = button->type ?: EV_KEY;int state;// 获取当前 gpio 的状态。state = gpiod_get_value_cansleep(bdata->gpiod);// 上报gpio 状态,并且同步 input 事件if (type == EV_ABS) {if (state)input_event(input, type, button->code, button->value);} else {input_event(input, type, *bdata->code, state);}input_sync(input);}

2.2.2.2 非gpio 的中断流程为 gpio_keys_irq_isr()

从前面代码分析,我们得知当 bdata->gpiod == 0 时,说明此时是irq中断,不带gpio 的那种,
注册中断过程中,配置了中断函数 gpio_keys_irq_isr()

工作流程如下:

  1. 获取中断对应的的有信息,key_code, irq
  2. 获取输入设备
  3. 判断 irq 是否为当前正确的irq
  4. 如果 bdata->key_pressed = 0 ,则说明是第一次按下
  5. 支持唤醒系统的话,此时就会唤醒系统
  6. 上报 key_event 事件,且同步 input 事件
  7. 如果release_delay == 0 ,则直接上报按键松开事件
  8. 使能按键按下标志位
  9. 如果进来说明是已经按下,此时,则开始定时器,定时时间为release_delay 到达后,再次运行本函数,直到松开
# msm-4.14/drivers/input/keyboard/gpio_keys.cstatic irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id){// 1. 获取中断对应的的有信息,code,irq, gpid等 struct gpio_button_data *bdata = dev_id;// 2. 获取输入设备struct input_dev *input = bdata->input;unsigned long flags;// 3. 判断 irq 是否为当前正确的irqBUG_ON(irq != bdata->irq);spin_lock_irqsave(&bdata->lock, flags);// 4. 如果 bdata->key_pressed = 0 ,则说明是第一次按下if (!bdata->key_pressed) {// 5. 支持唤醒系统的话,此时就会唤醒系统if (bdata->button->wakeup)pm_wakeup_event(bdata->input->dev.parent, 0);// 6. 上报 key_event 事件,且同步 input 事件input_event(input, EV_KEY, *bdata->code, 1);input_sync(input);// 7. 如果release_delay == 0 ,则直接上报按键松开事件if (!bdata->release_delay) {input_event(input, EV_KEY, *bdata->code, 0);input_sync(input);goto out;}// 8. 使能按键按下标志位bdata->key_pressed = true;}// 9. 如果进来说明是已经按下,此时,则开始定时器,定时时间为 release_delay 到达后,再次运行本函数,直到松开if (bdata->release_delay)mod_timer(&bdata->release_timer, jiffies + msecs_to_jiffies(bdata->release_delay));out:spin_unlock_irqrestore(&bdata->lock, flags);return IRQ_HANDLED;}

好了,至此 kernel gpio_key 中断代码,从配置到上报key event 整个流程,我们就分析完了。

键值上报上去后,上层其实并不会直接认识这个键值,
可以参考我之前写的文章《Android 中 KeyEvent keycode 配置 及 转换原理》

更多相关文章

  1. Android(安卓)中隐藏虚拟按键的方法实例代码
  2. Android(安卓)getevent/senevent
  3. android 区分按键长按及短按
  4. android 中断点续传理解
  5. Appium的一点一滴:Android(安卓)KEYCODE键值
  6. Android判断设备是否有NavigationBar(虚拟按键)并获取它的高度
  7. Android(安卓)获取屏幕的多种宽高信息的示例代码
  8. android power key 长按8s 关机
  9. Android系统RTC调试从驱动到应用(一)

随机推荐

  1. Android解析XML
  2. Android(安卓)学习笔记之如何实现简单相
  3. Android横竖屏切换问题
  4. android横竖屏总结
  5. Android系统架构
  6. android 远程调用.NET WCF服务
  7. Android常用命令之创建avd
  8. android:layout_weight属性详解
  9. android intent使用方法
  10. android ratingbar星星大小设定