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