xref: /netbsd-src/sys/arch/arm/s3c2xx0/s3c2440_touch.c (revision c7fb772b85b2b5d4cfb282f868f454b4701534fd)
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
sstouch_match(device_t parent,cfdata_t match,void * aux)111 sstouch_match(device_t parent, cfdata_t match, void *aux)
112 {
113 	/* XXX: Check CPU type? */
114 	return 1;
115 }
116 
117 void
sstouch_attach(device_t parent,device_t self,void * aux)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(self, &mas, wsmousedevprint, CFARGS_NONE);
148 
149 	tpcalib_init(&sc->tpcalib);
150 	tpcalib_ioctl(&sc->tpcalib, WSMOUSEIO_SCALIBCOORDS,
151 		      (void*)&default_calib, 0, 0);
152 
153 	sc->sample_count = 0;
154 
155 	/* Add CALLOUT_MPSAFE to avoid holding the global kernel lock */
156 	callout_init(&sc->callout, 0);
157 	callout_setfunc(&sc->callout, sstouch_callout, sc);
158 
159 	/* Actual initialization is performed by sstouch_initialize(),
160 	   which is called by sstouch_enable() */
161 }
162 
163 /* sstouch_tc_intr is the TC interrupt handler.
164    The TC interrupt is generated when the stylus changes up->down,
165    or down->up state (depending on configuration of ADC_ADCTSC).
166 */
167 int
sstouch_tc_intr(void * arg)168 sstouch_tc_intr(void *arg)
169 {
170 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
171 	uint32_t reg;
172 
173 	/*aprint_normal("%s\n", __func__);*/
174 
175 	/* Figure out if the stylus was lifted or lowered */
176 	reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCUPDN);
177 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCUPDN, 0x0);
178 	if( sc->next_stylus_intr == STYLUS_DOWN && (reg & ADCUPDN_TSC_DN) ) {
179 		sc->next_stylus_intr = STYLUS_UP;
180 
181 		sstouch_callout(sc);
182 
183 	} else if (sc->next_stylus_intr == STYLUS_UP && (reg & ADCUPDN_TSC_UP)) {
184 		uint32_t adctsc = 0;
185 		sc->next_stylus_intr = STYLUS_DOWN;
186 
187 		wsmouse_input(sc->wsmousedev, 0x0, 0, 0, 0, 0, 0);
188 
189 		sc->sample_count = 0;
190 
191 		adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN |
192 			sc->next_stylus_intr |
193 			3; /* 3 selects "Waiting for Interrupt Mode" */
194 		bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc);
195 	}
196 
197 	return 1;
198 }
199 
200 /* sstouch_adc_intr is ADC interrupt handler.
201    ADC interrupt is triggered when the ADC controller has a measurement ready.
202 */
203 int
sstouch_adc_intr(void * arg)204 sstouch_adc_intr(void *arg)
205 {
206 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
207 	uint32_t reg;
208 	uint32_t adctsc = 0;
209 	int x, y;
210 
211 	reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCDAT0);
212 	y = reg & ADCDAT_DATAMASK;
213 
214 	reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCDAT1);
215 	x = reg & ADCDAT_DATAMASK;
216 
217 
218 	sc->samples_x[sc->sample_count] = x;
219 	sc->samples_y[sc->sample_count] = y;
220 
221 	sc->sample_count++;
222 
223 	x = sstouch_filter_values(sc->samples_x, sc->sample_count);
224 	y = sstouch_filter_values(sc->samples_y, sc->sample_count);
225 
226 	if (x == -1 || y == -1) {
227 		/* If we do not have enough measurements, make some more. */
228 		sstouch_callout(sc);
229 		return 1;
230 	}
231 
232 	sc->sample_count = 0;
233 
234 	tpcalib_trans(&sc->tpcalib, x, y, &x, &y);
235 
236 	wsmouse_input(sc->wsmousedev, 0x1, x, y, 0, 0,
237 		      WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
238 
239 	/* Schedule a new adc measurement, unless the stylus has been lifed */
240 	if (sc->next_stylus_intr == STYLUS_UP) {
241 		callout_schedule(&sc->callout, hz/50);
242 	}
243 
244 	/* Until measurement is to be performed, listen for stylus up-events */
245 	adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN |
246 		sc->next_stylus_intr |
247 		3; /* 3 selects "Waiting for Interrupt Mode" */
248 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc);
249 
250 
251 	return 1;
252 }
253 
254 int
sstouch_enable(void * arg)255 sstouch_enable(void *arg)
256 {
257 	struct sstouch_softc *sc = arg;
258 
259 	sstouch_initialize(sc);
260 
261 	return 0;
262 }
263 
264 int
sstouch_ioctl(void * v,u_long cmd,void * data,int flag,struct lwp * l)265 sstouch_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
266 {
267 	struct sstouch_softc *sc = v;
268 
269 	aprint_normal("%s\n", __func__);
270 
271 	switch (cmd) {
272 	case WSMOUSEIO_GTYPE:
273 		*(uint *)data = WSMOUSE_TYPE_PSEUDO;
274 		break;
275 	case WSMOUSEIO_GCALIBCOORDS:
276 	case WSMOUSEIO_SCALIBCOORDS:
277 		return tpcalib_ioctl(&sc->tpcalib, cmd, data, flag, l);
278 	default:
279 		return EPASSTHROUGH;
280 	}
281 
282 	return 0;
283 }
284 
285 void
sstouch_disable(void * arg)286 sstouch_disable(void *arg)
287 {
288 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
289 
290 	/* By setting ADCCON register to 0, we also disable
291 	   the prescaler, which should disable any interrupts.
292 	 */
293 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, 0);
294 }
295 
296 void
sstouch_callout(void * arg)297 sstouch_callout(void *arg)
298 {
299 	struct sstouch_softc *sc = (struct sstouch_softc*)arg;
300 
301 	/* If stylus is down, perform a measurement */
302 	if (sc->next_stylus_intr == STYLUS_UP) {
303 		uint32_t reg;
304 		bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC,
305 				  ADCTSC_YM_SEN | ADCTSC_YP_SEN |
306 				  ADCTSC_XP_SEN | ADCTSC_PULL_UP |
307 				  ADCTSC_AUTO_PST);
308 
309 		reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCCON);
310 		bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON,
311 				  reg | ADCCON_ENABLE_START);
312 	}
313 
314 }
315 
316 /* Do some very simple filtering on the measured values */
317 int
sstouch_filter_values(int * vals,int val_count)318 sstouch_filter_values(int *vals, int val_count)
319 {
320 	int sum = 0;
321 
322 	if (val_count < 5)
323 		return -1;
324 
325 	for (int i=0; i<val_count; i++) {
326 		sum += vals[i];
327 	}
328 
329 	return sum/val_count;
330 }
331 
332 void
sstouch_initialize(struct sstouch_softc * sc)333 sstouch_initialize(struct sstouch_softc *sc)
334 {
335 	int prescaler;
336 	uint32_t adccon = 0;
337 	uint32_t adctsc = 0;
338 
339 	/* ADC Conversion rate is calculated by:
340 	   f(ADC) = PCLK/(prescaler+1)
341 
342 	   The ADC can operate at a maximum frequency of 2.5MHz for
343 	   500 KSPS.
344 	*/
345 
346 	/* Set f(ADC) = 50MHz / 256 = 1,95MHz */
347 	prescaler = 0xff;
348 
349 	adccon |= ((prescaler<<ADCCON_PRSCVL_SHIFT) &
350 		   ADCCON_PRSCVL_MASK);
351 	adccon |= ADCCON_PRSCEN;
352 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, adccon);
353 
354 	/* Use Auto Sequential measurement of X and Y positions */
355 	adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN |
356 		sc->next_stylus_intr |
357 		3; /* 3 selects "Waiting for Interrupt Mode" */
358 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc);
359 
360 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCUPDN, 0x0);
361 
362 	/* Time used to measure each X/Y position value? */
363 	bus_space_write_4(sc->iot, sc->ioh, ADC_ADCDLY, 10000);
364 }
365