| 
									
										
										
										
											2009-12-15 08:48:57 -08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Driver for the Freescale Semiconductor MC13783 touchscreen. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. | 
					
						
							|  |  |  |  * Copyright (C) 2009 Sascha Hauer, Pengutronix | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Initial development of this code was funded by | 
					
						
							|  |  |  |  * Phytec Messtechnik GmbH, http://www.phytec.de/
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify it | 
					
						
							|  |  |  |  * under the terms of the GNU General Public License version 2 as published by | 
					
						
							|  |  |  |  * the Free Software Foundation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #include <linux/platform_device.h>
 | 
					
						
							|  |  |  | #include <linux/mfd/mc13783.h>
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/input.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define MC13783_TS_NAME	"mc13783-ts"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define DEFAULT_SAMPLE_TOLERANCE 300
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static unsigned int sample_tolerance = DEFAULT_SAMPLE_TOLERANCE; | 
					
						
							|  |  |  | module_param(sample_tolerance, uint, S_IRUGO | S_IWUSR); | 
					
						
							|  |  |  | MODULE_PARM_DESC(sample_tolerance, | 
					
						
							|  |  |  | 		"If the minimal and maximal value read out for one axis (out " | 
					
						
							|  |  |  | 		"of three) differ by this value (default: " | 
					
						
							|  |  |  | 		__stringify(DEFAULT_SAMPLE_TOLERANCE) ") or more, the reading " | 
					
						
							|  |  |  | 		"is supposed to be wrong and is discarded.  Set to 0 to " | 
					
						
							|  |  |  | 		"disable this check."); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct mc13783_ts_priv { | 
					
						
							|  |  |  | 	struct input_dev *idev; | 
					
						
							|  |  |  | 	struct mc13783 *mc13783; | 
					
						
							|  |  |  | 	struct delayed_work work; | 
					
						
							|  |  |  | 	struct workqueue_struct *workq; | 
					
						
							|  |  |  | 	unsigned int sample[4]; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static irqreturn_t mc13783_ts_handler(int irq, void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mc13783_ts_priv *priv = data; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-03-05 13:44:26 -08:00
										 |  |  | 	mc13783_irq_ack(priv->mc13783, irq); | 
					
						
							| 
									
										
										
										
											2009-12-15 08:48:57 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Kick off reading coordinates. Note that if work happens already | 
					
						
							|  |  |  | 	 * be queued for future execution (it rearms itself) it will not | 
					
						
							|  |  |  | 	 * be rescheduled for immediate execution here. However the rearm | 
					
						
							|  |  |  | 	 * delay is HZ / 50 which is acceptable. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	queue_delayed_work(priv->workq, &priv->work, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return IRQ_HANDLED; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define sort3(a0, a1, a2) ({						\
 | 
					
						
							|  |  |  | 		if (a0 > a1)						\ | 
					
						
							|  |  |  | 			swap(a0, a1);					\ | 
					
						
							|  |  |  | 		if (a1 > a2)						\ | 
					
						
							|  |  |  | 			swap(a1, a2);					\ | 
					
						
							|  |  |  | 		if (a0 > a1)						\ | 
					
						
							|  |  |  | 			swap(a0, a1);					\ | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void mc13783_ts_report_sample(struct mc13783_ts_priv *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct input_dev *idev = priv->idev; | 
					
						
							|  |  |  | 	int x0, x1, x2, y0, y1, y2; | 
					
						
							|  |  |  | 	int cr0, cr1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * the values are 10-bit wide only, but the two least significant | 
					
						
							|  |  |  | 	 * bits are for future 12 bit use and reading yields 0 | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	x0 = priv->sample[0] & 0xfff; | 
					
						
							|  |  |  | 	x1 = priv->sample[1] & 0xfff; | 
					
						
							|  |  |  | 	x2 = priv->sample[2] & 0xfff; | 
					
						
							|  |  |  | 	y0 = priv->sample[3] & 0xfff; | 
					
						
							|  |  |  | 	y1 = (priv->sample[0] >> 12) & 0xfff; | 
					
						
							|  |  |  | 	y2 = (priv->sample[1] >> 12) & 0xfff; | 
					
						
							|  |  |  | 	cr0 = (priv->sample[2] >> 12) & 0xfff; | 
					
						
							|  |  |  | 	cr1 = (priv->sample[3] >> 12) & 0xfff; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_dbg(&idev->dev, | 
					
						
							|  |  |  | 		"x: (% 4d,% 4d,% 4d) y: (% 4d, % 4d,% 4d) cr: (% 4d, % 4d)\n", | 
					
						
							|  |  |  | 		x0, x1, x2, y0, y1, y2, cr0, cr1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sort3(x0, x1, x2); | 
					
						
							|  |  |  | 	sort3(y0, y1, y2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cr0 = (cr0 + cr1) / 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!cr0 || !sample_tolerance || | 
					
						
							|  |  |  | 			(x2 - x0 < sample_tolerance && | 
					
						
							|  |  |  | 			 y2 - y0 < sample_tolerance)) { | 
					
						
							|  |  |  | 		/* report the median coordinate and average pressure */ | 
					
						
							|  |  |  | 		if (cr0) { | 
					
						
							|  |  |  | 			input_report_abs(idev, ABS_X, x1); | 
					
						
							|  |  |  | 			input_report_abs(idev, ABS_Y, y1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			dev_dbg(&idev->dev, "report (%d, %d, %d)\n", | 
					
						
							|  |  |  | 					x1, y1, 0x1000 - cr0); | 
					
						
							|  |  |  | 			queue_delayed_work(priv->workq, &priv->work, HZ / 50); | 
					
						
							|  |  |  | 		} else | 
					
						
							|  |  |  | 			dev_dbg(&idev->dev, "report release\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		input_report_abs(idev, ABS_PRESSURE, | 
					
						
							|  |  |  | 				cr0 ? 0x1000 - cr0 : cr0); | 
					
						
							|  |  |  | 		input_report_key(idev, BTN_TOUCH, cr0); | 
					
						
							|  |  |  | 		input_sync(idev); | 
					
						
							|  |  |  | 	} else | 
					
						
							|  |  |  | 		dev_dbg(&idev->dev, "discard event\n"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void mc13783_ts_work(struct work_struct *work) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mc13783_ts_priv *priv = | 
					
						
							|  |  |  | 		container_of(work, struct mc13783_ts_priv, work.work); | 
					
						
							|  |  |  | 	unsigned int mode = MC13783_ADC_MODE_TS; | 
					
						
							|  |  |  | 	unsigned int channel = 12; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (mc13783_adc_do_conversion(priv->mc13783, | 
					
						
							|  |  |  | 				mode, channel, priv->sample) == 0) | 
					
						
							|  |  |  | 		mc13783_ts_report_sample(priv); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int mc13783_ts_open(struct input_dev *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mc13783_ts_priv *priv = input_get_drvdata(dev); | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mc13783_lock(priv->mc13783); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-03-05 13:44:26 -08:00
										 |  |  | 	mc13783_irq_ack(priv->mc13783, MC13783_IRQ_TS); | 
					
						
							| 
									
										
										
										
											2009-12-15 08:48:57 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ret = mc13783_irq_request(priv->mc13783, MC13783_IRQ_TS, | 
					
						
							|  |  |  | 		mc13783_ts_handler, MC13783_TS_NAME, priv); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = mc13783_reg_rmw(priv->mc13783, MC13783_ADC0, | 
					
						
							|  |  |  | 			MC13783_ADC0_TSMOD_MASK, MC13783_ADC0_TSMOD0); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		mc13783_irq_free(priv->mc13783, MC13783_IRQ_TS, priv); | 
					
						
							|  |  |  | out: | 
					
						
							|  |  |  | 	mc13783_unlock(priv->mc13783); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void mc13783_ts_close(struct input_dev *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mc13783_ts_priv *priv = input_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mc13783_lock(priv->mc13783); | 
					
						
							|  |  |  | 	mc13783_reg_rmw(priv->mc13783, MC13783_ADC0, | 
					
						
							|  |  |  | 			MC13783_ADC0_TSMOD_MASK, 0); | 
					
						
							|  |  |  | 	mc13783_irq_free(priv->mc13783, MC13783_IRQ_TS, priv); | 
					
						
							|  |  |  | 	mc13783_unlock(priv->mc13783); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cancel_delayed_work_sync(&priv->work); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init mc13783_ts_probe(struct platform_device *pdev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mc13783_ts_priv *priv; | 
					
						
							|  |  |  | 	struct input_dev *idev; | 
					
						
							|  |  |  | 	int ret = -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
					
						
							|  |  |  | 	idev = input_allocate_device(); | 
					
						
							|  |  |  | 	if (!priv || !idev) | 
					
						
							|  |  |  | 		goto err_free_mem; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	INIT_DELAYED_WORK(&priv->work, mc13783_ts_work); | 
					
						
							|  |  |  | 	priv->mc13783 = dev_get_drvdata(pdev->dev.parent); | 
					
						
							|  |  |  | 	priv->idev = idev; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * We need separate workqueue because mc13783_adc_do_conversion | 
					
						
							|  |  |  | 	 * uses keventd and thus would deadlock. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	priv->workq = create_singlethread_workqueue("mc13783_ts"); | 
					
						
							|  |  |  | 	if (!priv->workq) | 
					
						
							|  |  |  | 		goto err_free_mem; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	idev->name = MC13783_TS_NAME; | 
					
						
							|  |  |  | 	idev->dev.parent = &pdev->dev; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | 
					
						
							|  |  |  | 	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | 
					
						
							|  |  |  | 	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, 0xfff, 0, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	idev->open = mc13783_ts_open; | 
					
						
							|  |  |  | 	idev->close = mc13783_ts_close; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	input_set_drvdata(idev, priv); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = input_register_device(priv->idev); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		dev_err(&pdev->dev, | 
					
						
							|  |  |  | 			"register input device failed with %d\n", ret); | 
					
						
							|  |  |  | 		goto err_destroy_wq; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	platform_set_drvdata(pdev, priv); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | err_destroy_wq: | 
					
						
							|  |  |  | 	destroy_workqueue(priv->workq); | 
					
						
							|  |  |  | err_free_mem: | 
					
						
							|  |  |  | 	input_free_device(idev); | 
					
						
							|  |  |  | 	kfree(priv); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __devexit mc13783_ts_remove(struct platform_device *pdev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mc13783_ts_priv *priv = platform_get_drvdata(pdev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	platform_set_drvdata(pdev, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	destroy_workqueue(priv->workq); | 
					
						
							|  |  |  | 	input_unregister_device(priv->idev); | 
					
						
							|  |  |  | 	kfree(priv); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct platform_driver mc13783_ts_driver = { | 
					
						
							|  |  |  | 	.remove		= __devexit_p(mc13783_ts_remove), | 
					
						
							|  |  |  | 	.driver		= { | 
					
						
							|  |  |  | 		.owner	= THIS_MODULE, | 
					
						
							|  |  |  | 		.name	= MC13783_TS_NAME, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init mc13783_ts_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return platform_driver_probe(&mc13783_ts_driver, &mc13783_ts_probe); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | module_init(mc13783_ts_init); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void __exit mc13783_ts_exit(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	platform_driver_unregister(&mc13783_ts_driver); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | module_exit(mc13783_ts_exit); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_DESCRIPTION("MC13783 input touchscreen driver"); | 
					
						
							|  |  |  | MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL v2"); | 
					
						
							|  |  |  | MODULE_ALIAS("platform:" MC13783_TS_NAME); |