Android 2.1 farsight version for s5pc100
File Name: s3c-ts.c
1 簡介
1.1 本例基於s5pc100開發板,觸摸屏與CPU直接使用ADC連接。下次再找個I2C的驅動分析一下(比如:tsc2007.c)。
接口如下:
1.2 相關寄存器設置請看《S5PC100_UM_REV104.PDF》第1669頁,REGISTER DESCRIPTION.
1.3 總體上觸摸屏驅動程序的工作流程如下:
Step1:在Probe裏註冊一個Input設備,並註冊TSADC Pen Down Interrupt和TSADC EOC (End of conversion) Interrupt。
Step2:觸摸筆按下,響應中斷,打開AD轉換
Step3:響應AD轉換結束中斷,記錄轉換結果。
Step4:如果沒超過最大連續AD轉換次數,再次轉換。
Step5:如果超過最大連續AD轉換次數,則計算座標上報事件。打開Timer。
Step6:Timer到期,檢查按下狀態。
Step7:如果按下打開AD轉換,轉換結束進入Step3
Step8:如果擡起狀態,上報擡起事件。
1.4 源代碼如下:.
2 初始化
static struct platform_driver s3c_ts_driver = {
.probe = s3c_ts_probe,
.remove = s3c_ts_remove,
.suspend = s3c_ts_suspend,
.resume = s3c_ts_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-ts",
},
};
static char banner[] __initdata = KERN_INFO "S3C Touchscreen driver, (c) 2008 Samsung Electronics\n";//一個常量信息,放在init.data文件中,內核啓動結束後釋放。
static int __init s3c_ts_init(void)
{
printk(banner);
return platform_driver_register(&s3c_ts_driver);//註冊一個名爲s3c-ts的驅動程序
}
static void __exit s3c_ts_exit(void)
{
platform_driver_unregister(&s3c_ts_driver); //註銷一個名爲s3c-ts的驅動程序
}
module_init(s3c_ts_init);//入口宏
module_exit(s3c_ts_exit);//出口宏
MODULE_AUTHOR("Samsung AP");
MODULE_DESCRIPTION("S3C touchscreen driver");
MODULE_LICENSE("GPL");
3 探測函數s3c_ts_probe
3.1 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
獲得資源並保存在res變量中。獲得了flags= IORESOURCE_MEM,索引爲0的資源。
資源定義在如下文件中:arch\arm\plat-s3c\Dev-ts.c
注意除了ts外很多資源定義在arch\arm\plat-s5pc1xx\devs.c中
/* Touch srcreen */
static struct resource s3c_ts_resource[] = {
[0] = {
.start = S3C_PA_ADC,
.end = S3C_PA_ADC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PENDN,
.end = IRQ_PENDN,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_ADC,
.end = IRQ_ADC,
.flags = IORESOURCE_IRQ,
}
};
3.2 ts_mem = request_mem_region(res->start, size, pdev->name);
申請內存區域。申請之後,才能開始ioremap()或者ioremap_nocache()來映射,映射後的變量才能使用。
3.3 ts_base = ioremap(res->start, size);
將io映射到變量ts_base,以後訪問這個變量相當於訪問ts對應的io地址了。
3.4 ts_clock = clk_get(&pdev->dev, "adc");
得到adc的時鐘。adc時鐘定義在arch/arm/plat-s5pc1xx/clock.c中
static struct clk init_clocks[] = {
…
}, {
.name = "adc",
.id = -1,
.parent = &clk_p,
.enable = s5pc1xx_clk_d15_ctrl,
.ctrlbit = S5P_CLKGATE_D15_TSADC,
}, {
…
3.5 s3c_ts_cfg = s3c_ts_get_platdata(&pdev->dev); //得到dev的platdata,如果爲空則返回s3c_ts_default_cfg的數據。
3.6 寄存器初始化
if ((s3c_ts_cfg->presc&0xff) > 0)//cfg是否設置了proscale,
writel(S3C_ADCCON_PRSCEN | S3C_ADCCON_PRSCVL(s3c_ts_cfg->presc&0xFF),\
ts_base+S3C_ADCCON); // 如果設置了,則設置到寄存器
else
writel(0, ts_base+S3C_ADCCON);
/* Initialise registers */
if ((s3c_ts_cfg->delay&0xffff) > 0)//是否設置了延時
writel(s3c_ts_cfg->delay & 0xffff, ts_base+S3C_ADCDLY);
if (s3c_ts_cfg->resol_bit==12) {//設置AD模式,10bit 或12bit
switch(s3c_ts_cfg->s3c_adc_con) {
case ADC_TYPE_2://6410 或s5pc100
writel(readl(ts_base+S3C_ADCCON)|S3C_ADCCON_RESSEL_12BIT, ts_base+S3C_ADCCON);
break;
case ADC_TYPE_1://2410
writel(readl(ts_base+S3C_ADCCON)|S3C_ADCCON_RESSEL_12BIT_1, ts_base+S3C_ADCCON);
break;
default:
dev_err(dev, "Touchscreen over this type of AP isn't supported !\n");
break;
}
}
3.7 writel(WAIT4INT(0), ts_base+S3C_ADCTSC); // S3C_ADCTSC 寄存器清零,此時應該不會發生TouchScreen引起的AD轉換。
3.8 input_dev = input_allocate_device();
使用input子系統的一般流程爲:input_allocate_device()申請一個input_dev設備——>初始化該input_dev——>input_register_device()向子系統註冊該設備——>中斷時input_event()向子系統報告事件
3.9 初始化input_dev(即:ts->dev)
初始化的內容各個TS差不多。
注意:BTN_TOUCH 這個事件的相應是必須的。這是Andorid特有的要求。
ts->dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
3.10 register_early_suspend(&ts->early_suspend);
註冊一個early_suspend函數,這個函數會在power management的時候用到。
3.11 ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
獲得資源並保存在ts_irq變量中。獲得了flags= IORESOURCE_IRQ,索引爲0的資源。
資源定義在如下文件中:arch\arm\plat-s3c\Dev-ts.c
(詳見本節開頭)
3.12 ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts);
註冊一箇中斷響應函數。其中:ts_irq->start 等於IRQ_PENDN
此函數在觸摸屏按下或者擡起的時候發生。
詳見arch/arm/plat-s5pc1xx/ include/plat/irqs.h
#define IRQ_PENDN S5PC1XX_IRQ_VIC2(24)
#define IRQ_TC IRQ_PENDN
3.13 ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
獲得資源並保存在ts_irq變量中。獲得了flags= IORESOURCE_IRQ,索引爲1的資源。
資源定義在如下文件中:arch\arm\plat-s3c\Dev-ts.c
(詳見本節開頭)
3.14 ret = request_irq(ts_irq->start, stylus_action, IRQF_SAMPLE_RANDOM, "s3c_action", ts);
註冊一箇中斷響應函數。其中:ts_irq->start 等於IRQ_ADC
當觸摸屏按下後發生滑動的時候發生。
3.15 ret = input_register_device(ts->dev);
註冊一個input 設備
4 s3c_ts_remove
s3c_ts_probe的反過程。註銷設備,釋放資源。
5 static irqreturn_t stylus_updown(int irqno, void *param)
5.1 讀取按下的狀態
data0 = readl(ts_base+S3C_ADCDAT0);
data1 = readl(ts_base+S3C_ADCDAT1);
//判斷data0,data1最高位是否仍爲"0",爲“0”表示觸摸筆狀態保持爲down
updown = (!(data0 & S3C_ADCDAT0_UPDOWN)) && (!(data1 & S3C_ADCDAT1_UPDOWN));
#ifdef CONFIG_TOUCHSCREEN_S3C_DEBUG
printk(KERN_INFO " %c\n", updown ? 'D' : 'U');
#endif
如果updown是1表示按下。
5.2 判斷按下的狀態
if (updown)
touch_timer_fire(0);
5.3 如果是6410或者s5pc100則,關閉中斷(ADCCLRINT 和ADCCLRINTPNDNUP)
if(ts->s3c_adc_con==ADC_TYPE_2) {
__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
}
6 static irqreturn_t stylus_action(int irqno, void *param)
unsigned long data0;
unsigned long data1;
data0 = readl(ts_base+S3C_ADCDAT0);//讀取數據
data1 = readl(ts_base+S3C_ADCDAT1);
if(ts->resol_bit==12) {//根據類型設置不同的掩碼,將數據保存在yp,xp變量中
#if defined(CONFIG_SMDK6410_REV10) || defined(CONFIG_TOUCHSCREEN_NEW)
ts->yp += S3C_ADCDAT0_XPDATA_MASK_12BIT - (data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT);
ts->xp += S3C_ADCDAT1_YPDATA_MASK_12BIT - (data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT);
#else
ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT;
ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT;
#endif
}
else {//這裏是10bit
#if defined(CONFIG_SMDK6410_REV10) || defined(CONFIG_TOUCHSCREEN_NEW)
ts->yp += S3C_ADCDAT0_XPDATA_MASK - (data0 & S3C_ADCDAT0_XPDATA_MASK);
ts->xp += S3C_ADCDAT1_YPDATA_MASK - (data1 & S3C_ADCDAT1_YPDATA_MASK);
#else
ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK;
ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK;
#endif
}
ts->count++;//累計AD轉化次數
if (ts->count < (1<<ts->shift)) {//如果未到達次數限制,通過設置寄存器開始AD轉化
writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base+S3C_ADCTSC);
writel(readl(ts_base+S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base+S3C_ADCCON);
} else {
#ifdef CONFIG_FB_S3C_INNOLUX430
ts->yp = S3C_ADCDAT1_YPDATA_MASK_12BIT * ts->count - ts->yp;
#endif
mod_timer(&touch_timer, jiffies+1);//設置一個Timer
writel(WAIT4INT(1), ts_base+S3C_ADCTSC);//TS 的AD轉化進入等待狀態
}
if(ts->s3c_adc_con==ADC_TYPE_2) {//6410,S5pc100清理中斷
__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
}
return IRQ_HANDLED;
7 static void touch_timer_fire(unsigned long data)
7.1 校準變量
a0=205,a1=-4999,a2=64552724,a3=6326,a4=-12,a5=-37526976,a6=65536;
這是本驅動所用的變量。
Android要求對獲得的xy數據進行校準,代碼如下:(相關解釋見下面備註)
ts->xp=(long) ((a2+(a0*x)+(a1*y))/a6);
ts->yp=(long) ((a5+(a3*x)+(a4*y))/a6);
備註:
Android的校準主要有如下兩種方案:
方案一:移植TSLIB,通過TSLIB產生 pointercal 校準參數文件。
方案二:在驅動程序中校準。
校準公式解釋如下:
(XL, YL是顯示屏座標,XT, YT是觸摸屏座標,)
XL = XT*A+YT*B+C
YL = XT*D+YT*E+F
由於具體計算是希望是整數運算,所以實際中保存的ABCDEF爲整數,而增加一個參數Div
XL = (XT*A+YT*B+C) / Div
YL = (YT*D+YT*E+F) / Div
TSLIB把以上的7個參數 ABCDEF Div 保存在 pointercal 文件中。
不校準的數據: A=1, B=0, C=0, D=0, E=1, F=0, Div=1
A B C D E F Div
-411 37818 -3636780 -51325 39 47065584 65536
7.2 判斷是按下或者擡起
data0 = readl(ts_base+S3C_ADCDAT0);
data1 = readl(ts_base+S3C_ADCDAT1);
updown = (!(data0 & S3C_ADCDAT0_UPDOWN)) && (!(data1 & S3C_ADCDAT1_UPDOWN));
updown==1表示按下
7.3 如果累計AD轉換次數大於零,計算座標並上報事件
x=(int) ts->xp;
y=(int) ts->yp;
ts->xp=(long) ((a2+(a0*x)+(a1*y))/a6);//計算校準
ts->yp=(long) ((a5+(a3*x)+(a4*y))/a6);
if(ts->xp!=ts->xp_old || ts->yp!=ts->yp_old)//如果和上一次的座標不同
{
input_report_abs(ts->dev, ABS_X, ts->xp);//上報座標
input_report_abs(ts->dev, ABS_Y, ts->yp);
input_report_abs(ts->dev, ABS_Z, 0);
input_report_key(ts->dev, BTN_TOUCH, 1);
input_sync(ts->dev);
}
ts->xp_old=ts->xp;//記錄新的座標點,供下次比對
ts->yp_old=ts->yp;
7.4 如果累計AD轉換次數爲零,則開啓AD轉換
//設置ADC開啓轉換
writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base+S3C_ADCTSC);
writel(readl(ts_base+S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base+S3C_ADCCON);
7.5 如果不是按下,上報擡起事件
ts->count = 0;//計數器清空
#ifdef ANDROID_TS
input_report_abs(ts->dev, ABS_X, ts->xp_old);//上報位置
input_report_abs(ts->dev, ABS_Y, ts->yp_old);
input_report_abs(ts->dev, ABS_Z, 0);
#endif
input_report_key(ts->dev, BTN_TOUCH, 0);//上報擡起事件
#ifndef ANDROID_TS
input_report_abs(ts->dev, ABS_PRESSURE, 0);
#endif
input_sync(ts->dev);
writel(WAIT4INT(0), ts_base+S3C_ADCTSC);//清除ADC中斷
備註:
1. oversampling_shift 防止採樣過度。
2. 關於中斷類型的解釋如下:
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
3. adc type defination
File name: arch\arm\plat-s3c\include\plat\ts.h
enum s3c_adc_type {
ADC_TYPE_0,
ADC_TYPE_1, //S3C2416, S3C2450
ADC_TYPE_2, //S3C64XX, S5PC1XX
};
4. s3c_ts_mach_info type defination
//用於設置觸摸屏信息
struct s3c_ts_mach_info {
int delay; //AD轉換延時
int presc; //分頻
int oversampling_shift; // 採樣的數據
int resol_bit; //轉換精度
enum s3c_adc_type s3c_adc_con;
};
struct s3c_ts_mach_info s3c_ts_default_cfg __initdata = {
.delay = 10000,
.presc = 49,
.oversampling_shift = 2,
.resol_bit = 10
};
//採集觸摸屏信息
struct s3c_ts_info {
struct input_dev *dev;
long xp; //x方向位置
long yp; //y方向位置
int count; //累加xp或yp數據的次數
int shift;
char phys[32];
int resol_bit; //轉換精度
enum s3c_adc_type s3c_adc_con;
};