IMX6ULL开发板Linux中断驱动程序学习笔记
第一步:更改添加设备树相关属性(加粗内容为中断)
key{
compatible = "alientek,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
status = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
第二步:写驱动程序
备注:步骤1~5 是字符设备构建框架
步骤6是按键及其处理函数过程
步骤7是按键消抖处理过程
步骤8 是与应用程序沟通过程
以下是驱动程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/ide.h>
#define IMX6UIRQ_CNT 1 /*6.0.0:设备号个数*/
#define IMX6UIRQ_NAME "imx6uirq" /*6.0.0:名字*/
#define KEY_NUM 1 /*6.0.0:按键个数*/
#define KEY0VALUE 0X01 /*6.2.2.0:键值*/
#define INVAKEY 0XFF /*6.2.2.0:无效*/
/*6.1.1:单独做个结构体的目的:如果有很多按键需要一个数组来表示一个按键是一个数组元素,得用一个结构体来描述按键相关属性*/
/*key结构体-用一个结构体来描述按键相关属性(一个结构体描述一个设备或一个物体属性)*/
struct irq_keydesc{ /*keydesc按键中断描述之意*/
int gpio; /*按键结构体里有IO编号*/
int irqnum; /*中断号*/
unsigned char value; /*键值*/
char name[10]; /*名字*/
irqreturn_t (*handler) (int, void *); /*6.2.1.2:重要的中断函数*/
};
/*3.1:设备结构体*/
struct imx6uirq_dev{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor;
struct cdev cdev;/*3.3.1结构体里先定义字符设备*/
struct class *class; /*4.1:类*/
struct device *device; /*4.1:设备*/
struct device_node *nd; /*5.1设备节点*/
struct irq_keydesc irqkey[KEY_NUM]; /*6.1.2:一个按键一个数组,irq_keydesc里的内容放到这个结构体下面*/
struct timer_list timer; /*7.1.0消抖用的定时器timer*/
atomic_t keyvalue; /*8.0:两个原子变量*/
atomic_t releasekey; /*8.0:两个原子变量*/
};
struct imx6uirq_dev imx6uirq; /*3.2:设备*/
/*3.4.2:以下3个函数是对应file_operations具体函数*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq;
return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
/*8.0用read函数 读取*/
int ret = 0;
unsigned char keyvalue;
unsigned char releasekey;
struct imx6uirq_dev *dev = filp->private_data; /*私有数据*/
keyvalue = atomic_read(&dev->keyvalue);
releasekey =atomic_read(&dev->releasekey);
if(releasekey){ /*有效按键*/
if(keyvalue & 0x80){
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0); /*按下标志清零*/
}else{
goto data_error;
}
return ret;
data_error:
return -EINVAL;
}
static ssize_t imx6uirq_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations imx6uirq_fops = { /*3.4.1:定义字符设备操作集*/
.owner = THIS_MODULE,
.write = imx6uirq_write,
.open = imx6uirq_open,
.read = imx6uirq_read,
.release = imx6uirq_release,
};
/*6.2.1.1:按键中断处理函数*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{ /*6.3:此函数内容为具体操作*/
struct imx6uirq_dev *dev = dev_id; /*(volatile long)类型转换*/
dev->timer.data = (volatile unsigned long)dev_id; /*7.2.0在此中断处理函数中开启触发定时器,*/
/*传递给这个定时器处理函数是dev_id也就是imx6uirq这个变量(volatile unsigned long)是类型转换*/
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20) );/*10ms定时,执行后timer_func就会执行*/
return IRQ_HANDLED;
}
/*7.1.1.1定时器功能函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg; /*7.3:arg强制类型转换(struct imx6uirq_dev*)*/
// printk("timer_func\r\n");
/*7.4然后执行读取按键值*/
value = gpio_get_value(dev->irqkey[0].gpio);
if (value == 0){ /*按下*/
printk("key0 pushed!\r\n");
atomic_set(&dev->keyvalue, dev->irqkey[0].value);/*将dev->irqkey[0].value值写给keyvalue*/
}else if(value == 1){ /*释放*/
atomic_set(&dev->keyvalue, 0X80 | (dev->irqkey[0].value));/*或上一个0X80 把最高位置1,置1就代表释放掉了*/
atomic_set(&dev->releasekey,1);/*表示是完整的按键过程:有按下有释放*/
printk("key0 released!\r\n");
}
}
/*6.0.按键初始化*/
static int keyio_init(struct imx6uirq_dev *dev)
{
int ret = 0;
int i =0;
/*6.1.3:以下按键初始化*/
dev->nd = of_find_node_by_path("/key"); /*of...找设备节点函数*/
if (dev->nd == NULL){
ret = -EINVAL;
goto fail_nd;
}
for(i=0; i<KEY_NUM; i++){ /*,有很多按键的话就用循环来获取编号*/
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i); /*.gpio就是按键的io编号,从设备树里得到*/
}
/*初始化按键io*/
for(i=0; i<KEY_NUM; i++){
memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name)); /*m每个io名字不一样,需给每个io去命名,用memeset函数给key结构体里的成员变量*/
/*name初始化一个字符串,在初始化之前先要清一下数组*/
sprintf(dev->irqkey[i].name, "KEY%d", i); /*清零后,给name填入一些值,i就是代表key0,key1,key2...*/
gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name); /*irqkey[i].name有新值就可以request了*/
gpio_direction_input(dev->irqkey[i].gpio); /*用这个函数将GPIO设置为输入*/
/*以上6.1.3-end*/
/*6.2.0:以下中断相关初始化*/
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);/*获取中断号*/
//dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i); /*获取中断号方法2*/
}
/*6.2.1.0:按键中断初始化*/
dev->irqkey[0].handler = key0_handler; /*6.2.1.3*/
dev->irqkey[0].value = KEY0VALUE; /*6.2.2.1*/
/*6.2.1.1:申请*/
for(i=0; i<KEY_NUM; i++){
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, /*第二个参数先去6.2.1.1~3步骤初始化定义*/
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, dev->irqkey[i].name, &imx6uirq);/*第二个参数是出发方式选上升沿或下降沿跳变法用“|”*/
/*第三个参数是传递中断处理函数irqreturn_t key0_handler的参数,把结构体&imx6uirq传进去*/
/*当key0_handler函数执行时,void *dev_id就是imx6uirq_dev结构体,通过此大结构体就可以访问相关属性信息*/
if (ret){
printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
goto fail_irq;
}
}
/*7.0消抖处理*/
/*7.1需要先在结构体里加一个定时器见7.1.0*/
/*7.1.1初始化定时器*/
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_func; /*7.1.1.0:timer_func需要定义个函数见7.1.1.1*/
return 0;
fail_irq:
for(i=0; i<KEY_NUM; i++){
gpio_free(dev->irqkey[i].gpio); /*request失败就释放掉gpio*/
}
fail_nd:
return ret;
}
/*2.入口函数*/
static int __init imx6uirq_init(void)
{
int ret = 0;
/*初始化信号量*/
/*3:注册字符设备*/
/*3.0:注册字符设备号*/
imx6uirq.major = 0;/*3.2:表示设备号由内核分配*/
if(imx6uirq.major){ /*如果分配了设备号*/
imx6uirq.devid = MKDEV(imx6uirq.major, 0);/*那么主设备号和次设备号进行拼凑*/
ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
}else { /*如果没有分配了设备号,那么要申请一个设备号*/
ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
printk("imx6uirq major = %d, minor =%d\r\n", imx6uirq.major, imx6uirq.minor);
if (ret < 0){
goto fail_devid;
}
/*3.3:添加字符设备*/
imx6uirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6uirq.cdev, &imx6uirq_fops); /*3.4初始化cdev()里第二个参数就是字符设备的操作集见3.4.1*/
ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
if (ret < 0){
goto fail_cdevadd;
}
/*4:自动创建设备节点*/
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.class)){
ret = PTR_ERR(imx6uirq.class);
goto fail_class;
}
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if(IS_ERR(imx6uirq.device)){
ret = PTR_ERR(imx6uirq.device);
goto fail_device;
}
/*6.1.4:初始化IO--static int keyio_init(struct imx6uirq_dev *dev)*/
ret = keyio_init(&imx6uirq);
if(ret < 0){
goto fail_keyinit;
}
/*8.1:初始化原子变量*/
atomic_set(&imx6uirq.keyvalue, INVAKEY);/*默认是无效的按键值*/
atomic_set(&imx6uirq.releasekey, 0);
return 0;
fail_keyinit:
fail_device:
class_destroy(imx6uirq.class);
fail_class:
cdev_del(&imx6uirq.cdev);
fail_cdevadd:
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/*2.出口口函数*/
static void __exit imx6uirq_exit(void)
{
int i = 0;
/*7.1.2删除定时器*/
del_timer_sync(&imx6uirq.timer);
/*释放中断*/
for(i=0; i<KEY_NUM; i++){
free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq); /**dev就是结构体地址*/
}
/*释放IO*/
for(i=0; i<KEY_NUM; i++){
gpio_free(imx6uirq.irqkey[i].gpio);
}
/*3.5删除字符设备*/
cdev_del(&imx6uirq.cdev);
/*3.5.1释放设备号*/
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
/*摧毁类*/
class_destroy(imx6uirq.class);
}
/*1.注册驱动和卸载驱动*/
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZXL");
以下是应用程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char data;
if(argc !=2) {
printf("Error usage!\r\n");
return -1;
}
filename = argv[1];
/*int open(const char *pathname, int flags);*/
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
/*模拟应用占用驱动25秒*/
while(1){
ret = read(fd, &data, sizeof(data));
if(ret <0){
}else {
if(data){
printf("key value = %#x\r\n", data);
}
}
}
close(fd);
return 0;
}
以下是开发板操作:
/lib/modules/4.1.15 # ls
imx6uirq.ko imx6uirqAPP
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modprobe imx6uirq.ko
imx6uirq major = 249, minor =0
/lib/modules/4.1.15 # ./imx6uirqAPP /dev/imx6uirq
(按开发板上key0键后显示如下表示成功)
key0 pushed!
key0 released!
key value = 0x1