设备树——按键节点

/* 节点 */
r_gpio_keys {	/* 节点名称 */
    compatible = "gpio-keys";	/* 节点属性 */

    /* 子节点 */
    sw4 {	/* 子节点名称 */
        /* 子节点的属性 */
        label = "sw4";
        linux,code = <KEY_RESTART>;
        gpios = <&r_pio 0 3 GPIO_ACTIVE_LOW>;
    };
};

设备树节点与驱动的匹配

设备树和驱动既独立又联系。独立体现在他俩可以各自单独编译,独立存在;联系体现在他俩在运行时相互配合。那他俩是怎样配合的呢?

首先,内核必须要知道 dtb 文件的位置,这个由 uboot 来告诉内核,只要内核知道了 dtb 文件的位置,那么驱动就可以通过一些 API 获取设备树的内部信息。

接下来就是匹配,驱动程序通过设备树节点中的 compatible(兼容性)来与设备节点进行配对,如下

static const struct of_device_id gpio_keys_of_match[] = {
	{ .compatible = "gpio-keys", },
	{ },
};

static struct platform_driver gpio_keys_device_driver = {
	.probe		= gpio_keys_probe,
	.shutdown	= gpio_keys_shutdown,
	.driver		= {
		.name	= "gpio-keys",
		.pm	= &gpio_keys_pm_ops,
		.of_match_table = gpio_keys_of_match,
		.dev_groups	= gpio_keys_groups,
	}
};

设备树 r_gpio_keys 节点的 compatible 属性值为 “gpio-keys”,

驱动文件 gpio_keys.c 中 gpio_keys_device_driver.driver.of_match_table 中的 compatible 值也为 “gpio-keys”,

他俩就配对上了,👏👏👏。

配对发生在 late_initcall(gpio_keys_init) 后,

配对上后,对应的 probe 函数就会被触发。

static int gpio_keys_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
	struct fwnode_handle *child = NULL;
	struct gpio_keys_drvdata *ddata;
	struct input_dev *input;
	int i, error;
	int wakeup = 0;
...
	input->name = pdata->name ? : pdev->name;
	input->keycode = ddata->keymap;
...

	error = input_register_device(input);	// input: r_gpio_keys as ...

	device_init_wakeup(dev, wakeup);

	return 0;
}

在 probe 函数中将会赋能具体的属性和操作接口,然后调用 input_register_device() 注册一个输入设备,具体为注册 r_gpio_keys 设备为 /devices/platform/r_gpio_keys/input/input1 文件,可以通过 /proc/bus/input/devices 查看,

# cat /proc/bus/input/devices
...

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="r_gpio_keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/r_gpio_keys/input/input1
U: Uniq=
H: Handlers=event1 evbug
B: PROP=0
B: EV=3
B: KEY=1000000 0 0 0 0 0 0 0 0 0 0 0 0

/dev/input/eventX

看到 r_gpio_keys 对应 event1 设备,到这里,我们就可以在应用层很方便地读取 /dev/input/event1 文件来获取按键事件了

# hexdump /dev/input/event1
0000000 1dd8 0000 5f2b 000a 0001 0198 0001 0000
0000010 1dd8 0000 5f2b 000a 0000 0000 0000 0000

0000020 1dd8 0000 934e 000d 0001 0198 0000 0000
0000030 1dd8 0000 934e 000d 0000 0000 0000 0000

以第一行为例,1dd8 0000 表示按键时间(秒);5f2b 000a 表示按键时间(微秒);0001 表示事件类型是键盘;0198 为 code,键盘上的一个按键对应一个 code,0x198 为 KEY_RESTART,表示重启键;0001 0000 事件值,如果事件类型为 EV_KEY,当按下按键值为 1,松开值为0。

综上,第一行表示在 7640.679723 秒时重启按键被按下了。

事件类型:

/*
 * Event types
 */

#define EV_SYN			0x00	// 用于事件间的分割标志。事件可能按时间或空间进行分割,就像在多点触摸协议中的例子。
#define EV_KEY			0x01	// 键盘
#define EV_REL			0x02
#define EV_ABS			0x03
#define EV_MSC			0x04
#define EV_SW			0x05
#define EV_LED			0x11
#define EV_SND			0x12
#define EV_REP			0x14
#define EV_FF			0x15
#define EV_PWR			0x16
#define EV_FF_STATUS		0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

参考