diff options
author | Alexandre Belloni <alexandre.belloni@free-electrons.com> | 2016-08-29 22:57:06 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2016-08-29 23:00:58 -0400 |
commit | 680772647d96ed853d20f837a2726151f24d8b20 (patch) | |
tree | 79e7d495a74ca81775e13c6b5b1b4756baff67b6 /drivers/input | |
parent | f959cd8c0eafe2bdaa9d0ec3e3da3b9451ad38aa (diff) |
Input: add ADC resistor ladder driver
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.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 15 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/keyboard/adc-keys.c | 210 |
3 files changed, 226 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 509608c95994..cbd75cf44739 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig | |||
@@ -12,6 +12,21 @@ menuconfig INPUT_KEYBOARD | |||
12 | 12 | ||
13 | if INPUT_KEYBOARD | 13 | if INPUT_KEYBOARD |
14 | 14 | ||
15 | config KEYBOARD_ADC | ||
16 | tristate "ADC Ladder Buttons" | ||
17 | depends on IIO | ||
18 | select INPUT_POLLDEV | ||
19 | help | ||
20 | This driver implements support for buttons connected | ||
21 | to an ADC using a resistor ladder. | ||
22 | |||
23 | Say Y here if your device has such buttons connected to an ADC. Your | ||
24 | board-specific setup logic must also provide a configuration data | ||
25 | for mapping voltages to buttons. | ||
26 | |||
27 | To compile this driver as a module, choose M here: the | ||
28 | module will be called adc_keys. | ||
29 | |||
15 | config KEYBOARD_ADP5520 | 30 | config KEYBOARD_ADP5520 |
16 | tristate "Keypad Support for ADP5520 PMIC" | 31 | tristate "Keypad Support for ADP5520 PMIC" |
17 | depends on PMIC_ADP5520 | 32 | 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 @@ | |||
4 | 4 | ||
5 | # Each configuration option enables a list of files. | 5 | # Each configuration option enables a list of files. |
6 | 6 | ||
7 | obj-$(CONFIG_KEYBOARD_ADC) += adc-keys.o | ||
7 | obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o | 8 | obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o |
8 | obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o | 9 | obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o |
9 | obj-$(CONFIG_KEYBOARD_ADP5589) += adp5589-keys.o | 10 | 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..f8cf2ccacefd --- /dev/null +++ b/drivers/input/keyboard/adc-keys.c | |||
@@ -0,0 +1,210 @@ | |||
1 | /* | ||
2 | * Input driver for resistor ladder connected on ADC | ||
3 | * | ||
4 | * Copyright (c) 2016 Alexandre Belloni | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License version 2 as published by | ||
8 | * the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/err.h> | ||
12 | #include <linux/iio/consumer.h> | ||
13 | #include <linux/iio/types.h> | ||
14 | #include <linux/input.h> | ||
15 | #include <linux/input-polldev.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/of.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/property.h> | ||
21 | #include <linux/slab.h> | ||
22 | |||
23 | struct adc_keys_button { | ||
24 | u32 voltage; | ||
25 | u32 keycode; | ||
26 | }; | ||
27 | |||
28 | struct adc_keys_state { | ||
29 | struct iio_channel *channel; | ||
30 | u32 num_keys; | ||
31 | u32 last_key; | ||
32 | u32 keyup_voltage; | ||
33 | const struct adc_keys_button *map; | ||
34 | }; | ||
35 | |||
36 | static void adc_keys_poll(struct input_polled_dev *dev) | ||
37 | { | ||
38 | struct adc_keys_state *st = dev->private; | ||
39 | int i, value, ret; | ||
40 | u32 diff, closest = 0xffffffff; | ||
41 | int keycode = 0; | ||
42 | |||
43 | ret = iio_read_channel_processed(st->channel, &value); | ||
44 | if (unlikely(ret < 0)) { | ||
45 | /* Forcibly release key if any was pressed */ | ||
46 | value = st->keyup_voltage; | ||
47 | } else { | ||
48 | for (i = 0; i < st->num_keys; i++) { | ||
49 | diff = abs(st->map[i].voltage - value); | ||
50 | if (diff < closest) { | ||
51 | closest = diff; | ||
52 | keycode = st->map[i].keycode; | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | if (abs(st->keyup_voltage - value) < closest) | ||
58 | keycode = 0; | ||
59 | |||
60 | if (st->last_key && st->last_key != keycode) | ||
61 | input_report_key(dev->input, st->last_key, 0); | ||
62 | |||
63 | if (keycode) | ||
64 | input_report_key(dev->input, keycode, 1); | ||
65 | |||
66 | input_sync(dev->input); | ||
67 | st->last_key = keycode; | ||
68 | } | ||
69 | |||
70 | static int adc_keys_load_keymap(struct device *dev, struct adc_keys_state *st) | ||
71 | { | ||
72 | struct adc_keys_button *map; | ||
73 | struct fwnode_handle *child; | ||
74 | int i; | ||
75 | |||
76 | st->num_keys = device_get_child_node_count(dev); | ||
77 | if (st->num_keys == 0) { | ||
78 | dev_err(dev, "keymap is missing\n"); | ||
79 | return -EINVAL; | ||
80 | } | ||
81 | |||
82 | map = devm_kmalloc_array(dev, st->num_keys, sizeof(*map), GFP_KERNEL); | ||
83 | if (!map) | ||
84 | return -ENOMEM; | ||
85 | |||
86 | i = 0; | ||
87 | device_for_each_child_node(dev, child) { | ||
88 | if (fwnode_property_read_u32(child, "press-threshold-microvolt", | ||
89 | &map[i].voltage)) { | ||
90 | dev_err(dev, "Key with invalid or missing voltage\n"); | ||
91 | fwnode_handle_put(child); | ||
92 | return -EINVAL; | ||
93 | } | ||
94 | map[i].voltage /= 1000; | ||
95 | |||
96 | if (fwnode_property_read_u32(child, "linux,code", | ||
97 | &map[i].keycode)) { | ||
98 | dev_err(dev, "Key with invalid or missing linux,code\n"); | ||
99 | fwnode_handle_put(child); | ||
100 | return -EINVAL; | ||
101 | } | ||
102 | |||
103 | i++; | ||
104 | } | ||
105 | |||
106 | st->map = map; | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | static int adc_keys_probe(struct platform_device *pdev) | ||
111 | { | ||
112 | struct device *dev = &pdev->dev; | ||
113 | struct adc_keys_state *st; | ||
114 | struct input_polled_dev *poll_dev; | ||
115 | struct input_dev *input; | ||
116 | enum iio_chan_type type; | ||
117 | int i, value; | ||
118 | int error; | ||
119 | |||
120 | st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); | ||
121 | if (!st) | ||
122 | return -ENOMEM; | ||
123 | |||
124 | st->channel = devm_iio_channel_get(dev, "buttons"); | ||
125 | if (IS_ERR(st->channel)) | ||
126 | return PTR_ERR(st->channel); | ||
127 | |||
128 | if (!st->channel->indio_dev) | ||
129 | return -ENXIO; | ||
130 | |||
131 | error = iio_get_channel_type(st->channel, &type); | ||
132 | if (error < 0) | ||
133 | return error; | ||
134 | |||
135 | if (type != IIO_VOLTAGE) { | ||
136 | dev_err(dev, "Incompatible channel type %d\n", type); | ||
137 | return -EINVAL; | ||
138 | } | ||
139 | |||
140 | if (device_property_read_u32(dev, "keyup-threshold-microvolt", | ||
141 | &st->keyup_voltage)) { | ||
142 | dev_err(dev, "Invalid or missing keyup voltage\n"); | ||
143 | return -EINVAL; | ||
144 | } | ||
145 | st->keyup_voltage /= 1000; | ||
146 | |||
147 | error = adc_keys_load_keymap(dev, st); | ||
148 | if (error) | ||
149 | return error; | ||
150 | |||
151 | platform_set_drvdata(pdev, st); | ||
152 | |||
153 | poll_dev = devm_input_allocate_polled_device(dev); | ||
154 | if (!poll_dev) { | ||
155 | dev_err(dev, "failed to allocate input device\n"); | ||
156 | return -ENOMEM; | ||
157 | } | ||
158 | |||
159 | if (!device_property_read_u32(dev, "poll-interval", &value)) | ||
160 | poll_dev->poll_interval = value; | ||
161 | |||
162 | poll_dev->poll = adc_keys_poll; | ||
163 | poll_dev->private = st; | ||
164 | |||
165 | input = poll_dev->input; | ||
166 | |||
167 | input->name = pdev->name; | ||
168 | input->phys = "adc-keys/input0"; | ||
169 | |||
170 | input->id.bustype = BUS_HOST; | ||
171 | input->id.vendor = 0x0001; | ||
172 | input->id.product = 0x0001; | ||
173 | input->id.version = 0x0100; | ||
174 | |||
175 | __set_bit(EV_KEY, input->evbit); | ||
176 | for (i = 0; i < st->num_keys; i++) | ||
177 | __set_bit(st->map[i].keycode, input->keybit); | ||
178 | |||
179 | if (device_property_read_bool(dev, "autorepeat")) | ||
180 | __set_bit(EV_REP, input->evbit); | ||
181 | |||
182 | error = input_register_polled_device(poll_dev); | ||
183 | if (error) { | ||
184 | dev_err(dev, "Unable to register input device: %d\n", error); | ||
185 | return error; | ||
186 | } | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | #ifdef CONFIG_OF | ||
192 | static const struct of_device_id adc_keys_of_match[] = { | ||
193 | { .compatible = "adc-keys", }, | ||
194 | { } | ||
195 | }; | ||
196 | MODULE_DEVICE_TABLE(of, adc_keys_of_match); | ||
197 | #endif | ||
198 | |||
199 | static struct platform_driver __refdata adc_keys_driver = { | ||
200 | .driver = { | ||
201 | .name = "adc_keys", | ||
202 | .of_match_table = of_match_ptr(adc_keys_of_match), | ||
203 | }, | ||
204 | .probe = adc_keys_probe, | ||
205 | }; | ||
206 | module_platform_driver(adc_keys_driver); | ||
207 | |||
208 | MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>"); | ||
209 | MODULE_DESCRIPTION("Input driver for resistor ladder connected on ADC"); | ||
210 | MODULE_LICENSE("GPL v2"); | ||