鍵盤驅動

  作者:趙老師,華清遠見嵌進式學院講師。

  按鍵字符設備的file_operatiONs結構定義爲:

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

以下爲open和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, BUTT*_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;
    }

  以上先容了按鍵驅動程序中的主要內容。

  “本文由華清遠見http://www.embedu.org/index.htm提供”



  來源:華清遠見
發佈了18 篇原創文章 · 獲贊 12 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章