From db88172eac3d336d043a113efab917720a62af55 Mon Sep 17 00:00:00 2001
From: Tobias Poganiuch <tobias.poganiuch@seco.com>
Date: Mon, 21 Aug 2023 15:19:58 +0200
Subject: [PATCH] driver:ucb1400: Added reader/poll mode

Added a new mode, that polls the touchscreen periodically instead of using
the IRQ to trigger a read.

BCS 746-001452
---
 drivers/input/touchscreen/ucb1400_ts.c | 151 +++++++++++++++++--------
 drivers/mfd/ucb1400_core.c             |   4 +-
 include/linux/ucb1400.h                |   5 +
 3 files changed, 111 insertions(+), 49 deletions(-)

diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c
index d0c3b11640626..32da555741439 100644
--- a/drivers/input/touchscreen/ucb1400_ts.c
+++ b/drivers/input/touchscreen/ucb1400_ts.c
@@ -202,27 +202,13 @@ static void ucb1400_clear_pending_irq(struct ucb1400_ts *ucb)
 			"ucb1400: unexpected IE_STATUS = %#x\n", isr);
 }
 
-/*
- * A restriction with interrupts exists when using the ucb1400, as
- * the codec read/write routines may sleep while waiting for codec
- * access completion and uses semaphores for access control to the
- * AC97 bus. Therefore the driver is forced to use threaded interrupt
- * handler.
- */
-static irqreturn_t ucb1400_irq(int irqnr, void *devid)
+static void ucb1400_poll_touch(struct ucb1400_ts *ucb, struct touch_data *data)
 {
-	struct ucb1400_ts *ucb = devid;
 	bool penup, discard;
-	struct touch_data data = {0};
 	int ret;
 
 	int hys_down = SECO_INF_DOWN_HYSTERESIS;
 
-	if (unlikely(irqnr != ucb->irq))
-		return IRQ_NONE;
-
-	ucb1400_clear_pending_irq(ucb);
-
 	/* Start with a small delay before checking pendown state */
 	msleep(UCB1400_TS_POLL_PERIOD);
 
@@ -235,14 +221,14 @@ static irqreturn_t ucb1400_irq(int irqnr, void *devid)
 
 		if (!penup) {
 			ucb1400_adc_enable(ucb->ac97);
-			data.x = ucb1400_ts_read_xpos(ucb);
-			data.y = ucb1400_ts_read_ypos(ucb);
-			data.p = ucb1400_ts_read_pressure(ucb);
+			data->x = ucb1400_ts_read_xpos(ucb);
+			data->y = ucb1400_ts_read_ypos(ucb);
+			data->p = ucb1400_ts_read_pressure(ucb);
 			ucb1400_adc_disable(ucb->ac97);
 
-			if ((SECO_INVALID_VALUE == data.x) ||
-			    (SECO_INVALID_VALUE == data.y) ||
-			    (SECO_INVALID_VALUE == data.p)) {
+			if ((SECO_INVALID_VALUE == data->x) ||
+			    (SECO_INVALID_VALUE == data->y) ||
+			    (SECO_INVALID_VALUE == data->p)) {
 				discard = true;
 			}
 
@@ -254,7 +240,7 @@ static irqreturn_t ucb1400_irq(int irqnr, void *devid)
 			 * needs major rework, but this work improves touch
 			 * performance already very much.
 			 */
-			if (data.p < (UCB1400_FILTER_MAX_ADC_VALUE / 3))
+			if (data->p < (UCB1400_FILTER_MAX_ADC_VALUE / 3))
 				discard = true;
 		}
 
@@ -277,10 +263,10 @@ static irqreturn_t ucb1400_irq(int irqnr, void *devid)
 		ucb->filter_data.pen_up_start = jiffies_to_msecs(jiffies);
 
 		if (ucb->use_median_filter)
-			seco_median_filter(&ucb->ts_idev->dev, &ucb->filter_data, &data);
+			seco_median_filter(&ucb->ts_idev->dev, &ucb->filter_data, data);
 
 		if (ucb->use_low_pass_filter)
-			seco_low_pass_filter(&ucb->ts_idev->dev, &ucb->filter_data, &data);
+			seco_low_pass_filter(&ucb->ts_idev->dev, &ucb->filter_data, data);
 
 		if (hys_down) {
 			hys_down--;
@@ -289,7 +275,7 @@ static irqreturn_t ucb1400_irq(int irqnr, void *devid)
 			continue;
 		}
 
-		ucb1400_ts_report_event(ucb->ts_idev, &data);
+		ucb1400_ts_report_event(ucb->ts_idev, data);
 
 		wait_event_timeout(ucb->ts_wait, ucb->stopped,
 				   msecs_to_jiffies(UCB1400_TS_POLL_PERIOD));
@@ -304,6 +290,25 @@ static irqreturn_t ucb1400_irq(int irqnr, void *devid)
 	 * later on.
 	 */
 	seco_init_filter(&ucb->ts_idev->dev, &ucb->filter_data);
+}
+
+/*
+ * A restriction with interrupts exists when using the ucb1400, as
+ * the codec read/write routines may sleep while waiting for codec
+ * access completion and uses semaphores for access control to the
+ * AC97 bus. Therefore the driver is forced to use threaded interrupt
+ * handler.
+ */
+static irqreturn_t ucb1400_irq(int irqnr, void *devid)
+{
+	struct ucb1400_ts *ucb = devid;
+	struct touch_data data = {0};
+
+	if (unlikely(irqnr != ucb->irq))
+		return IRQ_NONE;
+
+	ucb1400_clear_pending_irq(ucb);
+	ucb1400_poll_touch(ucb, &data);
 
 	if (!ucb->stopped) {
 		/* Switch back to interrupt mode. */
@@ -314,15 +319,38 @@ static irqreturn_t ucb1400_irq(int irqnr, void *devid)
 	return IRQ_HANDLED;
 }
 
+static void ucb1400_ts_reader(struct work_struct *work)
+{
+	struct ucb1400_ts *ucb = container_of(work, struct ucb1400_ts, ts_reader.work);
+	struct touch_data data = {0};
+
+	ucb1400_poll_touch(ucb, &data);
+
+	if (!ucb->stopped) {
+		/* Enable pen-down detection */
+		ucb1400_ts_mode_int(ucb);
+		queue_delayed_work(ucb->ts_workq, &ucb->ts_reader,
+				   UCB1400_READER_INTERVAL);
+	}
+}
+
 static void ucb1400_ts_stop(struct ucb1400_ts *ucb)
 {
 	/* Signal IRQ thread to stop polling and disable the handler. */
 	ucb->stopped = true;
 	mb();
-	wake_up(&ucb->ts_wait);
-	disable_irq(ucb->irq);
 
-	ucb1400_ts_irq_disable(ucb);
+	if (ucb->irq) {
+		wake_up(&ucb->ts_wait);
+		disable_irq(ucb->irq);
+		ucb1400_ts_irq_disable(ucb);
+	} else {
+		cancel_delayed_work_sync(&ucb->ts_reader);
+		destroy_workqueue(ucb->ts_workq);
+
+		dev_dbg(&ucb->ts_idev->dev, "Disabled ts_reader");
+	}
+
 	ucb1400_reg_write(ucb->ac97, UCB_TS_CR, 0);
 }
 
@@ -333,10 +361,30 @@ static void ucb1400_ts_start(struct ucb1400_ts *ucb)
 	ucb->stopped = false;
 	mb();
 
+	/*
+	 * The ucb1400_ts_mode_int function
+	 * enables the pen-down detection,
+	 * which is needed for both operation
+	 * modes.
+	 */
 	ucb1400_ts_mode_int(ucb);
-	ucb1400_ts_irq_enable(ucb);
 
-	enable_irq(ucb->irq);
+	if (ucb->irq) {
+		ucb1400_ts_irq_enable(ucb);
+		enable_irq(ucb->irq);
+	} else {
+		dev_dbg(&ucb->ts_idev->dev, "Init ts_reader");
+
+		ucb->ts_workq = alloc_ordered_workqueue("kucb1400", 0);
+		if (ucb->ts_workq) {
+			INIT_DELAYED_WORK(&ucb->ts_reader, ucb1400_ts_reader);
+			queue_delayed_work(ucb->ts_workq, &ucb->ts_reader,
+					   UCB1400_READER_INTERVAL);
+		} else {
+			dev_err(&ucb->ts_idev->dev,
+				"Failed to create workqueue");
+		}
+	}
 }
 
 static int ucb1400_ts_open(struct input_dev *idev)
@@ -461,24 +509,28 @@ static int ucb1400_ts_probe(struct platform_device *pdev)
 	input_set_abs_params(ucb->ts_idev, ABS_Y, 0, y_res, 0, 0);
 	input_set_abs_params(ucb->ts_idev, ABS_PRESSURE, 0, UCB1400_FILTER_MAX_ADC_VALUE, 0, 0);
 
-	ucb1400_ts_stop(ucb);
+	if (ucb->irq) {
+		ucb1400_ts_stop(ucb);
 
-	error = request_threaded_irq(ucb->irq, NULL, ucb1400_irq,
-				     IRQF_TRIGGER_RISING | IRQF_ONESHOT,
-				     "UCB1400", ucb);
-	if (error) {
-		dev_err(&pdev->dev,
-			"unable to grab irq%d: %d\n", ucb->irq, error);
-		goto err_free_devs;
-	}
+		error = request_threaded_irq(ucb->irq, NULL, ucb1400_irq,
+					     IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					     "UCB1400", ucb);
+		if (error) {
+			dev_err(&pdev->dev,
+				"unable to grab irq%d: %d\n", ucb->irq, error);
+			goto err_free_devs;
+		}
 
-	/*
-	 * request_threaded_irq() also enables the IRQ. We disable it again
-	 * immediately to balance enable/disable calls.
-	 * Note: We already called ucb1400_ts_stop() just before, which has
-	 * taken care of IRQ disabling in the UCB1400 itself.
-	 */
-	disable_irq(ucb->irq);
+		/*
+		 * request_threaded_irq() also enables the IRQ. We disable it again
+		 * immediately to balance enable/disable calls.
+		 * Note: We already called ucb1400_ts_stop() just before, which has
+		 * taken care of IRQ disabling in the UCB1400 itself.
+		 */
+		disable_irq(ucb->irq);
+	} else {
+		dev_info(&pdev->dev, "No IRQ configured");
+	}
 
 	error = input_register_device(ucb->ts_idev);
 	if (error)
@@ -487,7 +539,8 @@ static int ucb1400_ts_probe(struct platform_device *pdev)
 	return 0;
 
 err_free_irq:
-	free_irq(ucb->irq, ucb);
+	if (ucb->irq)
+		free_irq(ucb->irq, ucb);
 err_free_devs:
 	input_free_device(ucb->ts_idev);
 err:
@@ -498,7 +551,9 @@ static int ucb1400_ts_remove(struct platform_device *pdev)
 {
 	struct ucb1400_ts *ucb = dev_get_platdata(&pdev->dev);
 
-	free_irq(ucb->irq, ucb);
+	if (ucb->irq)
+		free_irq(ucb->irq, ucb);
+
 	input_unregister_device(ucb->ts_idev);
 
 	return 0;
diff --git a/drivers/mfd/ucb1400_core.c b/drivers/mfd/ucb1400_core.c
index 0894831937fc1..a8ae575389010 100644
--- a/drivers/mfd/ucb1400_core.c
+++ b/drivers/mfd/ucb1400_core.c
@@ -135,8 +135,10 @@ static int ucb1400_core_probe(struct device *dev)
 	if( ucb_ts.irq < 0 && pdata != NULL && pdata->irq >= 0)
 		ucb_ts.irq = pdata->irq;
 
-	if( ucb_ts.irq < 0 )
+	if( ucb_ts.irq < 0 ) {
 		dev_warn(dev, "Failed to get irq: %d", ucb_ts.irq);
+		ucb_ts.irq = 0;
+	}
 
 	if (of_find_property(dev->of_node, "seco,use-infinite-mode", NULL))
 		ucb_ts.use_infinite_mode = true;
diff --git a/include/linux/ucb1400.h b/include/linux/ucb1400.h
index 32ca9e935fb5d..71b3d60ed0f8a 100644
--- a/include/linux/ucb1400.h
+++ b/include/linux/ucb1400.h
@@ -86,6 +86,9 @@
 
 #define UCB1400_FILTER_MAX_ADC_VALUE	0x03FF
 
+/* Interval ts_reader in ms */
+#define UCB1400_READER_INTERVAL	10
+
 struct ucb1400_gpio {
 	struct gpio_chip	gc;
 	struct snd_ac97		*ac97;
@@ -106,6 +109,8 @@ struct ucb1400_ts {
 	bool 			use_low_pass_filter;
 	unsigned		pen_up_debounce;
 	struct seco_filter_data	filter_data;
+	struct delayed_work	ts_reader;
+	struct workqueue_struct	*ts_workq;
 };
 
 struct ucb1400 {
-- 
GitLab