sep0718 key driver.
*
* Changelog:
* 1-June-2010 LSF Initial version   
*
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/spinlock_types.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/ioport.h>
#include <asm/irq.h>
#include <mach/gpio.h>

#define COL1_INT       15                  //中斷號
#define COL2_INT       14
#define COL3_INT       13    

#define KEY_MAJOR      200                 //主設備號
#define MAX_KEY_BUF    16                  //按鍵緩衝區的大小
#define KEY_XNUM       3
#define KEY_YNUM       3

#define KEY_UP         0
#define KEY_DOWN       1
#define KEY_UNSURE     2

#define KEY_TIMER_DELAY_JUDGE       4      //判斷是否有按鍵按下   jiffes
#define KEY_TIMER_DELAY_LONGTOUCH   10     //判斷是否長時間按下 jiffes

static int key_map[] = {0,1,2,3,4,5,6,7,8};
       
struct keydev
{   
unsigned int keystatus;             //按鍵狀態   
unsigned int buf[MAX_KEY_BUF];      //按鍵緩衝區
unsigned int write,read;            //按鍵緩衝區頭和尾
wait_queue_head_t wq;               //等待隊列
struct cdev cdev;
} ;

struct keydev *key_dev;             //鍵盤結構體
struct timer_list key_timer;        //定時器

//開啓鍵盤中斷
static void unmaskkey(void)   
{         
   SEP0718_INT_ENABLE(COL1_INT);
   SEP0718_INT_ENABLE(COL2_INT);
   SEP0718_INT_ENABLE(COL3_INT);
}

//關閉鍵盤中斷
static void maskkey(void)          
{
SEP0718_INT_DISABLE (COL1_INT) ;
SEP0718_INT_DISABLE (COL2_INT) ;
SEP0718_INT_DISABLE (COL3_INT) ;
}

static void sep0718_key_setup(void)
{
maskkey();                                  //關閉鍵盤中斷

   sep0718_gpio_cfgpin( E , 01 , 6 , 1 ,1);   
   sep0718_gpio_cfgpin( E , 01 , 7 , 1 ,1);
   sep0718_gpio_cfgpin( E , 01 , 8 , 1 ,1);     //set sel :general ; dir : output
   sep0718_gpio_setpin( E , 0 ,6);
   sep0718_gpio_setpin( E , 0 ,7);
   sep0718_gpio_setpin( E , 0 ,8);              //pin level :low

   sep0718_gpio_cfgpin( I , 01 , 13 , 0 ,1);
   sep0718_gpio_cfgpin( I , 01 , 14 , 0 ,1);
   sep0718_gpio_cfgpin( I , 01 , 15 , 0 ,1);   //dir : input
   sep0718_gpio_cfgpin( I , 10 , 13 , 11 ,1);
   sep0718_gpio_cfgpin( I , 10 , 14 , 11 ,1);
   sep0718_gpio_cfgpin( I , 10 , 15 , 11 ,1);//interrupt type: lowlevel triggered

unmaskkey();
}

static irqreturn_t sep0718_key_irqhandler(int irq, void *dev_id)
{      
   int i ;
int row_num = -1;
   int row = -1;
   int col = -1;
int irq_value = 0;
int key_value = 0;

maskkey();                                             //關閉鍵盤中斷
   SEP0718_INT_CLR(irq);                                   //清除相應的外部中斷signal    
    sep0718_gpio_cfgpin( I,11,0,0xe000,-1 );
// *(volatile unsigned long*)GPIO_PORTI_INTCLR_V = 0xe000 ;
key_dev->keystatus = KEY_UNSURE;
one:
   mdelay(10);
irq_value = ( sep0718_gpio_getpin(I,-1) & 0xe000 ) ;   //讀取中斷口數值
if (irq_value != 0xe000)                               //如果有低電平,表示鍵盤仍然有鍵被按着 //FP
   {
     if (key_dev->keystatus == KEY_UNSURE)
        {
     key_dev->keystatus = KEY_DOWN;
                             //讀取鍵盤的位置     
           col = irq -13 ;
       for (i=0; i<KEY_YNUM; i++)    
                  {
               row_num = 6+i ;
         sep0718_gpio_setpin( E,1,row_num );
         if ( sep0718_gpio_getpin( I,irq) )
                  {
        row = i + 1 ;
                   }
           sep0718_gpio_setpin( E,0,row_num );
                         }
           key_value = col*3 + row ;
           printk("row is %d\n col is %d\n key_value is %d\n",row,col,key_value);
        if ( key_value > 0 )
                                {
             key_dev->buf[key_dev->write] = key_map[key_value];
        if (++(key_dev->write) == MAX_KEY_BUF)              //按鍵緩衝區循環存取
               {
         key_dev->write = 0;
              }
                               }
     wake_up_interruptible(&(key_dev->wq));
      goto one ;    //檢測是否持續按鍵   
}
else    //一定是鍵按下
{
      goto one ;
}
}
else      //鍵已抬起
{
key_dev->keystatus = KEY_UP;
unmaskkey(); //打開鍵盤中斷
}
return IRQ_HANDLED;
}


static ssize_t sep0718_key_read(struct file *filp, char __user *buff, size_t size, loff_t *ppos)
{
int total_num = 0;
unsigned long err;
int i;
int buffer[17] = {0};
printk("enter into sep0718_key_read !!!!!\n");
retry:
    if ((key_dev->write) != (key_dev->read))    //當前緩衝隊列中有數據
{
   if ((key_dev->write) > (key_dev->read))
   {
    total_num = (key_dev->write) - (key_dev->read);
   }
   else
   {
    total_num = (16 - key_dev->read) + key_dev->write;
   }
  
   if (size > total_num)
   {
    size = total_num;
   }

//上面這一部分主要功能是計算出緩存中還有中數據的大小,並用size來表示  

buffer[0] = size;
   for (i=1; i<(size+1); i++)
   {
    buffer[i] = key_dev->buf[key_dev->read];
    if (++(key_dev->read) == MAX_KEY_BUF)
    {
     key_dev->read = 0;
    }
   }
   err = copy_to_user(buff, (char *)buffer, (size+1)*4);

//將緩存key_dev->buf[ ]中的數據全部拷貝到buffer[ ]數組中,然後通過copy_to_user()函數將buffer[ ]中的數據由內核拷貝到用户區,結束時key_dev->read == key_dev->write !!!

在鍵盤測試程序中,由於每次中按鍵一次就執行測試程序,故按鍵值顯示的始終是當前按鍵值!
   return err ? -EFAULT : 0;
}
else                                       //當前緩衝隊列中沒有數據
{
   if(filp->f_flags & O_NONBLOCK)          //假如用户採用的是非堵塞方式讀取
          return -EAGAIN;

   interruptible_sleep_on(&(key_dev->wq)); //用户採用阻塞方式讀取,調用該函數使進程睡眠
   goto retry;
}
}

static ssize_t sep0718_key_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
return 0;
}

static int sep0718_key_open(struct inode *inode, struct file *filp)
{  
sep0718_key_setup();
return 0;
}

static int sep0718_key_release(struct inode *inode, struct file *filp)
{
return 0;
}

static struct file_operations sep0718_key_fops =
{
.owner = THIS_MODULE,
.read = sep0718_key_read,
.write = sep0718_key_write,
.open = sep0718_key_open,
.release = sep0718_key_release,
};

static int sep0718_request_irqs(void)       //FP
{
      //申請中斷
if (request_irq(COL1_INT,sep0718_key_irqhandler,IRQF_DISABLED,"0718KEY",NULL))
goto irq0_fail;
if (request_irq(COL2_INT,sep0718_key_irqhandler,IRQF_DISABLED,"0718KEY",NULL))
goto irq1_fail;
if (request_irq(COL3_INT,sep0718_key_irqhandler,IRQF_DISABLED,"0718KEY",NULL))
goto irq2_fail;   
return 0;
//出錯處理
irq2_fail:
free_irq(COL3_INT,NULL);
irq1_fail:
free_irq(COL2_INT,NULL);           
irq0_fail:
free_irq(COL1_INT,NULL);
return -1;
}

static void sep0718_free_irqs(void)
{
free_irq(COL1_INT,NULL);
free_irq(COL2_INT,NULL);
free_irq(COL3_INT,NULL);
}

static int __init sep0718_key_init(void)
{
int err,result;
dev_t devno;
devno = MKDEV(KEY_MAJOR, 0);
   result = register_chrdev_region(devno, 1, "sep0718_key");   //向系統靜態申請設備號
   if (result < 0)
{
return result;
}
key_dev = kmalloc(sizeof(struct keydev), GFP_KERNEL);
if (key_dev == NULL)
{
result = -ENOMEM;
unregister_chrdev_region(devno, 1);
return result;
}
memset(key_dev,0,sizeof(struct keydev));       //初始化

if(sep0718_request_irqs())                      //註冊中斷函數
{
unregister_chrdev_region(devno,1);
kfree(key_dev);
return -1;
}
cdev_init(&key_dev->cdev, &sep0718_key_fops);
   key_dev->cdev.owner = THIS_MODULE;
key_dev->keystatus = KEY_UP;
init_waitqueue_head(&(key_dev->wq));

err = cdev_add(&key_dev->cdev, devno, 1);     //向系統註冊該字符設備
if (err)
{
unregister_chrdev_region(devno,1);
kfree(key_dev);
sep0718_free_irqs();
return err;
}
return 0;
}

static void __exit sep0718_key_exit(void)
{
sep0718_free_irqs();
cdev_del(&key_dev->cdev);
kfree(key_dev);
unregister_chrdev_region(MKDEV(KEY_MAJOR, 0), 1);
}

module_init(sep0718_key_init);
module_exit(sep0718_key_exit);

MODULE_AUTHOR("LSF");
MODULE_LICENSE("GPL");