1 /* $NetBSD: spic.c,v 1.18 2010/04/21 21:49:53 dyoung Exp $ */ 2 3 /* 4 * Copyright (c) 2002 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Lennart Augustsson (lennart@augustsson.net) at 9 * Carlstedt Research & Technology. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * The SPIC is used on some Sony Vaios to handle the jog dial and other 35 * peripherals. 36 * The protocol used by the SPIC seems to vary wildly among the different 37 * models, and I've found no documentation. 38 * This file handles the jog dial on the SRX77 model, and perhaps nothing 39 * else. 40 * 41 * The general way of talking to the SPIC was gleaned from the Linux and 42 * FreeBSD drivers. The hex numbers were taken from these drivers (they 43 * come from reverese engineering.) 44 * 45 * TODO: 46 * Make it handle more models. 47 * Figure out why the interrupt mode doesn't work. 48 */ 49 50 51 #include <sys/cdefs.h> 52 __KERNEL_RCSID(0, "$NetBSD: spic.c,v 1.18 2010/04/21 21:49:53 dyoung Exp $"); 53 54 #include <sys/param.h> 55 #include <sys/systm.h> 56 #include <sys/device.h> 57 #include <sys/proc.h> 58 #include <sys/kernel.h> 59 #include <sys/callout.h> 60 61 #include <sys/bus.h> 62 63 #include <dev/sysmon/sysmonvar.h> 64 65 #include <dev/ic/spicvar.h> 66 67 #include <dev/wscons/wsconsio.h> 68 #include <dev/wscons/wsmousevar.h> 69 70 #define SPIC_EVENT_BRIGHTNESS_DOWN 0x15 71 #define SPIC_EVENT_BRIGHTNESS_UP 0x16 72 73 #define POLLRATE (hz/30) 74 75 /* Some hardware constants */ 76 #define SPIC_PORT1 0 77 #define SPIC_PORT2 4 78 79 #ifdef SPIC_DEBUG 80 int spicdebug = 0; 81 #endif 82 83 static int spicerror = 0; 84 85 static int spic_enable(void *); 86 static void spic_disable(void *); 87 static int spic_ioctl(void *, u_long, void *, int, struct lwp *); 88 89 static const struct wsmouse_accessops spic_accessops = { 90 spic_enable, 91 spic_ioctl, 92 spic_disable, 93 }; 94 95 #define SPIC_COMMAND(quiet, command) do { \ 96 unsigned int n = 10000; \ 97 while (--n && (command)) \ 98 delay(1); \ 99 if (n == 0 && !(quiet)) { \ 100 printf("spic0: command failed at line %d\n", __LINE__); \ 101 spicerror++; \ 102 } \ 103 } while (0) 104 105 #if 0 106 #define INB(sc, p) (delay(100), printf("inb(%x)=%x\n", (uint)sc->sc_ioh+p, bus_space_read_1(sc->sc_iot, sc->sc_ioh, p)), delay(100), bus_space_read_1(sc->sc_iot, sc->sc_ioh, (p))) 107 #define OUTB(sc, v, p) do { delay(100); bus_space_write_1(sc->sc_iot, sc->sc_ioh, (p), (v)); printf("outb(%x, %x)\n", (uint)sc->sc_ioh+p, v); } while(0) 108 #else 109 #define INB(sc, p) (delay(100), bus_space_read_1(sc->sc_iot, sc->sc_ioh, (p))) 110 #define OUTB(sc, v, p) do { delay(100); bus_space_write_1(sc->sc_iot, sc->sc_ioh, (p), (v)); } while(0) 111 #endif 112 113 static u_int8_t 114 spic_call1(struct spic_softc *sc, u_int8_t dev) 115 { 116 u_int8_t v1, v2; 117 118 SPIC_COMMAND(0, INB(sc, SPIC_PORT2) & 2); 119 OUTB(sc, dev, SPIC_PORT2); 120 v1 = INB(sc, SPIC_PORT2); 121 v2 = INB(sc, SPIC_PORT1); 122 return v2; 123 } 124 125 static u_int8_t 126 spic_call2(struct spic_softc *sc, u_int8_t dev, u_int8_t fn) 127 { 128 u_int8_t v1; 129 130 SPIC_COMMAND(0, INB(sc, SPIC_PORT2) & 2); 131 OUTB(sc, dev, SPIC_PORT2); 132 SPIC_COMMAND(0, INB(sc, SPIC_PORT2) & 2); 133 OUTB(sc, fn, SPIC_PORT1); 134 v1 = INB(sc, SPIC_PORT1); 135 return v1; 136 } 137 138 /* Interrupt handler: some event is available */ 139 int 140 spic_intr(void *v) { 141 struct spic_softc *sc = v; 142 u_int8_t v1, v2; 143 int dz, buttons; 144 145 v1 = INB(sc, SPIC_PORT1); 146 v2 = INB(sc, SPIC_PORT2); 147 148 /* Handle lid switch */ 149 if (v2 == 0x30) { 150 switch (v1) { 151 case 0x50: /* opened */ 152 sysmon_pswitch_event(&sc->sc_smpsw[SPIC_PSWITCH_LID], 153 PSWITCH_EVENT_RELEASED); 154 goto skip; 155 break; 156 case 0x51: /* closed */ 157 sysmon_pswitch_event(&sc->sc_smpsw[SPIC_PSWITCH_LID], 158 PSWITCH_EVENT_PRESSED); 159 goto skip; 160 break; 161 default: 162 aprint_debug_dev(sc->sc_dev, "unknown lid event 0x%02x\n", v1); 163 goto skip; 164 break; 165 } 166 } 167 168 /* Handle suspend/hibernate buttons */ 169 if (v2 == 0x20) { 170 switch (v1) { 171 case 0x10: /* suspend */ 172 sysmon_pswitch_event( 173 &sc->sc_smpsw[SPIC_PSWITCH_SUSPEND], 174 PSWITCH_EVENT_PRESSED); 175 goto skip; 176 break; 177 case 0x1c: /* hibernate */ 178 sysmon_pswitch_event( 179 &sc->sc_smpsw[SPIC_PSWITCH_HIBERNATE], 180 PSWITCH_EVENT_PRESSED); 181 goto skip; 182 break; 183 } 184 } 185 186 buttons = 0; 187 if (v1 & 0x40) 188 buttons |= 1 << 1; 189 if (v1 & 0x20) 190 buttons |= 1 << 5; 191 dz = v1 & 0x1f; 192 switch (dz) { 193 case 0: 194 case 1: 195 case 2: 196 case 3: 197 break; 198 case 0x1f: 199 case 0x1e: 200 case 0x1d: 201 dz -= 0x20; 202 break; 203 case SPIC_EVENT_BRIGHTNESS_UP: 204 pmf_event_inject(sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_UP); 205 break; 206 case SPIC_EVENT_BRIGHTNESS_DOWN: 207 pmf_event_inject(sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_DOWN); 208 break; 209 default: 210 printf("spic0: v1=0x%02x v2=0x%02x\n", v1, v2); 211 goto skip; 212 } 213 214 if (!sc->sc_enabled) { 215 /*printf("spic: not enabled\n");*/ 216 goto skip; 217 } 218 219 if (dz != 0 || buttons != sc->sc_buttons) { 220 #ifdef SPIC_DEBUG 221 if (spicdebug) 222 printf("spic: but=0x%x dz=%d v1=0x%02x v2=0x%02x\n", 223 buttons, dz, v1, v2); 224 #endif 225 sc->sc_buttons = buttons; 226 if (sc->sc_wsmousedev != NULL) { 227 wsmouse_input(sc->sc_wsmousedev, buttons, 0, 0, dz, 0, 228 WSMOUSE_INPUT_DELTA); 229 } 230 } 231 232 skip: 233 spic_call2(sc, 0x81, 0xff); /* Clear event */ 234 return (1); 235 } 236 237 static void 238 spictimeout(void *v) 239 { 240 struct spic_softc *sc = v; 241 int s; 242 243 if (spicerror >= 3) 244 return; 245 246 s = spltty(); 247 spic_intr(v); 248 splx(s); 249 callout_reset(&sc->sc_poll, POLLRATE, spictimeout, sc); 250 } 251 252 void 253 spic_attach(struct spic_softc *sc) 254 { 255 struct wsmousedev_attach_args a; 256 int i, rv; 257 258 #ifdef SPIC_DEBUG 259 if (spicdebug) 260 printf("spic_attach %x\n", (uint)sc->sc_ioh); 261 #endif 262 263 callout_init(&sc->sc_poll, 0); 264 265 spic_call1(sc, 0x82); 266 spic_call2(sc, 0x81, 0xff); 267 spic_call1(sc, 0x92); /* or 0x82 */ 268 269 a.accessops = &spic_accessops; 270 a.accesscookie = sc; 271 sc->sc_wsmousedev = config_found(sc->sc_dev, &a, wsmousedevprint); 272 273 sc->sc_smpsw[SPIC_PSWITCH_LID].smpsw_name = "spiclid0"; 274 sc->sc_smpsw[SPIC_PSWITCH_LID].smpsw_type = PSWITCH_TYPE_LID; 275 sc->sc_smpsw[SPIC_PSWITCH_SUSPEND].smpsw_name = "spicsuspend0"; 276 sc->sc_smpsw[SPIC_PSWITCH_SUSPEND].smpsw_type = PSWITCH_TYPE_SLEEP; 277 sc->sc_smpsw[SPIC_PSWITCH_HIBERNATE].smpsw_name = "spichibernate0"; 278 sc->sc_smpsw[SPIC_PSWITCH_HIBERNATE].smpsw_type = PSWITCH_TYPE_SLEEP; 279 280 for (i = 0; i < SPIC_NPSWITCH; i++) { 281 rv = sysmon_pswitch_register(&sc->sc_smpsw[i]); 282 if (rv != 0) 283 aprint_error_dev(sc->sc_dev, "unable to register %s with sysmon\n", 284 sc->sc_smpsw[i].smpsw_name); 285 } 286 287 callout_reset(&sc->sc_poll, POLLRATE, spictimeout, sc); 288 289 return; 290 } 291 292 bool 293 spic_suspend(device_t dev, const pmf_qual_t *qual) 294 { 295 struct spic_softc *sc = device_private(dev); 296 297 callout_stop(&sc->sc_poll); 298 299 return true; 300 } 301 302 bool 303 spic_resume(device_t dev, const pmf_qual_t *qual) 304 { 305 struct spic_softc *sc = device_private(dev); 306 307 spic_call1(sc, 0x82); 308 spic_call2(sc, 0x81, 0xff); 309 spic_call1(sc, 0x92); /* or 0x82 */ 310 311 callout_reset(&sc->sc_poll, POLLRATE, spictimeout, sc); 312 return true; 313 } 314 315 static int 316 spic_enable(void *v) 317 { 318 struct spic_softc *sc = v; 319 320 if (sc->sc_enabled) 321 return (EBUSY); 322 323 sc->sc_enabled = 1; 324 sc->sc_buttons = 0; 325 326 #ifdef SPIC_DEBUG 327 if (spicdebug) 328 printf("spic_enable\n"); 329 #endif 330 331 return (0); 332 } 333 334 static void 335 spic_disable(void *v) 336 { 337 struct spic_softc *sc = v; 338 339 #ifdef DIAGNOSTIC 340 if (!sc->sc_enabled) { 341 printf("spic_disable: not enabled\n"); 342 return; 343 } 344 #endif 345 346 sc->sc_enabled = 0; 347 348 #ifdef SPIC_DEBUG 349 if (spicdebug) 350 printf("spic_disable\n"); 351 #endif 352 } 353 354 static int 355 spic_ioctl(void *v, u_long cmd, void *data, 356 int flag, struct lwp *l) 357 { 358 switch (cmd) { 359 case WSMOUSEIO_GTYPE: 360 /* XXX this is not really correct */ 361 *(u_int *)data = WSMOUSE_TYPE_PS2; 362 return (0); 363 } 364 365 return (-1); 366 } 367