diff options
author | Sanchayan Maity <maitysanchayan@gmail.com> | 2015-09-05 13:32:09 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2015-09-05 14:11:48 -0400 |
commit | 48ead50c1dd8e5cdb7ead067558a834c1e895e6e (patch) | |
tree | a5e12cf6a2763d7f6419389347d7a8cffe5a5339 | |
parent | f5d75341fac6033f6afac900da110cc78e06d40d (diff) |
Input: Add touchscreen support for Colibri VF50
The Colibri Vybrid VF50 module supports 4-wire touchscreens using
FETs and ADC inputs. This driver uses the IIO consumer interface
and relies on the vf610_adc driver based on the IIO framework.
Signed-off-by: Sanchayan Maity <maitysanchayan@gmail.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
-rw-r--r-- | Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt | 36 | ||||
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 12 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/colibri-vf50-ts.c | 386 |
4 files changed, 435 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt b/Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt new file mode 100644 index 000000000000..9d9e930f3251 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt | |||
@@ -0,0 +1,36 @@ | |||
1 | * Toradex Colibri VF50 Touchscreen driver | ||
2 | |||
3 | Required Properties: | ||
4 | - compatible must be toradex,vf50-touchscreen | ||
5 | - io-channels: adc channels being used by the Colibri VF50 module | ||
6 | - xp-gpios: FET gate driver for input of X+ | ||
7 | - xm-gpios: FET gate driver for input of X- | ||
8 | - yp-gpios: FET gate driver for input of Y+ | ||
9 | - ym-gpios: FET gate driver for input of Y- | ||
10 | - interrupt-parent: phandle for the interrupt controller | ||
11 | - interrupts: pen irq interrupt for touch detection | ||
12 | - pinctrl-names: "idle", "default", "gpios" | ||
13 | - pinctrl-0: pinctrl node for pen/touch detection state pinmux | ||
14 | - pinctrl-1: pinctrl node for X/Y and pressure measurement (ADC) state pinmux | ||
15 | - pinctrl-2: pinctrl node for gpios functioning as FET gate drivers | ||
16 | - vf50-ts-min-pressure: pressure level at which to stop measuring X/Y values | ||
17 | |||
18 | Example: | ||
19 | |||
20 | touchctrl: vf50_touchctrl { | ||
21 | compatible = "toradex,vf50-touchscreen"; | ||
22 | io-channels = <&adc1 0>,<&adc0 0>, | ||
23 | <&adc0 1>,<&adc1 2>; | ||
24 | xp-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; | ||
25 | xm-gpios = <&gpio2 29 GPIO_ACTIVE_HIGH>; | ||
26 | yp-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; | ||
27 | ym-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; | ||
28 | interrupt-parent = <&gpio0>; | ||
29 | interrupts = <8 IRQ_TYPE_LEVEL_LOW>; | ||
30 | pinctrl-names = "idle","default","gpios"; | ||
31 | pinctrl-0 = <&pinctrl_touchctrl_idle>; | ||
32 | pinctrl-1 = <&pinctrl_touchctrl_default>; | ||
33 | pinctrl-2 = <&pinctrl_touchctrl_gpios>; | ||
34 | vf50-ts-min-pressure = <200>; | ||
35 | status = "disabled"; | ||
36 | }; | ||
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 059edeb7f04a..a6d7a4d8dbb7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig | |||
@@ -1040,4 +1040,16 @@ config TOUCHSCREEN_ZFORCE | |||
1040 | To compile this driver as a module, choose M here: the | 1040 | To compile this driver as a module, choose M here: the |
1041 | module will be called zforce_ts. | 1041 | module will be called zforce_ts. |
1042 | 1042 | ||
1043 | config TOUCHSCREEN_COLIBRI_VF50 | ||
1044 | tristate "Toradex Colibri on board touchscreen driver" | ||
1045 | depends on GPIOLIB && IIO && VF610_ADC | ||
1046 | help | ||
1047 | Say Y here if you have a Colibri VF50 and plan to use | ||
1048 | the on-board provided 4-wire touchscreen driver. | ||
1049 | |||
1050 | If unsure, say N. | ||
1051 | |||
1052 | To compile this driver as a module, choose M here: the | ||
1053 | module will be called colibri_vf50_ts. | ||
1054 | |||
1043 | endif | 1055 | endif |
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index c85aae23e7f8..fb27f7e36070 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile | |||
@@ -85,3 +85,4 @@ obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o | |||
85 | obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o | 85 | obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o |
86 | obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o | 86 | obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o |
87 | obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o | 87 | obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o |
88 | obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o | ||
diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c new file mode 100644 index 000000000000..5d4903a402cc --- /dev/null +++ b/drivers/input/touchscreen/colibri-vf50-ts.c | |||
@@ -0,0 +1,386 @@ | |||
1 | /* | ||
2 | * Toradex Colibri VF50 Touchscreen driver | ||
3 | * | ||
4 | * Copyright 2015 Toradex AG | ||
5 | * | ||
6 | * Originally authored by Stefan Agner for 3.0 kernel | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/delay.h> | ||
15 | #include <linux/err.h> | ||
16 | #include <linux/gpio.h> | ||
17 | #include <linux/gpio/consumer.h> | ||
18 | #include <linux/iio/consumer.h> | ||
19 | #include <linux/iio/types.h> | ||
20 | #include <linux/input.h> | ||
21 | #include <linux/interrupt.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/pinctrl/consumer.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/types.h> | ||
28 | |||
29 | #define DRIVER_NAME "colibri-vf50-ts" | ||
30 | #define DRV_VERSION "1.0" | ||
31 | |||
32 | #define VF_ADC_MAX ((1 << 12) - 1) | ||
33 | |||
34 | #define COLI_TOUCH_MIN_DELAY_US 1000 | ||
35 | #define COLI_TOUCH_MAX_DELAY_US 2000 | ||
36 | #define COLI_PULLUP_MIN_DELAY_US 10000 | ||
37 | #define COLI_PULLUP_MAX_DELAY_US 11000 | ||
38 | #define COLI_TOUCH_NO_OF_AVGS 5 | ||
39 | #define COLI_TOUCH_REQ_ADC_CHAN 4 | ||
40 | |||
41 | struct vf50_touch_device { | ||
42 | struct platform_device *pdev; | ||
43 | struct input_dev *ts_input; | ||
44 | struct iio_channel *channels; | ||
45 | struct gpio_desc *gpio_xp; | ||
46 | struct gpio_desc *gpio_xm; | ||
47 | struct gpio_desc *gpio_yp; | ||
48 | struct gpio_desc *gpio_ym; | ||
49 | int pen_irq; | ||
50 | int min_pressure; | ||
51 | bool stop_touchscreen; | ||
52 | }; | ||
53 | |||
54 | /* | ||
55 | * Enables given plates and measures touch parameters using ADC | ||
56 | */ | ||
57 | static int adc_ts_measure(struct iio_channel *channel, | ||
58 | struct gpio_desc *plate_p, struct gpio_desc *plate_m) | ||
59 | { | ||
60 | int i, value = 0, val = 0; | ||
61 | int error; | ||
62 | |||
63 | gpiod_set_value(plate_p, 1); | ||
64 | gpiod_set_value(plate_m, 1); | ||
65 | |||
66 | usleep_range(COLI_TOUCH_MIN_DELAY_US, COLI_TOUCH_MAX_DELAY_US); | ||
67 | |||
68 | for (i = 0; i < COLI_TOUCH_NO_OF_AVGS; i++) { | ||
69 | error = iio_read_channel_raw(channel, &val); | ||
70 | if (error < 0) { | ||
71 | value = error; | ||
72 | goto error_iio_read; | ||
73 | } | ||
74 | |||
75 | value += val; | ||
76 | } | ||
77 | |||
78 | value /= COLI_TOUCH_NO_OF_AVGS; | ||
79 | |||
80 | error_iio_read: | ||
81 | gpiod_set_value(plate_p, 0); | ||
82 | gpiod_set_value(plate_m, 0); | ||
83 | |||
84 | return value; | ||
85 | } | ||
86 | |||
87 | /* | ||
88 | * Enable touch detection using falling edge detection on XM | ||
89 | */ | ||
90 | static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts) | ||
91 | { | ||
92 | /* Enable plate YM (needs to be strong GND, high active) */ | ||
93 | gpiod_set_value(vf50_ts->gpio_ym, 1); | ||
94 | |||
95 | /* | ||
96 | * Let the platform mux to idle state in order to enable | ||
97 | * Pull-Up on GPIO | ||
98 | */ | ||
99 | pinctrl_pm_select_idle_state(&vf50_ts->pdev->dev); | ||
100 | |||
101 | /* Wait for the pull-up to be stable on high */ | ||
102 | usleep_range(COLI_PULLUP_MIN_DELAY_US, COLI_PULLUP_MAX_DELAY_US); | ||
103 | } | ||
104 | |||
105 | /* | ||
106 | * ADC touch screen sampling bottom half irq handler | ||
107 | */ | ||
108 | static irqreturn_t vf50_ts_irq_bh(int irq, void *private) | ||
109 | { | ||
110 | struct vf50_touch_device *vf50_ts = private; | ||
111 | struct device *dev = &vf50_ts->pdev->dev; | ||
112 | int val_x, val_y, val_z1, val_z2, val_p = 0; | ||
113 | bool discard_val_on_start = true; | ||
114 | |||
115 | /* Disable the touch detection plates */ | ||
116 | gpiod_set_value(vf50_ts->gpio_ym, 0); | ||
117 | |||
118 | /* Let the platform mux to default state in order to mux as ADC */ | ||
119 | pinctrl_pm_select_default_state(dev); | ||
120 | |||
121 | while (!vf50_ts->stop_touchscreen) { | ||
122 | /* X-Direction */ | ||
123 | val_x = adc_ts_measure(&vf50_ts->channels[0], | ||
124 | vf50_ts->gpio_xp, vf50_ts->gpio_xm); | ||
125 | if (val_x < 0) | ||
126 | break; | ||
127 | |||
128 | /* Y-Direction */ | ||
129 | val_y = adc_ts_measure(&vf50_ts->channels[1], | ||
130 | vf50_ts->gpio_yp, vf50_ts->gpio_ym); | ||
131 | if (val_y < 0) | ||
132 | break; | ||
133 | |||
134 | /* | ||
135 | * Touch pressure | ||
136 | * Measure on XP/YM | ||
137 | */ | ||
138 | val_z1 = adc_ts_measure(&vf50_ts->channels[2], | ||
139 | vf50_ts->gpio_yp, vf50_ts->gpio_xm); | ||
140 | if (val_z1 < 0) | ||
141 | break; | ||
142 | val_z2 = adc_ts_measure(&vf50_ts->channels[3], | ||
143 | vf50_ts->gpio_yp, vf50_ts->gpio_xm); | ||
144 | if (val_z2 < 0) | ||
145 | break; | ||
146 | |||
147 | /* Validate signal (avoid calculation using noise) */ | ||
148 | if (val_z1 > 64 && val_x > 64) { | ||
149 | /* | ||
150 | * Calculate resistance between the plates | ||
151 | * lower resistance means higher pressure | ||
152 | */ | ||
153 | int r_x = (1000 * val_x) / VF_ADC_MAX; | ||
154 | |||
155 | val_p = (r_x * val_z2) / val_z1 - r_x; | ||
156 | |||
157 | } else { | ||
158 | val_p = 2000; | ||
159 | } | ||
160 | |||
161 | val_p = 2000 - val_p; | ||
162 | dev_dbg(dev, | ||
163 | "Measured values: x: %d, y: %d, z1: %d, z2: %d, p: %d\n", | ||
164 | val_x, val_y, val_z1, val_z2, val_p); | ||
165 | |||
166 | /* | ||
167 | * If touch pressure is too low, stop measuring and reenable | ||
168 | * touch detection | ||
169 | */ | ||
170 | if (val_p < vf50_ts->min_pressure || val_p > 2000) | ||
171 | break; | ||
172 | |||
173 | /* | ||
174 | * The pressure may not be enough for the first x and the | ||
175 | * second y measurement, but, the pressure is ok when the | ||
176 | * driver is doing the third and fourth measurement. To | ||
177 | * take care of this, we drop the first measurement always. | ||
178 | */ | ||
179 | if (discard_val_on_start) { | ||
180 | discard_val_on_start = false; | ||
181 | } else { | ||
182 | /* | ||
183 | * Report touch position and sleep for | ||
184 | * the next measurement. | ||
185 | */ | ||
186 | input_report_abs(vf50_ts->ts_input, | ||
187 | ABS_X, VF_ADC_MAX - val_x); | ||
188 | input_report_abs(vf50_ts->ts_input, | ||
189 | ABS_Y, VF_ADC_MAX - val_y); | ||
190 | input_report_abs(vf50_ts->ts_input, | ||
191 | ABS_PRESSURE, val_p); | ||
192 | input_report_key(vf50_ts->ts_input, BTN_TOUCH, 1); | ||
193 | input_sync(vf50_ts->ts_input); | ||
194 | } | ||
195 | |||
196 | usleep_range(COLI_PULLUP_MIN_DELAY_US, | ||
197 | COLI_PULLUP_MAX_DELAY_US); | ||
198 | } | ||
199 | |||
200 | /* Report no more touch, re-enable touch detection */ | ||
201 | input_report_abs(vf50_ts->ts_input, ABS_PRESSURE, 0); | ||
202 | input_report_key(vf50_ts->ts_input, BTN_TOUCH, 0); | ||
203 | input_sync(vf50_ts->ts_input); | ||
204 | |||
205 | vf50_ts_enable_touch_detection(vf50_ts); | ||
206 | |||
207 | return IRQ_HANDLED; | ||
208 | } | ||
209 | |||
210 | static int vf50_ts_open(struct input_dev *dev_input) | ||
211 | { | ||
212 | struct vf50_touch_device *touchdev = input_get_drvdata(dev_input); | ||
213 | struct device *dev = &touchdev->pdev->dev; | ||
214 | |||
215 | dev_dbg(dev, "Input device %s opened, starting touch detection\n", | ||
216 | dev_input->name); | ||
217 | |||
218 | touchdev->stop_touchscreen = false; | ||
219 | |||
220 | /* Mux detection before request IRQ, wait for pull-up to settle */ | ||
221 | vf50_ts_enable_touch_detection(touchdev); | ||
222 | |||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static void vf50_ts_close(struct input_dev *dev_input) | ||
227 | { | ||
228 | struct vf50_touch_device *touchdev = input_get_drvdata(dev_input); | ||
229 | struct device *dev = &touchdev->pdev->dev; | ||
230 | |||
231 | touchdev->stop_touchscreen = true; | ||
232 | |||
233 | /* Make sure IRQ is not running past close */ | ||
234 | mb(); | ||
235 | synchronize_irq(touchdev->pen_irq); | ||
236 | |||
237 | gpiod_set_value(touchdev->gpio_ym, 0); | ||
238 | pinctrl_pm_select_default_state(dev); | ||
239 | |||
240 | dev_dbg(dev, "Input device %s closed, disable touch detection\n", | ||
241 | dev_input->name); | ||
242 | } | ||
243 | |||
244 | static int vf50_ts_get_gpiod(struct device *dev, struct gpio_desc **gpio_d, | ||
245 | const char *con_id, enum gpiod_flags flags) | ||
246 | { | ||
247 | int error; | ||
248 | |||
249 | *gpio_d = devm_gpiod_get(dev, con_id, flags); | ||
250 | if (IS_ERR(*gpio_d)) { | ||
251 | error = PTR_ERR(*gpio_d); | ||
252 | dev_err(dev, "Could not get gpio_%s %d\n", con_id, error); | ||
253 | return error; | ||
254 | } | ||
255 | |||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | static void vf50_ts_channel_release(void *data) | ||
260 | { | ||
261 | struct iio_channel *channels = data; | ||
262 | |||
263 | iio_channel_release_all(channels); | ||
264 | } | ||
265 | |||
266 | static int vf50_ts_probe(struct platform_device *pdev) | ||
267 | { | ||
268 | struct input_dev *input; | ||
269 | struct iio_channel *channels; | ||
270 | struct device *dev = &pdev->dev; | ||
271 | struct vf50_touch_device *touchdev; | ||
272 | int num_adc_channels; | ||
273 | int error; | ||
274 | |||
275 | channels = iio_channel_get_all(dev); | ||
276 | if (IS_ERR(channels)) | ||
277 | return PTR_ERR(channels); | ||
278 | |||
279 | error = devm_add_action(dev, vf50_ts_channel_release, channels); | ||
280 | if (error) { | ||
281 | iio_channel_release_all(channels); | ||
282 | dev_err(dev, "Failed to register iio channel release action"); | ||
283 | return error; | ||
284 | } | ||
285 | |||
286 | num_adc_channels = 0; | ||
287 | while (channels[num_adc_channels].indio_dev) | ||
288 | num_adc_channels++; | ||
289 | |||
290 | if (num_adc_channels != COLI_TOUCH_REQ_ADC_CHAN) { | ||
291 | dev_err(dev, "Inadequate ADC channels specified\n"); | ||
292 | return -EINVAL; | ||
293 | } | ||
294 | |||
295 | touchdev = devm_kzalloc(dev, sizeof(*touchdev), GFP_KERNEL); | ||
296 | if (!touchdev) | ||
297 | return -ENOMEM; | ||
298 | |||
299 | touchdev->pdev = pdev; | ||
300 | touchdev->channels = channels; | ||
301 | |||
302 | error = of_property_read_u32(dev->of_node, "vf50-ts-min-pressure", | ||
303 | &touchdev->min_pressure); | ||
304 | if (error) | ||
305 | return error; | ||
306 | |||
307 | input = devm_input_allocate_device(dev); | ||
308 | if (!input) { | ||
309 | dev_err(dev, "Failed to allocate TS input device\n"); | ||
310 | return -ENOMEM; | ||
311 | } | ||
312 | |||
313 | platform_set_drvdata(pdev, touchdev); | ||
314 | |||
315 | input->name = DRIVER_NAME; | ||
316 | input->id.bustype = BUS_HOST; | ||
317 | input->dev.parent = dev; | ||
318 | input->open = vf50_ts_open; | ||
319 | input->close = vf50_ts_close; | ||
320 | |||
321 | input_set_capability(input, EV_KEY, BTN_TOUCH); | ||
322 | input_set_abs_params(input, ABS_X, 0, VF_ADC_MAX, 0, 0); | ||
323 | input_set_abs_params(input, ABS_Y, 0, VF_ADC_MAX, 0, 0); | ||
324 | input_set_abs_params(input, ABS_PRESSURE, 0, VF_ADC_MAX, 0, 0); | ||
325 | |||
326 | touchdev->ts_input = input; | ||
327 | input_set_drvdata(input, touchdev); | ||
328 | |||
329 | error = input_register_device(input); | ||
330 | if (error) { | ||
331 | dev_err(dev, "Failed to register input device\n"); | ||
332 | return error; | ||
333 | } | ||
334 | |||
335 | error = vf50_ts_get_gpiod(dev, &touchdev->gpio_xp, "xp", GPIOD_OUT_LOW); | ||
336 | if (error) | ||
337 | return error; | ||
338 | |||
339 | error = vf50_ts_get_gpiod(dev, &touchdev->gpio_xm, | ||
340 | "xm", GPIOD_OUT_LOW); | ||
341 | if (error) | ||
342 | return error; | ||
343 | |||
344 | error = vf50_ts_get_gpiod(dev, &touchdev->gpio_yp, "yp", GPIOD_OUT_LOW); | ||
345 | if (error) | ||
346 | return error; | ||
347 | |||
348 | error = vf50_ts_get_gpiod(dev, &touchdev->gpio_ym, "ym", GPIOD_OUT_LOW); | ||
349 | if (error) | ||
350 | return error; | ||
351 | |||
352 | touchdev->pen_irq = platform_get_irq(pdev, 0); | ||
353 | if (touchdev->pen_irq < 0) | ||
354 | return touchdev->pen_irq; | ||
355 | |||
356 | error = devm_request_threaded_irq(dev, touchdev->pen_irq, | ||
357 | NULL, vf50_ts_irq_bh, IRQF_ONESHOT, | ||
358 | "vf50 touch", touchdev); | ||
359 | if (error) { | ||
360 | dev_err(dev, "Failed to request IRQ %d: %d\n", | ||
361 | touchdev->pen_irq, error); | ||
362 | return error; | ||
363 | } | ||
364 | |||
365 | return 0; | ||
366 | } | ||
367 | |||
368 | static const struct of_device_id vf50_touch_of_match[] = { | ||
369 | { .compatible = "toradex,vf50-touchscreen", }, | ||
370 | { } | ||
371 | }; | ||
372 | MODULE_DEVICE_TABLE(of, vf50_touch_of_match); | ||
373 | |||
374 | static struct platform_driver vf50_touch_driver = { | ||
375 | .driver = { | ||
376 | .name = "toradex,vf50_touchctrl", | ||
377 | .of_match_table = vf50_touch_of_match, | ||
378 | }, | ||
379 | .probe = vf50_ts_probe, | ||
380 | }; | ||
381 | module_platform_driver(vf50_touch_driver); | ||
382 | |||
383 | MODULE_AUTHOR("Sanchayan Maity"); | ||
384 | MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver"); | ||
385 | MODULE_LICENSE("GPL"); | ||
386 | MODULE_VERSION(DRV_VERSION); | ||