xref: /netbsd-src/sys/arch/arm/s3c2xx0/s3c2440_touch.c (revision 48fb7bfab72acd4281a53bbee5ccf3f809019e75)
1 /*-
2  * Copyright (c) 2012 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by Paul Fleischer <paul@xpg.dk>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 #include <sys/cdefs.h>
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/conf.h>
34 #include <sys/callout.h>
35 #include <sys/kernel.h>
36 
37 #include <sys/bus.h>
38 
39 #include <arm/s3c2xx0/s3c24x0var.h>
40 #include <arm/s3c2xx0/s3c2440var.h>
41 #include <arm/s3c2xx0/s3c2440reg.h>
42 
43 #include <dev/wscons/wsconsio.h>
44 #include <dev/wscons/wsmousevar.h>
45 #include <dev/wscons/tpcalibvar.h>
46 
47 #include <lib/libsa/qsort.c>
48 
49 #include <dev/hpc/hpcfbio.h>
50 
51 #define MAX_SAMPLES 20
52 
53 struct sstouch_softc {
54 	device_t		dev;
55 
56 	bus_space_tag_t		iot;
57 	bus_space_handle_t	ioh;
58 
59 	uint32_t		next_stylus_intr;
60 
61 	device_t		wsmousedev;
62 
63 	struct tpcalib_softc	tpcalib;
64 
65 	int			sample_count;
66 	int			samples_x[MAX_SAMPLES];
67 	int			samples_y[MAX_SAMPLES];
68 
69 	callout_t		callout;
70 };
71 
72 /* Basic Driver Stuff */
73 static int	sstouch_match	(device_t, cfdata_t, void *);
74 static void	sstouch_attach	(device_t, device_t, void *);
75 
76 CFATTACH_DECL_NEW(sstouch, sizeof(struct sstouch_softc), sstouch_match,
77 	      sstouch_attach, NULL, NULL);
78 
79 /* wsmousedev */
80 int	sstouch_enable(void *);
81 int	sstouch_ioctl(void *, u_long, void *, int, struct lwp *);
82 void	sstouch_disable(void *);
83 
84 const struct wsmouse_accessops sstouch_accessops = {
85 	sstouch_enable,
86 	sstouch_ioctl,
87 	sstouch_disable
88 };
89 
90 /* Interrupt Handlers */
91 int sstouch_tc_intr(void *arg);
92 int sstouch_adc_intr(void *arg);
93 
94 void sstouch_callout(void *arg);
95 int sstouch_filter_values(int *vals, int val_count);
96 void sstouch_initialize(struct sstouch_softc *sc);
97 
98 #define STYLUS_DOWN	0
99 #define STYLUS_UP	ADCTSC_UD_SEN
100 
101 static struct wsmouse_calibcoords default_calib = {
102 	.minx = 0,
103 	.miny = 0,
104 	.maxx = 0,
105 	.maxy = 0,
106 	.samplelen = WSMOUSE_CALIBCOORDS_RESET
107 };
108 
109 /* IMPLEMENTATION PART */
110 int
111 sstouch_match(device_t parent, cfdata_t match, void *aux)
112 {
113 	/* XXX: Check CPU type? */
114 	return 1;
115 }
116 
117 void
118 sstouch_attach(device_t parent, device_t self, void *aux)
119 {
120 	struct sstouch_softc		*sc = device_private(self);
121 	struct s3c2xx0_attach_args	*sa = aux;
122 	struct wsmousedev_attach_args	mas;
123 
124 	sc->dev = self;
125 	sc->iot = sa->sa_iot;
126 
127 	if (bus_space_map(sc->iot, S3C2440_ADC_BASE,
128 			  S3C2440_ADC_SIZE, 0, &sc->ioh)) {
129 		aprint_error(": failed to map registers");
130 		return;
131 	}
132 
133 	sc->next_stylus_intr = STYLUS_DOWN;
134 
135 
136 	/* XXX: Is IPL correct? */
137 	s3c24x0_intr_establish(S3C2440_INT_TC, IPL_BIO, IST_EDGE_RISING,
138 			       sstouch_tc_intr, sc);
139 	s3c24x0_intr_establish(S3C2440_INT_ADC, IPL_BIO, IST_EDGE_RISING,
140 			       sstouch_adc_intr, sc);
141 
142 	aprint_normal("\n");
143 
144 	mas.accessops = &sstouch_accessops;
145 	mas.accesscookie = sc;
146 
147 	sc->wsmousedev = config_found_ia(self, "wsmousedev", &mas,
148 					 wsmousedevprint);
149 
150 	tpcalib_init(&sc->tpcalib);
151 	tpcalib_ioctl(&sc->tpcalib, WSMOUSEIO_SCALIBCOORDS,
152 		      (void*)&default_calib, 0, 0);
153 
154 	sc->sample_count = 0;
155 
156 	/* Add CALLOUT_MPSAFE to avoid holding the global kernel lock */
157 	callout_init(&sc->callout, 0);
158 	callout_setfunc(&sc->callout, sstouch_callout, sc);
159 
160 	/* Actual initialization is performed by sstouch_initialize(),
161 	   which is called by sstouch_enable() */
162 }
163 
164 /* sstouch_tc_intr is the TC interrupt handler.
165    The TC interrupt is generated when the stylus changes up->down,
166    or down->up state (depending on configuration of ADC_ADCTSC).
167 */
168 int
169 sstouch_tc_intr(void *arg)
170 {
171 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
172 	uint32_t reg;
173 
174 	/*aprint_normal("%s\n", __func__);*/
175 
176 	/* Figure out if the stylus was lifted or lowered */
177 	reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCUPDN);
178 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCUPDN, 0x0);
179 	if( sc->next_stylus_intr == STYLUS_DOWN && (reg & ADCUPDN_TSC_DN) ) {
180 		sc->next_stylus_intr = STYLUS_UP;
181 
182 		sstouch_callout(sc);
183 
184 	} else if (sc->next_stylus_intr == STYLUS_UP && (reg & ADCUPDN_TSC_UP)) {
185 		uint32_t adctsc = 0;
186 		sc->next_stylus_intr = STYLUS_DOWN;
187 
188 		wsmouse_input(sc->wsmousedev, 0x0, 0, 0, 0, 0, 0);
189 
190 		sc->sample_count = 0;
191 
192 		adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN |
193 			sc->next_stylus_intr |
194 			3; /* 3 selects "Waiting for Interrupt Mode" */
195 		bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc);
196 	}
197 
198 	return 1;
199 }
200 
201 /* sstouch_adc_intr is ADC interrupt handler.
202    ADC interrupt is triggered when the ADC controller has a measurement ready.
203 */
204 int
205 sstouch_adc_intr(void *arg)
206 {
207 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
208 	uint32_t reg;
209 	uint32_t adctsc = 0;
210 	int x, y;
211 
212 	reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCDAT0);
213 	y = reg & ADCDAT_DATAMASK;
214 
215 	reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCDAT1);
216 	x = reg & ADCDAT_DATAMASK;
217 
218 
219 	sc->samples_x[sc->sample_count] = x;
220 	sc->samples_y[sc->sample_count] = y;
221 
222 	sc->sample_count++;
223 
224 	x = sstouch_filter_values(sc->samples_x, sc->sample_count);
225 	y = sstouch_filter_values(sc->samples_y, sc->sample_count);
226 
227 	if (x == -1 || y == -1) {
228 		/* If we do not have enough measurements, make some more. */
229 		sstouch_callout(sc);
230 		return 1;
231 	}
232 
233 	sc->sample_count = 0;
234 
235 	tpcalib_trans(&sc->tpcalib, x, y, &x, &y);
236 
237 	wsmouse_input(sc->wsmousedev, 0x1, x, y, 0, 0,
238 		      WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
239 
240 	/* Schedule a new adc measurement, unless the stylus has been lifed */
241 	if (sc->next_stylus_intr == STYLUS_UP) {
242 		callout_schedule(&sc->callout, hz/50);
243 	}
244 
245 	/* Until measurement is to be performed, listen for stylus up-events */
246 	adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN |
247 		sc->next_stylus_intr |
248 		3; /* 3 selects "Waiting for Interrupt Mode" */
249 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc);
250 
251 
252 	return 1;
253 }
254 
255 int
256 sstouch_enable(void *arg)
257 {
258 	struct sstouch_softc *sc = arg;
259 
260 	sstouch_initialize(sc);
261 
262 	return 0;
263 }
264 
265 int
266 sstouch_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
267 {
268 	struct sstouch_softc *sc = v;
269 
270 	aprint_normal("%s\n", __func__);
271 
272 	switch (cmd) {
273 	case WSMOUSEIO_GTYPE:
274 		*(uint *)data = WSMOUSE_TYPE_PSEUDO;
275 		break;
276 	case WSMOUSEIO_GCALIBCOORDS:
277 	case WSMOUSEIO_SCALIBCOORDS:
278 		return tpcalib_ioctl(&sc->tpcalib, cmd, data, flag, l);
279 	default:
280 		return EPASSTHROUGH;
281 	}
282 
283 	return 0;
284 }
285 
286 void
287 sstouch_disable(void *arg)
288 {
289 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
290 
291 	/* By setting ADCCON register to 0, we also disable
292 	   the prescaler, which should disable any interrupts.
293 	 */
294 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, 0);
295 }
296 
297 void
298 sstouch_callout(void *arg)
299 {
300 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
301 
302 	/* If stylus is down, perform a measurement */
303 	if (sc->next_stylus_intr == STYLUS_UP) {
304 		uint32_t reg;
305 		bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC,
306 				  ADCTSC_YM_SEN | ADCTSC_YP_SEN |
307 				  ADCTSC_XP_SEN | ADCTSC_PULL_UP |
308 				  ADCTSC_AUTO_PST);
309 
310 		reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCCON);
311 		bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON,
312 				  reg | ADCCON_ENABLE_START);
313 	}
314 
315 }
316 
317 /* Do some very simple filtering on the measured values */
318 int
319 sstouch_filter_values(int *vals, int val_count)
320 {
321 	int sum = 0;
322 
323 	if (val_count < 5)
324 		return -1;
325 
326 	for (int i=0; i<val_count; i++) {
327 		sum += vals[i];
328 	}
329 
330 	return sum/val_count;
331 }
332 
333 void
334 sstouch_initialize(struct sstouch_softc *sc)
335 {
336 	int prescaler;
337 	uint32_t adccon = 0;
338 	uint32_t adctsc = 0;
339 
340 	/* ADC Conversion rate is calculated by:
341 	   f(ADC) = PCLK/(prescaler+1)
342 
343 	   The ADC can operate at a maximum frequency of 2.5MHz for
344 	   500 KSPS.
345 	*/
346 
347 	/* Set f(ADC) = 50MHz / 256 = 1,95MHz */
348 	prescaler = 0xff;
349 
350 	adccon |= ((prescaler<<ADCCON_PRSCVL_SHIFT) &
351 		   ADCCON_PRSCVL_MASK);
352 	adccon |= ADCCON_PRSCEN;
353 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, adccon);
354 
355 	/* Use Auto Sequential measurement of X and Y positions */
356 	adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN |
357 		sc->next_stylus_intr |
358 		3; /* 3 selects "Waiting for Interrupt Mode" */
359 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc);
360 
361 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCUPDN, 0x0);
362 
363 	/* Time used to measure each X/Y position value? */
364 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCDLY, 10000);
365 }
366