觸控螢幕的驅動程式在不是使用input子系統的實現過程中,其實和普通的字元裝置驅動沒有太大的差別,只要按照觸控螢幕那幾個步驟正常進行就可以。
①s3c2410_ts_init函數,這是整個程式的入口處。實現了各類初始化的作用。
/*裝置初始化函數*/static int __init s3c2410_ts_init(void){int ret;tsEvent = tsEvent_dummy;/* 註冊字元裝置第一個參數為0表示自動分配*/ret = register_chrdev(0, DEVICE_NAME, &s3c2410_fops);//普通的註冊失敗判斷。if (ret < 0) { printk(DEVICE_NAME " can't get major number\n"); return ret;}//tsMajor是一個全域變數,這裡被賦裝置註冊的傳回值tsMajor = ret;/* set gpio to XP, YM, YP and YM *///操作相應的GPIO寄存器,這些函數是核心留下來的介面。set_gpio_ctrl(GPIO_YPON); set_gpio_ctrl(GPIO_YMON);set_gpio_ctrl(GPIO_XPON);set_gpio_ctrl(GPIO_XMON);/*註冊中斷分別註冊ADC和TC中斷。DEVICE_NAME在開始處有宏定義。*/ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, SA_INTERRUPT, DEVICE_NAME, s3c2410_isr_adc);if (ret) goto adc_failed;ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT, DEVICE_NAME, s3c2410_isr_tc);if (ret) goto tc_failed;/*等待觸控螢幕被按下的中斷這個函數的實現是使用宏來實現的。這就是一個等待函數,讓我想起了當年的祥哥。*/wait_down_int();//建立裝置檔案。#ifdef CONFIG_DEVFS_FSdevfs_ts_dir = devfs_mk_dir(NULL, "touchscreen", NULL);devfs_tsraw = devfs_register(devfs_ts_dir, "0raw", DEVFS_FL_DEFAULT,tsMajor, TSRAW_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,&s3c2410_fops, NULL);#endifADCDLY = 0xFFFF;printk(DEVICE_NAME " initialized\n");return 0;//兩個中斷註冊失敗的跳轉點。 tc_failed:free_irq(IRQ_ADC_DONE, s3c2410_isr_adc); adc_failed:return ret;}
在這個函數中出現了以前沒有的條件編譯,所以自己注意一下,不過這個條件編譯的主要作用就是建立裝置檔案。同時還有兩個中斷註冊失敗的goto。為了自己看以後的程式不糊塗,先記下這句話://tsMajor是一個全域變數,這裡被賦裝置註冊的傳回值tsMajor = ret;
②s3c2410_ts_exit,這個函數是整個程式的退出函數,主要實現了裝置的登出和中斷的釋放。
static void __exit s3c2410_ts_exit(void){#ifdef CONFIG_DEVFS_FSdevfs_unregister(devfs_tsraw);devfs_unregister(devfs_ts_dir);#endifunregister_chrdev(tsMajor, DEVICE_NAME);free_irq(IRQ_ADC_DONE, s3c2410_isr_adc);free_irq(IRQ_TC, s3c2410_isr_tc);}
③一般分析字元裝置驅動程式的時候都是先看init函數,然後再去找file_operation。但是觸控螢幕的驅動程式的實現肯定有會有中斷,所以我們先來看一下中斷函數。
先看第一個中斷函數(在init函數中已經註冊完畢)
/* 當按鍵按下時首先產生的中斷 */static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg){//利用自旋鎖給中斷上鎖。spin_lock_irq(&(tsdev.lock));//這裡PEN_UP被宏定義為0,penStatus是一個結構體成員。if (tsdev.penStatus == PEN_UP) /*如果是按下中斷*/{ start_ts_adc(); /* 開始AD轉化 */} else { tsdev.penStatus = PEN_UP; /* 如果是彈起中斷*/ DPRINTK("PEN UP: x: %08d, y: %08d\n", x, y); wait_down_int(); /*等待按下中斷*/ tsEvent(); /* 調用後續處理函數 */}spin_unlock_irq(&(tsdev.lock));}
再說這個中斷函數之前,首先要說一下觸摸品主要的工作流程:
觸控螢幕的工作流程
①選擇X/Y的擷取模式。
②設定觸控螢幕等待中斷的狀態。
③如果中斷髮生啟動相應的數模轉換。
④當數模轉換之後,可以擷取X/Y座標值,返回到等待中斷的狀態(②之後③之前)。
同時還要說一下觸控螢幕的兩個中斷:
TC中斷:手按下拿起的時候
ADC中斷:發生在第四步。
上面的中斷處理函數其實就是TC中斷,從名字也可以看得出來,也就是觸控螢幕發生的第一個中斷。
當中斷髮生的時候首先是自旋鎖上鎖。然後給出判定並分配了兩種情況,假如確實是按下了,那麼就開始進行AD轉換,AD轉換的函數在另一個函數中實現,這裡只是調用。如果是彈起中斷,那麼就繼續等待,並調用後續函數(這裡的後續函數一定要仔細的看)。
上面的TC中斷函數中一共調用了兩個函數,分別是start_ts_adc和tsEvent函數。下面分別介紹這兩個函數。
start_ts_adc函數:
//通過響應的寄存器的設定來啟動AD轉換。//mode_x_axis,start_adc_x都是宏定義的函數。static inline void start_ts_adc(void){ adc_state = 0; mode_x_axis(); start_adc_x();}
這個函數主要是通過操作寄存器來實現的,具體的定義在整個程式開始的宏定義中有表現。
tsEvent函數:
static void tsEvent_raw(void){if (tsdev.penStatus == PEN_DOWN) { /* 儲存按下時的座標 */BUF_HEAD.x = x;BUF_HEAD.y = y;BUF_HEAD.pressure = PEN_DOWN;#ifdef HOOK_FOR_DRAG /*當筆在觸控螢幕上長期滑動的時候,如果不進行特殊處理不會產生點的軌跡。*/ts_timer.expires = jiffies + TS_TIMER_DELAY;add_timer(&ts_timer); /* 對長時間按下鍵的處理 這個函數的具體實現在本函數中實現。*/#endif} else {#ifdef HOOK_FOR_DRAG del_timer(&ts_timer);#endifBUF_HEAD.x = 0;BUF_HEAD.y = 0;BUF_HEAD.pressure = PEN_UP; /* 儲存彈起時的座標 */}tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF);//這裡的喚醒進程和read函數中的sleep on對應。wake_up_interruptible(&(tsdev.wq)); /* 喚醒進程 */}
在等待按下中斷函數後再調用後續處理函數,這裡是一個迴圈的調用。在後續處理函數要注意:
#ifdef HOOK_FOR_DRAG
/*當筆在觸控螢幕上長期滑動的時候,如果不進行特殊處理不會產生點的軌跡。*/
ts_timer.expires = jiffies + TS_TIMER_DELAY;
add_timer(&ts_timer);
/*
對長時間按下鍵的處理
這個函數的具體實現在本函數中實現。
*/
#endif
這個函數是針對長時間按鍵的情況,這裡ts_timer實現了檢測頻率的體現,同時,ts_timer中實現了如下函數:
static void ts_timer_handler(unsigned long data){spin_lock_irq(&(tsdev.lock));if (tsdev.penStatus == PEN_DOWN) {start_ts_adc();}//給這個程式解鎖。spin_unlock_irq(&(tsdev.lock));}
這樣長時間按鍵的情況就不是檢測連個中斷了,而且檢測的頻率還可以自己進行設定。
④當TC中斷函數執行完成以後自然而然的根據IRQ中斷號就要執行另一個中斷ADC中斷了。
static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg){spin_lock_irq(&(tsdev.lock));if (tsdev.penStatus == PEN_UP) s3c2410_get_XY();#ifdef HOOK_FOR_DRAGelse s3c2410_get_XY();#endifspin_unlock_irq(&(tsdev.lock));}
這一部分是ADC實現的代碼。其中這個函數主要調用了 s3c2410_get_XY這個函數,包括判定之後的執行也都是在s3c2410_get_XY這個函數再進行進一步判定之後進行的。
下面列出s3c2410_get_XY這個函數的主要代碼:
static inline void s3c2410_get_XY(void){//adc_state這個變數的作用是區分現在到底在擷取哪一個座標。if (adc_state == 0) { /* 轉換x */adc_state = 1;disable_ts_adc();y = (ADCDAT0 & 0x3ff); /* 擷取x座標 */mode_y_axis(); start_adc_y(); /*啟動y座標轉化*/} else if (adc_state == 1) { /*轉換y*/adc_state = 0;disable_ts_adc();x = (ADCDAT1 & 0x3ff); /* 擷取y座標 */tsdev.penStatus = PEN_DOWN; /* 改變屏狀態 */DPRINTK("PEN DOWN: x: %08d, y: %08d\n", x, y);wait_up_int(); /* 等待彈起中斷 */tsEvent();}}
在上述的幾個中斷程式中涉及到了部分流程式控制制的迴圈調用,所以大家務必仔細閱讀。
⑤中斷講解完畢以後開始說一下file_operations。
多麼的親切!
static struct file_operations s3c2410_fops = {
owner: THIS_MODULE,
open: s3c2410_ts_open,
read: s3c2410_ts_read,
release: s3c2410_ts_release,
poll: s3c2410_ts_poll,
};
說到了file_operations了,那麼就把代碼分別粘貼出來了ha~
s3c2410_ts_open:
static int s3c2410_ts_open(struct inode *inode, struct file *filp){tsdev.head = tsdev.tail = 0;tsdev.penStatus = PEN_UP;#ifdef HOOK_FOR_DRAG init_timer(&ts_timer);ts_timer.function = ts_timer_handler;#endiftsEvent = tsEvent_raw;init_waitqueue_head(&(tsdev.wq));MOD_INC_USE_COUNT;return 0;}
說到這裡還有一個問題沒有弄懂,那就是tsdev.head = tsdev.tail = 0;不懂為什麼頭和尾都要置零。其他的部分也就不做過多的解釋了。
s3c2410_ts_read函數:
static ssize_t s3c2410_ts_read(struct file *filp, char *buffer, size_t count, loff_t *ppos){TS_RET ts_ret;retry: if (tsdev.head != tsdev.tail) {int count;count = tsRead(&ts_ret);if (count) copy_to_user(buffer, (char *)&ts_ret, count);return count;} else {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;interruptible_sleep_on(&(tsdev.wq));//這個判斷的作用是:判斷是否是訊號把它喚醒了if (signal_pending(current))return -ERESTARTSYS;goto retry;}return sizeof(TS_RET);}
注意一下這裡://這個判斷的作用是:判斷是否是訊號把它喚醒了if (signal_pending(current))。
release函數:
static int s3c2410_ts_release(struct inode *inode, struct file *filp){#ifdef HOOK_FOR_DRAGdel_timer(&ts_timer);#endifMOD_DEC_USE_COUNT;return 0;}
大體部分也就上面這些,我是在看過了input子系統實現了觸控螢幕驅動之後寫的這個,突然感覺還是input子系統實現起來比這個簡單了很多。