1 /* $NetBSD: sunxi_ts.c,v 1.10 2021/08/07 16:18:45 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30
31 __KERNEL_RCSID(0, "$NetBSD: sunxi_ts.c,v 1.10 2021/08/07 16:18:45 thorpej Exp $");
32
33 #include <sys/param.h>
34 #include <sys/bus.h>
35 #include <sys/device.h>
36 #include <sys/intr.h>
37 #include <sys/systm.h>
38 #include <sys/time.h>
39
40 #include <dev/fdt/fdtvar.h>
41
42 #include <dev/sysmon/sysmonvar.h>
43
44 #include <dev/wscons/wsconsio.h>
45 #include <dev/wscons/wsmousevar.h>
46 #include <dev/wscons/tpcalibvar.h>
47
48 #define TS_TP_SENSITIVITY_ADJUST_DEFAULT 15
49 #define TS_FILTER_TYPE_DEFAULT 1
50
51 #define TP_CTRL0 0x00
52 #define TP_CTRL0_ADC_FIRST_DLY __BITS(31,24)
53 #define TP_CTRL0_ADC_FIRST_DLY_MODE __BIT(23)
54 #define TP_CTRL0_ADC_CLK_SELECT __BIT(22)
55 #define TP_CTRL0_ADC_CLK_DIVIDER __BITS(21,20)
56 #define TP_CTRL0_FS_DIV __BITS(19,16)
57 #define TP_CTRL0_T_ACQ __BITS(15,0)
58 #define TP_CTRL1 0x04
59 #define TP_CTRL1_STYLUS_UP_DEBOUNCE __BITS(19,12)
60 #define TP_CTRL1_STYLUS_UP_DEBOUNCE_EN __BIT(9)
61 #define TP_CTRL1_TOUCH_PAN_CALI_EN __BIT(6)
62 #define TP_CTRL1_TP_DUAL_EN __BIT(5)
63 #define TP_CTRL1_TP_MODE_EN __BIT(4)
64 #define TP_CTRL1_TP_ADC_SELECT __BIT(3)
65 #define TP_CTRL1_ADC_CHAN_SELECT __BITS(2,0)
66 #define TP_CTRL2 0x08
67 #define TP_CTRL2_SENSITIVE_ADJUST __BITS(31,28)
68 #define TP_CTRL2_MODE_SELECT __BITS(27,26)
69 #define TP_CTRL2_PRE_MEA_EN __BIT(24)
70 #define TP_CTRL2_PRE_MEA_THRE_CNT __BITS(23,0)
71 #define TP_CTRL3 0x0c
72 #define TP_CTRL3_FILTER_EN __BIT(2)
73 #define TP_CTRL3_FILTER_TYPE __BITS(1,0)
74 #define TP_INT 0x10
75 #define TP_INT_TEMP_IRQ_EN __BIT(18)
76 #define TP_INT_OVERRUN_IRQ_EN __BIT(17)
77 #define TP_INT_DATA_IRQ_EN __BIT(16)
78 #define TP_INT_DATA_XY_CHANGE __BIT(13)
79 #define TP_INT_FIFO_TRIG_LEVEL __BITS(12,8)
80 #define TP_INT_DATA_DRQ_EN __BIT(7)
81 #define TP_INT_FIFO_FLUSH __BIT(4)
82 #define TP_INT_UP_IRQ_EN __BIT(1)
83 #define TP_INT_DOWN_IRQ_EN __BIT(0)
84 #define TP_FIFOCS 0x14
85 #define TP_FIFOCS_TEMP_IRQ_PENDING __BIT(18)
86 #define TP_FIFOCS_OVERRUN_PENDING __BIT(17)
87 #define TP_FIFOCS_DATA_PENDING __BIT(16)
88 #define TP_FIFOCS_RXA_CNT __BITS(12,8)
89 #define TP_FIFOCS_IDLE_FLG __BIT(2)
90 #define TP_FIFOCS_UP_PENDING __BIT(1)
91 #define TP_FIFOCS_DOWN_PENDING __BIT(0)
92 #define TP_TPR 0x18
93 #define TP_TPR_TEMP_EN __BIT(16)
94 #define TP_TPR_TEMP_PER __BITS(15,0)
95 #define TP_CDAT 0x1c
96 #define TP_CDAT_MASK __BITS(11,0)
97 #define TEMP_DATA 0x20
98 #define TEMP_DATA_MASK __BITS(11,0)
99 #define TP_DATA 0x24
100 #define TP_DATA_MASK __BITS(11,0)
101 #define TP_IO_CONFIG 0x28
102 #define TP_PORT_DATA 0x2c
103 #define TP_PORT_DATA_MASK __BITS(3,0)
104
105 #define TEMP_C_TO_K 273150000
106
107 static int sunxi_ts_match(device_t, cfdata_t, void *);
108 static void sunxi_ts_attach(device_t, device_t, void *);
109
110 struct sunxi_ts_config {
111 int64_t temp_offset;
112 int64_t temp_step;
113 uint32_t tp_mode_en_mask;
114 };
115
116 static const struct sunxi_ts_config sun4i_a10_ts_config = {
117 .temp_offset = 257000000,
118 .temp_step = 133000,
119 .tp_mode_en_mask = TP_CTRL1_TP_MODE_EN,
120 };
121
122 static const struct sunxi_ts_config sun5i_a13_ts_config = {
123 .temp_offset = 144700000,
124 .temp_step = 100000,
125 .tp_mode_en_mask = TP_CTRL1_TP_MODE_EN,
126 };
127
128 static const struct sunxi_ts_config sun6i_a31_ts_config = {
129 .temp_offset = 271000000,
130 .temp_step = 167000,
131 .tp_mode_en_mask = TP_CTRL1_TP_DUAL_EN,
132 };
133
134 static const struct device_compatible_entry compat_data[] = {
135 { .compat = "allwinner,sun4i-a10-ts", .data = &sun4i_a10_ts_config },
136 { .compat = "allwinner,sun5i-a13-ts", .data = &sun5i_a13_ts_config },
137 { .compat = "allwinner,sun6i-a31-ts", .data = &sun6i_a31_ts_config },
138 DEVICE_COMPAT_EOL
139 };
140
141 static struct wsmouse_calibcoords sunxi_ts_default_calib = {
142 .minx = 0,
143 .miny = 0,
144 .maxx = __SHIFTOUT_MASK(TP_DATA_MASK),
145 .maxy = __SHIFTOUT_MASK(TP_DATA_MASK),
146 .samplelen = WSMOUSE_CALIBCOORDS_RESET,
147 };
148
149 struct sunxi_ts_softc {
150 device_t sc_dev;
151 int sc_phandle;
152 bus_space_tag_t sc_bst;
153 bus_space_handle_t sc_bsh;
154
155 const struct sunxi_ts_config *sc_conf;
156
157 bool sc_ts_attached;
158 bool sc_ts_inverted_x;
159 bool sc_ts_inverted_y;
160
161 struct tpcalib_softc sc_tpcalib;
162 device_t sc_wsmousedev;
163 bool sc_ignoredata;
164
165 u_int sc_tp_x;
166 u_int sc_tp_y;
167 u_int sc_tp_btns;
168
169 struct sysmon_envsys *sc_sme;
170 envsys_data_t sc_temp_data;
171 };
172
173 CFATTACH_DECL_NEW(sunxi_ts, sizeof(struct sunxi_ts_softc),
174 sunxi_ts_match, sunxi_ts_attach, NULL, NULL);
175
176 #define TS_READ(sc, reg) \
177 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
178 #define TS_WRITE(sc, reg, val) \
179 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
180
181 static int
sunxi_ts_enable(void * v)182 sunxi_ts_enable(void *v)
183 {
184 struct sunxi_ts_softc * const sc = v;
185 uint32_t val;
186
187 /* reset state */
188 sc->sc_ignoredata = true;
189 sc->sc_tp_x = 0;
190 sc->sc_tp_y = 0;
191 sc->sc_tp_btns = 0;
192
193 /* Enable touchpanel IRQs */
194 val = TS_READ(sc, TP_INT);
195 val |= TP_INT_DATA_IRQ_EN | TP_INT_FIFO_FLUSH | TP_INT_UP_IRQ_EN;
196 val &= ~TP_INT_FIFO_TRIG_LEVEL;
197 val |= __SHIFTIN(1, TP_INT_FIFO_TRIG_LEVEL);
198 TS_WRITE(sc, TP_INT, val);
199
200 return 0;
201 }
202
203 static void
sunxi_ts_disable(void * v)204 sunxi_ts_disable(void *v)
205 {
206 struct sunxi_ts_softc * const sc = v;
207 uint32_t val;
208
209 /* Disable touchpanel IRQs */
210 val = TS_READ(sc, TP_INT);
211 val &= ~(TP_INT_DATA_IRQ_EN | TP_INT_FIFO_FLUSH | TP_INT_UP_IRQ_EN);
212 TS_WRITE(sc, TP_INT, val);
213 }
214
215 static int
sunxi_ts_ioctl(void * v,u_long cmd,void * data,int flag,lwp_t * l)216 sunxi_ts_ioctl(void *v, u_long cmd, void *data, int flag, lwp_t *l)
217 {
218 struct sunxi_ts_softc * const sc = v;
219 struct wsmouse_id *id;
220
221 switch (cmd) {
222 case WSMOUSEIO_GTYPE:
223 *(int *)data = WSMOUSE_TYPE_TPANEL;
224 return 0;
225
226 case WSMOUSEIO_GETID:
227 id = data;
228 if (id->type != WSMOUSE_ID_TYPE_UIDSTR)
229 return EINVAL;
230 snprintf(id->data, WSMOUSE_ID_MAXLEN,
231 "Allwinner TS SN000000");
232 id->length = strlen(id->data);
233 return 0;
234
235 case WSMOUSEIO_SCALIBCOORDS:
236 case WSMOUSEIO_GCALIBCOORDS:
237 return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
238 }
239
240 return EPASSTHROUGH;
241 }
242
243 static const struct wsmouse_accessops sunxi_ts_accessops = {
244 .enable = sunxi_ts_enable,
245 .disable = sunxi_ts_disable,
246 .ioctl = sunxi_ts_ioctl,
247 };
248
249 static int
sunxi_ts_intr(void * priv)250 sunxi_ts_intr(void *priv)
251 {
252 struct sunxi_ts_softc * const sc = priv;
253 uint32_t fifocs, x, y;
254 int s;
255
256 fifocs = TS_READ(sc, TP_FIFOCS);
257
258 if (fifocs & TP_FIFOCS_TEMP_IRQ_PENDING) {
259 sc->sc_temp_data.value_cur = (TS_READ(sc, TEMP_DATA) *
260 sc->sc_conf->temp_step - sc->sc_conf->temp_offset) +
261 TEMP_C_TO_K;
262 sc->sc_temp_data.state = ENVSYS_SVALID;
263 }
264
265 if (fifocs & TP_FIFOCS_DATA_PENDING) {
266 x = TS_READ(sc, TP_DATA);
267 y = TS_READ(sc, TP_DATA);
268 if (sc->sc_ignoredata) {
269 /* Discard the first report */
270 sc->sc_ignoredata = false;
271 } else if (sc->sc_wsmousedev != NULL) {
272 if (sc->sc_ts_inverted_x)
273 x = __SHIFTOUT_MASK(TP_DATA_MASK) - x;
274 if (sc->sc_ts_inverted_y)
275 y = __SHIFTOUT_MASK(TP_DATA_MASK) - y;
276 tpcalib_trans(&sc->sc_tpcalib, x, y,
277 &sc->sc_tp_x, &sc->sc_tp_y);
278 sc->sc_tp_btns |= 1;
279
280 s = spltty();
281 wsmouse_input(sc->sc_wsmousedev,
282 sc->sc_tp_btns, sc->sc_tp_x, sc->sc_tp_y, 0, 0,
283 WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
284 splx(s);
285 }
286 }
287
288 if (fifocs & TP_FIFOCS_UP_PENDING) {
289 sc->sc_ignoredata = true;
290 sc->sc_tp_btns &= ~1;
291 s = spltty();
292 wsmouse_input(sc->sc_wsmousedev,
293 sc->sc_tp_btns, sc->sc_tp_x, sc->sc_tp_y, 0, 0,
294 WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
295 splx(s);
296 }
297
298 TS_WRITE(sc, TP_FIFOCS, fifocs);
299
300 return 1;
301 }
302
303 static void
sunxi_ts_init(struct sunxi_ts_softc * sc)304 sunxi_ts_init(struct sunxi_ts_softc *sc)
305 {
306 u_int tp_sensitivity_adjust = TS_TP_SENSITIVITY_ADJUST_DEFAULT;
307 u_int filter_type = TS_FILTER_TYPE_DEFAULT;
308
309 of_getprop_uint32(sc->sc_phandle, "allwinner,tp-sensitive-adjust",
310 &tp_sensitivity_adjust);
311 of_getprop_uint32(sc->sc_phandle, "allwinner,filter-type",
312 &filter_type);
313
314 TS_WRITE(sc, TP_CTRL0,
315 __SHIFTIN(0, TP_CTRL0_ADC_CLK_SELECT) |
316 __SHIFTIN(2, TP_CTRL0_ADC_CLK_DIVIDER) |
317 __SHIFTIN(7, TP_CTRL0_FS_DIV) |
318 __SHIFTIN(63, TP_CTRL0_T_ACQ));
319 TS_WRITE(sc, TP_CTRL2,
320 __SHIFTIN(0, TP_CTRL2_MODE_SELECT) |
321 __SHIFTIN(tp_sensitivity_adjust, TP_CTRL2_SENSITIVE_ADJUST));
322 TS_WRITE(sc, TP_CTRL3,
323 TP_CTRL3_FILTER_EN |
324 __SHIFTIN(filter_type, TP_CTRL3_FILTER_TYPE));
325 TS_WRITE(sc, TP_CTRL1,
326 sc->sc_conf->tp_mode_en_mask |
327 TP_CTRL1_STYLUS_UP_DEBOUNCE_EN |
328 __SHIFTIN(5, TP_CTRL1_STYLUS_UP_DEBOUNCE));
329
330 /* Enable temperature sensor */
331 TS_WRITE(sc, TP_TPR,
332 TP_TPR_TEMP_EN | __SHIFTIN(1953, TP_TPR_TEMP_PER));
333
334 /* Enable temperature sensor IRQ */
335 TS_WRITE(sc, TP_INT, TP_INT_TEMP_IRQ_EN);
336
337 /* Clear pending IRQs */
338 TS_WRITE(sc, TP_FIFOCS, TS_READ(sc, TP_FIFOCS));
339 }
340
341 static int
sunxi_ts_match(device_t parent,cfdata_t cf,void * aux)342 sunxi_ts_match(device_t parent, cfdata_t cf, void *aux)
343 {
344 struct fdt_attach_args * const faa = aux;
345
346 return of_compatible_match(faa->faa_phandle, compat_data);
347 }
348
349 static void
sunxi_ts_attach(device_t parent,device_t self,void * aux)350 sunxi_ts_attach(device_t parent, device_t self, void *aux)
351 {
352 struct sunxi_ts_softc * const sc = device_private(self);
353 struct fdt_attach_args * const faa = aux;
354 const int phandle = faa->faa_phandle;
355 struct wsmousedev_attach_args a;
356 char intrstr[128];
357 bus_addr_t addr;
358 bus_size_t size;
359 void *ih;
360
361 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
362 aprint_error(": couldn't get registers\n");
363 return;
364 }
365
366 if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
367 aprint_error(": failed to decode interrupt\n");
368 return;
369 }
370
371 sc->sc_dev = self;
372 sc->sc_phandle = phandle;
373 sc->sc_bst = faa->faa_bst;
374 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
375 aprint_error(": couldn't map registers\n");
376 return;
377 }
378 sc->sc_conf = of_compatible_lookup(phandle, compat_data)->data;
379
380 sc->sc_ts_attached = of_getprop_bool(phandle, "allwinner,ts-attached");
381 sc->sc_ts_inverted_x = of_getprop_bool(phandle,
382 "touchscreen-inverted-x");
383 sc->sc_ts_inverted_y = of_getprop_bool(phandle,
384 "touchscreen-inverted-y");
385
386 aprint_naive("\n");
387 aprint_normal(": Touch Screen Controller\n");
388
389 sunxi_ts_init(sc);
390
391 ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, 0, sunxi_ts_intr,
392 sc, device_xname(self));
393 if (ih == NULL) {
394 aprint_error_dev(self, "couldn't establish interrupt on %s\n",
395 intrstr);
396 return;
397 }
398 aprint_normal_dev(self, "interrupting on %s\n", intrstr);
399
400 sc->sc_sme = sysmon_envsys_create();
401 sc->sc_sme->sme_name = device_xname(self);
402 sc->sc_sme->sme_cookie = sc;
403 sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;
404
405 sc->sc_temp_data.units = ENVSYS_STEMP;
406 sc->sc_temp_data.state = ENVSYS_SINVALID;
407 snprintf(sc->sc_temp_data.desc, sizeof(sc->sc_temp_data.desc),
408 "temperature");
409 sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_temp_data);
410
411 sysmon_envsys_register(sc->sc_sme);
412
413 if (sc->sc_ts_attached) {
414 tpcalib_init(&sc->sc_tpcalib);
415 tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
416 &sunxi_ts_default_calib, 0, 0);
417
418 memset(&a, 0, sizeof(a));
419 a.accessops = &sunxi_ts_accessops;
420 a.accesscookie = sc;
421 sc->sc_wsmousedev =
422 config_found(self, &a, wsmousedevprint, CFARGS_NONE);
423 }
424 }
425