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(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 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 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 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 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 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 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 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 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