例程代碼路徑:ELF 1開發(fā)板資料包3-例程源碼3-2 驅(qū)動例程源碼8_input子系統(tǒng)keyboard
下面以控制開發(fā)板上的K2為例進行講解。
修改設(shè)備樹
(一)查看原理圖和引腳復用表格,可以得到K2由GPIO5_2控制,所以我們需要配置GPIO5_2引腳為輸入,而且能夠在用戶空間能夠獲取按鍵事件。
(二)在設(shè)備樹arch/arm/boot/dts/imx6ull-elf1-emmc.dts中添加keyboard節(jié)點和引腳復用,并檢查設(shè)備樹中是否有其它的地方也用到了此引腳,如果用到了就需要將其屏蔽掉,避免復用沖突。
添加節(jié)點:
keyboard {
compatible = "keyboard"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_keyboard>; gpios = <&gpio5 2 GPIO_ACTIVE_LOW>; }; |
添加后效果如下:
添加復用,因為GPIO5_IO2為SNVS引腳,所以需要在iomux_snvs中添加:
pinctrl_keyboard: keyboardgrp {
fsl,pins = < MX6ULL_PAD_SNVS_TAMPER2__GPIO5_IO02 ????0x17059 >; }; |
添加后效果如下:
(三)編譯設(shè)備樹
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs |
編譯生成的設(shè)備樹文件為imx6ull-elf1-emmc.dtb,參考《01-0 ELF1、ELF1S開發(fā)板_快速啟動手冊_V1》4.4節(jié)單獨更新設(shè)備樹。
驅(qū)動源碼my_keyboard.c編寫
(一)頭文件引用
include <linux/init.h>
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/device.h> ??????//包含設(shè)備節(jié)點相關(guān)的頭文件 #include <linux/gpio.h> ????????//包含gpio操作函數(shù)的相關(guān)頭文件 #include <linux/of_gpio.h> #include <linux/of.h> #include <linux/input.h> #include <linux/interrupt.h> |
(二)創(chuàng)建相關(guān)宏定義
struct device_node *node; ??//設(shè)備樹節(jié)點
int gpio; ??//gpio編號 struct keyboard { struct input_dev *input_dev; int irq; }; struct input_dev *input_dev; |
(三)定義platform_driver類型結(jié)構(gòu)體
static struct platform_driver my_platform_driver = {
.driver = { .name = "my_keyboard_driver", .owner = THIS_MODULE, .of_match_table = of_platform_match, }, .probe = keyboard_probe, .remove = my_platform_remove, }; |
(四)定義of_platform_match,用來與設(shè)備樹中的compatible匹配,匹配成功后才會進入到probe函數(shù)中
static const struct of_device_id of_platform_match[] = {
{ .compatible = "keyboard", }, {}, }; |
(五)probe函數(shù)的實現(xiàn)
static int keyboard_probe(struct platform_device *pdev)
{ struct keyboard *keyboard; int ret; node = of_find_node_by_name(NULL,"keyboard"); gpio = of_get_named_gpio(node, "gpios", 0); //為keyboard結(jié)構(gòu)體申請內(nèi)存 keyboard = devm_kzalloc(&pdev->dev,sizeof(*keyboard),GFP_KERNEL); if(!keyboard) return -ENOMEM; // 分配input設(shè)備結(jié)構(gòu) input_dev = input_allocate_device(); if(!input_dev) return -ENOMEM; keyboard->input_dev = input_dev; gpio_free(gpio); //申請GPIO編號 if (gpio_request(gpio, "keyboard")) { printk("request %s gpio%d faile n", "keyboard",gpio); return -1; } //設(shè)置為輸入 gpio_direction_input(gpio); keyboard->irq = gpio_to_irq(gpio); // 設(shè)置支持按鍵事件 set_bit(EV_KEY, input_dev->evbit); // 設(shè)置支持按鍵類型為KEY_ENTER set_bit(KEY_ENTER, input_dev->keybit); // 注冊輸入設(shè)備 ret = input_register_device(input_dev); if(ret) return ret; //申請中斷 ret = devm_request_irq(&pdev->dev, keyboard->irq, keyboard_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "keyboard_irq", keyboard); if (ret) { input_unregister_device(input_dev); return ret; } // 將輸入設(shè)備與平臺設(shè)備關(guān)聯(lián) platform_set_drvdata(pdev, input_dev); printk(KERN_INFO "my_platform_probe: Platform device probedn"); return 0; } |
可以看到probe函數(shù)中進行了一些列的初始化操作:
(1)獲取設(shè)備樹中的gpio引腳編號;
(2)分配input設(shè)備結(jié)構(gòu);
(3)申請GPIO編號并配置為輸入模式;
(4)設(shè)置按鍵事件和類型;
(5)注冊輸入設(shè)備;
(6)申請中斷;
(7)將輸入設(shè)備與平臺設(shè)備關(guān)聯(lián)。
(六)中斷服務函數(shù)實現(xiàn)
static irqreturn_t keyboard_irq_handler(int irq, void *dev_id)
{ struct keyboard *keyboard = dev_id; int value = gpio_get_value(gpio); input_report_key(keyboard->input_dev, KEY_ENTER, !value); input_sync(keyboard->input_dev); return IRQ_HANDLED; } |
中斷服務函數(shù)中讀取了引腳狀態(tài),然后通過input_report_key()和input_sync()函數(shù)將事件上報給用戶空間。
(1)input_report_key()原型:
void input_report_key(struct input_dev *dev, unsigned int code, int value); |
參數(shù)說明:
dev:指向 struct input_dev 的指針,表示目標輸入設(shè)備。
code:無符號整數(shù),表示按鍵碼(鍵值)。
value:整數(shù),表示按鍵狀態(tài)。0表示按鍵釋放,1表示按鍵按下,2表示按鍵重復。
input_report_key() 函數(shù)將按鍵事件報告給輸入設(shè)備 dev,并指定按鍵碼和按鍵狀態(tài)。該函數(shù)會更新 input_dev 結(jié)構(gòu)體中相應按鍵的狀態(tài)信息,并觸發(fā)輸入子系統(tǒng)將事件傳遞到用戶空間。
(2)input_sync()原型:
void input_sync(struct input_dev *dev); |
參數(shù)說明:
dev:指向 struct input_dev的指針,表示目標輸入設(shè)備。
input_sync()函數(shù)用于通知輸入子系統(tǒng)當前輸入設(shè)備的事件已經(jīng)全部報告完畢,需要立即將事件傳遞到用戶空間。在調(diào)用 input_report_*() 系列函數(shù)報告完輸入事件后,應當調(diào)用input_sync()函數(shù)以確保事件被及時處理。
完整的驅(qū)動my_keyboard.c示例源碼
#include <linux/init.h>
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/device.h> ??????//包含設(shè)備節(jié)點相關(guān)的頭文件 #include <linux/gpio.h> ????????//包含gpio操作函數(shù)的相關(guān)頭文件 #include <linux/of_gpio.h> #include <linux/of.h> #include <linux/input.h> #include <linux/interrupt.h> struct device_node *node; ??//設(shè)備樹節(jié)點 int gpio; ??//gpio編號 struct keyboard { struct input_dev *input_dev; int irq; }; struct input_dev *input_dev; static irqreturn_t keyboard_irq_handler(int irq, void *dev_id) { struct keyboard *keyboard = dev_id; int value = gpio_get_value(gpio); printk("------------------n"); input_report_key(keyboard->input_dev, KEY_ENTER, !value); input_sync(keyboard->input_dev); return IRQ_HANDLED; } static int keyboard_probe(struct platform_device *pdev) { struct keyboard *keyboard; int ret; node = of_find_node_by_name(NULL,"keyboard");  gpio = of_get_named_gpio(node, "gpios", 0);  keyboard = devm_kzalloc(&pdev->dev,sizeof(*keyboard),GFP_KERNEL);  if(!keyboard)   return -ENOMEM;  input_dev = input_allocate_device();  if(!input_dev)   return -ENOMEM;  keyboard->input_dev = input_dev;  gpio_free(gpio);  printk("-------------gpio=%d---------n",gpio);  if (gpio_request(gpio, "keyboard")) { printk("request %s gpio%d faile n", "keyboard",gpio); return -1; }  //設(shè)置為輸入  gpio_direction_input(gpio);  keyboard->irq = gpio_to_irq(gpio);  // 設(shè)置支持按鍵事件  set_bit(EV_KEY, input_dev->evbit);  // 設(shè)置支持按鍵類型為KEY_ENTER  set_bit(KEY_ENTER, input_dev->keybit);    // 注冊輸入設(shè)備  ret = input_register_device(input_dev);  if(ret)   return ret;  ret = devm_request_irq(&pdev->dev, keyboard->irq, keyboard_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "keyboard_irq", keyboard);  if (ret) {   input_unregister_device(input_dev);   return ret;  }  // 將輸入設(shè)備與平臺設(shè)備關(guān)聯(lián)  platform_set_drvdata(pdev, input_dev); printk(KERN_INFO "my_platform_probe: Platform device probedn"); return 0; } static int my_platform_remove(struct platform_device *pdev) {  input_unregister_device(input_dev);  input_free_device(input_dev); printk(KERN_INFO "my_platform_remove: Platform device removedn"); return 0; } static const struct of_device_id of_platform_match[] = { { .compatible = "keyboard", }, {}, }; static struct platform_driver my_platform_driver = { .driver = { .name = "my_keyboard_driver", .owner = THIS_MODULE, .of_match_table = of_platform_match, }, .probe = keyboard_probe, .remove = my_platform_remove, }; module_platform_driver(my_platform_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Platform Driver Example"); |
編譯
復制7.8.3驅(qū)動中的Makefile文件,將其中的elf-aht20.o修改為my_keyboard.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/08_input子系統(tǒng)/keyboard$ make |
將編譯生成的my_keyboard.ko模塊拷貝到開發(fā)板。
測試
root@ELF1:~# insmod my_keyboard.ko
-------------gpio=130--------- input: Unspecified device as /devices/virtual/input/input4 my_platform_probe: Platform device probed |
加載驅(qū)動后,打印信息中可以看到,已經(jīng)進入到了probe函數(shù),輸出如下命令查看對應的event事件:
root@ELF1:~#?cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000 N: Name="20cc000.snvs:snvs-powerkey" P: Phys=snvs-pwrkey/input0 S: Sysfs=/devices/platform/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0 U: Uniq= H: Handlers=kbd event0 B: PROP=0 B: EV=3 B: KEY=100000 0 0 0 I: Bus=0019 Vendor=0000 Product=0000 Version=0000 N: Name="iMX6UL TouchScreen Controller" P: Phys= S: Sysfs=/devices/platform/soc/2000000.aips-bus/2040000.tsc/input/input1 U: Uniq= H: Handlers=mouse0 event1 B: PROP=0 B: EV=b B: KEY=400 0 0 0 0 0 0 0 0 0 0 B: ABS=3 I: Bus=0000 Vendor=0000 Product=0000 Version=0000 N: Name="" P: Phys= S: Sysfs=/devices/virtual/input/input2 U: Uniq= H: Handlers=kbd event2 B: PROP=0 B: EV=3 B: KEY=10000000 |
可以看到生成的是event2的節(jié)點。接下來使用evtest檢查這個節(jié)點,按下抬起K2按鍵,會上報對應的鍵值和事件:
root@ELF1:~# evtest /dev/input/event2
Input driver version is 1.0.1 Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0 Input device name: "Unknown" Supported events: Event type 0 (EV_SYN) Event type 1 (EV_KEY) Event code 28 (KEY_ENTER) Properties: Testing ... (interrupt to exit) ------------------ Event: time 1691609102.952275, type 1 (EV_KEY), code 28 (KEY_ENTER), value 1 Event: time 1691609102.952275, -------------- SYN_REPORT ------------ ------------------ ------------------ Event: time 1691609103.106850, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0 Event: time 1691609103.106850, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609103.682438, type 1 (EV_KEY), code 28 (KEY_ENTER), value 1 Event: time 1691609103.682438, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609103.831401, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0 Event: time 1691609103.831401, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609104.223788, type 1 (EV_KEY), code 28 (KEY_ENTER), value 1 Event: time 1691609104.223788, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609104.430191, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0 Event: time 1691609104.430191, -------------- SYN_REPORT ------------ root@ELF1:~# rmmod my_keyboard.ko my_platform_remove: Platform device removed |