(三)编译设备树
. /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
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
驱动源码my_keyboard.c编写
(一)头文件引用
include
#include // 包含模块相关函数的头文件
#include // 包含文件系统相关函数的头文件
#include
#include
#include //包含设备节点相关的头文件
#include //包含gpio操作函数的相关头文件
#include
#include
#include
#include
(二)创建相关宏定义
struct device_node *node; //设备树节点
int gpio; //gpio编号
struct keyboard {
struct input_dev *input_dev;
int irq;
};
struct input_dev *input_dev;
(三)定义platform_driver类型结构体
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,用来与设备树中的compatible匹配,匹配成功后才会进入到probe函数中
static const struct of_device_id of_platform_match[] = {
{ .compatible = "keyboard", },
{},
};
(五)probe函数的实现
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结构体申请内存
keyboard = devm_kzalloc(&pdev->dev,sizeof(*keyboard),GFP_KERNEL);
if(!keyboard)
return -ENOMEM;
// 分配input设备结构
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;
}
//设置为输入
gpio_direction_input(gpio);
keyboard->irq = gpio_to_irq(gpio);
// 设置支持按键事件
set_bit(EV_KEY, input_dev->evbit);
// 设置支持按键类型为KEY_ENTER
set_bit(KEY_ENTER, input_dev->keybit);
// 注册输入设备
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;
}
// 将输入设备与平台设备关联
platform_set_drvdata(pdev, input_dev);
printk(KERN_INFO "my_platform_probe: Platform device probed\n");
return 0;
}
可以看到probe函数中进行了一些列的初始化操作:
(1)获取设备树中的gpio引脚编号;
(2)分配input设备结构;
(3)申请GPIO编号并配置为输入模式;
(4)设置按键事件和类型;
(5)注册输入设备;
(6)申请中断;
(7)将输入设备与平台设备关联。
(六)中断服务函数实现
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;
}
中断服务函数中读取了引脚状态,然后通过input_report_key()和input_sync()函数将事件上报给用户空间。
(1)input_report_key()原型:
void input_report_key(struct input_dev *dev, unsigned int code, int value);
参数说明:
dev:指向 struct input_dev 的指针,表示目标输入设备。
code:无符号整数,表示按键码(键值)。
value:整数,表示按键状态。0表示按键释放,1表示按键按下,2表示按键重复。
input_report_key() 函数将按键事件报告给输入设备 dev,并指定按键码和按键状态。该函数会更新 input_dev 结构体中相应按键的状态信息,并触发输入子系统将事件传递到用户空间。
(2)input_sync()原型:
void input_sync(struct input_dev *dev);
参数说明:
dev:指向 struct input_dev的指针,表示目标输入设备。
input_sync()函数用于通知输入子系统当前输入设备的事件已经全部报告完毕,需要立即将事件传递到用户空间。在调用 input_report_*() 系列函数报告完输入事件后,应当调用input_sync()函数以确保事件被及时处理。
完整的驱动my_keyboard.c示例源码
#include
#include // 包含模块相关函数的头文件
#include // 包含文件系统相关函数的头文件
#include
#include
#include //包含设备节点相关的头文件
#include //包含gpio操作函数的相关头文件
#include
#include
#include
#include
struct device_node *node; //设备树节点
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;
}
//设置为输入
gpio_direction_input(gpio);
keyboard->irq = gpio_to_irq(gpio);
// 设置支持按键事件
set_bit(EV_KEY, input_dev->evbit);
// 设置支持按键类型为KEY_ENTER
set_bit(KEY_ENTER, input_dev->keybit);
// 注册输入设备
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;
}
// 将输入设备与平台设备关联
platform_set_drvdata(pdev, input_dev);
printk(KERN_INFO "my_platform_probe: Platform device probed\n");
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 removed\n");
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驱动中的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子系统/keyboard$ make
将编译生成的my_keyboard.ko模块拷贝到开发板。
测试
root@ELF1:~# insmod my_keyboard.ko
-------------gpio=130---------
input: Unspecified device as /devices/virtual/input/input4
my_platform_probe: Platform device probed
加载驱动后,打印信息中可以看到,已经进入到了probe函数,输出如下命令查看对应的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的节点。接下来使用evtest检查这个节点,按下抬起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