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