Linux驅動開發10:【裝置樹】nanopi的按鍵驅動
介紹
這一節在nanopi上實現按鍵驅動,和LED驅動一樣,通用的按鍵驅動在linux核心中已經實現好,我們只需要按照要求寫好裝置樹即可,不用我們自己實現按鍵驅動。這一節中首先修改裝置樹並測試按鍵驅動,然後分析drivers/input/keyboard/gpio_keys.c
檔案,看按鍵驅動是如何實現的。
新增裝置樹節點
在sun8i-h3-neo-nanopi-air.dtsi
檔案中加入以下內容
/ {
mygpio-key {
compatible = "gpio-keys";
input-name = "key-test";
pinctrl-names = "default" ;
pinctrl-0 = <&test_pin1>;
[email protected]0 {
lable = "key-test";
linux,code = <BTN_0>;
gpios = <&pio 6 8 GPIO_ACTIVE_LOW>;
};
};
...
};
&pio {
...
test_pin1: [email protected]1 {
pins = "PG8" ;
function = "gpio_in";
};
};
和LED的裝置樹節點相似,按鍵的裝置樹節點也由兩部分組成,一部分是mygpio-key,另一部分是[email protected],其中[email protected]也是pio的子節點,表明了GPIO的編號和模式,在mygpio-key的pinctrl-0屬性中對其進行了引用。
mygpio-key的compatible屬性用來與驅動相匹配,input-name是輸入裝置的名稱,因為在linux核心的按鍵驅動中使用的是input子系統。接下來是[email protected]
測試
由於nanopi中預設是不支援通用的gpio-key驅動的,所以需要對核心進行重新配置和編譯。在核心根目錄執行
make menuconfig
然後進入Device Drivers --- Input device support --- Keyborads
,選中GPIO_Buttons,如下圖所示
儲存退出後重新編譯核心和裝置樹,然後啟動nanopi,進入/sys/class/input/event2
可以看到如下內容,其中name就是我們定義的key的名字
可見event2就是我們的按鍵輸入事件,使用以下程式讀取/dev/input/event2
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char const *argv[])
{
int fd = 0;
struct input_event event[2] = {0};
int ret = 0;
if (argc != 2) {
printf("./iread <file>\n");
return -1;
}
fd = open(argv[1],O_RDONLY);
if (fd < 0) {
perror("open");
return -1;
}
while(1){
ret = read(fd, &event, sizeof(event));
if(ret < 0) {
perror("read");
return -1;
}
printf("ret:%d, val0:%d, val1:%d\n", ret,
event[0].value, event[1].value);
sleep(1);
}
return 0;
}
將該檔案編譯為iread,然後執行sudo ./iread /dev/input/event2
,程式會阻塞,此時如果在PG8引腳接一個按鍵到3.3V,按下該按鍵會讀取該引腳的狀態,如下圖所示
按鍵驅動實現的分析
按鍵驅動的實現在drivers/input/keyborad/gpio_keys.c檔案中,這是一個platform驅動,platform_driver的定義如下:
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,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = gpio_keys_of_match,
}
};
這裡的compatible屬性和我們定義的裝置樹節點相同,gpio_keys_probe函式執行。
drivers/input/keyborad/gpio_keys.c --- gpio_keys_probe( --- struct gpio_keys_platform_data *pdata
| struct platform_device *pdev) |- struct gpio_keys_drvdata *ddata
| |- struct input_dev *input
| |- pdata = gpio_keys_get_devtree_pdata(dev)
| |- size = sizeof(struct gpio_keys_drvdata) +
| | pdata->nbuttons *
| | sizeof(struct gpio_button_data);
| |- ddata = devm_kzalloc(dev, size, GFP_KERNEL);
| |- input = devm_input_allocate_device(dev)
| |- input->open = gpio_keys_open
| |- input->close = gpio_keys_close
| |- input->keycodemax = pdata->nbuttons
| |- for (i = 0; i < pdata->nbuttons; i++) {
| | struct gpio_keys_button *button
| | = &pdata->buttons[i];
| | gpio_keys_setup_key(pdev, input,
| | ddata, button, i, child);
| | }
| |- input_register_device(input)
|- gpio_keys_get_devtree_pdata( --- int nbuttons;
| struct device *dev) |- struct gpio_keys_platform_data *pdata
| |- struct gpio_keys_button *button
| |- nbuttons = device_get_child_node_count(dev)
| |- pdata = devm_kzalloc(dev,
| | sizeof(*pdata)+nbuttons*sizeof(*button),
| | GFP_KERNEL);
| |- button = (struct gpio_keys_button *)
| | (pdata + 1);
| |- pdata->buttons = button
| |- pdata->nbuttons = nbuttons
| |- device_property_read_string(
| | dev,"label",&pdata->name);
| |- device_for_each_child_node(dev, child) {
| | fwnode_property_read_string(child,
| | "label", &button->desc);
| | fwnode_property_read_u32(child,
| | "linux,input-type", &button->type));
| | }
|- gpio_keys_setup_key( --- struct gpio_button_data *bdata
struct platform_device *pdev, |- bdata = &ddata->data[idx]
struct input_dev *input, |- bdata->input = input
struct gpio_keys_drvdata *ddata, |- bdata->gpiod =
const struct gpio_keys_button *button, | devm_fwnode_get_gpiod_from_child(
int idx, | dev, NULL,child,GPIOD_IN,desc)
struct fwnode_handle *child) |- irq = gpiod_to_irq(bdata->gpiod)
|- bdata->irq = irq
|- isr = gpio_keys_irq_isr
|- bdata->code = &ddata->keymap[idx]
|- *bdata->code = button->code
|- input_set_capability(
| input, button->type ?: EV_KEY, *bdata->code)
|- devm_request_any_context_irq(
| dev, bdata->irq, isr, irqflags,
| desc, bdata);
該過程和LED驅動一致,先是解析裝置樹節點的屬性填充私有資料,然後註冊中斷,對input進行填充,最後註冊input子系統