2018-12-14 11:17:17 weixin_42108004 阅读数 372
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5163 人正在学习 去看看 朱有鹏

0.0上一个按键驱动使用查询方式,占用cpu为99%,根本不实用,因此使用中断方式按键驱动。

0.1驱动功能:记录按键按下次数并发往用户端。读取按键状态时,如果按键未按下则休眠进程,按键按下则进入中断服务函数,在isr中唤醒进程并将对应按键按下的次数加1.

一、

宏定义设备名称和主设备号,定义中断描述结构体及初始化结构体参数,按键次数静态全局数组,按键状态变量(0表示未按下,1表示按下),注册等待队列。


#define DEVICE_NAME	"buttons"		/* 设备名称 */
#define BUTTON_MAJOR	232				/* 主设备号 */

static volatile unsigned int ev_press = 0;	/* 按键状态量 */

struct button_irq_des {
		unsigned int irq;
		unsigned long flags;
		char *name;
};
static volatile int  press_cnt[4];
static struct button_irq_des button_irqs[4]=
{
	{IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"},/* K1 */
	{IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"},/* K2 */
	{IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"},/* K3 */
	{IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"},/* K4 */
};
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);/* 注册等待队列 */

二、

定义并初始化file_operations,其中的tird_dvr_open,third_drv_read函数在测试程序中的open,read函数会用的到。

open中主要是对按键中断注册,一共四个按键中断,逐个注册,若有一个注册失败,则释放之前所有已经注册的中断。

read负责传输按键次数数组的值到用户层。

close释放所有已经注册的中断。

static struct file_operations third_drv_fops = {
		.owner = THIS_MODULE,
		.open   = third_drv_open,
		.read	=	third_drv_read,
		.release = third_drv_close,
};

 

static int third_drv_open(struct inode *inode, struct file *file)
{
	int i;
	int err;
	printk("enter open fun\n");
	/* 逐个注册中断 */
	for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
	{
		err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
							  button_irqs[i].name, (void*)&press_cnt[i]);
		if(err)
			break;
	}
	/* 如果最后一个注册失败,释放全部已经注册的中断 */
	if(err)
	{
		i--;
		while(i>=0)
		{
			free_irq(button_irqs[i].irq, (void*)&press_cnt[i]);
			i--;
		}
	return -EBUSY;
	}
	return 0; 
}

 


static int third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long err;
	printk("enter read fun\n");
	/* 等待创建的队列,当条件为1时,唤醒进程,否则进程一直休眠 */
	wait_event_interruptible(buttons_waitq, ev_press);
	
	/* 执行到此处说明ev_press已经为1,已经执行完中断处理程序,将ev_press清零 */
	ev_press = 0;

	/* 复制按键状态到用户*/
	printk("copy to usr\n");
	err = copy_to_user(buf, (const void *)press_cnt, min(sizeof(press_cnt), size));
	/** 此处是关键找了好久bug,若想增加按键按下的次数,此处不能对数组清零 **/
	//memset((void *)press_cnt, 0, sizeof(press_cnt));
	//printk("%d\n",err);
	return (!err ? 0 : -EFAULT);

}

 


static int third_drv_close(struct inode * inode, struct file * file)
{
	int i;

	for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++)
	{
			free_irq(button_irqs[i].irq, (void*)&press_cnt[i]);
	}
	return 0;
}


三、

在中断服务程序中对按键计数数组对应值计数,dev_id是在注册中断时传入的press_cnt数组元素的地址,因此直接强制类型转换为press_cnt所指向的类型后自加。接着唤醒休眠的进程,返回上一次进入休眠的下一步操作,即清零按键状态和向用户传输数据。

static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
	/* 进入中断,按键按下 */
	ev_press = 1;

  *(volatile int *)dev_id += 1;

	/* 唤醒休眠的进程 */
	wake_up_interruptible(&buttons_waitq);
  printk("wake up\n");  
	
  return IRQ_RETVAL(IRQ_HANDLED);
}

四、

注册file_operations和卸载file_operations,在注册时由于没有使用mdev自动创建设备节点的方法(即创建class和device_class的方法),因此之后需要手动创建设备节点(mknod buttons  c  232  0).


static int __init third_drv_init(void)
{
	int err;
	
  err	= register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &third_drv_fops); // 注册, 告诉内核
	//thirddrv_class = class_create(THIS_MODULE,DEVICE_NAME);
	//thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(BUTTON_MAJOR, 0), NULL,DEVICE_NAME);
	if(err < 0)
	{
		printk(DEVICE_NAME"can't register!\n");
		return err;
	}
	printk(DEVICE_NAME "initialized\n");
	return 0;
}

static void __exit third_drv_exit(void)
{		
	unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME); // 卸载
	//class_device_unregister(thirddrv_class_dev);
	//class_destroy(thirddrv_class);
}

module_init(third_drv_init);
module_exit(third_drv_exit);

MODULE_LICENSE("GPL");

五、

在按键程序中打开设备节点,读取传入user的数据放入数组,并打印数组值。

int main(int argc, char *argv[])
{
	int fd;
	int i;
	int ret;
	int  val[4];

	fd = open("/dev/buttons", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
		return -1;
	}
	while(1)
	{
		ret = read(fd, val, sizeof(val));
   	if (ret < 0) {
        printf("read err!\n");
        continue;
    }
		for(i = 0; i < 4; i++)
		{
			if(val[i])
			{
				printf("K%d has been pressed %d times!\n", i+1, val[i]);
			}
		}
	}
	
	return 0;
}

测试信息:成功显示按键次数,并且按键未按下时cpu只占用0%

使用函数printf()时,记得加上换行,否则数据在输出缓冲区中不立即输出至终端。

isr中对按键次数操作时由于dev_id是地址,所以不能忽略间接访问符  *

测试时必须手动创建设备节点。

 

 

 

 

 

 

 

 

2014-09-02 17:22:20 u013388374 阅读数 334
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5163 人正在学习 去看看 朱有鹏
混杂设备驱动模型
Linux中断处理
按键驱动硬件操作实现
中断分层设计
按键定时器去抖
阻塞型驱动程序设计
/—————————————————————————/
                              混杂设备驱动模型

Linux中,存在一类字符设备,其拥有相同主设备号(10),但次设备号不同,称混杂设备(miscdevice)。所有混杂设备形成一链表,对设备访问时据次设备号找到相应设备。

使用struct miscdevice来描述一混杂设备
struct miscdevice {
int minor; /* 次设备号*/
const char *name; /* 设备名*/
const struct file_operations *fops; /*文件操作*/
struct list_head list;
struct device *parent;
struct device *this_device;
};

注册int misc_register(struct miscdevice * misc);
注销int misc_deregister(struct miscdevice *misc);

/——————/
                              Linux中断处理
__irq_svc:  //中断统一入口,entry-armv.S
	irq_handler  //标号

.macro	irq_handler
	arch_irq_handler_default
.endm

.macro	arch_irq_handler_default  //entry-macro-multi.S
1:	get_irqnr_and_base r0, r2, r6, lr  //获取中断号,架构不同,方法异
	bne	asm_do_IRQ
.endm

asm_do_IRQ (unsigned int irq, struct pt_regs *regs)  //中断号
	handle_IRQ
		generic_handle_irq(irq);
			struct irq_desc *desc = irq_to_desc(irq);  //根据irq获得irq_desc
			generic_handle_irq_desc(irq, desc);
				desc->handle_irq(irq, desc);  //调用irq对应的中断处理函数

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)  //返回0表成功,或返回一错误码
void *dev_id共享中断时使用
IRQF_DISABLED(SA_INTERRUPT)
如设置该位,表为快速中断处理程序;如没设置该位,表为慢速中断处理程序
IRQF_SHARED(SA_SHIRQ)表该中断为多设备共享(如上图多个struct irqaction,一设备中断,会顺序执行多个irqaction的handler,只需在handler中检测,实际未发生中断则立即退出即可)

快/慢速中断主要区别:快速中断保证中断处理原子性,而慢速中断则不保证。换句话说即开启中断标志位在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型中断所打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。

中断处理程序特别之处是在中断上下文中运行,它的行为受到某些限制:
1不能使用可能引起阻塞的函数(特别是快速中断处理程序)
2不能使用可能引起调度的函数

中断处理程序:检测设备是否真正产生中断——清除中断标志(CPU内部中断标志无需…,外部如DM9000中中断标志需…)——相应硬件操作
void free_irq(unsigned int irq, void *dev_id)

/——————/
                              按键驱动硬件操作实现
#define S3C2410_CPUIRQ_OFFSET	 (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
#define IRQ_EINT0      S3C2410_IRQ(0)	    /* 16 */
即IRQ_EINT0为中断号16,中断号0-15留给软中断(2440)
6410留0-31给软中断

request_irq(IRQ_EINT0,……)时所初始化为irq_desc[16],实际发生中断时:
.macro	get_irqnr_and_base, irqnr, irqstat, base, tmp  //entry-macro.S
	ldr	\irqnr, [ \base, #INTOFFSET ]
	adds	\irqnr, \irqnr, #IRQ_EINT0  //加16

/——————/
                              中断分层设计
慢速中断:不同中断类型,产生中断嵌套;相同中断类型,被忽略
快速中断:全被忽略
故会出现中断丢失,为避免该情况,引入中断分层概念(上半部:和硬件相关、下半部:和硬件无关)

上半部:当中断发生时,它进行相应硬件读写,并登记该中断。通常由中断处理程序充当上半部
下半部:在系统空闲时对上半部登记的中断进行后续处理

中断分层方式:软中断、tasklet、工作队列(使用最广泛)
工作队列是一种将任务推后执行的形式,他把推后的任务交由一内核线程去执行。这
样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做工
作,由这些工作组成的队列称工作队列

Linux内核使用struct workqueue_struct来描述一工作队列:
struct workqueue_struct {
	struct cpu_workqueue_struct *cpu_wq;
	struct list_head list;
	const char *name; /*workqueue name*/
	int singlethread;
	int freezeable; /* Freeze threads during suspend */
	int rt;
};
Linux内核使用struct work_struct来描述一工作项:
struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;  //最重要
};
typedef void (*work_func_t)(struct work_struct *work);
创建工作队列create_workqueue
创建工作INIT_WORK
提交工作queue_work
#include <linux/init.h>
#include <linux/module.h>

struct workqueue_struct *my_wq;
struct work_struct *work1;
struct work_struct *work2;

void work1_func(struct work_struct *work)
{
    printk("this is work1->\n");	
}

void work2_func(struct work_struct *work)
{
    printk("this is work2->\n");	
}

int init_que(void)
{
    my_wq = create_workqueue("my_que");
    work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    INIT_WORK(work1, work1_func);
    queue_work(my_wq,work1);

    work2 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    INIT_WORK(work2, work2_func);
    queue_work(my_wq,work2);
    return 0;
}

void clean_que()
{
}

module_init(init_que);
module_exit(clean_que);
MODULE_LICENSE("GPL");
大多数情况下, 驱动并不需自己建立工作队列,只需定义工作, 然后将工作提交到内核已
定义好的工作队列keventd_wq中:schedule_work
int init_que(void)
{	
    work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    INIT_WORK(work1, work1_func);
    schedule_work(work1);

    work2 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    INIT_WORK(work2, work2_func);
    schedule_work(work2);
    return 0;
}

——————/
                             按键定时器去抖
按键去抖方法主要有二种,一种是硬件电路去抖;另一种是软件延时去抖。而延时又分二种,一种是for循环等待,另一种是定时器延时。在OS中,由于效率方面原因,只能使用定时器。
Linux内核使用struct timer_list描述一定时器:
struct timer_list {
	struct list_head entry;
	unsigned long expires;
	struct tvec_base *base;
	void (*function)(unsigned long);
	unsigned long data;
	int slack;
};
定义定时器变量:struct timer_list buttons_timer;
初始化定时器:
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
注册定时器:add_timer(&buttons_timer);
启动定时器:mod_timer(&buttons_timer, jiffies + (HZ /10));

——————/
                             阻塞型驱动程序设计
定义等待队列wait_queue_head_t my_queue
初始化等待队列init_waitqueue_head(&my_queue)
定义+初始化等待队列DECLARE_WAIT_QUEUE_HEAD(my_queue)

wait_event(queue,condition)
当condition为真,立即返回;否则进入TASK_UNINTERRUPTIBLE模式睡眠,并挂在queue所指定队列上
wait_event_interruptible(queue,condition)当condition为真,立即返回;否则进入
TASK_INTERRUPTIBLE睡眠,并……
int wait_event_killable(queue, condition)当condition为真,立即返回;否则进入
TASK_KILLABLE睡眠,并……

wake_up(wait_queue_t *q)从等待队列唤醒状态为TASK_UNINTERRUPTIBLE,
TASK_INTERRUPTIBLE,TASK_KILLABLE 所有进程

wake_up_interruptible(wait_queue_t *q)唤醒状态为TASK_INTERRUPTIBLE进程
2019-05-15 10:01:56 Chasing_Chasing 阅读数 72
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5163 人正在学习 去看看 朱有鹏

31.1 前言

按键是设备中是最常见的人机交互方式,本节中将学习两部分。

(1)如何4个GPIO 16个按键的实现;

(2)Linux input按键驱动开发实例编程;

31.2 4个IO驱动16按键原理

在常见的按键驱动中,我们可以设计一个按键矩阵,给按键设定一个坐标。如由8个GPIO驱动的4x4矩阵按键,便是通过水平、垂直两个维度对按键进行坐标化。本节讲到的由4GPIO驱动16按键的设计,是GPIO按键驱动按键的一种优化,该方案需要软硬结合,在按键矩阵中添加晶体管,以完成4GPIO驱动16按键的方案。硬件实现如下示:

                                                    

 

如上图示,4个GPIO默认都是上拉输入(即默认在高电平状态),当有按键按下时,4个GPIO的某个就会被拉为低电平。读取数值的原理为通过扫描4个GPIO进行按键判断。

扫描方法如下:

(1)KEY1-4 设置为上拉输入,程序依次读取这四个IO的值,此时读取的到值是对应上图E列的按键,当发现某个IO为低电平时,返回此时扫描的序号,该序号可以辨别是哪个按键按下的;

(2)设KEY1输出低电平,KEY2-4设置为上拉输入,程序读取KEY2-4IO的值,此时对应的是A列IO

(3)设KEY2输出低电平,KEY1、KEY3-4设置为上拉输入,程序读取KEY1、KEY3-4IO的值,此时对应的是B列IO

(4)设KEY3输出低电平,KEY1-2、KEY-4设置为上拉输入,程序读取KEY1-2、KEY-4IO的值,此时对应的是C列IO

(5)设KEY4输出低电平,KEY1-3设置为上拉输入,程序读取KEY1-3IO的值,此时对应的是D列IO

读取按键代码实现:

static int read_kbd_key(void)  
{  
    int key_num = 0;  
    int i, j;  
      
    /*设GPIO全为上拉输入*/  
    if (gpio_direction_set(0x00) == -1)  
        goto err_direction_set;  
  
    /*读取E列IO*/  
    for (j = 0; j < 4; j++) {  
        if ((gpio_get_value(gpio_pin[j]) & 0x01) == 0)  
            return key_num;  
        key_num++;  
    }  
      
    /*读取A-D列IO*/  
    for (i = 0; i < 4; i++) {  
        for (j = 0; j < 4; j++) {  
            if (j == i)  
                continue;  
  
            if ((gpio_get_value(gpio_pin[j]) & 0x01) == 0)  
                return key_num;  
            key_num++;  
        }  
    }  
err_direction_set:  
    return -1;  
} 

31.3 Linux Input按键驱动实现

Linux驱动编程包含两个部分,第一个是对硬件设备初始化;第二个是根据Linux驱动框架填充驱动代码。

         对于Linux 3.x有引入设备树及gpiolib的内核,一般会在编译内核的时候已经配置好处理器的所有IO,并使用GPIOLib对所有GPIO进行统一管理,因此如需操作某个GPIO那么可以直接调用内核gpiolib库提供的操作函数库,申请对某IO的控制权。引入GPIOLib的目的是避免多个驱动控制一个IO所带来的混乱,gpiolib需要在编译内核的时候选上支持gpiolib。

         输入子系统由驱动层、输入子系统核心、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。

struct input_dev 结构数据说明:

struct input_dev {

 

const char *name

设备名

char *phys;

设备文件节点名

char *uniq

全球唯一的ID号

struct input_id id

设备id;用于匹配事件处理层handler

unsigned long evbit

表示设备支持的事件类型

unsigned long keybit

表示支持的按键类型

unsigned long relbit

支持相对坐标的值

unsigned long absbit

支持绝对坐标的值

………

 

 

相关函数说明

struct input_dev *input_allocate_device

动态分配一个struct input_dev 结构;返回一个指针

input_register_device(*dev)

注册input设备;dev为inputDev指针

input_unregister_device(*dev)

注销input设备;dev为inputDev指针

set_bit(event,bit)

设置input子系统支持哪些事件;参数为事件类型,设置位。如设置支持按键:set_bit(EV_KEY,input_dev->evbit);

input_report_key(*dev, code, value)

上报按键事件及值

input_report_rel(*dev, code, value)

上报相对值

input_report_abs (*dev, code, value)

上报绝对值

input_sync(*dev)

同步上报事件

 

 

31.4 Linux input子系统驱动示例

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/slab.h> //kmalloc头文件
#include <linux/input.h>
#include <linux/sys_config.h>
#include <linux/gpio.h>
#include <asm/io.h>

struct input_dev *inputKeyDev;		/*input设备指针*/

//上报示例处理
static int inputDev_IRQHandler()
{
	input_report_key(inputKeyDev,KEY_1, !gpio_get_value(KEY_1));
	input_sync(inputKeyDev);		
	return 0;
}

static int __init inputDev_Init(void)
{
	int  nRet = -1;
	/*分配一个input结构*/
	inputKeyDev = input_allocate_device();
	if (!inputKeyDev) {
		nRet = -ENOMEM;
		goto iExit0;
	}
	/*设置支持事件类型*/
	set_bit(EV_SYN,inputKeyDev->evbit);
	set_bit(EV_KEY,inputKeyDev->evbit);
	set_bit(KEY_1,inputKeyDev->keybit);

	/*注册到输入子系统中*/
	nRet = input_register_device(inputKeyDev);
	 if(nRet)
		 goto iExit0;

	return nRet;
iExit0:	
	if(inputKeyDev!=NULL)  input_free_device(inputKeyDev);

	return nRet;
}

static void __exit inputDev_Exit(void)
{
	/*注销输入子系统设备*/
	input_unregister_device(inputKeyDev);
	/*释放申请的内存*/
	input_free_device(inputKeyDev);
}

module_init(inputDev_Init);
module_exit(inputDev_Exit);
MODULE_LICENSE("GPL");

31.5 Linux 16按键驱动实例

/*****************************************************************
* 包含头文件
******************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/slab.h> //kmalloc头文件
#include <linux/input.h>
#include <linux/sys_config.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>  
#include <linux/sched.h>

/*****************************************************************
* 宏定义(仅在当前C文件使用的宏定义写在当前C文件中,否则需写在H文件中)
******************************************************************/
#define IKEY_PIN_1 (GPIOD(16))
#define IKEY_PIN_2 (GPIOD(17))
#define IKEY_PIN_3 (GPIOD(19))
#define IKEY_PIN_4 (GPIOD(22))

#define INPUT_IKEY_NAME "iKey"
#define IKEY_TIMEOUT_MS	(80)			/*80ms扫描一次按键*/
/*****************************************************************
* 结构定义(仅在当前C文件使用的结构体写在当前C文件中,否则需写在H文件中)
******************************************************************/
struct _iKeyInfoSt{
	int iKeyNum;						/*保存按键值*/	
	int iKeyUpFlag;						/*是否上报*/
	int nQueWaiting;					/*等待队列条件*/
	wait_queue_head_t iwaitQue;			/*等待队列*/
	struct hrtimer ihrtimer;			/*高精度定时器*/
	struct timer_list iKeyTimer;		/*轮询定制器,每隔50ms轮询一次键盘*/	
	struct work_struct iKeyDectWork;	/*键盘检测工作队列*/
	struct delayed_work iKeyDelayWork;/*键盘检测工作队列*/
	struct input_dev *inputKeyDev;		/*input设备指针*/
};

/*****************************************************************
* 全局变量定义
******************************************************************/
struct _iKeyInfoSt *piKeyInfoSt = NULL;

static unsigned int igpio_pin[] = {
	IKEY_PIN_1, IKEY_PIN_2, IKEY_PIN_3, IKEY_PIN_4,
};

/*键盘对应的上报的键值*/
static u32 iKeycodes[16] = {
	KEY_DOWN,
	KEY_F1,
	KEY_HOME,
	KEY_UP,
	KEY_8,
	KEY_0,
	KEY_1,
	KEY_5,
	KEY_BACKSPACE,
	KEY_2,
	KEY_6,
	KEY_9,
	KEY_3,
	KEY_4,
	KEY_7,
	KEY_KPASTERISK
};
/*****************************************************************
* 静态变量定义
******************************************************************/

/*****************************************************************
* 外部变量声明(如果全局变量没有在其它的H文件声明,引用时需在此处声明,
*如果已在其它H文件声明,则只需包含此H文件即可)
******************************************************************/
																						
/*****************************************************************
* 函数原型声明
******************************************************************/
static enum hrtimer_restart iKey_hrtimerHander(struct hrtimer *timer)  
{
	piKeyInfoSt->nQueWaiting = 1;
	wake_up(&piKeyInfoSt->iwaitQue);
	return HRTIMER_NORESTART;
}

static int iKey_gpioSetDirection(unsigned int ndir)
{
	unsigned int i = 0;	
	for (i = 0; i < 4; i++) {
		if (ndir & BIT(i)) {
			if (gpio_direction_output(igpio_pin[i], 0)) {
				return -1;
			}
		} else {
			if (gpio_direction_input(igpio_pin[i])) {
				return -1;
			}
		}
	}

	/*启动高精度定时器,结合等待队列延时1ms*/	
	hrtimer_start(&piKeyInfoSt->ihrtimer,ktime_set(0,1500*1000),HRTIMER_MODE_REL);
	wait_event(piKeyInfoSt->iwaitQue,piKeyInfoSt->nQueWaiting);
	piKeyInfoSt->nQueWaiting = 0;
	
	return 0;
}


static int iKey_readValue(void)
{
	int key_num = 0;
	int i, j;

	if (iKey_gpioSetDirection(0x00) == -1)
		goto iExit;

	for (j = 0; j < 4; j++)
	{
		if ((gpio_get_value(igpio_pin[j]) & 0x01) == 0)
			return key_num;
		key_num++;
	}

	for (i = 0; i < 4; i++) {
		if (iKey_gpioSetDirection(BIT(i)) == -1)
			goto iExit;

		for (j = 0; j < 4; j++)
		{
			if (j == i)
				continue;

			if ((gpio_get_value(igpio_pin[j]) & 0x01) == 0)
				return key_num;
			key_num++;
		}
	}

iExit:
	return -1;
}


static void iKey_delayWorkCallBack(struct work_struct *data)
{
	int key_num = 0;

	key_num = iKey_readValue();
	if (key_num == piKeyInfoSt->iKeyNum)
	{
		input_report_key(piKeyInfoSt->inputKeyDev, iKeycodes[piKeyInfoSt->iKeyNum], 1);
		input_sync(piKeyInfoSt->inputKeyDev);
		piKeyInfoSt->iKeyUpFlag = 1;
	}
}


static void iKey_dectWorkCallBack(struct work_struct *data)
{
	int key_num = 0;

	/*扫描读取按键值*/
	key_num = iKey_readValue();
	if (key_num != -1) 
	{
		if(piKeyInfoSt->iKeyUpFlag!=1) piKeyInfoSt->iKeyNum = key_num;
		/*延时20ms消抖*/
		schedule_delayed_work(&piKeyInfoSt->iKeyDelayWork, msecs_to_jiffies(20));
	}
	else if(piKeyInfoSt->iKeyUpFlag == 1)
	{
		input_report_key(piKeyInfoSt->inputKeyDev, iKeycodes[piKeyInfoSt->iKeyNum], 0);
		input_sync(piKeyInfoSt->inputKeyDev);		
		piKeyInfoSt->iKeyUpFlag = 0;
	}
	
	mod_timer(&piKeyInfoSt->iKeyTimer, jiffies + msecs_to_jiffies(IKEY_TIMEOUT_MS));
}

void iKey_timerCallBack(unsigned long arg)
{
	schedule_work(&piKeyInfoSt->iKeyDectWork);
}

static int iKey_gpioInit()
{
	int i = 0;
	char chlabel[16] ={0};
	
	for (i = 0; i < 4; i++)
	{
		if (gpio_is_valid(igpio_pin[i]))
		{
			memset(chlabel, 0, 16);
			sprintf(chlabel, "igpio_%d", i);
			if (gpio_request(igpio_pin[i], chlabel))
			{
				pr_err("gpio_request failed [%d]\n", igpio_pin[i]);
				goto iExit;
			}
		}
		else 
		{
			pr_err("wrong gpio num [%d]\n", igpio_pin[i]);
			goto iExit;
		}
	}

	return 0;
iExit:
	pr_err("gpio init failed\n");
	for (; i > 0; i--) {
		gpio_free(igpio_pin[i - 1]);
	}
	return -1;
}

static void iKey_gpioExit(void)
{
	unsigned int i = 0;
	for (i = 0; i < 4; i++)
	{
		gpio_free(igpio_pin[i]);
	}
}


static int __init iKey_Init(void)
{
	int  ni = 0;
	int  nRet = -1;
	
	/*分配内存保存结构*/
	piKeyInfoSt = kzalloc(sizeof(struct _iKeyInfoSt), GFP_KERNEL);
	if (!piKeyInfoSt){
		nRet = -ENOMEM;
		goto iExit0;
	}

	/*分配一个input结构*/
	piKeyInfoSt->inputKeyDev = input_allocate_device();
	if (!piKeyInfoSt->inputKeyDev) {
		nRet = -ENOMEM;
		goto iExit0;
	}
	
	/*填充对应结构的数据*/
	piKeyInfoSt->inputKeyDev->name			= INPUT_IKEY_NAME;
	piKeyInfoSt->inputKeyDev->phys			= "iKeyPhys";
	piKeyInfoSt->inputKeyDev->id.bustype	= BUS_HOST;
	piKeyInfoSt->inputKeyDev->id.vendor		= 0x0001;
	piKeyInfoSt->inputKeyDev->id.product	= 0x0001;
	piKeyInfoSt->inputKeyDev->id.version	= 0x0100;

	/*设置支持事件类型*/
	set_bit(EV_SYN,piKeyInfoSt->inputKeyDev->evbit);
	set_bit(EV_KEY,piKeyInfoSt->inputKeyDev->evbit);

	/*设置支持哪些按键*/
	for (ni = 0; ni < 16; ni++)
		set_bit(iKeycodes[ni], piKeyInfoSt->inputKeyDev->keybit);

	/*注册到输入子系统中*/
	nRet = input_register_device(piKeyInfoSt->inputKeyDev);
	 if(nRet)
		 goto iExit0;

	/*使用高精度定时器+等待队列进行延时*/
	piKeyInfoSt->iKeyUpFlag	= 0;
	piKeyInfoSt->nQueWaiting = 0;
	init_waitqueue_head(&piKeyInfoSt->iwaitQue);
	hrtimer_init(&piKeyInfoSt->ihrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);	
	piKeyInfoSt->ihrtimer.function = iKey_hrtimerHander;	/* 设置回调函数 */  

	/*初始化工作队列*/
	INIT_WORK(&piKeyInfoSt->iKeyDectWork, iKey_dectWorkCallBack);
	INIT_DELAYED_WORK(&piKeyInfoSt->iKeyDelayWork, iKey_delayWorkCallBack);

	/*Linux内核定时器初始化*/
	init_timer(&piKeyInfoSt->iKeyTimer);
	piKeyInfoSt->iKeyTimer.function= iKey_timerCallBack;
	piKeyInfoSt->iKeyTimer.expires = jiffies + msecs_to_jiffies(IKEY_TIMEOUT_MS);
	add_timer(&piKeyInfoSt->iKeyTimer);

	/*初始化GPIO*/
	nRet = iKey_gpioInit();
	if(nRet<0)
		goto iExit0;

	return 0;
iExit0:	
	if(piKeyInfoSt->inputKeyDev!=NULL)  input_free_device(piKeyInfoSt->inputKeyDev);
	if(piKeyInfoSt!=NULL) kfree(piKeyInfoSt);

	return nRet;
}

static void __exit iKey_Exit(void)
{
	/*注销输入子系统设备*/
	input_unregister_device(piKeyInfoSt->inputKeyDev);
	/*删除定时器*/
	del_timer(&piKeyInfoSt->iKeyTimer);	
	/*释放已申请的GPIO*/
	iKey_gpioExit();
	/*删除工作队列*/
	cancel_work_sync(&piKeyInfoSt->iKeyDectWork);
	cancel_delayed_work(&piKeyInfoSt->iKeyDelayWork);
	/*删除高精度定时器*/
	hrtimer_cancel(&piKeyInfoSt->ihrtimer);
	
	/*释放申请的内存*/
	if(piKeyInfoSt->inputKeyDev!=NULL)  input_free_device(piKeyInfoSt->inputKeyDev);
	if(piKeyInfoSt!=NULL) kfree(piKeyInfoSt);
}

module_init(iKey_Init);
module_exit(iKey_Exit);
MODULE_LICENSE("GPL");

 

2014-12-20 19:04:02 a912293097 阅读数 893
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5163 人正在学习 去看看 朱有鹏

目的:编写按键驱动

原理图:

按键与引脚对应关系:



1、编写驱动程序框架

2、查看原理图与s3c2440手册

3、编写open函数

配置引脚

4、编写read函数功能

驱动代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
MODULE_LICENSE("Dual BSD/GPL");

static struct class *buttondrv_class;
static struct class_devices *buttondrv_class_dev;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

static int button_dev_open(struct inode *inode ,struct file* file)
{
	//配置按键的引脚 GPF0,1,2,4为输入引脚
	*gpfcon &= ~((0x3<<0*2)|(0x3<<1*2)|(0x3<<2*2)|(0x3<<4*2));

	return 0;
}
ssize_t button_dev_read(struct file *file,char __user *buf,size_t size,loff_t *ppos)
{
	/*返回四个引脚的电平*/
	unsigned char key_vals[4];
	int regval;
	if(size !=sizeof(key_vals))
	{
		return -EINVAL;
	}
	regval = *gpfdat;
	/*读四个引脚的电平*/
	key_vals[0] = (regval &(1<<0))? 1 : 0;
	key_vals[1] = (regval &(1<<1))? 1 : 0;
	key_vals[2] = (regval &(1<<2))? 1 : 0;
	key_vals[3] = (regval &(1<<4))? 1 : 0;
	copy_to_user(buf,key_vals,sizeof(key_vals));
	return sizeof(key_vals);
}
static struct file_operations button_sdv_fops =
{
	.owner = THIS_MODULE,
	.open  = button_dev_open,
	.read = button_dev_read,
};
int major;
static int button_dev_init(void)//入口函数
{
	major = register_chrdev(0,"button_drv",&button_sdv_fops);

	buttondrv_class = class_create(THIS_MODULE,"button_drv");
	if(IS_ERR(buttondrv_class))
		return PTR_ERR(buttondrv_class);
	buttondrv_class_dev= device_create(buttondrv_class,NULL,MKDEV(major,0),NULL,"wq_button");
		if(unlikely(IS_ERR(buttondrv_class_dev)))
			return PTR_ERR(buttondrv_class_dev);

	/*映射物理地址*/
	gpfcon = (volatile unsigned long *) ioremap(0x56000050 ,16);
	gpfdat = gpfcon + 1;

	return 0;
}
static void button_dev_exit(void)
{
	unregister_chrdev(major,"button_drv");
	device_unregister(buttondrv_class_dev);
	class_destroy(buttondrv_class);

	iounmap(gpfcon);
}
module_init(button_dev_init);
module_exit(button_dev_exit);

测试程序代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/*
 * wq_device <dev> <on|off>
 */
int main(int argc, char **argv)
{
	int cnt=0;
	int fd;
	unsigned char key_vals[4];
	fd = open("/dev/wq_button",	O_RDWR);
	if(fd<0)
	{
		printf("can't open \n");
	}
	while(1)
	{
		read(fd,key_vals,sizeof(key_vals));
		if(!key_vals[0]||!key_vals[1]||!key_vals[2]||!key_vals[3])
		{
			printf("%04d k1 k2 k3 k4  is %d %d %d %d \n",cnt++,key_vals[0],key_vals[1],key_vals[2],key_vals[3]);
		}
	}
	return 0;
}



2015-06-26 20:06:28 u010827484 阅读数 346
  • input子系统基础之按键-linux驱动开发第8部分

    本课程是linux驱动开发的第8个课程,主要内容是linux的input子系统。学习本课程的目标是对input子系统的框架结构有深入理解,应用层和驱动层的调用关系和方法完全了解,能够自己移植按键等常见输入类设备的驱动。

    5163 人正在学习 去看看 朱有鹏
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <asm/uaccess.h>


#define GPFCON 0x56000050
#define GPFDAT 0x56000054

struct work_struct *work1;

struct timer_list buttons_timer;

unsigned int *gpio_data;

unsigned int key_num;

void work1_func(struct work_struct *work)
{
    mod_timer(&buttons_timer, jiffies + (HZ /10));  
}

void buttons_timer_function(unsigned long data)  
{
    unsigned int key_val;

    key_val = readw(gpio_data)&0x1; 
    if (key_val == 0)
       key_num = 4;


    key_val = readw(gpio_data)&0x4;
    if (key_val == 0)
        key_num = 3;

} 


irqreturn_t key_int(int irq, void *dev_id)
{
    //1. 检测是否发生了按键中断

    //2. 清除已经发生的按键中断

    //3. 提交下半部
    schedule_work(work1);

    //return 0;
    return IRQ_HANDLED;

}

void key_hw_init()
{ 
    unsigned int *gpio_config;
    unsigned short data;

    gpio_config = ioremap(GPFCON,4);
    data = readw(gpio_config);
    data &= ~0b110011;
    data |= 0b100010;

    writew(data,gpio_config);

    gpio_data = ioremap(GPFDAT,4);

}


int key_open(struct inode *node,struct file *filp)
{
    return 0;   
}

ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    printk("in kernel :key num is %d\n",key_num);   
    copy_to_user(buf, &key_num, 4);

    return 4;
}

struct file_operations key_fops = 
{
    .open = key_open,
    .read = key_read,   
};

struct miscdevice key_miscdev = {
    .minor = 200,
    .name = "key",
    .fops = &key_fops,  
};

static int button_init()
{
    int ret;
    ret = misc_register(&key_miscdev);

    if (ret !=0)
        printk("register fail!\n");

    //注册中断处理程序
    request_irq(IRQ_EINT0,key_int,IRQF_TRIGGER_FALLING,"key",(void *)4);
    request_irq(IRQ_EINT2,key_int,IRQF_TRIGGER_FALLING,"key",(void *)3);

    //按键初始化
    key_hw_init();

    //. 创建工作
    work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    INIT_WORK(work1, work1_func);

    /* 初始化定时器 */  
    init_timer(&buttons_timer);   
    buttons_timer.function  = buttons_timer_function;  

    /* 向内核注册一个定时器 */  
    add_timer(&buttons_timer);  

    return 0;

}


static void button_exit()
{
    misc_deregister(&key_miscdev);  
}


module_init(button_init);
module_exit(button_exit);


linux按键驱动

阅读数 3632

LINUX 按键驱动

博文 来自: wince_lover

linux 按键驱动

阅读数 881

没有更多推荐了,返回首页