歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

AT9260 GPIO中斷

#i nclude <linux/init.h>
#i nclude <linux/dma-mapping.h>
#i nclude <linux/module.h>
#i nclude <linux/sched.h>
#i nclude <linux/completion.h>
#i nclude <linux/interrupt.h>
#i nclude <linux/clk.h>
#i nclude <linux/platform_device.h>
#i nclude <linux/atmel_pdc.h>
#i nclude <asm/io.h>
#i nclude <asm/system.h>
#i nclude <asm/uaccess.h>
#i nclude <linux/semaphore.h>
#i nclude <linux/kernel.h>
#i nclude <linux/cdev.h>
#i nclude <linux/types.h>
#i nclude <linux/fs.h>
#i nclude <linux/input.h>
#i nclude <linux/errno.h>
#i nclude <linux/irq.h>
#i nclude <linux/debugfs.h>
#i nclude <linux/seq_file.h>
#i nclude <linux/list.h>

 


#i nclude <mach/board.h>
#i nclude <mach/gpio.h>
#i nclude <mach/irqs.h>
#i nclude <mach/hardware.h>
#i nclude <mach/at91_pio.h>
#define DEVICE_NAME "button"


#define KEY_TIMER_DELAY1    (HZ/50)             //按鍵按下去抖延時20毫秒       
#define KEY_TIMER_DELAY2    (HZ/10)             //按鍵抬起去抖延時100毫秒


#define KEYSTATUS_DOWN              0           //按鍵按下                   
#define KEYSTATUS_UP                1           //按鍵抬起               
#define KEYSTATUS_DOWNX             2           //按鍵不確定 
#define KEY_NUM                     4           // 4個按鍵


#define DP_MAJOR 0//主設備號
#define DP_MINOR 0  //次設備號
unsigned int button_major = DP_MAJOR;


// 按鍵驅動的設備結構體、定時器
#define MAX_KEY_BUF     16   // 按鍵緩存區大小
typedef unsigned char KEY_RET;
// 設備結構體
typedef struct
{
    unsigned int keyStatus[KEY_NUM];    // 4個按鍵的按鍵狀態
    KEY_RET buf[MAX_KEY_BUF];
    unsigned int head,tail;             // 按鍵緩存區頭和尾
    wait_queue_head_t wq;               // 等待隊列
    struct cdev cdev;                   // cdev 結構體
}KEY_DEV;
KEY_DEV key_dev,*key_devp;

 


#define BUF_HEAD (key_dev.buf[key_dev.head]) //緩沖頭
#define BUF_TAIL (key_dev.buf[key_dev.tail]) //緩沖尾
#define INCBUF(x,mod) ((++(x))&((mod)-1))

 


static struct timer_list key_timer[KEY_NUM]; // 4個按鍵去抖定時器


// 按鍵硬件資源、鍵值信息結構體
static struct key_info
{
    int irq_no;                         // 中斷號
    int irq_type;                       // 中斷類型
    unsigned int gpio_port;             // GPIO端口
    int key_no;                         // 鍵值
   
   
}key_info_tab[4]=
// 按鍵所使用的CPU資源
{
   
    {
        AT91_PIN_PB0,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB0,1
    },
    {
        AT91_PIN_PB1,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB1,2
    },
    {
        AT91_PIN_PB2,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB2,3
    },
    {
        AT91_PIN_PB3,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB3,4
    },
};


// 初始化
static void at91sam9260_key_io_init(void)
{
    at91_set_gpio_input(AT91_PIN_PB0, 1);       
        at91_set_deglitch(AT91_PIN_PB0, 1);
       
        at91_set_gpio_input(AT91_PIN_PB1, 1);       
        at91_set_deglitch(AT91_PIN_PB1, 1);


        at91_set_gpio_input(AT91_PIN_PB2, 1);       
        at91_set_deglitch(AT91_PIN_PB2, 1);
       
        at91_set_gpio_input(AT91_PIN_PB3, 1);       
        at91_set_deglitch(AT91_PIN_PB3, 1);
}


/* 記錄鍵值並喚醒等待隊列 */
static void keyEvent(unsigned int key)
{
    BUF_HEAD = key_info_tab[key].key_no;                  // 記錄鍵值
    key_dev.head = INCBUF(key_dev.head, MAX_KEY_BUF); // 調整緩沖區頭指針
    wake_up_interruptible(&(key_dev.wq));                // 喚醒等待隊列
}

 


// 按鍵設備的中斷處理
// 鍵被按下後,將發生中斷,在中斷處理程序中,應該關閉中斷進入查詢模式,延遲20ms以實現去抖動
// 這個中斷處理過程只包含頂半部,無底半部
static irqreturn_t at91sam9260_enit_key(int irq,void *dev_id,struct pt_regs *reg)
{
    int key = (int)dev_id;
    int found = 0;
    int i;
 
    for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)   // 查找產生中斷的按鍵
    {
        if (key_info_tab[i].irq_no == irq)
        {
            found = 1;
            break;
        }
    }
    if (!found)                                     // 沒找到
    {
        printk(KERN_NOTICE"bad irq %d in button\n", irq);
        return IRQ_NONE;   //錯誤的中斷
    }
   
    disable_irq(key_info_tab[key].irq_no);          // 找到,關閉對應中斷   


    key_dev.keyStatus[key] = KEYSTATUS_DOWNX;  // 不確定是否為按下
    key_timer[key].expires = jiffies + KEY_TIMER_DELAY1;// 延遲
    add_timer(&key_timer[key]);  // 啟動定時器 


    return IRQ_HANDLED;   //正確的中斷
}
// 定時器處理函數
static void key_timer_handler(unsigned long data)
{
    int key = data;
    //獲取當前按鍵引腳上的電平值來判斷按鍵是按下還是抬起
    int up = at91_get_gpio_value(key_info_tab[key].gpio_port);
    if (!up)
    {
        if (key_dev.keyStatus[key] == KEYSTATUS_DOWNX)
        //從中斷進入
        {
            key_dev.keyStatus[key] = KEYSTATUS_DOWN;
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY2; //延遲          
            keyEvent(key);  //記錄鍵值,喚醒等待隊列
            add_timer(&key_timer[key]);
        }
        else
        {
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY2; //延遲
            add_timer(&key_timer[key]);
        }
    }
    else       //鍵已抬起
    {
        key_dev.keyStatus[key] = KEYSTATUS_UP;
        enable_irq(key_info_tab[key].irq_no);
    }
}

 


// 按鍵設備驅動的中斷申請函數
// 申請系統中斷,中斷方式為下降沿觸發
static int request_irqs(void)
{
    struct key_info *k;
    int i;
    int ret;
    for(i = 0; i < sizeof(key_info_tab)/sizeof(key_info_tab[1]); i++)
  //  for(i = 0; i < 1; i++)
    {
        k = key_info_tab + i;
        //設置4個IO口為中斷下降沿觸發方式
         set_irq_type(key_info_tab[i].irq_no, key_info_tab[i].irq_type);      // 設置中斷類型
           
        //申請中斷(類型為快速中斷,中斷服務時屏蔽所有外部中斷?)將按鍵序列號作為參數傳入中斷���務程序       
      //  if(request_irq(k->irq_no,at91sam9260_enit_key,0,DEVICE_NAME,(void *)i))
        ret = request_irq(k->irq_no,at91sam9260_enit_key,0,DEVICE_NAME,(void *)i);       
        if(ret)
        {
            printk(KERN_NOTICE "buttons:ret is %d\r\n",ret);
            return -1;
        }
    }


    return 0;
   
}
// 釋放中斷
static void free_irqs(void)
{
    struct key_info *k;
    int i;


    for(i = 0; i < sizeof(key_info_tab)/sizeof(key_info_tab[1]); i++)
    {
        k = key_info_tab + i;
        free_irq(k->irq_no,(void *)i);
    }
   
}

 

 

 


// 按鍵設備驅動的打開、釋放函數
static int at91sam9260_key_open(struct inode *inode, struct file *filp)
{
    key_dev.head = key_dev.tail = 0; //清空按鍵動作緩沖區
  //  keyEvent = keyEvent_raw; //函數指針指向按鍵處理函數keyEvent_raw
   
    return 0;
}


static int at91sam9260_key_release(struct inode *inode, struct file *filp)
{
  //  keyEvent = keyEvent_dummy; //函數指針指向空函數
   
    return 0;
}

 


// 按鍵設備驅動讀函數
static ssize_t at91sam9260_key_read(struct file *filp, char __user *buf, ssize_t count,
    loff_t*ppos)
{
    unsigned int key_ret;
    unsigned long flags;
retry:
    if (key_dev.head != key_dev.tail)                         // 緩沖區有數據?
    {
        local_irq_save(flags);                                      // 進入臨界區 ,關中斷      
        key_ret = BUF_TAIL;                                         // 讀出鍵值
        key_dev.tail = INCBUF(key_dev.tail, MAX_KEY_BUF);     // 調整緩沖區尾指針
        local_irq_restore(flags);                                   // 退出臨界區,開中斷       
        if(copy_to_user(buf, (void *)&key_ret, sizeof(unsigned int)))       // 拷貝到用戶空間   
        {
            return -EFAULT;
        }
        else
        {
            return sizeof(unsigned int);
        }
       
    }
    else   // 緩沖區沒數據
    {
        if (filp->f_flags & O_NONBLOCK)                             // 若采用非阻塞方式讀取則返回錯誤
        {
            return -EAGAIN;
        }
        interruptible_sleep_on(&(key_dev.wq));                   // 使進程睡眠
        if (signal_pending(current))                               //在這裡等中斷
        {   // 如果是信號中斷
            return -ERESTARTSYS;
        }
        goto retry;
    }
   
    return sizeof(unsigned int);
}


// 按鍵設備驅動文件操作結構體,主要實現打開、釋放和讀函數
static struct file_operations at91sam9260_key_fops =
{
    .owner = THIS_MODULE,
    .open = at91sam9260_key_open,            // 啟動設備
    .release = at91sam9260_key_release,      // 關閉設備
    .read = at91sam9260_key_read,            // 讀取按鍵的鍵值
};

 


// 模塊加載函數包括:設備號申請、cdev的添加,另:申請中斷、初始化定時器和隊列;
// 模塊卸載函數恰好相反
static int __init at91sam9260_key_init(void)
{
    int i;
    int result; 
   
    // 申請設備號      
    dev_t dev = MKDEV(button_major, DP_MINOR); 


     /* 申請設備號 */
    if (button_major)
        result = register_chrdev_region(dev, 1, DEVICE_NAME);
    else    /* 動態申請設備號 */
    {
        result = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
        button_major = MAJOR(dev);
        printk(KERN_INFO "Todo: mknod /dev/%s c %d 0\n", DEVICE_NAME, button_major);
    }
 


    // 分配設備結構體的內存
    key_devp = kmalloc(sizeof(key_dev),GFP_KERNEL);
    if(!key_devp)
    {
        result = - ENOMEM;
        goto fail_malloc;
    }


    // 添加cdev
    memset(key_devp,0,sizeof(key_dev));


    cdev_init(&key_dev.cdev, &at91sam9260_key_fops);
    if(cdev_add(&key_dev.cdev, dev, 1)){
        printk(KERN_ALERT"Add char dev error!\n");
    }
 


    key_dev.head = key_dev.tail = 0;             // 初始化結構體
    for(i = 0; i < KEY_NUM; i++)
    {
        key_dev.keyStatus[i] = KEYSTATUS_UP;
    }
    init_waitqueue_head(&(key_dev.wq));  // 等待隊列


 
    // 初始化定時器,實現軟件的去抖動


     /* 初始化定時器 */
    for(i = 0; i < KEY_NUM; i++)
    {       
        key_timer[i]. = key_timer_handler;
        key_timer[i].data = i;      
        init_timer(&key_timer[i]);
    }
    at91sam9260_key_io_init();   // 初始化  
     // 注冊中斷函數
    if(-1 == request_irqs())
    {
        printk(KERN_NOTICE "request_irqs failed!\r\n");
    }
    else
    {
        printk(KERN_NOTICE "request_irqs success!\r\n");
    }
    /*
    for(i = 0; i < KEY_NUM; i++)
    {
        setup_timer(&key_timer[i],key_timer_handler,i);
    }
    */
    fail_malloc:unregister_chrdev_region(dev,1);


    return result;
   
}


// 按鍵設備驅動的模塊卸載函數
static void __exit at91sam9260_key_exit(void)
{
    free_irqs();    // 注銷中斷


    // 釋放設備號,刪除cdev
    cdev_del(&key_dev.cdev);   // 刪除字符設備結構體
    kfree(key_devp);
    unregister_chrdev_region(MKDEV(button_major,DP_MINOR),1);  // 刪除字符設備
}


MODULE_AUTHOR("launch");
MODULE_LICENSE("Dual BSD/GPL");


module_init(at91sam9260_key_init);
module_exit(at91sam9260_key_exit);

Copyright © Linux教程網 All Rights Reserved