http://blog.csdn.net/hanxuefan/article/details/7370028
1.3.1 在內核中添加觸摸屏驅動程序
以下介紹爲網上摘取的一部分內容:
1)、Linux輸入子系統(Input Subsystem):
在Linux中,輸入子系統是由輸入子系統設備驅動層、輸入子系統核心層(Input Core)和輸入子系統事件處理層(Event Handler)組成。其中設備驅動層提供對硬件各寄存器的讀寫訪問和將底層硬件對用戶輸入訪問的響應轉換爲標準的輸入事件,再通過核心層提交給事件處理層;而核心層對下提供了設備驅動層的編程接口,對上又提供了事件處理層的編程接口;而事件處理層就爲我們用戶空間的應用程序提供了統一訪問設備的接口和驅動層提交來的事件處理。所以這使得我們輸入設備的驅動部分不在用關心對設備文件的操作,而是要關心對各硬件寄存器的操作和提交的輸入事件。下面用圖形來描述一下這三者的關係吧!
另外,又找了另一幅圖來說明Linux輸入子系統的結構,可能更加形象容易理解。如下:
2)、輸入子系統設備驅動層實現原理:
在Linux中,Input設備用input_dev結構體描述,定義在input.h中。設備的驅動只需按照如下步驟就可實現了。
①、在驅動模塊加載函數中設置Input設備支持input子系統的哪些事件;
②、將Input設備註冊到input子系統中;
③、在Input設備發生輸入操作時(如:鍵盤被按下/擡起、觸摸屏被觸摸/擡起/移動、鼠標被移動/單擊/擡起時等),提交所發生的事件及對應的鍵值/座標等狀態。
Linux中輸入設備的事件類型有(這裏只列出了常用的一些,更多請看linux/input.h中):
1. EV_SYN 0x00 同步事件
2.
3. EV_KEY 0x01 按鍵事件
4.
5. EV_REL 0x02 相對座標(如:鼠標移動,報告的是相對最後一次位置的偏移)
6.
7. EV_ABS 0x03 絕對座標(如:觸摸屏和操作杆,報告的是絕對的座標位置)
8.
9. EV_MSC 0x04 其它
10.
11. EV_LED 0x11 LED
12.
13. EV_SND 0x12 聲音
14.
15. EV_REP 0x14 Repeat
16.
17. EV_FF 0x15 力反饋
18.
用於提交較常用的事件類型給輸入子系統的函數有:
1. void input_report_key(struct input_dev *dev, unsigned int code, int value);
2.
3. //提交按鍵事件的函數
4.
5. void input_report_rel(struct input_dev *dev, unsigned int code, int value);
6.
7. //提交相對座標事件的函數
8.
9. void input_report_abs(struct input_dev *dev, unsigned int code, int value);
10.
11. //提交絕對座標事件的函數
12.
注意,在提交輸入設備的事件後必須用下列方法使事件同步,讓它告知input系統,設備驅動已經發出了一個完整的報告:
1. void input_sync(struct input_dev *dev)
3)、觸摸屏驅動的實現步驟
S3c2440芯片內部觸摸屏接口與ADC接口是集成在一起的,硬件結構原理圖請看:S3C2440上ADC驅動實例開發講解中的圖,其中通道7(XP或AIN7)作爲觸摸屏接口的X座標輸入,通道5(YP或AIN5)作爲觸摸屏接口的Y座標輸入。在"S3C2440上ADC驅動實例開發講解"中,AD轉換的模擬信號是由開發板上的一個電位器產生並通過通道1(AIN0)輸入的,而這裏的模擬信號則是由點觸觸摸屏所產生的X座標和Y座標兩個模擬信號,並分別通過通道7和通道5輸入。S3c2440提供的觸摸屏接口有4種處理模式,分別是:正常轉換模式、單獨的X/Y位置轉換模式、自動X/Y位置轉換模式和等待中斷模式,對於在每種模式下工作的要求,請詳細查看數據手冊的描述。本驅動實例將採用自動X/Y位置轉換模式和等待中斷模式。
4)Linux-2.6.32.2 內核也沒有包含支持 S3C2440 的觸摸屏驅動,因此我們需要加入一個mini2440_touchscreen.c
將它放於linux-src/drivers/input/touchscreen目錄下
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <plat/regs-adc.h>
#include <asm/irq.h>
#include <asm/io.h>
/*用於保存從平臺時鐘列表中獲取的ADC時鐘*/
static struct clk *adc_clk;
/*定義了一個用來保存經過虛擬映射後的內存地址*/
static void __iomem *adc_base;
/*定義一個輸入設備來表示我們的觸摸屏設備*/
static struct input_dev *ts_dev;
/*設備名稱*/
#define DEVICE_NAME "my2440_TouchScreen"
/*定義一個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))
/*定義一個外部的信號量ADC_LOCK,因爲ADC_LOCK在ADC驅動程序中已申明
這樣就能保證ADC資源在ADC驅動和觸摸屏驅動中進行互斥訪問*/
extern struct semaphore ADC_LOCK;
/*做爲一個標籤,只有對觸摸屏操作後纔對X和Y座標進行轉換*/
static int OwnADC = 0; //在ADC驅動裏面也有 ADC資源的控制權
/*用於記錄轉換後的X座標值和Y座標值*/
static long xp;
static long yp;
/*用於計數對觸摸屏壓下或擡起時模擬輸入轉換的次數*/
static int count;
/*定義一個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 void touch_timer_fire(unsigned long data) ;
/*定義並初始化了一個定時器touch_timer,定時器服務程序爲touch_timer_fire*/
static struct timer_list touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0);
static void touch_timer_fire(unsigned long data)
{
/*用於記錄這一次AD轉換後的值*/
unsigned long data0;
unsigned long data1;
/*用於記錄觸摸屏操作狀態是按下還是擡起*/
int updown;
/*讀取這一次AD轉換後的值,注意這次主要讀的是狀態*/
data0 = readl(adc_base + S3C2410_ADCDAT0);
data1 = readl(adc_base + 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;
xp >>= 2;
yp >>= 2;
#ifdef CONFIG_TOUCHSCREEN_MY2440_DEBUG
/*觸摸屏調試信息,編譯內核時選上此項後,點擊觸摸屏會在終端上打印出座標信息*/
struct timeval tv;
do_gettimeofday(&tv);
printk(KERN_DEBUG "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, xp, yp);
#endif
/*報告X、Y的絕對座標值*/
input_report_abs(ts_dev, ABS_X, xp);
input_report_abs(ts_dev, ABS_Y, yp);
/*報告觸摸屏的狀態,1表明觸摸屏被按下*/
input_report_abs(ts_dev, ABS_PRESSURE, 1);
/*報告按鍵事件,鍵值爲1(代表觸摸屏對應的按鍵被按下)*/
input_report_key(ts_dev, BTN_TOUCH, 1);
/*等待接收方受到數據後回覆確認,用於同步*/
input_sync(ts_dev);
}
/*如果狀態是按下,並且ADC還沒有開始轉換就啓動ADC進行轉換*/
xp = 0;
yp = 0;
count = 0;
/*設置觸摸屏的模式爲自動轉換模式*/
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, adc_base + S3C2410_ADCTSC);
/*啓動ADC轉換*/
writel(readl(adc_base + S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START,
adc_base + S3C2410_ADCCON);
}
else
{
/*否則是擡起狀態*/
count = 0;
/*報告按鍵事件,鍵值爲0(代表觸摸屏對應的按鍵被釋放)*/
input_report_key(ts_dev, BTN_TOUCH, 0);
/*報告觸摸屏的狀態,0表明觸摸屏沒被按下*/
input_report_abs(ts_dev, ABS_PRESSURE, 0);
/*等待接收方受到數據後回覆確認,用於同步*/
input_sync(ts_dev);
/*將觸摸屏重新設置爲等待中斷狀態*/
writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC);
/*如果觸摸屏擡起,就意味着這一次的操作結束,所以就釋放ADC資源的佔有*/
if (OwnADC)
{
OwnADC = 0;
up(&ADC_LOCK);
}
}
}
/*觸摸屏中斷服務程序,對觸摸屏按下或提筆時觸發執行*/
static irqreturn_t tc_irq(int irq, void *dev_id)
{
/*用於記錄這一次AD轉換後的值*/
unsigned long data0;
unsigned long data1;
/*用於記錄觸摸屏操作狀態是按下還是擡起*/
int updown;
/*ADC資源可以獲取,即上鎖*/
if (down_trylock(&ADC_LOCK) == 0)
{
/*標識對觸摸屏進行了操作*/
OwnADC = 1;
/*讀取這一次AD轉換後的值,注意這次主要讀的是狀態*/
data0 = readl(adc_base + S3C2410_ADCDAT0);
data1 = readl(adc_base + 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;
}
/*ADC中斷服務程序,AD轉換完成後觸發執行*/
static irqreturn_t adc_irq(int irq, void *dev_id)
{
/*用於記錄這一次AD轉換後的值*/
unsigned long data0;
unsigned long data1;
if(OwnADC) //OwnADC初始值爲0,只有對觸摸屏操作後纔對X和Y座標進行轉換
{
/*讀取這一次AD轉換後的值,注意這次主要讀的是座標*/
data0 = readl(adc_base + S3C2410_ADCDAT0);
data1 = readl(adc_base + 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++;
if (count < (1<<2))
{
/*如果轉換的次數小於4,則重新啓動ADC轉換*/
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, adc_base + S3C2410_ADCTSC);
writel(readl(adc_base + S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START,
adc_base + S3C2410_ADCCON);
}
else
{
/*否則,啓動1個時間滴答的定時器,這是就會去執行定時器服務程序上報事件和數據*/
/*touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0); */
mod_timer(&touch_timer, jiffies + 1);
writel(WAIT4INT(1), adc_base + S3C2410_ADCTSC);
}
}
return IRQ_HANDLED;
}
/*初始化ADC控制寄存器和ADC觸摸屏控制寄存器*/
static void adc_initialize(void)
{
/*計算結果爲(二進制):111111111000000,再根據數據手冊得知
此處是將AD轉換預定標器值設爲255、AD轉換預定標器使能有效*/
writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF), adc_base + S3C2410_ADCCON);
/*對ADC開始延時寄存器進行設置,延時值爲0xffff*/
writel(0xffff, adc_base + S3C2410_ADCDLY);
/*WAIT4INT宏計算結果爲(二進制):11010011,再根據數據手冊得知
此處是將ADC觸摸屏控制寄存器設置成等待中斷模式*/
writel(WAIT4INT(0), adc_base + S3C2410_ADCTSC);
}
static int __init ts_init(void)
{
int ret;
//touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0);
/*從平臺時鐘隊列中獲取ADC的時鐘,這裏爲什麼要取得這個時鐘,因爲ADC的轉換頻率跟時鐘有關。
系統的一些時鐘定義在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
adc_clk = clk_get(NULL, "adc");
if(!adc_clk)
{
/*錯誤處理*/
printk(KERN_ERR "falied to find adc clock source\n");
return -ENOENT;
}
/*時鐘獲取後要使能後纔可以使用,clk_enable定義在arch/arm/plat-s3c/clock.c中*/
clk_enable(adc_clk);
/*將ADC的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中。
注意:IO空間要映射後才能使用,以後對虛擬地址的操作就是對IO空間的操作,
S3C2410_PA_ADC是ADC控制器的基地址,定義在mach-s3c2410/include/mach/map.h
中,0x20是虛擬地址長度大小*/
adc_base = ioremap(S3C2410_PA_ADC, 0x20);
if(adc_base == NULL)
{
/*錯誤處理*/
printk(KERN_ERR "failed to remap register block\n");
ret = -EINVAL;
goto err_noclk;
}
/*初始化ADC控制寄存器和ADC 觸摸屏控制寄存器*/
adc_initialize();
/*申請ADC中斷,AD轉換完成後觸發。這裏使用共享中斷IRQF_SHARED 是因爲該中斷號在ADC驅動
中也使用了,最後一個參數1是隨便給的一個值,因爲如果不給值設爲NULL的話,中斷就申請不成功*/
//因爲是共享中斷必須最後一個此參數不爲NULL
ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED | \
IRQF_SAMPLE_RANDOM, DEVICE_NAME, 1);
if(ret)
{
printk(KERN_ERR "IRQ%d error %d\n", IRQ_ADC, ret);
ret = -EINVAL;
goto err_nomap;
}
/*申請觸摸屏中斷,對觸摸屏按下或提筆時觸發*/
ret = request_irq(IRQ_TC, tc_irq, IRQF_SAMPLE_RANDOM, DEVICE_NAME, 1);
if(ret)
{
printk(KERN_ERR "IRQ%d error %d\n", IRQ_TC, ret);
ret = -EINVAL;
goto err_noirq;
}
/*給輸入設備申請空間,input_allocate_device定義在input.h中*/
ts_dev = input_allocate_device();
/*下面初始化輸入設備,即給輸入設備結構體input_dev的成員設置值。
evbit字段用於描述支持的事件,這裏支持同步事件、按鍵事件、絕對座標事件,
BIT宏實際就是對1進行位操作,定義在linux/bitops.h中*/
ts_dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
/*keybit字段用於描述按鍵的類型,在input.h中定義了很多,
這裏用BTN_TOUCH類型來表示觸摸屏的點擊*/
ts_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(ts_dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
/*以下是設置觸摸屏輸入設備的身份信息,直接在這裏寫死。
這些信息可以在驅動掛載後在/proc/bus/input/devices中查看到*/
ts_dev->name = DEVICE_NAME; /*設備名稱*/
ts_dev->id.bustype = BUS_RS232; /*總線類型*/
ts_dev->id.vendor = 0xDEAD; /*經銷商ID號*/
ts_dev->id.product = 0xBEEF; /*產品ID號*/
ts_dev->id.version = 0x0101; /*版本ID號*/
/*好了,一些都準備就緒,現在就把 ts_dev觸摸屏設備註冊到輸入子系統中*/
input_register_device(ts_dev);
return 0;
/*下面是錯誤跳轉處理*/
err_noclk:
clk_disable(adc_clk);
clk_put(adc_clk);
err_nomap:
iounmap(adc_base);
err_noirq:
free_irq(IRQ_ADC, 1);
return ret;
}
static void __exit ts_exit(void)
{
/*屏蔽和釋放中斷*/
disable_irq(IRQ_ADC);
disable_irq(IRQ_TC);
free_irq(IRQ_ADC, 1);
free_irq(IRQ_TC, 1);
/*釋放虛擬地址映射空間*/
iounmap(adc_base);
/*屏蔽和銷燬時鐘*/
if(adc_clk)
{
clk_disable(adc_clk);
clk_put(adc_clk);
adc_clk = NULL;
}
/*將觸摸屏設備從輸入子系統中註銷*/
input_unregister_device(ts_dev);
}
module_init(ts_init);
module_exit(ts_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("My2440 Touch Screen Driver");
然後在 linux-2.6.32.2/drivers/input/touchscreen/Makefile 文件中添加該源代碼的目標模塊,如圖紅色部分:
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) +=zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o
obj-$(CONFIG_TOUCHSCREEN_MINI2440) += mini2440_touchscreen.o
再打開 linux-2.6.32.2/drivers/input/touchscreen/Kconfig 文件,加入如下紅色部分,這樣就在內核配置中添加了 mini2440 的觸摸屏驅動選項:
menuconfig INPUT_TOUCHSCREEN
bool "Touchscreens"
help
Say Y here, and a list of supported touchscreens will be displayed.
This option doesn't affect the kernel.
If unsure, say Y.
if INPUT_TOUCHSCREEN
config TOUCHSCREEN_MINI2440
tristate "Samsung S3C2410 touchscreen input driver"
depends on MACH_MINI2440 && INPUT && INPUT_TOUCHSCREEN && MINI2440_ADC
help
Say Y here if you have the mini2440 touchscreen.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called s3c2410_ts.
config TOUCHSCREEN_ADS7846
tristate "ADS7846/TSC2046 and ADS7843 based touchscreens"
depends on SPI_MASTER
depends on HWMON = n || HWMON
help
1.3.2 配置編譯內核並測試觸摸屏驅動
在命令行執行:make menuconfig,然後依次選擇如下子菜單,找到剛剛添加的觸摸屏驅動選項:
Device Drivers --->
Input device support --->
[*] Touchscreens --->
按空格鍵選中觸摸屏驅動配置選項,並且進入選擇自己的驅動。退出並保存以上內核配置,在命令行輸入:make zImage,將生成 arch/arm/boot/zImage 文件,使用supervivi 的"k"命令把它燒寫到開發板。
1.3.3 觸摸屏驅動原理詳解
原文出處:
http://www.arm9home.net/read.php?tid-2406-keyword-%B4%A5%C3%FE%C6%C1%B3%CC%D0%F2.html
這裏不再贅述。