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");
+