mini2440_buttons驅動詳解

 

//mini2440_buttons_my.c//
//後面加了_my//

//按鍵驅動程序//

//mini2440所用到的按鍵資源//
//
// 按鍵          對應的IO寄存器     對應的中斷引腳//
// K1             GPG0                 EINT8      //
// K2             GPG3                 EINT11     //
// K3             GPG5                 EINT13     //
// K4             GPG6                 EINT14     //
// K5             GPG7                 EINT15     //
// K6             GPG11                EINT19     //
//

//要搞清楚誰是輸入//
//在這裏,按鍵控制對應的中斷引腳,從而控制對應的IO寄存器//
//相當於信息從外面輸入//
//我們要做的是根據對應的輸入信息,來採取相應的響應動作//
//這就達到了中斷響應的目的//
//其核心就是要檢測//
//那麼,該如何去檢測呢?//
//通過什麼來檢測呢?//

//如何得知一個設備究竟用到哪些資源呢?//
//這是個非常重要的問題//
//我想應該看具體的電路原理圖//
//只有看圖,才能瞭解具體的電路連接情況//
//從而得知設備所需的硬件資源//
//廠商的原理圖通常給的都比較詳細//

//引用的頭文件//

#include <linux/module.h> //模塊有關的//

#include <linux/kernel.h> //內核有關的//

#include <linux/fs.h> //文件系統有關的//

#include <linux/init.h> //init//

#include <linux/delay.h> //delay//

#include <linux/poll.h> //poll//

#include <asm/irq.h> //中斷//

#include <linux/interrupt.h> //linux中斷//

#include <asm/uaccess.h> //uaccess//

#include <asm/arch/regs-gpio.h> //寄存器設置//

#include <asm/hardware.h> //hardware//

//定義宏//

#define BUTTON_MAJOR 221 //主設備號,本來是232,我改爲221//

#define DEVICE_NAME "buttons_my" //設備名,本來是buttons,我加上了_my//

//定義按鈕中斷的描述結構體//
//由它把按鈕中斷的信息綜合起來//
//各個成員表示什麼意思?//

struct button_irq_desc
{
int irq; //中斷號//
//中斷號唯一表示一箇中斷//

int pin; //中斷控制的寄存器//
//該寄存器的值由中斷引腳設置//
//我們希望從該寄存器讀出控制信息//

int pin_setting; //中斷的引腳//
//該引腳的電平由按鍵來控制//
//從而最終我們由按鍵控制了寄存器的值//

int number; //編號//
char *name; //名稱//
};

//指定6個按鍵的信息//

static struct button_irq_desc button_irqs [] =
{
{IRQ_EINT8,S3C2410_GPG0,S3C2410_GPG0_EINT8,0,"KEY1"}, //K1//
{IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG3_EINT11,1,"KEY2"}, //K2//
{IRQ_EINT13,S3C2410_GPG5,S3C2410_GPG5_EINT13,2,"KEY3"}, //K3//
{IRQ_EINT14,S3C2410_GPG6,S3C2410_GPG6_EINT14,3,"KEY4"}, //K4//
{IRQ_EINT15,S3C2410_GPG7,S3C2410_GPG7_EINT15,4,"KEY5"}, //K5//
{IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG11_EINT19,5,"KEY6"}, //K6//
}

//這樣,資源就組織起來了//
//事實上,在這裏我們不僅組織起了硬件資源//
//我們也把一定的軟件資源也糅合進去了//
//像中斷號//


//key_values數組//
//存放各個按鍵在發生中斷情況下的值//
//volatile是什麼意思呢?//
//這個數組是我們存放按鍵操作結果的,因此非常重要//

static volatile int key_values [] = {0,0,0,0,0,0};


//宏DECLARE_WAIT_QUEUE_HEAD(),是幹什麼的呢?//
//該宏應該是創建了一個等待隊列//
//等待隊列,是進程調度的一種重要方法//
//等待隊列也很有意思,button_waitq,表示按鍵等待的隊列//
//就是說,按鍵一按下,就會激活其等待隊列裏的進程,來做相應的處理//
//因此,按鍵的等待隊列,或者說中斷所設置的等待隊列,//
//是中斷處理中非常重要的資源,它大大擴展了中斷處理的能力//


static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //button_waitq是什麼呢?//
//應該是等待隊列的名稱//


//key_values數組中是否有數據的標誌,0表示無數據可讀,1表示有數據可讀//

static volatile int ev_press = 0; //初始爲0//

//中斷服務程序buttons_interrupt()的申明//
//即當檢測到有中斷時,就會執行該中斷服務程序//
//那麼如何檢測到有中斷髮生呢?//
//並且中斷髮生了,知道發生了什麼樣的中斷呢?//
//中斷有很多種,該中斷服務程序究竟該服務於哪一個中斷呢?//
//顯然,要把中斷號與中斷服務程序聯結起來,構成一個整體//
//這個工作可以在open函數裏做//

//參數irq---中斷號//
//中斷服務程序應該是與中斷號一一對應的//
//對應於某個中斷號的中斷一發生,就會調用該中斷號對應的服務程序//
//那麼,檢測中斷的發生,就成了先決條件//
//參數dev_id ---具體是哪一個按鈕//

static irqreturn_t buttons_interrupt(int irq,void *dev_id);

//mini2440_buttons_open()函數申明//
//驅動函數open調用的具體函數//
//由open函數具體實現硬件的初始化工作//
//以及軟件的初始化工作//
//爲我們的鍵盤設備的運行創造好環境//

static int mini2440_buttons_open(struct inode *inode,struct file *file);

//mini2440_buttons_close()函數的申明//
//release調用的具體函數//
//設備軟件環境的拆卸//
//具體就是中斷的釋放工作//
//因爲中斷資源,也是系統寶貴的資源,所以不用的時候,要釋放//

static int mini2440_buttons_close(struct inode *inode,struct file *file);

//mini2440_buttons_read()函數的申明//
//read調用的具體函數//
//由它讀取鍵盤輸入的結果//
//實質上就是讀取key_values數組的值//
//它完成了鍵盤作爲輸入設備的核心功能//
//數組是否可讀,要根據標誌位ev_press來判斷//
//如果數組可讀,則讀取數據到用戶buffer中//
//如果數組不可讀,則進程進入等待隊列,等待到數組可讀爲止//
//等待隊列機制,是中斷管理中常用到的機制//
//因爲有些進程經常需要等待某一事件的發生//

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
//注意__user,指的是用戶空間//
//即要把鍵盤的輸入結果讀取到用戶空間去//

//mini2440_buttons_poll()函數的申明//
//poll調用的具體函數//
//poll實質上是select的調用函數//
//如果有按鍵數據,則select會立刻返回//
//如果沒有按鍵數據,則等待//
//實質上這是鍵盤等待輸入的機制//

static unsigned int mini2440_buttons_poll(struct file *file,struct poll_table_struct *wait);


//file_operations結構體//
//驅動函數的設置//
//分別將前面的驅動函數設置進來//

static struct file_operations mini2440_buttons_fops =
{
.owner = THIS_MODULE,

.open = mini2440_buttons_open, //open()//

.release = mini2440_buttons_close, //release()//

.read = mini2440_buttons_read, //read()//

.poll = mini2440_buttons_poll //poll()//
};

//mini2440_buttons_init()函數的申明//
//module_init調用的具體函數//
//模塊創建時的初始化函數//
//主要做的工作是註冊設備和創建設備//
//而具體的硬件初始化工作,它可以不做//
//而把它留給fops裏的函數來做//

static int __init mini2440_buttons_init(void);

//mini2440_buttons_exit()函數的申明//
//模塊卸載時的掃尾工作//
//主要是設備的卸載工作//

static void __exit mini2440_buttons_exit(void);

//模塊創建時的入口點//

module_init(mini2440_buttons_init);


//模塊卸載時的入口點//

module_exit(mini2440_buttons_exit);

//驅動程序的一些信息//

MODULE_AUTHOR("http://www.arm9.net"); //驅動程序的作者//

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver"); //描述信息//

MODULE_LICENSE("GPL"); //遵循的協議//

//下面是前面申明函數的實現//

static int __init mini2440_buttons_init(void)
{
int ret; //設備註冊的返回值//

//註冊設備驅動程序//
//設備號,設備名,和驅動函數//

ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops);

//對註冊失敗的處理//

if(ret < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return ret;
}

//創建設備//
//devfs_mk_cdev()函數是內核態的設備創建函數//
//而mknod是用戶態的設備創建函數//

devfs_mk_cdev(MKDEV(BUTTON_MAJOR,0),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP,DEVICE_NAME);

printk(DEVICE_NAME " initialized\n");

return 0;
}


//

static void __exit mini2440_buttons_exit(void)
{
//移除設備//

devfs_remove(DEVICE_NAME);

//注消設備驅動//

unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);
}


//

static int mini2440_buttons_open(struct inode *inode,struct file *file)
{
int i;    //循環變量,因爲有6個按鈕//

int err; //中斷註冊函數的返回值//

//對每個按鈕分別處理,用for循環來做//
//具體地是要聯結寄存器和相應的引腳//
//聯結中斷號和相應的中斷服務程序//
//這一步類似於前面所說的驅動的註冊//
//我們可以成功稱作中斷的註冊//

for(i = 0;i < sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
{
//寄存器與中斷引腳的聯結//

   s3c2410_gpio_cfgpin(button_irqs[i].pin,button_irqs[i].pin_setting);

   //中斷的註冊//
//request_irq()函數//
//要注意其輸入參數//
//&button_irqs[i]是該中斷享有的資源//
//會被傳入buttons_interrupt,進行處理//

   err = request_irq(button_irqs[i].irq,buttons_interrupt,NULL,button_irqs[i].name,(void *)&button_irqs[i]);

   //中斷類型的設置//
//set_irq_type()函數//
//IRQT_BOTHEDGE的中斷類型代表什麼樣的中斷呢?//

   //有幾個非常重要的問題//
//中斷註冊後,並設置好其中斷類型之後,當有中斷髮生時,//
//即按下某個按鈕時,系統能夠自動檢測到有中斷髮生嗎?//
//檢測到有中斷髮生,它能夠自動辨別是幾號中斷嗎?//
//知道了是幾號中斷,那麼它能自動調用其中斷服務程序嗎?//
//對這幾個問題的解答,夠成了linux系統中斷處理機制的核心//

   set_irq_type(button_irqs[i].irq,IRQT_BOTHEDGE);

   //註冊失敗的處理//

   if(err)
break; //跳出循環//
}

//若有一個按鈕中斷註冊失敗//
//則還需把前面註冊成功的中斷給拆了//

if(err)
{
i--; //回到前面一個按鈕的處理//

   for(;i >=0; i--) //依此拆除//
{
//使中斷不起作用//

    disable_irq(button_irqs[i].irq);

    //釋放中斷資源//

    free_irq(button_irqs[i].irq,(void *)&button_irqs[i]);
}

   return -EBUSY; //中斷註冊沒成功的最終的返回值//
}

return 0; //正常返回//
}


//
//此中斷服務程序,在每中斷一次,就要對key_values數組設一下值//
//並對數組可讀標誌位ev_press設一下值//
//並喚醒在等待隊列裏的進程//
//這是中斷處理經常要做的事情//
//在這裏,等待隊列button_waitq裏經常等待的進程是數組的讀取進程//
//就是說,讀取進程在沒有讀到數據的時候就一直在等待,等待按鍵的輸入//
//讀取進程在等待,並不代表所有進程在等待,其它進程該幹啥幹啥去//

static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
//button_irq_desc結構體變量//
//對傳入的資源進行處理//

struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;

//獲取寄存器的值//
//這一步至關重要//
//s3c2410_gpio_getpin()函數直接獲取寄存器的值//

//要注意,按一下按鈕,會發生兩次中斷//

//即按下是一次中斷,放開又是一次中斷//

int up = s3c2410_gpio_getpin(button_irqs->pin);

//通過電路原理圖,可以知道沒按下的時候,中斷引腳應該是高電平//
//從而寄存器的值應該是1//
//變量取up也是有意義的,表示默認狀態是彈起的狀態//
//當按下按鈕的狀態下,寄存器的值就應該是0//

//下面對up的值進行處理//
//即是要把數據經過一定的變換存入key_values數組中//

if(up)   //如果是彈起的狀態//
//那麼就要在key_values數組的相應位存入很大的一個值//
//同時又要能從值裏辨別出是哪個按鍵//

   key_values[button_irqs->number] = (button_irqs->number + 1) + 0x80;
//比如K1鍵開啓的狀態下,key_values[0]被置爲(0+1)+0x80,即爲129//

else //如果按鍵是閉合的狀態//
//那麼就要在key_values數組的相應位存入一個很小的數//
//同時又要能從值中辨別出是哪個鍵//

   key_values[button_irqs->number] = (button_irqs->number + 1);
//比如K1鍵閉合,則key_values[0]被置爲(0+1),即爲1//

//對數組可讀標誌位進行設置//

ev_press = 1; //表示數組已經可讀了//

//喚醒休眠的進程?//
//button_waitq隊列裏存放有相應的處理進程//
//如讀取數組的值的進程//
//要注意wake_up_interruptible()這些函數的用法//

wake_up_interruptible(&button_waitq);

//返回//

return IRQ_RETVAL(IRQ_HANDLED); //?//
}

//

static int mini2440_buttons_close(struct inode *inode,struct file *file)
{
int i; //循環變量,要操作好幾個按鍵//

//for循環,對各個按鍵依此釋放中斷//

for(i = 0;i < sizeof(button_irqs)/sizeof(button_irqs[0]);i++)
{
//使中斷失效//

   disable_irq(button_irqs[i].irq);

   //釋放資源//

   free_irq(button_irqs[i].irq,(void *)&button_irqs[i]);
}

//返回//

return 0;
}


//

//要注意,該read函數,只讀取一次中斷的值,而不是連續地讀入//
//要做到連續地讀入,則需要做一個循環,不斷地調用該read函數,但那不是驅動程序裏該做的事情//

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
unsigned long err;   //copy_to_user()函數的返回值//

//如果key_values 數組裏沒有值,則會此進程會休眠//
//一直到中斷來臨之後,中斷服務程序會喚醒此休眠進程從而繼續讀取值//
//key_values數組裏有沒有值,是靠ev_press標誌位來判斷的//
//有值,就是1,無值,就是0//

//進程等待隊列的機制,是進程調度的一種方法//

if(!ev_press) //標誌位爲0,即無數據時//
{
if(filp->f_flags & O_NONBLOCK) //??//
return -EAGAIN;
else //進程休眠,放進button_waitq等待隊列//
//這裏,把ev_press標誌位設成了休眠進程的標誌位了?//
//這是爲了便於利用poll_wait函數//
//也就是利於select函數//
wait_event_interruptible(button_waitq,ev_press);
//在中斷處理函數中,此進程會被喚醒//
//喚醒前,ev_press 已被置1了//
//喚醒後的執行點從這裏開始//
}

//下面就是標誌位爲1,即有數據可讀的的處理情況//

//那就開始往用戶空間讀數據唄//

err = copy_to_user(buff,(const void *)key_values,min(sizeof(key_values),count));
//copy_to_user()函數的使用//

//對key_values數組清零//

memset((void *)key_values,0,sizeof(key_values));

//對標誌位置0//
//表示讀取過了//

ev_press = 0;

//對err的處理//

if(err) //讀取錯誤//
return -EFAULT;
else //讀取正確//
//則返回讀取到的字節數//
return min(sizeof(key_values),count);
}

//

static unsigned int mini2440_buttons_poll(struct file *file,struct poll_table_struct *wait)
{
unsigned int mask = 0; // //

//poll_wait()函數//
//會監測進程隊列button_waitq裏的進程//
//例如,如果mini2440_button_read所在的進程的標誌位ev_press置爲1了//
//那麼就不會再等待了//
//這實質上就是select函數的運行機制//

poll_wait(file,&button_waitq,wait);

if(ev_press)
mask |= POLLIN | POLLRDNORM; //??//

return mask;
}

下面是測試代碼:

//按鍵測試程序//

#include <stdio.h> //標準輸入輸出頭文件//

#include <stdlib.h> //標準庫//

#include <unistd.h> //一些宏的定義在這裏//

#include <sys/ioctl.h> //設備的控制//

#include <sys/types.h> //定義了一些類型//

#include <sys/stat.h> //狀態//

#include <fcntl.h> //文件控制//

#include <sys/select.h> //選擇?//

#include <sys/time.h> //時間方面的函數//

#include <errno.h> //有關錯誤方面的宏//

//主函數入口//

int main(void)
{
int i; //鍵盤輸出時用到的循環變量//

int buttons_fd; //buttons設備號//

int key_value[4]; //四個按鍵的取值//

//打開鍵盤設備文件//

buttons_fd = open("/dev/buttons",0); //以0方式打開//

//打開出錯處理//

if(buttons_fd < 0) //打開出錯就會返回一個負值//
{
perror("open device buttons"); //perror函數?//

   exit(1); //返回1//
}

//for無限循環,等待用戶輸入//
//這是很典型的程序執行方式//

for(;;)
{
fd_set rds; //fd_set是types.h中定義的類型,實質上是int型//
//rds用來存儲設備號//

   int ret; //for循環內定義的局部變量ret//

   FD_ZERO(&rds); //rds初始化//
//FD_ZERO是哪裏定義的呢?//

   FD_SET(buttons_fd,&rds); //將buttons設備號賦給rds//
//FD_SET是哪裏定義的呢?//

   //使用系統調用select檢查是否能夠從/dev/buttons設備讀取數據//
//select函數是幹什麼的呢?//

   ret = select(buttons_fd + 1,&rds,NULL,NULL,NULL);
//返回值ret//
//返回值的具體意義是什麼呢?//

   //對ret的處理//

   if(ret < 0) //當ret小於0//
{
perror("select");
exit(1);
}    

   if(ret == 0) //當ret等於0//
{
printf("Timeout.\n");
}
else //能夠讀到數據//
if(FD_ISSET(buttons_fd,&rds)) //??//
{
//讀取鍵盤驅動發出的數據//
//key_value和鍵盤驅動中定義一致//

     int ret = read(buttons_fd,key_value,sizeof(key_value)); //注意此處的ret和前面的ret有何不同//
//注意鍵盤設備讀取的特點//

     //對ret的處理//
if(ret != sizeof(key_value)) //沒有接收夠//
{
if(errno != EAGAIN)   //???//
perror("read buttons\n");
continue;
}
else //正確接收,則打印到標準終端//
{
for(i = 0;i < 4;i++) //最開始定義的循環變量i//
printf("K%d %s, key value = 0xx\n",i,(key_value[i] & 0x80) ? "released" : key_value[i] ? "pressed down" : "",key_value[i]);
//這一連串的輸出,要注意格式//
}
}
}

//關閉設備//

close(buttons_fd);

return 0; //主函數返回//

END!!

 

發佈了10 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章