sunxi_ts.c revision 1.5
1/* $NetBSD: sunxi_ts.c,v 1.5 2021/01/18 02:35:49 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.5 2021/01/18 02:35:49 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
107static int sunxi_ts_match(device_t, cfdata_t, void *);
108static void sunxi_ts_attach(device_t, device_t, void *);
109
110struct sunxi_ts_config {
111	int64_t	temp_offset;
112	int64_t	temp_step;
113	uint32_t tp_mode_en_mask;
114};
115
116static 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
122static 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
128static 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
134static 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
139	{ 0 }
140};
141
142static struct wsmouse_calibcoords sunxi_ts_default_calib = {
143	.minx = 0,
144	.miny = 0,
145	.maxx = __SHIFTOUT_MASK(TP_DATA_MASK),
146	.maxy = __SHIFTOUT_MASK(TP_DATA_MASK),
147	.samplelen = WSMOUSE_CALIBCOORDS_RESET,
148};
149
150struct sunxi_ts_softc {
151	device_t		sc_dev;
152	int			sc_phandle;
153	bus_space_tag_t		sc_bst;
154	bus_space_handle_t	sc_bsh;
155
156	const struct sunxi_ts_config *sc_conf;
157
158	bool			sc_ts_attached;
159	bool			sc_ts_inverted_x;
160	bool			sc_ts_inverted_y;
161
162	struct tpcalib_softc	sc_tpcalib;
163	device_t		sc_wsmousedev;
164	bool			sc_ignoredata;
165
166	u_int			sc_tp_x;
167	u_int			sc_tp_y;
168	u_int			sc_tp_btns;
169
170	struct sysmon_envsys	*sc_sme;
171	envsys_data_t		sc_temp_data;
172};
173
174CFATTACH_DECL_NEW(sunxi_ts, sizeof(struct sunxi_ts_softc),
175	sunxi_ts_match, sunxi_ts_attach, NULL, NULL);
176
177#define	TS_READ(sc, reg)		\
178	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
179#define	TS_WRITE(sc, reg, val)		\
180	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
181
182static int
183sunxi_ts_enable(void *v)
184{
185	struct sunxi_ts_softc * const sc = v;
186	uint32_t val;
187
188	/* reset state */
189	sc->sc_ignoredata = true;
190	sc->sc_tp_x = 0;
191	sc->sc_tp_y = 0;
192	sc->sc_tp_btns = 0;
193
194	/* Enable touchpanel IRQs */
195	val = TS_READ(sc, TP_INT);
196	val |= TP_INT_DATA_IRQ_EN | TP_INT_FIFO_FLUSH | TP_INT_UP_IRQ_EN;
197	val &= ~TP_INT_FIFO_TRIG_LEVEL;
198	val |= __SHIFTIN(1, TP_INT_FIFO_TRIG_LEVEL);
199	TS_WRITE(sc, TP_INT, val);
200
201	return 0;
202}
203
204static void
205sunxi_ts_disable(void *v)
206{
207	struct sunxi_ts_softc * const sc = v;
208	uint32_t val;
209
210	/* Disable touchpanel IRQs */
211	val = TS_READ(sc, TP_INT);
212	val &= ~(TP_INT_DATA_IRQ_EN | TP_INT_FIFO_FLUSH | TP_INT_UP_IRQ_EN);
213	TS_WRITE(sc, TP_INT, val);
214}
215
216static int
217sunxi_ts_ioctl(void *v, u_long cmd, void *data, int flag, lwp_t *l)
218{
219	struct sunxi_ts_softc * const sc = v;
220	struct wsmouse_id *id;
221
222	switch (cmd) {
223	case WSMOUSEIO_GTYPE:
224		*(int *)data = WSMOUSE_TYPE_TPANEL;
225		return 0;
226
227	case WSMOUSEIO_GETID:
228		id = data;
229		if (id->type != WSMOUSE_ID_TYPE_UIDSTR)
230			return EINVAL;
231		snprintf(id->data, WSMOUSE_ID_MAXLEN,
232		    "Allwinner TS SN000000");
233		id->length = strlen(id->data);
234		return 0;
235
236	case WSMOUSEIO_SCALIBCOORDS:
237	case WSMOUSEIO_GCALIBCOORDS:
238		return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
239	}
240
241	return EPASSTHROUGH;
242}
243
244static const struct wsmouse_accessops sunxi_ts_accessops = {
245	.enable = sunxi_ts_enable,
246	.disable = sunxi_ts_disable,
247	.ioctl = sunxi_ts_ioctl,
248};
249
250static int
251sunxi_ts_intr(void *priv)
252{
253	struct sunxi_ts_softc * const sc = priv;
254	uint32_t fifocs, x, y;
255	int s;
256
257	fifocs = TS_READ(sc, TP_FIFOCS);
258
259	if (fifocs & TP_FIFOCS_TEMP_IRQ_PENDING) {
260		sc->sc_temp_data.value_cur = (TS_READ(sc, TEMP_DATA) *
261		    sc->sc_conf->temp_step - sc->sc_conf->temp_offset) +
262		    TEMP_C_TO_K;
263		sc->sc_temp_data.state = ENVSYS_SVALID;
264	}
265
266	if (fifocs & TP_FIFOCS_DATA_PENDING) {
267		x = TS_READ(sc, TP_DATA);
268		y = TS_READ(sc, TP_DATA);
269		if (sc->sc_ignoredata) {
270			/* Discard the first report */
271			sc->sc_ignoredata = false;
272		} else if (sc->sc_wsmousedev != NULL) {
273			if (sc->sc_ts_inverted_x)
274				x = __SHIFTOUT_MASK(TP_DATA_MASK) - x;
275			if (sc->sc_ts_inverted_y)
276				y = __SHIFTOUT_MASK(TP_DATA_MASK) - y;
277			tpcalib_trans(&sc->sc_tpcalib, x, y,
278			    &sc->sc_tp_x, &sc->sc_tp_y);
279			sc->sc_tp_btns |= 1;
280
281			s = spltty();
282			wsmouse_input(sc->sc_wsmousedev,
283			    sc->sc_tp_btns, sc->sc_tp_x, sc->sc_tp_y, 0, 0,
284			    WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
285			splx(s);
286		}
287	}
288
289	if (fifocs & TP_FIFOCS_UP_PENDING) {
290		sc->sc_ignoredata = true;
291		sc->sc_tp_btns &= ~1;
292		s = spltty();
293		wsmouse_input(sc->sc_wsmousedev,
294		    sc->sc_tp_btns, sc->sc_tp_x, sc->sc_tp_y, 0, 0,
295		    WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
296		splx(s);
297	}
298
299	TS_WRITE(sc, TP_FIFOCS, fifocs);
300
301	return 1;
302}
303
304static void
305sunxi_ts_init(struct sunxi_ts_softc *sc)
306{
307	u_int tp_sensitivity_adjust = TS_TP_SENSITIVITY_ADJUST_DEFAULT;
308	u_int filter_type = TS_FILTER_TYPE_DEFAULT;
309
310	of_getprop_uint32(sc->sc_phandle, "allwinner,tp-sensitive-adjust",
311	    &tp_sensitivity_adjust);
312	of_getprop_uint32(sc->sc_phandle, "allwinner,filter-type",
313	    &filter_type);
314
315	TS_WRITE(sc, TP_CTRL0,
316	    __SHIFTIN(0, TP_CTRL0_ADC_CLK_SELECT) |
317	    __SHIFTIN(2, TP_CTRL0_ADC_CLK_DIVIDER) |
318	    __SHIFTIN(7, TP_CTRL0_FS_DIV) |
319	    __SHIFTIN(63, TP_CTRL0_T_ACQ));
320	TS_WRITE(sc, TP_CTRL2,
321	    __SHIFTIN(0, TP_CTRL2_MODE_SELECT) |
322	    __SHIFTIN(tp_sensitivity_adjust, TP_CTRL2_SENSITIVE_ADJUST));
323	TS_WRITE(sc, TP_CTRL3,
324	    TP_CTRL3_FILTER_EN |
325	    __SHIFTIN(filter_type, TP_CTRL3_FILTER_TYPE));
326	TS_WRITE(sc, TP_CTRL1,
327	    sc->sc_conf->tp_mode_en_mask |
328	    TP_CTRL1_STYLUS_UP_DEBOUNCE_EN |
329	    __SHIFTIN(5, TP_CTRL1_STYLUS_UP_DEBOUNCE));
330
331	/* Enable temperature sensor */
332	TS_WRITE(sc, TP_TPR,
333	    TP_TPR_TEMP_EN | __SHIFTIN(1953, TP_TPR_TEMP_PER));
334
335	/* Enable temperature sensor IRQ */
336	TS_WRITE(sc, TP_INT, TP_INT_TEMP_IRQ_EN);
337
338	/* Clear pending IRQs */
339	TS_WRITE(sc, TP_FIFOCS, TS_READ(sc, TP_FIFOCS));
340}
341
342static int
343sunxi_ts_match(device_t parent, cfdata_t cf, void *aux)
344{
345	struct fdt_attach_args * const faa = aux;
346
347	return of_match_compat_data(faa->faa_phandle, compat_data);
348}
349
350static void
351sunxi_ts_attach(device_t parent, device_t self, void *aux)
352{
353	struct sunxi_ts_softc * const sc = device_private(self);
354	struct fdt_attach_args * const faa = aux;
355	const int phandle = faa->faa_phandle;
356	struct wsmousedev_attach_args a;
357	char intrstr[128];
358	bus_addr_t addr;
359	bus_size_t size;
360	void *ih;
361
362	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
363		aprint_error(": couldn't get registers\n");
364		return;
365	}
366
367	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
368		aprint_error(": failed to decode interrupt\n");
369		return;
370	}
371
372	sc->sc_dev = self;
373	sc->sc_phandle = phandle;
374	sc->sc_bst = faa->faa_bst;
375	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
376		aprint_error(": couldn't map registers\n");
377		return;
378	}
379	sc->sc_conf = of_search_compatible(phandle, compat_data)->data;
380
381	sc->sc_ts_attached = of_getprop_bool(phandle, "allwinner,ts-attached");
382	sc->sc_ts_inverted_x = of_getprop_bool(phandle,
383	    "touchscreen-inverted-x");
384	sc->sc_ts_inverted_y = of_getprop_bool(phandle,
385	    "touchscreen-inverted-y");
386
387	aprint_naive("\n");
388	aprint_normal(": Touch Screen Controller\n");
389
390	sunxi_ts_init(sc);
391
392	ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, 0, sunxi_ts_intr,
393	    sc, device_xname(self));
394	if (ih == NULL) {
395		aprint_error_dev(self, "couldn't establish interrupt on %s\n",
396		    intrstr);
397		return;
398	}
399	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
400
401	sc->sc_sme = sysmon_envsys_create();
402	sc->sc_sme->sme_name = device_xname(self);
403	sc->sc_sme->sme_cookie = sc;
404	sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;
405
406	sc->sc_temp_data.units = ENVSYS_STEMP;
407	sc->sc_temp_data.state = ENVSYS_SINVALID;
408	snprintf(sc->sc_temp_data.desc, sizeof(sc->sc_temp_data.desc),
409	    "temperature");
410	sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_temp_data);
411
412	sysmon_envsys_register(sc->sc_sme);
413
414	if (sc->sc_ts_attached) {
415		tpcalib_init(&sc->sc_tpcalib);
416		tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
417		    &sunxi_ts_default_calib, 0, 0);
418
419		memset(&a, 0, sizeof(a));
420		a.accessops = &sunxi_ts_accessops;
421		a.accesscookie = sc;
422		sc->sc_wsmousedev = config_found_ia(self, "wsmousedev",
423		    &a, wsmousedevprint);
424	}
425}
426