沒說程式之前先一張,這個圖方便理解input子系統下的觸控螢幕的實現。
關於input子系統的實現在前幾篇部落格中已經實現了input子系統的按鍵驅動。
下面開始說觸控螢幕的驅動程式。
在說之前先結合上面的圖說一下幾個主要的函數的調用情況。
個中斷函數的調用次序:
先是stylus_updown TC中斷處理函數,
如果要是壓下則調用touch_timer_fire來啟動ADC轉換,
在ADC轉換結束之後調用stylus_action。
程式中前面定義的宏和變數:
/* For ts.dev.id.version */#define S3C2410TSVERSION0x0101/*定義一個WAIT4INT宏,該宏將對ADC觸控螢幕控制寄存器進行操作 S3C2410_ADCTSC_YM_SEN這些宏都定義在regs-adc.h中*/ #define WAIT4INT(x) (((x)<<8) | \ S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \ S3C2410_ADCTSC_XY_PST(3))/*定義一個AUTOPST宏,將ADC觸控螢幕控制寄存器設定成自動轉換模式*/ #define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \ S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))/*裝置名稱*/static char *s3c2410ts_name = "s3c2410 TouchScreen";/*定義一個輸入裝置來表示我們的觸控螢幕裝置*/ staticstruct input_dev *dev;/*用於記錄轉換後的X座標值和Y座標值*/ staticlong xp;staticlong yp;/*用於計數對觸控螢幕壓下或抬起時類比輸入轉換的次數*/ staticint count;/*定義一個外部的訊號量ADC_LOCK,因為ADC_LOCK在ADC驅動程式中已申明 這樣就能保證ADC資源在ADC驅動和觸控螢幕驅動中進行互斥訪問*/ extern struct semaphore ADC_LOCK;/*做為一個標籤,只有對觸控螢幕操作後才對X和Y座標進行轉換*/ static int OwnADC = 0;static void __iomem *base_addr;
老規矩:從init走你
static int __init s3c2410ts_init(void){struct input_dev *input_dev;/*從平台時鐘隊列中擷取ADC的時鐘,這裡為什麼要取得這個時鐘,因為ADC的轉換頻率跟時鐘有關。 系統的一些時鐘定義在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/adc_clock = clk_get(NULL, "adc");/*普通的錯誤處理*/if (!adc_clock) {printk(KERN_ERR "failed to get adc clock source\n");return -ENOENT;} /*時鐘擷取後要使能後才可以使用,clk_enable定義在arch/arm/plat-s3c/clock.c中*/ clk_enable(adc_clock);/* 將ADC的IO連接埠佔用的這段IO空間映射到記憶體的虛擬位址,ioremap定義在io.h中。 注意:IO空間要映射後才能使用,以後對虛擬位址的操作就是對IO空間的操作, S3C2410_PA_ADC是ADC控制器的基地址,定義在mach-s3c2410/include/mach/map.h中, 0x20是虛擬位址長度大小 */base_addr=ioremap(S3C2410_PA_ADC,0x20);//普通的錯誤驗證。if (base_addr == NULL) {printk(KERN_ERR "Failed to remap register block\n");return -ENOMEM;}/* Configure GPIOs *//*初始化ADC控制寄存器和ADC觸控螢幕控制寄存器*/s3c2410_ts_connect();//不懂這是做什麼。iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\ base_addr+S3C2410_ADCCON);iowrite32(0xffff, base_addr+S3C2410_ADCDLY);iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);/* Initialise input stuff *//*給輸入裝置申請空間,input_allocate_device定義在input.h中*/ input_dev = input_allocate_device();if (!input_dev) {printk(KERN_ERR "Unable to allocate the input device !!\n");return -ENOMEM;}/*初始化input子系統的dev結構體*/dev = input_dev;/*下面初始化輸入裝置,即給輸入裝置結構體input_dev的成員設定值。 evbit欄位用於描述支援的事件,這裡支援同步事件、按鍵事件、絕對座標事件, BIT宏實際就是對1進行位操作,定義在linux/bitops.h中*/ dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);/*keybit欄位用於描述按鍵的類型,在input.h中定義了很多,這裡用BTN_TOUCH類型來表示觸控螢幕的點擊*/ dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);/*對於觸控螢幕來說,使用的是絕對座標系統。這裡設定該座標系統中X和Y座標的最小值和最大值(0-1023範圍) ABS_X和ABS_Y就表示X座標和Y座標,ABS_PRESSURE就表示觸控螢幕是按下還是抬起狀態*/ input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);//ABS_PRESSURE表示觸控螢幕是按下還是抬起狀態。input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);/*以下是設定觸控螢幕輸入裝置的身份資訊,直接在這裡寫死。 這些資訊可以在驅動掛載後在/proc/bus/input/devices中查看到*/ dev->name = s3c2410ts_name;dev->id.bustype = BUS_RS232;dev->id.vendor = 0xDEAD;dev->id.product = 0xBEEF;dev->id.version = S3C2410TSVERSION;/* Get irqs *//*申請ADC中斷,AD轉換完成後觸發。這裡使用共用中斷IRQF_SHARED是因為該中斷號在ADC驅動中也使用了, 最後一個參數1是隨便給的一個值,因為如果不給值設為NULL的話,中斷就申請不成功*/ if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,"s3c2410_action", 1)) {printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !\n");iounmap(base_addr);return -EIO;}/*申請觸控螢幕中斷,對觸控螢幕按下或提筆時觸發*/if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,"s3c2410_action", dev)) {printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");iounmap(base_addr);return -EIO;}printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name);/* All went ok, so register to the input system *//*!!!!把觸控螢幕裝置的描述結構體dev註冊進input子系統中*/input_register_device(dev);return 0;}
這段函數的主要功能是:啟用ADC所需要的時鐘、映射IO口、初始化寄存器、申請中斷、初始化輸入裝置、將輸入裝置註冊到輸入子系統。同時注意這句話:/*!!!!把觸控螢幕裝置的描述結構體dev註冊進input子系統中*/input_register_device(dev);很協助真正的理解。還有別忘了根本:/*初始化input子系統的dev結構體*/。同時,/*從平台時鐘隊列中擷取ADC的時鐘,這裡為什麼要取得這個時鐘,因為ADC的轉換頻率跟時鐘有關。
系統的一些時鐘定義在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/adc_clock = clk_get(NULL, "adc");不跑裸機好多年,時鐘感覺距離好遠!
exit退出函數:
static void __exit s3c2410ts_exit(void){/*屏蔽和釋放中斷*/disable_irq(IRQ_ADC);disable_irq(IRQ_TC);free_irq(IRQ_TC,dev);free_irq(IRQ_ADC,dev);/*屏蔽並銷毀時鐘*/if (adc_clock) {clk_disable(adc_clock);clk_put(adc_clock);adc_clock = NULL;}//釋放input裝置。input_unregister_device(dev);/*釋放虛擬位址映射空間*/iounmap(base_addr);}
關於先釋放input裝置,還是先釋放地址我覺得還是來源程式對。
下面開始說幾個中斷函數:
/*TC中斷的處理函數*/static irqreturn_t stylus_updown(int irq, void *dev_id){/*用於記錄這一次AD轉換後的值*/ unsigned long data0;unsigned long data1;/*用於記錄觸控螢幕操作狀態是按下還是抬起*/int updown;if (down_trylock(&ADC_LOCK) == 0) {OwnADC = 1;/*讀的是狀態*/ data0 = ioread32(base_addr+S3C2410_ADCDAT0);data1 = ioread32(base_addr+S3C2410_ADCDAT1);/*記錄這一次對觸控螢幕是壓下還是抬起,該狀態儲存在資料寄存器的第15位,所以與上S3C2410_ADCDAT0_UPDOWN*/ updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));if (updown) {//如果要是壓下則調用touch_timer_fire來啟動ADC轉換。touch_timer_fire(0);} else {/*如果是抬起狀態,就結束了這一次的操作,所以就釋放ADC資源的佔有*/ OwnADC = 0;up(&ADC_LOCK);}}return IRQ_HANDLED;}
關鍵點在這裡:if (updown) {//如果要是壓下則調用touch_timer_fire來啟動ADC轉換。touch_timer_fire(0);
touch_timer_fire函數:
//AD轉換函式static void touch_timer_fire(unsigned long data){ /*用於記錄這一次AD轉換後的值*/ unsigned long data0; unsigned long data1;/*用於記錄觸控螢幕操作狀態是按下還是抬起*/int updown;//在未進行判定之前先讀取一次狀態。 data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1);/*判斷這一次對觸控螢幕是壓下還是抬起,該狀態儲存在資料寄存器的第15位,所以與上S3C2410_ADCDAT0_UPDOWN*/ updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));//開始判斷觸控螢幕的狀態。 if (updown) {/*如果狀態是按下,並且ADC已經轉換了就報告事件和資料*/ if (count != 0) {long tmp; tmp = xp;xp = yp;yp = tmp;/*取4次平均值*/ xp >>= 2; yp >>= 2;/*上報座標報告X,Y的絕對座標值*/ input_report_abs(dev, ABS_X, xp); input_report_abs(dev, ABS_Y, yp);/*報告觸控螢幕的狀態,1表明觸控螢幕被按下*/ input_report_key(dev, BTN_TOUCH, 1);/*報告按鍵事件,索引值為1(代表觸控螢幕對應的按鍵被按下)*/ input_report_abs(dev, ABS_PRESSURE, 1);/*結束報告*/ input_sync(dev); }//歸零 xp = 0; yp = 0; count = 0;/* 啟動AD 轉換*///設定為自動轉換模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);//啟動AD轉換 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { count = 0;/*報告按鍵事件,索引值為0(代表觸控螢幕對應的按鍵被釋放)*/ input_report_key(dev, BTN_TOUCH, 0);/*報告觸控螢幕的狀態,0表明觸控螢幕沒被按下*/ input_report_abs(dev, ABS_PRESSURE, 0);//結束報告 input_sync(dev);/*將觸控螢幕重新設定為等待中斷狀態*/ iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);/*如果觸控螢幕抬起,就意味著這一次的操作結束,所以就釋放ADC資源的佔有*/ if (OwnADC) {OwnADC = 0;up(&ADC_LOCK);} }}
/* 啟動AD 轉換*///設定為自動轉換模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);//啟動AD轉換 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
這裡先留一個疑問。我們來看最後一個中斷函數:
/*ADC中斷服務程式,AD轉換完成後觸發執行*/
static irqreturn_t stylus_action(int irq, void *dev_id){ unsigned long data0; unsigned long data1; if (OwnADC) { /*讀取這一次AD轉換後的值,注意這次主要讀的是座標*/ data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); /*記錄這一次通過AD轉換後的X座標值和Y座標值,根據資料手冊可知,X和Y座標轉換數值 分別儲存在資料寄存器0和1的第0-9位,所以這裡與上S3C2410_ADCDAT0_XPDATA_MASK就是取0-9位的值*/ xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; /*計數這一次AD轉換的次數*/ count++; //這裡說如果轉換次數小於4,則重新啟動轉換。為什嗎? if (count < (1<<2)) { /* 再一次啟動AD轉換*/ iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { /*這裡不懂*/ mod_timer(&touch_timer, jiffies+1); iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC); } } return IRQ_HANDLED;}
到這裡整個的input子系統實現觸控螢幕的主體部分也就結束了。還有好多細節部分和要注意的部分,由於一個腦殘的同學讓我給他百度論文,思路斷了,所以不寫了。不過在源碼裡都有體現了。