s3c2440_touch.c revision 1.2 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 (at) 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