Logo Search packages:      
Sourcecode: linux-fsl-imx51 version File versions  Download package

stmp3xxx_ts.c

/*
 * Freesclae STMP37XX/STMP378X Touchscreen driver
 *
 * Author: Vitaly Wool <vital@embeddedalley.com>
 *
 * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
 * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */
/* #define DEBUG*/

#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/input.h>
#include <linux/interrupt.h>

#include <mach/lradc.h>
#include <mach/hardware.h>
#include <mach/platform.h>
#include <mach/regs-lradc.h>

#define TOUCH_DEBOUNCE_TOLERANCE    100

struct stmp3xxx_ts_info {
      int touch_irq;
      int device_irq;
      struct input_dev *idev;
      enum {
            TS_STATE_DISABLED,
            TS_STATE_TOUCH_DETECT,
            TS_STATE_TOUCH_VERIFY,
            TS_STATE_X_PLANE,
            TS_STATE_Y_PLANE,
      } state;
      u16 x;
      u16 y;
      int sample_count;
};

static inline void enter_state_touch_detect(struct stmp3xxx_ts_info *info)
{
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(2));
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(3));
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(4));
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(5));
      __raw_writel(BM_LRADC_CTRL1_LRADC5_IRQ,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);
      __raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);
      /*
       * turn off the yplus and yminus pullup and pulldown, and turn off touch
       * detect (enables yminus, and xplus through a resistor.On a press,
       * xplus is pulled down)
       */
      __raw_writel(BM_LRADC_CTRL0_YMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_YPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_SET);

      hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 0);
      info->state = TS_STATE_TOUCH_DETECT;
      info->sample_count = 0;
}

static inline void enter_state_disabled(struct stmp3xxx_ts_info *info)
{
      __raw_writel(BM_LRADC_CTRL0_YMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_YPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);

      hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 0);
      info->state = TS_STATE_DISABLED;
      info->sample_count = 0;
}


static inline void enter_state_x_plane(struct stmp3xxx_ts_info *info)
{
      __raw_writel(BM_LRADC_CTRL0_YMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_SET);
      __raw_writel(BM_LRADC_CTRL0_YPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_SET);
      __raw_writel(BM_LRADC_CTRL0_XMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);

      hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);

      info->state = TS_STATE_X_PLANE;
      info->sample_count = 0;
}

static inline void enter_state_y_plane(struct stmp3xxx_ts_info *info)
{
      __raw_writel(BM_LRADC_CTRL0_YMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_YPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_SET);
      __raw_writel(BM_LRADC_CTRL0_XPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_SET);
      __raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);

      hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
      info->state = TS_STATE_Y_PLANE;
      info->sample_count = 0;
}

static inline void enter_state_touch_verify(struct stmp3xxx_ts_info *info)
{
      __raw_writel(BM_LRADC_CTRL0_YMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_YPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XMINUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_XPLUS_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_CLR);
      __raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
            REGS_LRADC_BASE + HW_LRADC_CTRL0_SET);

      info->state = TS_STATE_TOUCH_VERIFY;
      hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
      info->sample_count = 0;
}

static void process_lradc(struct stmp3xxx_ts_info *info, u16 x, u16 y,
                  int pressure)
{
      switch (info->state) {
      case TS_STATE_X_PLANE:
            pr_debug("%s: x plane state, sample_count %d\n", __func__,
                        info->sample_count);
            if (info->sample_count < 2) {
                  info->x = x;
                  info->sample_count++;
            } else {
                  if (abs(info->x - x) > TOUCH_DEBOUNCE_TOLERANCE)
                        info->sample_count = 1;
                  else {
                        u16 x_c = info->x * (info->sample_count - 1);
                        info->x = (x_c + x) / info->sample_count;
                        info->sample_count++;
                  }
            }
            if (info->sample_count > 4)
                  enter_state_y_plane(info);
            else
                  hw_lradc_set_delay_trigger_kick(
                              LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
            break;

      case TS_STATE_Y_PLANE:
            pr_debug("%s: y plane state, sample_count %d\n", __func__,
                        info->sample_count);
            if (info->sample_count < 2) {
                  info->y = y;
                  info->sample_count++;
            } else {
                  if (abs(info->y - y) > TOUCH_DEBOUNCE_TOLERANCE)
                        info->sample_count = 1;
                  else {
                        u16 y_c = info->y * (info->sample_count - 1);
                        info->y = (y_c + y) / info->sample_count;
                        info->sample_count++;
                  }
            }
            if (info->sample_count > 4)
                  enter_state_touch_verify(info);
            else
                  hw_lradc_set_delay_trigger_kick(
                              LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
            break;

      case TS_STATE_TOUCH_VERIFY:
            pr_debug("%s: touch verify state, sample_count %d\n", __func__,
                        info->sample_count);
            pr_debug("%s: x %d, y %d\n", __func__, info->x, info->y);
            input_report_abs(info->idev, ABS_X, info->x);
            input_report_abs(info->idev, ABS_Y, info->y);
            input_report_abs(info->idev, ABS_PRESSURE, pressure);
            input_sync(info->idev);
            /* fall through */
      case TS_STATE_TOUCH_DETECT:
            pr_debug("%s: touch detect state, sample_count %d\n", __func__,
                        info->sample_count);
            if (pressure) {
                  input_report_abs(info->idev, ABS_PRESSURE, pressure);
                  enter_state_x_plane(info);
                  hw_lradc_set_delay_trigger_kick(
                              LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
            } else
                  enter_state_touch_detect(info);
            break;

      default:
            printk(KERN_ERR "%s: unknown touchscreen state %d\n", __func__,
                        info->state);
      }
}

static irqreturn_t ts_handler(int irq, void *dev_id)
{
      struct stmp3xxx_ts_info *info = dev_id;
      u16 x_plus, y_plus;
      int pressure = 0;

      if (irq == info->touch_irq)
            __raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ,
                  REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);
      else if (irq == info->device_irq)
            __raw_writel(BM_LRADC_CTRL1_LRADC5_IRQ,
                  REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);

      /* get x, y values */
      x_plus = __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_TOUCH_X_PLUS)) & BM_LRADC_CHn_VALUE;
      y_plus = __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_TOUCH_Y_PLUS)) & BM_LRADC_CHn_VALUE;

      /* pressed? */
      if (__raw_readl(REGS_LRADC_BASE + HW_LRADC_STATUS) & BM_LRADC_STATUS_TOUCH_DETECT_RAW)
            pressure = 1;

      pr_debug("%s: irq %d, x_plus %d, y_plus %d, pressure %d\n",
                  __func__, irq, x_plus, y_plus, pressure);

      process_lradc(info, x_plus, y_plus, pressure);

      return IRQ_HANDLED;
}

static int stmp3xxx_ts_probe(struct platform_device *pdev)
{
      struct input_dev *idev;
      struct stmp3xxx_ts_info *info;
      int ret = 0;
      struct resource *res;

      idev = input_allocate_device();
      info = kzalloc(sizeof(struct stmp3xxx_ts_info), GFP_KERNEL);
      if (idev == NULL || info == NULL) {
            ret = -ENOMEM;
            goto out_nomem;
      }

      idev->name = "STMP3XXX touchscreen";
      idev->evbit[0] = BIT(EV_ABS);
      input_set_abs_params(idev, ABS_X, 0, 0xFFF, 0, 0);
      input_set_abs_params(idev, ABS_Y, 0, 0xFFF, 0, 0);
      input_set_abs_params(idev, ABS_PRESSURE, 0, 1, 0, 0);

      ret = input_register_device(idev);
      if (ret)
            goto out_nomem;

      info->idev = idev;

      res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
      if (!res) {
            printk(KERN_ERR "%s: couldn't get IRQ resource\n", __func__);
            ret = -ENODEV;
            goto out_nodev;
      }
      info->touch_irq = res->start;

      res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
      if (!res) {
            printk(KERN_ERR "%s: couldn't get IRQ resource\n", __func__);
            ret = -ENODEV;
            goto out_nodev;
      }
      info->device_irq = res->start;

      ret = request_irq(info->touch_irq, ts_handler, IRQF_DISABLED,
                        "stmp3xxx_ts_touch", info);
      if (ret)
            goto out_nodev;

      ret = request_irq(info->device_irq, ts_handler, IRQF_DISABLED,
                        "stmp3xxx_ts_dev", info);
      if (ret) {
            free_irq(info->touch_irq, info);
            goto out_nodev;
      }
      enter_state_touch_detect(info);

      hw_lradc_use_channel(LRADC_CH2);
      hw_lradc_use_channel(LRADC_CH3);
      hw_lradc_use_channel(LRADC_CH5);
      hw_lradc_configure_channel(LRADC_CH2, 0, 0, 0);
      hw_lradc_configure_channel(LRADC_CH3, 0, 0, 0);
      hw_lradc_configure_channel(LRADC_CH5, 0, 0, 0);

      /* Clear the accumulator & NUM_SAMPLES for the channels */
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(LRADC_CH2));
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(LRADC_CH3));
      __raw_writel(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn_CLR(LRADC_CH5));

      hw_lradc_set_delay_trigger(LRADC_DELAY_TRIGGER_TOUCHSCREEN,
                  0x3c, 0, 0, 8);

      __raw_writel(BM_LRADC_CTRL1_LRADC5_IRQ,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);
      __raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);

      __raw_writel(BM_LRADC_CTRL1_LRADC5_IRQ_EN,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_SET);
      __raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_SET);

      platform_set_drvdata(pdev, info);
      device_init_wakeup(&pdev->dev, 1);
      goto out;

out_nodev:
      input_free_device(idev);
out_nomem:
      kfree(idev);
      kfree(info);
out:
      return ret;
}

static int stmp3xxx_ts_remove(struct platform_device *pdev)
{
      struct stmp3xxx_ts_info *info = platform_get_drvdata(pdev);

      platform_set_drvdata(pdev, NULL);

      hw_lradc_unuse_channel(LRADC_CH2);
      hw_lradc_unuse_channel(LRADC_CH3);
      hw_lradc_unuse_channel(LRADC_CH5);
      __raw_writel(BM_LRADC_CTRL1_LRADC5_IRQ_EN,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);
      __raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
            REGS_LRADC_BASE + HW_LRADC_CTRL1_CLR);

      free_irq(info->device_irq, info);
      free_irq(info->touch_irq, info);
      input_free_device(info->idev);

      enter_state_disabled(info);
      kfree(info->idev);
      kfree(info);
      return 0;
}

static int stmp3xxx_ts_suspend(struct platform_device *pdev,
                        pm_message_t state)
{
#ifdef CONFIG_PM
      if (!device_may_wakeup(&pdev->dev)) {
            hw_lradc_unuse_channel(LRADC_CH2);
            hw_lradc_unuse_channel(LRADC_CH3);
            hw_lradc_unuse_channel(LRADC_CH5);
      }
#endif
      return 0;
}

static int stmp3xxx_ts_resume(struct platform_device *pdev)
{
#ifdef CONFIG_PM
      if (!device_may_wakeup(&pdev->dev)) {
            hw_lradc_use_channel(LRADC_CH2);
            hw_lradc_use_channel(LRADC_CH3);
            hw_lradc_use_channel(LRADC_CH5);
      }
#endif
      return 0;
}

static struct platform_driver stmp3xxx_ts_driver = {
      .probe            = stmp3xxx_ts_probe,
      .remove           = stmp3xxx_ts_remove,
      .suspend    = stmp3xxx_ts_suspend,
      .resume           = stmp3xxx_ts_resume,
      .driver           = {
            .name       = "stmp3xxx_ts",
      },
};

static int __init stmp3xxx_ts_init(void)
{
      return platform_driver_register(&stmp3xxx_ts_driver);
}

static void __exit stmp3xxx_ts_exit(void)
{
      platform_driver_unregister(&stmp3xxx_ts_driver);
}

module_init(stmp3xxx_ts_init);
module_exit(stmp3xxx_ts_exit);

Generated by  Doxygen 1.6.0   Back to index