ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/input/touchscreen/asr_tsc.c b/marvell/linux/drivers/input/touchscreen/asr_tsc.c
new file mode 100644
index 0000000..9cd7944
--- /dev/null
+++ b/marvell/linux/drivers/input/touchscreen/asr_tsc.c
@@ -0,0 +1,540 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ASR built-in touchscreen driver
+ *
+ * Copyright (C) 2023 ASR
+ */
+
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/timer.h>
+
+/*
+ * Touchscreen controller register offsets
+ */
+#define ASR_TSCR		0x0
+#define ASR_TSSR		0x4
+#define ASR_XYDR(x)	(0x8 + (x) * 0x4)
+
+#define ASR_TSSR_DOWN 0
+#define ASR_TSSR_UP   1
+
+#define ASR_TSC_MIN_XY_VAL			0
+#define ASR_TSC_MAX_XY_VAL			4095
+#define ASR_TSC_DOT_CNT_MAX			8
+
+#define MOD_NAME "ts-asr"
+
+#define tsc_readl(dev, reg) \
+	__raw_readl((dev)->tsc_base + (reg))
+#define tsc_writel(dev, reg, val) \
+	__raw_writel((val), (dev)->tsc_base + (reg))
+
+struct asr_tp_info {
+	int x;
+	int y;
+	int last_x;
+	int last_y;
+};
+
+struct asr_tsc {
+	struct timer_list move_timer;
+	struct input_dev *dev;
+	void __iomem *tsc_base;
+	int rdy_irq;
+	int up_irq;
+	int down_irq;
+	struct clk *clk;
+	struct asr_tp_info tp;
+	int dummpy_points;
+};
+
+#define ASR_TSC_MOVE_TIMEOUT	10	/* ms */
+#define ASR_TSC_DUMMY_POINTS	10
+#define ASR_TSC_TP_MARGIN			10
+
+enum {
+	TSC_MODE_IDLE = 0,
+	TSC_MODE_WAIT_PRESSDOWN,
+	TSC_MODE_WAIT_RISEUP,
+	TSC_MODE_WAIT_AUTOMEASURE,
+};
+static void asr_ts_set_mode(struct asr_tsc *tsc, int mode);
+
+static void asr_ts_movement_detect(struct timer_list *t)
+{
+	struct asr_tsc *tsc = from_timer(tsc, t, move_timer);
+	u32 val;
+	val = tsc_readl(tsc, ASR_TSSR);
+	if (val & ASR_TSSR_UP) {
+		asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
+		return;
+	}
+
+	asr_ts_set_mode(tsc, TSC_MODE_WAIT_AUTOMEASURE);
+}
+
+static void asr_ts_adc_init(void)
+{
+	void __iomem* apb_spare;
+	u32 val;
+
+	apb_spare = ioremap(0xD4090000, SZ_4K);
+	if (!apb_spare) {
+		pr_err("%s: error to ioremap apb_spare base\n", __func__);
+		return;
+	}
+
+	val = readl(apb_spare + 0x12c);
+	val |= (1 << 8) | (1 << 20) | (1 << 22) | (0x4 << 24);
+	writel(val, apb_spare + 0x12c);
+	iounmap(apb_spare);
+}
+
+static void asr_ts_set_mode(struct asr_tsc *tsc, int mode)
+{
+	u32 val;
+	val = tsc_readl(tsc, ASR_TSCR);
+	val &= ~(0x3 << 1);
+	switch (mode) {
+	case TSC_MODE_WAIT_PRESSDOWN:
+		val &= ~((1 << 8) | (1 << 7));
+		val |= (1 << 6) | (1 << 9);
+		break;
+	case TSC_MODE_WAIT_RISEUP:
+		val &= ~((1 << 8) | (1 << 6));
+		val |= (1 << 7) | (1 << 9);
+		break;
+	case TSC_MODE_WAIT_AUTOMEASURE:
+		val &= ~((1 << 6) | (1 << 7) | (1 << 9));
+		val |= (1 << 8);
+		break;
+	}
+	val |= (mode << 1);
+	tsc_writel(tsc, ASR_TSCR, val);
+}
+
+static void asr_ts_measure(struct asr_tsc *tsc)
+{
+	int i;
+	u32 val, meas_x, meas_y, sum_x = 0, sum_y = 0;
+	u16 min_x, max_x, min_y, max_y;
+	u16 x[ASR_TSC_DOT_CNT_MAX] = {0};
+	u16 y[ASR_TSC_DOT_CNT_MAX] = {0};
+	struct asr_tp_info *tp = (struct asr_tp_info *)&tsc->tp;
+
+	for (i = 0; i < ASR_TSC_DOT_CNT_MAX; i++) {
+		val = tsc_readl(tsc, ASR_XYDR(i));
+		x[i] = val & 0xffff;
+		sum_x += x[i];
+		if (i == 0 ) {
+			min_x = x[0];
+			max_x = x[0];
+		}
+		if (min_x > x[i])
+			min_x = x[i];
+		if (max_x < x[i])
+			max_x = x[i];
+
+		y[i] = (val >> 16) & 0xffff;
+		sum_y += y[i];
+
+		if (i == 0 ) {
+			min_y = y[0];
+			max_y = y[0];
+		}
+		if (min_y > y[i])
+			min_y = y[i];
+		if(max_y < y[i])
+			max_y = y[i];
+		pr_debug("	%s: [%d] (%d, %d)\n", __func__, i, x[i], y[i]);
+	}
+
+	meas_x = (sum_x - max_x - min_x) / (ASR_TSC_DOT_CNT_MAX - 2);
+	meas_y = (sum_y - max_y - min_y) / (ASR_TSC_DOT_CNT_MAX - 2);
+
+	pr_debug("meas_x: %d, meas_y: %d\n", meas_x, meas_y);
+	tp->x = meas_x;
+	tp->y = meas_y;
+}
+
+static irqreturn_t asr_ts_down_isr(int irq, void *dev_id)
+{
+	u32 val;
+	struct asr_tsc *tsc = (struct asr_tsc *)dev_id;
+	val = tsc_readl(tsc, ASR_TSSR);
+
+	if (val & (1 << 6)) {
+		val |= (1 << 6);
+		tsc_writel(tsc, ASR_TSSR, val);
+		if (val & ASR_TSSR_UP) {
+			asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
+			return IRQ_HANDLED;
+		}
+
+		tsc->dummpy_points = 1;
+		asr_ts_set_mode(tsc, TSC_MODE_WAIT_AUTOMEASURE);
+		pr_debug("%s: got down irq.\n", __func__);
+	} else {
+		pr_warn("%s: mismatch irq?\n", __func__);
+	}
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t asr_ts_rdy_isr(int irq, void *dev_id)
+{
+	u32 val;
+	int x, y;
+	struct asr_tsc *tsc = (struct asr_tsc *)dev_id;
+	struct input_dev *input = tsc->dev;
+
+	val = tsc_readl(tsc, ASR_TSSR);
+	if (val & (1 << 8)) {
+		val |= (1 << 8);
+		tsc_writel(tsc, ASR_TSSR, val);
+#if 0
+		if (val & ASR_TSSR_UP) {
+			asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
+			return IRQ_HANDLED;
+		}
+#endif
+		asr_ts_measure(tsc);
+		if (tsc->dummpy_points) {
+			tsc->dummpy_points++;
+			if (tsc->dummpy_points > ASR_TSC_DUMMY_POINTS) {
+				tsc->dummpy_points = 0;
+				x = tsc->tp.last_x = tsc->tp.x;
+				y = tsc->tp.last_y = tsc->tp.y;
+			} else
+				goto skip;
+		} else {
+			if (abs(tsc->tp.x - tsc->tp.last_x) < ASR_TSC_TP_MARGIN &&
+				  abs(tsc->tp.y - tsc->tp.last_y) < ASR_TSC_TP_MARGIN)
+					goto skip;
+		}
+
+		x = tsc->tp.last_x = tsc->tp.x;
+		y = tsc->tp.last_y = tsc->tp.y;
+
+		input_report_abs(input, ABS_X, x);
+		input_report_abs(input, ABS_Y, y);
+		input_report_key(input, BTN_TOUCH, 1);
+		input_sync(input);
+skip:
+		asr_ts_set_mode(tsc, TSC_MODE_WAIT_RISEUP);
+		mod_timer(&tsc->move_timer, jiffies + msecs_to_jiffies(ASR_TSC_MOVE_TIMEOUT));
+		pr_debug("%s: got rdy irq. 0x%x, (%d, %d)\n", __func__, val, tsc->tp.x, tsc->tp.y);
+	} else {
+		pr_warn("%s: mismatch irq?\n", __func__);
+	}
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t asr_ts_up_isr(int irq, void *dev_id)
+{
+	u32 val;
+	struct asr_tsc *tsc = (struct asr_tsc *)dev_id;
+	struct input_dev *input = tsc->dev;
+
+	val = tsc_readl(tsc, ASR_TSSR);
+	if (val & (1 << 7)) {
+		val |= (1 << 7);
+		tsc_writel(tsc, ASR_TSSR, val);
+		if ((val & 1) == ASR_TSSR_DOWN)
+			return IRQ_HANDLED;
+
+		pr_debug("%s: got up irq. (%d, %d)\n", __func__, tsc->tp.x, tsc->tp.y);
+		input_report_key(input, BTN_TOUCH, 0);
+		input_sync(input);
+		asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
+	} else {
+		pr_warn("%s: mismatch irq?\n", __func__);
+	}
+	return IRQ_HANDLED;
+}
+
+static void asr_stop_tsc(struct asr_tsc *tsc)
+{
+	tsc_writel(tsc, ASR_TSCR, 0);
+	clk_disable_unprepare(tsc->clk);
+}
+
+static int asr_setup_tsc(struct asr_tsc *tsc)
+{
+	u32 tmp;
+	int err;
+	asr_ts_adc_init();
+
+	err = clk_prepare_enable(tsc->clk);
+	if (err)
+		return err;
+
+	tmp = tsc_readl(tsc, ASR_TSCR);
+	tmp |= (1 << 0); 	/* enabled tsc */
+	tmp |= 0x7 << 16;	/* YD ADC channel slection */
+	tmp |= 0x6 << 11;	/* XL ADC channel slection */
+	tmp |= (1 << 8);	/* INT_RDY_EN */
+	tmp |= (0x20 << 24);
+	tmp |= (0 << 21); 	/* XL_PU_RES_SEL */
+	tmp |= ((ASR_TSC_DOT_CNT_MAX - 1) << 3); 	/* DOT_CNT */
+	tsc_writel(tsc, ASR_TSCR, tmp);
+	asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
+	return 0;
+}
+
+static int asr_ts_open(struct input_dev *dev)
+{
+	struct asr_tsc *tsc = input_get_drvdata(dev);
+	return asr_setup_tsc(tsc);
+}
+
+static void asr_ts_close(struct input_dev *dev)
+{
+	struct asr_tsc *tsc = input_get_drvdata(dev);
+	asr_stop_tsc(tsc);
+}
+
+static int asr_ts_probe(struct platform_device *pdev)
+{
+	struct asr_tsc *tsc;
+	struct input_dev *input;
+	struct resource *res;
+	resource_size_t size;
+	int irq0, irq1, irq2;
+	int error;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Can't get memory resource\n");
+		return -ENOENT;
+	}
+
+	irq0 = platform_get_irq(pdev, 0);
+	irq1 = platform_get_irq(pdev, 1);
+	irq2 = platform_get_irq(pdev, 2);
+	if (irq0 < 0 || irq1 < 0 || irq2 < 0) {
+		dev_err(&pdev->dev, "err irq:%d, %d, %d\n", irq0, irq1, irq2);
+		return -ENOENT;
+	}
+
+	tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
+	input = input_allocate_device();
+	if (!tsc || !input) {
+		dev_err(&pdev->dev, "failed allocating memory\n");
+		error = -ENOMEM;
+		goto err_free_mem;
+	}
+
+	tsc->dev = input;
+	tsc->down_irq = irq0;
+	tsc->rdy_irq = irq1;
+	tsc->up_irq = irq2;
+
+	size = resource_size(res);
+
+	if (!request_mem_region(res->start, size, pdev->name)) {
+		dev_err(&pdev->dev, "TSC registers are not free\n");
+		error = -EBUSY;
+		goto err_free_mem;
+	}
+
+	tsc->tsc_base = ioremap(res->start, size);
+	if (!tsc->tsc_base) {
+		dev_err(&pdev->dev, "Can't map memory\n");
+		error = -ENOMEM;
+		goto err_release_mem;
+	}
+
+	tsc->clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(tsc->clk)) {
+		dev_err(&pdev->dev, "failed getting clock\n");
+		error = PTR_ERR(tsc->clk);
+		goto err_unmap;
+	}
+
+	input->name = MOD_NAME;
+	input->phys = "asr/input0";
+	input->id.bustype = BUS_HOST;
+	input->id.vendor = 0x0001;
+	input->id.product = 0x0002;
+	input->id.version = 0x0100;
+	input->dev.parent = &pdev->dev;
+	input->open = asr_ts_open;
+	input->close = asr_ts_close;
+
+	__set_bit(EV_ABS, input->evbit);
+	__set_bit(ABS_X, input->absbit);
+	__set_bit(ABS_Y, input->absbit);
+	__set_bit(EV_SYN, input->evbit);
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(BTN_TOUCH, input->keybit);
+
+	input_set_abs_params(input, ABS_X, ASR_TSC_MIN_XY_VAL,
+			     ASR_TSC_MAX_XY_VAL, 0, 0);
+	input_set_abs_params(input, ABS_Y, ASR_TSC_MIN_XY_VAL,
+			     ASR_TSC_MAX_XY_VAL, 0, 0);
+
+	input_set_drvdata(input, tsc);
+
+	error = request_irq(tsc->down_irq, asr_ts_down_isr,
+			    IRQF_SHARED, pdev->name, tsc);
+	if (error) {
+		dev_err(&pdev->dev, "failed requesting down irq\n");
+		goto err_put_clock;
+	}
+
+	error = request_irq(tsc->rdy_irq, asr_ts_rdy_isr,
+			    IRQF_SHARED, pdev->name, tsc);
+	if (error) {
+		dev_err(&pdev->dev, "failed requesting rdy irq\n");
+		goto err_free_irq0;
+	}
+
+	error = request_irq(tsc->up_irq, asr_ts_up_isr,
+			    IRQF_SHARED, pdev->name, tsc);
+	if (error) {
+		dev_err(&pdev->dev, "failed requesting up irq\n");
+		goto err_free_irq1;
+	}
+
+	error = input_register_device(input);
+	if (error) {
+		dev_err(&pdev->dev, "failed registering input device\n");
+		goto err_free_irq2;
+	}
+
+	platform_set_drvdata(pdev, tsc);
+	device_init_wakeup(&pdev->dev, 1);
+	timer_setup(&tsc->move_timer, asr_ts_movement_detect, 0);
+	dev_info(&pdev->dev, "bringup ok.\n");
+	return 0;
+
+err_free_irq2:
+	free_irq(tsc->up_irq, tsc);
+err_free_irq1:
+	free_irq(tsc->rdy_irq, tsc);
+err_free_irq0:
+	free_irq(tsc->down_irq, tsc);
+err_put_clock:
+	clk_put(tsc->clk);
+err_unmap:
+	iounmap(tsc->tsc_base);
+err_release_mem:
+	release_mem_region(res->start, size);
+err_free_mem:
+	input_free_device(input);
+	kfree(tsc);
+
+	return error;
+}
+
+static int asr_ts_remove(struct platform_device *pdev)
+{
+	struct asr_tsc *tsc = platform_get_drvdata(pdev);
+	struct resource *res;
+
+	free_irq(tsc->down_irq, tsc);
+	free_irq(tsc->rdy_irq, tsc);
+	free_irq(tsc->up_irq, tsc);
+
+	input_unregister_device(tsc->dev);
+
+	clk_put(tsc->clk);
+
+	iounmap(tsc->tsc_base);
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(res->start, resource_size(res));
+
+	kfree(tsc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int asr_ts_suspend(struct device *dev)
+{
+	struct asr_tsc *tsc = dev_get_drvdata(dev);
+	struct input_dev *input = tsc->dev;
+
+	/*
+	 * Suspend and resume can be called when the device hasn't been
+	 * enabled. If there are no users that have the device open, then
+	 * avoid calling the TSC stop and start functions as the TSC
+	 * isn't yet clocked.
+	 */
+	mutex_lock(&input->mutex);
+
+	if (input->users) {
+		if (device_may_wakeup(dev)) {
+			enable_irq_wake(tsc->down_irq);
+			enable_irq_wake(tsc->rdy_irq);
+			enable_irq_wake(tsc->up_irq);
+		} else
+			asr_stop_tsc(tsc);
+	}
+
+	mutex_unlock(&input->mutex);
+
+	return 0;
+}
+
+static int asr_ts_resume(struct device *dev)
+{
+	struct asr_tsc *tsc = dev_get_drvdata(dev);
+	struct input_dev *input = tsc->dev;
+
+	mutex_lock(&input->mutex);
+
+	if (input->users) {
+		if (device_may_wakeup(dev)) {
+			disable_irq_wake(tsc->down_irq);
+			disable_irq_wake(tsc->rdy_irq);
+			disable_irq_wake(tsc->up_irq);
+		} else
+			asr_setup_tsc(tsc);
+	}
+
+	mutex_unlock(&input->mutex);
+
+	return 0;
+}
+
+static const struct dev_pm_ops asr_ts_pm_ops = {
+	.suspend	= asr_ts_suspend,
+	.resume		= asr_ts_resume,
+};
+#define ASR_TS_PM_OPS (&asr_ts_pm_ops)
+#else
+#define ASR_TS_PM_OPS NULL
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id asr_tsc_of_match[] = {
+	{ .compatible = "asr,tsc", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, asr_tsc_of_match);
+#endif
+
+static struct platform_driver asr_ts_driver = {
+	.probe		= asr_ts_probe,
+	.remove		= asr_ts_remove,
+	.driver		= {
+		.name	= MOD_NAME,
+		.pm	= ASR_TS_PM_OPS,
+		.of_match_table = of_match_ptr(asr_tsc_of_match),
+	},
+};
+module_platform_driver(asr_ts_driver);
+
+MODULE_AUTHOR("Mark Zhang <markzhang@asrmicro.com>");
+MODULE_DESCRIPTION("ASR TSC Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:asr_ts");
+