我們首先來分析drivers/input/touchsreen/s3c2410_ts.c這個檔案:
static struct device_driver s3c2410ts_driver = {
.name = "s3c2410-ts", //在注冊這個driver的時候,系統就是以這個結構的.name來查找相同name的裝置的。
.bus = &platform_bus_type,
.probe = s3c2410ts_probe,
.remove = s3c2410ts_remove,
};
int __init s3c2410ts_init(void)
{
return driver_register(&s3c2410ts_driver);
}
void __exit s3c2410ts_exit(void)
{
driver_unregister(&s3c2410ts_driver);
}
(我們可以看出,在子產品初始化函數中,直接調用了driver_register(),而不是像往常那樣使用平台裝置驅動的模型。到底什麼時候需要使用這個模型,什麼時候不需要呢?而且我們看到很多不是內建在晶片内的裝置,也用了平台裝置的模型,比如framebuffer,是以我覺得平台裝置模型是為了模拟出一條總線,進而調用probe等函數。)
接下來要看的當然是探測函數int __init s3c2410ts_probe(struct device *dev)了:
static int __init s3c2410ts_probe(struct device *dev)
{
struct s3c2410_ts_mach_info *info;
info = ( struct s3c2410_ts_mach_info *)dev->platform_data; //在framebuffer中,也有這個擷取platform的方法,但是這個到底是誰指派的?
//而且這個struct s3c2410_ts_mach_info *info 結構體的拿來幹嘛的?
if (!info)
{
printk(KERN_ERR "Hm... too bad : no platform data for ts\n");
return -EINVAL;
}
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
printk(DEBUG_LVL "Entering s3c2410ts_init\n");
#endif
adc_clock = clk_get(NULL, "adc");
if (!adc_clock) {
printk(KERN_ERR "failed to get adc clock source\n");
return -ENOENT;
}
//clk_use(adc_clock);
clk_enable(adc_clock);
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
printk(DEBUG_LVL "got and enabled clock\n");
#endif
base_addr=ioremap(S3C2410_PA_ADC,0x20); //将0x58000000的IO記憶體映射到自己的位址空間中,跟平台裝置驅動相比,少了擷取IO記憶體資源描述 //這一步。
if (base_addr == NULL) {
printk(KERN_ERR "Failed to remap register block\n");
return -ENOMEM;
}
s3c2410_ts_connect();
if ((info->presc&0xff) > 0)
writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(info->presc&0xFF),\ //設定預分頻器的值
base_addr+S3C2410_ADCCON);
else
writel(0,base_addr+S3C2410_ADCCON);
if ((info->delay&0xffff) > 0)
writel(info->delay & 0xffff, base_addr+S3C2410_ADCDLY); //設定延時時間
writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC); //設定觸摸屏為等待中斷模式
memset(&ts, 0, sizeof(struct s3c2410ts)); //初始化struct s3c2410ts ts這個結構體
//init_input_dev(&ts.dev);
ts.dev = input_allocate_device(); //生成并初始化一個輸入裝置,至于為什麼要生成一個輸入裝置,可以先看input子系統相關的知識。
ts.dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
ts.dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT(BTN_TOUCH);
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);
sprintf(ts.phys, "ts0");
ts.dev->private = &ts;
ts.dev->name = s3c2410ts_name;
ts.dev->phys = ts.phys;
ts.dev->id.bustype = BUS_RS232;
ts.dev->id.vendor = 0xDEAD;
ts.dev->id.product = 0xBEEF;
ts.dev->id.version = S3C2410TSVERSION;
ts.shift = info->oversampling_shift;
if (request_irq(IRQ_ADC, stylus_action, SA_SAMPLE_RANDOM,
"s3c2410_action", ts.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, SA_SAMPLE_RANDOM,
"s3c2410_action", ts.dev)) {
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");
iounmap(base_addr);
return -EIO;
}
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
unsigned long debug_regs;
debug_regs = 0;
debug_regs = readl(base_addr+S3C2410_ADCCON);
printk("S3C2410_ADCCON:0x%08lx\n",debug_regs);
debug_regs = readl(base_addr+S3C2410_ADCTSC);
printk("S3C2410_ADCTSC:0x%08lx\n",debug_regs);
debug_regs = readl(base_addr+S3C2410_ADCDLY);
printk("S3C2410_ADCDLY:0x%08lx\n",debug_regs);
#endif
printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name);
input_register_device(ts.dev);
return 0;
}
說明:
我們看到這個結構體:
struct s3c2410_ts_mach_info {
int delay; //要設定的延時
int presc; //要設定的預分頻值
int oversampling_shift; //這個值是4,說明要取4次坐标,然後求平均值。
};
s3c2410_ts_connect()這一句主要就是配置4個引腳功能:
static inline void s3c2410_ts_connect(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPG12_XMON);
s3c2410_gpio_cfgpin(S3C2410_GPG13, S3C2410_GPG13_nXPON);
s3c2410_gpio_cfgpin(S3C2410_GPG14, S3C2410_GPG14_YMON);
s3c2410_gpio_cfgpin(S3C2410_GPG15, S3C2410_GPG15_nYPON);
}
也就是根據下圖來配置GPG12~GPG15分别用來讀寫ADC的XM、XP、YM、YP引腳的值:

有一個全局變量:
struct s3c2410ts {
struct input_dev *dev; //在跟input子系統打交道要用到的。
long xp; //要向input子系統報告的X、Y值。
long yp;
int count; //已經取的坐标次數,當它為4時,才報告。
int shift; //相當于一個閥值,跟oversampling_shift相同。
char phys[32];
};
static struct s3c2410ts ts;
在調用完probe函數後,就處于等中斷的過程,下面我們來看兩個中斷處理函數:
1、在probe中,
request_irq(IRQ_TC, stylus_updown, SA_SAMPLE_RANDOM, "s3c2410_action", ts.dev)) ;
在觸摸屏被按下時,會産生IRQ_TC中斷,調用下面的函數:
static irqreturn_t stylus_updown(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long data0;
unsigned long data1;
int updown;
disable_irq(IRQ_TC);//add by lili 2007-6-19
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = readl(base_addr+S3C2410_ADCDAT1);
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) //觸筆必須處于點選狀态才調用這個函數
touch_timer_fire(0);
enable_irq(IRQ_TC);//add by lili 2007-6-19
return IRQ_HANDLED;
}
了解這個中斷服務函數,我們先來看一下ADCDAT0跟ADCDAT1這兩個寄存器:(ADCDAT1與之相似)。
我們這裡用到的,主要就是UPDOWN與XPDATA這兩個,分别表示觸筆點選/提起狀态,X、Y軸坐标值。
然後就是調用touch_timer_fire(0)了:
static void touch_timer_fire(unsigned long data)
{
unsigned long data0;
unsigned long data1;
int updown;
int tmp;//add by lili 2006-12-12
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = readl(base_addr+S3C2410_ADCDAT1);
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) {
if (ts.count != 0) {
ts.xp >>= ts.shift;
ts.yp >>= ts.shift;
//reset coordinate system add by lili 2006-12-12
#ifdef CONFIG_RESET_COORDINATE_SYSTEM
tmp = ts.xp;
ts.xp = ts.yp;
ts.yp = tmp;
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
{
struct timeval tv;
do_gettimeofday(&tv);
printk("Touch screen info:");//add by lili 2006-12-9
printk(DEBUG_LVL "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, ts.xp, ts.yp);
}
#endif
input_report_abs(ts.dev, ABS_X, ts.xp);
input_report_abs(ts.dev, ABS_Y, ts.yp);
input_report_key(ts.dev, BTN_TOUCH, 1);
input_report_abs(ts.dev, ABS_PRESSURE, 1);
input_sync(ts.dev);
}
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else {
ts.count = 0;
input_report_key(ts.dev, BTN_TOUCH, 0);
input_report_abs(ts.dev, ABS_PRESSURE, 0);
input_sync(ts.dev);
writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
}
}
我相信看到這裡,對寄存器的各種設定我們一定覺得很亂沒思路,現在理一遍寄存器的設定與ADC的工作模式:
s3c2440觸摸屏接口有4種工作模式:
1、(1号模式我還沒用到,用到了再寫進來。)
2、等待中斷:
這時觸摸屏等待信号的到來,當信号到來時,産生INT_TC中斷,表示有觸摸動作發生。當中斷發生後,觸摸屏需要轉換成其他兩種狀态來讀取觸摸點的位置(X,Y)。
3、獨立的X/Y位置轉換模式:
獨立的X/Y位置轉換模式分為兩種子模式:X位置模式與Y位置模式。X位置模式就是将轉換後的X坐标寫到ADCDAT0寄存器的XPDATA位中,轉換後,觸摸屏接口控制器會産生INT_ADC中斷。
4、自動X/Y位置轉換模式:
該模式下,觸摸屏接口控制器自動轉換X位置和Y位置,轉換後,控制器自動将X、Y坐标寫到ADCDAT0與ADCDAT1中,然後産生INT_ADC中斷。
寄存器請大家自行對照S3C2440A的手冊,這裡就不一 一貼出來了。
我們接着看驅動程式是怎麼在不同模式之間轉換的:
a、在probe函數中,writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);将模式設定為等待中斷模式。
是通過#define WAIT4INT(x) (((x)<<8) | \
S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
S3C2410_ADCTSC_XY_PST(3))
來設定的,也就是說,把ADCTSC設定為0XD3就可以了。
b、調用了probe後,觸摸屏就一直處在等待中斷模式下,然後當有觸摸事件發生後,進入INT_TC的中斷服務函數中:
向輸入子系統報告目前觸摸筆的位置後,就通過下面這句,轉換到了4自動轉換模式。
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
c、上面轉換到了自動模式後,觸摸屏控制器會自動的将X、Y坐标轉換後寫入ADCDAT0、ADCDAT1中,并産生INT_ADC中斷,進而調用INT_ADC相應的中斷處理函數。這樣各個模式之間的轉換,都比較清晰了吧?
現在我們再來看,INT_ADC的中斷服務程式:
static irqreturn_t stylus_action(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long data0;
unsigned long data1;
disable_irq(IRQ_ADC);//add by lili 2007-06-19
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = readl(base_addr+S3C2410_ADCDAT1);
ts.xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
ts.yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
ts.count++;
if (ts.count < (1<<ts.shift)) {
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else {
mod_timer(&touch_timer, jiffies+1);
writel(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
}
enable_irq(IRQ_ADC);
return IRQ_HANDLED;
}
說明:
檢測如果count<4,主要是為了多次讀取觸摸屏XY坐标,求平均值。
如果,未到次數,繼續啟動ADC轉換
再次執行stylus_action
如此進行4次
此時,count=4,得到預設次數,執行else後面語句
也就是,修改touch_timer定時器,将其延後一個機關,在下一個定時時刻到來,調用touch_timer指定的函數:
static struct timer_list touch_timer =TIMER_INITIALIZER(touch_timer_fire, 0, 0);
從這裡看出,出發的是touch_time_fire這個函數。
同時設定等待手寫筆擡起中斷。
然後,分兩種情況:
(1)如果手寫筆還沒有擡起,定時器到了,則觸發touch_timer_fire函數,執行下面的一段代碼:
最要就是求出4次的平均值;向input子系統報告;count等清零;(此時的模式是等待中斷模式,等待觸摸筆提起)又設定為自動轉換模式。
if (updown) {
if (ts.count != 0) {
ts.xp >>= ts.shift; //除以4求四次的平均值。
ts.yp >>= ts.shift;
//reset coordinate system add by lili 2006-12-12
#ifdef CONFIG_RESET_COORDINATE_SYSTEM
tmp = ts.xp;
ts.xp = ts.yp;
ts.yp = tmp;
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
{
struct timeval tv;
do_gettimeofday(&tv);
printk("Touch screen info:"); //add by lili 2006-12-9
printk(DEBUG_LVL "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, ts.xp, ts.yp);
}
#endif
input_report_abs(ts.dev, ABS_X, ts.xp);
input_report_abs(ts.dev, ABS_Y, ts.yp);
input_report_key(ts.dev, BTN_TOUCH, 1);
input_report_abs(ts.dev, ABS_PRESSURE, 1);
input_sync(ts.dev);
}
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
(2)如果在定時器到之前,手寫筆由于抖動,而擡起,那麼會觸發stylus_updown中斷函數,由于此時,updown=0,是以,在此函數中直接傳回了。
然後,在定時器到了之後,仍然會觸發touch_timer_fire,但是由于updown=0,會執行else後面語句:
就是向輸入子系統報告觸摸筆提起事件,并回到等待中斷模式。
else {
ts.count = 0;
input_report_key(ts.dev, BTN_TOUCH, 0);
input_report_abs(ts.dev, ABS_PRESSURE, 0);
input_sync(ts.dev);
writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
}
所 以整個觸摸事件的流程畫出來是這樣的:
ADC的驅動程式到這裡就差不多完成了,希望高手如果發現有錯誤,不吝賜教~