1/* $NetBSD: spic.c,v 1.19 2013/10/17 21:24:24 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@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.19 2013/10/17 21:24:24 christos 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
80int spicdebug = 0;
81#endif
82
83static int spicerror = 0;
84
85static int spic_enable(void *);
86static void spic_disable(void *);
87static int spic_ioctl(void *, u_long, void *, int, struct lwp *);
88
89static 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
113static u_int8_t
114spic_call1(struct spic_softc *sc, u_int8_t dev)
115{
116 u_int8_t v2;
117
118 SPIC_COMMAND(0, INB(sc, SPIC_PORT2) & 2);
119 OUTB(sc, dev, SPIC_PORT2);
120 (void)INB(sc, SPIC_PORT2);
121 v2 = INB(sc, SPIC_PORT1);
122 return v2;
123}
124
125static u_int8_t
126spic_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 */
139int
140spic_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
232skip:
233 spic_call2(sc, 0x81, 0xff); /* Clear event */
234 return (1);
235}
236
237static void
238spictimeout(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
252void
253spic_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
292bool
293spic_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
302bool
303spic_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
315static int
316spic_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
334static void
335spic_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
354static int
355spic_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