1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Toradex Colibri VF50 Touchscreen driver |
4 | * |
5 | * Copyright 2015 Toradex AG |
6 | * |
7 | * Originally authored by Stefan Agner for 3.0 kernel |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/err.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/iio/consumer.h> |
14 | #include <linux/iio/types.h> |
15 | #include <linux/input.h> |
16 | #include <linux/interrupt.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/module.h> |
19 | #include <linux/of.h> |
20 | #include <linux/pinctrl/consumer.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/types.h> |
24 | |
25 | #define DRIVER_NAME "colibri-vf50-ts" |
26 | |
27 | #define VF_ADC_MAX ((1 << 12) - 1) |
28 | |
29 | #define COLI_TOUCH_MIN_DELAY_US 1000 |
30 | #define COLI_TOUCH_MAX_DELAY_US 2000 |
31 | #define COLI_PULLUP_MIN_DELAY_US 10000 |
32 | #define COLI_PULLUP_MAX_DELAY_US 11000 |
33 | #define COLI_TOUCH_NO_OF_AVGS 5 |
34 | #define COLI_TOUCH_REQ_ADC_CHAN 4 |
35 | |
36 | struct vf50_touch_device { |
37 | struct platform_device *pdev; |
38 | struct input_dev *ts_input; |
39 | struct iio_channel *channels; |
40 | struct gpio_desc *gpio_xp; |
41 | struct gpio_desc *gpio_xm; |
42 | struct gpio_desc *gpio_yp; |
43 | struct gpio_desc *gpio_ym; |
44 | int pen_irq; |
45 | int min_pressure; |
46 | bool stop_touchscreen; |
47 | }; |
48 | |
49 | /* |
50 | * Enables given plates and measures touch parameters using ADC |
51 | */ |
52 | static int adc_ts_measure(struct iio_channel *channel, |
53 | struct gpio_desc *plate_p, struct gpio_desc *plate_m) |
54 | { |
55 | int i, value = 0, val = 0; |
56 | int error; |
57 | |
58 | gpiod_set_value(desc: plate_p, value: 1); |
59 | gpiod_set_value(desc: plate_m, value: 1); |
60 | |
61 | usleep_range(COLI_TOUCH_MIN_DELAY_US, COLI_TOUCH_MAX_DELAY_US); |
62 | |
63 | for (i = 0; i < COLI_TOUCH_NO_OF_AVGS; i++) { |
64 | error = iio_read_channel_raw(chan: channel, val: &val); |
65 | if (error < 0) { |
66 | value = error; |
67 | goto error_iio_read; |
68 | } |
69 | |
70 | value += val; |
71 | } |
72 | |
73 | value /= COLI_TOUCH_NO_OF_AVGS; |
74 | |
75 | error_iio_read: |
76 | gpiod_set_value(desc: plate_p, value: 0); |
77 | gpiod_set_value(desc: plate_m, value: 0); |
78 | |
79 | return value; |
80 | } |
81 | |
82 | /* |
83 | * Enable touch detection using falling edge detection on XM |
84 | */ |
85 | static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts) |
86 | { |
87 | /* Enable plate YM (needs to be strong GND, high active) */ |
88 | gpiod_set_value(desc: vf50_ts->gpio_ym, value: 1); |
89 | |
90 | /* |
91 | * Let the platform mux to idle state in order to enable |
92 | * Pull-Up on GPIO |
93 | */ |
94 | pinctrl_pm_select_idle_state(dev: &vf50_ts->pdev->dev); |
95 | |
96 | /* Wait for the pull-up to be stable on high */ |
97 | usleep_range(COLI_PULLUP_MIN_DELAY_US, COLI_PULLUP_MAX_DELAY_US); |
98 | } |
99 | |
100 | /* |
101 | * ADC touch screen sampling bottom half irq handler |
102 | */ |
103 | static irqreturn_t vf50_ts_irq_bh(int irq, void *private) |
104 | { |
105 | struct vf50_touch_device *vf50_ts = private; |
106 | struct device *dev = &vf50_ts->pdev->dev; |
107 | int val_x, val_y, val_z1, val_z2, val_p = 0; |
108 | bool discard_val_on_start = true; |
109 | |
110 | /* Disable the touch detection plates */ |
111 | gpiod_set_value(desc: vf50_ts->gpio_ym, value: 0); |
112 | |
113 | /* Let the platform mux to default state in order to mux as ADC */ |
114 | pinctrl_pm_select_default_state(dev); |
115 | |
116 | while (!vf50_ts->stop_touchscreen) { |
117 | /* X-Direction */ |
118 | val_x = adc_ts_measure(channel: &vf50_ts->channels[0], |
119 | plate_p: vf50_ts->gpio_xp, plate_m: vf50_ts->gpio_xm); |
120 | if (val_x < 0) |
121 | break; |
122 | |
123 | /* Y-Direction */ |
124 | val_y = adc_ts_measure(channel: &vf50_ts->channels[1], |
125 | plate_p: vf50_ts->gpio_yp, plate_m: vf50_ts->gpio_ym); |
126 | if (val_y < 0) |
127 | break; |
128 | |
129 | /* |
130 | * Touch pressure |
131 | * Measure on XP/YM |
132 | */ |
133 | val_z1 = adc_ts_measure(channel: &vf50_ts->channels[2], |
134 | plate_p: vf50_ts->gpio_yp, plate_m: vf50_ts->gpio_xm); |
135 | if (val_z1 < 0) |
136 | break; |
137 | val_z2 = adc_ts_measure(channel: &vf50_ts->channels[3], |
138 | plate_p: vf50_ts->gpio_yp, plate_m: vf50_ts->gpio_xm); |
139 | if (val_z2 < 0) |
140 | break; |
141 | |
142 | /* Validate signal (avoid calculation using noise) */ |
143 | if (val_z1 > 64 && val_x > 64) { |
144 | /* |
145 | * Calculate resistance between the plates |
146 | * lower resistance means higher pressure |
147 | */ |
148 | int r_x = (1000 * val_x) / VF_ADC_MAX; |
149 | |
150 | val_p = (r_x * val_z2) / val_z1 - r_x; |
151 | |
152 | } else { |
153 | val_p = 2000; |
154 | } |
155 | |
156 | val_p = 2000 - val_p; |
157 | dev_dbg(dev, |
158 | "Measured values: x: %d, y: %d, z1: %d, z2: %d, p: %d\n" , |
159 | val_x, val_y, val_z1, val_z2, val_p); |
160 | |
161 | /* |
162 | * If touch pressure is too low, stop measuring and reenable |
163 | * touch detection |
164 | */ |
165 | if (val_p < vf50_ts->min_pressure || val_p > 2000) |
166 | break; |
167 | |
168 | /* |
169 | * The pressure may not be enough for the first x and the |
170 | * second y measurement, but, the pressure is ok when the |
171 | * driver is doing the third and fourth measurement. To |
172 | * take care of this, we drop the first measurement always. |
173 | */ |
174 | if (discard_val_on_start) { |
175 | discard_val_on_start = false; |
176 | } else { |
177 | /* |
178 | * Report touch position and sleep for |
179 | * the next measurement. |
180 | */ |
181 | input_report_abs(dev: vf50_ts->ts_input, |
182 | ABS_X, VF_ADC_MAX - val_x); |
183 | input_report_abs(dev: vf50_ts->ts_input, |
184 | ABS_Y, VF_ADC_MAX - val_y); |
185 | input_report_abs(dev: vf50_ts->ts_input, |
186 | ABS_PRESSURE, value: val_p); |
187 | input_report_key(dev: vf50_ts->ts_input, BTN_TOUCH, value: 1); |
188 | input_sync(dev: vf50_ts->ts_input); |
189 | } |
190 | |
191 | usleep_range(COLI_PULLUP_MIN_DELAY_US, |
192 | COLI_PULLUP_MAX_DELAY_US); |
193 | } |
194 | |
195 | /* Report no more touch, re-enable touch detection */ |
196 | input_report_abs(dev: vf50_ts->ts_input, ABS_PRESSURE, value: 0); |
197 | input_report_key(dev: vf50_ts->ts_input, BTN_TOUCH, value: 0); |
198 | input_sync(dev: vf50_ts->ts_input); |
199 | |
200 | vf50_ts_enable_touch_detection(vf50_ts); |
201 | |
202 | return IRQ_HANDLED; |
203 | } |
204 | |
205 | static int vf50_ts_open(struct input_dev *dev_input) |
206 | { |
207 | struct vf50_touch_device *touchdev = input_get_drvdata(dev: dev_input); |
208 | struct device *dev = &touchdev->pdev->dev; |
209 | |
210 | dev_dbg(dev, "Input device %s opened, starting touch detection\n" , |
211 | dev_input->name); |
212 | |
213 | touchdev->stop_touchscreen = false; |
214 | |
215 | /* Mux detection before request IRQ, wait for pull-up to settle */ |
216 | vf50_ts_enable_touch_detection(vf50_ts: touchdev); |
217 | |
218 | return 0; |
219 | } |
220 | |
221 | static void vf50_ts_close(struct input_dev *dev_input) |
222 | { |
223 | struct vf50_touch_device *touchdev = input_get_drvdata(dev: dev_input); |
224 | struct device *dev = &touchdev->pdev->dev; |
225 | |
226 | touchdev->stop_touchscreen = true; |
227 | |
228 | /* Make sure IRQ is not running past close */ |
229 | mb(); |
230 | synchronize_irq(irq: touchdev->pen_irq); |
231 | |
232 | gpiod_set_value(desc: touchdev->gpio_ym, value: 0); |
233 | pinctrl_pm_select_default_state(dev); |
234 | |
235 | dev_dbg(dev, "Input device %s closed, disable touch detection\n" , |
236 | dev_input->name); |
237 | } |
238 | |
239 | static int vf50_ts_get_gpiod(struct device *dev, struct gpio_desc **gpio_d, |
240 | const char *con_id, enum gpiod_flags flags) |
241 | { |
242 | int error; |
243 | |
244 | *gpio_d = devm_gpiod_get(dev, con_id, flags); |
245 | if (IS_ERR(ptr: *gpio_d)) { |
246 | error = PTR_ERR(ptr: *gpio_d); |
247 | dev_err(dev, "Could not get gpio_%s %d\n" , con_id, error); |
248 | return error; |
249 | } |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | static void vf50_ts_channel_release(void *data) |
255 | { |
256 | struct iio_channel *channels = data; |
257 | |
258 | iio_channel_release_all(chan: channels); |
259 | } |
260 | |
261 | static int vf50_ts_probe(struct platform_device *pdev) |
262 | { |
263 | struct input_dev *input; |
264 | struct iio_channel *channels; |
265 | struct device *dev = &pdev->dev; |
266 | struct vf50_touch_device *touchdev; |
267 | int num_adc_channels; |
268 | int error; |
269 | |
270 | channels = iio_channel_get_all(dev); |
271 | if (IS_ERR(ptr: channels)) |
272 | return PTR_ERR(ptr: channels); |
273 | |
274 | error = devm_add_action(dev, vf50_ts_channel_release, channels); |
275 | if (error) { |
276 | iio_channel_release_all(chan: channels); |
277 | dev_err(dev, "Failed to register iio channel release action" ); |
278 | return error; |
279 | } |
280 | |
281 | num_adc_channels = 0; |
282 | while (channels[num_adc_channels].indio_dev) |
283 | num_adc_channels++; |
284 | |
285 | if (num_adc_channels != COLI_TOUCH_REQ_ADC_CHAN) { |
286 | dev_err(dev, "Inadequate ADC channels specified\n" ); |
287 | return -EINVAL; |
288 | } |
289 | |
290 | touchdev = devm_kzalloc(dev, size: sizeof(*touchdev), GFP_KERNEL); |
291 | if (!touchdev) |
292 | return -ENOMEM; |
293 | |
294 | touchdev->pdev = pdev; |
295 | touchdev->channels = channels; |
296 | |
297 | error = of_property_read_u32(np: dev->of_node, propname: "vf50-ts-min-pressure" , |
298 | out_value: &touchdev->min_pressure); |
299 | if (error) |
300 | return error; |
301 | |
302 | input = devm_input_allocate_device(dev); |
303 | if (!input) { |
304 | dev_err(dev, "Failed to allocate TS input device\n" ); |
305 | return -ENOMEM; |
306 | } |
307 | |
308 | input->name = DRIVER_NAME; |
309 | input->id.bustype = BUS_HOST; |
310 | input->dev.parent = dev; |
311 | input->open = vf50_ts_open; |
312 | input->close = vf50_ts_close; |
313 | |
314 | input_set_capability(dev: input, EV_KEY, BTN_TOUCH); |
315 | input_set_abs_params(dev: input, ABS_X, min: 0, VF_ADC_MAX, fuzz: 0, flat: 0); |
316 | input_set_abs_params(dev: input, ABS_Y, min: 0, VF_ADC_MAX, fuzz: 0, flat: 0); |
317 | input_set_abs_params(dev: input, ABS_PRESSURE, min: 0, VF_ADC_MAX, fuzz: 0, flat: 0); |
318 | |
319 | touchdev->ts_input = input; |
320 | input_set_drvdata(dev: input, data: touchdev); |
321 | |
322 | error = input_register_device(input); |
323 | if (error) { |
324 | dev_err(dev, "Failed to register input device\n" ); |
325 | return error; |
326 | } |
327 | |
328 | error = vf50_ts_get_gpiod(dev, gpio_d: &touchdev->gpio_xp, con_id: "xp" , flags: GPIOD_OUT_LOW); |
329 | if (error) |
330 | return error; |
331 | |
332 | error = vf50_ts_get_gpiod(dev, gpio_d: &touchdev->gpio_xm, |
333 | con_id: "xm" , flags: GPIOD_OUT_LOW); |
334 | if (error) |
335 | return error; |
336 | |
337 | error = vf50_ts_get_gpiod(dev, gpio_d: &touchdev->gpio_yp, con_id: "yp" , flags: GPIOD_OUT_LOW); |
338 | if (error) |
339 | return error; |
340 | |
341 | error = vf50_ts_get_gpiod(dev, gpio_d: &touchdev->gpio_ym, con_id: "ym" , flags: GPIOD_OUT_LOW); |
342 | if (error) |
343 | return error; |
344 | |
345 | touchdev->pen_irq = platform_get_irq(pdev, 0); |
346 | if (touchdev->pen_irq < 0) |
347 | return touchdev->pen_irq; |
348 | |
349 | error = devm_request_threaded_irq(dev, irq: touchdev->pen_irq, |
350 | NULL, thread_fn: vf50_ts_irq_bh, IRQF_ONESHOT, |
351 | devname: "vf50 touch" , dev_id: touchdev); |
352 | if (error) { |
353 | dev_err(dev, "Failed to request IRQ %d: %d\n" , |
354 | touchdev->pen_irq, error); |
355 | return error; |
356 | } |
357 | |
358 | return 0; |
359 | } |
360 | |
361 | static const struct of_device_id vf50_touch_of_match[] = { |
362 | { .compatible = "toradex,vf50-touchscreen" , }, |
363 | { } |
364 | }; |
365 | MODULE_DEVICE_TABLE(of, vf50_touch_of_match); |
366 | |
367 | static struct platform_driver vf50_touch_driver = { |
368 | .driver = { |
369 | .name = "toradex,vf50_touchctrl" , |
370 | .of_match_table = vf50_touch_of_match, |
371 | }, |
372 | .probe = vf50_ts_probe, |
373 | }; |
374 | module_platform_driver(vf50_touch_driver); |
375 | |
376 | MODULE_AUTHOR("Sanchayan Maity" ); |
377 | MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver" ); |
378 | MODULE_LICENSE("GPL" ); |
379 | |