summaryrefslogtreecommitdiff
path: root/touch_bus_negotiator.c
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2021-01-28 21:25:27 +0800
committerTai Kuo <taikuo@google.com>2021-02-19 00:54:23 +0000
commitd14f349b5856ae60d32f233c1eb3acd972b32ff9 (patch)
treea45dd138cf5b9c8e92a8a597d0380ae120b9c518 /touch_bus_negotiator.c
parentce1ac162d224c42080a58180e93c05abc9981937 (diff)
downloadcommon-d14f349b5856ae60d32f233c1eb3acd972b32ff9.tar.gz
touch: common: Add touch bus negotiator module
Touch bus negotiator is used for bus switching handshaking. Driver need to ensure the bus ownership before any spi transfer. Bug: 170687957 Bug: 173331067 Test: gpio handshake pins work. Signed-off-by: Tai Kuo <taikuo@google.com> Change-Id: I8b2e3e87086e013d91c75cf3e306f231e0d99f3d
Diffstat (limited to 'touch_bus_negotiator.c')
-rw-r--r--touch_bus_negotiator.c206
1 files changed, 206 insertions, 0 deletions
diff --git a/touch_bus_negotiator.c b/touch_bus_negotiator.c
new file mode 100644
index 0000000..4848c2d
--- /dev/null
+++ b/touch_bus_negotiator.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Touch Bus Negotiator for Google Pixel devices.
+ *
+ * Copyright (C) 2021 Google, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include "touch_bus_negotiator.h"
+
+
+enum tbn_operation {
+ AP_RELEASE_BUS,
+ AP_REQUEST_BUS,
+};
+
+static irqreturn_t tbn_aoc2ap_irq_thread(int irq, void *ptr)
+{
+ struct tbn_context *tbn = ptr;
+
+ if (completion_done(&tbn->bus_released) && completion_done(&tbn->bus_requested))
+ return IRQ_HANDLED;
+
+ /*
+ * For bus release, there two possibilities:
+ * 1. aoc2ap gpio value already changed to AOC
+ * 2. tbn_release_bus() with TBN_RELEASE_BUS_TIMEOUT_MS timeout
+ * for complete_all(&tbn->bus_released);
+ */
+ while (!completion_done(&tbn->bus_released)) {
+ if (gpio_get_value(tbn->aoc2ap_gpio) == TBN_BUS_OWNER_AOC)
+ complete_all(&tbn->bus_released);
+ else
+ usleep_range(10000, 10000); /* wait 10 ms for gpio stablized */
+ }
+
+ /*
+ * For bus request, there two possibilities:
+ * 1. aoc2ap gpio value already changed to AP
+ * 2. tbn_request_bus() with TBN_REQUEST_BUS_TIMEOUT_MS timeout
+ * for complete_all(&tbn->bus_requested);
+ */
+ while (!completion_done(&tbn->bus_requested)) {
+ if (gpio_get_value(tbn->aoc2ap_gpio) == TBN_BUS_OWNER_AP)
+ complete_all(&tbn->bus_requested);
+ else
+ usleep_range(10000, 10000); /* wait 10 ms for gpio stablized */
+ }
+
+ return IRQ_HANDLED;
+}
+
+int tbn_handshaking(struct tbn_context *tbn, enum tbn_operation operation)
+{
+ struct completion *wait_for_completion;
+ enum tbn_bus_owner bus_owner;
+ unsigned int irq_type;
+ unsigned int timeout;
+ const char *msg;
+
+ if (!tbn || !tbn->connected)
+ return 0;
+
+ if (operation == AP_REQUEST_BUS) {
+ wait_for_completion = &tbn->bus_requested;
+ bus_owner = TBN_BUS_OWNER_AP;
+ irq_type = IRQF_TRIGGER_FALLING;
+ timeout = TBN_REQUEST_BUS_TIMEOUT_MS;
+ msg = "request";
+ } else {
+ wait_for_completion = &tbn->bus_released;
+ bus_owner = TBN_BUS_OWNER_AOC;
+ irq_type = IRQF_TRIGGER_RISING;
+ timeout = TBN_RELEASE_BUS_TIMEOUT_MS;
+ msg = "release";
+ }
+
+ reinit_completion(wait_for_completion);
+
+ if (tbn->mode == TBN_MODE_GPIO) {
+ irq_set_irq_type(tbn->aoc2ap_irq, irq_type);
+ enable_irq(tbn->aoc2ap_irq);
+ gpio_direction_output(tbn->ap2aoc_gpio, bus_owner);
+ if (wait_for_completion_timeout(wait_for_completion,
+ msecs_to_jiffies(timeout)) == 0) {
+ dev_err(tbn->dev, "AP %s bus ... timeout!\n", msg);
+ complete_all(wait_for_completion);
+ } else
+ dev_info(tbn->dev, "AP %s bus ... SUCCESS!\n", msg);
+ disable_irq_nosync(tbn->aoc2ap_irq);
+ }
+
+ return 0;
+}
+
+int tbn_request_bus(struct tbn_context *tbn)
+{
+ return tbn_handshaking(tbn, AP_REQUEST_BUS);
+}
+EXPORT_SYMBOL_GPL(tbn_request_bus);
+
+int tbn_release_bus(struct tbn_context *tbn)
+{
+ return tbn_handshaking(tbn, AP_RELEASE_BUS);
+}
+EXPORT_SYMBOL_GPL(tbn_release_bus);
+
+struct tbn_context *tbn_init(struct device *dev)
+{
+ int err = 0;
+ struct tbn_context *tbn = NULL;
+ struct device_node *np = dev->of_node;
+
+ tbn = devm_kzalloc(dev, sizeof(struct tbn_context), GFP_KERNEL);
+ if (!tbn)
+ goto failed;
+
+ tbn->dev = dev;
+
+ if (of_property_read_bool(np, "tbn,ap2aoc_gpio") &&
+ of_property_read_bool(np, "tbn,aoc2ap_gpio")) {
+ tbn->mode = TBN_MODE_GPIO;
+
+ tbn->ap2aoc_gpio = of_get_named_gpio(np, "tbn,ap2aoc_gpio", 0);
+ if (gpio_is_valid(tbn->ap2aoc_gpio)) {
+ err = devm_gpio_request_one(tbn->dev, tbn->ap2aoc_gpio,
+ GPIOF_OUT_INIT_LOW, "tbn,ap2aoc_gpio");
+ if (err) {
+ dev_err(tbn->dev, "%s: Unable to request ap2aoc_gpio %d, err %d!\n",
+ __func__, tbn->ap2aoc_gpio, err);
+ goto failed;
+ }
+ }
+
+ tbn->aoc2ap_gpio = of_get_named_gpio(np, "tbn,aoc2ap_gpio", 0);
+ if (gpio_is_valid(tbn->aoc2ap_gpio)) {
+ err = devm_gpio_request_one(tbn->dev, tbn->aoc2ap_gpio,
+ GPIOF_DIR_IN, "tbn,aoc2ap_gpio");
+ if (err) {
+ dev_err(tbn->dev, "%s: Unable to request aoc2ap_gpio %d, err %d!\n",
+ __func__, tbn->aoc2ap_gpio, err);
+ goto failed;
+ }
+ tbn->aoc2ap_irq = gpio_to_irq(tbn->aoc2ap_gpio);
+ err = devm_request_threaded_irq(tbn->dev,
+ tbn->aoc2ap_irq, NULL,
+ tbn_aoc2ap_irq_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT, "tbn", tbn);
+ if (err) {
+ dev_err(tbn->dev,
+ "%s: Unable to request_threaded_irq, err %d!\n",
+ __func__, err);
+ goto failed;
+ }
+ disable_irq_nosync(tbn->aoc2ap_irq);
+ } else {
+ dev_err(tbn->dev, "%s: invalid aoc2ap_gpio %d!\n",
+ __func__, tbn->aoc2ap_gpio);
+ goto failed;
+ }
+
+ tbn->connected = true;
+ } else {
+ tbn->mode = TBN_MODE_DISABLED;
+ tbn->connected = false;
+ }
+
+ init_completion(&tbn->bus_requested);
+ init_completion(&tbn->bus_released);
+ complete_all(&tbn->bus_requested);
+ complete_all(&tbn->bus_released);
+
+ dev_info(tbn->dev,
+ "%s: gpios(aoc2ap: %d ap2aoc: %d), mode %d\n",
+ __func__, tbn->aoc2ap_gpio, tbn->ap2aoc_gpio, tbn->mode);
+
+ dev_dbg(tbn->dev, "bus negotiator initialized: %pK\n", tbn);
+
+ return tbn;
+
+failed:
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tbn_init);
+
+void tbn_cleanup(struct tbn_context *tbn)
+{
+ if (!tbn)
+ return;
+
+ dev_dbg(tbn->dev, "destructing bus negotiator: %pK\n", tbn);
+}
+EXPORT_SYMBOL_GPL(tbn_cleanup);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Touch Bus Negotiator");
+MODULE_AUTHOR("Google, Inc.");