天天看點

s3c2440的ADC觸摸屏驅動——學習筆記

我們首先來分析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引腳的值:

s3c2440的ADC觸摸屏驅動——學習筆記

有一個全局變量:

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與之相似)。

s3c2440的ADC觸摸屏驅動——學習筆記

我們這裡用到的,主要就是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);

  }

所 以整個觸摸事件的流程畫出來是這樣的:

s3c2440的ADC觸摸屏驅動——學習筆記

ADC的驅動程式到這裡就差不多完成了,希望高手如果發現有錯誤,不吝賜教~