1*d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2d201fd5dSSascha Hauer /* 3d201fd5dSSascha Hauer * Driver for the Freescale Semiconductor MC13783 touchscreen. 4d201fd5dSSascha Hauer * 5d201fd5dSSascha Hauer * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. 6d201fd5dSSascha Hauer * Copyright (C) 2009 Sascha Hauer, Pengutronix 7d201fd5dSSascha Hauer * 8d201fd5dSSascha Hauer * Initial development of this code was funded by 9d201fd5dSSascha Hauer * Phytec Messtechnik GmbH, http://www.phytec.de/ 10d201fd5dSSascha Hauer */ 11d201fd5dSSascha Hauer #include <linux/platform_device.h> 12d201fd5dSSascha Hauer #include <linux/mfd/mc13783.h> 13d201fd5dSSascha Hauer #include <linux/kernel.h> 14d201fd5dSSascha Hauer #include <linux/module.h> 15d201fd5dSSascha Hauer #include <linux/input.h> 16d201fd5dSSascha Hauer #include <linux/sched.h> 175a0e3ad6STejun Heo #include <linux/slab.h> 18d201fd5dSSascha Hauer #include <linux/init.h> 19d201fd5dSSascha Hauer 20d201fd5dSSascha Hauer #define MC13783_TS_NAME "mc13783-ts" 21d201fd5dSSascha Hauer 22d201fd5dSSascha Hauer #define DEFAULT_SAMPLE_TOLERANCE 300 23d201fd5dSSascha Hauer 24d201fd5dSSascha Hauer static unsigned int sample_tolerance = DEFAULT_SAMPLE_TOLERANCE; 25d201fd5dSSascha Hauer module_param(sample_tolerance, uint, S_IRUGO | S_IWUSR); 26d201fd5dSSascha Hauer MODULE_PARM_DESC(sample_tolerance, 27d201fd5dSSascha Hauer "If the minimal and maximal value read out for one axis (out " 28d201fd5dSSascha Hauer "of three) differ by this value (default: " 29d201fd5dSSascha Hauer __stringify(DEFAULT_SAMPLE_TOLERANCE) ") or more, the reading " 30d201fd5dSSascha Hauer "is supposed to be wrong and is discarded. Set to 0 to " 31d201fd5dSSascha Hauer "disable this check."); 32d201fd5dSSascha Hauer 33d201fd5dSSascha Hauer struct mc13783_ts_priv { 34d201fd5dSSascha Hauer struct input_dev *idev; 358dd93eeeSUwe Kleine-König struct mc13xxx *mc13xxx; 36d201fd5dSSascha Hauer struct delayed_work work; 37d201fd5dSSascha Hauer unsigned int sample[4]; 381039d762SMichael Thalmeier struct mc13xxx_ts_platform_data *touch; 39d201fd5dSSascha Hauer }; 40d201fd5dSSascha Hauer 41d201fd5dSSascha Hauer static irqreturn_t mc13783_ts_handler(int irq, void *data) 42d201fd5dSSascha Hauer { 43d201fd5dSSascha Hauer struct mc13783_ts_priv *priv = data; 44d201fd5dSSascha Hauer 458dd93eeeSUwe Kleine-König mc13xxx_irq_ack(priv->mc13xxx, irq); 46d201fd5dSSascha Hauer 47d201fd5dSSascha Hauer /* 48d201fd5dSSascha Hauer * Kick off reading coordinates. Note that if work happens already 49d201fd5dSSascha Hauer * be queued for future execution (it rearms itself) it will not 50d201fd5dSSascha Hauer * be rescheduled for immediate execution here. However the rearm 51d201fd5dSSascha Hauer * delay is HZ / 50 which is acceptable. 52d201fd5dSSascha Hauer */ 5362e51475SBhaktipriya Shridhar schedule_delayed_work(&priv->work, 0); 54d201fd5dSSascha Hauer 55d201fd5dSSascha Hauer return IRQ_HANDLED; 56d201fd5dSSascha Hauer } 57d201fd5dSSascha Hauer 58d201fd5dSSascha Hauer #define sort3(a0, a1, a2) ({ \ 59d201fd5dSSascha Hauer if (a0 > a1) \ 60d201fd5dSSascha Hauer swap(a0, a1); \ 61d201fd5dSSascha Hauer if (a1 > a2) \ 62d201fd5dSSascha Hauer swap(a1, a2); \ 63d201fd5dSSascha Hauer if (a0 > a1) \ 64d201fd5dSSascha Hauer swap(a0, a1); \ 65d201fd5dSSascha Hauer }) 66d201fd5dSSascha Hauer 67d201fd5dSSascha Hauer static void mc13783_ts_report_sample(struct mc13783_ts_priv *priv) 68d201fd5dSSascha Hauer { 69d201fd5dSSascha Hauer struct input_dev *idev = priv->idev; 70d201fd5dSSascha Hauer int x0, x1, x2, y0, y1, y2; 71d201fd5dSSascha Hauer int cr0, cr1; 72d201fd5dSSascha Hauer 73d201fd5dSSascha Hauer /* 74d201fd5dSSascha Hauer * the values are 10-bit wide only, but the two least significant 75d201fd5dSSascha Hauer * bits are for future 12 bit use and reading yields 0 76d201fd5dSSascha Hauer */ 77d201fd5dSSascha Hauer x0 = priv->sample[0] & 0xfff; 78d201fd5dSSascha Hauer x1 = priv->sample[1] & 0xfff; 79d201fd5dSSascha Hauer x2 = priv->sample[2] & 0xfff; 80d201fd5dSSascha Hauer y0 = priv->sample[3] & 0xfff; 81d201fd5dSSascha Hauer y1 = (priv->sample[0] >> 12) & 0xfff; 82d201fd5dSSascha Hauer y2 = (priv->sample[1] >> 12) & 0xfff; 83d201fd5dSSascha Hauer cr0 = (priv->sample[2] >> 12) & 0xfff; 84d201fd5dSSascha Hauer cr1 = (priv->sample[3] >> 12) & 0xfff; 85d201fd5dSSascha Hauer 86d201fd5dSSascha Hauer dev_dbg(&idev->dev, 87d201fd5dSSascha Hauer "x: (% 4d,% 4d,% 4d) y: (% 4d, % 4d,% 4d) cr: (% 4d, % 4d)\n", 88d201fd5dSSascha Hauer x0, x1, x2, y0, y1, y2, cr0, cr1); 89d201fd5dSSascha Hauer 90d201fd5dSSascha Hauer sort3(x0, x1, x2); 91d201fd5dSSascha Hauer sort3(y0, y1, y2); 92d201fd5dSSascha Hauer 93d201fd5dSSascha Hauer cr0 = (cr0 + cr1) / 2; 94d201fd5dSSascha Hauer 95d201fd5dSSascha Hauer if (!cr0 || !sample_tolerance || 96d201fd5dSSascha Hauer (x2 - x0 < sample_tolerance && 97d201fd5dSSascha Hauer y2 - y0 < sample_tolerance)) { 98d201fd5dSSascha Hauer /* report the median coordinate and average pressure */ 99d201fd5dSSascha Hauer if (cr0) { 100d201fd5dSSascha Hauer input_report_abs(idev, ABS_X, x1); 101d201fd5dSSascha Hauer input_report_abs(idev, ABS_Y, y1); 102d201fd5dSSascha Hauer 103d201fd5dSSascha Hauer dev_dbg(&idev->dev, "report (%d, %d, %d)\n", 104d201fd5dSSascha Hauer x1, y1, 0x1000 - cr0); 10562e51475SBhaktipriya Shridhar schedule_delayed_work(&priv->work, HZ / 50); 10662e51475SBhaktipriya Shridhar } else { 107d201fd5dSSascha Hauer dev_dbg(&idev->dev, "report release\n"); 10862e51475SBhaktipriya Shridhar } 109d201fd5dSSascha Hauer 110d201fd5dSSascha Hauer input_report_abs(idev, ABS_PRESSURE, 111d201fd5dSSascha Hauer cr0 ? 0x1000 - cr0 : cr0); 112d201fd5dSSascha Hauer input_report_key(idev, BTN_TOUCH, cr0); 113d201fd5dSSascha Hauer input_sync(idev); 11462e51475SBhaktipriya Shridhar } else { 115d201fd5dSSascha Hauer dev_dbg(&idev->dev, "discard event\n"); 116d201fd5dSSascha Hauer } 11762e51475SBhaktipriya Shridhar } 118d201fd5dSSascha Hauer 119d201fd5dSSascha Hauer static void mc13783_ts_work(struct work_struct *work) 120d201fd5dSSascha Hauer { 121d201fd5dSSascha Hauer struct mc13783_ts_priv *priv = 122d201fd5dSSascha Hauer container_of(work, struct mc13783_ts_priv, work.work); 1238dd93eeeSUwe Kleine-König unsigned int mode = MC13XXX_ADC_MODE_TS; 124d201fd5dSSascha Hauer unsigned int channel = 12; 125d201fd5dSSascha Hauer 1268dd93eeeSUwe Kleine-König if (mc13xxx_adc_do_conversion(priv->mc13xxx, 1271039d762SMichael Thalmeier mode, channel, 1281039d762SMichael Thalmeier priv->touch->ato, priv->touch->atox, 1291039d762SMichael Thalmeier priv->sample) == 0) 130d201fd5dSSascha Hauer mc13783_ts_report_sample(priv); 131d201fd5dSSascha Hauer } 132d201fd5dSSascha Hauer 133d201fd5dSSascha Hauer static int mc13783_ts_open(struct input_dev *dev) 134d201fd5dSSascha Hauer { 135d201fd5dSSascha Hauer struct mc13783_ts_priv *priv = input_get_drvdata(dev); 136d201fd5dSSascha Hauer int ret; 137d201fd5dSSascha Hauer 1388dd93eeeSUwe Kleine-König mc13xxx_lock(priv->mc13xxx); 139d201fd5dSSascha Hauer 1408dd93eeeSUwe Kleine-König mc13xxx_irq_ack(priv->mc13xxx, MC13XXX_IRQ_TS); 141d201fd5dSSascha Hauer 1428dd93eeeSUwe Kleine-König ret = mc13xxx_irq_request(priv->mc13xxx, MC13XXX_IRQ_TS, 143d201fd5dSSascha Hauer mc13783_ts_handler, MC13783_TS_NAME, priv); 144d201fd5dSSascha Hauer if (ret) 145d201fd5dSSascha Hauer goto out; 146d201fd5dSSascha Hauer 1478dd93eeeSUwe Kleine-König ret = mc13xxx_reg_rmw(priv->mc13xxx, MC13XXX_ADC0, 1488dd93eeeSUwe Kleine-König MC13XXX_ADC0_TSMOD_MASK, MC13XXX_ADC0_TSMOD0); 149d201fd5dSSascha Hauer if (ret) 1508dd93eeeSUwe Kleine-König mc13xxx_irq_free(priv->mc13xxx, MC13XXX_IRQ_TS, priv); 151d201fd5dSSascha Hauer out: 1528dd93eeeSUwe Kleine-König mc13xxx_unlock(priv->mc13xxx); 153d201fd5dSSascha Hauer return ret; 154d201fd5dSSascha Hauer } 155d201fd5dSSascha Hauer 156d201fd5dSSascha Hauer static void mc13783_ts_close(struct input_dev *dev) 157d201fd5dSSascha Hauer { 158d201fd5dSSascha Hauer struct mc13783_ts_priv *priv = input_get_drvdata(dev); 159d201fd5dSSascha Hauer 1608dd93eeeSUwe Kleine-König mc13xxx_lock(priv->mc13xxx); 1618dd93eeeSUwe Kleine-König mc13xxx_reg_rmw(priv->mc13xxx, MC13XXX_ADC0, 1628dd93eeeSUwe Kleine-König MC13XXX_ADC0_TSMOD_MASK, 0); 1638dd93eeeSUwe Kleine-König mc13xxx_irq_free(priv->mc13xxx, MC13XXX_IRQ_TS, priv); 1648dd93eeeSUwe Kleine-König mc13xxx_unlock(priv->mc13xxx); 165d201fd5dSSascha Hauer 166d201fd5dSSascha Hauer cancel_delayed_work_sync(&priv->work); 167d201fd5dSSascha Hauer } 168d201fd5dSSascha Hauer 169d201fd5dSSascha Hauer static int __init mc13783_ts_probe(struct platform_device *pdev) 170d201fd5dSSascha Hauer { 171d201fd5dSSascha Hauer struct mc13783_ts_priv *priv; 172d201fd5dSSascha Hauer struct input_dev *idev; 173d201fd5dSSascha Hauer int ret = -ENOMEM; 174d201fd5dSSascha Hauer 175d201fd5dSSascha Hauer priv = kzalloc(sizeof(*priv), GFP_KERNEL); 176d201fd5dSSascha Hauer idev = input_allocate_device(); 177d201fd5dSSascha Hauer if (!priv || !idev) 178d201fd5dSSascha Hauer goto err_free_mem; 179d201fd5dSSascha Hauer 180d201fd5dSSascha Hauer INIT_DELAYED_WORK(&priv->work, mc13783_ts_work); 1818dd93eeeSUwe Kleine-König priv->mc13xxx = dev_get_drvdata(pdev->dev.parent); 182d201fd5dSSascha Hauer priv->idev = idev; 1831039d762SMichael Thalmeier priv->touch = dev_get_platdata(&pdev->dev); 1841039d762SMichael Thalmeier if (!priv->touch) { 1851039d762SMichael Thalmeier dev_err(&pdev->dev, "missing platform data\n"); 1861039d762SMichael Thalmeier ret = -ENODEV; 1871039d762SMichael Thalmeier goto err_free_mem; 1881039d762SMichael Thalmeier } 189d201fd5dSSascha Hauer 190d201fd5dSSascha Hauer idev->name = MC13783_TS_NAME; 191d201fd5dSSascha Hauer idev->dev.parent = &pdev->dev; 192d201fd5dSSascha Hauer 193d201fd5dSSascha Hauer idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 194d201fd5dSSascha Hauer idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 195d201fd5dSSascha Hauer input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0); 196d201fd5dSSascha Hauer input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0); 197d201fd5dSSascha Hauer input_set_abs_params(idev, ABS_PRESSURE, 0, 0xfff, 0, 0); 198d201fd5dSSascha Hauer 199d201fd5dSSascha Hauer idev->open = mc13783_ts_open; 200d201fd5dSSascha Hauer idev->close = mc13783_ts_close; 201d201fd5dSSascha Hauer 202d201fd5dSSascha Hauer input_set_drvdata(idev, priv); 203d201fd5dSSascha Hauer 204d201fd5dSSascha Hauer ret = input_register_device(priv->idev); 205d201fd5dSSascha Hauer if (ret) { 206d201fd5dSSascha Hauer dev_err(&pdev->dev, 207d201fd5dSSascha Hauer "register input device failed with %d\n", ret); 20862e51475SBhaktipriya Shridhar goto err_free_mem; 209d201fd5dSSascha Hauer } 210d201fd5dSSascha Hauer 211d201fd5dSSascha Hauer platform_set_drvdata(pdev, priv); 212d201fd5dSSascha Hauer return 0; 213d201fd5dSSascha Hauer 214d201fd5dSSascha Hauer err_free_mem: 215d201fd5dSSascha Hauer input_free_device(idev); 216d201fd5dSSascha Hauer kfree(priv); 217d201fd5dSSascha Hauer return ret; 218d201fd5dSSascha Hauer } 219d201fd5dSSascha Hauer 220e2619cf7SBill Pemberton static int mc13783_ts_remove(struct platform_device *pdev) 221d201fd5dSSascha Hauer { 222d201fd5dSSascha Hauer struct mc13783_ts_priv *priv = platform_get_drvdata(pdev); 223d201fd5dSSascha Hauer 224d201fd5dSSascha Hauer input_unregister_device(priv->idev); 225d201fd5dSSascha Hauer kfree(priv); 226d201fd5dSSascha Hauer 227d201fd5dSSascha Hauer return 0; 228d201fd5dSSascha Hauer } 229d201fd5dSSascha Hauer 230d201fd5dSSascha Hauer static struct platform_driver mc13783_ts_driver = { 2311cb0aa88SBill Pemberton .remove = mc13783_ts_remove, 232d201fd5dSSascha Hauer .driver = { 233d201fd5dSSascha Hauer .name = MC13783_TS_NAME, 234d201fd5dSSascha Hauer }, 235d201fd5dSSascha Hauer }; 236d3d25808SDmitry Torokhov 23738a46eb8SFabio Porcedda module_platform_driver_probe(mc13783_ts_driver, mc13783_ts_probe); 238d201fd5dSSascha Hauer 239d201fd5dSSascha Hauer MODULE_DESCRIPTION("MC13783 input touchscreen driver"); 240d201fd5dSSascha Hauer MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); 241d201fd5dSSascha Hauer MODULE_LICENSE("GPL v2"); 242d201fd5dSSascha Hauer MODULE_ALIAS("platform:" MC13783_TS_NAME); 243