zoukankan      html  css  js  c++  java
  • 基于GPL329xx linux平台电容屏gsl1680的驱动调试分析

       因客户有用到了gsl1680 7寸电容屏,所以拿了一块过来,便在329xx的平台上面开始调试了。

           大概浏览了一下所提供的资料,只有介绍模组的资料跟一份中文版的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-----NC

    5----NC          6-----GND

    7----SDA        8-----SCL

    9----IO Cntl  10-----INT

    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 ");

    #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 ", __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 ");
      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! ");
      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 ", __func__);
      ret = -ENOMEM;
      goto __err_register;
     }
     gp_gpio_set_output(touch_reset, 1,0);

     DBG_PRINT("=======gslx680 ctp test===== ");
     gp_i2c_handle = gslx680_i2c_request();
     if (gp_i2c_handle == -1) {
      DIAG_ERROR("cap touch panel iic request error! ");
      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 ", __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 ");

     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] ", __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. ");

    #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 ",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这款手指按下的中断信号为周期性的方波。

      以上仅供参考,不足之处还请指正。

  • 相关阅读:
    1082 射击比赛 (20 分)
    1091 N-自守数 (15 分)
    1064 朋友数 (20 分)
    1031 查验身份证 (15 分)
    1028 人口普查 (20 分)
    1059 C语言竞赛 (20 分)
    1083 是否存在相等的差 (20 分)
    1077 互评成绩计算 (20 分)
    792. 高精度减法
    791. 高精度加法
  • 原文地址:https://www.cnblogs.com/0822vaj/p/4195694.html
Copyright © 2011-2022 走看看