HID: i2c-hid: Introduce goodix-i2c-hid using i2c-hid core
authorDouglas Anderson <dianders@chromium.org>
Fri, 15 Jan 2021 17:06:40 +0000 (09:06 -0800)
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>
Mon, 18 Jan 2021 15:56:22 +0000 (16:56 +0100)
Goodix i2c-hid touchscreens are mostly i2c-hid compliant but have some
special power sequencing requirements, including the need to drive a
reset line during the sequencing.

Let's use the new rejiggering of i2c-hid to support this with a thin
wrapper driver to support the first Goodix i2c-hid touchscreen:
GT7375P

Signed-off-by: Douglas Anderson <dianders@chromium.org>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
drivers/hid/i2c-hid/Kconfig
drivers/hid/i2c-hid/Makefile
drivers/hid/i2c-hid/i2c-hid-of-goodix.c [new file with mode: 0644]

index 819b752..a16c6a6 100644 (file)
@@ -32,10 +32,25 @@ config I2C_HID_OF
          will be called i2c-hid-of.  It will also build/depend on the
          module i2c-hid.
 
+config I2C_HID_OF_GOODIX
+       tristate "Driver for Goodix hid-i2c based devices on OF systems"
+       default n
+       depends on I2C && INPUT && OF
+       help
+         Say Y here if you want support for Goodix i2c devices that use
+         the i2c-hid protocol on Open Firmware (Device Tree)-based
+         systems.
+
+         If unsure, say N.
+
+         This support is also available as a module.  If so, the module
+         will be called i2c-hid-of-goodix.  It will also build/depend on
+         the module i2c-hid.
+
 endmenu
 
 config I2C_HID_CORE
        tristate
-       default y if I2C_HID_ACPI=y || I2C_HID_OF=y
-       default m if I2C_HID_ACPI=m || I2C_HID_OF=m
+       default y if I2C_HID_ACPI=y || I2C_HID_OF=y || I2C_HID_OF_GOODIX=y
+       default m if I2C_HID_ACPI=m || I2C_HID_OF=m || I2C_HID_OF_GOODIX=m
        select HID
index 9b4a734..302545a 100644 (file)
@@ -10,3 +10,4 @@ i2c-hid-$(CONFIG_DMI)                         += i2c-hid-dmi-quirks.o
 
 obj-$(CONFIG_I2C_HID_ACPI)                     += i2c-hid-acpi.o
 obj-$(CONFIG_I2C_HID_OF)                       += i2c-hid-of.o
+obj-$(CONFIG_I2C_HID_OF_GOODIX)                        += i2c-hid-of-goodix.o
diff --git a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c
new file mode 100644 (file)
index 0000000..ee02259
--- /dev/null
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Goodix touchscreens that use the i2c-hid protocol.
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
+
+#include "i2c-hid.h"
+
+struct goodix_i2c_hid_timing_data {
+       unsigned int post_gpio_reset_delay_ms;
+       unsigned int post_power_delay_ms;
+};
+
+struct i2c_hid_of_goodix {
+       struct i2chid_ops ops;
+
+       struct regulator *vdd;
+       struct gpio_desc *reset_gpio;
+       const struct goodix_i2c_hid_timing_data *timings;
+};
+
+static int goodix_i2c_hid_power_up(struct i2chid_ops *ops)
+{
+       struct i2c_hid_of_goodix *ihid_goodix =
+               container_of(ops, struct i2c_hid_of_goodix, ops);
+       int ret;
+
+       ret = regulator_enable(ihid_goodix->vdd);
+       if (ret)
+               return ret;
+
+       if (ihid_goodix->timings->post_power_delay_ms)
+               msleep(ihid_goodix->timings->post_power_delay_ms);
+
+       gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 0);
+       if (ihid_goodix->timings->post_gpio_reset_delay_ms)
+               msleep(ihid_goodix->timings->post_gpio_reset_delay_ms);
+
+       return 0;
+}
+
+static void goodix_i2c_hid_power_down(struct i2chid_ops *ops)
+{
+       struct i2c_hid_of_goodix *ihid_goodix =
+               container_of(ops, struct i2c_hid_of_goodix, ops);
+
+       gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
+       regulator_disable(ihid_goodix->vdd);
+}
+
+static int i2c_hid_of_goodix_probe(struct i2c_client *client,
+                                  const struct i2c_device_id *id)
+{
+       struct i2c_hid_of_goodix *ihid_goodix;
+
+       ihid_goodix = devm_kzalloc(&client->dev, sizeof(*ihid_goodix),
+                                  GFP_KERNEL);
+       if (!ihid_goodix)
+               return -ENOMEM;
+
+       ihid_goodix->ops.power_up = goodix_i2c_hid_power_up;
+       ihid_goodix->ops.power_down = goodix_i2c_hid_power_down;
+
+       /* Start out with reset asserted */
+       ihid_goodix->reset_gpio =
+               devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
+       if (IS_ERR(ihid_goodix->reset_gpio))
+               return PTR_ERR(ihid_goodix->reset_gpio);
+
+       ihid_goodix->vdd = devm_regulator_get(&client->dev, "vdd");
+       if (IS_ERR(ihid_goodix->vdd))
+               return PTR_ERR(ihid_goodix->vdd);
+
+       ihid_goodix->timings = device_get_match_data(&client->dev);
+
+       return i2c_hid_core_probe(client, &ihid_goodix->ops, 0x0001);
+}
+
+static const struct goodix_i2c_hid_timing_data goodix_gt7375p_timing_data = {
+       .post_power_delay_ms = 10,
+       .post_gpio_reset_delay_ms = 180,
+};
+
+static const struct of_device_id goodix_i2c_hid_of_match[] = {
+       { .compatible = "goodix,gt7375p", .data = &goodix_gt7375p_timing_data },
+       { }
+};
+MODULE_DEVICE_TABLE(of, goodix_i2c_hid_of_match);
+
+static struct i2c_driver goodix_i2c_hid_ts_driver = {
+       .driver = {
+               .name   = "i2c_hid_of_goodix",
+               .pm     = &i2c_hid_core_pm,
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+               .of_match_table = of_match_ptr(goodix_i2c_hid_of_match),
+       },
+       .probe          = i2c_hid_of_goodix_probe,
+       .remove         = i2c_hid_core_remove,
+       .shutdown       = i2c_hid_core_shutdown,
+};
+module_i2c_driver(goodix_i2c_hid_ts_driver);
+
+MODULE_AUTHOR("Douglas Anderson <dianders@chromium.org>");
+MODULE_DESCRIPTION("Goodix i2c-hid touchscreen driver");
+MODULE_LICENSE("GPL v2");