Linux设备树的按键中断驱动程序

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) 

Linux设备树的按键中断驱动程序

1. 管脚复用

  1. 设置为GPIO;
  2. 上拉电阻/下拉电阻使能, 具体看自己的管脚复用手册;
  3. 电平转换速率控制设置为慢(有些芯片没有这个选项);

2. GPIO的中断寄存器使能

  1. 选择电平触发/边缘触发;
  2. 选择单边缘触发/双边缘触发; (电平触发才需要设置)
  3. 选择下降沿或低电平触发/上升沿或高电平触发
  4. 设置为不屏蔽中断; (硬件层面上屏蔽了中断, 你软件上就没机会接收中断了, 这也是很多同学奇怪为什么接收不到中断的原因)

3. 设备树中的按键中断结点

	// 在设备树中表明, 要使用哪个中断.
	button@0x120D8000 {				// 自己添加的一个按键结点
		compatible = "ybk_btn";		// 用于与驱动匹配
		reg = <0x120d8000 0x1000>;	// 该按键的GPIO地址及属性
		interrupt-parent = <&gic>;	// 该按键对应的硬件中断属于哪一个中断控制器
		/** 
		 * 具体含义由中断控制器来解释. 用多少个u32描述也是由中断控制器来指定.
		
		 * 下面是我的平台的解释
		 * 0,  属于该中断控制器的第0个子中断控制器
		 * 23, 属于第0个子中断控制器中的第23个中断位
		 * 4,  中断触发方式的选择, 即该GPIO什么情况下会触发中断
		 * 		1 = low-to-high edge triggered
		 *      2 = high-to-low edge triggered
		 *      4 = active high level-sensitive
		 *      8 = active low level-sensitive 
		 */
		interrupts = <0 23 4>;		
	};

其中的interrupt-parentinterrupts是描述一个中断结点所必备的, 分别表明了这个中断属于哪一个中断控制器以及使用这个中断控制器的第几号中断, 以及怎么样的触发方式.

4. 驱动程序中关于按键中断的部分

请查看我上一篇文章:
  Linux 设备树中的中断

/**
 * 请求中断函数, 设置对应的中断号irq(已软硬件绑定)触发中断时将调用的中断处理函数handler;
 * 
 * @param irq		软件中断号, 已经和硬件相绑定; (具体可以查看我的上一篇文章)
 * @param handler	中断处理函数, irq对应的硬件触发中断时, 这个函数将会被调用;
 * @param flags		标志位, 可以使用多个或操作将多个flag结合起来;
 * 		IRQF_SHARED, 该标志表示多个设备共享一条IRQ线, 因此相应的每个设备都需要各自的中断服务例程;
 * 		(下面的是中断触发类型)
 * 		IRQF_TRIGGER_NONE, 失能中断
 * 		IRQF_TRIGGER_RISING, 上升沿触发
 * 		IRQF_TRIGGER_FALLING, 下降沿触发
 * 		IRQF_TRIGGER_HIGH, 高电平触发
 * 		IRQF_TRIGGER_LOW, 低电平触发
 * @param name		请求中断的设备的名称, 属于自定义的字符串
 * @param dev		指针变量, 目的是为即将要释放中断处理程序提供唯一标志.
 * 		因为多个设备共享一条中断线, 因此要释放某个中断处理程序时, 必须通过此标志来唯一指定这个中断 号的中断处理程序
 * 
 * @return 返回0成功, 否则返回错误值.
 */
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)

举个例子:

ret =request_irq(pins_desc[0].irq, btn_handler, 
				IRQF_TRIGGER_HIGH | IRQF_SHARED, "ybk_btn", &pins_desc[0]);

细心的同学会发现, 在GPIO的寄存器设置, 设备树结点的编写以及按键中断驱动程序中都描述了该按键的中断触发方式是什么(边缘触发呢? 还是电平触发呢? 高电平触发呢? 还是低电平触发呢?), 具体哪个是最终决定者呢? 是硬件层面上的GPIO寄存器设置. 但为了具有更好的可读性, 大家还是需要把这三者的描述保持一致!

5.文章的结尾添上按键中断驱动程序的源码

#include <linux/init.h>
#include <linux/module.h>

#include <linux/of.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#include <linux/cdev.h>
#include <linux/device.h>

#include <linux/interrupt.h>

static dev_t dev_num = 0;
static struct cdev *cdevice = NULL;

static struct class *sys_class = NULL;
static struct device *class_device = NULL;

struct pin_desc{
	volatile unsigned int *gpio_con;
	volatile unsigned int *gpio_dat;
	int irq;
};

static int gkey_val;
struct pin_desc pins_desc[1] = {
	{NULL, NULL}, 
};

/* 中断事件标志, 中断服务程序将它置1,btn_read将它清0 */
static volatile int ev_press = 0;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static irqreturn_t btn_handler(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	printk("btn_handler running!\n");
	
	gkey_val = *(pindesc->gpio_dat);
	ev_press = 1;	/* 表示中断发生了 */
	wake_up_interruptible(&button_waitq);	/* 唤醒休眠的进程 */

	return IRQ_RETVAL(IRQ_HANDLED);;
}

int btn_open(struct inode *pinode, struct file *pfile)
{
	int ret = -1;

	// input mode
	*(pins_desc[0].gpio_con + 100) &= 0<<1;

	// request irq
	ret = request_irq(pins_desc[0].irq, btn_handler, IRQF_TRIGGER_HIGH | IRQF_SHARED, 
						"ybk_btn", &pins_desc[0]);
	printk("ret = %d, pins_desc[0].irq = %d\n", ret, pins_desc[0].irq);
	
	return 0;
}

int btn_close(struct inode *pinode, struct file *pfile)
{
	free_irq(pins_desc[0].irq, &pins_desc[0]);
	return 0;
}

ssize_t btn_read(struct file *pfile, char __user *userbuf, size_t size, loff_t *loff)
{
	/**
	 * 轮询方式的read()
	 * 
	 * int value = *(pins_desc[0].gpio_dat);
	 * copy_to_user(userbuf, &value, sizeof(value));
	 */ 
	
	if(size != sizeof(int))
		return -EINVAL;

	/* 如果没有按键动作, 休眠 */
	wait_event_interruptible(button_waitq, ev_press);

	/* 如果有按键动作, 返回键值 */
	copy_to_user(userbuf, &gkey_val, sizeof(gkey_val));
	ev_press = 0;

	return size;
}

static struct file_operations btn_fops = {
	.owner = THIS_MODULE,
	.open  = btn_open,
	.read  = btn_read,
	.release = btn_close,
};

int btn_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *dp_node = dev->of_node;
	struct resource *res = NULL;
	int i, reg[2];
	
	for(i=0; i<sizeof(pins_desc)/sizeof(pins_desc[0]); i++) {
		res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
		if(res) {
			pins_desc[i].irq = res->start;
		} else {
			printk("get irq failed!\n");
			return -EINVAL;
		}
		
		of_property_read_u32_array(dp_node, "reg", reg, sizeof(reg)/sizeof(reg[0]));

		pins_desc[i].gpio_con = (volatile unsigned int *)ioremap(reg[0], reg[1]);
		pins_desc[i].gpio_dat = pins_desc[i].gpio_con + 2;
	}

	printk("btn_probe, found btn\n");
	cdevice = cdev_alloc();
	cdev_init(cdevice, &btn_fops);

	alloc_chrdev_region(&dev_num, 0, 1, "yangbkBtn");
	cdev_add(cdevice, dev_num, 1);

	sys_class = class_create(THIS_MODULE, "yangbkClass");
	class_device = device_create(sys_class, NULL, dev_num, NULL, "yangbkDevice");
	
	return 0;
}

int btn_remove(struct platform_device *pdev)
{
	printk("btn_remove, remove btn\n");

	device_destroy(sys_class, dev_num);
	class_destroy(sys_class);

	unregister_chrdev_region(dev_num, 1);
	
	cdev_del(cdevice);

	iounmap(pins_desc[0].gpio_con);

	return 0;
}

static struct of_device_id	of_match_table = {
	.compatible = "ybk_btn",
	.data = NULL,
};

struct platform_driver btn_drv = {
	.probe		= btn_probe,
	.remove		= btn_remove,
	.driver		= {
		.name	= "idle",
		.of_match_table = &of_match_table,
	}
};


static int __init btn_drv_init(void)
{
	platform_driver_register(&btn_drv);
	return 0;
}

static void __exit btn_drv_exit(void)
{
	platform_driver_unregister(&btn_drv);
}

module_init(btn_drv_init);
module_exit(btn_drv_exit);

MODULE_AUTHOR("yangbkGit");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("platform driver model.");
MODULE_ALIAS("model");
发布了68 篇原创文章 · 获赞 22 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/MACMACip/article/details/105782624