imx6ull的中断实验

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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 <linux/semaphore.h>
 #include <linux/timer.h>
 #include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT                        1                                /* 设备号个数 */
 #define IMX6UIRQ_NAME                     "imx6uirq"             /* 名字 */
 #define KEY0VALUE                                   0X01                     /* KEY0 按键值 */
 #define INVAKEY                                         0XFF                    /* 无效的按键值 */
 #define KEY_NUM 1 /* 按键数量 */

//中断IO描述结构体
struct irq_ketdasc{
    int gpio;                    /*gpio*/
    int irqnum;            /*中断号*/
    unsigned char  value;    /*按键对因的键值*/
    char name[10];            /*名字*/
    irqreturn_t    (*handler) (int, void *);        /*中断服务函数*/
};
/*imx6uirq_dev设备结构体*/
struct     imx6uirq_dev
{
    dev_t devid;
    int major;
    int minor;
    struct device *device;
    struct cdev  cdev ;
    struct class *class;
    struct device_nade *nd;                /*设备节点*/
unsigned char curkeynum; /* 当前的按键号 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键*/
struct timer_list timer; /* 定义一个定时器*/
struct irq_keydesc irqkeydesc[KEY_NUM];/* 按键描述数组 */
    /* data */
};
struct imx6uirq_dev  imx6uirq;

/*@description : 中断服务函数,开启定时器,延时 10ms, 定时器用于按键消抖。
 @param - irq : 中断号 @param - dev_id : 设备结构。 @return : 中断执行结果*/
/*,key0_handler 函数,按键 KEY0 中断处理函数,参数 dev_id 为设备结构体,
也就是 imx6uirq。第 74 行设置 curkeynum=0,表示当前按键为 KEY0,第 76 行使用 mod_timer
函数启动定时器,定时器周期为 10ms。*/
  static irqreturn_t key0_handler(int irq, void *dev_id)
  {
        struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;、
        dev->curkeynum = 0;
        dev->timer.data = (volatile long)dev_id;
        /*mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时
器!函数原型如下:int mod_timer(struct timer_list *timer, unsigned long expires)
函数参数和返回值含义如下:timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间。返回值:0,调用 mod_timer 函数前定时器未被激活;
1,调用 mod_timer 函数前定时器已被激活。*/
        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

        /*出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)*/
        return IRQ_RETVAL(IRQ_HANDLED);
  }

/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param – arg : 设备结构变量* @return : 无*/
/*timer_function 函数,定时器定时处理函数,参数 arg 是设备结构体,也就是
imx6uirq,在此函数中读取按键值。第 95 行通过 gpio_get_value 函数读取按键值。如果为 0 的
话就表示按键被按下去了,按下去的话就设置 imx6uirq 结构体的 keyvalue 成员变量为按键的键
值,比如 KEY0 按键的话按键值就是 KEY0VALUE=0。如果按键值为 1 的话表示按键被释放了,
按键释放了的话就将 imx6uirq 结构体的 keyvalue 成员变量的最高位置 1,表示按键值有效,也
就是将 keyvalue 与 0x80 进行或运算,表示按键松开了,并且设置 imx6uirq 结构体的 releasekey
成员变量为 1,表示按键释放,一次有效的按键过程发生。*/
 void timer_function(unsigned long arg)
 {
         unsigned char value;
        unsigned char num;
        struct irq_keydesc *keydesc;
        struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

        num = dev->curkeynum;
        keydesc = &dev->irqkeydesc[num];
        /*gpio_get_value 函数此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:
#define gpio_get_value __gpio_get_valueint __gpio_get_value(unsigned gpio)函数参数和返回值含义如下:
gpio:要获取的 GPIO 标号。返回值:非负值,得到的 GPIO 值;负值,获取失败*/
        value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
        if(value == 0){atomic_set(&dev->keyvalue, keydesc->value);}/* 按下按键void atomic_set(atomic_t *v, int i) 向 v 写入 i 值。 */
        else{ /* 按键松开 */
                atomic_set(&dev->keyvalue, 0x80 | keydesc->value);atomic_set(&dev->releasekey, 1); }/* 标记松开按键 */
 }

  /*keyio_init 函数,按键 IO 初始化函数,在驱动入口函数里面会调用 keyio_init来初始化按键 IO*/
   static int keyio_init(void)
   {
       unsigned char i = 0;
       int ret = 0;
       /*of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:
inline struct device_node *of_find_node_by_path(const char *path)
函数参数和返回值含义如下:path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个
节点的全路径。返回值:找到的节点,如果为 NULL 表示查找失败*/
       imx6uirq.nd = of_find_node_by_path("/key");
       if (imx6uirq.nd== NULL){printk("key node not find!\r\n");
       return -EINVAL;}
 /* 提取 GPIO */
 /*此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,
 此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编
号,此函数在驱动中使用很频繁!函数原型如下:int of_get_named_gpio(struct device_node *np,
 const char *propname, int index)
函数参数和返回值含义如下:np:设备节点。propname:包含要获取 GPIO 信息的属性名*/
        for (i = 0; i < KEY_NUM; i++) {imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,"key-gpio", i);

                    if (imx6uirq.irqkeydesc[i].gpio < 0) {printk("can't get key%d\r\n", i);}
                }

                /* 初始化 key 所使用的 IO,并且设置成中断模式 */
                /*void *memset(void *str, int c, size_t n)
 解释:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
作用:是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
头文件:C中#include<string.h>,C++中#include<cstring>*/
/*轮流初始化所有的按键,包括申请 IO、设置 IO 为输入模式、从设备树中获取 IO 的中断号等等*/
                for (i = 0; i < KEY_NUM; i++) 
                {memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));
               sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
               /*gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request
进行申请,函数原型如下:int gpio_request(unsigned gpio, const char *label)
函数参数和返回值含义如下:
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
label:给 gpio 设置个名字。返回值:0,申请成功;其他值,申请失败*/
               gpio_request(imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].name);
               /*此函数用于设置某个 GPIO 为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)函数参数和返回值含义如下:
gpio:要设置为输入的 GPIO 标号。返回值:0,设置成功;负值,设置失败*/
// irq_of_parse_and_map 函数从设备树中获取按键 IO 对应的中断号
               gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(
imx6uirq.nd, i);
#if 0
                imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
                printk("key%d:gpio=%d, irqnum=%d\r\n",i,imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].irqnum);
                }
                /* 申请中断 */
//设置 KEY0 按键对应的按键中断处理函数为 key0_handler、KEY0 的按键值为 KEY0VALUE
                imx6uirq.irqkeydesc[0].handler = key0_handler;
                imx6uirq.irqkeydesc[0].value = KEY0VALUE;

                for (i = 0; i < KEY_NUM; i++) {
                    /*request_irq 函数用于申请中断*/
                    /*轮流调用 request_irq 函数申请中断号,设置中断触发模式为
IRQF_TRIGGER_FALLING 和 IRQF_TRIGGER_RISING,也就是上升沿和下降沿都可以触发中断。*/
                    ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,imx6uirq.irqkeydesc[i].handler,
                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,imx6uirq.irqkeydesc[i].name, &imx6uirq);

                if(ret < 0){printk("irq %d request failed!\r\n",imx6uirq.irqkeydesc[i].irqnum);
                return -EFAULT;
                                }
                }/* 创建定时器 */
                init_timer(&imx6uirq.timer);
                imx6uirq.timer.function = timer_function;
            return 0;

   }
/*
 * @description        : 打开设备  @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量  一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq; /* 设置私有数据 */
    return 0;
}

/*
 * @description        : 从设备读取数据   @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区  @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移  @return             : 读取的字节数,如果为负值,表示读取失败
 */
/*imx6uirq_read 函数,对应应用程序的 read 函数。此函数向应用程序返回按
键值。首先判断 imx6uirq 结构体的 releasekey 成员变量值是否为 1,如果为 1 的话表示有一次
有效按键发生,否则的话就直接返回-EINVAL。当有按键事件发生的话就要向应用程序发送按
键值,首先判断按键值的最高位是否为 1,如果为 1 的话就表示按键值有效。如果按键值有效
的话就将最高位清除,得到真实的按键值,然后通过 copy_to_user 函数返回给应用程序。向应
用程序发送按键值完成以后就将 imx6uirq 结构体的 releasekey 成员变量清零,准备下一次按键
操作。*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    /*int atomic_read(atomic_t *v) 读取 v 的值,并且返回。*/
    /*atomic_t keyvalue; 有效的按键键值             atomic_t releasekey; 标记是否完成一次完成的按键*/
    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 0;
    data_error:
    return -EINVAL;
}

/*
 * @description        : 向设备写数据  @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据 @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移   @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t imx6uirq_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
  @description        : 关闭/释放设备 @param - filp     : 
  要关闭的设备文件(文件描述符) @return             : 0 成功;其他 失败 */
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops= {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .write = imx6uirq_write,
    .release =     imx6uirq_release,
};

static int __init imx6uirq_init(void)
{//构建设备号
        if (imx6uirq.major)
        {    imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,IMX6UIRQ_NAME);
        }
        else
        {
            alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,IMX6UIRQ_NAME);
            imx6uirq.major = MAJOR(imx6uirq.devid);
            imx6uirq.minor = MINOR(imx6uirq.devid);
        }
        /* 2、注册字符设备 */
        /*void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。*/
        cdev_init(&imx6uirq.cdev, &imx6uirq_fops);

        /*3、添加字符设备*/
        /*cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),
        首先使用 cdev_init 函数cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数*/
        /*int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参数 count 是要添加的设备数量*/
        cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

/*4、创建类 */
/*struct class *class_create (struct module *owner, const char *name)
class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。
返回值是个指向结构体 class 的指针,也就是创建的类。*/
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
/* 5、创建设备 */
/*创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设
备。使用 device_create 函数在类下面创建设备
device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父
设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用
的一些数据,一般为 NULL;参数 fmt 是设备名字*/
  imx6uirq.device = device_create(imx6uirq.class, NULL,imx6uirq.devid, NULL, IMX6UIRQ_NAME);
  if (IS_ERR(imx6uirq.device)) {return PTR_ERR(imx6uirq.device);}

  /* 5、初始化按键 */
  /* INVAKEY             0XFF            无效的按键值 */
  /*atomic_t keyvalue; 有效的按键键值             atomic_t releasekey; 标记是否完成一次完成的按键*/
  atomic_set(&imx6uirq.keyvalue, INVAKEY);
  atomic_set(&imx6uirq.releasekey, 0);
  keyio_init();

    return 0;
}

static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;

    /* 删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
    free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);

    gpio_free(imx6uirq.irqkeydesc[i].gpio);}

    cdev_del(&imx6uirq.cdev);

    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

    device_destroy(imx6uirq.class, imx6uirq.devid);

    class_destroy(imx6uirq.class);

    /*卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备,cdev_del
函数原型如下:void cdev_del(struct cdev *p)参数 p 就是要删除的字符设备。如果要删除字符设备*/
cdev_del(&imx6uirq.cdev);

/*注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
此函数有两个参数:from:要释放的设备号。count:表示从 from 开始,要释放的设备号数量。*/
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

/*卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原
型如下:void device_destroy(struct class *class, dev_t devt)
参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。*/
 device_destroy(imx6uirq.class, imx6uirq.devid);

/*卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:
void class_destroy(struct class *cls);参数 cls 就是要删除的类*/
 class_destroy(imx6uirq.class);

  
};

module_init(imx6uirq_init);
module_exit( imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cgb");

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值