spic.c revision 1.11 1 /* $NetBSD: spic.c,v 1.11 2007/12/17 19:51:10 christos 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 (at) 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.11 2007/12/17 19:51:10 christos 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("%s: unknown lid event 0x%02x\n",
170 sc->sc_dev.dv_xname, v1);
171 goto skip;
172 break;
173 }
174 }
175
176 /* Handle suspend/hibernate buttons */
177 if (v2 == 0x20) {
178 switch (v1) {
179 case 0x10: /* suspend */
180 sysmon_pswitch_event(
181 &sc->sc_smpsw[SPIC_PSWITCH_SUSPEND],
182 PSWITCH_EVENT_PRESSED);
183 goto skip;
184 break;
185 case 0x1c: /* hibernate */
186 sysmon_pswitch_event(
187 &sc->sc_smpsw[SPIC_PSWITCH_HIBERNATE],
188 PSWITCH_EVENT_PRESSED);
189 goto skip;
190 break;
191 }
192 }
193
194 buttons = 0;
195 if (v1 & 0x40)
196 buttons |= 1 << 1;
197 if (v1 & 0x20)
198 buttons |= 1 << 5;
199 dz = v1 & 0x1f;
200 switch (dz) {
201 case 0:
202 case 1:
203 case 2:
204 case 3:
205 break;
206 case 0x1f:
207 case 0x1e:
208 case 0x1d:
209 dz -= 0x20;
210 break;
211 case SPIC_EVENT_BRIGHTNESS_UP:
212 pmf_event_inject(&sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_UP);
213 break;
214 case SPIC_EVENT_BRIGHTNESS_DOWN:
215 pmf_event_inject(&sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_DOWN);
216 break;
217 default:
218 printf("spic0: v1=0x%02x v2=0x%02x\n", v1, v2);
219 goto skip;
220 }
221
222 if (!sc->sc_enabled) {
223 /*printf("spic: not enabled\n");*/
224 goto skip;
225 }
226
227 if (dz != 0 || buttons != sc->sc_buttons) {
228 #ifdef SPIC_DEBUG
229 if (spicdebug)
230 printf("spic: but=0x%x dz=%d v1=0x%02x v2=0x%02x\n",
231 buttons, dz, v1, v2);
232 #endif
233 sc->sc_buttons = buttons;
234 if (sc->sc_wsmousedev != NULL) {
235 wsmouse_input(sc->sc_wsmousedev, buttons, 0, 0, dz, 0,
236 WSMOUSE_INPUT_DELTA);
237 }
238 }
239
240 skip:
241 spic_call2(sc, 0x81, 0xff); /* Clear event */
242 return (1);
243 }
244
245 static void
246 spictimeout(void *v)
247 {
248 struct spic_softc *sc = v;
249 int s;
250
251 if (spicerror >= 3)
252 return;
253
254 s = spltty();
255 spic_intr(v);
256 splx(s);
257 callout_reset(&sc->sc_poll, POLLRATE, spictimeout, sc);
258 }
259
260 void
261 spic_attach(struct spic_softc *sc)
262 {
263 struct wsmousedev_attach_args a;
264 int i, rv;
265
266 #ifdef SPIC_DEBUG
267 if (spicdebug)
268 printf("spic_attach %x %x\n", sc->sc_iot, (uint)sc->sc_ioh);
269 #endif
270
271 callout_init(&sc->sc_poll, 0);
272
273 spic_call1(sc, 0x82);
274 spic_call2(sc, 0x81, 0xff);
275 spic_call1(sc, 0x92); /* or 0x82 */
276
277 a.accessops = &spic_accessops;
278 a.accesscookie = sc;
279 sc->sc_wsmousedev = config_found(&sc->sc_dev, &a, wsmousedevprint);
280
281 sc->sc_smpsw[SPIC_PSWITCH_LID].smpsw_name = "spiclid0";
282 sc->sc_smpsw[SPIC_PSWITCH_LID].smpsw_type = PSWITCH_TYPE_LID;
283 sc->sc_smpsw[SPIC_PSWITCH_SUSPEND].smpsw_name = "spicsuspend0";
284 sc->sc_smpsw[SPIC_PSWITCH_SUSPEND].smpsw_type = PSWITCH_TYPE_SLEEP;
285 sc->sc_smpsw[SPIC_PSWITCH_HIBERNATE].smpsw_name = "spichibernate0";
286 sc->sc_smpsw[SPIC_PSWITCH_HIBERNATE].smpsw_type = PSWITCH_TYPE_SLEEP;
287
288 for (i = 0; i < SPIC_NPSWITCH; i++) {
289 rv = sysmon_pswitch_register(&sc->sc_smpsw[i]);
290 if (rv != 0)
291 aprint_error("%s: unable to register %s with sysmon\n",
292 sc->sc_dev.dv_xname,
293 sc->sc_smpsw[i].smpsw_name);
294 }
295
296 callout_reset(&sc->sc_poll, POLLRATE, spictimeout, sc);
297
298 return;
299 }
300
301 bool
302 spic_suspend(device_t dev)
303 {
304 struct spic_softc *sc = device_private(dev);
305
306 callout_stop(&sc->sc_poll);
307
308 return true;
309 }
310
311 bool
312 spic_resume(device_t dev)
313 {
314 struct spic_softc *sc = device_private(dev);
315
316 spic_call1(sc, 0x82);
317 spic_call2(sc, 0x81, 0xff);
318 spic_call1(sc, 0x92); /* or 0x82 */
319
320 callout_reset(&sc->sc_poll, POLLRATE, spictimeout, sc);
321 return true;
322 }
323
324 static int
325 spic_enable(void *v)
326 {
327 struct spic_softc *sc = v;
328
329 if (sc->sc_enabled)
330 return (EBUSY);
331
332 sc->sc_enabled = 1;
333 sc->sc_buttons = 0;
334
335 #ifdef SPIC_DEBUG
336 if (spicdebug)
337 printf("spic_enable\n");
338 #endif
339
340 return (0);
341 }
342
343 static void
344 spic_disable(void *v)
345 {
346 struct spic_softc *sc = v;
347
348 #ifdef DIAGNOSTIC
349 if (!sc->sc_enabled) {
350 printf("spic_disable: not enabled\n");
351 return;
352 }
353 #endif
354
355 sc->sc_enabled = 0;
356
357 #ifdef SPIC_DEBUG
358 if (spicdebug)
359 printf("spic_disable\n");
360 #endif
361 }
362
363 static int
364 spic_ioctl(void *v, u_long cmd, void *data,
365 int flag, struct lwp *l)
366 {
367 switch (cmd) {
368 case WSMOUSEIO_GTYPE:
369 /* XXX this is not really correct */
370 *(u_int *)data = WSMOUSE_TYPE_PS2;
371 return (0);
372 }
373
374 return (-1);
375 }
376