因客戶有用到了gsl1680 7寸電容屏,因此拿了一塊過來,便在329xx的平臺上面開始調試了。linux
大概瀏覽了一下所提供的資料,只有介紹模組的資料跟一份中文版的datasheet,datasheet只是說了個大概,沒有提到讀取觸摸座標的寄存器。不過還好有給一份在其餘處理器平臺的驅動,因此讀取座標的部分代碼移植過來就能夠了。算法
gsl1680接口跟其餘的電容屏同樣,也是i2c接口的,貌似市面上的電容屏都是i2c接口,電容屏自帶了微控制器MCU,用與處理採樣,座標轉換等,還有一些抖動算法處理,完後將座標保存在固定的寄存器裏面,以後會給出一箇中斷信號輸出到pin腳,通知主控讀取座標。不過手上拿到這一款內部是沒有flash的,即自身不帶firmware的,初始化時要靠主控將firmware透過i2c接口加載到觸摸屏的RAM裏面,以後電容屏才能正常工做。我的以爲這種方式有兩個缺點:1.firmware有8000多個值,加載須要花掉大概10s左右,增長了開機時間(後續能夠將i2c clk調爲400K試試看);2.自己不帶firmware,須要加載才能工做,無疑給調試增長了難度。不過,相對電阻屏,電容屏的調試仍是比較簡單的,沒必要處理採樣/轉換,還有干擾/抖動等問題。電容屏而言,自帶了的MCU,因此這些工做都已經幫你作好了,卻是很方便。函數
下面咱們具體先看一下gsl1680硬件模組的接口,總共有12個pin,有效的只有6個pin,分別是GND,VCC,SDA,SCL,IO CNTL,INT。線程
1----GND 2-----NC調試
3----NC 4-----NCorm
5----NC 6-----GND接口
7----SDA 8-----SCL隊列
9----IO Cntl 10-----INTip
11----VDD 12-----VDD資源
簡單說明一下:
1,6 :pin接GND;
2,3,4,5 懸空
7,8 i2c接口
9 IO Cntl 硬件reset/wake pin
10 INT 中斷pin腳
11,12 VCC接3.0或者3.3V
瞭解了上面的硬件接口,觸摸屏須要佔用的主控的硬件資源有:i2c接口,一個外部中斷,一個GPIO(用於控制觸摸屏硬件reset/power down進入省電模式)就能夠完成觸摸屏座標的獲取了。
下面,先介紹一下驅動具體流程。
1.module_init函數裏面,註冊平臺設備platform device;
2.註冊platform device driver;
static struct platform_device gp_tp_device = {
.name = "gp_tp",
.id = -1,
.dev = {
.release = gp_tp_device_release,
}
};
static struct platform_driver gp_tp_driver = {
.driver = {
.name = "gp_tp",
.owner = THIS_MODULE,
},
.probe = gp_tp_probe,
.remove = gp_tp_remove,
.suspend = gp_tp_suspend,
.resume = gp_tp_resume,
};
上面的platform_driver裏面的probe函數將會完成如下幾件事情:
1)建立單一內核線程,註冊內核線程函數,用於中斷下半部的觸摸屏座標的讀取,以及將座標值上報給linux input子系統;
經過調用create_singlethread_workqueue API建立一個工做隊列內核線程;
2)建立一個event設備,從觸摸屏獲取到的座標會上報給event,用戶層將會從這個event讀取到觸摸屏按下的座標;
3)i2c 設備的申請;用於與觸摸屏通訊跟加載firmware;
4)申請一個GPIO口,用於控制觸摸屏的reset/wake ,即接到9 IO Cntl pin腳;
5)軟件/硬件復位觸摸屏,加載firmware;
6)申請/註冊外部中斷函數;
以上就是在probe函數裏面完成的工做,接下來就是等待觸摸屏按下,而後中斷函數通知內核線程函數去讀取觸摸的座標,上報給event,
以後也是不斷循環這個過程。
觸摸屏驅動probe函數完整的實現代碼以下所示:
/** device driver probe*/
static int __init gp_tp_probe(struct platform_device *pdev)
{
int rc;
int ret = 0;
int intidx, slaveAddr;
int io_wake= -1;
gpio_content_t ctx;
unsigned int debounce = 1;//27000; /*1ms*/
//gp_board_touch_t *touch_config = NULL;
print_info("Entering gp_tp_probe\n");
#ifdef VIRTUAL_KEYS
virtual_keys_init();
#endif
memset(&ts, 0, sizeof(gp_tp_t));
/* Create single thread work queue */
ts.touch_wq = create_singlethread_workqueue("touch_wq");
if (!ts.touch_wq)
{
print_info("%s unable to create single thread work queue\n", __func__);
ret = -ENOMEM;
goto __err_work_queue;
}
INIT_WORK(&ts.mt_set_nice_work, gp_mt_set_nice_work);
queue_work(ts.touch_wq, &ts.mt_set_nice_work);
ts.dev = input_allocate_device();
if ( NULL==ts.dev ){
print_info("Unable to alloc input device\n");
ret = -ENOMEM;
goto __err_alloc;
}
I2C_REQUEST:
gp_i2c_handle = gslx680_i2c_request();
if (gp_i2c_handle == -1) {
DIAG_ERROR("cap touch panel iic request error!\n");
goto I2C_REQUEST;
return -ENOMEM;
}
__set_bit(EV_ABS, ts.dev->evbit);
input_set_abs_params(ts.dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_X - 1, 0, 0);
input_set_abs_params(ts.dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y - 1, 0, 0);
input_set_abs_params(ts.dev, ABS_MT_TRACKING_ID, 0, (MAX_CONTACTS + 1), 0, 0);
input_set_abs_params(ts.dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_capability(ts.dev, EV_KEY, KEY_BACK);
input_set_capability(ts.dev, EV_KEY, KEY_HOME);
input_set_capability(ts.dev, EV_KEY, KEY_MENU);
ts.dev->name = "gp_ts";
ts.dev->phys = "gp_ts";
ts.dev->id.bustype = BUS_I2C;
/* All went ok, so register to the input system */
rc = input_register_device(ts.dev);
if (rc) {
ret = -EIO;
goto __err_reg_input;
}
/* request cpt reset pin*/
intidx = MK_GPIO_INDEX( RST_CHANNEL, RST_FUNC, RST_GID, RST_PIN );
touch_reset = gp_gpio_request(intidx, "touch_reset"); /* GPIO1[7] ---- */
if(IS_ERR((void*)touch_reset)) {
print_info("%s unable to register client\n", __func__);
ret = -ENOMEM;
goto __err_register;
}
gp_gpio_set_output(touch_reset, 1,0);
DBG_PRINT("=======gslx680 ctp test=====\n");
gp_i2c_handle = gslx680_i2c_request();
if (gp_i2c_handle == -1) {
DIAG_ERROR("cap touch panel iic request error!\n");
return -ENOMEM;
}
gp_gpio_set_output(touch_reset, 0,0);
msleep(100);
gp_gpio_set_output(touch_reset, 1,0);
msleep(100);
reset_chip();
gp_load_ctp_fw();
startup_chip();
msleep(50);
reset_chip();
startup_chip();
//gp_i2c_bus_release(gp_i2c_handle);
INIT_WORK(&ts.mt_work, gp_multi_touch_work);
intidx = MK_GPIO_INDEX( INT_IRQ_CHANNEL, INT_IRQ_FUNC, INT_IRQ_GID, INT_IRQ_PIN );
ts.client = gp_gpio_request(intidx, "touch_int"); /* GPIO1[7] ---- */
if(IS_ERR((void*)ts.client)) {
print_info("%s unable to register client\n", __func__);
ret = -ENOMEM;
goto __err_register;
}
gp_gpio_set_input(ts.client, GPIO_PULL_LOW);
gp_gpio_irq_property(ts.client, GPIO_IRQ_EDGE_TRIGGER|GPIO_IRQ_ACTIVE_RISING, &debounce);
gp_gpio_register_isr(ts.client, gp_ts_callback, (void *)ts.client);
print_info("End gp_tp_probe\n");
return 0;
__err_register:
input_unregister_device(ts.dev);
__err_reg_input:
//gp_ti2c_bus_release(ts.i2c_handle);
__err_i2c:
// kfree(ts.i2c_handle);
__err_i2c_allocate:
//gp_gpio_release(ts.touch_reset);
__err_pin_request:
input_free_device(ts.dev);
__err_alloc:
destroy_workqueue(ts.touch_wq);
__err_work_queue:
return ret;
}
觸摸屏按下中斷處理函數以下所示
/**
* interrupt callback
*/
void gp_ts_callback(void* client)
{
gp_gpio_enable_irq(ts.client, 0);
queue_work(ts.touch_wq, &ts.mt_work);
}
linux的中斷機制通常分爲兩個部分:上半部跟下半部。上半部處理的工做通常要儘量的少,快速;而將繁重、繁瑣、處理時間比較長的工做留在下半部去處理。因此,上半部執行的任務算是比較輕鬆的,這裏只是清掉了外部中端狀態寄存器,而後調度下半部工做的運行。
下面咱們再看一下,觸摸屏按下中斷的下半部的工做:
static void
gp_mt_set_nice_work(
struct work_struct *work
)
{
print_info("[%s:%d]\n", __FUNCTION__, __LINE__);
set_user_nice(current, -20);
}
static inline unsigned int join_bytes(unsigned char a, unsigned char b)
{
unsigned int ab = 0;
ab = ab | a;
ab = ab << 8 | b;
return ab;
}
static void
gp_multi_touch_work(
struct work_struct *work
)
{
int i,ret;
char touched, id;
unsigned short x, y;
unsigned int pending;
int irq_state;
char tp_data[(MULTI_TP_POINTS + 1)*4 ];
//print_info("WQ gp_multi_touch_work.\n");
#if ADJUST_CPU_FREQ
clockstatus_configure(CLOCK_STATUS_TOUCH,1);
#endif
ret = gsl_ts_read(0x80, tp_data, sizeof(tp_data));
if( ret < 0) {
print_info("gp_tp_get_data fail,return %d\n",ret);
gp_gpio_enable_irq(ts.client, 1);
return;
}
touched = (tp_data[0]< MULTI_TP_POINTS ? tp_data[0] : MULTI_TP_POINTS);
for (i=1;i<=MAX_CONTACTS;i++) {
id_state_flag[i] = 0;
}
//printk("point = %d ",touched);
for (i = 0; i < touched; i++) {
id = tp_data[4 *( i + 1) + 3] >> 4;
x = join_bytes(tp_data[4 *( i + 1) + 3] & 0xf,tp_data[4 *( i + 1) + 2]);
y = join_bytes(tp_data[4 *( i + 1) + 1],tp_data[4 *( i + 1) + 0]);
if(1 <= id && id <= MAX_CONTACTS){
record_point(x, y, id);
report_data(x_new, y_new, 10, id);
id_state_flag[id] = 1;
}
}
if (touched == 0) {
input_mt_sync(ts.dev);
}
for(i=1;i<=MAX_CONTACTS;i++)
{
if( (0 == touched) || ((0 != id_state_old_flag[i]) && (0 == id_state_flag[i])) )
{
id_sign[i]=0;
}
id_state_old_flag[i] = id_state_flag[i];
}
#if ADJUST_CPU_FREQ
if(touched == 0){
clockstatus_configure(CLOCK_STATUS_TOUCH,0);
}
#endif
ts.prev_touched = touched;
input_sync(ts.dev);
__error_check_touch_int:
__error_get_mt_data:
/* Clear interrupt flag */
pending = (1 << GPIO_PIN_NUMBER(ts.intIoIndex));
gpHalGpioSetIntPending(ts.intIoIndex, pending);
gp_gpio_enable_irq(ts.client, 1);
}
先從觸摸屏控制器0x80的寄存器開始讀取24個byte,而後將讀回來的數據進行解析,獲得當前觸摸的點數,座標等,以後通過相關的處理後,上報給event。由於是採集多點座標,而後一塊兒處理這些點的座標,因此上報完一個點要調用input_mt_sync,直到上報完當前全部按下的點的座標以後,再調用input_sync 函數達到同步的目的。
以上大體分析了gsl1680電容屏的linux驅動,其實電容屏的驅動處理流程都是大同小異的,處理流程都差很少,具備通用性。
以前還調試過一款墩泰的ft5x06的電容屏,這款比較簡單,初始化不用加載firmware,接口也基本差很少,只是ft5x06 手指按下時中斷輸出引腳爲低電平,然而gsl1680這款手指按下的中斷信號爲週期性的方波。
以上僅供參考,不足之處還請指正。