1.s3c2440觸摸屏工作模式
1.1進入分離的X/Y方向轉換模式,會自動先轉換x座標產生中斷,再轉換y座標產生中斷,並將轉換結果分別存入ADCDAT0和ADCDAT1兩個寄存器中
1.2進入自動X/Y方向轉換模式,順序轉換xy座標並將轉換結果分別存入ADCDAT0和ADCDAT1兩個寄存器中,然後產生一箇中斷表明轉換結束。
1.3等待中斷模式(設置寄存器ADCTSC=0xd3即可)
1.4待機模式
2.觸摸屏使用過程:
2.1初始化設置觸摸屏爲等待中斷模式(等待觸摸筆落下)。按下產生中斷(IRQ_TC)
2.2在觸摸屏中斷處理函數裏啓動ADC轉換xy座標值
2.3ADC轉換結束產生ADC中斷
2.4在ADC中斷處理函數裏上報事件,啓動定時器
2.5如果定時時間到,再次啓動ADC轉換xy座標,處理長按滑動的情況
2.6鬆開
優化措施:
1.設置ADCDLY爲最大值,讓電壓穩定之後再發出IRQ中斷
2.如果ADC完成時判斷觸摸筆是否鬆開,如果是則丟棄本次結果
3.多次測量求平均值
4.使用定時器處理長按滑動的情況
下圖是電壓測量的原理。圖中XP、XM、YP、YM接地接3.3v或不接就可以通過設置ADC的寄存器某些位來實現。
3.回顧輸入子系統
我們看到輸入子系統體系結構大概包括三部分: drivers、input core 、handlers,其中 input core、handlers是由內核來實現的,程序員要做的就是利用 input core提供的接口函數來實現drivers。下面我們來具體說一下這三層的功能:
驅動層(drivers):將底層的硬件輸入轉化爲統一事件形式,向輸入核心層(input core)彙報。
輸入核心層(input core):爲驅動層提供輸入設備註冊與操作接口,如 input_register_device;通知事件處理層對事件進行處理;在 /proc下產生相應的設備信息;
事件處理層(handlers):主要作用是和用戶空間交互,我們知道 linux在用戶空間將所有設備當成文件來處理,在一般的驅動程序中都有提供 fops接口,以及在/dev下生成相應的設備文件節點,而在輸入子系統中,這些工作由事件處理層來完成。
上圖展示了輸入子系統的操作。此子系統包括一前一後運行的兩類驅動:輸入事件(event)驅動和輸入設備(device)驅動。在子系統中,事件驅動是標準的,對所有的輸入類都是可以用的 (接口) ,所以你更可能的是實現輸入設備驅動,對各硬件寄存器的操作和提交的輸入事件。輸入事件(event)驅動負責和應用程序的接口,而輸入設備(device)驅動負責和底層輸入設備的通信。
問:怎麼寫符合輸入子系統框架的驅動程序?
答:
3.1 分配一個input_dev結構體
input_allocate_device();
3.2 設置
設置支持什麼類型事件
設置該類事件中的哪些事件
3.3 註冊
input_register_device()
3.4 硬件相關的代碼,比如在中斷服務程序裏上報事件
4. s3c2410_ts.c觸摸屏驅動分析
看驅動先從入口函數開始。
static struct clk *adc_clock; static int __init s3c2410ts_init(void) { struct input_dev *input_dev; /*獲得ADC時鐘*/ adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_enable(adc_clock);/*使能時鐘*/ /*將ADC的IO端口占用的這段IO空間映射到 *內存的虛擬地址,S3C2410_PA_ADC(0x58000000)是 *ADC控制器的基地址,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 */ s3c2410_ts_connect(); /*A/D 轉換器預分頻器使能預分頻值 255+1=256*/ iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\ base_addr+S3C2410_ADCCON); /*ADC啓動延時寄存器,延時值*/ iowrite32(0xffff, base_addr+S3C2410_ADCDLY); /*bit[8]:0 檢測筆尖落下中斷信號 *bit[7]: 1 = YM輸出驅動器使能 *bit[6]: 1 = YP輸出驅動器使能 *bit[5]: 0 = XM輸出驅動器禁止 *bit[4]: 1 = XP輸出驅動器使能 *bit[1:0]:3 = 等待中斷模式*/ iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); /* Initialise input stuff */ input_dev = input_allocate_device();/*申請*/ if (!input_dev) { printk(KERN_ERR "Unable to allocate the input device !!\n"); return -ENOMEM; } dev = input_dev; /*即給輸入設備結構體input_dev的成員設置值。 evbit字段用於描述支持的事件,這裏支持同步事件、按鍵事件、絕對座標事件, BIT宏實際就是對1進行位操作*/ dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); /*支持按鍵類中的觸摸屏點擊*/ dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);/*X絕對位移事件*/ input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);/*壓力事件*/ dev->name = s3c2410ts_name;/*設備名稱*/ /*id結構中這幾個成員是輸入子系統中 *用於判斷處理器是否能支持該設備, *event處理器能支持所有設備,所以這裏 *不設置也無所謂(默認爲0)*/ dev->id.bustype = BUS_RS232;/*總線類型*/ dev->id.vendor = 0xDEAD;/*經銷商ID號*/ dev->id.product = 0xBEEF; /*產品ID號*/ dev->id.version = S3C2410TSVERSION; /*版本ID號*/ /* Get irqs */ if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, "s3c2410_action", dev)) { 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 */ input_register_device(dev);/*註冊*/ return 0; } 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_unregister_device(dev); iounmap(base_addr); }
在驅動加載部分,主要做的事情是:啓用ADC所需要的時鐘、映射IO口、初始化寄存器、申請中斷、初始化輸入設備、將輸入設備註冊到輸入子系統。設置觸摸屏爲等待筆尖落下的模式。
當筆尖落下時產生觸摸屏中斷。
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
int updown;/*觸摸筆的狀態是按下還是擡起*/
/*ADC資源可以獲取,即上鎖*/
if (down_trylock(&ADC_LOCK) == 0) {
OwnADC = 1;
/*base_addr是ADC映射到內存空間的虛擬基地址
*加上S3C2410_ADCDAT0就是寄存器ADCDAT0的虛擬
*地址,下面兩句的作用讀取寄存器的狀態*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*判斷觸摸筆的狀態,只要讀取寄存器ADCDAT0/1
*的bit[15]:1表示擡起,0爲按下 */
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) {/*爲1表示觸摸筆按下狀態*/
touch_timer_fire(0);/*調用touch_timer_fire函數來啓動ADC轉換*/
} else {
/*如果是擡起狀態,就結束了這一
*次的操作,所以就釋放ADC資源的佔有*/
OwnADC = 0;
up(&ADC_LOCK);
}
}
return IRQ_HANDLED;
}
進入中斷處理函數先上鎖獲得ADC資源使用權,讀取寄存器的值判斷此時筆尖是否真的處於落下狀態。如果是就調用定時器處理函數啓動ADC進行XY座標的轉換。否則就釋放ADC資源touch_timer_fire定時器處理函數
static void touch_timer_fire(unsigned long data)
{
unsigned long data0;
unsigned long data1;
int updown;
/*讀取AD轉換後的狀態值*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*判斷觸摸筆此時的狀態*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) {
if (count != 0) {
long tmp; tmp = xp;
xp = yp;
yp = tmp;
xp >>= 2;
yp >>= 2;
input_report_abs(dev, ABS_X, xp);/*上報事件*/
input_report_abs(dev, ABS_Y, yp);
input_report_key(dev, BTN_TOUCH, 1);
input_report_abs(dev, ABS_PRESSURE, 1);
input_sync(dev);
}
xp = 0;
yp = 0;
count = 0;
/*設置觸摸屏的模式爲自動轉換模式*/
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
/*啓動ADC轉換*/
iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else { /*否則是擡起狀態*/
count = 0;
input_report_key(dev, BTN_TOUCH, 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);
}
}
}
讀取寄存器的值判斷此時筆尖是否處於落下狀態且ADC轉換已經完成。如果是就向內核筆尖落下和xy絕對位移值上報事件;如果處於落下狀態但ADC未啓動則啓動ADC轉換;如果筆尖處於擡起狀態就上報筆記擡起事件。
當ADC轉換完成後產生ADC中斷,進入ADC中斷服務程序
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);
xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;/*10AD,與上0x3FF取出寄存器低10位數據*/
yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
count++;/*AD轉換計數加1*/
/*計數小於4,再次啓動ADC轉換*/
if (count < (1<<2)) {
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 {
/*否則,啓動1個時間滴答的定時器,
*這時就會去執行定時器服務程序
*上報事件和數據
*/
mod_timer(&touch_timer, jiffies+1);
/*設置爲等待筆尖擡起*/
iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
}
}
return IRQ_HANDLED;
}
完成4次ADC轉換結果的讀取後,啓動定時器並關閉ADC的轉換。定時時間到執行定時器處理函數
總結轉換這個的過程
1.入口設置觸摸屏爲等待中斷模式,等待觸摸筆被按下
2.如果觸摸筆按下則進入觸摸屏中斷處理函數stylus_updown。獲取獲取ADC_LOCK後判斷觸摸屏狀態爲按下,則調用touch_timer_fire啓動ADC轉換。
3.當ADC轉換啓動後,觸發ADC中斷即進入adc_irq,如果這一次轉換的次數小於4,則重新啓動ADC進行轉換,如果4次完畢後,啓動1個時間滴答的定時器,停止ADC轉換,設置觸摸屏爲等待筆尖擡起。
4.如果1個時間滴答到來則進入定時器服務程序touch_timer_fire,判斷觸摸屏仍然處於按下狀態則上報事件和轉換的數據。然後將原來讀取的數據清楚並重啓ADC轉換,重複第(2)步(這一步處理筆尖長按和滑動的情況)
5.如果觸摸擡起了,則上報釋放事件,並將觸摸屏重新設置爲等待中斷狀態。
補充:
id結構中這幾個成員是輸入子系統中 用於判斷處理器是否能支持該設備, event處理器能支持所有設備,所以這裏
不設置也無所謂(默認爲0) 。下面是有設置和沒設置的實驗截圖。cat proc/bus/input/devices 可以看到相關的設置
不設置也無所謂(默認爲0) 。下面是有設置和沒設置的實驗截圖。cat proc/bus/input/devices 可以看到相關的設置
dev->name = s3c2410ts_name;/*設備名稱*/
dev->id.bustype = BUS_RS232;/*總線類型*/
dev->id.vendor = 0xDEAD;/*經銷商ID號*/
dev->id.product = 0xBEEF; /*產品ID號*/
dev->id.version = S3C2410TSVERSION; /*版本ID號*/
有設置時與源碼一一對應
問:爲什麼可以支持所有輸入設備呢?
要回答這個問題,我們得先回去複習一下輸入子系統中事件處理器與設備是怎樣匹配的。先來看看匹配函數
static const struct input_device_id *input_match_device(const struct input_device_id *id,
struct input_dev *dev)
{
int i;
for (; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
return id;
}
return NULL;
}
可以看到如果時間處理器中設置flag的,就會逐一比較輸入設備中設置的id結構成員bustype、vendor、product、
version值是否跟時間處理器input_device_id結構中對應成員的值相同。只要其中有一個不同就比較下一個處理器的
接下來我們再來看看evdev_handler處理器能支持什麼什麼設備。在drivers/input/evdev.c源碼中有這麼一段,對照上面的匹配函數我們很容易知道evdev_handler處理器能支持所有輸入設備。因此輸入設備中我們不設置bustype、vendor、product、version也行。
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
使用tslib庫進行測試,參考