1. 程式人生 > >android 電容屏:驅動除錯之驅動程式分析篇

android 電容屏:驅動除錯之驅動程式分析篇

以goodix的gt8105為例

一、總體架構

硬體部分:先看一個總體的圖吧,其實觸控式螢幕原理也比較簡單,觸控式螢幕和主控晶片間的聯絡,如下主要有三部分:

1、IIC部分,初始化gt8105的資料和傳回主控制的座標位置資訊就是通過IIC這條線傳輸的;

2、INT,當gt8105初觸控時,會發出中斷通知主控接收資訊(座標資料);

3、gt8105電源、復位這一部分,不同晶片有所不同,可以根據觸控式螢幕晶片來配置。

 軟體部分:

 二、電容觸控式螢幕的主要引數(這部分面試的時候也許有人會問的)

                記得剛出來找工作時有人問我一些問題,我答不上來,現在感覺很清晰(那時候剛畢業IIC我都說不全)
1、IIC
(1)、clk370KHz~400KHz;
(2)、觸控式螢幕工作在從模式,這個比較簡單;
2、電容檢測頻率,也就是每秒檢測的次數:(大概)
(1)、單指≥100Hz;
(2)、五指≥80Hz;
(3)、十指≥60Hz。
3、手指按下,沒擡起時觸發多少中斷?
            中斷個數也就是檢測頻率,按下沒提起一直有中斷。這樣我們就可有判斷單點、劃線之類的操作;
4、校準功能、自動校準(有個別電容屏沒有的,用軟體校準)
(1)、初始化校準
             不同的溫度、溼度及物理空間結構均會影響到電容感測器在閒置狀態的基準值。一般電容觸控式螢幕會在初始化的 200ms內根據環境情況自動獲得新的檢測基準。完成觸控式螢幕檢測的初始化。
(2)、 自動溫漂補償
              溫度、溼度或灰塵等環境因素的緩慢變化,也會影響到電容感測器在閒置狀態的基準值。實時檢測各點資料的變化,對歷史資料進行統計分析,由此來修正檢測基準。從而降低環境變化對觸控式螢幕檢測的影響。    
5、推薦工作條件(環境溫度為 25°C,VDD=2.8V)

引數

最小值

典型值

最大值

單位

模擬AVDD(參考AGND)

2.5

2.8

3.6

V

數字DVDD(參考DGND)

2.5

2.8

3.6

V

電源紋波

50(注意電池、充電器的影響)

mV

工作溫度

-20

+25

+85

工作溼度

-

-

95

%

三、硬體介面電路:

如下圖:

SDA

IIC資料 要上拉電阻,為1K;

SCL

IIC 時鐘(400KHz)

TP_EN

使能腳(gt8105為高電平)

INT

中斷(一直點到觸控式螢幕時中斷是一直髮出的)

VCC

3.3V 這個電壓一直有

GND

對於S5P4418電路:

與LCD介面部分;

與主控觸控式螢幕中斷輸入管腳;

與主控I2C資料傳遞引腳連線;

復位控制引腳與主控連線;

-------

軟體部分,整體流程如下:

三、IIC配置

裝置到晶片的資料、初始化值都是從這條總線上傳輸的,首先我們要配置這個條匯流排,

/linux/arch/arm/mach-exynos/mach-smdkv310.c,這個因平臺而已,地址右移也跟情況而定,如果本來就是7bit的地址就不用移位。

複製程式碼

static struct i2c_board_info i2c_devs5[] __initdata = {  
#if CONFIG_TOUCHSCREEN_GT8105  
          {  
                    I2C_BOARD_INFO("Goodix-TS", (0xaa>>1)),  
                    .irq = IRQ_EINT(5),  
          }  
#endif  
};  

複製程式碼

四、電源、復位(使能腳)

1、電源

          3.3V的電源是一直有的,這個硬體上給就行了。

2、復位(時能腳),這個因觸控式螢幕而已,gt8105工作時要高電平。

在:linux3.0/drivers/input/touchscreen/goodix_touch.h中

複製程式碼

#define          RESETPIN_CFG          s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT)  
#define          RESETPIN_SET0           gpio_direction_output(EXYNOS4_GPB(4),0)  
#define          RESETPIN_SET1          gpio_direction_output(EXYNOS4_GPB(4),1)  
static void goodix_reset(void)  
{  
          int err;  
          err = gpio_request(EXYNOS4_GPB(4), "GPX1");  
          if (err)  
          printk(KERN_ERR "#### failed to request GPB_4 ####\n");  
          RESETPIN_CFG; //配置管腳功能  
          RESETPIN_SET0;//管腳拉低  
          mdelay(20); //延時  
          RESETPIN_SET1;//管腳拉高  
          mdelay(60);  
          gpio_free(EXYNOS4_GPB(4));  
}  

複製程式碼

 4418:在:linux3.0/drivers/input/touchscreen/goodix_tool.h中

五、中斷配置

在:linux3.0/drivers/input/touchscreen/goodix_touch.h中

複製程式碼

#define INT_PORT EXYNOS4_GPX0(5)  
#ifdef INT_PORT  
          #define TS_INT                     IRQ_EINT(5)//中斷引腳,中斷號            
          #define INT_CFG           S3C_GPIO_SFN(0x0F)                                                    
#else  
   
在:linux3.0/drivers/input/touchscreen/goodix_touch.h中 中斷申請  
#ifdef INT_PORT  
          client->irq=TS_INT;  
          if (client->irq)   
          {  
             ret = request_irq(client->irq, goodix_ts_irq_handler , IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,client->name, ts);  
#endif  

複製程式碼

 上面三部完成了觸控式螢幕工作的最基本配置,保證IIC、上電、INT正常,觸控式螢幕就可以工作。
 

 驅動有幾個比較重要的部分:probe函式分析;中斷申請、工作佇列排程;中斷下半部函式的執行,座標值計算、上報。

1、probe函式分析

複製程式碼

static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)  
{  
          struct goodix_ts_data *ts;  
          …………  
          // 1,分配觸控式螢幕結構核心空間;  
          ts = kzalloc(sizeof(*ts), GFP_KERNEL);   
          …………  
          // 2,初始化工作佇列,這個比較重要,中斷觸發後,呼叫佇列中的goodix_ts_work_func函式,計算上報座標值;  
          INIT_WORK(&ts->work, goodix_ts_work_func);   
          …………  
          // 3, 觸控晶片初始化;  
          for(retry=0; retry<3; retry++)  
          {  
                    ret=goodix_init_panel(ts);  
          …………  
          }  
          //4、觸控式螢幕復位,拉高;  
          goodix_reset();                       
#ifdef INT_PORT  
          // 5,中斷申請,TS_INT就是我們所設定的中斷腳;  
          client->irq=TS_INT;                                                              
                    ret = request_irq(client->irq, goodix_ts_irq_handler , IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,  
                              client->name, ts);  
          ………………  
#endif  
   
          // 6、分配input驅動核心空間;  
          ts->input_dev = input_allocate_device();   
  // 7,input初始化引數設定,我們在前面提到Linux與Android 多點觸控協議裡有對這部分說明;  
          ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;  
          ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);  
          ts->input_dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);                                                             // absolute coor (x,y)  
#ifdef HAVE_TOUCH_KEY  
          for(retry = 0; retry < MAX_KEY_NUM; retry++)  
          {  
                    input_set_capability(ts->input_dev,EV_KEY,touch_key_array[retry]);            
          }  
#endif  
   
          input_set_abs_params(ts->input_dev, ABS_X, 0, ts->abs_x_max, 0, 0);  
          input_set_abs_params(ts->input_dev, ABS_Y, 0, ts->abs_y_max, 0, 0);  
          input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, 0, 0);  
          //8、這部分針對觸控式螢幕引數設定;  
#ifdef GOODIX_MULTI_TOUCH  
          input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);  
          input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);  
          input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0);  
          input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, ts->abs_y_max, 0, 0);  
          input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, ts->max_touch_num, 0, 0);  
#endif            
          //9、觸控式螢幕版本資訊設定;  
          sprintf(ts->phys, "input/ts");  
          ts->input_dev->name = goodix_ts_name;  
          ts->input_dev->phys = ts->phys;  
          ts->input_dev->id.bustype = BUS_I2C;  
          ts->input_dev->id.vendor = 0xDEAD;  
          ts->input_dev->id.product = 0xBEEF;  
          ts->input_dev->id.version = 10427;          //screen firmware version  
          //10,對於input子系統來說,這個是重頭戲了,只有註冊了input子系統,其他的才有做用;  
          ret = input_register_device(ts->input_dev);   
          ………………  
          // 11,對睡眠喚醒操作;  
#ifdef CONFIG_HAS_EARLYSUSPEND   
          ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;  
          ts->early_suspend.suspend = goodix_ts_early_suspend;  
          ts->early_suspend.resume = goodix_ts_late_resume;  
          register_early_suspend(&ts->early_suspend);  
#endif  
………………  
}  
 

複製程式碼

(1)、分配觸控式螢幕結構核心空間;

複製程式碼

struct goodix_ts_data {  
          uint16_t addr;  
          uint8_t bad_data;  
          struct i2c_client *client;  
          struct input_dev *input_dev;  
          int use_reset;                    //use RESET flag  
          int use_irq;                    //use EINT flag  
          int read_mode;                    //read moudle mode,20110221 by andrew  
          struct hrtimer timer;  
          struct work_struct work;  
          char phys[32];  
          int retry;  
          struct early_suspend early_suspend;  
          int (*power)(struct goodix_ts_data * ts, int on);  
          uint16_t abs_x_max;  
          uint16_t abs_y_max;  
          uint8_t max_touch_num;  
          uint8_t int_trigger_type;  
          uint8_t green_wake_mode;  
};  

複製程式碼

(2)、初始化工作佇列,這個比較重要,中斷觸發後,呼叫佇列中的goodix_ts_work_func函式,計算上報座標值;這個和中斷申請一起分析;

(3)、觸控晶片初始化;

          對觸控晶片暫存器的初始化,這裡面對中斷方式設定等,一般晶片廠的FAE在除錯的時候會修改這裡面的值,這個也是因晶片而異,有的在驅動裡做,可以直接改;有的直接做成韌體了,那部分要FAE幫忙了。

複製程式碼

uint8_t cfg_info_group1[] =   
          {           
             0x65,0x00,0x25,0x80,0x19,0x00,0x00,0x2C,0x11,0x11,0x32,0x02,0x08,0x10,0x20,0x00,  
             0x00,0x88,0x88,0x88,0x03,0x13,0x32,0x64,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,  
             0x08,0x09,0x0A,0x0B,0x0C,0xFF,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,  
             0x17,0x18,0x19,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  
             0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  
             0x00,0x00,0x00,0x00  
          };  

複製程式碼

(4)、觸控式螢幕復位,拉高;

          gt8015在工作時要拉高,所以我們做一個拉低—延時--拉高的操作;

(5)、中斷申請,TS_INT就是我們所設定的中斷腳,和(2)一起後面分析;

(6)、分配input驅動核心空間;

ts->input_dev= input_allocate_device();  

(7)、input初始化引數設定,我們在前面提到Linux與Android 多點觸控協議裡有對這部分說明;(8)、這部分針對觸控式螢幕引數設定;

(9)、觸控式螢幕版本資訊設定;

複製程式碼

cat /proc/bus/input/devices時可以看到下面資訊(這個是pixcir的觸控式螢幕)  
I: Bus=0018 Vendor=0000 Product=0000 Version=0000  
N: Name="pixcir-ts"  
P: Phys=  
S: Sysfs=/devices/platform/s3c2440-i2c.5/i2c-5/5-005c/input/input3  
U: Uniq=  
H: Handlers=kbd event3   
B: PROP=0  
B: EV=b  
B: KEY=400 0 0 0 0 1000 40000800 0 0 0 0  
B: ABS=2650000 1000000  

複製程式碼

(10)、對於input子系統來說,這個是重頭戲了,驅動註冊到input子系統;

      input_register_device(ts->input_dev);  

(11),觸控式螢幕睡眠喚醒操作,這部分不做詳細說明,感興趣的可以看下……

2、中斷申請、工作佇列排程

(1)、中斷申請

複製程式碼

                 ret = request_irq(client->irq, goodix_ts_irq_handler , IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,  
                              client->name, ts);  
第一個引數: 中斷號,client->irq,client->irq=TS_INT;  
#define TS_INT                     IRQ_EINT(5)對應到我們要申請的中斷;  
第二個引數:中斷執行函式,goodix_ts_irq_handler ;  
第三個引數:中斷觸發方式:上升沿觸發、下降沿觸發、高電平觸發、低電平觸發  
IRQ_TYPE_EDGE_RISING,  
IRQ_TYPE_EDGE_FALLING,  
IRQ_TYPE_LEVEL_LOW,  
IRQ_TYPE_LEVEL_HIGH  
第四個引數:  
第五個引數: 

複製程式碼

(2)、中斷處理函式 goodix_ts_irq_handler

複製程式碼

static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)  
{  
          struct goodix_ts_data *ts = dev_id;  
          queue_work(goodix_wq, &ts->work);  
          return IRQ_HANDLED;  
}  

複製程式碼

看下queue_work()這個函式中的兩個引數:

a、goodix_wq

goodix_wq=create_singlethread_workqueue("goodix_wq");                    //createa work queue and worker thread  

在函式 goodix_ts_init中,建立工作佇列和工作執行緒,初始化時建立執行緒。

b、&ts->work

            在函式goodix_ts_probe()中:

INIT_WORK(&ts->work,goodix_ts_work_func);  

           在工作佇列&ts->work中增加 goodix_ts_work_func任務。

          也就是當中斷函式觸發時,執行中斷函式goodix_ts_irq_handler(),中斷函式裡面對佇列排程,呼叫佇列中的goodix_ts_work_func()函式。

3、中斷下半部函式的執行goodix_ts_work_func()函式

這就是核心部分,座標點的計算、上報、多點處理都在這個函式中執行。

複製程式碼

static void goodix_ts_work_func(struct work_struct *work)  
{            
          int ret=-1;  
          int tmp = 0;  
          uint8_t point_data[(1-READ_COOR_ADDR)+1+2+5*MAX_FINGER_NUM+1]={ 0 }; //read address(1byte)+key index(1byte)+point mask(2bytes)+5bytes*MAX_FINGER_NUM+coor checksum(1byte)  
          uint8_t check_sum = 0;  
          uint16_t finger_current = 0;  
          uint16_t finger_bit = 0;  
          unsigned int count = 0, point_count = 0;  
          unsigned int position = 0;            
          uint8_t track_id[MAX_FINGER_NUM] = {0};  
          unsigned int input_x = 0;  
          unsigned int input_y = 0;  
          unsigned int input_w = 0;  
          unsigned char index = 0;  
          unsigned char touch_num = 0;  
             
          struct goodix_ts_data *ts = container_of(work, struct goodix_ts_data, work);  
   
   
          if(g_enter_isp)return;  
   
          COORDINATE_POLL:  
          if((ts->int_trigger_type> 1)&& (gpio_get_value(INT_PORT) != (ts->int_trigger_type&0x01)))  
          {  
                    goto NO_ACTION;  
          }                      
   
          if( tmp > 9) {  
                       
                    dev_info(&(ts->client->dev), "I2C transfer error,touchscreen stop working.\n");  
                    goto XFER_ERROR ;  
          }  
             
          if(ts->bad_data)            
                    msleep(20);  
             
          point_data[0] = READ_COOR_ADDR;                    //read coor address  
          //1、讀取觸控式螢幕值,手指數、座標值等;  
          ret=i2c_read_bytes(ts->client, point_data, ((1-READ_COOR_ADDR)+1+2+5*ts->max_touch_num+1));  
          …………  
          //2、判斷是否有手指按下;  
          finger_current = (point_data[3 - READ_COOR_ADDR]<<8) + point_data[2 – READ_COOR_ADDR];  
             
          if(finger_current)//3、如果有手指按下  
          {            
                    point_count = 0, finger_bit = finger_current;  
                    //3,迴圈判斷有多少手指按下;  
                    for(count = 0; (finger_bit != 0) && (count < ts->max_touch_num); count++)//cal how many point touch currntly  
                    {  
                              if(finger_bit & 0x01)  
                              {  
                                        track_id[point_count] = count;  
                                        point_count++;  
                              }  
                              finger_bit >>= 1;  
                    }  
                    //4、把按下手指數賦給touch_num;  
                    touch_num = point_count;  
                    //5、計算座標值;  
                    check_sum = point_data[2 - READ_COOR_ADDR] + point_data[3 - READ_COOR_ADDR];                               //cal coor checksum  
                    count = 4 - READ_COOR_ADDR;  
                    for(point_count *= 5; point_count > 0; point_count--)  
                              check_sum += point_data[count++];  
                    check_sum += point_data[count];  
                    if(check_sum != 0)                              //checksum verify error  
                    {  
                              printk("coor checksum error!\n");  
                              if(ts->int_trigger_type> 1)  
                                        goto COORDINATE_POLL;  
                              else            
                                        goto XFER_ERROR;  
                    }  
          }  
          //6、讀取值座標值上報;  
          if(touch_num)  
          {  
                    //7、touch_num為按下手指個數,依次迴圈讀取;  
                    for(index=0; index<touch_num; index++)  
                    {  
                              position = 4 - READ_COOR_ADDR + 5*index;  
                              //8、讀出X的值;  
                              input_x = (unsigned int) (point_data[position]<<8) + (unsigned int)( point_data[position+1]);  
                              //9、讀出Y的值;  
                              input_y = (unsigned int)(point_data[position+2]<<8) + (unsigned int) (point_data[position+3]);  
                              input_w =(unsigned int) (point_data[position+4]);                      
                              //10、如果讀出值超出範圍,退出;  
                              if((input_x > ts->abs_x_max)||(input_y > ts->abs_y_max))  
                              continue;  
                              //11、下面的函式依次上報座標, input_mt_sync單點同步  
                              input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x);  
                              input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y);                                
                              input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);  
                              input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w);  
                              input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, track_id[index]);  
                              input_mt_sync(ts->input_dev);  
                    }  
          }  
          //12、沒有觸控時,初始值為0;  
          else  
          {  
                    input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0);  
                    input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0);  
                    input_mt_sync(ts->input_dev);  
          }  
          //13、同步多點值;  
          input_sync(ts->input_dev);  
   
          if(ts->int_trigger_type> 1)  
          {  
                    msleep(POLL_TIME);  
                    goto COORDINATE_POLL;  
          }  
          goto END_WORK_FUNC;  
   
          NO_ACTION:            
          END_WORK_FUNC:  
          XFER_ERROR:  
                       
          return;  
}  

複製程式碼

                總的來數,當我們手指按下是,不管是單個手指,還是多個手指,座標值和一些資訊儲存到觸控晶片的相應暫存器中,然後再通過IIC讀出,送到主控中就可以了,其他事情就是android去處理了。

           如下圖所示,規格書中座標及重量:XY座標快取暫存器的高低位:

中斷觸發--中斷函式--工作佇列排程--功能函式執行