第四节:GPIO控制器实战——从设备树绑定到按键驱动

好,咱们今天聊点实在的。GPIO控制器,说白了就是芯片上那些能输出高低电平、能检测外部信号的引脚。在设备树里怎么描述它?怎么让一个按键被内核正确识别?这些坑,我当年可没少踩。

GPIO控制器的设备树绑定

先看一个典型的GPIO控制器节点长什么样。以我常用的i.MX6ULL为例:

gpio1: gpio@0209c000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

这里有两个属性,是GPIO控制器的灵魂:

  • gpio-controller:告诉内核“我是GPIO控制器”。没有这个,内核不认识你。
  • #gpio-cells:描述每个GPIO引用需要几个参数。常见的是2个:引脚号和标志位。

核心要点:gpio-controller是声明,#gpio-cells是协议。缺一不可。

#gpio-cells的玄机

为什么是2个cell?我解释一下:

  • 第一个cell:GPIO引脚编号。比如GPIO1_IO03,编号就是3。
  • 第二个cell:标志位。控制高低电平有效、上拉下拉等。

标志位的定义,内核里有个标准:

位 含义 值
bit 0 极性 0=高电平有效,1=低电平有效
bit 1 上拉/下拉 0=无,1=上拉,2=下拉
bit 2 驱动能力 0=默认,1=强驱动

举个例子:<&gpio1 3 GPIO_ACTIVE_LOW>,表示GPIO1的第3脚,低电平有效。这个GPIO_ACTIVE_LOW在内核里定义成0x1。

个人习惯:我一般把标志位写成宏定义,比如GPIO_ACTIVE_HIGH,这样代码可读性高。别直接写数字,三个月后你自己都看不懂。

实战:描述一个GPIO按键

好,现在我们要在设备树里加一个按键。假设按键接在GPIO1_IO03上,按下为低电平。

首先,在根节点下添加按键节点:

/ {
    gpio-keys {
        compatible = "gpio-keys";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_key>;

        power-key {
            label = "Power Key";
            linux,code = <KEY_POWER>;
            gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
            debounce-interval = <10>;
            wakeup-source;
        };
    };
};

这里有几个关键点:

  • compatible = "gpio-keys":内核里对应的驱动是gpio-keys.c,它会解析这个节点。
  • gpios:引用GPIO控制器,格式就是<&控制器 引脚号 标志位>。
  • linux,code:按键上报的键值。KEY_POWER是116,定义在input-event-codes.h里。
  • debounce-interval:消抖时间,单位毫秒。我一般设10ms,够用。
  • wakeup-source:允许按键唤醒系统。这个属性不加,休眠后按了没反应。

我曾经踩过的坑:有一次忘记加pinctrl配置,结果GPIO引脚默认是浮空状态,按键怎么按都没反应。查了两天才发现是引脚复用没配好。所以,GPIO的pinctrl一定要配,不然内核不知道这个引脚要当GPIO用。

引脚复用配置(pinctrl)

在i.MX平台上,GPIO引脚需要先配置复用功能。一般在iomuxc节点里:

&iomuxc {
    pinctrl_key: keygrp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x17059
        >;
    };
};

这个0x17059是配置值,控制上下拉、驱动能力、速度等。每个芯片的配置值含义不同,得看数据手册。

我个人习惯把pinctrl和按键节点分开写,这样结构清晰。别图省事写在一起,后期维护会想骂人。

知识体系图

下面这张图,是我自己总结的GPIO设备树配置流程:

GPIO设备树配置流程 GPIO控制器节点 pinctrl引脚复用配置 gpio-keys按键节点 内核驱动解析 & 注册 关键属性 • gpio-controller • #gpio-cells = <2> • pinctrl-names • pinctrl-0 • compatible = "gpio-keys" • gpios = <&gpio 3 0> • linux,code • debounce-interval • wakeup-source

调试技巧

配好了设备树,怎么验证?我一般这么做:

  1. 查看设备树是否被正确解析:ls /proc/device-tree/gpio-keys/,能看到节点说明解析成功。
  2. 检查GPIO是否被申请:cat /sys/kernel/debug/gpio,看对应引脚状态。
  3. 测试按键事件:evtest /dev/input/eventX,按一下看有没有事件上报。

避坑指南:如果按键没反应,先查dmesg。我曾经遇到过gpio-keys驱动没编译进内核的情况,dmesg里直接报No driver found。所以,先确认内核配置里有没有选上CONFIG_KEYBOARD_GPIO。

嗯,GPIO控制器的设备树绑定,说白了就是三件事:声明控制器、配置引脚复用、描述外设。把这三步走通,按键就能跑起来。下次遇到LED、蜂鸣器这些,套路是一样的,换汤不换药。

可可精品资料库