#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");