summaryrefslogtreecommitdiff
path: root/drivers/input/touchscreen/stmp3xxx_ts.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/touchscreen/stmp3xxx_ts.c')
-rw-r--r--drivers/input/touchscreen/stmp3xxx_ts.c422
1 files changed, 422 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/stmp3xxx_ts.c b/drivers/input/touchscreen/stmp3xxx_ts.c
new file mode 100644
index 000000000000..4e6ab20ea780
--- /dev/null
+++ b/drivers/input/touchscreen/stmp3xxx_ts.c
@@ -0,0 +1,422 @@
+/*
+ * 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);