嵌入式Linux输入子系统驱动开发

发布于:2025-09-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

嵌入式Linux输入子系统驱动开发

1. 输入子系统概述

Linux输入子系统是内核中专门处理输入设备的框架,它为各种输入设备(如键盘、鼠标、触摸屏、游戏手柄等)提供统一的接口和抽象层。输入子系统的主要目标是简化输入设备驱动的开发,提高代码的可重用性,并为用户空间应用程序提供一致的访问接口。

1.1 设计目标

  • 抽象化: 将不同类型的输入设备抽象为统一的接口
  • 模块化: 允许独立开发和加载不同的输入设备驱动
  • 标准化: 提供标准的事件报告机制
  • 可扩展性: 支持新类型输入设备的轻松集成

1.2 子系统组成

Linux输入子系统主要由三部分组成:

  1. 驱动层(Driver Layer): 负责与具体硬件交互,将硬件事件转换为标准输入事件
  2. 核心层(Core Layer): 提供统一的API和事件处理机制
  3. 事件处理层(Event Handler Layer): 将输入事件传递给用户空间应用程序

2. 输入子系统核心架构

2.1 核心数据结构

输入子系统的核心是input_dev结构体,它定义了输入设备的基本属性和操作方法:

struct input_dev {
    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;
    
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
    
    unsigned int keycodemax;
    unsigned int keycodesize;
    void *keycode;
    
    int (*setkeycode)(struct input_dev *dev,
                      const struct input_keymap_entry *ke,
                      unsigned int *old_keycode);
    int (*getkeycode)(struct input_dev *dev,
                      struct input_keymap_entry *ke);
    
    struct input_absinfo *absinfo;
    
    unsigned int rep[REP_CNT];
    
    struct timer_list timer;
    
    int (*open)(struct input_dev *dev);
    void (*close)(struct input_dev *dev);
    int (*flush)(struct input_dev *dev, struct file *file);
    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
    
    struct input_handle *grab;
    
    spinlock_t event_lock;
    struct mutex mutex;
    
    unsigned int users;
    bool going_away;
    
    bool sync;
    
    struct device dev;
    
    struct list_head    h_list;
    struct list_head    node;
};

2.2 事件类型

输入子系统定义了多种事件类型,每种类型对应不同的输入行为:

  • EV_SYN: 同步事件,用于分隔事件包
  • EV_KEY: 按键事件,包括键盘、按钮等
  • EV_REL: 相对坐标事件,如鼠标移动
  • EV_ABS: 绝对坐标事件,如触摸屏
  • EV_MSC: 杂项事件,用于传输设备特定信息
  • EV_SW: 开关事件,用于检测设备状态变化
  • EV_LED: LED事件,用于控制设备LED
  • EV_SND: 声音事件,用于控制设备声音
  • EV_REP: 重复事件,用于按键重复
  • EV_FF: 力反馈事件,用于振动反馈
  • EV_PWR: 电源事件,用于电源管理

2.3 事件编码

每种事件类型都有相应的编码,用于标识具体的输入源。例如:

  • KEY_0KEY_9: 数字键
  • KEY_AKEY_Z: 字母键
  • KEY_ENTER: 回车键
  • KEY_SPACE: 空格键
  • BTN_LEFT: 鼠标左键
  • BTN_RIGHT: 鼠标右键
  • ABS_X, ABS_Y: 绝对X、Y坐标

3. 事件类型与编码

3.1 事件类型详解

EV_KEY

按键事件是最常见的输入事件类型,用于表示按键的按下和释放。每个按键事件包含三个主要信息:

  • type: 事件类型,固定为EV_KEY
  • code: 按键编码,标识具体哪个按键
  • value: 按键状态,0表示释放,1表示按下,2表示自动重复
EV_SYN

同步事件用于分隔事件包,确保事件的原子性。当多个相关事件需要同时处理时,它们会被打包在一个事件包中,以EV_SYN事件结束。常见的同步事件包括:

  • SYN_REPORT: 表示一个完整的事件包结束
  • SYN_CONFIG: 表示设备配置发生变化
  • SYN_MT_REPORT: 多点触控事件报告

3.2 事件编码规范

Linux内核定义了标准的事件编码,确保不同设备之间的兼容性。编码遵循以下原则:

  • 可读性: 编码名称应直观反映其功能
  • 一致性: 相同类别的设备使用相似的编码方案
  • 扩展性: 预留足够的编码空间以支持新设备

4. 输入设备注册流程

4.1 设备注册步骤

输入设备的注册流程遵循以下步骤:

  1. 分配输入设备结构体
    使用input_allocate_device()函数分配input_dev结构体

  2. 设置设备属性

    • 设置设备名称name
    • 设置物理路径phys
    • 设置唯一标识uniq
  3. 设置支持的事件类型
    使用__set_bit()函数设置设备支持的事件类型位图

  4. 设置支持的按键编码
    对于按键设备,需要设置支持的按键编码

  5. 注册输入设备
    使用input_register_device()函数将设备注册到输入子系统

  6. 设备注销
    在模块卸载时,使用input_unregister_device()注销设备

4.2 代码示例

static int __init keyinput_init(void)
{
    int ret = 0;

    // 初始化按键
    ret = key_init(&keyinputdev);
    if (ret < 0) {
        goto fail_key_init;
    }

    // 分配输入设备
    keyinputdev.inputdev = input_allocate_device();
    if (keyinputdev.inputdev == NULL) {
        ret = -EINVAL;
        goto fail_input_allocate;
    }

    // 设置设备名称
    keyinputdev.inputdev->name = KEYINPUT_NAME;
    
    // 设置支持的事件类型
    __set_bit(EV_KEY, keyinputdev.inputdev->evbit);
    __set_bit(EV_REP, keyinputdev.inputdev->evbit);
    
    // 设置支持的按键
    __set_bit(KEY_0, keyinputdev.inputdev->keybit);

    // 注册输入设备
    ret = input_register_device(keyinputdev.inputdev);
    if (ret) {
        ret = -EINVAL;
        goto fail_input_reg;
    }

    return 0;
    
fail_input_reg:
    input_free_device(keyinputdev.inputdev);
fail_input_allocate:
fail_key_init:
    return ret;
}

5. 中断处理机制

5.1 中断请求

在嵌入式系统中,按键通常通过GPIO引脚连接到处理器。当按键状态发生变化时,会触发GPIO中断。驱动程序需要注册中断处理函数来响应这些中断。

5.1.1 中断注册
ret = request_irq(dev->key[i].irqnum, 
                  dev->key[i].handler, 
                  IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
                  dev->key[i].name, 
                  &keyinputdev);

参数说明:

  • irqnum: 中断号
  • handler: 中断处理函数
  • flags: 中断触发方式
    • IRQF_TRIGGER_FALLING: 下降沿触发
    • IRQF_TRIGGER_RISING: 上升沿触发
    • IRQF_TRIGGER_EDGE: 边沿触发
    • IRQF_TRIGGER_LEVEL: 电平触发
  • name: 中断名称
  • dev_id: 设备标识,用于共享中断

5.2 中断处理函数

中断处理函数应该尽可能简洁,避免长时间占用中断上下文。通常的做法是将耗时的操作推迟到下半部执行。

static irqreturn_t key0_handler(int irq, void *filp)
{
    struct keyinput_dev *dev = filp;
    dev->timer.data = (volatile long)filp;

    // 启动定时器进行防抖
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));

    return IRQ_HANDLED;
}

5.3 中断下半部

Linux内核提供了多种机制来处理中断下半部:

  • 软中断(Softirq)
  • tasklet
  • 工作队列(Workqueue)
  • 定时器(Timer)

在本例中,使用定时器作为中断下半部,既实现了防抖功能,又完成了按键状态的检测。

6. 定时器防抖技术

6.1 按键抖动问题

机械按键在按下和释放时会产生电气抖动,导致短时间内多次触发中断。如果不进行处理,会导致按键事件被错误地报告多次。

6.2 防抖解决方案

常见的防抖方法包括:

  1. 硬件防抖: 使用RC电路或施密特触发器
  2. 软件防抖: 使用延时检测

本驱动采用软件防抖方案,通过定时器实现:

static void timer_func(unsigned long arg)
{
    struct keyinput_dev *dev = (struct keyinput_dev *)arg;

    int value = 0;
    value = gpio_get_value(dev->key[0].gpio);
    if (value == 0)
    {
        input_event(dev->inputdev, EV_KEY, KEY_0, 1);
        input_sync(dev->inputdev);
    }
    else
    {
        input_event(dev->inputdev, EV_KEY, KEY_0, 0);
        input_sync(dev->inputdev);
    }
}

6.3 防抖参数选择

防抖时间的选择需要平衡响应速度和可靠性:

  • 过短: 无法有效消除抖动
  • 过长: 影响用户体验

通常,机械按键的抖动时间为5-20ms,因此选择20ms作为防抖时间是合理的。

7. 设备树(DTS)配置

7.1 设备树概述

设备树(Device Tree)是一种描述硬件配置的数据结构,它将硬件信息从内核代码中分离出来,提高了代码的可移植性和可维护性。

7.2 按键节点配置

key{
    compatible = "alientek,key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    states = "okay";
    key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
    interrupt-parent = <&gpio1>;
    interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
7.2.1 属性说明
  • compatible: 兼容性字符串,用于匹配驱动程序
  • pinctrl-names: 引脚控制状态名称
  • pinctrl-0: 默认状态下的引脚配置
  • key-gpios: GPIO引脚配置
    • &gpio1: GPIO控制器
    • 18: GPIO编号
    • GPIO_ACTIVE_HIGH: 高电平有效
  • interrupt-parent: 中断父节点
  • interrupts: 中断配置
    • 18: 中断号
    • IRQ_TYPE_EDGE_BOTH: 边沿触发(上升沿和下降沿)

7.3 引脚控制配置

pinctrl_key: keygrp {
    fsl,pins = <
        MX6UL_PAD_UART1_CTS_B__GPIO1_IO18    0xF080
    >;
};
7.3.1 引脚配置参数
  • MX6UL_PAD_UART1_CTS_B__GPIO1_IO18: 引脚复用配置
  • 0xF080: 电气特性配置
    • 驱动强度
    • 上拉/下拉电阻
    • 压摆率等

8. 驱动代码详细分析

8.1 头文件包含

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
8.1.1 头文件功能
  • linux/module.h: 模块相关定义
  • linux/kernel.h: 内核常用宏和函数
  • linux/init.h: 初始化相关宏
  • linux/fs.h: 文件系统相关定义
  • asm/io.h: I/O操作函数
  • asm/uaccess.h: 用户空间访问函数
  • linux/cdev.h: 字符设备相关定义
  • linux/device.h: 设备模型相关定义
  • linux/of.h: 设备树相关定义
  • linux/of_address.h: 设备树地址相关定义
  • linux/slab.h: 内存分配函数
  • linux/gpio.h: GPIO操作函数
  • linux/of_gpio.h: 设备树GPIO操作函数
  • linux/string.h: 字符串操作函数
  • linux/interrupt.h: 中断相关定义
  • linux/irq.h: IRQ相关定义
  • linux/input.h: 输入子系统相关定义

8.2 宏定义

#define KEYINPUT_CNT 1
#define KEYINPUT_NAME "keyinputdev"
#define KEY0VALUE 0x10
#define KEYINVA 0xff
#define KEY_NUM 1
8.2.1 宏说明
  • KEYINPUT_CNT: 设备计数
  • KEYINPUT_NAME: 设备名称
  • KEY0VALUE: 按键值
  • KEYINVA: 无效按键值
  • KEY_NUM: 按键数量

8.3 数据结构定义

8.3.1 key_desc结构体
struct key_desc
{
    char name[10];
    int gpio;
    int irqnum;
    unsigned char value;
    irqreturn_t (*handler)(int, void *);
};

该结构体描述单个按键的属性:

  • name: 按键名称
  • gpio: GPIO编号
  • irqnum: 中断号
  • value: 按键值
  • handler: 中断处理函数
8.3.2 keyinput_dev结构体
struct keyinput_dev
{
    struct device_node *key_nd;
    struct key_desc key[KEY_NUM];
    struct timer_list timer;

    struct input_dev *inputdev;
};

该结构体描述整个输入设备:

  • key_nd: 设备树节点
  • key: 按键数组
  • timer: 定时器
  • inputdev: 输入设备结构体

8.4 函数实现

8.4.1 中断处理函数
static irqreturn_t key0_handler(int irq, void *filp)
{
    struct keyinput_dev *dev = filp;
    dev->timer.data = (volatile long)filp;

    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));

    return IRQ_HANDLED;
}

功能说明:

  1. 获取设备结构体指针
  2. 设置定时器数据
  3. 启动定时器(20ms后执行)
  4. 返回中断处理完成
8.4.2 定时器回调函数
static void timer_func(unsigned long arg)
{
    struct keyinput_dev *dev = (struct keyinput_dev *)arg;

    int value = 0;
    value = gpio_get_value(dev->key[0].gpio);
    if (value == 0)
    {
        input_event(dev->inputdev, EV_KEY, KEY_0, 1);
        input_sync(dev->inputdev);
    }
    else
    {
        input_event(dev->inputdev, EV_KEY, KEY_0, 0);
        input_sync(dev->inputdev);
    }
}

功能说明:

  1. 获取设备结构体指针
  2. 读取GPIO引脚状态
  3. 根据状态报告按键事件
  4. 同步事件
8.4.3 定时器初始化函数
void timer1_init(struct keyinput_dev *dev)
{
    init_timer(&dev->timer);
    dev->timer.function = timer_func;
}

功能说明:

  1. 初始化定时器
  2. 设置定时器回调函数
8.4.4 按键初始化函数
int key_init(struct keyinput_dev *dev)
{
    u8 ret = 0, i = 0;
    dev->key_nd = of_find_node_by_path("/key");
    if (dev->key_nd == NULL)
    {
        ret = -EFAULT;
        goto fail_find_nd;
    }
    dev->key[i].handler = key0_handler;
    dev->key[i].value = KEY0VALUE;
    for (i = 0; i < KEY_NUM; i++)
    {
        dev->key[i].gpio = of_get_named_gpio(dev->key_nd, "key-gpios", i);
        if (dev->key[i].gpio < 0)
        {
            ret = -EFAULT;
            goto fail_get_gpio;
        }

        memset(dev->key[i].name, 0, sizeof(dev->key[i].name));
        sprintf(dev->key[i].name, "KEY%d", i);
        ret = gpio_request(dev->key[i].gpio, dev->key[i].name);
        if (ret)
        {
            ret = -EFAULT;
            goto fail_gpio_req;
        }

        ret = gpio_direction_input(dev->key[i].gpio);
        if (ret)
        {
            ret = -EFAULT;
            goto fail_gpio_dir;
        }

        dev->key[i].irqnum = gpio_to_irq(dev->key[i].gpio);
        ret = request_irq(dev->key[i].irqnum, dev->key[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, dev->key[i].name, &keyinputdev);
        if (ret)
        {
            ret = -EFAULT;
            goto fail_req_irq;
        }
    }

    timer1_init(&keyinputdev);

    return 0;
fail_req_irq:
    printk("Kernel: fail_req_irq\r\n");
fail_gpio_dir:
    for (i = 0; i < KEY_NUM; i++)
    {
        gpio_free(dev->key[i].gpio);
    }
    printk("Kernel: fail_gpio_dir\r\n");
fail_gpio_req:
    printk("Kernel: fail_gpio_req\r\n");
fail_get_gpio:
    printk("Kernel: fail_get_gpio\r\n");
fail_find_nd:
    printk("Kernel: fail_find_nd\r\n");
    return ret;
}

功能说明:

  1. 查找设备树节点
  2. 配置按键属性
  3. 获取GPIO编号
  4. 请求GPIO
  5. 设置GPIO方向为输入
  6. 获取中断号
  7. 请求中断
  8. 初始化定时器

错误处理采用goto模式,确保资源正确释放。

8.4.5 模块初始化函数
static int __init keyinput_init(void)
{
    int ret = 0;

    ret = key_init(&keyinputdev);
    if (ret < 0)
    {
        goto fail_key_init;
    }

    keyinputdev.inputdev = input_allocate_device();
    if (keyinputdev.inputdev == NULL)
    {
        ret = -EINVAL;
        goto fail_input_allocate;
    }

    keyinputdev.inputdev->name = KEYINPUT_NAME;
    __set_bit(EV_KEY, keyinputdev.inputdev->evbit);
    __set_bit(EV_REP, keyinputdev.inputdev->evbit);
    __set_bit(KEY_0, keyinputdev.inputdev->keybit);

    ret = input_register_device(keyinputdev.inputdev);
    if (ret)
    {
        ret = -EINVAL;
        goto fail_input_reg;
    }

    return 0;
fail_input_reg:
    input_free_device(keyinputdev.inputdev);
    printk("Driver: fail_input_reg\r\n");
fail_input_allocate:
    printk("Driver: fail_input_allocate\r\n");
fail_key_init:
    printk("Driver: fail_key_init\r\n");
    return ret;
}

功能说明:

  1. 初始化按键
  2. 分配输入设备
  3. 设置设备属性
  4. 注册输入设备
8.4.6 模块退出函数
static void __exit keyinput_exit(void)
{
    u8 i = 0;
    input_unregister_device(keyinputdev.inputdev);
    input_free_device(keyinputdev.inputdev);

    for (i = 0; i < KEY_NUM; i++)
    {
        free_irq(keyinputdev.key[i].irqnum, &keyinputdev);
        gpio_free(keyinputdev.key[i].gpio);
    }

    del_timer(&keyinputdev.timer);
}

功能说明:

  1. 注销输入设备
  2. 释放输入设备内存
  3. 释放中断
  4. 释放GPIO
  5. 删除定时器

8.5 模块声明

module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");

9. 用户空间应用程序

9.1 应用程序功能

用户空间应用程序负责打开输入设备文件,读取按键事件,并显示按键状态。

9.2 代码分析

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <asm/ioctl.h>

#define KEY0VALUE 0x10
#define KEYINVA 0xff

int main(int argc, char *argv[])
{
    int cnt = 0;
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <led_device> <0|1>\n", argv[0]);
        return -1;
    }

    char *fileanme;
    unsigned char databuf[1];
    fileanme = argv[1];

    int fd = 0;

    fd = open(fileanme, O_RDWR);
    if (fd < 0)
    {
        perror("open led device error");
        return -1;
    }

    while (1)
    {
        unsigned char ch = 0;
        int ret = read(fd, &ch, sizeof(ch));
        // printf("User: ret is: %d", ret);
        if (ret < 0)
        {
            // printf("User: read error");
        }
        else
        {
            if (ch == KEY0VALUE)
            {
                printf("User: key is pressing, ret is: %d\r\n", ret);
            }
        }

        // sleep(100);
    }
    printf("APP runing finished! \r\n");

    close(fd);
    return 0;
}

9.3 功能说明

  1. 参数检查: 检查命令行参数数量
  2. 设备打开: 打开输入设备文件
  3. 事件读取: 循环读取按键事件
  4. 事件处理: 判断按键状态并输出信息
  5. 资源释放: 关闭设备文件

9.4 改进建议

当前应用程序存在以下问题:

  1. 错误的设备打开模式: 输入设备通常以只读模式打开
  2. 错误的读取方式: 输入事件应该使用struct input_event结构体读取
  3. 缺少事件解析: 没有正确解析输入事件

改进版本:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <input_device>\n", argv[0]);
        return -1;
    }

    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open input device");
        return -1;
    }

    struct input_event ev;
    while (1) {
        ssize_t n = read(fd, &ev, sizeof(ev));
        if (n == (ssize_t)-1) {
            perror("read");
            break;
        } else if (n != sizeof(ev)) {
            fprintf(stderr, "short read\n");
            break;
        }

        if (ev.type == EV_KEY && ev.code == KEY_0) {
            if (ev.value == 1) {
                printf("Key 0 pressed\n");
            } else if (ev.value == 0) {
                printf("Key 0 released\n");
            }
        }
    }

    close(fd);
    return 0;
}

10. 编译与部署

10.1 Makefile分析

KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)

obj-m := keyinput.o
build : kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modules

clean:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
10.1.1 变量说明
  • KERNERDIR: 内核源码目录
  • CURRENTDIR: 当前目录
  • obj-m: 要编译的模块对象
10.1.2 目标说明
  • build: 编译目标
  • kernel_modules: 实际编译命令
  • clean: 清理目标

10.2 编译步骤

  1. 设置环境变量

    export ARCH=arm
    export CROSS_COMPILE=arm-linux-gnueabihf-
    
  2. 编译模块

    make -C /path/to/kernel M=$(pwd) modules
    
  3. 清理

    make -C /path/to/kernel M=$(pwd) clean
    

10.3 部署步骤

  1. 复制模块到目标板

    scp keyinput.ko root@target:/lib/modules/$(uname -r)/
    
  2. 加载模块

    insmod keyinput.ko
    
  3. 验证模块加载

    lsmod | grep keyinput
    
  4. 检查设备节点

    ls /dev/input/
    

14. 扩展应用

14.1 多按键支持

修改驱动以支持多个按键:

#define KEY_NUM 4

struct key_desc key[KEY_NUM] = {
    {.name = "KEY0", .gpio = -1, .irqnum = -1, .value = KEY_0},
    {.name = "KEY1", .gpio = -1, .irqnum = -1, .value = KEY_1},
    {.name = "KEY2", .gpio = -1, .irqnum = -1, .value = KEY_2},
    {.name = "KEY3", .gpio = -1, .irqnum = -1, .value = KEY_3},
};

14.2 自定义事件处理

添加自定义事件处理逻辑:

static void custom_event_handler(struct keyinput_dev *dev, int key_value)
{
    switch(key_value) {
        case KEY_0:
            // 处理KEY0事件
            break;
        case KEY_1:
            // 处理KEY1事件
            break;
        default:
            break;
    }
}

14.3 与其他子系统集成

将输入事件与其他子系统集成:

// 与LED子系统集成
extern void led_control(int state);

static void timer_func(unsigned long arg)
{
    struct keyinput_dev *dev = (struct keyinput_dev *)arg;

    int value = gpio_get_value(dev->key[0].gpio);
    if (value == 0) {
        input_event(dev->inputdev, EV_KEY, KEY_0, 1);
        led_control(1); // 点亮LED
    } else {
        input_event(dev->inputdev, EV_KEY, KEY_0, 0);
        led_control(0); // 熄灭LED
    }
    input_sync(dev->inputdev);
}

参考文档

  • Linux内核源码: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
  • 设备树规范: https://www.devicetree.org/
  • 《Linux设备驱动程序》第三版
  • 《深入理解Linux内核》第三版

https://gitee.com/dream-cometrue/linux_driver_imx6ull


网站公告

今日签到

点亮在社区的每一天
去签到