写一个rv1106的gpio输入驱动(带中断)

在设备树添加key_1节点

c 复制代码
key_1: key_1{
		compatible = "key_1";
		status = "okay";

		key_1_gpio = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>;
		// 中断配置
        interrupt-parent = <&gpio1>;
        interrupts = <RK_PB0 IRQ_TYPE_EDGE_RISING>;

	};

这里使用上升沿触发。

驱动文件key_1.c

c 复制代码
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/clk.h>
#include <linux/pwm.h>
#include <linux/file.h>
#include <linux/list.h>
#include <linux/gpio.h>
#include <linux/time.h>
#include <linux/hrtimer.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/kthread.h>
#include <linux/mempolicy.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/poll.h>
#include "key_1.h"


int irq = -1;
int key_press = 0;

// 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	struct key_device *kdev = (struct key_device *)dev_id;
	
	// 处理中断事件
    printk("key_1 pressed \r\n");

	key_press = 1;
	wake_up_interruptible(&kdev->read_queue);

    // 返回中断处理状态
    return IRQ_HANDLED;
}


//打开函数
static int key_1_open(struct inode *inode, struct file *file)
{
	//从file里面提取出kdev
	struct miscdevice *dev = file->private_data;
	struct key_device *kdev = container_of(dev, struct key_device, misc_dev);
	int ret = 0;

	if (kdev->key_open_flag) {
		ret = -EBUSY;
		dev_err(kdev->dev, "key driver busy now!\n");
	} else {
		printk("open key is success!\n");
		kdev->key_open_flag = 1;
	}

	return ret;
}

//释放函数
static int key_1_release(struct inode *inode, struct file *file)
{
	struct miscdevice *dev = file->private_data;
	struct key_device *kdev = container_of(dev, struct key_device, misc_dev);

	kdev->key_open_flag = 0;
	printk("release key is success!\n");
	return 0;
	
}

//操作函数
static long key_1_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
	struct miscdevice *dev = file->private_data;
	struct key_device *kdev = container_of(dev, struct key_device, misc_dev);
	long ret = 0;

	if (kdev->key_open_flag == 0) {
		printk("Please Open /dev/key_1 Firstly\n");
		return -EPERM;
	}
	printk("cmd: %u\r\n", cmd);
	return ret;

}

//poll
static __poll_t key_1_poll(struct file *file, poll_table *wait)
{
    struct miscdevice *dev = file->private_data;
	struct key_device *kdev = container_of(dev, struct key_device, misc_dev);
    __poll_t mask = 0;
    
    printk("pollwait \r\n");
	poll_wait(file, &kdev->read_queue, wait);

    if(key_press == 1){
		key_press = 0;
		mask |= POLLIN | POLLRDNORM;
	}
	
    
    return mask;
}



static struct file_operations key_1_fops = {
	.owner = THIS_MODULE,
	.open = key_1_open,
	.release = key_1_release,
	.poll = key_1_poll,
	.unlocked_ioctl = key_1_ioctl,
};

static int key_request_gpio(struct platform_device *pdev, struct key_device *kdev)
{
	int ret;
	int GPIO_ID_KEY1 = -1;


	//获得id
	GPIO_ID_KEY1 = of_get_named_gpio(pdev->dev.of_node, "key_1_gpio", 0);
	if (GPIO_ID_KEY1 < 0) {
		printk("get KEY1 named is error!\n");
		return -1;
	}

	//申请key1的gpio控制权
	ret = gpio_request(GPIO_ID_KEY1, "key1_gpio");
	if (ret != 0) {
		printk("key1 gpio request is error!\n");
		return -1;
	}
	
	//设置方向
	ret = gpio_direction_input(GPIO_ID_KEY1);
	if (ret) {
		printk("key1 set direction fail%d\n", ret);
		goto free_gpio;
	}

	// 获取GPIO对应的中断号
    irq = gpio_to_irq(GPIO_ID_KEY1);
    if (irq < 0) {
        printk(KERN_ERR "Unable to get IRQ for GPIO %d\n", GPIO_ID_KEY1);
        goto free_gpio;
    }

	// 申请中断,触发方式为上升沿触发,中断处理函数为gpio_irq_handler
    // 注意:中断处理函数不能进行长时间的操作,如果需要,考虑使用线程化中断
    ret = request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING, "key_1_irq", kdev);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d for GPIO %d\n", irq, GPIO_ID_KEY1);
        goto free_gpio;
    }

	//把key的gpio保存到kdev结构体中
	kdev->key.key_gpio = GPIO_ID_KEY1;
	return 0;

	free_gpio:
	if (GPIO_ID_KEY1  != -1)
		gpio_free(GPIO_ID_KEY1);

	return -1;
}

static int key_1_probe(struct platform_device *pdev)
{
	int ret;
	struct key_device *kdev;

	printk("%s enter\n", __func__);

	//给私有的kedv分配内存
	kdev = devm_kzalloc(&pdev->dev, sizeof(struct key_device), GFP_KERNEL);
	if (!kdev) {
		ret = -ENOENT;
		dev_err(&pdev->dev, "kzalloc key device memery error\n");
		goto error_devm_kzalloc;
	}

	//私有的dev指向平台设备的dev
	kdev->dev = &pdev->dev;

	// 初始化等待队列
    init_waitqueue_head(&kdev->read_queue);

	//互斥锁和线程锁初始化
	mutex_init(&kdev->dev_mutex);
	spin_lock_init(&kdev->slock);

	//把kdev挂到pdev
	platform_set_drvdata(pdev, kdev);

	//注册misc驱动
	kdev->misc_dev.minor = MISC_DYNAMIC_MINOR;
	kdev->misc_dev.name = "key_1";
	//fops指明打开,释放,操作函数
	kdev->misc_dev.fops = &key_1_fops;
	//注册混合设备
	ret = misc_register(&kdev->misc_dev);
	if (ret < 0) {
		ret = -ENOENT;
		dev_err(&pdev->dev, "misc_register failed\n");
	}

	//获取gpio
	ret = key_request_gpio(pdev, kdev);
	if (ret) {
		printk("key_request_gpio fail\n");
		ret = -1;
		goto key_request_gpio_fail;
	}
	printk("key pin id is %d \r\n", kdev->key.key_gpio);
	printk("%s leave\n", __func__);
	return 0;

key_request_gpio_fail:
error_devm_kzalloc:
	return ret;	

}

static int key_1_remove(struct platform_device *pdev)
{
	printk("key_1 remove\n");
	return 0;
}

struct of_device_id of_match_table = {
	.compatible = "key_1"
};

static struct platform_driver key_1_platform_driver = {
	.probe = key_1_probe,
	.remove = key_1_remove,
	.driver = {
		.name = "key_1",
		.owner = THIS_MODULE,
		.of_match_table = &of_match_table,
	}
};

static int __init key_1_init(void)
{
	int ret;
	ret = platform_driver_register(&key_1_platform_driver);
	return ret;
}

static void __exit key_1_exit(void)
{
	platform_driver_unregister(&key_1_platform_driver);
}

module_init(key_1_init);
module_exit(key_1_exit);

MODULE_LICENSE("GPL");

驱动分析

基本架构
c 复制代码
static int key_1_probe(struct platform_device *pdev)
{
	int ret;
	struct key_device *kdev;

	printk("%s enter\n", __func__);

	//给私有的kedv分配内存
	kdev = devm_kzalloc(&pdev->dev, sizeof(struct key_device), GFP_KERNEL);
	if (!kdev) {
		ret = -ENOENT;
		dev_err(&pdev->dev, "kzalloc key device memery error\n");
		goto error_devm_kzalloc;
	}

	//私有的dev指向平台设备的dev
	kdev->dev = &pdev->dev;

	// 初始化等待队列
    init_waitqueue_head(&kdev->read_queue);

	//互斥锁和线程锁初始化
	mutex_init(&kdev->dev_mutex);
	spin_lock_init(&kdev->slock);

	//把kdev挂到pdev
	platform_set_drvdata(pdev, kdev);

	//注册misc驱动
	kdev->misc_dev.minor = MISC_DYNAMIC_MINOR;
	kdev->misc_dev.name = "key_1";
	//fops指明打开,释放,操作函数
	kdev->misc_dev.fops = &key_1_fops;
	//注册混合设备
	ret = misc_register(&kdev->misc_dev);
	if (ret < 0) {
		ret = -ENOENT;
		dev_err(&pdev->dev, "misc_register failed\n");
	}

	//获取gpio
	ret = key_request_gpio(pdev, kdev);
	if (ret) {
		printk("key_request_gpio fail\n");
		ret = -1;
		goto key_request_gpio_fail;
	}
	printk("key pin id is %d \r\n", kdev->key.key_gpio);
	printk("%s leave\n", __func__);
	return 0;

key_request_gpio_fail:
error_devm_kzalloc:
	return ret;	

}

static int key_1_remove(struct platform_device *pdev)
{
	printk("key_1 remove\n");
	return 0;
}

struct of_device_id of_match_table = {
	.compatible = "key_1"
};

static struct platform_driver key_1_platform_driver = {
	.probe = key_1_probe,
	.remove = key_1_remove,
	.driver = {
		.name = "key_1",
		.owner = THIS_MODULE,
		.of_match_table = &of_match_table,
	}
};

static int __init key_1_init(void)
{
	int ret;
	ret = platform_driver_register(&key_1_platform_driver);
	return ret;
}

static void __exit key_1_exit(void)
{
	platform_driver_unregister(&key_1_platform_driver);
}

module_init(key_1_init);
module_exit(key_1_exit);

MODULE_LICENSE("GPL");

这些函数构成了一个平台驱动的基本架构

probe函数

probe函数主要完成私有dev的内存分配,注册混合设备的驱动,获取gpio。

fops

fops指明了各个功能函数,在注册设备的时候用到

c 复制代码
static struct file_operations key_1_fops = {
	.owner = THIS_MODULE,
	.open = key_1_open,
	.release = key_1_release,
	.poll = key_1_poll,
	.unlocked_ioctl = key_1_ioctl,
};

和LED驱动相比,增加了

c 复制代码
.poll = key_1_poll,

这个对应应用程序的poll操作,使应用程序可以进行异步操作。

轮询函数

c 复制代码
//poll
static __poll_t key_1_poll(struct file *file, poll_table *wait)
{
    struct miscdevice *dev = file->private_data;
	struct key_device *kdev = container_of(dev, struct key_device, misc_dev);
    __poll_t mask = 0;
    
    printk("pollwait \r\n");
	poll_wait(file, &kdev->read_queue, wait);

    if(key_press == 1){
		key_press = 0;
		mask |= POLLIN | POLLRDNORM;
	}
	
    
    return mask;
}

轮询函数主要调用poll_wait函数进行轮询,当被唤醒的时候,通过key_press 变量判断时超时间还是中断唤醒。key_press 在中断回调函数中赋值。

中断注册

在key_request_gpio函数中,不但获取了GPIO的控制权,还对输入的中断进行了注册

c 复制代码
//设置方向
	ret = gpio_direction_input(GPIO_ID_KEY1);
	if (ret) {
		printk("key1 set direction fail%d\n", ret);
		goto free_gpio;
	}

	// 获取GPIO对应的中断号
    irq = gpio_to_irq(GPIO_ID_KEY1);
    if (irq < 0) {
        printk(KERN_ERR "Unable to get IRQ for GPIO %d\n", GPIO_ID_KEY1);
        goto free_gpio;
    }

	// 申请中断,触发方式为上升沿触发,中断处理函数为gpio_irq_handler
    // 注意:中断处理函数不能进行长时间的操作,如果需要,考虑使用线程化中断
    ret = request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING, "key_1_irq", kdev);
    if (ret) {
        printk(KERN_ERR "Failed to request IRQ %d for GPIO %d\n", irq, GPIO_ID_KEY1);
        goto free_gpio;
    }

	//把key的gpio保存到kdev结构体中
	kdev->key.key_gpio = GPIO_ID_KEY1;
	return 0;

其中

c 复制代码
request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING, "key_1_irq", kdev);

注册了irq中断的回调函数:gpio_irq_handler,中断的类型:IRQF_TRIGGER_RISING还传参kdev到中断回调函数。

中断回调函数

c 复制代码
// 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	struct key_device *kdev = (struct key_device *)dev_id;
	
	// 处理中断事件
    printk("key_1 pressed \r\n");

	key_press = 1;
	wake_up_interruptible(&kdev->read_queue);

    // 返回中断处理状态
    return IRQ_HANDLED;
}

发生中断的时候,会回调这个函数,对key_press进行赋值,唤醒poll。key_1_poll轮询函数被唤醒后,执行poll_wait后面的程序,对key_press进行判断,对mask相应位进行质位,返回mask。

测试程序

在./src下面建立key_test.c

c 复制代码
#include<stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <poll.h>

/* ioctl cmd */
//#define LED_ON          _IOW('M',0,long)
//#define LED_OFF         _IOW('M',1,long)

int key_fd = -1;

void main(void)
{
    struct pollfd pfd;
	int ret = -1;

	key_fd = open("/dev/key_1", O_RDWR);
    printf("key fd is %d\r\n", key_fd);

	// 轮询事件
    

	

	do {
		pfd.fd = key_fd;
        pfd.events = POLLIN;
		ret = poll(&pfd, 1, 2000);
		if(ret == POLLIN){
			printf("key pressed \r\n");
		}
	}while(!(ret < 0));	    
    close(key_fd);

}

程序通过调用poll函数,调用驱动的key_1_poll,一旦返回的值为POLLIN,说明按键已经产生了中断,输出提示。

测试

在下位机ismod key_1.ko,运行key_test应用程序,按键按下时可以见到提示输出,dmesg可以看到内核部分的提示。

相关推荐
林鸿群21 小时前
Ubuntu 26.04 本地安装 GitLab CE 完整教程(非 Docker 方式)
linux·ubuntu·gitlab·私有部署·代码托管·ubuntu 26.04·omnibus
勇闯逆流河21 小时前
【Linux】Linux进程概念(进程优先级,进程切换详解)
linux·运维·服务器
老师好,我是刘同学21 小时前
30个核心Linux命令速查手册
linux
fsj2009yx21 小时前
如何把无公网的求生之路2服务器借助VPS转发注册到steam master列表中
linux·wireguard·求生之路2
慵懒的猫mi21 小时前
deepin UOS AI 助手接入飞书(Feishu)配置指南
linux·人工智能·ai·gpt-3·飞书·文心一言·deepin
Jiozg21 小时前
ES安装到linux(ubuntu)
linux·ubuntu·elasticsearch
面对疾风叭!哈撒给21 小时前
Linux之docker-compose使用(redis、nginx、tdengine、java应用)
linux·redis·docker
zzzsde21 小时前
【Linux】进程(6):程序地址空间
linux·运维·服务器
慵懒的猫mi21 小时前
deepin UOS AI 助手接入钉钉(DingTalk)配置指南
linux·数据库·人工智能·ai·钉钉·deepin
returnthem21 小时前
Ubuntu 22.04 + XFCE4 + 非 Snap 版 Firefox + VNC/noVNC 部署全步骤
linux·ubuntu·firefox