xref: /netbsd-src/sys/dev/pckbport/elantech.c (revision 4f6fb3bf35736e218e24c924928624b00f4fe151)
1 /* $NetBSD: elantech.c,v 1.6 2014/02/25 18:30:10 pooka Exp $ */
2 
3 /*-
4  * Copyright (c) 2008 Jared D. McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "opt_pms.h"
30 
31 #include <sys/cdefs.h>
32 __KERNEL_RCSID(0, "$NetBSD: elantech.c,v 1.6 2014/02/25 18:30:10 pooka Exp $");
33 
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/device.h>
37 #include <sys/kernel.h>
38 #include <sys/sysctl.h>
39 #include <sys/bus.h>
40 
41 #include <dev/wscons/wsconsio.h>
42 #include <dev/wscons/wsmousevar.h>
43 
44 #include <dev/pckbport/pckbportvar.h>
45 #include <dev/pckbport/elantechreg.h>
46 #include <dev/pckbport/elantechvar.h>
47 #include <dev/pckbport/pmsreg.h>
48 #include <dev/pckbport/pmsvar.h>
49 
50 /* #define ELANTECH_DEBUG */
51 
52 static int elantech_xy_unprecision_nodenum;
53 static int elantech_z_unprecision_nodenum;
54 
55 static int elantech_xy_unprecision = 2;
56 static int elantech_z_unprecision = 3;
57 
58 struct elantech_packet {
59 	int16_t		ep_x, ep_y, ep_z;
60 	int8_t		ep_buttons;
61 	uint8_t		ep_nfingers;
62 };
63 
64 static int
pms_sysctl_elantech_verify(SYSCTLFN_ARGS)65 pms_sysctl_elantech_verify(SYSCTLFN_ARGS)
66 {
67 	int error, t;
68 	struct sysctlnode node;
69 
70 	node = *rnode;
71 	t = *(int *)rnode->sysctl_data;
72 	node.sysctl_data = &t;
73 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
74 	if (error || newp == NULL)
75 		return error;
76 
77 	if (node.sysctl_num == elantech_xy_unprecision_nodenum ||
78 	    node.sysctl_num == elantech_z_unprecision_nodenum) {
79 		if (t < 0 || t > 7)
80 			return EINVAL;
81 	} else
82 		return EINVAL;
83 
84 	*(int *)rnode->sysctl_data = t;
85 
86 	return 0;
87 }
88 
89 static void
pms_sysctl_elantech(struct sysctllog ** clog)90 pms_sysctl_elantech(struct sysctllog **clog)
91 {
92 	const struct sysctlnode *node;
93 	int rc, root_num;
94 
95 	if ((rc = sysctl_createv(clog, 0, NULL, &node,
96 	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "elantech",
97 	    SYSCTL_DESCR("Elantech touchpad controls"),
98 	    NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0)
99 		goto err;
100 
101 	root_num = node->sysctl_num;
102 
103 	if ((rc = sysctl_createv(clog, 0, NULL, &node,
104 	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
105 	    CTLTYPE_INT, "xy_precision_shift",
106 	    SYSCTL_DESCR("X/Y-axis precision shift value"),
107 	    pms_sysctl_elantech_verify, 0,
108 	    &elantech_xy_unprecision,
109 	    0, CTL_HW, root_num, CTL_CREATE,
110 	    CTL_EOL)) != 0)
111 		goto err;
112 
113 	elantech_xy_unprecision_nodenum = node->sysctl_num;
114 
115 	if ((rc = sysctl_createv(clog, 0, NULL, &node,
116 	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
117 	    CTLTYPE_INT, "z_precision_shift",
118 	    SYSCTL_DESCR("Z-axis precision shift value"),
119 	    pms_sysctl_elantech_verify, 0,
120 	    &elantech_z_unprecision,
121 	    0, CTL_HW, root_num, CTL_CREATE,
122 	    CTL_EOL)) != 0)
123 		goto err;
124 
125 	elantech_z_unprecision_nodenum = node->sysctl_num;
126 	return;
127 
128 err:
129 	aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
130 }
131 
132 static int
pms_elantech_read_1(pckbport_tag_t tag,pckbport_slot_t slot,uint8_t reg,uint8_t * val)133 pms_elantech_read_1(pckbport_tag_t tag, pckbport_slot_t slot, uint8_t reg,
134     uint8_t *val)
135 {
136 	int res;
137 	uint8_t cmd;
138 	uint8_t resp[3];
139 
140 	cmd = ELANTECH_CUSTOM_CMD;
141 	res = pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
142 	cmd = ELANTECH_REG_READ;
143 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
144 	cmd = ELANTECH_CUSTOM_CMD;
145 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
146 	cmd = reg;
147 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
148 	cmd = PMS_SEND_DEV_STATUS;
149 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 3, resp, 0);
150 
151 	if (res == 0)
152 		*val = resp[0];
153 
154 	return res;
155 }
156 
157 static int
pms_elantech_write_1(pckbport_tag_t tag,pckbport_slot_t slot,uint8_t reg,uint8_t val)158 pms_elantech_write_1(pckbport_tag_t tag, pckbport_slot_t slot, uint8_t reg,
159     uint8_t val)
160 {
161 	int res;
162 	uint8_t cmd;
163 
164 	cmd = ELANTECH_CUSTOM_CMD;
165 	res = pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
166 	cmd = ELANTECH_REG_WRITE;
167 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
168 	cmd = ELANTECH_CUSTOM_CMD;
169 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
170 	cmd = reg;
171 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
172 	cmd = ELANTECH_CUSTOM_CMD;
173 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
174 	cmd = val;
175 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
176 	cmd = PMS_SET_SCALE11;
177 	res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
178 
179 	return res;
180 }
181 
182 static int
pms_elantech_init(struct pms_softc * psc)183 pms_elantech_init(struct pms_softc *psc)
184 {
185 	uint8_t val;
186 	int res;
187 
188 	/* set absolute mode */
189 	res = pms_elantech_write_1(psc->sc_kbctag, psc->sc_kbcslot, 0x10, 0x54);
190 	if (res)
191 		return res;
192 	res = pms_elantech_write_1(psc->sc_kbctag, psc->sc_kbcslot, 0x11, 0x88);
193 	if (res)
194 		return res;
195 	res = pms_elantech_write_1(psc->sc_kbctag, psc->sc_kbcslot, 0x21, 0x60);
196 	if (res)
197 		return res;
198 
199 	res = pms_elantech_read_1(psc->sc_kbctag, psc->sc_kbcslot, 0x10, &val);
200 
201 	if (res)
202 		aprint_error_dev(psc->sc_dev, "couldn't set absolute mode\n");
203 
204 	return res;
205 }
206 
207 static void
pms_elantech_input(void * opaque,int data)208 pms_elantech_input(void *opaque, int data)
209 {
210 	struct pms_softc *psc = opaque;
211 	struct elantech_softc *sc = &psc->u.elantech;
212 	struct elantech_packet ep;
213 	int s;
214 
215 	if (!psc->sc_enabled)
216 		return;
217 
218 	if (sc->version >= 0x020800) {
219 		if ((psc->inputstate == 0 && (data & 0x0c) != 0x04) ||
220 		    (psc->inputstate == 3 && (data & 0x0f) != 0x02)) {
221 			aprint_debug_dev(psc->sc_dev, "waiting for sync..\n");
222 			psc->inputstate = 0;
223 			return;
224 		}
225 	} else {
226 		if ((psc->inputstate == 0 && (data & 0x0c) != 0x0c) ||
227 		    (psc->inputstate == 3 && (data & 0x0e) != 0x08)) {
228 			aprint_debug_dev(psc->sc_dev, "waiting for sync..\n");
229 			psc->inputstate = 0;
230 			return;
231 		}
232 	}
233 
234 	psc->packet[psc->inputstate++] = data & 0xff;
235 	if (psc->inputstate != 6)
236 		return;
237 
238 	psc->inputstate = 0;
239 
240 	ep.ep_nfingers = (psc->packet[0] & 0xc0) >> 6;
241 	ep.ep_buttons = 0;
242 	ep.ep_buttons = psc->packet[0] & 1;		/* left button */
243 	ep.ep_buttons |= (psc->packet[0] & 2) << 1;	/* right button */
244 
245 	if (ep.ep_nfingers == 0 || ep.ep_nfingers != sc->last_nfingers)
246 		sc->initializing = true;
247 
248 	switch (ep.ep_nfingers) {
249 	case 0:
250 		/* FALLTHROUGH */
251 	case 1:
252 		ep.ep_x = ((int16_t)(psc->packet[1] & 0xf) << 8) | psc->packet[2];
253 		ep.ep_y = ((int16_t)(psc->packet[4] & 0xf) << 8) | psc->packet[5];
254 
255 		aprint_debug_dev(psc->sc_dev,
256 		    "%d finger detected in elantech mode:\n", ep.ep_nfingers);
257 		aprint_debug_dev(psc->sc_dev,
258 		    "  X=%d Y=%d\n", ep.ep_x, ep.ep_y);
259 		aprint_debug_dev(psc->sc_dev,
260 		    "  %02x %02x %02x %02x %02x %02x\n",
261 		    psc->packet[0], psc->packet[1], psc->packet[2],
262 		    psc->packet[3], psc->packet[4], psc->packet[5]);
263 
264 		s = spltty();
265 		wsmouse_input(psc->sc_wsmousedev, ep.ep_buttons,
266 		    sc->initializing ?
267 		      0 : (ep.ep_x - sc->last_x) >> elantech_xy_unprecision,
268 		    sc->initializing ?
269 		      0 : (ep.ep_y - sc->last_y) >> elantech_xy_unprecision,
270 		    0, 0,
271 		    WSMOUSE_INPUT_DELTA);
272 		splx(s);
273 
274 		if (sc->initializing == true ||
275 		    ((ep.ep_x - sc->last_x) >> elantech_xy_unprecision) != 0)
276 			sc->last_x = ep.ep_x;
277 		if (sc->initializing == true ||
278 		    ((ep.ep_y - sc->last_y) >> elantech_xy_unprecision) != 0)
279 			sc->last_y = ep.ep_y;
280 		break;
281 	case 2:
282 		/* emulate z axis */
283 		ep.ep_z = psc->packet[2];
284 		aprint_debug_dev(psc->sc_dev,
285 		    "2 fingers detected in elantech mode:\n");
286 		aprint_debug_dev(psc->sc_dev,
287 		    "  %02x %02x %02x %02x %02x %02x\n",
288 		    psc->packet[0], psc->packet[1], psc->packet[2],
289 		    psc->packet[3], psc->packet[4], psc->packet[5]);
290 
291 		s = spltty();
292 		wsmouse_input(psc->sc_wsmousedev, 0,
293 		    0, 0,
294 		    sc->initializing ?
295 		      0 : (sc->last_z - ep.ep_z) >> elantech_z_unprecision,
296 		    0,
297 		    WSMOUSE_INPUT_DELTA);
298 		splx(s);
299 
300 		if (sc->initializing == true ||
301 		    ((sc->last_z - ep.ep_z) >> elantech_z_unprecision) != 0)
302 			sc->last_z = ep.ep_z;
303 		break;
304 	default:
305 		aprint_debug_dev(psc->sc_dev, "that's a lot of fingers!\n");
306 		return;
307 	}
308 
309 	if (ep.ep_nfingers > 0)
310 		sc->initializing = false;
311 	sc->last_nfingers = ep.ep_nfingers;
312 }
313 
314 int
pms_elantech_probe_init(void * opaque)315 pms_elantech_probe_init(void *opaque)
316 {
317 	struct pms_softc *psc = opaque;
318 	struct elantech_softc *sc = &psc->u.elantech;
319 	struct sysctllog *clog = NULL;
320 	u_char cmd[1], resp[3];
321 	uint16_t fwversion;
322 	int res;
323 
324 	pckbport_flush(psc->sc_kbctag, psc->sc_kbcslot);
325 
326 	cmd[0] = PMS_SET_SCALE11;
327 	if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
328 	    cmd, 1, 0, NULL, 0)) != 0)
329 		goto doreset;
330 	cmd[0] = PMS_SET_SCALE11;
331 	if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
332 	    cmd, 1, 0, NULL, 0)) != 0)
333 		goto doreset;
334 	cmd[0] = PMS_SET_SCALE11;
335 	if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
336 	    cmd, 1, 0, NULL, 0)) != 0)
337 		goto doreset;
338 
339 	cmd[0] = PMS_SEND_DEV_STATUS;
340 	if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
341 	    cmd, 1, 3, resp, 0)) != 0)
342 		goto doreset;
343 
344 	if (!ELANTECH_MAGIC(resp)) {
345 #ifdef ELANTECH_DEBUG
346 		aprint_error_dev(psc->sc_dev,
347 		    "bad elantech magic (%X %X %X)\n",
348 		    resp[0], resp[1], resp[2]);
349 #endif
350 		res = 1;
351 		goto doreset;
352 	}
353 
354 	res = pms_sliced_command(psc->sc_kbctag, psc->sc_kbcslot,
355 	    ELANTECH_FW_VERSION);
356 	cmd[0] = PMS_SEND_DEV_STATUS;
357 	res |= pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
358 	    cmd, 1, 3, resp, 0);
359 	if (res) {
360 		aprint_error_dev(psc->sc_dev,
361 		    "unable to query elantech firmware version\n");
362 		goto doreset;
363 	}
364 
365 	fwversion = (resp[0] << 8) | resp[2];
366 	if (fwversion < ELANTECH_MIN_VERSION) {
367 		aprint_error_dev(psc->sc_dev,
368 		    "unsupported Elantech version %d.%d (%X %X %X)\n",
369 		    resp[0], resp[2], resp[0], resp[1], resp[2]);
370 		goto doreset;
371 	}
372 	sc->version = (resp[0] << 16) | (resp[1] << 8) | resp[2];
373 	aprint_normal_dev(psc->sc_dev, "Elantech touchpad version %d.%d (%06x)\n",
374 	    resp[0], resp[2], sc->version);
375 
376 	res = pms_elantech_init(psc);
377 	if (res) {
378 		aprint_error_dev(psc->sc_dev,
379 		    "couldn't initialize elantech touchpad\n");
380 		goto doreset;
381 	}
382 
383 	pms_sysctl_elantech(&clog);
384 	pckbport_set_inputhandler(psc->sc_kbctag, psc->sc_kbcslot,
385 	    pms_elantech_input, psc, device_xname(psc->sc_dev));
386 
387 	return 0;
388 
389 doreset:
390 	cmd[0] = PMS_RESET;
391 	(void)pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot, cmd,
392 	    1, 2, resp, 1);
393 	return res;
394 }
395 
396 void
pms_elantech_enable(void * opaque)397 pms_elantech_enable(void *opaque)
398 {
399 	struct pms_softc *psc = opaque;
400 	struct elantech_softc *sc = &psc->u.elantech;
401 
402 	sc->initializing = true;
403 }
404 
405 void
pms_elantech_resume(void * opaque)406 pms_elantech_resume(void *opaque)
407 {
408 	struct pms_softc *psc = opaque;
409 	uint8_t cmd, resp[2];
410 	int res;
411 
412 	cmd = PMS_RESET;
413 	res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot, &cmd,
414 	    1, 2, resp, 1);
415 	if (res)
416 		aprint_error_dev(psc->sc_dev,
417 		    "elantech reset on resume failed\n");
418 	else {
419 		pms_elantech_init(psc);
420 		pms_elantech_enable(psc);
421 	}
422 }
423