| /* drivers/input/touchscreen/msm_touch.c | 
 |  * | 
 |  * Copyright (c) 2008-2009, 2011, Code Aurora Forum. All rights reserved. | 
 |  * | 
 |  * This software is licensed under the terms of the GNU General Public | 
 |  * License version 2, as published by the Free Software Foundation, and | 
 |  * may be copied, distributed, and modified under those terms. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/slab.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/input.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/jiffies.h> | 
 | #include <linux/io.h> | 
 |  | 
 | #include <mach/msm_touch.h> | 
 |  | 
 | /* HW register map */ | 
 | #define TSSC_CTL_REG      0x100 | 
 | #define TSSC_SI_REG       0x108 | 
 | #define TSSC_OPN_REG      0x104 | 
 | #define TSSC_STATUS_REG   0x10C | 
 | #define TSSC_AVG12_REG    0x110 | 
 |  | 
 | /* status bits */ | 
 | #define TSSC_STS_OPN_SHIFT 0x6 | 
 | #define TSSC_STS_OPN_BMSK  0x1C0 | 
 | #define TSSC_STS_NUMSAMP_SHFT 0x1 | 
 | #define TSSC_STS_NUMSAMP_BMSK 0x3E | 
 |  | 
 | /* CTL bits */ | 
 | #define TSSC_CTL_EN		(0x1 << 0) | 
 | #define TSSC_CTL_SW_RESET	(0x1 << 2) | 
 | #define TSSC_CTL_MASTER_MODE	(0x3 << 3) | 
 | #define TSSC_CTL_AVG_EN		(0x1 << 5) | 
 | #define TSSC_CTL_DEB_EN		(0x1 << 6) | 
 | #define TSSC_CTL_DEB_12_MS	(0x2 << 7)	/* 1.2 ms */ | 
 | #define TSSC_CTL_DEB_16_MS	(0x3 << 7)	/* 1.6 ms */ | 
 | #define TSSC_CTL_DEB_2_MS	(0x4 << 7)	/* 2 ms */ | 
 | #define TSSC_CTL_DEB_3_MS	(0x5 << 7)	/* 3 ms */ | 
 | #define TSSC_CTL_DEB_4_MS	(0x6 << 7)	/* 4 ms */ | 
 | #define TSSC_CTL_DEB_6_MS	(0x7 << 7)	/* 6 ms */ | 
 | #define TSSC_CTL_INTR_FLAG1	(0x1 << 10) | 
 | #define TSSC_CTL_DATA		(0x1 << 11) | 
 | #define TSSC_CTL_SSBI_CTRL_EN	(0x1 << 13) | 
 |  | 
 | /* control reg's default state */ | 
 | #define TSSC_CTL_STATE	  ( \ | 
 | 		TSSC_CTL_DEB_12_MS | \ | 
 | 		TSSC_CTL_DEB_EN | \ | 
 | 		TSSC_CTL_AVG_EN | \ | 
 | 		TSSC_CTL_MASTER_MODE | \ | 
 | 		TSSC_CTL_EN) | 
 |  | 
 | #define TSSC_NUMBER_OF_OPERATIONS 2 | 
 | #define TS_PENUP_TIMEOUT_MS 20 | 
 |  | 
 | #define TS_DRIVER_NAME "msm_touchscreen" | 
 |  | 
 | #define X_MAX	1024 | 
 | #define Y_MAX	1024 | 
 | #define P_MAX	256 | 
 |  | 
 | struct ts { | 
 | 	struct input_dev *input; | 
 | 	struct timer_list timer; | 
 | 	int irq; | 
 | 	unsigned int x_max; | 
 | 	unsigned int y_max; | 
 | }; | 
 |  | 
 | static void __iomem *virt; | 
 | #define TSSC_REG(reg) (virt + TSSC_##reg##_REG) | 
 |  | 
 | static void ts_update_pen_state(struct ts *ts, int x, int y, int pressure) | 
 | { | 
 | 	if (pressure) { | 
 | 		input_report_abs(ts->input, ABS_X, x); | 
 | 		input_report_abs(ts->input, ABS_Y, y); | 
 | 		input_report_abs(ts->input, ABS_PRESSURE, pressure); | 
 | 		input_report_key(ts->input, BTN_TOUCH, !!pressure); | 
 | 	} else { | 
 | 		input_report_abs(ts->input, ABS_PRESSURE, 0); | 
 | 		input_report_key(ts->input, BTN_TOUCH, 0); | 
 | 	} | 
 |  | 
 | 	input_sync(ts->input); | 
 | } | 
 |  | 
 | static void ts_timer(unsigned long arg) | 
 | { | 
 | 	struct ts *ts = (struct ts *)arg; | 
 |  | 
 | 	ts_update_pen_state(ts, 0, 0, 0); | 
 | } | 
 |  | 
 | static irqreturn_t ts_interrupt(int irq, void *dev_id) | 
 | { | 
 | 	u32 avgs, x, y, lx, ly; | 
 | 	u32 num_op, num_samp; | 
 | 	u32 status; | 
 |  | 
 | 	struct ts *ts = dev_id; | 
 |  | 
 | 	status = readl_relaxed(TSSC_REG(STATUS)); | 
 | 	avgs = readl_relaxed(TSSC_REG(AVG12)); | 
 | 	x = avgs & 0xFFFF; | 
 | 	y = avgs >> 16; | 
 |  | 
 | 	/* For pen down make sure that the data just read is still valid. | 
 | 	 * The DATA bit will still be set if the ARM9 hasn't clobbered | 
 | 	 * the TSSC. If it's not set, then it doesn't need to be cleared | 
 | 	 * here, so just return. | 
 | 	 */ | 
 | 	if (!(readl_relaxed(TSSC_REG(CTL)) & TSSC_CTL_DATA)) | 
 | 		goto out; | 
 |  | 
 | 	/* Data has been read, OK to clear the data flag */ | 
 | 	writel_relaxed(TSSC_CTL_STATE, TSSC_REG(CTL)); | 
 | 	/* barrier: Write to complete before the next sample */ | 
 | 	mb(); | 
 | 	/* Valid samples are indicated by the sample number in the status | 
 | 	 * register being the number of expected samples and the number of | 
 | 	 * samples collected being zero (this check is due to ADC contention). | 
 | 	 */ | 
 | 	num_op = (status & TSSC_STS_OPN_BMSK) >> TSSC_STS_OPN_SHIFT; | 
 | 	num_samp = (status & TSSC_STS_NUMSAMP_BMSK) >> TSSC_STS_NUMSAMP_SHFT; | 
 |  | 
 | 	if ((num_op == TSSC_NUMBER_OF_OPERATIONS) && (num_samp == 0)) { | 
 | 		/* TSSC can do Z axis measurment, but driver doesn't support | 
 | 		 * this yet. | 
 | 		 */ | 
 |  | 
 | 		/* | 
 | 		 * REMOVE THIS: | 
 | 		 * These x, y co-ordinates adjustments will be removed once | 
 | 		 * Android framework adds calibration framework. | 
 | 		 */ | 
 | #ifdef CONFIG_ANDROID_TOUCHSCREEN_MSM_HACKS | 
 | 		lx = ts->x_max - x; | 
 | 		ly = ts->y_max - y; | 
 | #else | 
 | 		lx = x; | 
 | 		ly = y; | 
 | #endif | 
 | 		ts_update_pen_state(ts, lx, ly, 255); | 
 | 		/* kick pen up timer - to make sure it expires again(!) */ | 
 | 		mod_timer(&ts->timer, | 
 | 			jiffies + msecs_to_jiffies(TS_PENUP_TIMEOUT_MS)); | 
 |  | 
 | 	} else | 
 | 		printk(KERN_INFO "Ignored interrupt: {%3d, %3d}," | 
 | 				" op = %3d samp = %3d\n", | 
 | 				 x, y, num_op, num_samp); | 
 |  | 
 | out: | 
 | 	return IRQ_HANDLED; | 
 | } | 
 |  | 
 | static int __devinit ts_probe(struct platform_device *pdev) | 
 | { | 
 | 	int result; | 
 | 	struct input_dev *input_dev; | 
 | 	struct resource *res, *ioarea; | 
 | 	struct ts *ts; | 
 | 	unsigned int x_max, y_max, pressure_max; | 
 | 	struct msm_ts_platform_data *pdata = pdev->dev.platform_data; | 
 |  | 
 | 	/* The primary initialization of the TS Hardware | 
 | 	 * is taken care of by the ADC code on the modem side | 
 | 	 */ | 
 |  | 
 | 	ts = kzalloc(sizeof(struct ts), GFP_KERNEL); | 
 | 	input_dev = input_allocate_device(); | 
 | 	if (!input_dev || !ts) { | 
 | 		result = -ENOMEM; | 
 | 		goto fail_alloc_mem; | 
 | 	} | 
 |  | 
 | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	if (!res) { | 
 | 		dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); | 
 | 		result = -ENOENT; | 
 | 		goto fail_alloc_mem; | 
 | 	} | 
 |  | 
 | 	ts->irq = platform_get_irq(pdev, 0); | 
 | 	if (!ts->irq) { | 
 | 		dev_err(&pdev->dev, "Could not get IORESOURCE_IRQ\n"); | 
 | 		result = -ENODEV; | 
 | 		goto fail_alloc_mem; | 
 | 	} | 
 |  | 
 | 	ioarea = request_mem_region(res->start, resource_size(res), pdev->name); | 
 | 	if (!ioarea) { | 
 | 		dev_err(&pdev->dev, "Could not allocate io region\n"); | 
 | 		result = -EBUSY; | 
 | 		goto fail_alloc_mem; | 
 | 	} | 
 |  | 
 | 	virt = ioremap(res->start, resource_size(res)); | 
 | 	if (!virt) { | 
 | 		dev_err(&pdev->dev, "Could not ioremap region\n"); | 
 | 		result = -ENOMEM; | 
 | 		goto fail_ioremap; | 
 | 	} | 
 |  | 
 | 	input_dev->name = TS_DRIVER_NAME; | 
 | 	input_dev->phys = "msm_touch/input0"; | 
 | 	input_dev->id.bustype = BUS_HOST; | 
 | 	input_dev->id.vendor = 0x0001; | 
 | 	input_dev->id.product = 0x0002; | 
 | 	input_dev->id.version = 0x0100; | 
 | 	input_dev->dev.parent = &pdev->dev; | 
 |  | 
 | 	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | 
 | 	input_dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); | 
 | 	input_dev->absbit[BIT_WORD(ABS_MISC)] = BIT_MASK(ABS_MISC); | 
 | 	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | 
 |  | 
 | 	if (pdata) { | 
 | 		x_max = pdata->x_max ? : X_MAX; | 
 | 		y_max = pdata->y_max ? : Y_MAX; | 
 | 		pressure_max = pdata->pressure_max ? : P_MAX; | 
 | 	} else { | 
 | 		x_max = X_MAX; | 
 | 		y_max = Y_MAX; | 
 | 		pressure_max = P_MAX; | 
 | 	} | 
 |  | 
 | 	ts->x_max = x_max; | 
 | 	ts->y_max = y_max; | 
 |  | 
 | 	input_set_abs_params(input_dev, ABS_X, 0, x_max, 0, 0); | 
 | 	input_set_abs_params(input_dev, ABS_Y, 0, y_max, 0, 0); | 
 | 	input_set_abs_params(input_dev, ABS_PRESSURE, 0, pressure_max, 0, 0); | 
 |  | 
 | 	result = input_register_device(input_dev); | 
 | 	if (result) | 
 | 		goto fail_ip_reg; | 
 |  | 
 | 	ts->input = input_dev; | 
 |  | 
 | 	setup_timer(&ts->timer, ts_timer, (unsigned long)ts); | 
 | 	result = request_irq(ts->irq, ts_interrupt, IRQF_TRIGGER_RISING, | 
 | 				 "touchscreen", ts); | 
 | 	if (result) | 
 | 		goto fail_req_irq; | 
 |  | 
 | 	platform_set_drvdata(pdev, ts); | 
 |  | 
 | 	return 0; | 
 |  | 
 | fail_req_irq: | 
 | 	input_unregister_device(input_dev); | 
 | 	input_dev = NULL; | 
 | fail_ip_reg: | 
 | 	iounmap(virt); | 
 | fail_ioremap: | 
 | 	release_mem_region(res->start, resource_size(res)); | 
 | fail_alloc_mem: | 
 | 	input_free_device(input_dev); | 
 | 	kfree(ts); | 
 | 	return result; | 
 | } | 
 |  | 
 | static int __devexit ts_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct resource *res; | 
 | 	struct ts *ts = platform_get_drvdata(pdev); | 
 |  | 
 | 	free_irq(ts->irq, ts); | 
 | 	del_timer_sync(&ts->timer); | 
 |  | 
 | 	input_unregister_device(ts->input); | 
 | 	iounmap(virt); | 
 | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	release_mem_region(res->start, resource_size(res)); | 
 | 	platform_set_drvdata(pdev, NULL); | 
 | 	kfree(ts); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct platform_driver ts_driver = { | 
 | 	.probe		= ts_probe, | 
 | 	.remove		= __devexit_p(ts_remove), | 
 | 	.driver		= { | 
 | 		.name = TS_DRIVER_NAME, | 
 | 		.owner = THIS_MODULE, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init ts_init(void) | 
 | { | 
 | 	return platform_driver_register(&ts_driver); | 
 | } | 
 | module_init(ts_init); | 
 |  | 
 | static void __exit ts_exit(void) | 
 | { | 
 | 	platform_driver_unregister(&ts_driver); | 
 | } | 
 | module_exit(ts_exit); | 
 |  | 
 | MODULE_DESCRIPTION("MSM Touch Screen driver"); | 
 | MODULE_LICENSE("GPL v2"); | 
 | MODULE_ALIAS("platform:msm_touchscreen"); |