Analysis of Touch Panel Driver of Marvell (Marvell PXA310触摸屏驱动代码分析)

1.               Abstract

 抱歉这篇报告是我当初用英文写的,有兴趣的朋友可以参考。

2.               Introduction

It is very difficult to understand this part without any background knowledge of I2C, and therefore please take some time to understand basic ideas of I2C first before moving on. You may read my document of our codes of I2C in reference document [2].

3.               Driver Codes

Codes exist in drivers/input/touchscreen/lt_micco_touch.c.

3.1        Data Structure

The device structure in arch/arm/mach-pxa/generic.c

static struct platform_device lt_ts_device = {

       .name            = "lt_ts",

       .id          = -1,

};

The driver data structure in drivers/input/touchscreen/lt_micco_touch.c:

static struct platform_driver lt_ts_drv = {

       .driver = {

              .name    = "lt_ts",

       },

       .probe           = lt_ts_probe,

       .remove        = lt_ts_remove,

       .resume        = lt_ts_resume,

       .suspend      = lt_ts_suspend,

};

The driver private data structure in drivers/input/touchscreen/lt_micco_touch.c, which is set in global variable, not reference by dev:

struct lt_touch_data {

       spinlock_t                   ts_lock;

       struct task_struct        *thread;

       int                        suspended;

       int                        pen_state;

       int                        use_count;

       wait_queue_head_t           ts_wait_queue;

       struct completion              thread_init;

       struct completion              thread_exit;

};

3.2        Initialize Codes

3.2.1          Probe Function

Codes exist in drivers/input/touchscreen/lt_micco_touch.c.

static int lt_ts_probe(struct platform_device *pdev)

{

       int ret;

       /* register input device */

       littleton_ts_input_dev = input_allocate_device();

       littleton_ts_input_dev->name = "Littleton touchscreen";

       littleton_ts_input_dev->open = littleton_ts_input_open;//This function will be explained later.

       littleton_ts_input_dev->close = littleton_ts_input_close;

       __set_bit(EV_ABS, littleton_ts_input_dev->evbit);

       __set_bit(ABS_X, littleton_ts_input_dev->absbit);

       __set_bit(ABS_Y, littleton_ts_input_dev->absbit);

       __set_bit(ABS_PRESSURE, littleton_ts_input_dev->absbit);

/* Added BEGIN, by  HEYANLI, on: 2008-1-14 10:36:58 */

         littleton_ts_input_dev->absmax[ABS_PRESSURE] = 0xfff;

         littleton_ts_input_dev->absmin[ABS_PRESSURE] = 0;

         littleton_ts_input_dev->absfuzz[ABS_PRESSURE] = 0x40;//What is the meaning of this field?

/* Added END, by  HEYANLI, on: 2008-1-14 10:36:58 */

       input_register_device(littleton_ts_input_dev);

       /* The Littleton IRQ come from Micco. So we just implemate

         * a callback IRQ function for Micco.

         */

       lt_touch = kzalloc(sizeof(struct lt_touch_data), GFP_KERNEL);

       ……

       ret = pmic_callback_register(PMIC_EVENT_TOUCH, lt_touch_interrupt); //This function registered a callback in PMIC’s interruption, please refer to document [] for details of this code.

       ……

       spin_lock_init(&lt_touch->ts_lock);

       init_waitqueue_head(&lt_touch->ts_wait_queue);

       lt_touch->pen_state = TSI_PEN_UP;

       lt_touch->suspended = 0;

       lt_touch->use_count = 0;

       /* Enable the touch IRQ here for lcd backlight */

       micco_enable_pen_down_irq(1);

       micco_tsi_poweron();     

       return 0;

       ……

}

3.2.2          Function micco_enable_pen_down_irq

int micco_enable_pen_down_irq(int enable)

{

       int ret;

       u8 val;

 

       if (enable) {

              /* enable pen down IRQ */

              ret = micco_read(MICCO_IRQ_MASK_C, &val);

              val &= ~0x10;

              ret = micco_write(MICCO_IRQ_MASK_C, val); //Enable pen_down, which means clear the pen down mask in IRQ_MASK_C, refer to document [1] P125.

       } else {

              /* disable pen down IRQ */

              ret = micco_read(MICCO_IRQ_MASK_C, &val);

              if (!(val & IRQ_MASK_C_PEN_DOWN)) {

                     val |= 0x10;

                     ret = micco_write(MICCO_IRQ_MASK_C, val); //Disable pen_down, which means set the pen down mask in IRQ_MASK_C, refer to document [1] P125.

              }

       }

       return ret;

}

3.2.3          Function micco_tsi_poweron

int micco_tsi_poweron(void)

{

       int status;

       u8 val;

       val = 0x10;

       status = micco_write(MICCO_ADC_MAN_CONTROL, val); //Enable reference voltage supply for ADC, refer to document [1] P150. The fifth bit is: LDO_ADC_ENABLE

       status = micco_read(MICCO_ADC_AUTO_CONTROL_2, &val);

       if (!(val & MICCO_ADC_AUTO_2_PENDET_EN)) {

              val |= MICCO_ADC_AUTO_2_PENDET_EN;

              status = micco_write(MICCO_ADC_AUTO_CONTROL_2, val); //Enable TSI Pen Detect circuit, refer to document [1] P150. The fifth bit is: PEN_DETECT_ENABLE

       }

       /* TSI_DEABY: 8 slot. TSI_SKIP: 8 slot */

       micco_read (MICCO_TSI_CONTROL_1, &val);

       val = 0x4 | (0x4 << 3);//Set TSI_DELAY<2:0>=4, and set TSI_SKIP<2:0>=4, refer to document [1] P151. This is the place that we set the delay of touch panel of ADC.

     /* Added END, by  HEYANLI, on: 2008-1-14 11:08:55 */

       status = micco_write(MICCO_TSI_CONTROL_1, val);

       val = 0x00;

       status = micco_write(MICCO_TSI_CONTROL_2, val);//Clear TSI_CONTROL_2

       if (status)

              return -EIO;

       return 0;

}

3.2.4          Function littleton_ts_input_open

static int littleton_ts_input_open(struct input_dev *idev)

{

       int ret = 0;

       unsigned long flags;

       ……

       spin_lock_irqsave(&lt_touch->ts_lock, flags);

       if (lt_touch->use_count++ ==0) {

              spin_unlock_irqrestore(&lt_touch->ts_lock, flags);

              lt_touch_init(); //See the explanation below

              init_completion(&lt_touch->thread_init);

              ret = kernel_thread(ts_thread, lt_touch, 0); //This function is the main loop of touch panel.

              if (ret < 0)

                     return ret;

              wait_for_completion(&lt_touch->thread_init);

       } else {

              spin_unlock_irqrestore(&lt_touch->ts_lock, flags);

       }

       return 0;

}

 

void lt_touch_init(void)

{

/* Added BEGIN, by  HEYANLI, on: 2008-1-14 10:25:40 */

    u8 val = 0;

    micco_read (MICCO_IRQ_MASK_C, &val);

    val &= (~(IRQ_MASK_C_PEN_DOWN | IRQ_MASK_C_TSI_READY));

    micco_write(MICCO_IRQ_MASK_C, val); //Enable pen_down and tsi_ready interruption, which means clear the pen down mask and tsi_ready mask in IRQ_MASK_C, refer to document [1] P125.

/* Added END, by  HEYANLI, on: 2008-1-14 10:25:40 */

       return;

}

3.3        Main Process Codes

3.3.1          Function ts_thread

static int ts_thread( void *d )

{

       struct lt_touch_data *lt_td = d;

       u16 tem_x = 0xffff;

       u16 tem_y = 0xffff;

       int ret = 0, state;

       u8 val;

       DEFINE_WAIT(ts_wait);

       /* set up thread context */

       lt_td->thread = current;   

       daemonize("lt_touch_thread");

              /* init is complete */

       complete(&lt_td->thread_init); //Notify littleton_ts_input_open to continue.

       /* touch reader loop */

       while (1) {   

              /* if the pen state is up, sleep */

              if (TSI_PEN_UP == lt_td->pen_state) {// If previous status is PEN_UP, then we will sleep until there is a PEN_DOWN event, which is sent by function lt_touch_interrupt.

                     prepare_to_wait(&lt_touch->ts_wait_queue,

                            &ts_wait, TASK_INTERRUPTIBLE);

                           

                     if (TSI_PEN_UP == lt_td->pen_state) {

                            /* Added BEGIN, by  HEYANLI, on: 2008-1-14 10:26:56 */

                            micco_read(MICCO_EVENT_C, &val); //I do not know why Yanli has added these codes, since it seemed to be no sense here.

                            /* Added END, by  HEYANLI, on: 2008-1-14 10:26:56 */

                            schedule(); //Enter sleep and wait for the PEN_DOWN event.

                     }

                     finish_wait(&lt_td->ts_wait_queue, &ts_wait);

              }

              try_to_freeze();

              //Before doing following codes, we should check if we are waked up by signals, since we have used interruptible sleep. I think it would be a bug.

              /* Now the pen state is down */

              ret = micco_read(MICCO_STATUS_A, &val);

              if (val & MICCO_STATUS_A_PEN_DOWN) {     /* pen down */

                     if (TSI_PEN_UP ==  lt_touch->pen_state) {

                            lt_touch->pen_state = TSI_PEN_DOWN;

                            pr_debug("%s: touch pen down!", __func__);

                     }

                     /* Enable the auto measure of the TSI.

                       * This will disable pen down interupt automatically.

                       */

                     micco_tsi_enable_tsi(1); //Enable auto ADC measurements. This function call will disable PEN_DOWN detection automatically, please refer to document [1] P73 Pen Down Detect for details.

/* Wjt modify begin, by Raymond, 2008-6-16  13:42:20 */

//  we just sleep for 10ms , and give up the method which read tsi_ready bit

// this might have some problem ? if in 10ms , the auto measure is not done.

// but for the following tsi_ready bit read method will spend too much CPU time on busy waiting.

// so we had to give it up.

                     msleep(10); //Just want to skip some events? It seemed very strange to me.

……

/* Wjt remove end, by Raymond, 2008-6-16  13:42:20 */

                     pr_debug("%s: micco c val = 0x%x when TSI ready",

                                   __func__, val);

                     /* Deleteed BEGIN, by  HEYANLI, on: 2008-1-14 10:28:53 */

                        /*

                     lt_micco_touch_read(&tem_x,&tem_y, &state);

                     pr_debug("%s: tem_x:0x%x; tem_y:0x%x; pen_state:0x%x",

                                   __func__, tem_x, tem_y, state);

                      */

                     /* Deleted END, by  HEYANLI, on: 2008-1-14 10:28:53 */

#ifdef CONFIG_IPM // I have not trace what is the meaning of IPM.

                     ipm_event_notify(IPM_EVENT_UI, IPM_EVENT_DEVICE_TSI, NULL, 0);

#endif

              }

              micco_tsi_enable_tsi(0);//Disable the auto ADC measurements.

              /* After disable the auto tsi, need wait a while to read the

                * correct pen_down state. Currently, just wait for 1 ms.

                */

              mdelay(1);

              ret = micco_read(MICCO_STATUS_A, &val);//To check if we are still in PEN_DOWN status

              pr_debug("%s: after tsi disable, before pen_down" /

                            "enabled status a = 0x%x", __func__, val);

              if (val & MICCO_STATUS_A_PEN_DOWN) {

#ifdef CONFIG_LITTLETON_BACKLIGHT

                     extern void led_touch_press(void);

                     led_touch_press();

#endif

                     /* Pen still down. The X, Y data is valid.

                       * Report it.

                       */

                     /* Added BEGIN, by  HEYANLI, on: 2008-1-14 10:34:00 */

                     lt_micco_touch_read(&tem_x,&tem_y, &state); //Get the value of X and Y. Please refer to document [1] P157 Section TSI_X_MSB, TSI_Y_MSB and TSI_X+Y_LSB

                     /* Added END, by  HEYANLI, on: 2008-1-14 10:34:00 */

                     input_report_abs(littleton_ts_input_dev,

                                   ABS_X, tem_x & 0xfff);

                     input_report_abs(littleton_ts_input_dev,

                                   ABS_Y, tem_y & 0xfff);

                     input_report_abs(littleton_ts_input_dev,

                                   ABS_PRESSURE, 0xfff);

                     /* Added BEGIN, by  HEYANLI, on: 2008-1-14 10:34:53 */

                     input_sync (littleton_ts_input_dev);

                     /* Added END, by  HEYANLI, on: 2008-1-14 10:34:53 */

                     // we have sleep 10 ms in the beginning of measure, so skip it here.

                     //msleep(10);

                     /* don't enable the pen down interrupt because

                       * the pen down interrupt enabling for Micco will

                       * trigger an pen down interrupt. polling to get

                       * the next pen X, Y.

                       */

                     continue; //Enter the main thread loop again. From my point of view, it is very inefficiency. We should use the event of TSI_Ready instead.

              } else {  /* Pen is up now */

                     /* Report a pen up event */     

                     input_report_abs(littleton_ts_input_dev, ABS_PRESSURE, 0);

                     /* Added BEGIN, by  HEYANLI, on: 2008-1-14 10:35:42 */

                     input_sync (littleton_ts_input_dev);

                     /* Added END, by  HEYANLI, on: 2008-1-14 10:35:42 */

                     pr_debug("%s: report pen up", __func__);

                     lt_touch->pen_state = TSI_PEN_UP;

                     micco_tsi_enable_pen(1); //Now that the status is PEN_UP, we should enable the detection of PEN circuit. Please refer to document [1] P73 section Pen Down Detect for details.

              }

              if (!lt_td->thread)

                     break;

       }

       /* Always enable the pen down detection before exit from thread */

       micco_tsi_enable_pen(1);

       complete_and_exit(&lt_td->thread_exit, 0);

       return 0;

}

 

int micco_tsi_enable_tsi(int tsi_en)

{

       int status;

       u8 val;

       status = micco_read(MICCO_ADC_AUTO_CONTROL_2, &val);

       if (status)

              return -EIO;

       if (tsi_en)

              val |= MICCO_ADC_AUTO_2_TSI_EN;

       else

              val &= ~MICCO_ADC_AUTO_2_TSI_EN;

       status = micco_write(MICCO_ADC_AUTO_CONTROL_2, val); //Write if we want to enable auto ADC measurements. Please refer to document [1] P150 for details. And please refer to document [1] P73 Section TSI Scheduler for the meaning of auto ADC measurements. Note: If we do not set auto ADC measurements, we have to do it by ourselves, which means that we should set the TSI_CONTROL_2 related register bit and get the result of X and Y one by one.

       if (status)

              return -EIO;

       return 0;

}

3.3.2          Function lt_touch_interrupt

This function is registered as the callback of the Dialog interruption callback.

void lt_touch_interrupt(unsigned long event)

{

       if (lt_touch->use_count > 0)

              wake_up_interruptible(&lt_touch->ts_wait_queue); //Now we have received an interruption, and we then can wake up the main thread for following process.

      return ;

}

4.               Future Works

I do not know if following works are necessary:

1. Remove the infinite thread loop and add all response the interruption call back.

It seemed that the work tsi_thread can be merged into event call back lt_touch_interrupt.

2. Add response to event TSI_READY, refer to section 5.2.

5.               Questions

5.1        What is the meaning of TSI_DELAY<2:0> and TSI_SKIP<2:0> ?

Place: P151 of Data Sheet: DA9034 Audio and Power Management IC

Following is the description on P73:

Filtering of LCD noise is handled by external capacitors, connected from each TSI pin to ground. The low-pass filter thus formed with the touch screen resistance forces a longer settling time, prior to ADC sampling. The time between switching and measurement conversion is handled by TSI_DELAY<2:0> in register TSI_CONTROL_1. It specifies a delay in multiples of slot time. A value of zero indicates the XY switches are only selected during an ADC conversion. This provides 6 μs for settling.

For a delay of N slots, the X switches are selected and the measurement performed N slots later. Immediately after the conversion, the Y switches are selected and, N slots later, the measurement is performed. The X-Y measurement is then available for reading from the registers.

To reduce power consumption, the X-Y measurement can be made intermittently. The gap between actual measurements is set by TSI_SKIP<2:0> in register TSI_CONTROL_1. It specifies a delay in multiples of slot time. A value of zero indicates the X-Y measurements are continually repeated. Otherwise, a specified number of slots are skipped before the next X-Y measurement occurs.

5.2        Why do we only monitor the interruption of event PEN_DOWN?

There are two kinds of events of touch panel, PEN_DOWN and TSI_READY, which are used for indicating if there is a pen down action and if the ADC result is ready for read. However, I am amazing that we only used the previous event in our codes. Refer to drivers/i2c/chips/micco.c:

static unsigned int micco_event_change(void)

{

       unsigned int ret = 0;

       u8 val, mask;

       ……

       micco_read(MICCO_EVENT_C, &val);

       if (val & MICCO_EC_PEN_DOWN)

              ret |= PMIC_EVENT_TOUCH;

       ……

       return ret;

}

From my point of view, it is more efficient if we can respond to the event of TSI_READY, instead of checking the status of TSI Register in the main thread loop of function ts_thread.

5.3        Why do not we check if we are waked up by signals in function ts_thread?

It seemed to be bug, since we do not need to send touch panel events if we are waked up by signals.

6.               References

[1]. Data Sheet: DA9034 Audio and Power Management IC

[2]. Analysis of I2C.doc, Huang Gao

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值