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

相关推荐
天才奇男子19 小时前
HAProxy高级功能全解析
linux·运维·服务器·微服务·云原生
学嵌入式的小杨同学19 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
酥暮沐19 小时前
iscsi部署网络存储
linux·网络·存储·iscsi
❀͜͡傀儡师20 小时前
centos 7部署dns服务器
linux·服务器·centos·dns
Dying.Light20 小时前
Linux部署问题
linux·运维·服务器
S190120 小时前
Linux的常用指令
linux·运维·服务器
小义_20 小时前
【RH134知识点问答题】第7章 管理基本存储
linux·运维·服务器
梁洪飞21 小时前
内核的schedule和SMP多核处理器启动协议
linux·arm开发·嵌入式硬件·arm
_运维那些事儿21 小时前
VM环境的CI/CD
linux·运维·网络·阿里云·ci/cd·docker·云计算
Y1rong1 天前
linux之文件IO
linux