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