xref: /openbsd-src/sys/dev/i2c/ietp.c (revision 07e9317caff2fb037172176fa6113868d345aa5c)
1*07e9317cSderaadt /* $OpenBSD: ietp.c,v 1.3 2024/08/18 03:25:04 deraadt Exp $ */
2593d792cSjcs /*
38b995cd9Sjcs  * Elan I2C Touchpad driver
4593d792cSjcs  *
5593d792cSjcs  * Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
6593d792cSjcs  * Copyright (c) 2020, 2022 Vladimir Kondratyev <wulf@FreeBSD.org>
7593d792cSjcs  * Copyright (c) 2023 vladimir serbinenko <phcoder@gmail.com>
8593d792cSjcs  *
9593d792cSjcs  * Permission to use, copy, modify, and distribute this software for any
10593d792cSjcs  * purpose with or without fee is hereby granted, provided that the above
11593d792cSjcs  * copyright notice and this permission notice appear in all copies.
12593d792cSjcs  *
13593d792cSjcs  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14593d792cSjcs  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15593d792cSjcs  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16593d792cSjcs  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17593d792cSjcs  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18593d792cSjcs  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19593d792cSjcs  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20593d792cSjcs  */
21593d792cSjcs 
228b995cd9Sjcs /* Protocol documentation:
238b995cd9Sjcs  * https://lkml.indiana.edu/hypermail/linux/kernel/1205.0/02551.html
248b995cd9Sjcs  * Based on FreeBSD ietp driver.
25593d792cSjcs  */
26593d792cSjcs 
27593d792cSjcs #include <sys/param.h>
28593d792cSjcs #include <sys/systm.h>
29593d792cSjcs #include <sys/device.h>
30593d792cSjcs #include <sys/malloc.h>
31593d792cSjcs #include <sys/stdint.h>
32593d792cSjcs 
33593d792cSjcs #include <dev/i2c/i2cvar.h>
34593d792cSjcs #include <dev/i2c/ietp.h>
35593d792cSjcs 
36593d792cSjcs #include <dev/wscons/wsconsio.h>
37593d792cSjcs #include <dev/wscons/wsmousevar.h>
38593d792cSjcs 
39593d792cSjcs /* #define IETP_DEBUG */
40593d792cSjcs 
41593d792cSjcs #ifdef IETP_DEBUG
42593d792cSjcs #define DPRINTF(x) printf x
43593d792cSjcs #else
44593d792cSjcs #define DPRINTF(x)
45593d792cSjcs #endif
46593d792cSjcs 
47593d792cSjcs enum {
48593d792cSjcs 	I2C_HID_CMD_DESCR	= 0x0,
49593d792cSjcs 	I2C_HID_CMD_RESET	= 0x1,
50593d792cSjcs 	I2C_HID_CMD_SET_POWER	= 0x8,
51593d792cSjcs };
52593d792cSjcs 
53593d792cSjcs #define I2C_HID_POWER_ON	0x0
54593d792cSjcs #define I2C_HID_POWER_OFF	0x1
55593d792cSjcs 
56593d792cSjcs #define IETP_PATTERN            0x0100
57593d792cSjcs #define	IETP_UNIQUEID		0x0101
58593d792cSjcs #define	IETP_IC_TYPE		0x0103
59593d792cSjcs #define	IETP_OSM_VERSION	0x0103
60593d792cSjcs #define	IETP_NSM_VERSION	0x0104
61593d792cSjcs #define	IETP_TRACENUM		0x0105
62593d792cSjcs #define	IETP_MAX_X_AXIS		0x0106
63593d792cSjcs #define	IETP_MAX_Y_AXIS		0x0107
64593d792cSjcs #define	IETP_RESOLUTION		0x0108
65593d792cSjcs #define	IETP_PRESSURE		0x010A
66593d792cSjcs 
67593d792cSjcs #define	IETP_CONTROL		0x0300
68593d792cSjcs #define	IETP_CTRL_ABSOLUTE	0x0001
69593d792cSjcs #define	IETP_CTRL_STANDARD	0x0000
70593d792cSjcs 
71593d792cSjcs #define	IETP_REPORT_LEN_LO	31
72593d792cSjcs #define	IETP_REPORT_LEN_HI	36
73593d792cSjcs #define	IETP_MAX_FINGERS	5
74593d792cSjcs 
75593d792cSjcs #define	IETP_REPORT_ID_LO	0x5D
76593d792cSjcs #define	IETP_REPORT_ID_HI	0x60
77593d792cSjcs 
78593d792cSjcs #define	IETP_TOUCH_INFO		0
79593d792cSjcs #define	IETP_FINGER_DATA	1
80593d792cSjcs #define	IETP_FINGER_DATA_LEN	5
81593d792cSjcs #define	IETP_WH_DATA		31
82593d792cSjcs 
83593d792cSjcs #define	IETP_TOUCH_LMB		(1 << 0)
84593d792cSjcs #define	IETP_TOUCH_RMB		(1 << 1)
85593d792cSjcs #define	IETP_TOUCH_MMB		(1 << 2)
86593d792cSjcs 
87593d792cSjcs #define	IETP_MAX_PRESSURE	255
88593d792cSjcs #define	IETP_FWIDTH_REDUCE	90
89593d792cSjcs #define	IETP_PRESSURE_BASE	25
90593d792cSjcs 
91593d792cSjcs int	ietp_match(struct device *, void *, void *);
92593d792cSjcs void	ietp_attach(struct device *, struct device *, void *);
93593d792cSjcs int	ietp_detach(struct device *, int);
94593d792cSjcs int	ietp_activate(struct device *, int);
95593d792cSjcs 
96593d792cSjcs int	ietp_intr(void *);
97593d792cSjcs int	ietp_reset(struct ietp_softc *);
98593d792cSjcs 
99593d792cSjcs int	ietp_fetch_descriptor(struct ietp_softc *sc);
100593d792cSjcs int	ietp_set_power(struct ietp_softc *sc, int power);
101593d792cSjcs int	ietp_reset_cmd(struct ietp_softc *sc);
102593d792cSjcs 
103593d792cSjcs int32_t		ietp_res2dpmm(uint8_t, bool);
104593d792cSjcs 
105593d792cSjcs int		ietp_iic_read_reg(struct ietp_softc *, uint16_t, size_t, void *);
106593d792cSjcs int		ietp_iic_write_reg(struct ietp_softc *, uint16_t, uint16_t);
107593d792cSjcs int		ietp_iic_set_absolute_mode(struct ietp_softc *, bool);
108593d792cSjcs 
109593d792cSjcs const struct cfattach ietp_ca = {
110593d792cSjcs 	sizeof(struct ietp_softc),
111593d792cSjcs 	ietp_match,
112593d792cSjcs 	ietp_attach,
113593d792cSjcs 	ietp_detach,
114593d792cSjcs 	ietp_activate,
115593d792cSjcs };
116593d792cSjcs 
117593d792cSjcs const struct wsmouse_accessops ietp_mouse_access = {
118593d792cSjcs 	ietp_enable,
119593d792cSjcs 	ietp_ioctl,
120593d792cSjcs 	ietp_disable
121593d792cSjcs };
122593d792cSjcs 
123593d792cSjcs struct cfdriver ietp_cd = {
124593d792cSjcs 	NULL, "ietp", DV_DULL
125593d792cSjcs };
126593d792cSjcs 
127593d792cSjcs int
128593d792cSjcs ietp_match(struct device *parent, void *match, void *aux)
129593d792cSjcs {
130593d792cSjcs 	struct i2c_attach_args *ia = aux;
131593d792cSjcs 
132593d792cSjcs 	if (strcmp(ia->ia_name, "ietp") == 0)
133593d792cSjcs 		return (1);
134593d792cSjcs 
135593d792cSjcs 	return (0);
136593d792cSjcs }
137593d792cSjcs 
138593d792cSjcs int32_t
139593d792cSjcs ietp_res2dpmm(uint8_t res, bool hi_precision)
140593d792cSjcs {
141593d792cSjcs 	int32_t dpi;
142593d792cSjcs 
143593d792cSjcs 	dpi = hi_precision ? 300 + res * 100 : 790 + res * 10;
144593d792cSjcs 
145593d792cSjcs 	return (dpi * 10 /254);
146593d792cSjcs }
147593d792cSjcs 
148593d792cSjcs void
149593d792cSjcs ietp_attach(struct device *parent, struct device *self, void *aux)
150593d792cSjcs {
151593d792cSjcs 	struct ietp_softc *sc = (struct ietp_softc *)self;
152593d792cSjcs  	struct i2c_attach_args *ia = aux;
153593d792cSjcs 	uint16_t buf, reg;
154593d792cSjcs 	uint8_t *buf8;
155593d792cSjcs 	uint8_t pattern;
156593d792cSjcs 	struct wsmousedev_attach_args a;
157593d792cSjcs 	struct wsmousehw *hw;
158593d792cSjcs 
159593d792cSjcs 	sc->sc_tag = ia->ia_tag;
160593d792cSjcs 	sc->sc_addr = ia->ia_addr;
161593d792cSjcs 
162593d792cSjcs 	ietp_fetch_descriptor(sc);
163593d792cSjcs 
164593d792cSjcs 	if (ia->ia_intr) {
165593d792cSjcs 		printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr));
166593d792cSjcs 
167593d792cSjcs 		sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr,
168593d792cSjcs 		    IPL_TTY, ietp_intr, sc, sc->sc_dev.dv_xname);
169593d792cSjcs 		if (sc->sc_ih == NULL) {
1708b995cd9Sjcs 			printf(", can't establish interrupt\n");
171593d792cSjcs 			return;
172593d792cSjcs 		}
173593d792cSjcs 	}
174593d792cSjcs 
175593d792cSjcs 	sc->sc_buttons = 0;
176593d792cSjcs 	sc->sc_refcnt = 0;
177593d792cSjcs 
178593d792cSjcs 	buf8 = (uint8_t *)&buf;
179593d792cSjcs 
180593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_UNIQUEID, sizeof(buf), &buf) != 0) {
1818b995cd9Sjcs 		printf(": failed reading product ID\n");
182593d792cSjcs 		return;
183593d792cSjcs 	}
184593d792cSjcs 	sc->product_id = le16toh(buf);
185593d792cSjcs 
186593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_PATTERN, sizeof(buf), &buf) != 0) {
1878b995cd9Sjcs 		printf(": failed reading pattern\n");
188593d792cSjcs 		return;
189593d792cSjcs 	}
190593d792cSjcs 	pattern = buf == 0xFFFF ? 0 : buf8[1];
191593d792cSjcs 	sc->hi_precision = pattern >= 0x02;
192593d792cSjcs 
193593d792cSjcs 	reg = pattern >= 0x01 ? IETP_IC_TYPE : IETP_OSM_VERSION;
194593d792cSjcs 	if (ietp_iic_read_reg(sc, reg, sizeof(buf), &buf) != 0) {
1958b995cd9Sjcs 		printf(": failed reading IC type\n");
196593d792cSjcs 		return;
197593d792cSjcs 	}
198593d792cSjcs 	sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1];
199593d792cSjcs 
200593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) {
2018b995cd9Sjcs 		printf(": failed reading SM version\n");
202593d792cSjcs 		return;
203593d792cSjcs 	}
204593d792cSjcs 	sc->is_clickpad = (buf8[0] & 0x10) != 0;
205593d792cSjcs 
206593d792cSjcs 	if (ietp_iic_set_absolute_mode(sc, true) != 0) {
2078b995cd9Sjcs 		printf(": failed to set absolute mode\n");
208593d792cSjcs 		return;
209593d792cSjcs 	}
210593d792cSjcs 
211593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) {
2128b995cd9Sjcs 		printf(": failed reading max x\n");
213593d792cSjcs 		return;
214593d792cSjcs 	}
215593d792cSjcs 	sc->max_x = le16toh(buf);
216593d792cSjcs 
217593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) {
2188b995cd9Sjcs 		printf(": failed reading max y\n");
219593d792cSjcs 		return;
220593d792cSjcs 	}
221593d792cSjcs 	sc->max_y = le16toh(buf);
222593d792cSjcs 
223593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_TRACENUM, sizeof(buf), &buf) != 0) {
2248b995cd9Sjcs 		printf(": failed reading trace info\n");
225593d792cSjcs 		return;
226593d792cSjcs 	}
227593d792cSjcs 	sc->trace_x = sc->max_x / buf8[0];
228593d792cSjcs 	sc->trace_y = sc->max_y / buf8[1];
229593d792cSjcs 
230593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_PRESSURE, sizeof(buf), &buf) != 0) {
2318b995cd9Sjcs 		printf(": failed reading pressure format\n");
232593d792cSjcs 		return;
233593d792cSjcs 	}
234593d792cSjcs 	sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE;
235593d792cSjcs 
236593d792cSjcs 	if (ietp_iic_read_reg(sc, IETP_RESOLUTION, sizeof(buf), &buf)  != 0) {
2378b995cd9Sjcs 		printf(": failed reading resolution\n");
238593d792cSjcs 		return;
239593d792cSjcs 	}
240593d792cSjcs 	/* Conversion from internal format to dot per mm */
241593d792cSjcs 	sc->res_x = ietp_res2dpmm(buf8[0], sc->hi_precision);
242593d792cSjcs 	sc->res_y = ietp_res2dpmm(buf8[1], sc->hi_precision);
243593d792cSjcs 
244593d792cSjcs 	sc->report_id = sc->hi_precision ?
245593d792cSjcs 	    IETP_REPORT_ID_HI : IETP_REPORT_ID_LO;
246593d792cSjcs 	sc->report_len = sc->hi_precision ?
247593d792cSjcs 	    IETP_REPORT_LEN_HI : IETP_REPORT_LEN_LO;
248593d792cSjcs 
2498b995cd9Sjcs 	sc->sc_ibuf = malloc(IETP_REPORT_LEN_HI + 12, M_DEVBUF,
2508b995cd9Sjcs 	    M_NOWAIT | M_ZERO);
251593d792cSjcs 	sc->sc_isize = sc->report_len + 3;
252593d792cSjcs 
253593d792cSjcs 	a.accessops = &ietp_mouse_access;
254593d792cSjcs 	a.accesscookie = sc;
255593d792cSjcs 	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
256593d792cSjcs 
257593d792cSjcs 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
258593d792cSjcs 	hw->type = WSMOUSE_TYPE_TOUCHPAD;
259593d792cSjcs 	hw->hw_type = sc->is_clickpad ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD;
260593d792cSjcs 	hw->x_min = 0;
261593d792cSjcs 	hw->x_max = sc->max_x;
262593d792cSjcs 	hw->y_min = 0;
263593d792cSjcs 	hw->y_max = sc->max_y;
264593d792cSjcs 	hw->h_res = sc->res_x;
265593d792cSjcs 	hw->v_res = sc->res_y;
266593d792cSjcs 	hw->mt_slots = IETP_MAX_FINGERS;
267593d792cSjcs 
268593d792cSjcs 	wsmouse_configure(sc->sc_wsmousedev, NULL, 0);
269593d792cSjcs 
270593d792cSjcs 	/* power down until we're opened */
271593d792cSjcs 	if (ietp_set_power(sc, I2C_HID_POWER_OFF)) {
2728b995cd9Sjcs 		printf(": failed to power down\n");
273593d792cSjcs 		return;
274593d792cSjcs 	}
275593d792cSjcs 
2768b995cd9Sjcs 	printf("\n");
2778b995cd9Sjcs 
278593d792cSjcs 	DPRINTF(("%s: max_x=%d, max_y=%d, %s\n", sc->sc_dev.dv_xname,
279593d792cSjcs 		 sc->max_x, sc->max_y,
280593d792cSjcs 		 sc->is_clickpad ? "clickpad" : "touchpad"));
281593d792cSjcs 
282593d792cSjcs 	return;
283593d792cSjcs }
284593d792cSjcs 
285593d792cSjcs int
286593d792cSjcs ietp_detach(struct device *self, int flags)
287593d792cSjcs {
288593d792cSjcs 	struct ietp_softc *sc = (struct ietp_softc *)self;
289593d792cSjcs 
290593d792cSjcs 	if (sc->sc_ih != NULL) {
291593d792cSjcs 		iic_intr_disestablish(sc->sc_tag, sc->sc_ih);
292593d792cSjcs 		sc->sc_ih = NULL;
293593d792cSjcs 	}
294593d792cSjcs 
295593d792cSjcs 	if (sc->sc_ibuf != NULL) {
296593d792cSjcs 		free(sc->sc_ibuf, M_DEVBUF, sc->sc_isize);
297593d792cSjcs 		sc->sc_ibuf = NULL;
298593d792cSjcs 	}
299593d792cSjcs 
300593d792cSjcs 	return (0);
301593d792cSjcs }
302593d792cSjcs 
303593d792cSjcs int
304593d792cSjcs ietp_activate(struct device *self, int act)
305593d792cSjcs {
306593d792cSjcs 	struct ietp_softc *sc = (struct ietp_softc *)self;
307*07e9317cSderaadt 	int rv;
308593d792cSjcs 
309593d792cSjcs 	DPRINTF(("%s(%d)\n", __func__, act));
310593d792cSjcs 
311593d792cSjcs 	switch (act) {
312593d792cSjcs 	case DVACT_QUIESCE:
313*07e9317cSderaadt 		rv = config_activate_children(self, act);
314593d792cSjcs 		sc->sc_dying = 1;
315593d792cSjcs 		if (ietp_set_power(sc, I2C_HID_POWER_OFF))
316593d792cSjcs 			printf("%s: failed to power down\n",
317593d792cSjcs 			    sc->sc_dev.dv_xname);
318593d792cSjcs 		break;
319593d792cSjcs 	case DVACT_WAKEUP:
320593d792cSjcs 		ietp_reset(sc);
321593d792cSjcs 		sc->sc_dying = 0;
322*07e9317cSderaadt 		rv = config_activate_children(self, act);
323*07e9317cSderaadt 		break;
324*07e9317cSderaadt 	default:
325*07e9317cSderaadt 		rv = config_activate_children(self, act);
326593d792cSjcs 		break;
327593d792cSjcs 	}
328*07e9317cSderaadt 	return rv;
329593d792cSjcs }
330593d792cSjcs 
331593d792cSjcs void
332593d792cSjcs ietp_sleep(struct ietp_softc *sc, int ms)
333593d792cSjcs {
334593d792cSjcs 	if (cold)
335593d792cSjcs 		delay(ms * 1000);
336593d792cSjcs 	else
337593d792cSjcs 		tsleep_nsec(&sc, PWAIT, "ietp", MSEC_TO_NSEC(ms));
338593d792cSjcs }
339593d792cSjcs 
340593d792cSjcs int
341593d792cSjcs ietp_iic_set_absolute_mode(struct ietp_softc *sc, bool enable)
342593d792cSjcs {
343593d792cSjcs 	static const struct {
344593d792cSjcs 		uint16_t	ic_type;
345593d792cSjcs 		uint16_t	product_id;
346593d792cSjcs 	} special_fw[] = {
347593d792cSjcs 	    { 0x0E, 0x05 }, { 0x0E, 0x06 }, { 0x0E, 0x07 }, { 0x0E, 0x09 },
348593d792cSjcs 	    { 0x0E, 0x13 }, { 0x08, 0x26 },
349593d792cSjcs 	};
350593d792cSjcs 	uint16_t val;
351593d792cSjcs 	int i, error;
352593d792cSjcs 	bool require_wakeup;
353593d792cSjcs 
354593d792cSjcs 	error = 0;
355593d792cSjcs 
356593d792cSjcs 	/*
357593d792cSjcs 	 * Some ASUS touchpads need to be powered on to enter absolute mode.
358593d792cSjcs 	 */
359593d792cSjcs 	require_wakeup = false;
360593d792cSjcs 	for (i = 0; i < nitems(special_fw); i++) {
361593d792cSjcs 		if (sc->ic_type == special_fw[i].ic_type &&
362593d792cSjcs 		    sc->product_id == special_fw[i].product_id) {
363593d792cSjcs 			require_wakeup = true;
364593d792cSjcs 			break;
365593d792cSjcs 		}
366593d792cSjcs 	}
367593d792cSjcs 
368593d792cSjcs 	if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_ON) != 0) {
3698b995cd9Sjcs 		printf("%s: failed writing poweron command\n",
3708b995cd9Sjcs 		    sc->sc_dev.dv_xname);
371593d792cSjcs 		return (EIO);
372593d792cSjcs 	}
373593d792cSjcs 
374593d792cSjcs 	val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD;
375593d792cSjcs 	if (ietp_iic_write_reg(sc, IETP_CONTROL, val) != 0) {
3768b995cd9Sjcs 		printf("%s: failed setting absolute mode\n",
3778b995cd9Sjcs 		    sc->sc_dev.dv_xname);
378593d792cSjcs 		error = EIO;
379593d792cSjcs 	}
380593d792cSjcs 
381593d792cSjcs 	if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_OFF) != 0) {
3828b995cd9Sjcs 		printf("%s: failed writing poweroff command\n",
3838b995cd9Sjcs 		    sc->sc_dev.dv_xname);
384593d792cSjcs 		error = EIO;
385593d792cSjcs 	}
386593d792cSjcs 
387593d792cSjcs 	return (error);
388593d792cSjcs }
389593d792cSjcs 
390593d792cSjcs int
391593d792cSjcs ietp_iic_read_reg(struct ietp_softc *sc, uint16_t reg, size_t len, void *val)
392593d792cSjcs {
393593d792cSjcs 	uint8_t cmd[] = {
394593d792cSjcs 		reg & 0xff,
395593d792cSjcs 		reg >> 8,
396593d792cSjcs 	};
397593d792cSjcs 
398593d792cSjcs 	return iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
399593d792cSjcs 		 &cmd, 2, val, len, 0);
400593d792cSjcs }
401593d792cSjcs 
402593d792cSjcs int
403593d792cSjcs ietp_iic_write_reg(struct ietp_softc *sc, uint16_t reg, uint16_t val)
404593d792cSjcs {
405593d792cSjcs 	uint8_t cmd[] = {
406593d792cSjcs 		reg & 0xff,
407593d792cSjcs 		reg >> 8,
408593d792cSjcs 		val & 0xff,
409593d792cSjcs 		val >> 8,
410593d792cSjcs 	};
411593d792cSjcs 
412593d792cSjcs 	return iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
413593d792cSjcs 		 &cmd, 4, NULL, 0, 0);
414593d792cSjcs }
415593d792cSjcs 
416593d792cSjcs int
417593d792cSjcs ietp_set_power(struct ietp_softc *sc, int power)
418593d792cSjcs {
419593d792cSjcs 	int res = 1;
420593d792cSjcs 	uint8_t cmd[] = {
421593d792cSjcs 		htole16(sc->hid_desc.wCommandRegister) & 0xff,
422593d792cSjcs 		htole16(sc->hid_desc.wCommandRegister) >> 8,
423593d792cSjcs 		power,
424593d792cSjcs 		I2C_HID_CMD_SET_POWER,
425593d792cSjcs 	};
426593d792cSjcs 
427593d792cSjcs 	iic_acquire_bus(sc->sc_tag, 0);
428593d792cSjcs 
429593d792cSjcs 	DPRINTF(("%s: HID command I2C_HID_CMD_SET_POWER(%d)\n",
430593d792cSjcs 		 sc->sc_dev.dv_xname, power));
431593d792cSjcs 
432593d792cSjcs 	/* 22 00 00 08 */
433593d792cSjcs 	res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
434593d792cSjcs 		       &cmd, sizeof(cmd), NULL, 0, 0);
435593d792cSjcs 
436593d792cSjcs 	iic_release_bus(sc->sc_tag, 0);
437593d792cSjcs 
438593d792cSjcs 	return (res);
439593d792cSjcs }
440593d792cSjcs 
441593d792cSjcs int
442593d792cSjcs ietp_reset_cmd(struct ietp_softc *sc)
443593d792cSjcs {
444593d792cSjcs 	int res = 1;
445593d792cSjcs 	uint8_t cmd[] = {
446593d792cSjcs 		htole16(sc->hid_desc.wCommandRegister) & 0xff,
447593d792cSjcs 		htole16(sc->hid_desc.wCommandRegister) >> 8,
448593d792cSjcs 		0,
449593d792cSjcs 		I2C_HID_CMD_RESET,
450593d792cSjcs 	};
451593d792cSjcs 
452593d792cSjcs 	iic_acquire_bus(sc->sc_tag, 0);
453593d792cSjcs 
454593d792cSjcs 	DPRINTF(("%s: HID command I2C_HID_CMD_RESET\n",
455593d792cSjcs 		 sc->sc_dev.dv_xname));
456593d792cSjcs 
457593d792cSjcs 	/* 22 00 00 01 */
458593d792cSjcs 	res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
459593d792cSjcs 		       &cmd, sizeof(cmd), NULL, 0, 0);
460593d792cSjcs 
461593d792cSjcs 	iic_release_bus(sc->sc_tag, 0);
462593d792cSjcs 
463593d792cSjcs 	return (res);
464593d792cSjcs }
465593d792cSjcs 
466593d792cSjcs int
467593d792cSjcs ietp_fetch_descriptor(struct ietp_softc *sc)
468593d792cSjcs {
469593d792cSjcs 	int i, res = 1;
470593d792cSjcs 	/*
471593d792cSjcs 	 * 5.2.2 - HID Descriptor Retrieval
472593d792cSjcs 	 * register is passed from the controller
473593d792cSjcs 	 */
474593d792cSjcs 	uint8_t cmd[] = {
475593d792cSjcs 		1,
476593d792cSjcs 		0,
477593d792cSjcs 	};
478593d792cSjcs 
479593d792cSjcs 	iic_acquire_bus(sc->sc_tag, 0);
480593d792cSjcs 
481593d792cSjcs 	DPRINTF(("%s: HID command I2C_HID_CMD_DESCR at 0x1\n",
482593d792cSjcs 		 sc->sc_dev.dv_xname));
483593d792cSjcs 
484593d792cSjcs 	/* 20 00 */
485593d792cSjcs 	res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
486593d792cSjcs 		       &cmd, sizeof(cmd), &sc->hid_desc_buf,
487593d792cSjcs 		       sizeof(struct i2c_hid_desc), 0);
488593d792cSjcs 
489593d792cSjcs 	DPRINTF(("%s: HID descriptor:", sc->sc_dev.dv_xname));
490593d792cSjcs 	for (i = 0; i < sizeof(struct i2c_hid_desc); i++)
491593d792cSjcs 		DPRINTF((" %.2x", sc->hid_desc_buf[i]));
492593d792cSjcs 	DPRINTF(("\n"));
493593d792cSjcs 
494593d792cSjcs 	iic_release_bus(sc->sc_tag, 0);
495593d792cSjcs 
496593d792cSjcs 	return (res);
497593d792cSjcs }
498593d792cSjcs 
499593d792cSjcs int
500593d792cSjcs ietp_reset(struct ietp_softc *sc)
501593d792cSjcs {
502593d792cSjcs 	DPRINTF(("%s: resetting\n", sc->sc_dev.dv_xname));
503593d792cSjcs 
504593d792cSjcs 	if (ietp_set_power(sc, I2C_HID_POWER_ON)) {
505593d792cSjcs 		printf("%s: failed to power on\n", sc->sc_dev.dv_xname);
506593d792cSjcs 		return (1);
507593d792cSjcs 	}
508593d792cSjcs 
509593d792cSjcs 	ietp_sleep(sc, 100);
510593d792cSjcs 
511593d792cSjcs 	if (ietp_reset_cmd(sc)) {
512593d792cSjcs 		printf("%s: failed to reset hardware\n", sc->sc_dev.dv_xname);
513593d792cSjcs 
514593d792cSjcs 		ietp_set_power(sc, I2C_HID_POWER_OFF);
515593d792cSjcs 
516593d792cSjcs 		return (1);
517593d792cSjcs 	}
518593d792cSjcs 
519593d792cSjcs 	ietp_sleep(sc, 100);
520593d792cSjcs 
521593d792cSjcs 	return (0);
522593d792cSjcs }
523593d792cSjcs 
524593d792cSjcs void
525593d792cSjcs parse_input(struct ietp_softc *sc, u_char *report, int len)
526593d792cSjcs {
527593d792cSjcs 	uint8_t *fdata;
528593d792cSjcs 	int32_t finger;
529593d792cSjcs 	int32_t x, y, p;
530593d792cSjcs 	int buttons = 0;
531593d792cSjcs 	int s;
532593d792cSjcs 
533593d792cSjcs 	/* we seem to get 0 length reports sometimes, ignore them */
534593d792cSjcs 	if (len == 0)
535593d792cSjcs 		return;
536593d792cSjcs 	if (len != sc->report_len) {
5378b995cd9Sjcs 		printf("%s: wrong report length (%d vs %d expected)",
5388b995cd9Sjcs 		    sc->sc_dev.dv_xname, len, (int) sc->report_len);
539593d792cSjcs 		return;
540593d792cSjcs 	}
541593d792cSjcs 
542593d792cSjcs 	s = spltty();
543593d792cSjcs 
544593d792cSjcs 	buttons = report[IETP_TOUCH_INFO] & 7;
545593d792cSjcs 
546593d792cSjcs 	if (sc->sc_buttons != buttons) {
547593d792cSjcs 		wsmouse_buttons(sc->sc_wsmousedev, buttons);
548593d792cSjcs 		sc->sc_buttons = buttons;
549593d792cSjcs 	}
550593d792cSjcs 
551593d792cSjcs 	for (finger = 0, fdata = report + IETP_FINGER_DATA;
552593d792cSjcs 	     finger < IETP_MAX_FINGERS;
553593d792cSjcs 	     finger++, fdata += IETP_FINGER_DATA_LEN) {
554593d792cSjcs 		if ((report[IETP_TOUCH_INFO] & (1 << (finger + 3))) != 0) {
555593d792cSjcs 			if (sc->hi_precision) {
556593d792cSjcs 				x = fdata[0] << 8 | fdata[1];
557593d792cSjcs 				y = fdata[2] << 8 | fdata[3];
558593d792cSjcs 			} else {
559593d792cSjcs 				x = (fdata[0] & 0xf0) << 4 | fdata[1];
560593d792cSjcs 				y = (fdata[0] & 0x0f) << 8 | fdata[2];
561593d792cSjcs 			}
562593d792cSjcs 
563593d792cSjcs 			if (x > sc->max_x || y > sc->max_y) {
564593d792cSjcs 				printf("%s: [%d] x=%d y=%d over max (%d, %d)\n",
5658b995cd9Sjcs 				    sc->sc_dev.dv_xname, finger, x, y,
5668b995cd9Sjcs 				    sc->max_x, sc->max_y);
567593d792cSjcs 				continue;
568593d792cSjcs 			}
569593d792cSjcs 
570593d792cSjcs 
571593d792cSjcs 			p = MIN((int32_t)fdata[4] + sc->pressure_base,
572593d792cSjcs 				    IETP_MAX_PRESSURE);
573593d792cSjcs 
574593d792cSjcs 		} else {
575593d792cSjcs 			x = 0;
576593d792cSjcs 			y = 0;
577593d792cSjcs 			p = 0;
578593d792cSjcs 		}
579593d792cSjcs 
5808b995cd9Sjcs 		DPRINTF(("position: [finger=%d, x=%d, y=%d, p=%d]\n", finger,
5818b995cd9Sjcs 		    x, y, p));
582593d792cSjcs 		wsmouse_mtstate(sc->sc_wsmousedev, finger, x, y, p);
583593d792cSjcs 	}
584593d792cSjcs 
585593d792cSjcs 	wsmouse_input_sync(sc->sc_wsmousedev);
586593d792cSjcs 
587593d792cSjcs 	splx(s);
588593d792cSjcs }
589593d792cSjcs 
590593d792cSjcs int
591593d792cSjcs ietp_intr(void *arg)
592593d792cSjcs {
593593d792cSjcs 	struct ietp_softc *sc = arg;
594593d792cSjcs 	int psize, i;
595593d792cSjcs 	u_char *p;
596593d792cSjcs 	u_int rep = 0;
597593d792cSjcs 
598593d792cSjcs 	if (sc->sc_dying)
599593d792cSjcs 		return 1;
600593d792cSjcs 
601593d792cSjcs 	/*
602593d792cSjcs 	 * XXX: force I2C_F_POLL for now to avoid dwiic interrupting
603593d792cSjcs 	 * while we are interrupting
604593d792cSjcs 	 */
605593d792cSjcs 
606593d792cSjcs 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
607593d792cSjcs 	iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, NULL, 0,
608593d792cSjcs 	    sc->sc_ibuf, letoh16(sc->hid_desc.wMaxInputLength), I2C_F_POLL);
609593d792cSjcs 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
610593d792cSjcs 
611593d792cSjcs 	/*
612593d792cSjcs 	 * 6.1.1 - First two bytes are the packet length, which must be less
613593d792cSjcs 	 * than or equal to wMaxInputLength
614593d792cSjcs 	 */
615593d792cSjcs 	psize = sc->sc_ibuf[0] | sc->sc_ibuf[1] << 8;
616593d792cSjcs 	if (psize <= 2 || psize > sc->sc_isize) {
617593d792cSjcs 		DPRINTF(("%s: %s: invalid packet size (%d vs. %d)\n",
618593d792cSjcs 			    sc->sc_dev.dv_xname, __func__, psize,
619593d792cSjcs 			    sc->sc_isize));
620593d792cSjcs 		return (1);
621593d792cSjcs 	}
622593d792cSjcs 
623593d792cSjcs 	/* 3rd byte is the report id */
624593d792cSjcs 	p = sc->sc_ibuf + 2;
625593d792cSjcs 	psize -= 2;
626593d792cSjcs 	rep = *p++;
627593d792cSjcs 	psize--;
628593d792cSjcs 
629593d792cSjcs 	DPRINTF(("%s: %s: hid input (rep 0x%x):", sc->sc_dev.dv_xname, __func__,
630593d792cSjcs 	    rep));
631593d792cSjcs 	for (i = 0; i < psize; i++) {
632593d792cSjcs 		DPRINTF((" %.2x", p[i]));
633593d792cSjcs 	}
634593d792cSjcs 	DPRINTF(("\n"));
635593d792cSjcs 
636593d792cSjcs 	if (sc->sc_refcnt && rep == sc->report_id) {
637593d792cSjcs 		parse_input(sc, p, psize);
638593d792cSjcs 	}
639593d792cSjcs 
640593d792cSjcs 	return (1);
641593d792cSjcs }
642593d792cSjcs 
643593d792cSjcs int
644593d792cSjcs ietp_enable(void *dev)
645593d792cSjcs {
646593d792cSjcs 	struct ietp_softc *sc = dev;
647593d792cSjcs 
648593d792cSjcs 	DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname,
649593d792cSjcs 	    __func__, sc->sc_refcnt));
650593d792cSjcs 
651593d792cSjcs 	if (sc->sc_refcnt++ || sc->sc_isize == 0)
652593d792cSjcs 		return (0);
653593d792cSjcs 
654593d792cSjcs 	/* power on */
655593d792cSjcs 	ietp_reset(sc);
656593d792cSjcs 
657593d792cSjcs 	return (0);
658593d792cSjcs }
659593d792cSjcs 
660593d792cSjcs void
661593d792cSjcs ietp_disable(void *dev)
662593d792cSjcs {
663593d792cSjcs 	struct ietp_softc *sc = dev;
664593d792cSjcs 	DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname,
665593d792cSjcs 	    __func__, sc->sc_refcnt));
666593d792cSjcs 
667593d792cSjcs 	if (--sc->sc_refcnt)
668593d792cSjcs 		return;
669593d792cSjcs 
670593d792cSjcs 	/* no sub-devices open, conserve power */
671593d792cSjcs 
672593d792cSjcs 	if (ietp_set_power(sc, I2C_HID_POWER_OFF))
673593d792cSjcs 		printf("%s: failed to power down\n", sc->sc_dev.dv_xname);
674593d792cSjcs }
675593d792cSjcs 
676593d792cSjcs int
677593d792cSjcs ietp_ioctl(void *dev, u_long cmd, caddr_t data, int flag,
678593d792cSjcs     struct proc *p)
679593d792cSjcs {
680593d792cSjcs 	struct ietp_softc *sc = dev;
681593d792cSjcs  	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
682593d792cSjcs 
683593d792cSjcs 	switch (cmd) {
684593d792cSjcs 	case WSMOUSEIO_GTYPE:
685593d792cSjcs 		*(u_int *)data = WSMOUSE_TYPE_TOUCHPAD;
686593d792cSjcs 		return 0;
687593d792cSjcs 
688593d792cSjcs 	case WSMOUSEIO_GCALIBCOORDS:
689593d792cSjcs 		wsmc->minx = 0;
690593d792cSjcs 		wsmc->maxx = sc->max_x;
691593d792cSjcs 		wsmc->miny = 0;
692593d792cSjcs 		wsmc->maxy = sc->max_y;
693593d792cSjcs 		wsmc->swapxy = 0;
694593d792cSjcs 		wsmc->resx = sc->res_x;
695593d792cSjcs 		wsmc->resy = sc->res_y;
696593d792cSjcs 		return 0;
697593d792cSjcs 	}
698593d792cSjcs 	return -1;
699593d792cSjcs }
700