Commit | Line | Data |
---|---|---|
6decea7c HG |
1 | /* |
2 | * Allwinner sunxi resistive touchscreen controller driver | |
3 | * | |
4 | * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede@redhat.com> | |
5 | * | |
f09f98d3 HG |
6 | * The hwmon parts are based on work by Corentin LABBE which is: |
7 | * Copyright (C) 2013 Corentin LABBE <clabbe.montjoie@gmail.com> | |
8 | * | |
6decea7c HG |
9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | */ | |
19 | ||
20 | /* | |
21 | * The sun4i-ts controller is capable of detecting a second touch, but when a | |
22 | * second touch is present then the accuracy becomes so bad the reported touch | |
23 | * location is not useable. | |
24 | * | |
25 | * The original android driver contains some complicated heuristics using the | |
26 | * aprox. distance between the 2 touches to see if the user is making a pinch | |
27 | * open / close movement, and then reports emulated multi-touch events around | |
28 | * the last touch coordinate (as the dual-touch coordinates are worthless). | |
29 | * | |
30 | * These kinds of heuristics are just asking for trouble (and don't belong | |
31 | * in the kernel). So this driver offers straight forward, reliable single | |
32 | * touch functionality only. | |
4ed0e032 JT |
33 | * |
34 | * s.a. A20 User Manual "1.15 TP" (Documentation/arm/sunxi/README) | |
35 | * (looks like the description in the A20 User Manual v1.3 is better | |
36 | * than the one in the A10 User Manual v.1.5) | |
6decea7c HG |
37 | */ |
38 | ||
39 | #include <linux/err.h> | |
f09f98d3 | 40 | #include <linux/hwmon.h> |
22369710 | 41 | #include <linux/thermal.h> |
6decea7c HG |
42 | #include <linux/init.h> |
43 | #include <linux/input.h> | |
44 | #include <linux/interrupt.h> | |
45 | #include <linux/io.h> | |
46 | #include <linux/module.h> | |
47 | #include <linux/of_platform.h> | |
48 | #include <linux/platform_device.h> | |
49 | #include <linux/slab.h> | |
50 | ||
51 | #define TP_CTRL0 0x00 | |
52 | #define TP_CTRL1 0x04 | |
53 | #define TP_CTRL2 0x08 | |
54 | #define TP_CTRL3 0x0c | |
55 | #define TP_INT_FIFOC 0x10 | |
56 | #define TP_INT_FIFOS 0x14 | |
57 | #define TP_TPR 0x18 | |
58 | #define TP_CDAT 0x1c | |
59 | #define TEMP_DATA 0x20 | |
60 | #define TP_DATA 0x24 | |
61 | ||
62 | /* TP_CTRL0 bits */ | |
63 | #define ADC_FIRST_DLY(x) ((x) << 24) /* 8 bits */ | |
64 | #define ADC_FIRST_DLY_MODE(x) ((x) << 23) | |
65 | #define ADC_CLK_SEL(x) ((x) << 22) | |
66 | #define ADC_CLK_DIV(x) ((x) << 20) /* 3 bits */ | |
67 | #define FS_DIV(x) ((x) << 16) /* 4 bits */ | |
68 | #define T_ACQ(x) ((x) << 0) /* 16 bits */ | |
69 | ||
70 | /* TP_CTRL1 bits */ | |
71 | #define STYLUS_UP_DEBOUN(x) ((x) << 12) /* 8 bits */ | |
72 | #define STYLUS_UP_DEBOUN_EN(x) ((x) << 9) | |
73 | #define TOUCH_PAN_CALI_EN(x) ((x) << 6) | |
74 | #define TP_DUAL_EN(x) ((x) << 5) | |
75 | #define TP_MODE_EN(x) ((x) << 4) | |
76 | #define TP_ADC_SELECT(x) ((x) << 3) | |
77 | #define ADC_CHAN_SELECT(x) ((x) << 0) /* 3 bits */ | |
78 | ||
43c0e223 CYT |
79 | /* on sun6i, bits 3~6 are left shifted by 1 to 4~7 */ |
80 | #define SUN6I_TP_MODE_EN(x) ((x) << 5) | |
81 | ||
6decea7c HG |
82 | /* TP_CTRL2 bits */ |
83 | #define TP_SENSITIVE_ADJUST(x) ((x) << 28) /* 4 bits */ | |
84 | #define TP_MODE_SELECT(x) ((x) << 26) /* 2 bits */ | |
85 | #define PRE_MEA_EN(x) ((x) << 24) | |
86 | #define PRE_MEA_THRE_CNT(x) ((x) << 0) /* 24 bits */ | |
87 | ||
88 | /* TP_CTRL3 bits */ | |
89 | #define FILTER_EN(x) ((x) << 2) | |
90 | #define FILTER_TYPE(x) ((x) << 0) /* 2 bits */ | |
91 | ||
92 | /* TP_INT_FIFOC irq and fifo mask / control bits */ | |
93 | #define TEMP_IRQ_EN(x) ((x) << 18) | |
94 | #define OVERRUN_IRQ_EN(x) ((x) << 17) | |
95 | #define DATA_IRQ_EN(x) ((x) << 16) | |
96 | #define TP_DATA_XY_CHANGE(x) ((x) << 13) | |
97 | #define FIFO_TRIG(x) ((x) << 8) /* 5 bits */ | |
98 | #define DATA_DRQ_EN(x) ((x) << 7) | |
99 | #define FIFO_FLUSH(x) ((x) << 4) | |
100 | #define TP_UP_IRQ_EN(x) ((x) << 1) | |
101 | #define TP_DOWN_IRQ_EN(x) ((x) << 0) | |
102 | ||
103 | /* TP_INT_FIFOS irq and fifo status bits */ | |
104 | #define TEMP_DATA_PENDING BIT(18) | |
105 | #define FIFO_OVERRUN_PENDING BIT(17) | |
106 | #define FIFO_DATA_PENDING BIT(16) | |
107 | #define TP_IDLE_FLG BIT(2) | |
108 | #define TP_UP_PENDING BIT(1) | |
109 | #define TP_DOWN_PENDING BIT(0) | |
110 | ||
111 | /* TP_TPR bits */ | |
112 | #define TEMP_ENABLE(x) ((x) << 16) | |
113 | #define TEMP_PERIOD(x) ((x) << 0) /* t = x * 256 * 16 / clkin */ | |
114 | ||
115 | struct sun4i_ts_data { | |
116 | struct device *dev; | |
117 | struct input_dev *input; | |
118 | void __iomem *base; | |
119 | unsigned int irq; | |
120 | bool ignore_fifo_data; | |
f09f98d3 | 121 | int temp_data; |
43c0e223 CYT |
122 | int temp_offset; |
123 | int temp_step; | |
6decea7c HG |
124 | }; |
125 | ||
f09f98d3 | 126 | static void sun4i_ts_irq_handle_input(struct sun4i_ts_data *ts, u32 reg_val) |
6decea7c | 127 | { |
f09f98d3 | 128 | u32 x, y; |
6decea7c HG |
129 | |
130 | if (reg_val & FIFO_DATA_PENDING) { | |
131 | x = readl(ts->base + TP_DATA); | |
132 | y = readl(ts->base + TP_DATA); | |
133 | /* The 1st location reported after an up event is unreliable */ | |
134 | if (!ts->ignore_fifo_data) { | |
135 | input_report_abs(ts->input, ABS_X, x); | |
136 | input_report_abs(ts->input, ABS_Y, y); | |
137 | /* | |
138 | * The hardware has a separate down status bit, but | |
139 | * that gets set before we get the first location, | |
140 | * resulting in reporting a click on the old location. | |
141 | */ | |
142 | input_report_key(ts->input, BTN_TOUCH, 1); | |
143 | input_sync(ts->input); | |
144 | } else { | |
145 | ts->ignore_fifo_data = false; | |
146 | } | |
147 | } | |
148 | ||
149 | if (reg_val & TP_UP_PENDING) { | |
150 | ts->ignore_fifo_data = true; | |
151 | input_report_key(ts->input, BTN_TOUCH, 0); | |
152 | input_sync(ts->input); | |
153 | } | |
f09f98d3 HG |
154 | } |
155 | ||
156 | static irqreturn_t sun4i_ts_irq(int irq, void *dev_id) | |
157 | { | |
158 | struct sun4i_ts_data *ts = dev_id; | |
159 | u32 reg_val; | |
160 | ||
161 | reg_val = readl(ts->base + TP_INT_FIFOS); | |
162 | ||
163 | if (reg_val & TEMP_DATA_PENDING) | |
164 | ts->temp_data = readl(ts->base + TEMP_DATA); | |
165 | ||
166 | if (ts->input) | |
167 | sun4i_ts_irq_handle_input(ts, reg_val); | |
6decea7c HG |
168 | |
169 | writel(reg_val, ts->base + TP_INT_FIFOS); | |
170 | ||
171 | return IRQ_HANDLED; | |
172 | } | |
173 | ||
174 | static int sun4i_ts_open(struct input_dev *dev) | |
175 | { | |
176 | struct sun4i_ts_data *ts = input_get_drvdata(dev); | |
177 | ||
f09f98d3 HG |
178 | /* Flush, set trig level to 1, enable temp, data and up irqs */ |
179 | writel(TEMP_IRQ_EN(1) | DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) | | |
180 | TP_UP_IRQ_EN(1), ts->base + TP_INT_FIFOC); | |
6decea7c HG |
181 | |
182 | return 0; | |
183 | } | |
184 | ||
185 | static void sun4i_ts_close(struct input_dev *dev) | |
186 | { | |
187 | struct sun4i_ts_data *ts = input_get_drvdata(dev); | |
188 | ||
f09f98d3 HG |
189 | /* Deactivate all input IRQs */ |
190 | writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC); | |
191 | } | |
192 | ||
17e8351a | 193 | static int sun4i_get_temp(const struct sun4i_ts_data *ts, int *temp) |
22369710 CYT |
194 | { |
195 | /* No temp_data until the first irq */ | |
196 | if (ts->temp_data == -1) | |
197 | return -EAGAIN; | |
198 | ||
877bef7d | 199 | *temp = ts->temp_data * ts->temp_step - ts->temp_offset; |
22369710 CYT |
200 | |
201 | return 0; | |
202 | } | |
203 | ||
17e8351a | 204 | static int sun4i_get_tz_temp(void *data, int *temp) |
22369710 CYT |
205 | { |
206 | return sun4i_get_temp(data, temp); | |
207 | } | |
208 | ||
209 | static struct thermal_zone_of_device_ops sun4i_ts_tz_ops = { | |
210 | .get_temp = sun4i_get_tz_temp, | |
211 | }; | |
212 | ||
f09f98d3 HG |
213 | static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, |
214 | char *buf) | |
215 | { | |
216 | struct sun4i_ts_data *ts = dev_get_drvdata(dev); | |
17e8351a | 217 | int temp; |
22369710 | 218 | int error; |
f09f98d3 | 219 | |
22369710 CYT |
220 | error = sun4i_get_temp(ts, &temp); |
221 | if (error) | |
222 | return error; | |
f09f98d3 | 223 | |
17e8351a | 224 | return sprintf(buf, "%d\n", temp); |
f09f98d3 HG |
225 | } |
226 | ||
227 | static ssize_t show_temp_label(struct device *dev, | |
228 | struct device_attribute *devattr, char *buf) | |
229 | { | |
230 | return sprintf(buf, "SoC temperature\n"); | |
6decea7c HG |
231 | } |
232 | ||
f09f98d3 HG |
233 | static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL); |
234 | static DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL); | |
235 | ||
236 | static struct attribute *sun4i_ts_attrs[] = { | |
237 | &dev_attr_temp1_input.attr, | |
238 | &dev_attr_temp1_label.attr, | |
239 | NULL | |
240 | }; | |
241 | ATTRIBUTE_GROUPS(sun4i_ts); | |
242 | ||
6decea7c HG |
243 | static int sun4i_ts_probe(struct platform_device *pdev) |
244 | { | |
245 | struct sun4i_ts_data *ts; | |
246 | struct device *dev = &pdev->dev; | |
f09f98d3 HG |
247 | struct device_node *np = dev->of_node; |
248 | struct device *hwmon; | |
6decea7c | 249 | int error; |
43c0e223 | 250 | u32 reg; |
f09f98d3 | 251 | bool ts_attached; |
4ed0e032 JT |
252 | u32 tp_sensitive_adjust = 15; |
253 | u32 filter_type = 1; | |
6decea7c HG |
254 | |
255 | ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL); | |
256 | if (!ts) | |
257 | return -ENOMEM; | |
258 | ||
259 | ts->dev = dev; | |
260 | ts->ignore_fifo_data = true; | |
f09f98d3 | 261 | ts->temp_data = -1; |
43c0e223 | 262 | if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts")) { |
877bef7d HG |
263 | /* Allwinner SDK has temperature (C) = (value / 6) - 271 */ |
264 | ts->temp_offset = 271000; | |
43c0e223 | 265 | ts->temp_step = 167; |
91c68a7c HG |
266 | } else if (of_device_is_compatible(np, "allwinner,sun4i-a10-ts")) { |
267 | /* | |
268 | * The A10 temperature sensor has quite a wide spread, these | |
269 | * parameters are based on the averaging of the calibration | |
270 | * results of 4 completely different boards, with a spread of | |
877bef7d | 271 | * temp_step from 0.096 - 0.170 and temp_offset from 176 - 331. |
91c68a7c | 272 | */ |
877bef7d | 273 | ts->temp_offset = 257000; |
91c68a7c | 274 | ts->temp_step = 133; |
43c0e223 CYT |
275 | } else { |
276 | /* | |
277 | * The user manuals do not contain the formula for calculating | |
278 | * the temperature. The formula used here is from the AXP209, | |
279 | * which is designed by X-Powers, an affiliate of Allwinner: | |
280 | * | |
877bef7d | 281 | * temperature (C) = (value * 0.1) - 144.7 |
43c0e223 CYT |
282 | * |
283 | * Allwinner does not have any documentation whatsoever for | |
284 | * this hardware. Moreover, it is claimed that the sensor | |
285 | * is inaccurate and cannot work properly. | |
286 | */ | |
877bef7d | 287 | ts->temp_offset = 144700; |
43c0e223 CYT |
288 | ts->temp_step = 100; |
289 | } | |
f09f98d3 HG |
290 | |
291 | ts_attached = of_property_read_bool(np, "allwinner,ts-attached"); | |
292 | if (ts_attached) { | |
293 | ts->input = devm_input_allocate_device(dev); | |
294 | if (!ts->input) | |
295 | return -ENOMEM; | |
296 | ||
297 | ts->input->name = pdev->name; | |
298 | ts->input->phys = "sun4i_ts/input0"; | |
299 | ts->input->open = sun4i_ts_open; | |
300 | ts->input->close = sun4i_ts_close; | |
301 | ts->input->id.bustype = BUS_HOST; | |
302 | ts->input->id.vendor = 0x0001; | |
303 | ts->input->id.product = 0x0001; | |
304 | ts->input->id.version = 0x0100; | |
305 | ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); | |
306 | __set_bit(BTN_TOUCH, ts->input->keybit); | |
307 | input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0); | |
308 | input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0); | |
309 | input_set_drvdata(ts->input, ts); | |
310 | } | |
6decea7c HG |
311 | |
312 | ts->base = devm_ioremap_resource(dev, | |
313 | platform_get_resource(pdev, IORESOURCE_MEM, 0)); | |
314 | if (IS_ERR(ts->base)) | |
315 | return PTR_ERR(ts->base); | |
316 | ||
317 | ts->irq = platform_get_irq(pdev, 0); | |
318 | error = devm_request_irq(dev, ts->irq, sun4i_ts_irq, 0, "sun4i-ts", ts); | |
319 | if (error) | |
320 | return error; | |
321 | ||
322 | /* | |
323 | * Select HOSC clk, clkin = clk / 6, adc samplefreq = clkin / 8192, | |
324 | * t_acq = clkin / (16 * 64) | |
325 | */ | |
326 | writel(ADC_CLK_SEL(0) | ADC_CLK_DIV(2) | FS_DIV(7) | T_ACQ(63), | |
327 | ts->base + TP_CTRL0); | |
328 | ||
329 | /* | |
4ed0e032 | 330 | * tp_sensitive_adjust is an optional property |
6decea7c HG |
331 | * tp_mode = 0 : only x and y coordinates, as we don't use dual touch |
332 | */ | |
4ed0e032 JT |
333 | of_property_read_u32(np, "allwinner,tp-sensitive-adjust", |
334 | &tp_sensitive_adjust); | |
335 | writel(TP_SENSITIVE_ADJUST(tp_sensitive_adjust) | TP_MODE_SELECT(0), | |
6decea7c HG |
336 | ts->base + TP_CTRL2); |
337 | ||
4ed0e032 JT |
338 | /* |
339 | * Enable median and averaging filter, optional property for | |
340 | * filter type. | |
341 | */ | |
342 | of_property_read_u32(np, "allwinner,filter-type", &filter_type); | |
343 | writel(FILTER_EN(1) | FILTER_TYPE(filter_type), ts->base + TP_CTRL3); | |
6decea7c HG |
344 | |
345 | /* Enable temperature measurement, period 1953 (2 seconds) */ | |
346 | writel(TEMP_ENABLE(1) | TEMP_PERIOD(1953), ts->base + TP_TPR); | |
347 | ||
348 | /* | |
349 | * Set stylus up debounce to aprox 10 ms, enable debounce, and | |
350 | * finally enable tp mode. | |
351 | */ | |
43c0e223 | 352 | reg = STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1); |
91c68a7c | 353 | if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts")) |
43c0e223 | 354 | reg |= SUN6I_TP_MODE_EN(1); |
91c68a7c HG |
355 | else |
356 | reg |= TP_MODE_EN(1); | |
43c0e223 | 357 | writel(reg, ts->base + TP_CTRL1); |
6decea7c | 358 | |
22369710 CYT |
359 | /* |
360 | * The thermal core does not register hwmon devices for DT-based | |
361 | * thermal zone sensors, such as this one. | |
362 | */ | |
f09f98d3 HG |
363 | hwmon = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts", |
364 | ts, sun4i_ts_groups); | |
365 | if (IS_ERR(hwmon)) | |
366 | return PTR_ERR(hwmon); | |
367 | ||
e28d0c9c | 368 | devm_thermal_zone_of_sensor_register(ts->dev, 0, ts, &sun4i_ts_tz_ops); |
22369710 | 369 | |
f09f98d3 HG |
370 | writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC); |
371 | ||
372 | if (ts_attached) { | |
373 | error = input_register_device(ts->input); | |
374 | if (error) { | |
375 | writel(0, ts->base + TP_INT_FIFOC); | |
376 | return error; | |
377 | } | |
378 | } | |
6decea7c HG |
379 | |
380 | platform_set_drvdata(pdev, ts); | |
381 | return 0; | |
382 | } | |
383 | ||
f09f98d3 HG |
384 | static int sun4i_ts_remove(struct platform_device *pdev) |
385 | { | |
386 | struct sun4i_ts_data *ts = platform_get_drvdata(pdev); | |
387 | ||
388 | /* Explicit unregister to avoid open/close changing the imask later */ | |
389 | if (ts->input) | |
390 | input_unregister_device(ts->input); | |
391 | ||
392 | /* Deactivate all IRQs */ | |
393 | writel(0, ts->base + TP_INT_FIFOC); | |
394 | ||
395 | return 0; | |
396 | } | |
397 | ||
6decea7c HG |
398 | static const struct of_device_id sun4i_ts_of_match[] = { |
399 | { .compatible = "allwinner,sun4i-a10-ts", }, | |
91c68a7c | 400 | { .compatible = "allwinner,sun5i-a13-ts", }, |
43c0e223 | 401 | { .compatible = "allwinner,sun6i-a31-ts", }, |
6decea7c HG |
402 | { /* sentinel */ } |
403 | }; | |
404 | MODULE_DEVICE_TABLE(of, sun4i_ts_of_match); | |
405 | ||
406 | static struct platform_driver sun4i_ts_driver = { | |
407 | .driver = { | |
6decea7c HG |
408 | .name = "sun4i-ts", |
409 | .of_match_table = of_match_ptr(sun4i_ts_of_match), | |
410 | }, | |
411 | .probe = sun4i_ts_probe, | |
f09f98d3 | 412 | .remove = sun4i_ts_remove, |
6decea7c HG |
413 | }; |
414 | ||
415 | module_platform_driver(sun4i_ts_driver); | |
416 | ||
417 | MODULE_DESCRIPTION("Allwinner sun4i resistive touchscreen controller driver"); | |
418 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | |
419 | MODULE_LICENSE("GPL"); |