From: Alexandre Belloni <alexandre.belloni@free-electrons.com> To: Dmitry Torokhov <dmitry.torokhov@gmail.com> Cc: Jonathan Cameron <jic23@kernel.org>, Maxime Ripard <maxime.ripard@free-electrons.com>, linux-input@vger.kernel.org, linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org, Alexandre Belloni <alexandre.belloni@free-electrons.com> Subject: [PATCH 2/2] input: add ADC resistor ladder driver Date: Fri, 1 Jul 2016 23:30:02 +0200 [thread overview] Message-ID: <1467408602-16546-2-git-send-email-alexandre.belloni@free-electrons.com> (raw) In-Reply-To: <1467408602-16546-1-git-send-email-alexandre.belloni@free-electrons.com> A common way of multiplexing buttons on a single input in cheap devices is to use a resistor ladder on an ADC. This driver supports that configuration by polling an ADC channel provided by IIO. Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com> --- drivers/input/keyboard/Kconfig | 15 +++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/adc-keys.c | 209 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 drivers/input/keyboard/adc-keys.c diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 509608c95994..ba20808f31e0 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -12,6 +12,21 @@ menuconfig INPUT_KEYBOARD if INPUT_KEYBOARD +config KEYBOARD_ADC + tristate "ADC ladder Buttons" + depends on IIO || COMPILE_TEST + select INPUT_POLLDEV + help + This driver implements support for buttons connected + to an ADC using a resistor ladder. + + Say Y here if your device has such buttons connected to an ADC. Your + board-specific setup logic must also provide a configuration data + saying mapping voltages to buttons. + + To compile this driver as a module, choose M here: the + module will be called adc_keys. + config KEYBOARD_ADP5520 tristate "Keypad Support for ADP5520 PMIC" depends on PMIC_ADP5520 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 1d416ddf84e4..d9f4cfcf3410 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -4,6 +4,7 @@ # Each configuration option enables a list of files. +obj-$(CONFIG_KEYBOARD_ADC) += adc-keys.o obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o obj-$(CONFIG_KEYBOARD_ADP5589) += adp5589-keys.o diff --git a/drivers/input/keyboard/adc-keys.c b/drivers/input/keyboard/adc-keys.c new file mode 100644 index 000000000000..ba6ee91272b2 --- /dev/null +++ b/drivers/input/keyboard/adc-keys.c @@ -0,0 +1,209 @@ +/* Input driver for resistor ladder connected on ADC + * + * Copyright (c) 2016 Alexandre Belloni + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/iio/consumer.h> +#include <linux/iio/types.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +struct adc_keys_button { + u32 voltage; + u32 keycode; +}; + +struct adc_keys_state { + struct iio_channel *channel; + u32 num_keys; + u32 last_key; + u32 keyup_voltage; + struct adc_keys_button *map; +}; + +static void adc_keys_poll(struct input_polled_dev *dev) +{ + struct adc_keys_state *st = dev->private; + int i, value, ret; + u32 diff, closest = 0xffffffff; + int keycode = 0; + + ret = iio_read_channel_processed(st->channel, &value); + if (ret < 0) { + if (st->last_key) { + input_report_key(dev->input, st->last_key, 0); + input_sync(dev->input); + st->last_key = 0; + } + return; + } + + for (i = 0; i < st->num_keys; i++) { + diff = abs(st->map[i].voltage - value); + if (diff < closest) { + closest = diff; + keycode = st->map[i].keycode; + } + } + + if (abs(st->keyup_voltage - value) < closest) { + input_report_key(dev->input, st->last_key, 0); + st->last_key = 0; + } else { + if (st->last_key && st->last_key != keycode) + input_report_key(dev->input, st->last_key, 0); + input_report_key(dev->input, keycode, 1); + st->last_key = keycode; + } + + input_sync(dev->input); +} + +static int adc_keys_load_dt_keymap(struct device *dev, + struct adc_keys_state *st) +{ + struct device_node *pp, *np = dev->of_node; + int i; + + st->num_keys = of_get_child_count(np); + if (st->num_keys == 0) { + dev_err(dev, "keymap is missing\n"); + return -EINVAL; + } + + st->map = devm_kmalloc_array(dev, st->num_keys, sizeof(*st->map), + GFP_KERNEL); + if (!st->map) + return -ENOMEM; + + i = 0; + for_each_child_of_node(np, pp) { + struct adc_keys_button *map = &st->map[i]; + + if (of_property_read_u32(pp, "voltage-mvolt", &map->voltage)) { + dev_err(dev, "%s: Invalid or missing voltage\n", + pp->name); + return -EINVAL; + } + + if (of_property_read_u32(pp, "linux,code", &map->keycode)) { + dev_err(dev, "%s: Invalid or missing linux,code\n", + pp->name); + return -EINVAL; + } + + i++; + } + + return 0; +} + +static int adc_keys_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct adc_keys_state *st; + struct input_polled_dev *poll_dev = NULL; + struct input_dev *input; + enum iio_chan_type type; + int i, value, ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->channel = devm_iio_channel_get(dev, "buttons"); + if (IS_ERR(st->channel)) + return PTR_ERR(st->channel); + + if (!st->channel->indio_dev) + return -ENODEV; + + ret = iio_get_channel_type(st->channel, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "Incompatible channel type %d\n", type); + return -EINVAL; + } + + if (of_property_read_u32(node, "voltage-keyup-mvolt", + &st->keyup_voltage)) { + dev_err(dev, "Invalid or missing keyup voltage\n"); + return -EINVAL; + } + + ret = adc_keys_load_dt_keymap(dev, st); + if (ret) + return ret; + + platform_set_drvdata(pdev, st); + + poll_dev = devm_input_allocate_polled_device(dev); + if (!poll_dev) { + dev_err(dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + if (!of_property_read_u32(node, "poll-interval", &value)) + poll_dev->poll_interval = value; + poll_dev->poll = adc_keys_poll; + poll_dev->private = st; + + input = poll_dev->input; + + input->name = pdev->name; + input->phys = "adc-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < st->num_keys; i++) + __set_bit(st->map[i].keycode, input->keybit); + + if (!!of_get_property(node, "autorepeat", NULL)) + __set_bit(EV_REP, input->evbit); + + ret = input_register_polled_device(poll_dev); + if (ret) { + dev_err(dev, "Unable to register input device\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id adc_keys_of_match[] = { + { .compatible = "adc-keys", }, + { } +}; +MODULE_DEVICE_TABLE(of, adc_keys_of_match); + +static struct platform_driver __refdata adc_keys_driver = { + .driver = { + .name = "adc_keys", + .of_match_table = adc_keys_of_match, + }, + .probe = adc_keys_probe, +}; + +module_platform_driver(adc_keys_driver); + +MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>"); +MODULE_DESCRIPTION("Input driver for resistor ladder connected on ADC"); +MODULE_LICENSE("GPL v2"); -- 2.8.1
WARNING: multiple messages have this Message-ID (diff)
From: Alexandre Belloni <alexandre.belloni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> To: Dmitry Torokhov <dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> Cc: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>, Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>, linux-input-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Alexandre Belloni <alexandre.belloni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> Subject: [PATCH 2/2] input: add ADC resistor ladder driver Date: Fri, 1 Jul 2016 23:30:02 +0200 [thread overview] Message-ID: <1467408602-16546-2-git-send-email-alexandre.belloni@free-electrons.com> (raw) In-Reply-To: <1467408602-16546-1-git-send-email-alexandre.belloni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> A common way of multiplexing buttons on a single input in cheap devices is to use a resistor ladder on an ADC. This driver supports that configuration by polling an ADC channel provided by IIO. Signed-off-by: Alexandre Belloni <alexandre.belloni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> --- drivers/input/keyboard/Kconfig | 15 +++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/adc-keys.c | 209 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 drivers/input/keyboard/adc-keys.c diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 509608c95994..ba20808f31e0 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -12,6 +12,21 @@ menuconfig INPUT_KEYBOARD if INPUT_KEYBOARD +config KEYBOARD_ADC + tristate "ADC ladder Buttons" + depends on IIO || COMPILE_TEST + select INPUT_POLLDEV + help + This driver implements support for buttons connected + to an ADC using a resistor ladder. + + Say Y here if your device has such buttons connected to an ADC. Your + board-specific setup logic must also provide a configuration data + saying mapping voltages to buttons. + + To compile this driver as a module, choose M here: the + module will be called adc_keys. + config KEYBOARD_ADP5520 tristate "Keypad Support for ADP5520 PMIC" depends on PMIC_ADP5520 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 1d416ddf84e4..d9f4cfcf3410 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -4,6 +4,7 @@ # Each configuration option enables a list of files. +obj-$(CONFIG_KEYBOARD_ADC) += adc-keys.o obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o obj-$(CONFIG_KEYBOARD_ADP5589) += adp5589-keys.o diff --git a/drivers/input/keyboard/adc-keys.c b/drivers/input/keyboard/adc-keys.c new file mode 100644 index 000000000000..ba6ee91272b2 --- /dev/null +++ b/drivers/input/keyboard/adc-keys.c @@ -0,0 +1,209 @@ +/* Input driver for resistor ladder connected on ADC + * + * Copyright (c) 2016 Alexandre Belloni + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/iio/consumer.h> +#include <linux/iio/types.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +struct adc_keys_button { + u32 voltage; + u32 keycode; +}; + +struct adc_keys_state { + struct iio_channel *channel; + u32 num_keys; + u32 last_key; + u32 keyup_voltage; + struct adc_keys_button *map; +}; + +static void adc_keys_poll(struct input_polled_dev *dev) +{ + struct adc_keys_state *st = dev->private; + int i, value, ret; + u32 diff, closest = 0xffffffff; + int keycode = 0; + + ret = iio_read_channel_processed(st->channel, &value); + if (ret < 0) { + if (st->last_key) { + input_report_key(dev->input, st->last_key, 0); + input_sync(dev->input); + st->last_key = 0; + } + return; + } + + for (i = 0; i < st->num_keys; i++) { + diff = abs(st->map[i].voltage - value); + if (diff < closest) { + closest = diff; + keycode = st->map[i].keycode; + } + } + + if (abs(st->keyup_voltage - value) < closest) { + input_report_key(dev->input, st->last_key, 0); + st->last_key = 0; + } else { + if (st->last_key && st->last_key != keycode) + input_report_key(dev->input, st->last_key, 0); + input_report_key(dev->input, keycode, 1); + st->last_key = keycode; + } + + input_sync(dev->input); +} + +static int adc_keys_load_dt_keymap(struct device *dev, + struct adc_keys_state *st) +{ + struct device_node *pp, *np = dev->of_node; + int i; + + st->num_keys = of_get_child_count(np); + if (st->num_keys == 0) { + dev_err(dev, "keymap is missing\n"); + return -EINVAL; + } + + st->map = devm_kmalloc_array(dev, st->num_keys, sizeof(*st->map), + GFP_KERNEL); + if (!st->map) + return -ENOMEM; + + i = 0; + for_each_child_of_node(np, pp) { + struct adc_keys_button *map = &st->map[i]; + + if (of_property_read_u32(pp, "voltage-mvolt", &map->voltage)) { + dev_err(dev, "%s: Invalid or missing voltage\n", + pp->name); + return -EINVAL; + } + + if (of_property_read_u32(pp, "linux,code", &map->keycode)) { + dev_err(dev, "%s: Invalid or missing linux,code\n", + pp->name); + return -EINVAL; + } + + i++; + } + + return 0; +} + +static int adc_keys_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct adc_keys_state *st; + struct input_polled_dev *poll_dev = NULL; + struct input_dev *input; + enum iio_chan_type type; + int i, value, ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->channel = devm_iio_channel_get(dev, "buttons"); + if (IS_ERR(st->channel)) + return PTR_ERR(st->channel); + + if (!st->channel->indio_dev) + return -ENODEV; + + ret = iio_get_channel_type(st->channel, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "Incompatible channel type %d\n", type); + return -EINVAL; + } + + if (of_property_read_u32(node, "voltage-keyup-mvolt", + &st->keyup_voltage)) { + dev_err(dev, "Invalid or missing keyup voltage\n"); + return -EINVAL; + } + + ret = adc_keys_load_dt_keymap(dev, st); + if (ret) + return ret; + + platform_set_drvdata(pdev, st); + + poll_dev = devm_input_allocate_polled_device(dev); + if (!poll_dev) { + dev_err(dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + if (!of_property_read_u32(node, "poll-interval", &value)) + poll_dev->poll_interval = value; + poll_dev->poll = adc_keys_poll; + poll_dev->private = st; + + input = poll_dev->input; + + input->name = pdev->name; + input->phys = "adc-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < st->num_keys; i++) + __set_bit(st->map[i].keycode, input->keybit); + + if (!!of_get_property(node, "autorepeat", NULL)) + __set_bit(EV_REP, input->evbit); + + ret = input_register_polled_device(poll_dev); + if (ret) { + dev_err(dev, "Unable to register input device\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id adc_keys_of_match[] = { + { .compatible = "adc-keys", }, + { } +}; +MODULE_DEVICE_TABLE(of, adc_keys_of_match); + +static struct platform_driver __refdata adc_keys_driver = { + .driver = { + .name = "adc_keys", + .of_match_table = adc_keys_of_match, + }, + .probe = adc_keys_probe, +}; + +module_platform_driver(adc_keys_driver); + +MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>"); +MODULE_DESCRIPTION("Input driver for resistor ladder connected on ADC"); +MODULE_LICENSE("GPL v2"); -- 2.8.1
next prev parent reply other threads:[~2016-07-01 21:30 UTC|newest] Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top 2016-07-01 21:30 [PATCH 1/2] input: adc-keys: add DT binding documentation Alexandre Belloni 2016-07-01 21:30 ` Alexandre Belloni [this message] 2016-07-01 21:30 ` [PATCH 2/2] input: add ADC resistor ladder driver Alexandre Belloni 2016-07-02 22:21 ` Matt Ranostay 2016-07-03 13:50 ` Jonathan Cameron 2016-07-03 13:50 ` Jonathan Cameron 2016-07-03 13:43 ` [PATCH 1/2] input: adc-keys: add DT binding documentation Jonathan Cameron 2016-07-04 16:29 ` Alexandre Belloni 2016-07-04 16:29 ` Alexandre Belloni 2016-07-05 14:53 ` Rob Herring
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=1467408602-16546-2-git-send-email-alexandre.belloni@free-electrons.com \ --to=alexandre.belloni@free-electrons.com \ --cc=dmitry.torokhov@gmail.com \ --cc=jic23@kernel.org \ --cc=linux-iio@vger.kernel.org \ --cc=linux-input@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=maxime.ripard@free-electrons.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.