按鍵字符設(shè)備的file_operations結(jié)構(gòu)定義為:
static struct file_operations button_fops =
{
.owner = THIS_MODULE,
.ioctl = button_ioctl,
.open = button_open,
.read = button_read,
.release = button_release,
};
以下為open和release函數(shù)接口的實現(xiàn)。
/* 打開文件, 申請中斷 */
static int button_open(struct inode *inode,struct file *filp)
{
int ret = nonseekable_open(inode, filp);
if (ret < 0)
{
return ret;
}
init_gpio(); /* 相關(guān)GPIO端口的初始化*/
request_irqs(); /* 申請4個中斷 */
if (ret < 0)
{
return ret;
}
init_keybuffer(); /* 初始化按鍵緩沖數(shù)據(jù)結(jié)構(gòu) */
return ret;
}
/* 關(guān)閉文件, 屏蔽中斷 */
static int button_release(struct inode *inode,struct file *filp)
{
free_irqs(); /* 屏蔽中斷 */
return 0;
}
在open函數(shù)接口中,進行了GPIO端口的初始化、申請硬件中斷以及按鍵緩沖的初始化等工作。在以前的章節(jié)中提過,中斷端口是比較寶貴而且數(shù)量有限的資源。因此需要注意,好要在第一次打開設(shè)備時申請(調(diào)用request_irq函數(shù))中斷端口,而不是在驅(qū)動模塊加載的時候申請。如果已加載的設(shè)備驅(qū)動占用而在一定時間段內(nèi)不使用某些中斷資源,則這些資源不會被其他驅(qū)動所使用,只能白白浪費掉。而在打開設(shè)備的時候(調(diào)用open函數(shù)接口)申請中斷,則不同的設(shè)備驅(qū)動可以共享這些寶貴的中斷資源。
以下為中斷申請和釋放的部分以及中斷處理函數(shù)。
/* 中斷處理函數(shù),其中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))
{
/* 如果緩沖區(qū)已滿, 則不添加 */
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端口,主要是為了恢復(fù)中斷端口配置 */
enable_irqs(); /* 開啟中斷 */
return IRQ_HANDLED;/* 2.6內(nèi)核返回值一般是這個宏 */
}
/* 申請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);
}
}
中斷處理函數(shù)在每次中斷產(chǎn)生的時候會被調(diào)用,因此它的執(zhí)行時間要盡可能得短。通常中斷處理函數(shù)只是簡單地喚醒等待資源的任務(wù),而復(fù)雜且耗時的工作則讓這個任務(wù)去完成。中斷處理函數(shù)不能向用戶空間發(fā)送數(shù)據(jù)或者接收數(shù)據(jù),不能做任何可能發(fā)生睡眠的操作,而且不能調(diào)用schedule()函數(shù)。
為了簡單起見,而且考慮到按鍵操作的時間比較長,在本實例中的中斷處理函數(shù)button_irq()里,通過調(diào)用睡眠函數(shù)來消除毛刺信號。讀者可以根據(jù)以上介紹的對中斷處理函數(shù)的要求改進該部分代碼。
按鍵掃描函數(shù)如下所示。首先根據(jù)中斷號確定操作按鍵所在行的位置,然后采用逐列掃描法終確定操作按鍵所在的位置。
/*
** 進入中斷后, 掃描銨鍵碼
** 返回: 按鍵碼(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)
{ /* 根據(jù)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))
{ /* 觀察對應(yīng)的中斷線的輸入端口值 */
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函數(shù)接口的實現(xiàn)。首先在按鍵緩沖中刪除已經(jīng)過時的按鍵操作信息,接下來,從按鍵緩沖中讀取一條信息(按鍵ID)并傳遞給用戶層。
/* 從緩沖刪除過時數(shù)據(jù)(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;
}
以上介紹了按鍵驅(qū)動程序中的主要內(nèi)容。