1 1.1 nisimura /*- 2 1.1 nisimura * Copyright (c) 2012 The NetBSD Foundation, Inc. 3 1.1 nisimura * All rights reserved. 4 1.1 nisimura * 5 1.1 nisimura * This code is derived from software contributed to The NetBSD Foundation 6 1.1 nisimura * by Paul Fleischer <paul (at) xpg.dk> 7 1.1 nisimura * 8 1.1 nisimura * Redistribution and use in source and binary forms, with or without 9 1.1 nisimura * modification, are permitted provided that the following conditions 10 1.1 nisimura * are met: 11 1.1 nisimura * 1. Redistributions of source code must retain the above copyright 12 1.1 nisimura * notice, this list of conditions and the following disclaimer. 13 1.1 nisimura * 2. Redistributions in binary form must reproduce the above copyright 14 1.1 nisimura * notice, this list of conditions and the following disclaimer in the 15 1.1 nisimura * documentation and/or other materials provided with the distribution. 16 1.1 nisimura * 17 1.1 nisimura * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 1.1 nisimura * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 1.1 nisimura * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 1.1 nisimura * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 1.1 nisimura * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 1.1 nisimura * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 1.1 nisimura * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 1.1 nisimura * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 1.1 nisimura * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 1.1 nisimura * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 1.1 nisimura * POSSIBILITY OF SUCH DAMAGE. 28 1.1 nisimura */ 29 1.1 nisimura #include <sys/cdefs.h> 30 1.1 nisimura 31 1.1 nisimura #include <sys/param.h> 32 1.1 nisimura #include <sys/systm.h> 33 1.1 nisimura #include <sys/conf.h> 34 1.1 nisimura #include <sys/callout.h> 35 1.1 nisimura #include <sys/kernel.h> 36 1.1 nisimura 37 1.1 nisimura #include <sys/bus.h> 38 1.1 nisimura 39 1.1 nisimura #include <arm/s3c2xx0/s3c24x0var.h> 40 1.1 nisimura #include <arm/s3c2xx0/s3c2440var.h> 41 1.1 nisimura #include <arm/s3c2xx0/s3c2440reg.h> 42 1.1 nisimura 43 1.1 nisimura #include <dev/wscons/wsconsio.h> 44 1.1 nisimura #include <dev/wscons/wsmousevar.h> 45 1.1 nisimura #include <dev/wscons/tpcalibvar.h> 46 1.1 nisimura 47 1.1 nisimura #include <lib/libsa/qsort.c> 48 1.1 nisimura 49 1.1 nisimura #include <dev/hpc/hpcfbio.h> 50 1.1 nisimura 51 1.1 nisimura #define MAX_SAMPLES 20 52 1.1 nisimura 53 1.1 nisimura struct sstouch_softc { 54 1.1 nisimura device_t dev; 55 1.1 nisimura 56 1.1 nisimura bus_space_tag_t iot; 57 1.1 nisimura bus_space_handle_t ioh; 58 1.1 nisimura 59 1.1 nisimura uint32_t next_stylus_intr; 60 1.1 nisimura 61 1.1 nisimura device_t wsmousedev; 62 1.1 nisimura 63 1.1 nisimura struct tpcalib_softc tpcalib; 64 1.1 nisimura 65 1.1 nisimura int sample_count; 66 1.1 nisimura int samples_x[MAX_SAMPLES]; 67 1.1 nisimura int samples_y[MAX_SAMPLES]; 68 1.1 nisimura 69 1.1 nisimura callout_t callout; 70 1.1 nisimura }; 71 1.1 nisimura 72 1.1 nisimura /* Basic Driver Stuff */ 73 1.2 chs static int sstouch_match (device_t, cfdata_t, void *); 74 1.2 chs static void sstouch_attach (device_t, device_t, void *); 75 1.1 nisimura 76 1.1 nisimura CFATTACH_DECL_NEW(sstouch, sizeof(struct sstouch_softc), sstouch_match, 77 1.1 nisimura sstouch_attach, NULL, NULL); 78 1.1 nisimura 79 1.1 nisimura /* wsmousedev */ 80 1.1 nisimura int sstouch_enable(void *); 81 1.1 nisimura int sstouch_ioctl(void *, u_long, void *, int, struct lwp *); 82 1.1 nisimura void sstouch_disable(void *); 83 1.1 nisimura 84 1.1 nisimura const struct wsmouse_accessops sstouch_accessops = { 85 1.1 nisimura sstouch_enable, 86 1.1 nisimura sstouch_ioctl, 87 1.1 nisimura sstouch_disable 88 1.1 nisimura }; 89 1.1 nisimura 90 1.1 nisimura /* Interrupt Handlers */ 91 1.1 nisimura int sstouch_tc_intr(void *arg); 92 1.1 nisimura int sstouch_adc_intr(void *arg); 93 1.1 nisimura 94 1.1 nisimura void sstouch_callout(void *arg); 95 1.1 nisimura int sstouch_filter_values(int *vals, int val_count); 96 1.1 nisimura void sstouch_initialize(struct sstouch_softc *sc); 97 1.1 nisimura 98 1.1 nisimura #define STYLUS_DOWN 0 99 1.1 nisimura #define STYLUS_UP ADCTSC_UD_SEN 100 1.1 nisimura 101 1.1 nisimura static struct wsmouse_calibcoords default_calib = { 102 1.1 nisimura .minx = 0, 103 1.1 nisimura .miny = 0, 104 1.1 nisimura .maxx = 0, 105 1.1 nisimura .maxy = 0, 106 1.1 nisimura .samplelen = WSMOUSE_CALIBCOORDS_RESET 107 1.1 nisimura }; 108 1.1 nisimura 109 1.1 nisimura /* IMPLEMENTATION PART */ 110 1.1 nisimura int 111 1.2 chs sstouch_match(device_t parent, cfdata_t match, void *aux) 112 1.1 nisimura { 113 1.1 nisimura /* XXX: Check CPU type? */ 114 1.1 nisimura return 1; 115 1.1 nisimura } 116 1.1 nisimura 117 1.1 nisimura void 118 1.2 chs sstouch_attach(device_t parent, device_t self, void *aux) 119 1.1 nisimura { 120 1.1 nisimura struct sstouch_softc *sc = device_private(self); 121 1.2 chs struct s3c2xx0_attach_args *sa = aux; 122 1.1 nisimura struct wsmousedev_attach_args mas; 123 1.1 nisimura 124 1.1 nisimura sc->dev = self; 125 1.1 nisimura sc->iot = sa->sa_iot; 126 1.1 nisimura 127 1.1 nisimura if (bus_space_map(sc->iot, S3C2440_ADC_BASE, 128 1.1 nisimura S3C2440_ADC_SIZE, 0, &sc->ioh)) { 129 1.1 nisimura aprint_error(": failed to map registers"); 130 1.1 nisimura return; 131 1.1 nisimura } 132 1.1 nisimura 133 1.1 nisimura sc->next_stylus_intr = STYLUS_DOWN; 134 1.1 nisimura 135 1.1 nisimura 136 1.1 nisimura /* XXX: Is IPL correct? */ 137 1.1 nisimura s3c24x0_intr_establish(S3C2440_INT_TC, IPL_BIO, IST_EDGE_RISING, 138 1.1 nisimura sstouch_tc_intr, sc); 139 1.1 nisimura s3c24x0_intr_establish(S3C2440_INT_ADC, IPL_BIO, IST_EDGE_RISING, 140 1.1 nisimura sstouch_adc_intr, sc); 141 1.1 nisimura 142 1.1 nisimura aprint_normal("\n"); 143 1.1 nisimura 144 1.1 nisimura mas.accessops = &sstouch_accessops; 145 1.1 nisimura mas.accesscookie = sc; 146 1.1 nisimura 147 1.4 thorpej sc->wsmousedev = config_found(self, &mas, wsmousedevprint, CFARGS_NONE); 148 1.1 nisimura 149 1.1 nisimura tpcalib_init(&sc->tpcalib); 150 1.1 nisimura tpcalib_ioctl(&sc->tpcalib, WSMOUSEIO_SCALIBCOORDS, 151 1.1 nisimura (void*)&default_calib, 0, 0); 152 1.1 nisimura 153 1.1 nisimura sc->sample_count = 0; 154 1.1 nisimura 155 1.1 nisimura /* Add CALLOUT_MPSAFE to avoid holding the global kernel lock */ 156 1.1 nisimura callout_init(&sc->callout, 0); 157 1.1 nisimura callout_setfunc(&sc->callout, sstouch_callout, sc); 158 1.1 nisimura 159 1.1 nisimura /* Actual initialization is performed by sstouch_initialize(), 160 1.1 nisimura which is called by sstouch_enable() */ 161 1.1 nisimura } 162 1.1 nisimura 163 1.1 nisimura /* sstouch_tc_intr is the TC interrupt handler. 164 1.1 nisimura The TC interrupt is generated when the stylus changes up->down, 165 1.1 nisimura or down->up state (depending on configuration of ADC_ADCTSC). 166 1.1 nisimura */ 167 1.1 nisimura int 168 1.1 nisimura sstouch_tc_intr(void *arg) 169 1.1 nisimura { 170 1.1 nisimura struct sstouch_softc *sc = (struct sstouch_softc*)arg; 171 1.1 nisimura uint32_t reg; 172 1.1 nisimura 173 1.1 nisimura /*aprint_normal("%s\n", __func__);*/ 174 1.1 nisimura 175 1.1 nisimura /* Figure out if the stylus was lifted or lowered */ 176 1.1 nisimura reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCUPDN); 177 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCUPDN, 0x0); 178 1.1 nisimura if( sc->next_stylus_intr == STYLUS_DOWN && (reg & ADCUPDN_TSC_DN) ) { 179 1.1 nisimura sc->next_stylus_intr = STYLUS_UP; 180 1.1 nisimura 181 1.1 nisimura sstouch_callout(sc); 182 1.1 nisimura 183 1.1 nisimura } else if (sc->next_stylus_intr == STYLUS_UP && (reg & ADCUPDN_TSC_UP)) { 184 1.1 nisimura uint32_t adctsc = 0; 185 1.1 nisimura sc->next_stylus_intr = STYLUS_DOWN; 186 1.1 nisimura 187 1.1 nisimura wsmouse_input(sc->wsmousedev, 0x0, 0, 0, 0, 0, 0); 188 1.1 nisimura 189 1.1 nisimura sc->sample_count = 0; 190 1.1 nisimura 191 1.1 nisimura adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN | 192 1.1 nisimura sc->next_stylus_intr | 193 1.1 nisimura 3; /* 3 selects "Waiting for Interrupt Mode" */ 194 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc); 195 1.1 nisimura } 196 1.1 nisimura 197 1.1 nisimura return 1; 198 1.1 nisimura } 199 1.1 nisimura 200 1.1 nisimura /* sstouch_adc_intr is ADC interrupt handler. 201 1.1 nisimura ADC interrupt is triggered when the ADC controller has a measurement ready. 202 1.1 nisimura */ 203 1.1 nisimura int 204 1.1 nisimura sstouch_adc_intr(void *arg) 205 1.1 nisimura { 206 1.1 nisimura struct sstouch_softc *sc = (struct sstouch_softc*)arg; 207 1.1 nisimura uint32_t reg; 208 1.1 nisimura uint32_t adctsc = 0; 209 1.1 nisimura int x, y; 210 1.1 nisimura 211 1.1 nisimura reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCDAT0); 212 1.1 nisimura y = reg & ADCDAT_DATAMASK; 213 1.1 nisimura 214 1.1 nisimura reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCDAT1); 215 1.1 nisimura x = reg & ADCDAT_DATAMASK; 216 1.1 nisimura 217 1.1 nisimura 218 1.1 nisimura sc->samples_x[sc->sample_count] = x; 219 1.1 nisimura sc->samples_y[sc->sample_count] = y; 220 1.1 nisimura 221 1.1 nisimura sc->sample_count++; 222 1.1 nisimura 223 1.1 nisimura x = sstouch_filter_values(sc->samples_x, sc->sample_count); 224 1.1 nisimura y = sstouch_filter_values(sc->samples_y, sc->sample_count); 225 1.1 nisimura 226 1.1 nisimura if (x == -1 || y == -1) { 227 1.1 nisimura /* If we do not have enough measurements, make some more. */ 228 1.1 nisimura sstouch_callout(sc); 229 1.1 nisimura return 1; 230 1.1 nisimura } 231 1.1 nisimura 232 1.1 nisimura sc->sample_count = 0; 233 1.1 nisimura 234 1.1 nisimura tpcalib_trans(&sc->tpcalib, x, y, &x, &y); 235 1.1 nisimura 236 1.1 nisimura wsmouse_input(sc->wsmousedev, 0x1, x, y, 0, 0, 237 1.1 nisimura WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y); 238 1.1 nisimura 239 1.1 nisimura /* Schedule a new adc measurement, unless the stylus has been lifed */ 240 1.1 nisimura if (sc->next_stylus_intr == STYLUS_UP) { 241 1.1 nisimura callout_schedule(&sc->callout, hz/50); 242 1.1 nisimura } 243 1.1 nisimura 244 1.1 nisimura /* Until measurement is to be performed, listen for stylus up-events */ 245 1.1 nisimura adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN | 246 1.1 nisimura sc->next_stylus_intr | 247 1.1 nisimura 3; /* 3 selects "Waiting for Interrupt Mode" */ 248 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc); 249 1.1 nisimura 250 1.1 nisimura 251 1.1 nisimura return 1; 252 1.1 nisimura } 253 1.1 nisimura 254 1.1 nisimura int 255 1.1 nisimura sstouch_enable(void *arg) 256 1.1 nisimura { 257 1.1 nisimura struct sstouch_softc *sc = arg; 258 1.1 nisimura 259 1.1 nisimura sstouch_initialize(sc); 260 1.1 nisimura 261 1.1 nisimura return 0; 262 1.1 nisimura } 263 1.1 nisimura 264 1.1 nisimura int 265 1.1 nisimura sstouch_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l) 266 1.1 nisimura { 267 1.1 nisimura struct sstouch_softc *sc = v; 268 1.1 nisimura 269 1.1 nisimura aprint_normal("%s\n", __func__); 270 1.1 nisimura 271 1.1 nisimura switch (cmd) { 272 1.1 nisimura case WSMOUSEIO_GTYPE: 273 1.1 nisimura *(uint *)data = WSMOUSE_TYPE_PSEUDO; 274 1.1 nisimura break; 275 1.1 nisimura case WSMOUSEIO_GCALIBCOORDS: 276 1.1 nisimura case WSMOUSEIO_SCALIBCOORDS: 277 1.1 nisimura return tpcalib_ioctl(&sc->tpcalib, cmd, data, flag, l); 278 1.1 nisimura default: 279 1.1 nisimura return EPASSTHROUGH; 280 1.1 nisimura } 281 1.1 nisimura 282 1.1 nisimura return 0; 283 1.1 nisimura } 284 1.1 nisimura 285 1.1 nisimura void 286 1.1 nisimura sstouch_disable(void *arg) 287 1.1 nisimura { 288 1.1 nisimura struct sstouch_softc *sc = (struct sstouch_softc*)arg; 289 1.1 nisimura 290 1.1 nisimura /* By setting ADCCON register to 0, we also disable 291 1.1 nisimura the prescaler, which should disable any interrupts. 292 1.1 nisimura */ 293 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, 0); 294 1.1 nisimura } 295 1.1 nisimura 296 1.1 nisimura void 297 1.1 nisimura sstouch_callout(void *arg) 298 1.1 nisimura { 299 1.1 nisimura struct sstouch_softc *sc = (struct sstouch_softc*)arg; 300 1.1 nisimura 301 1.1 nisimura /* If stylus is down, perform a measurement */ 302 1.1 nisimura if (sc->next_stylus_intr == STYLUS_UP) { 303 1.1 nisimura uint32_t reg; 304 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, 305 1.1 nisimura ADCTSC_YM_SEN | ADCTSC_YP_SEN | 306 1.1 nisimura ADCTSC_XP_SEN | ADCTSC_PULL_UP | 307 1.1 nisimura ADCTSC_AUTO_PST); 308 1.1 nisimura 309 1.1 nisimura reg = bus_space_read_4(sc->iot, sc->ioh, ADC_ADCCON); 310 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, 311 1.1 nisimura reg | ADCCON_ENABLE_START); 312 1.1 nisimura } 313 1.1 nisimura 314 1.1 nisimura } 315 1.1 nisimura 316 1.1 nisimura /* Do some very simple filtering on the measured values */ 317 1.1 nisimura int 318 1.1 nisimura sstouch_filter_values(int *vals, int val_count) 319 1.1 nisimura { 320 1.1 nisimura int sum = 0; 321 1.1 nisimura 322 1.1 nisimura if (val_count < 5) 323 1.1 nisimura return -1; 324 1.1 nisimura 325 1.1 nisimura for (int i=0; i<val_count; i++) { 326 1.1 nisimura sum += vals[i]; 327 1.1 nisimura } 328 1.1 nisimura 329 1.1 nisimura return sum/val_count; 330 1.1 nisimura } 331 1.1 nisimura 332 1.1 nisimura void 333 1.1 nisimura sstouch_initialize(struct sstouch_softc *sc) 334 1.1 nisimura { 335 1.1 nisimura int prescaler; 336 1.1 nisimura uint32_t adccon = 0; 337 1.1 nisimura uint32_t adctsc = 0; 338 1.1 nisimura 339 1.1 nisimura /* ADC Conversion rate is calculated by: 340 1.1 nisimura f(ADC) = PCLK/(prescaler+1) 341 1.1 nisimura 342 1.1 nisimura The ADC can operate at a maximum frequency of 2.5MHz for 343 1.1 nisimura 500 KSPS. 344 1.1 nisimura */ 345 1.1 nisimura 346 1.1 nisimura /* Set f(ADC) = 50MHz / 256 = 1,95MHz */ 347 1.1 nisimura prescaler = 0xff; 348 1.1 nisimura 349 1.1 nisimura adccon |= ((prescaler<<ADCCON_PRSCVL_SHIFT) & 350 1.1 nisimura ADCCON_PRSCVL_MASK); 351 1.1 nisimura adccon |= ADCCON_PRSCEN; 352 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCCON, adccon); 353 1.1 nisimura 354 1.1 nisimura /* Use Auto Sequential measurement of X and Y positions */ 355 1.1 nisimura adctsc |= ADCTSC_YM_SEN | ADCTSC_YP_SEN | ADCTSC_XP_SEN | 356 1.1 nisimura sc->next_stylus_intr | 357 1.1 nisimura 3; /* 3 selects "Waiting for Interrupt Mode" */ 358 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCTSC, adctsc); 359 1.1 nisimura 360 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCUPDN, 0x0); 361 1.1 nisimura 362 1.1 nisimura /* Time used to measure each X/Y position value? */ 363 1.1 nisimura bus_space_write_4(sc->iot, sc->ioh, ADC_ADCDLY, 10000); 364 1.1 nisimura } 365