162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for the Freescale Semiconductor MC13783 touchscreen.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
662306a36Sopenharmony_ci * Copyright (C) 2009 Sascha Hauer, Pengutronix
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Initial development of this code was funded by
962306a36Sopenharmony_ci * Phytec Messtechnik GmbH, http://www.phytec.de/
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci#include <linux/mfd/mc13783.h>
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/input.h>
1662306a36Sopenharmony_ci#include <linux/sched.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/init.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define MC13783_TS_NAME	"mc13783-ts"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define DEFAULT_SAMPLE_TOLERANCE 300
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic unsigned int sample_tolerance = DEFAULT_SAMPLE_TOLERANCE;
2562306a36Sopenharmony_cimodule_param(sample_tolerance, uint, S_IRUGO | S_IWUSR);
2662306a36Sopenharmony_ciMODULE_PARM_DESC(sample_tolerance,
2762306a36Sopenharmony_ci		"If the minimal and maximal value read out for one axis (out "
2862306a36Sopenharmony_ci		"of three) differ by this value (default: "
2962306a36Sopenharmony_ci		__stringify(DEFAULT_SAMPLE_TOLERANCE) ") or more, the reading "
3062306a36Sopenharmony_ci		"is supposed to be wrong and is discarded.  Set to 0 to "
3162306a36Sopenharmony_ci		"disable this check.");
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct mc13783_ts_priv {
3462306a36Sopenharmony_ci	struct input_dev *idev;
3562306a36Sopenharmony_ci	struct mc13xxx *mc13xxx;
3662306a36Sopenharmony_ci	struct delayed_work work;
3762306a36Sopenharmony_ci	unsigned int sample[4];
3862306a36Sopenharmony_ci	struct mc13xxx_ts_platform_data *touch;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic irqreturn_t mc13783_ts_handler(int irq, void *data)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct mc13783_ts_priv *priv = data;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	mc13xxx_irq_ack(priv->mc13xxx, irq);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	/*
4862306a36Sopenharmony_ci	 * Kick off reading coordinates. Note that if work happens already
4962306a36Sopenharmony_ci	 * be queued for future execution (it rearms itself) it will not
5062306a36Sopenharmony_ci	 * be rescheduled for immediate execution here. However the rearm
5162306a36Sopenharmony_ci	 * delay is HZ / 50 which is acceptable.
5262306a36Sopenharmony_ci	 */
5362306a36Sopenharmony_ci	schedule_delayed_work(&priv->work, 0);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return IRQ_HANDLED;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define sort3(a0, a1, a2) ({						\
5962306a36Sopenharmony_ci		if (a0 > a1)						\
6062306a36Sopenharmony_ci			swap(a0, a1);					\
6162306a36Sopenharmony_ci		if (a1 > a2)						\
6262306a36Sopenharmony_ci			swap(a1, a2);					\
6362306a36Sopenharmony_ci		if (a0 > a1)						\
6462306a36Sopenharmony_ci			swap(a0, a1);					\
6562306a36Sopenharmony_ci		})
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic void mc13783_ts_report_sample(struct mc13783_ts_priv *priv)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct input_dev *idev = priv->idev;
7062306a36Sopenharmony_ci	int x0, x1, x2, y0, y1, y2;
7162306a36Sopenharmony_ci	int cr0, cr1;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/*
7462306a36Sopenharmony_ci	 * the values are 10-bit wide only, but the two least significant
7562306a36Sopenharmony_ci	 * bits are for future 12 bit use and reading yields 0
7662306a36Sopenharmony_ci	 */
7762306a36Sopenharmony_ci	x0 = priv->sample[0] & 0xfff;
7862306a36Sopenharmony_ci	x1 = priv->sample[1] & 0xfff;
7962306a36Sopenharmony_ci	x2 = priv->sample[2] & 0xfff;
8062306a36Sopenharmony_ci	y0 = priv->sample[3] & 0xfff;
8162306a36Sopenharmony_ci	y1 = (priv->sample[0] >> 12) & 0xfff;
8262306a36Sopenharmony_ci	y2 = (priv->sample[1] >> 12) & 0xfff;
8362306a36Sopenharmony_ci	cr0 = (priv->sample[2] >> 12) & 0xfff;
8462306a36Sopenharmony_ci	cr1 = (priv->sample[3] >> 12) & 0xfff;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	dev_dbg(&idev->dev,
8762306a36Sopenharmony_ci		"x: (% 4d,% 4d,% 4d) y: (% 4d, % 4d,% 4d) cr: (% 4d, % 4d)\n",
8862306a36Sopenharmony_ci		x0, x1, x2, y0, y1, y2, cr0, cr1);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	sort3(x0, x1, x2);
9162306a36Sopenharmony_ci	sort3(y0, y1, y2);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	cr0 = (cr0 + cr1) / 2;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (!cr0 || !sample_tolerance ||
9662306a36Sopenharmony_ci			(x2 - x0 < sample_tolerance &&
9762306a36Sopenharmony_ci			 y2 - y0 < sample_tolerance)) {
9862306a36Sopenharmony_ci		/* report the median coordinate and average pressure */
9962306a36Sopenharmony_ci		if (cr0) {
10062306a36Sopenharmony_ci			input_report_abs(idev, ABS_X, x1);
10162306a36Sopenharmony_ci			input_report_abs(idev, ABS_Y, y1);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci			dev_dbg(&idev->dev, "report (%d, %d, %d)\n",
10462306a36Sopenharmony_ci					x1, y1, 0x1000 - cr0);
10562306a36Sopenharmony_ci			schedule_delayed_work(&priv->work, HZ / 50);
10662306a36Sopenharmony_ci		} else {
10762306a36Sopenharmony_ci			dev_dbg(&idev->dev, "report release\n");
10862306a36Sopenharmony_ci		}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		input_report_abs(idev, ABS_PRESSURE,
11162306a36Sopenharmony_ci				cr0 ? 0x1000 - cr0 : cr0);
11262306a36Sopenharmony_ci		input_report_key(idev, BTN_TOUCH, cr0);
11362306a36Sopenharmony_ci		input_sync(idev);
11462306a36Sopenharmony_ci	} else {
11562306a36Sopenharmony_ci		dev_dbg(&idev->dev, "discard event\n");
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic void mc13783_ts_work(struct work_struct *work)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	struct mc13783_ts_priv *priv =
12262306a36Sopenharmony_ci		container_of(work, struct mc13783_ts_priv, work.work);
12362306a36Sopenharmony_ci	unsigned int mode = MC13XXX_ADC_MODE_TS;
12462306a36Sopenharmony_ci	unsigned int channel = 12;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (mc13xxx_adc_do_conversion(priv->mc13xxx,
12762306a36Sopenharmony_ci				mode, channel,
12862306a36Sopenharmony_ci				priv->touch->ato, priv->touch->atox,
12962306a36Sopenharmony_ci				priv->sample) == 0)
13062306a36Sopenharmony_ci		mc13783_ts_report_sample(priv);
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic int mc13783_ts_open(struct input_dev *dev)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	struct mc13783_ts_priv *priv = input_get_drvdata(dev);
13662306a36Sopenharmony_ci	int ret;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	mc13xxx_lock(priv->mc13xxx);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	mc13xxx_irq_ack(priv->mc13xxx, MC13XXX_IRQ_TS);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	ret = mc13xxx_irq_request(priv->mc13xxx, MC13XXX_IRQ_TS,
14362306a36Sopenharmony_ci		mc13783_ts_handler, MC13783_TS_NAME, priv);
14462306a36Sopenharmony_ci	if (ret)
14562306a36Sopenharmony_ci		goto out;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	ret = mc13xxx_reg_rmw(priv->mc13xxx, MC13XXX_ADC0,
14862306a36Sopenharmony_ci			MC13XXX_ADC0_TSMOD_MASK, MC13XXX_ADC0_TSMOD0);
14962306a36Sopenharmony_ci	if (ret)
15062306a36Sopenharmony_ci		mc13xxx_irq_free(priv->mc13xxx, MC13XXX_IRQ_TS, priv);
15162306a36Sopenharmony_ciout:
15262306a36Sopenharmony_ci	mc13xxx_unlock(priv->mc13xxx);
15362306a36Sopenharmony_ci	return ret;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic void mc13783_ts_close(struct input_dev *dev)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	struct mc13783_ts_priv *priv = input_get_drvdata(dev);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	mc13xxx_lock(priv->mc13xxx);
16162306a36Sopenharmony_ci	mc13xxx_reg_rmw(priv->mc13xxx, MC13XXX_ADC0,
16262306a36Sopenharmony_ci			MC13XXX_ADC0_TSMOD_MASK, 0);
16362306a36Sopenharmony_ci	mc13xxx_irq_free(priv->mc13xxx, MC13XXX_IRQ_TS, priv);
16462306a36Sopenharmony_ci	mc13xxx_unlock(priv->mc13xxx);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	cancel_delayed_work_sync(&priv->work);
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int __init mc13783_ts_probe(struct platform_device *pdev)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct mc13783_ts_priv *priv;
17262306a36Sopenharmony_ci	struct input_dev *idev;
17362306a36Sopenharmony_ci	int ret = -ENOMEM;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
17662306a36Sopenharmony_ci	idev = input_allocate_device();
17762306a36Sopenharmony_ci	if (!priv || !idev)
17862306a36Sopenharmony_ci		goto err_free_mem;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	INIT_DELAYED_WORK(&priv->work, mc13783_ts_work);
18162306a36Sopenharmony_ci	priv->mc13xxx = dev_get_drvdata(pdev->dev.parent);
18262306a36Sopenharmony_ci	priv->idev = idev;
18362306a36Sopenharmony_ci	priv->touch = dev_get_platdata(&pdev->dev);
18462306a36Sopenharmony_ci	if (!priv->touch) {
18562306a36Sopenharmony_ci		dev_err(&pdev->dev, "missing platform data\n");
18662306a36Sopenharmony_ci		ret = -ENODEV;
18762306a36Sopenharmony_ci		goto err_free_mem;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	idev->name = MC13783_TS_NAME;
19162306a36Sopenharmony_ci	idev->dev.parent = &pdev->dev;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
19462306a36Sopenharmony_ci	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
19562306a36Sopenharmony_ci	input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0);
19662306a36Sopenharmony_ci	input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0);
19762306a36Sopenharmony_ci	input_set_abs_params(idev, ABS_PRESSURE, 0, 0xfff, 0, 0);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	idev->open = mc13783_ts_open;
20062306a36Sopenharmony_ci	idev->close = mc13783_ts_close;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	input_set_drvdata(idev, priv);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	ret = input_register_device(priv->idev);
20562306a36Sopenharmony_ci	if (ret) {
20662306a36Sopenharmony_ci		dev_err(&pdev->dev,
20762306a36Sopenharmony_ci			"register input device failed with %d\n", ret);
20862306a36Sopenharmony_ci		goto err_free_mem;
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
21262306a36Sopenharmony_ci	return 0;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cierr_free_mem:
21562306a36Sopenharmony_ci	input_free_device(idev);
21662306a36Sopenharmony_ci	kfree(priv);
21762306a36Sopenharmony_ci	return ret;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic int mc13783_ts_remove(struct platform_device *pdev)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct mc13783_ts_priv *priv = platform_get_drvdata(pdev);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	input_unregister_device(priv->idev);
22562306a36Sopenharmony_ci	kfree(priv);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	return 0;
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic struct platform_driver mc13783_ts_driver = {
23162306a36Sopenharmony_ci	.remove		= mc13783_ts_remove,
23262306a36Sopenharmony_ci	.driver		= {
23362306a36Sopenharmony_ci		.name	= MC13783_TS_NAME,
23462306a36Sopenharmony_ci	},
23562306a36Sopenharmony_ci};
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cimodule_platform_driver_probe(mc13783_ts_driver, mc13783_ts_probe);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ciMODULE_DESCRIPTION("MC13783 input touchscreen driver");
24062306a36Sopenharmony_ciMODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
24162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
24262306a36Sopenharmony_ciMODULE_ALIAS("platform:" MC13783_TS_NAME);
243