pms.c revision 1.7
1/* $OpenBSD: pms.c,v 1.7 2010/10/02 00:28:57 krw Exp $ */
2/* $NetBSD: psm.c,v 1.11 2000/06/05 22:20:57 sommerfeld Exp $ */
3
4/*-
5 * Copyright (c) 1994 Charles M. Hannum.
6 * Copyright (c) 1992, 1993 Erik Forsberg.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
18 * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/param.h>
28#include <sys/systm.h>
29#include <sys/device.h>
30#include <sys/ioctl.h>
31
32#include <machine/bus.h>
33
34#include <dev/ic/pckbcvar.h>
35
36#include <dev/pckbc/pmsreg.h>
37
38#include <dev/wscons/wsconsio.h>
39#include <dev/wscons/wsmousevar.h>
40
41struct pms_softc {		/* driver status information */
42	struct device sc_dev;
43
44	pckbc_tag_t sc_kbctag;
45	int sc_kbcslot;
46
47	int sc_state;
48#define PMS_STATE_DISABLED	0
49#define PMS_STATE_ENABLED	1
50#define PMS_STATE_SUSPENDED	2
51
52	int poll;
53	int intelli;
54	int inputstate;
55	u_int buttons, oldbuttons;	/* mouse button status */
56	signed char dx, dy;
57
58	struct device *sc_wsmousedev;
59};
60
61int pmsprobe(struct device *, void *, void *);
62void pmsattach(struct device *, struct device *, void *);
63int pmsactivate(struct device *, int);
64void pmsinput(void *, int);
65
66struct cfattach pms_ca = {
67	sizeof(struct pms_softc), pmsprobe, pmsattach, NULL,
68	pmsactivate
69};
70
71int	pms_change_state(struct pms_softc *, int);
72int	pms_ioctl(void *, u_long, caddr_t, int, struct proc *);
73int	pms_enable(void *);
74void	pms_disable(void *);
75
76int	pms_cmd(struct pms_softc *, u_char *, int, u_char *, int);
77
78int	pms_setintellimode(struct pms_softc *sc);
79
80const struct wsmouse_accessops pms_accessops = {
81	pms_enable,
82	pms_ioctl,
83	pms_disable,
84};
85
86int
87pms_cmd(struct pms_softc *sc, u_char *cmd, int len, u_char *resp, int resplen)
88{
89	if (sc->poll) {
90		return pckbc_poll_cmd(sc->sc_kbctag, sc->sc_kbcslot,
91		    cmd, len, resplen, resp, 1);
92	} else {
93		return pckbc_enqueue_cmd(sc->sc_kbctag, sc->sc_kbcslot,
94		    cmd, len, resplen, 1, resp);
95	}
96}
97
98int
99pms_setintellimode(struct pms_softc *sc)
100{
101	u_char cmd[2], resp[1];
102	int i, res;
103	static const u_char rates[] = {200, 100, 80};
104
105	cmd[0] = PMS_SET_SAMPLE;
106	for (i = 0; i < 3; i++) {
107		cmd[1] = rates[i];
108		res = pms_cmd(sc, cmd, 2, NULL, 0);
109		if (res)
110			return (0);
111	}
112
113	cmd[0] = PMS_SEND_DEV_ID;
114	res = pms_cmd(sc, cmd, 1, resp, 1);
115	if (res || resp[0] != 3)
116		return (0);
117
118	return (1);
119}
120
121int
122pmsprobe(parent, match, aux)
123	struct device *parent;
124	void *match;
125	void *aux;
126{
127	struct pckbc_attach_args *pa = aux;
128	u_char cmd[1], resp[2];
129	int res;
130
131	if (pa->pa_slot != PCKBC_AUX_SLOT)
132		return (0);
133
134	/* Flush any garbage. */
135	pckbc_flush(pa->pa_tag, pa->pa_slot);
136
137	/* reset the device */
138	cmd[0] = PMS_RESET;
139	res = pckbc_poll_cmd(pa->pa_tag, pa->pa_slot, cmd, 1, 2, resp, 1);
140	if (res) {
141#ifdef DEBUG
142		printf("pmsprobe: reset error %d\n", res);
143#endif
144		return (0);
145	}
146	if (resp[0] != PMS_RSTDONE) {
147		printf("pmsprobe: reset response 0x%x\n", resp[0]);
148		return (0);
149	}
150
151	/* get type number (0 = mouse) */
152	if (resp[1] != 0) {
153#ifdef DEBUG
154		printf("pmsprobe: type 0x%x\n", resp[1]);
155#endif
156		return (0);
157	}
158
159	return (1);
160}
161
162void
163pmsattach(parent, self, aux)
164	struct device *parent, *self;
165	void *aux;
166{
167	struct pms_softc *sc = (void *)self;
168	struct pckbc_attach_args *pa = aux;
169	struct wsmousedev_attach_args a;
170	u_char cmd[1], resp[2];
171	int res;
172
173	sc->sc_kbctag = pa->pa_tag;
174	sc->sc_kbcslot = pa->pa_slot;
175
176	printf("\n");
177
178	/* Flush any garbage. */
179	pckbc_flush(pa->pa_tag, pa->pa_slot);
180
181	/* reset the device */
182	cmd[0] = PMS_RESET;
183	res = pckbc_poll_cmd(pa->pa_tag, pa->pa_slot, cmd, 1, 2, resp, 1);
184#ifdef DEBUG
185	if (res || resp[0] != PMS_RSTDONE || resp[1] != 0) {
186		printf("pmsattach: reset error\n");
187		return;
188	}
189#endif
190
191	sc->inputstate = 0;
192	sc->oldbuttons = 0;
193
194	pckbc_set_inputhandler(sc->sc_kbctag, sc->sc_kbcslot,
195			       pmsinput, sc, sc->sc_dev.dv_xname);
196
197	a.accessops = &pms_accessops;
198	a.accesscookie = sc;
199
200	/*
201	 * Attach the wsmouse, saving a handle to it.
202	 * Note that we don't need to check this pointer against NULL
203	 * here or in pmsintr, because if this fails pms_enable() will
204	 * never be called, so pmsinput() will never be called.
205	 */
206	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
207
208	/* no interrupts until enabled */
209	sc->poll = 1;
210	pms_change_state(sc, PMS_STATE_DISABLED);
211}
212
213int
214pmsactivate(struct device *self, int act)
215{
216	struct pms_softc *sc = (struct pms_softc *)self;
217
218	switch (act) {
219	case DVACT_SUSPEND:
220		if (sc->sc_state == PMS_STATE_ENABLED)
221			pms_change_state(sc, PMS_STATE_SUSPENDED);
222		break;
223	case DVACT_RESUME:
224		if (sc->sc_state == PMS_STATE_SUSPENDED)
225			pms_change_state(sc, PMS_STATE_ENABLED);
226		break;
227	}
228	return (0);
229}
230
231int
232pms_change_state(struct pms_softc *sc, int newstate)
233{
234	u_char cmd[1], resp[2];
235	int res;
236
237	switch (newstate) {
238	case PMS_STATE_ENABLED:
239		if (sc->sc_state == PMS_STATE_ENABLED)
240			return EBUSY;
241		sc->inputstate = 0;
242		sc->oldbuttons = 0;
243
244		pckbc_slot_enable(sc->sc_kbctag, sc->sc_kbcslot, 1);
245
246		pckbc_flush(sc->sc_kbctag, sc->sc_kbcslot);
247
248		cmd[0] = PMS_RESET;
249		res = pms_cmd(sc, cmd, 1, resp, 2);
250
251		sc->intelli = pms_setintellimode(sc);
252
253		cmd[0] = PMS_DEV_ENABLE;
254		res = pms_cmd(sc, cmd, 1, NULL, 0);
255		if (res)
256			printf("pms_enable: command error\n");
257#if 0
258		{
259			u_char scmd[2];
260
261			scmd[0] = PMS_SET_RES;
262			scmd[1] = 3; /* 8 counts/mm */
263			res = pckbc_enqueue_cmd(sc->sc_kbctag, sc->sc_kbcslot, scmd,
264						2, 0, 1, 0);
265			if (res)
266				printf("pms_enable: setup error1 (%d)\n", res);
267
268			scmd[0] = PMS_SET_SCALE21;
269			res = pckbc_enqueue_cmd(sc->sc_kbctag, sc->sc_kbcslot, scmd,
270						1, 0, 1, 0);
271			if (res)
272				printf("pms_enable: setup error2 (%d)\n", res);
273
274			scmd[0] = PMS_SET_SAMPLE;
275			scmd[1] = 100; /* 100 samples/sec */
276			res = pckbc_enqueue_cmd(sc->sc_kbctag, sc->sc_kbcslot, scmd,
277						2, 0, 1, 0);
278			if (res)
279				printf("pms_enable: setup error3 (%d)\n", res);
280		}
281#endif
282		sc->sc_state = newstate;
283		sc->poll = 0;
284		break;
285	case PMS_STATE_DISABLED:
286
287		/* FALLTHROUGH */
288	case PMS_STATE_SUSPENDED:
289		cmd[0] = PMS_DEV_DISABLE;
290		res = pms_cmd(sc, cmd, 1, NULL, 0);
291		if (res)
292			printf("pms_disable: command error\n");
293		pckbc_slot_enable(sc->sc_kbctag, sc->sc_kbcslot, 0);
294		sc->sc_state = newstate;
295		sc->poll = (newstate == PMS_STATE_SUSPENDED) ? 1 : 0;
296		break;
297	}
298	return 0;
299}
300
301int
302pms_enable(v)
303	void *v;
304{
305	struct pms_softc *sc = v;
306
307	return pms_change_state(sc, PMS_STATE_ENABLED);
308}
309
310void
311pms_disable(v)
312	void *v;
313{
314	struct pms_softc *sc = v;
315
316	pms_change_state(sc, PMS_STATE_DISABLED);
317}
318
319int
320pms_ioctl(v, cmd, data, flag, p)
321	void *v;
322	u_long cmd;
323	caddr_t data;
324	int flag;
325	struct proc *p;
326{
327	struct pms_softc *sc = v;
328	u_char kbcmd[2];
329	int i;
330
331	switch (cmd) {
332	case WSMOUSEIO_GTYPE:
333		*(u_int *)data = WSMOUSE_TYPE_PS2;
334		break;
335
336	case WSMOUSEIO_SRES:
337		i = ((int) *(u_int *)data - 12) / 25;
338		/* valid values are {0,1,2,3} */
339		if (i < 0)
340			i = 0;
341		if (i > 3)
342			i = 3;
343
344		kbcmd[0] = PMS_SET_RES;
345		kbcmd[1] = (unsigned char) i;
346		i = pckbc_enqueue_cmd(sc->sc_kbctag, sc->sc_kbcslot, kbcmd,
347		    2, 0, 1, 0);
348
349		if (i)
350			printf("pms_ioctl: SET_RES command error\n");
351		break;
352
353	default:
354		return (-1);
355	}
356	return (0);
357}
358
359/* Masks for the first byte of a packet */
360#define PS2LBUTMASK 0x01
361#define PS2RBUTMASK 0x02
362#define PS2MBUTMASK 0x04
363
364void pmsinput(vsc, data)
365void *vsc;
366int data;
367{
368	struct pms_softc *sc = vsc;
369	signed char dz = 0;
370	u_int changed;
371
372	if (sc->sc_state != PMS_STATE_ENABLED) {
373		/* Interrupts are not expected.  Discard the byte. */
374		return;
375	}
376
377	switch (sc->inputstate) {
378
379	case 0:
380		if ((data & 0xc0) == 0) { /* no ovfl, bit 3 == 1 too? */
381			sc->buttons = ((data & PS2LBUTMASK) ? 0x1 : 0) |
382			    ((data & PS2MBUTMASK) ? 0x2 : 0) |
383			    ((data & PS2RBUTMASK) ? 0x4 : 0);
384			++sc->inputstate;
385		}
386		break;
387
388	case 1:
389		sc->dx = data;
390		/* Bounding at -127 avoids a bug in XFree86. */
391		sc->dx = (sc->dx == -128) ? -127 : sc->dx;
392		++sc->inputstate;
393		break;
394
395	case 2:
396		sc->dy = data;
397		sc->dy = (sc->dy == -128) ? -127 : sc->dy;
398		++sc->inputstate;
399		break;
400
401	case 3:
402		dz = data;
403		dz = (dz == -128) ? -127 : dz;
404		++sc->inputstate;
405		break;
406	}
407
408	if ((sc->inputstate == 3 && sc->intelli == 0) || sc->inputstate == 4) {
409		sc->inputstate = 0;
410
411		changed = (sc->buttons ^ sc->oldbuttons);
412		sc->oldbuttons = sc->buttons;
413
414		if (sc->dx || sc->dy || dz || changed)
415			wsmouse_input(sc->sc_wsmousedev,
416				      sc->buttons, sc->dx, sc->dy, dz, 0,
417				      WSMOUSE_INPUT_DELTA);
418	}
419
420	return;
421}
422
423struct cfdriver pms_cd = {
424	NULL, "pms", DV_DULL
425};
426