diff options
Diffstat (limited to 'drivers/input/misc/gpio_decoder.c')
-rw-r--r-- | drivers/input/misc/gpio_decoder.c | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/drivers/input/misc/gpio_decoder.c b/drivers/input/misc/gpio_decoder.c new file mode 100644 index 000000000..ee668eba3 --- /dev/null +++ b/drivers/input/misc/gpio_decoder.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/ + * + * A generic driver to read multiple gpio lines and translate the + * encoded numeric value into an input event. + */ + +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +struct gpio_decoder { + struct gpio_descs *input_gpios; + struct device *dev; + u32 axis; + u32 last_stable; +}; + +static int gpio_decoder_get_gpios_state(struct gpio_decoder *decoder) +{ + struct gpio_descs *gpios = decoder->input_gpios; + unsigned int ret = 0; + int i, val; + + for (i = 0; i < gpios->ndescs; i++) { + val = gpiod_get_value_cansleep(gpios->desc[i]); + if (val < 0) { + dev_err(decoder->dev, + "Error reading gpio %d: %d\n", + desc_to_gpio(gpios->desc[i]), val); + return val; + } + + val = !!val; + ret = (ret << 1) | val; + } + + return ret; +} + +static void gpio_decoder_poll_gpios(struct input_dev *input) +{ + struct gpio_decoder *decoder = input_get_drvdata(input); + int state; + + state = gpio_decoder_get_gpios_state(decoder); + if (state >= 0 && state != decoder->last_stable) { + input_report_abs(input, decoder->axis, state); + input_sync(input); + decoder->last_stable = state; + } +} + +static int gpio_decoder_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_decoder *decoder; + struct input_dev *input; + u32 max; + int err; + + decoder = devm_kzalloc(dev, sizeof(*decoder), GFP_KERNEL); + if (!decoder) + return -ENOMEM; + + decoder->dev = dev; + device_property_read_u32(dev, "linux,axis", &decoder->axis); + + decoder->input_gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN); + if (IS_ERR(decoder->input_gpios)) { + dev_err(dev, "unable to acquire input gpios\n"); + return PTR_ERR(decoder->input_gpios); + } + + if (decoder->input_gpios->ndescs < 2) { + dev_err(dev, "not enough gpios found\n"); + return -EINVAL; + } + + if (device_property_read_u32(dev, "decoder-max-value", &max)) + max = (1U << decoder->input_gpios->ndescs) - 1; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input_set_drvdata(input, decoder); + + input->name = pdev->name; + input->id.bustype = BUS_HOST; + input_set_abs_params(input, decoder->axis, 0, max, 0, 0); + + err = input_setup_polling(input, gpio_decoder_poll_gpios); + if (err) { + dev_err(dev, "failed to set up polling\n"); + return err; + } + + err = input_register_device(input); + if (err) { + dev_err(dev, "failed to register input device\n"); + return err; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id gpio_decoder_of_match[] = { + { .compatible = "gpio-decoder", }, + { }, +}; +MODULE_DEVICE_TABLE(of, gpio_decoder_of_match); +#endif + +static struct platform_driver gpio_decoder_driver = { + .probe = gpio_decoder_probe, + .driver = { + .name = "gpio-decoder", + .of_match_table = of_match_ptr(gpio_decoder_of_match), + } +}; +module_platform_driver(gpio_decoder_driver); + +MODULE_DESCRIPTION("GPIO decoder input driver"); +MODULE_AUTHOR("Vignesh R <vigneshr@ti.com>"); +MODULE_LICENSE("GPL v2"); |