123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- // 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 notifier_block nb;
- struct gpio_desc *reset_gpio;
- const struct goodix_i2c_hid_timing_data *timings;
- };
- static void goodix_i2c_hid_deassert_reset(struct i2c_hid_of_goodix *ihid_goodix,
- bool regulator_just_turned_on)
- {
- if (regulator_just_turned_on && 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);
- }
- 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);
- return regulator_enable(ihid_goodix->vdd);
- }
- 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);
- regulator_disable(ihid_goodix->vdd);
- }
- static int ihid_goodix_vdd_notify(struct notifier_block *nb,
- unsigned long event,
- void *ignored)
- {
- struct i2c_hid_of_goodix *ihid_goodix =
- container_of(nb, struct i2c_hid_of_goodix, nb);
- int ret = NOTIFY_OK;
- switch (event) {
- case REGULATOR_EVENT_PRE_DISABLE:
- gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
- break;
- case REGULATOR_EVENT_ENABLE:
- goodix_i2c_hid_deassert_reset(ihid_goodix, true);
- break;
- case REGULATOR_EVENT_ABORT_DISABLE:
- goodix_i2c_hid_deassert_reset(ihid_goodix, false);
- break;
- default:
- ret = NOTIFY_DONE;
- break;
- }
- return ret;
- }
- static int i2c_hid_of_goodix_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
- {
- struct i2c_hid_of_goodix *ihid_goodix;
- int ret;
- 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);
- /*
- * We need to control the "reset" line in lockstep with the regulator
- * actually turning on an off instead of just when we make the request.
- * This matters if the regulator is shared with another consumer.
- * - If the regulator is off then we must assert reset. The reset
- * line is active low and on some boards it could cause a current
- * leak if left high.
- * - If the regulator is on then we don't want reset asserted for very
- * long. Holding the controller in reset apparently draws extra
- * power.
- */
- ihid_goodix->nb.notifier_call = ihid_goodix_vdd_notify;
- ret = devm_regulator_register_notifier(ihid_goodix->vdd, &ihid_goodix->nb);
- if (ret)
- return dev_err_probe(&client->dev, ret,
- "regulator notifier request failed\n");
- /*
- * If someone else is holding the regulator on (or the regulator is
- * an always-on one) we might never be told to deassert reset. Do it
- * now... and temporarily bump the regulator reference count just to
- * make sure it is impossible for this to race with our own notifier!
- * We also assume that someone else might have _just barely_ turned
- * the regulator on so we'll do the full "post_power_delay" just in
- * case.
- */
- if (ihid_goodix->reset_gpio && regulator_is_enabled(ihid_goodix->vdd)) {
- ret = regulator_enable(ihid_goodix->vdd);
- if (ret)
- return ret;
- goodix_i2c_hid_deassert_reset(ihid_goodix, true);
- regulator_disable(ihid_goodix->vdd);
- }
- return i2c_hid_core_probe(client, &ihid_goodix->ops, 0x0001, 0);
- }
- 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 <[email protected]>");
- MODULE_DESCRIPTION("Goodix i2c-hid touchscreen driver");
- MODULE_LICENSE("GPL v2");
|