下文介绍了ACPI规范对通用按钮设备的支持情况。
1、概述
通用按钮设备是一种标准设备,用于通过硬件中断报告按钮事件,并将这些中断映射到人机接口设备(HID)规范中定义的特定usage。这些设备包括:音量控制,摄像头控制,背光控制,wifi控制。为了向OS表示按钮的功能,需要两条信息:HID控件的Usage和控件所属的HID集合的Us/age。这类设备的HID值为:ACPI0011。
Usage是HID Report Descriptor的一部分,可用来表明一个特殊的控制,或一组控制。每个Usage包含一个usage ID, usage name, 和一个详细的描述。Usage由16bit的Usage page和16bit的Usage ID组成。例如,“音量调高”按钮在使用者控件集合(“Usage page 为0x0C,Usage ID为 0x01”)中被标识为“音量调高使用量”(Usage page 为0x0C,Usage ID为 0xE9)。
下表通用按键设备控制器方法
名称 | 描述 |
_CRS | 仅中断资源(GpioInt()和Interrupt())对此设备有效。列出的每个中断都必须发出一个不同的按钮事件信号。 |
_DSD | 提供了HID按钮设备的描述符,使用UUID FA6BD625-9CE8-470D-A2C7-B3CA36C4282E. |
2、中断
通用按钮设备的中断要求沿边沿触发而不是电平触发,因为在接收到中断后,没有为驱动程序定义接口来使驱动程序停顿中断线。 中断的极性(ActiveLow / High与ActiveBoth)由与中断关联的HIDusage的usage类型确定,下表中所述
表设备极性说明
usage类型 | 中断极性 | 说明 |
OSC - One Shot Control | ActiveHigh/ ActiveLow | 按下按键产生中断事件 示例:静音按钮 |
MC - Momentary Control | ActiveBoth | 按下和释放按钮均触发一个中断。 示例:鼠标左键。 |
RTC - Re-trigger Control | ActiveBoth | 按下和释放按钮均应触发一个中断。在按下按钮时,操作系统将反复重新执行按下按钮时将执行的操作。示例:按住音量调高按钮时,将反复增加音量。 |
OOC - On/Off Control | ActiveHigh/ ActiveLow OR ActiveBoth | 可以通过以下任何一种方式来实现开/关控制:
|
尽管原则上可以使用_DSM(特定于设备的方法)来实现_DSD提供的功能,但不建议这样做。由于_DSD更适合提供设备配置数据,因此应在适用的情况下将其用于此目的。_DSD每次评估时都必须返回相同的数据,因此,如果不能保证,则必须使用_DSM代替它。注意:_DSD除了返回数据外,不应写入设备寄存器。
3、按钮usage和集合
HID usage表包含各种按钮的标准化usage的详尽列表。 下中列出了在计算设备上找到的一些常用按钮及其usage。有关完整列表,请参阅“HID Usage Tables”。
按钮被分组在HID集合下。 操作系统通常会理解几个HID集合,例如键盘集合,用户控件集合,无线无线电控件集合等。
Button | Usage Page / Usage | Usage Type | Interrupt Polarity |
Power | Generic Desktop Page (0x01) System Power Down (0x01) | OSC | ActiveBoth |
Volume Up | Consumer Page (0x0C) Volume Increment (0xE9) | RTC | ActiveBoth |
Volume Down | Consumer Page (0x0C) Volume Decrement (0xEA) | RTC | ActiveBoth |
Camera Shutter | Camera Control Page (0x90) Camera Shutter (0x21) | OSC | Active High/ ActiveLow |
Display Brightness Up | Consumer Page (0x0C) Display Brightness Increment (0x6F) | RTC | ActiveBoth |
Display Brightness Down | Consumer Page (0x0C) Display Brightness Decrement (0x6F) | RTC | ActiveBoth |
Wireless Radio Button | Generic Desktop Page (0x01) Wireless Radio Button (0xC6) | OOC | ActiveHigh/ ActiveLow |
Wireless Radio Slider Switch | Generic Desktop Page (0x01) Wireless Radio Slider Switch (0xC8) | OOC | ActiveBoth |
4 流程及实现
- 固件中的实现
在UEFI DSDT中添加下面对象:
Device (BTNS)
{
Name (_HID, "ACPI0011")
Name (_CRS, ResourceTemplate() {
GpioInt(Edge, ActiveBoth, ExclusiveAndWake, PullUp, , "\\_SB.GPO0")
})
Name (_DSD, Package(2) {
ToUUID ("FA6BD625-9CE8-470D-A2C7-B3CA36C4282E"),
Package() {
Package(5) {
0,
1,
0,
0x0c,
0x01
},
Package(5) {
1,
0,
1,
0x01,
0x81
},
}
})
}
HID控件的Usage和控件所属的HID集合的Us/age。这类设备的HID值为:ACPI0011。内核驱动soc_button_array.c通过识别此HID来加载相应驱动。
GpioInt宏申明了此GPIO中断管教信息。每个中断引脚都配置为在两个边沿都中断的非共享(独占),边沿触发(边沿)中断资源(ActiveBoth)。
\\_SB.GPO0查找名称空间中GPIO控制器对象,解析相关信息。
UUID:FA6BD625-9CE8-470D-A2C7-B3CA36C4282E唯一表示了_DSD对象作为申明Generic Buttons Device设备使用。
Package对象中包含了按钮相关信息,通过解析这些package对象来确定具体按钮。
5. 内核中的流程
5.1设备初始化流程
在drivers/input/misc/soc_button_array.c文件soc_button_probe函数中,通过调用soc_button_get_button_info函数进行generic按钮设备对象的初始化。
soc_button_get_button_info评估_DSD对象,获取关于GPIO按钮及中断信息。
最后调用soc_button_device_create接口将设备注册到内核中。
5.2设备驱动初始化
在drivers/input/keyboard/gpio_keys.c文件gpio_keys_probe函数中,获取设备初始化信息,调用gpio_keys_setup_key函数配置对应GPIO管脚中断,并注册相应中断处理函数。
调用input_register_device函数将设备注册到input子系统。
如按钮设备支持唤醒系统,则调用device_init_wakeup函数将设备作为唤醒源注册到系统中。
5.3中断触发
在gpio_keys_setup_key()函数中,先使用gpio_is_valid来测试端口是否合法,然后使用gpio_request_one进行GPIO的申请,同时将引脚配置成输入,通过irq =gpio_to_irq(button_irq)将引脚设为外部中断,同时获得的中断号赋值给bdata_irq,然后设置外部中断服务函数isr = gpio_keys_gpio_isr以及配置中断处理标志irqflag为上升沿触发和下降沿触发,。最后通过 error =request_any_context_irq(bdata->irq, isr, irqflags,desc, bdata)来完成中断服务的注册。
在对input进行配置时,由input->open =gpio_keys_open;
input->close=gpio_keys_close可知通过gpio_keys_open/gpio_keys_close
函数可以对GPIO端口进行打开和关闭,对GPIO端口进行控制。
该程序中具体的中断流程如下:
(1) 首先完成中断的配置以及注册
(2)按键按下时,触发外部中断,调用中断服务程序gpio_keys_gpio_isr,使用定时器进行消抖,然后调用工作队列中的gpio_keys_gpio_work_func()函数
(3)通过gpio_keys_gpio_report_events()函数中的gpio_get_value_cansleep(button_gpio)函数来对按键进行扫描,获得按键值,使用input_event传递事件信息,最后使用input_sync()完成事件的同步。
(4)最后使用intgpio_keys_remove()中完成卸载,该函数主要是删除定时器,取消工作队列的工作,释放申请的引脚, 通过input_unregister_device(input)注销输入设备, 最后通过kfree(ddata)释放之前申请的数据内存空间。
6、调试问题
调通从DSDT中添加相关上述对象解析,以及后续gpio-key驱动使用,到上报带input核心整个过程遇到的问题:
1、[ 3.105468] No GPIO consumer (null) found, desc: ffffffea
在解析GpioInt(Edge, ActiveBoth, ExclusiveAndWake, PullUp, , "\\_SB.GPO0") 时,获取到_SB.GPO0对象,解析GPIO信息时出错。
2、[ 6.671875] gpio-keys gpio-keys.0.auto: Unable to claim irq 87; error -22
[ 6.679687] gpio-keys: probe of gpio-keys.0.auto failed with error -22
目前龙芯GPIO驱动不支持IRQ_TYPE_EDGE_BOTH类型中断出发方式。添加对应代码解决。
3、[ 6.792968] gpio-keys gpio-keys.0.auto: Unable to get irq number for GPIO 19, error -6
目前龙芯GPIO驱动没有实现chip.to_irq函数。添加对应代码解决。
4、上述解析完成后,将GPIO03管脚引出,手动触发中断,无中断相应。
添加irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW);设置中断出发方式,产生相应中断。