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