LINUX驱动之矩阵键盘


/* butt_drv.h */
//……

typedef struct _st_key_info_matrix       /* 按键数据结构 */
{
	unsigned char key_id;                /* 按键ID */
	unsigned int irq_no;                 /* 对应的中断号 */
	unsigned int irq_gpio_port;          /* 对应的中断线的输入端口地址*/
	unsigned int kscan_gpio_port;        /* 对应的KSCAN端口地址 */
} st_key_info_matrix;

typedef struct _st_key_buffer             /* 按键缓冲数据结构 */
{
	unsigned long jiffy[MAX_KEY_COUNT];   /* 按键时间, 5秒钟以前的铵键作废*/
	unsigned char buf[MAX_KEY_COUNT];     /* 按键缓冲区 */
	unsigned int head,tail;               /* 按键缓冲区头和尾 */
} st_key_buffer;
//……

//下面是矩阵按键数组的定义,数组元素的信息(一个按键信息)按照0行0列,0行1列,…,3行2列,3行3列的顺序逐行排列。
static st_key_info_matrix key_info_matrix[MAX_COLUMN][MAX_ROW] =
{
	{{10, 	IRQ_EINT0, S3C2410_GPF0, S3C2410_GPE11},		/* 0行0列 */
	{11, 	IRQ_EINT0, S3C2410_GPF0, S3C2410_GPG6},
	{12, 	IRQ_EINT0, S3C2410_GPF0, S3C2410_GPE13},
	{16, 	IRQ_EINT0, S3C2410_GPF0, S3C2410_GPG2}},
	{{7, 	IRQ_EINT2, S3C2410_GPF2, S3C2410_GPE11},		/* 1行0列 */
	{8, 	IRQ_EINT2, S3C2410_GPF2, S3C2410_GPG6},
	{9, 	IRQ_EINT2, S3C2410_GPF2, S3C2410_GPE13},
	{15, 	IRQ_EINT2, S3C2410_GPF2, S3C2410_GPG2}},
	{{4, 	IRQ_EINT11, S3C2410_GPG3, S3C2410_GPE11},		/* 2行0列 */
	{5, 	IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG6},
	{6, 	IRQ_EINT11, S3C2410_GPG3, S3C2410_GPE13},
	{14,	IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG2}},
	{{1, 	IRQ_EINT19, S3C2410_GPG11, S3C2410_GPE11},		/* 3行0列 */
	{2, 	IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG6},
	{3, 	IRQ_EINT19, S3C2410_GPG11, S3C2410_GPE13},
	{13, 	IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG2}},
};

//下面是与按键相关的端口的初始化函数。这些函数已经在简单的GPIO字符设备驱动程序里被使用过。此外,set_irq_type()函数用于设定中断线的类型,在本实例中通过该函数将4个中断线的类型配置为下降沿触发式。
static void init_gpio(void)
{
	s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2410_GPE11_OUTP);     /* GPE11 */
	s3c2410_gpio_setpin(S3C2410_GPE11, 0);
	s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2410_GPE13_OUTP);     /* GPE13 */
	s3c2410_gpio_setpin(S3C2410_GPE13, 0);
	s3c2410_gpio_cfgpin(S3C2410_GPG2, S3C2410_GPG2_OUTP);     /* GPG2 */
	s3c2410_gpio_setpin(S3C2410_GPG2, 0);
	s3c2410_gpio_cfgpin(S3C2410_GPG6, S3C2410_GPG6_OUTP);     /* GPG6 */
	s3c2410_gpio_setpin(S3C2410_GPG6, 0);
	s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_EINT0);     /* GPF0 */
	s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);     /* GPF2 */
	s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_EINT11);     /* GPG3 */
	s3c2410_gpio_cfgpin(S3C2410_GPG11, S3C2410_GPG11_EINT19);     /* GPG11 */
	set_irq_type(IRQ_EINT0, IRQT_FALLING);
	set_irq_type(IRQ_EINT2, IRQT_FALLING);
	set_irq_type(IRQ_EINT11, IRQT_FALLING);
	set_irq_type(IRQ_EINT19, IRQT_FALLING);
}
现在讨论按键驱动的主要接口,以下为驱动模块的入口和卸载函数。
/* 初始化并添加struct cdev结构到系统之中 */
static void button_setup_cdev(struct cdev *dev,
				   int minor, struct file_operations *fops)
{
	int err;
	int devno = MKDEV(button_major,minor);
	cdev_init(dev, fops); /* 初始化结构体struct cdev */
	dev->owner = THIS_MODULE;
	dev->ops = fops; /* 关联到设备的file_operations结构 */
	err = cdev_add(dev, devno, 1); /* 将struct cdev结构添加到系统之中 */
	if (err)
	{
		printk(KERN_INFO"Error %d adding button %d\n",err, minor);
	}
}
……
/* 驱动初始化 */
static int button_init(void)
{
	int ret;
	/* 将主设备号和次设备号定义到一个dev_t数据类型的结构体之中 */
	dev_t dev = MKDEV(button_major, 0);
	if (button_major)
	{/* 静态注册一个设备,设备号先前指定好,并设定设备名,用cat /proc/devices来查看 */
		ret = register_chrdev_region(dev, 1, BUTTONS_DEVICE_NAME);
	}
	else
	{ /*由系统动态分配主设备号 */
		ret = alloc_chrdev_region(&dev, 0, 1, BUTTONS_DEVICE_NAME);
		button_major = MAJOR(dev); /* 获得主设备号 */
	}
	if (ret < 0)
	{
		printk(KERN_WARNING"Button:unable to get major %d\n",button_major);
		return ret;
	}
	/* 初始化和添加结构体struct cdev到系统之中 */
	button_setup_cdev(&button_dev, 0, &button_fops);
	printk("Button driver initialized.\n");
	return 0;
}
/* 驱动卸载 */
static void __exit button_exit(void)
{
	cdev_del(&button_dev); /* 删除结构体struct cdev */
	/* 卸载设备驱动所占有的资源 */
	unregister_chrdev_region(MKDEV(button_major, 0), 1);
	printk("Button driver uninstalled\n");
}
module_init(button_init); /* 初始化设备驱动程序的入口 */
module_exit(button_exit); /* 卸载设备驱动程序的入口 */
MODULE_AUTHOR("David");
MODULE_LICENSE("Dual BSD/GPL");


static struct file_operations button_fops =
{
	.owner = THIS_MODULE,
	.ioctl = button_ioctl,
	.open = button_open,
	.read = button_read,
	.release = button_release,
};


/* 打开文件, 申请中断 */
static int button_open(struct inode *inode,struct file *filp) 
{
	int ret = nonseekable_open(inode, filp);
	if (ret < 0) 
	{
		 return ret;
	}
	init_gpio(); /* 相关GPIO端口的初始化*/
	request_irqs(); /* 申请4个中断 */
	if (ret < 0) 
	{
		 return ret;
	}
	init_keybuffer(); /* 初始化按键缓冲数据结构 */
	return ret;
}

/* 关闭文件, 屏蔽中断 */
static int button_release(struct inode *inode,struct file *filp)
{
	free_irqs(); /* 屏蔽中断 */
	return 0;
}

//在open函数接口中,进行了GPIO端口的初始化、申请硬件中断以及按键缓冲的初始化等工作。在以前的章节中提过,中断端口是比较宝贵而且数量有限的资源。因此需要注意,最好要在第一次打开设备时申请(调用request_irq函数)中断端口,而不是在驱动模块加载的时候申请。如果已加载的设备驱动占用而在一定时间段内不使用某些中断资源,则这些资源不会被其他驱动所使用,只能白白浪费掉。而在打开设备的时候(调用open函数接口)申请中断,则不同的设备驱动可以共享这些宝贵的中断资源。

//以下为中断申请和释放的部分以及中断处理函数。

/* 中断处理函数,其中irq为中断号 */
static irqreturn_t button_irq(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned char ucKey = 0;
	disable_irqs(); /* 屏蔽中断 */
	/* 延迟50毫秒, 屏蔽按键毛刺 */
	udelay(50000);
	ucKey = button_scan(irq); /* 扫描按键,获得进行操作的按键的ID */
	if ((ucKey >= 1) && (ucKey <= 16))
	{
		/* 如果缓冲区已满, 则不添加 */
		if (((key_buffer.head + 1) & (MAX_KEY_COUNT - 1)) != key_buffer.tail)
		{
			spin_lock_irq(&buffer_lock);
			key_buffer.jiffy[key_buffer.tail] = get_tick_count();
			key_buffer.tail ++;
			key_buffer.tail &= (MAX_KEY_COUNT -1);
			spin_unlock_irq(&buffer_lock);
		 }
	}
	init_gpio(); /* 初始化GPIO端口,主要是为了恢复中断端口配置 */
	enable_irqs(); /* 开启中断 */
	return IRQ_HANDLED;/* 2.6内核返回值一般是这个宏 */
}
	
/* 申请4个中断 */
static int request_irqs(void)
{
	int ret, i, j;
	for (i = 0; i < MAX_COLUMN; i++)
	{
		 ret = request_irq(key_info_matrix[i][0].irq_no, 
				  button_irq, SA_INTERRUPT, BUTTONS_DEVICE_NAME, NULL);
		 if (ret < 0)
		 {
				 for (j = 0; j < i; j++)
				 {
					 free_irq(key_info_matrix[j][0].irq_no, NULL); 
				 }
				 return -EFAULT;
		 }
	 }
	 return 0;
}
	
/* 释放中断 */
static __inline void free_irqs(void)
{
	int i;
	for (i = 0; i < MAX_COLUMN; i++)
	{
		free_irq(key_info_matrix[i][0].irq_no, NULL);
	}
}

//中断处理函数在每次中断产生的时候会被调用,因此它的执行时间要尽可能得短。通常中断处理函数只是简单地唤醒等待资源的任务,而复杂且耗时的工作则让这个任务去完成。中断处理函数不能向用户空间发送数据或者接收数据,不能做任何可能发生睡眠的操作,而且不能调用schedule()函数。

//为了简单起见,而且考虑到按键操作的时间比较长,在本实例中的中断处理函数button_irq()里,通过调用睡眠函数来消除毛刺信号。读者可以根据以上介绍的对中断处理函数的要求改进该部分代码。

//按键扫描函数如下所示。首先根据中断号确定操作按键所在行的位置,然后采用逐列扫描法最终确定操作按键所在的位置。

/* 
** 进入中断后, 扫描铵键码 
** 返回: 按键码(1-16), 0xff表示错误 
*/
static __inline unsigned char button_scan(int irq)
{
	unsigned char key_id = 0xff;
	unsigned char column = 0xff, row = 0xff;
	s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_INP); /* GPF0 */
	s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_INP); /* GPF2 */
	s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_INP); /* GPG3 */
	s3c2410_gpio_cfgpin(S3C2410_GPG11, S3C2410_GPG11_INP); /* GPG11 */
	switch (irq)
	{ /* 根据irq值确定操作按键所在行的位置*/
		case IRQ_EINT0:
		{
			  column = 0;
		}
		break;
		case IRQ_EINT2:
		{
			  column = 1;
		}
		break;
		case IRQ_EINT11:
		{
			  column = 2;
		}
		break;
		case IRQ_EINT19:
		{
			  column = 3;
		}
		break;
	} 
	if (column != 0xff)
	{ /* 开始逐列扫描, 扫描第0列 */
		 s3c2410_gpio_setpin(S3C2410_GPE11, 0); /* 将KSCAN0置为低电平 */
		 s3c2410_gpio_setpin(S3C2410_GPG6, 1);
		 s3c2410_gpio_setpin(S3C2410_GPE13, 1);
		 s3c2410_gpio_setpin(S3C2410_GPG2, 1);
	 if(!s3c2410_gpio_getpin(key_info_matrix[column][0].irq_gpio_port))
		 { /* 观察对应的中断线的输入端口值 */
			  key_id = key_info_matrix[column][0].key_id;
			  return key_id;
		 }
		 /* 扫描第1列*/
		 s3c2410_gpio_setpin(S3C2410_GPE11, 1);
		 s3c2410_gpio_setpin(S3C2410_GPG6, 0); /* 将KSCAN1置为低电平 */
		 s3c2410_gpio_setpin(S3C2410_GPE13, 1);
		 s3c2410_gpio_setpin(S3C2410_GPG2, 1);
		 if(!s3c2410_gpio_getpin(key_info_matrix[column][1].irq_gpio_port))
		 {
			  key_id = key_info_matrix[column][1].key_id; 
			  return key_id;
		 }
		 /* 扫描第2列*/
		 s3c2410_gpio_setpin(S3C2410_GPE11, 1);
		 s3c2410_gpio_setpin(S3C2410_GPG6, 1);
		 s3c2410_gpio_setpin(S3C2410_GPE13, 0); /* 将KSCAN2置为低电平 */
		 s3c2410_gpio_setpin(S3C2410_GPG2, 1); 
		 if(!s3c2410_gpio_getpin(key_info_matrix[column][2].irq_gpio_port))
		 {
			  key_id = key_info_matrix[column][2].key_id;
			  return key_id;
		 }
		 /* 扫描第3列*/
		 s3c2410_gpio_setpin(S3C2410_GPE11, 1);
		 s3c2410_gpio_setpin(S3C2410_GPG6, 1);
		 s3c2410_gpio_setpin(S3C2410_GPE13, 1);
		 s3c2410_gpio_setpin(S3C2410_GPG2, 0); /* 将KSCAN3置为低电平 */
		 if(!s3c2410_gpio_getpin(key_info_matrix[column][3].irq_gpio_port))
		 {
			   key_id = key_info_matrix[column][3].key_id;
			   return key_id;
		 }
	 } 
	 return key_id;
}

//以下是read函数接口的实现。首先在按键缓冲中删除已经过时的按键操作信息,接下来,从按键缓冲中读取一条信息(按键ID)并传递给用户层。

/* 从缓冲删除过时数据(5秒前的按键值) */
static void remove_timeoutkey(void)
{
	unsigned long tick;
	spin_lock_irq(&buffer_lock); /* 获得一个自旋锁 */
	while(key_buffer.head != key_buffer.tail)
	{
		 tick = get_tick_count() - key_buffer.jiffy[key_buffer.head];
		 if (tick < 5000) /* 5秒 */
		 break;
		 key_buffer.buf[key_buffer.head] = 0;
		 key_buffer.jiffy[key_buffer.head] = 0;
		 key_buffer.head ++;
		 key_buffer.head &= (MAX_KEY_COUNT -1);
	}
	spin_unlock_irq(&buffer_lock); /* 释放自旋锁 */
}

/* 读键盘 */
static ssize_t button_read(struct file *filp, 
						char *buffer, size_t count, loff_t *f_pos)
{
	 ssize_t ret = 0;
	 remove_timeoutkey(); /* 删除过时的按键操作信息 */
	 spin_lock_irq(&buffer_lock);
	 while((key_buffer.head != key_buffer.tail) && (((size_t)ret) < count))
	 {
		   put_user((char)(key_buffer.buf[key_buffer.head]), &buffer[ret]);
		   key_buffer.buf[key_buffer.head] = 0;
		   key_buffer.jiffy[key_buffer.head] = 0;
		   key_buffer.head ++;
		   key_buffer.head &= (MAX_KEY_COUNT -1);
		   ret ++;
	 }
	 spin_unlock_irq(&buffer_lock);
	 return ret;
}


你可能感兴趣的:(LINUX驱动之矩阵键盘)