16 内核里gpio-keys设备驱动的设备树描述

本文介绍了一个基于ARM64架构的GPIO按键驱动实现方法,详细解释了如何通过平台设备注册及设备树配置完成按键驱动的初始化过程。展示了如何利用设备树节点获取硬件资源,并通过实例演示了按键配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此设备驱动适用于连接到一个具有中断功能的io口的按键驱动.
使用platform_device方法可参考:http://blog.youkuaiyun.com/jklinux/article/details/73828786

此设备驱动在内核里配置:

make menuconfig ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

  Device Drivers  ---> 
     Input device support  ---> 
        [*]   Keyboards  ---> 
           <*>   GPIO Buttons

驱动源码在: drivers/input/keyboard/gpio_keys.c


898 static struct platform_driver gpio_keys_device_driver = {
899     .probe      = gpio_keys_probe,
900     .remove     = gpio_keys_remove,
901     .driver     = {
902         .name   = "gpio-keys",
903         .pm = &gpio_keys_pm_ops,
904         .of_match_table = gpio_keys_of_match,
905     }
906 };

722 static const struct of_device_id gpio_keys_of_match[] = {
723     { .compatible = "gpio-keys", },
724     { },
725 };
726 MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

通过上面两部分内容可以得知,在设备树里设备节点的compatible属性值应为"gpio-keys".

当匹配上时, gpio_keys_probe函数就会被触发调用,获取设备提供的资源.
728 static int gpio_keys_probe(struct platform_device *pdev)
729 {
730     struct device *dev = &pdev->dev;
731     const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
732     struct fwnode_handle *child = NULL;
733     struct gpio_keys_drvdata *ddata;
734     struct input_dev *input;
735     size_t size;
736     int i, error;
737     int wakeup = 0;
738 
739     if (!pdata) {
740         pdata = gpio_keys_get_devtree_pdata(dev); //获取设备树里设备节点提供的资源
741         if (IS_ERR(pdata))
742             return PTR_ERR(pdata);
743     }
744 
745     size = sizeof(struct gpio_keys_drvdata) +
746             pdata->nbuttons * sizeof(struct gpio_button_data);
747     ddata = devm_kzalloc(dev, size, GFP_KERNEL);
748     if (!ddata) {
749         dev_err(dev, "failed to allocate state\n");
750         return -ENOMEM;
751     }
752 
753     ddata->keymap = devm_kcalloc(dev,
754                      pdata->nbuttons, sizeof(ddata->keymap[0]),
755                      GFP_KERNEL);
756     if (!ddata->keymap)
757         return -ENOMEM;
758 
759     input = devm_input_allocate_device(dev);
760     if (!input) {
761         dev_err(dev, "failed to allocate input device\n");
762         return -ENOMEM;
763     }
764 
    ... //初始化input_dev对象的成员
788     if (pdata->rep)
789         __set_bit(EV_REP, input->evbit);
790 
791     for (i = 0; i < pdata->nbuttons; i++) {
792         const struct gpio_keys_button *button = &pdata->buttons[i];
793 
794         if (!dev_get_platdata(dev)) {
795             child = device_get_next_child_node(dev, child); //获取设备节点里的子节点
            ...
803 
804         error = gpio_keys_setup_key(pdev, input, ddata,
805                         button, i, child); //根据子节点提供的属性值设置input_dev对象所支持的键码
        ...
813     }
        ...
824     error = input_register_device(input);
    ...

659 static struct gpio_keys_platform_data *
660 gpio_keys_get_devtree_pdata(struct device *dev)
661 {
662     struct gpio_keys_platform_data *pdata;
663     struct gpio_keys_button *button;
664     struct fwnode_handle *child;
665     int nbuttons;
666     
667     nbuttons = device_get_child_node_count(dev);
668     if (nbuttons == 0)
669         return ERR_PTR(-ENODEV);//意味着需要通过设备节点的子节点来提供资源
        ...
681 
682     pdata->rep = device_property_read_bool(dev, "autorepeat");
683     //表示设备节点可以有一个autorepeat属性,属性值为bool类型(0/1). 用于表示输入设备是否自动间隔地重复提交按键.
684     device_property_read_string(dev, "label", &pdata->name);
685     //表示设备节点可以有一个label属性, 属性性为string类型.用于指定输入设备的名字
686     device_for_each_child_node(dev, child) { //遍历子节点
687         if (is_of_node(child))
688             button->irq =
689                 irq_of_parse_and_map(to_of_node(child), 0);
690 
691         if (fwnode_property_read_u32(child, "linux,code",
692                          &button->code)) {
693             dev_err(dev, "Button without keycode\n");
694             fwnode_handle_put(child);
695             return ERR_PTR(-EINVAL); //意味着每个子节点都必须有"linux,code"属性,属性值为u32类型。用于指定此子节点对应的按键的键码.
696         }
697 
698         fwnode_property_read_string(child, "label", &button->desc);
699         //每个子节点还可以有一个label属性,属性值为string类型
700         if (fwnode_property_read_u32(child, "linux,input-type",
701                          &button->type))
702             button->type = EV_KEY; //每个字节点还可以通过"linux,input-type"属性来指定输入设备所支持的事件类型.  如果不提供则设置为EV_KEY
703 
704         button->wakeup =
705             fwnode_property_read_bool(child, "wakeup-source") ||
706             /* legacy name */
707             fwnode_property_read_bool(child, "gpio-key,wakeup");
708 
709         button->can_disable =
710             fwnode_property_read_bool(child, "linux,can-disable");
711 
712         if (fwnode_property_read_u32(child, "debounce-interval",
713                      &button->debounce_interval))
714             button->debounce_interval = 5; //如是中断触发的按键,则"debounce-interval"属性无需设置,如是定时轮询方式的则需要设置此间隔时间.
715 
716         button++;
717     }
718 
719     return pdata;
720 }

当在probe函数里遍历每个子节点时,会调用gpio_keys_setup_key函数来设置并获取io口信息.
468 static int gpio_keys_setup_key(struct platform_device *pdev,
469                 struct input_dev *input,
470                 struct gpio_keys_drvdata *ddata,
471                 const struct gpio_keys_button *button,
472                 int idx,
473                 struct fwnode_handle *child)
474 {
        ...
486 
487     if (child) {
488         bdata->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL,
489                                 child,
490                                 GPIOD_IN,
491                                 desc); 
    // con_id为NULL, 则表示子节点里应用gpios属性来提供io口资源.

如板上有两个按键,一个按键接PA12(作键盘上的UP键), 另一个接PA11(作键盘上的ENTER键).
设备树里的设备节点:

mykeys {
    compatible = "gpio-keys";
    autorepeat = <1>;
    label = "mykeys";

    btn0  {
        label = "btn0";
        gpios = <&pio  0  12  GPIO_ACTIVE_HIGH>;
        linux,code = <KEY_UP>;  
    };

    btn1  {
        label = "btn1";
        gpios = <&pio  0  11  GPIO_ACTIVE_HIGH>;
        linux,code = <KEY_ENTER>;   
    };  
};

更新使用设备树,重启系统后,可以查看到:

^_^ / # cat /proc/bus/input/devices 
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="mykeys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/mykeys/input/input1
U: Uniq=
H: Handlers=kbd event1 
B: PROP=0
B: EV=100003
B: KEY=8000000000 10000000

另如按键所接的IO口是没有中断功能的,则可以使用gpio_keys_polled.c设备驱动.

可参考内核源码里的: Documentation/devicetree/bindings/input/gpio-keys.txt 
         Documentation/devicetree/bindings/input/gpio-keys-polled.txt
<think>我们正在为RV1126平台配置多个GPIO按键。根据引用[2]中的信息,RV1126的Linux内核支持多种按键驱动,其中`gpio-keys`驱动对应的设备树兼容性字符串为`"gpio-keys"`。我们将使用这个驱动来配置多个按键。 在设备树中定义多个按键的步骤: 1. 创建一个父节点,其`compatible`属性为`"gpio-keys"`。 2. 在该父节点下为每个按键创建子节点,每个子节点指定GPIO引脚、键码、标签等属性。 3. 注意GPIO控制器的引用,RV1126的GPIO控制器通常由Rockchip的Pinctrl驱动管理。 引用[1]提到,在驱动代码`gpio_keys_probe`中,会读取设备树中每个按键的配置,并初始化输入设备。其中,`input->keycode`指向一个键码数组,该数组的大小由`pdata->nbuttons`(即按键数量)决定。因此,设备树中定义的每个按键子节点都会被解析为一个按钮。 引用[2]还提到,除了PMIC的pwrkey(仅支持中断方式)外,其他GPIO按键默认以轮询方式被识别。但为了提高效率,我们通常配置为中断方式。 下面是一个在RV1126设备树中定义多个GPIO按键的示例: ```dts &pinctrl { // 如果需要对GPIO进行引脚控制配置,可以在这定义,但gpio-keys通常不需要特别配置pinctrl,除非需要设置上拉/下拉等 // 例如,我们可以为每个按键定义一个引脚状态(可选) gpio_keys_pin: gpio-keys-pin { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>, // 第一个按键,使用GPIO0_A0,上拉 <0 RK_PA1 RK_FUNC_GPIO &pcfg_pull_up>; // 第二个按键,使用GPIO0_A1,上拉 }; }; // 定义gpio-keys节点 gpio-keys { compatible = "gpio-keys"; #address-cells = <1>; #size-cells = <0>; pinctrl-names = "default"; pinctrl-0 = <&gpio_keys_pin>; // 引用上面定义的pinctrl配置(可选) // 第一个按键 button@0 { label = "GPIO Key Enter"; linux,code = <KEY_ENTER>; // 键码,对应回车键 gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_LOW>; // 使用GPIO0_A0,低电平有效 debounce-interval = <20>; // 去抖动时间20ms }; // 第二个按键 button@1 { label = "GPIO Key Esc"; linux,code = <KEY_ESC>; // 键码,对应ESC键 gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_LOW>; // 使用GPIO0_A1 debounce-interval = <20>; }; // 可以继续添加更多按键... }; ``` 说明: - `compatible`属性必须为`"gpio-keys"`以匹配驱动。 - 每个按键子节点中的`linux,code`属性指定了按键的键值,这些键值在`input-event-codes.h`中定义(如`KEY_ENTER`为28,`KEY_ESC`为1)。 - `gpios`属性指定了使用的GPIO,格式为`<&gpio控制器 GPIO号 标志>`。这`GPIO_ACTIVE_LOW`表示按键按下时GPIO为低电平(通常按键设计为按下时接地)。 - `debounce-interval`是去抖动时间,单位毫秒(ms),根据实际按键特性设置。 - 如果需要,可以通过`pinctrl`来配置GPIO的默认状态(如上拉),但也可以省略,因为驱动在初始化时会根据设备树中的GPIO标志进行配置。 注意: - GPIO编号在Rockchip平台中,通常使用`RK_PA0`这样的宏来表示引脚(如GPIO0_A0对应`RK_PA0`,GPIO1_B3对应`RK_PB3`,注意前面的数字表示GPIO组)。 - 确保使用的GPIO引脚没有被其他功能占用(如复用为I2C、SPI等)。 如果希望使用中断方式(推荐),设备树中不需要额外配置中断属性,因为`gpio-keys`驱动内部会将GPIO配置为中断模式。但是,如果需要指定中断触发类型,可以在按键子节点中添加`interrupts`属性,例如: ```dts button@0 { ... interrupts = <RK_PA0 IRQ_TYPE_EDGE_FALLING>; // 下降沿触发 interrupt-parent = <&gpio0>; // 指定中断控制器 }; ``` 不过,根据引用[1]中的驱动代码,驱动在初始化时会自动将GPIO配置为中断模式,并设置触发边沿(根据`gpios`属性中的标志,例如`GPIO_ACTIVE_LOW`通常对应下降沿触发)。因此,一般情况下不需要显式指定中断属性。 最后,编译设备树并更新到系统中,然后通过`evtest`工具测试按键事件。 相关问题: 1. 在RV1126上如何调试GPIO按键的设备树配置? 2. 如何修改内核驱动以支持特殊按键功能(如长按、组合键)? 3. 在设备树中配置GPIO按键时,如何避免与其他外设的GPIO冲突?</think>### RV1126平台GPIO-keys设备树多按键配置指南 在RV1126平台上配置多个GPIO按键的设备树节点时,需要遵循以下步骤(基于Rockchip平台特性和Linux内核规范): #### 1. **基础设备树节点结构** ```dts / { gpio-keys { compatible = "gpio-keys"; #address-cells = <1>; #size-cells = <0>; pinctrl-names = "default"; pinctrl-0 = <&key_pins>; // 关联引脚控制配置 autorepeat; // 启用按键重复检测 // 按键1:电源键 power_key { label = "Power Key"; linux,code = <KEY_POWER>; // 键值116 gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_LOW>; // GPIO0_A0, 低电平有效 debounce-interval = <20>; // 20ms消抖 }; // 按键2:音量+ vol_up_key { label = "Volume Up"; linux,code = <KEY_VOLUMEUP>; // 键值115 gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_LOW>; // GPIO0_A1 }; // 按键3:音量- vol_down_key { label = "Volume Down"; linux,code = <KEY_VOLUMEDOWN>; // 键值114 gpios = <&gpio0 RK_PA2 GPIO_ACTIVE_LOW>; // GPIO0_A2 }; }; }; ``` #### 2. **关键配置说明** 1. **GPIO引脚定义**: - 使用Rockchip格式:`<&gpioX RK_PYn GPIO_ACTIVE_LOW>` - `X`=GPIO组号(0-4),`Y`=引脚字母(A-D),`n`=引脚号 - 示例:`<&gpio2 RK_PC3 GPIO_ACTIVE_LOW>` = GPIO2_C3 2. **键值定义**: - `linux,code`:使用标准Linux键值(定义于`input-event-codes.h`) - 常用键值: - `KEY_POWER`=116, - `KEY_VOLUMEUP`=115, - `KEY_VOLUMEDOWN`=114 3. **消抖配置**: - `debounce-interval`:建议20-50ms,防止机械抖动[^1] - RV1126默认使用中断模式(非轮询),响应更快[^2] #### 3. **引脚控制配置** 在pinctrl节点中添加GPIO状态定义: ```dts &pinctrl { key_pins: key-pins { rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>, // GPIO0_A0, 上拉 <0 RK_PA1 RK_FUNC_GPIO &pcfg_pull_up>, // GPIO0_A1 <0 RK_PA2 RK_FUNC_GPIO &pcfg_pull_up>; // GPIO0_A2 }; }; ``` - `&pcfg_pull_up`:启用内部上拉电阻 - `RK_FUNC_GPIO`:配置为GPIO功能 #### 4. **中断配置(推荐)** ```dts vol_up_key { ... interrupt-parent = <&gpio0>; // GPIO控制器 interrupts = <RK_PA1 IRQ_TYPE_EDGE_FALLING>; // 下降沿触发 wakeup-source; // 支持唤醒系统 }; ``` - 中断触发类型: - `IRQ_TYPE_EDGE_FALLING`:下降沿(适合低电平触发按键) - `IRQ_TYPE_EDGE_BOTH`:双沿触发 #### 5. **驱动匹配与验证** 1. 确认内核配置: ```bash # 检查驱动是否启用 zcat /proc/config.gz | grep CONFIG_KEYBOARD_GPIO ``` 2. 系统启动后验证: ```bash # 查看输入设备 cat /proc/bus/input/devices # 测试按键事件(需安装evtest) evtest /dev/input/eventX ``` #### 常见问题解决 1. **按键无响应**: - 检查GPIO复用:`cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins` - 确认GPIO方向:`cat /sys/kernel/debug/gpio` 2. **按键抖动**: - 增加`debounce-interval`值(最大100ms) - 硬件添加RC滤波电路 3. **唤醒功能失效**: - 添加`wakeup-source`属性 - 检查`/sys/devices/platform/gpio-keys/power/wakeup`状态 > **硬件连接建议**:使用GPIO电压1.8V域引脚(如GPIO0),避免与3.3V外设冲突。按键推荐电路:GPIO -- 10K上拉电阻 -- 按键 -- GND。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值