mini2440的觸控式螢幕驅動----使用input子系統實現
沒說程式之前先上圖一張,這個圖方便理解input子系統下的觸控式螢幕的實現。
關於input子系統的實現在前幾篇部落格中已經實現了input子系統的按鍵驅動。
下面開始說觸控式螢幕的驅動程式。
在說之前先結合上面的圖說一下幾個主要的函式的呼叫情況。
箇中斷函式的呼叫次序:
先是stylus_updown TC中斷處理函式,
如果要是壓下則呼叫touch_timer_fire來啟動ADC轉換,
在ADC轉換結束之後呼叫stylus_action。
程式中前面定義的巨集和變數:
/* For ts.dev.id.version */ #define S3C2410TSVERSION 0x0101 /*定義一個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"; /*定義一個輸入裝置來表示我們的觸控式螢幕裝置*/ static struct input_dev *dev; /*用於記錄轉換後的X座標值和Y座標值*/ static long xp; static long yp; /*用於計數對觸控式螢幕壓下或擡起時模擬輸入轉換的次數*/ static int 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子系統實現觸控式螢幕的主體部分也就結束了。還有好多細節部分和要注意的部分,由於一個腦殘的同學讓我給他百度論文,思路斷了,所以不寫了。不過在原始碼裡都有體現了。