spic.c revision 1.13 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 (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.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