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