写一个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可以看到内核部分的提示。

相关推荐
Calebbbbb5 小时前
Windows 向 Vmware Ubuntu 传大文件校验不一致问题完整排查记录
linux·windows·ubuntu
被遗忘的旋律.5 小时前
Linux驱动开发笔记(二十四)——(下)IIO + MPU6050驱动
linux·驱动开发·笔记
optimistic_chen5 小时前
【Redis系列】Redis缓存
linux·数据库·redis·mysql·缓存·火山引擎
我想发发发5 小时前
.deb格式软件包安装方式(Ubuntu 22.04为例)
linux·运维·ubuntu
cyber_两只龙宝5 小时前
LVS-NAT模式实验配置以及详解
linux·运维·云原生·lvs
海涛高软5 小时前
vmware虚拟机 ubuntu20.4手动设置静态IP
linux·运维·服务器
学嵌入式的小杨同学6 小时前
【嵌入式 C 语言实战】栈、队列、二叉树核心解析:存储原理 + 应用场景 + 实现思路
linux·c语言·网络·数据结构·数据库·后端·spring
VekiSon6 小时前
ARM架构——时钟系统与定时器详解
linux·c语言·arm开发·嵌入式硬件·架构
optimistic_chen6 小时前
【Docker入门】Docker Registry(镜像仓库)
linux·运维·服务器·docker·容器·镜像仓库·空间隔离