1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Allwinner sunxi resistive touchscreen controller driver |
4 | * |
5 | * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede@redhat.com> |
6 | * |
7 | * The hwmon parts are based on work by Corentin LABBE which is: |
8 | * Copyright (C) 2013 Corentin LABBE <clabbe.montjoie@gmail.com> |
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. |
24 | * |
25 | * s.a. A20 User Manual "1.15 TP" (Documentation/arch/arm/sunxi.rst) |
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) |
28 | */ |
29 | |
30 | #include <linux/err.h> |
31 | #include <linux/hwmon.h> |
32 | #include <linux/thermal.h> |
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 | |
70 | /* on sun6i, bits 3~6 are left shifted by 1 to 4~7 */ |
71 | #define SUN6I_TP_MODE_EN(x) ((x) << 5) |
72 | |
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; |
112 | int temp_data; |
113 | int temp_offset; |
114 | int temp_step; |
115 | }; |
116 | |
117 | static void sun4i_ts_irq_handle_input(struct sun4i_ts_data *ts, u32 reg_val) |
118 | { |
119 | u32 x, y; |
120 | |
121 | if (reg_val & FIFO_DATA_PENDING) { |
122 | x = readl(addr: ts->base + TP_DATA); |
123 | y = readl(addr: 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(dev: ts->input, ABS_X, value: x); |
127 | input_report_abs(dev: ts->input, ABS_Y, value: 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(dev: ts->input, BTN_TOUCH, value: 1); |
134 | input_sync(dev: 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(dev: ts->input, BTN_TOUCH, value: 0); |
143 | input_sync(dev: ts->input); |
144 | } |
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(addr: ts->base + TP_INT_FIFOS); |
153 | |
154 | if (reg_val & TEMP_DATA_PENDING) |
155 | ts->temp_data = readl(addr: ts->base + TEMP_DATA); |
156 | |
157 | if (ts->input) |
158 | sun4i_ts_irq_handle_input(ts, reg_val); |
159 | |
160 | writel(val: reg_val, addr: 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 | |
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), addr: ts->base + TP_INT_FIFOC); |
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 | |
180 | /* Deactivate all input IRQs */ |
181 | writel(TEMP_IRQ_EN(1), addr: ts->base + TP_INT_FIFOC); |
182 | } |
183 | |
184 | static int sun4i_get_temp(const struct sun4i_ts_data *ts, int *temp) |
185 | { |
186 | /* No temp_data until the first irq */ |
187 | if (ts->temp_data == -1) |
188 | return -EAGAIN; |
189 | |
190 | *temp = ts->temp_data * ts->temp_step - ts->temp_offset; |
191 | |
192 | return 0; |
193 | } |
194 | |
195 | static int sun4i_get_tz_temp(struct thermal_zone_device *tz, int *temp) |
196 | { |
197 | return sun4i_get_temp(ts: thermal_zone_device_priv(tzd: tz), temp); |
198 | } |
199 | |
200 | static const struct thermal_zone_device_ops sun4i_ts_tz_ops = { |
201 | .get_temp = sun4i_get_tz_temp, |
202 | }; |
203 | |
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); |
208 | int temp; |
209 | int error; |
210 | |
211 | error = sun4i_get_temp(ts, temp: &temp); |
212 | if (error) |
213 | return error; |
214 | |
215 | return sprintf(buf, fmt: "%d\n" , temp); |
216 | } |
217 | |
218 | static ssize_t show_temp_label(struct device *dev, |
219 | struct device_attribute *devattr, char *buf) |
220 | { |
221 | return sprintf(buf, fmt: "SoC temperature\n" ); |
222 | } |
223 | |
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 | |
234 | static int sun4i_ts_probe(struct platform_device *pdev) |
235 | { |
236 | struct sun4i_ts_data *ts; |
237 | struct device *dev = &pdev->dev; |
238 | struct device_node *np = dev->of_node; |
239 | struct device *hwmon; |
240 | struct thermal_zone_device *thermal; |
241 | int error; |
242 | u32 reg; |
243 | bool ts_attached; |
244 | u32 tp_sensitive_adjust = 15; |
245 | u32 filter_type = 1; |
246 | |
247 | ts = devm_kzalloc(dev, size: sizeof(struct sun4i_ts_data), GFP_KERNEL); |
248 | if (!ts) |
249 | return -ENOMEM; |
250 | |
251 | ts->dev = dev; |
252 | ts->ignore_fifo_data = true; |
253 | ts->temp_data = -1; |
254 | if (of_device_is_compatible(device: np, "allwinner,sun6i-a31-ts" )) { |
255 | /* Allwinner SDK has temperature (C) = (value / 6) - 271 */ |
256 | ts->temp_offset = 271000; |
257 | ts->temp_step = 167; |
258 | } else if (of_device_is_compatible(device: np, "allwinner,sun4i-a10-ts" )) { |
259 | /* |
260 | * The A10 temperature sensor has quite a wide spread, these |
261 | * parameters are based on the averaging of the calibration |
262 | * results of 4 completely different boards, with a spread of |
263 | * temp_step from 0.096 - 0.170 and temp_offset from 176 - 331. |
264 | */ |
265 | ts->temp_offset = 257000; |
266 | ts->temp_step = 133; |
267 | } else { |
268 | /* |
269 | * The user manuals do not contain the formula for calculating |
270 | * the temperature. The formula used here is from the AXP209, |
271 | * which is designed by X-Powers, an affiliate of Allwinner: |
272 | * |
273 | * temperature (C) = (value * 0.1) - 144.7 |
274 | * |
275 | * Allwinner does not have any documentation whatsoever for |
276 | * this hardware. Moreover, it is claimed that the sensor |
277 | * is inaccurate and cannot work properly. |
278 | */ |
279 | ts->temp_offset = 144700; |
280 | ts->temp_step = 100; |
281 | } |
282 | |
283 | ts_attached = of_property_read_bool(np, propname: "allwinner,ts-attached" ); |
284 | if (ts_attached) { |
285 | ts->input = devm_input_allocate_device(dev); |
286 | if (!ts->input) |
287 | return -ENOMEM; |
288 | |
289 | ts->input->name = pdev->name; |
290 | ts->input->phys = "sun4i_ts/input0" ; |
291 | ts->input->open = sun4i_ts_open; |
292 | ts->input->close = sun4i_ts_close; |
293 | ts->input->id.bustype = BUS_HOST; |
294 | ts->input->id.vendor = 0x0001; |
295 | ts->input->id.product = 0x0001; |
296 | ts->input->id.version = 0x0100; |
297 | ts->input->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); |
298 | __set_bit(BTN_TOUCH, ts->input->keybit); |
299 | input_set_abs_params(dev: ts->input, ABS_X, min: 0, max: 4095, fuzz: 0, flat: 0); |
300 | input_set_abs_params(dev: ts->input, ABS_Y, min: 0, max: 4095, fuzz: 0, flat: 0); |
301 | input_set_drvdata(dev: ts->input, data: ts); |
302 | } |
303 | |
304 | ts->base = devm_platform_ioremap_resource(pdev, index: 0); |
305 | if (IS_ERR(ptr: ts->base)) |
306 | return PTR_ERR(ptr: ts->base); |
307 | |
308 | ts->irq = platform_get_irq(pdev, 0); |
309 | error = devm_request_irq(dev, irq: ts->irq, handler: sun4i_ts_irq, irqflags: 0, devname: "sun4i-ts" , dev_id: 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 | addr: ts->base + TP_CTRL0); |
319 | |
320 | /* |
321 | * tp_sensitive_adjust is an optional property |
322 | * tp_mode = 0 : only x and y coordinates, as we don't use dual touch |
323 | */ |
324 | of_property_read_u32(np, propname: "allwinner,tp-sensitive-adjust" , |
325 | out_value: &tp_sensitive_adjust); |
326 | writel(TP_SENSITIVE_ADJUST(tp_sensitive_adjust) | TP_MODE_SELECT(0), |
327 | addr: ts->base + TP_CTRL2); |
328 | |
329 | /* |
330 | * Enable median and averaging filter, optional property for |
331 | * filter type. |
332 | */ |
333 | of_property_read_u32(np, propname: "allwinner,filter-type" , out_value: &filter_type); |
334 | writel(FILTER_EN(1) | FILTER_TYPE(filter_type), addr: ts->base + TP_CTRL3); |
335 | |
336 | /* Enable temperature measurement, period 1953 (2 seconds) */ |
337 | writel(TEMP_ENABLE(1) | TEMP_PERIOD(1953), addr: ts->base + TP_TPR); |
338 | |
339 | /* |
340 | * Set stylus up debounce to aprox 10 ms, enable debounce, and |
341 | * finally enable tp mode. |
342 | */ |
343 | reg = STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1); |
344 | if (of_device_is_compatible(device: np, "allwinner,sun6i-a31-ts" )) |
345 | reg |= SUN6I_TP_MODE_EN(1); |
346 | else |
347 | reg |= TP_MODE_EN(1); |
348 | writel(val: reg, addr: ts->base + TP_CTRL1); |
349 | |
350 | /* |
351 | * The thermal core does not register hwmon devices for DT-based |
352 | * thermal zone sensors, such as this one. |
353 | */ |
354 | hwmon = devm_hwmon_device_register_with_groups(dev: ts->dev, name: "sun4i_ts" , |
355 | drvdata: ts, groups: sun4i_ts_groups); |
356 | if (IS_ERR(ptr: hwmon)) |
357 | return PTR_ERR(ptr: hwmon); |
358 | |
359 | thermal = devm_thermal_of_zone_register(dev: ts->dev, id: 0, data: ts, |
360 | ops: &sun4i_ts_tz_ops); |
361 | if (IS_ERR(ptr: thermal)) |
362 | return PTR_ERR(ptr: thermal); |
363 | |
364 | writel(TEMP_IRQ_EN(1), addr: ts->base + TP_INT_FIFOC); |
365 | |
366 | if (ts_attached) { |
367 | error = input_register_device(ts->input); |
368 | if (error) { |
369 | writel(val: 0, addr: ts->base + TP_INT_FIFOC); |
370 | return error; |
371 | } |
372 | } |
373 | |
374 | platform_set_drvdata(pdev, data: ts); |
375 | return 0; |
376 | } |
377 | |
378 | static int sun4i_ts_remove(struct platform_device *pdev) |
379 | { |
380 | struct sun4i_ts_data *ts = platform_get_drvdata(pdev); |
381 | |
382 | /* Explicit unregister to avoid open/close changing the imask later */ |
383 | if (ts->input) |
384 | input_unregister_device(ts->input); |
385 | |
386 | /* Deactivate all IRQs */ |
387 | writel(val: 0, addr: ts->base + TP_INT_FIFOC); |
388 | |
389 | return 0; |
390 | } |
391 | |
392 | static const struct of_device_id sun4i_ts_of_match[] = { |
393 | { .compatible = "allwinner,sun4i-a10-ts" , }, |
394 | { .compatible = "allwinner,sun5i-a13-ts" , }, |
395 | { .compatible = "allwinner,sun6i-a31-ts" , }, |
396 | { /* sentinel */ } |
397 | }; |
398 | MODULE_DEVICE_TABLE(of, sun4i_ts_of_match); |
399 | |
400 | static struct platform_driver sun4i_ts_driver = { |
401 | .driver = { |
402 | .name = "sun4i-ts" , |
403 | .of_match_table = sun4i_ts_of_match, |
404 | }, |
405 | .probe = sun4i_ts_probe, |
406 | .remove = sun4i_ts_remove, |
407 | }; |
408 | |
409 | module_platform_driver(sun4i_ts_driver); |
410 | |
411 | MODULE_DESCRIPTION("Allwinner sun4i resistive touchscreen controller driver" ); |
412 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>" ); |
413 | MODULE_LICENSE("GPL" ); |
414 | |