elantech.c revision 1.3.10.3 1 /* $NetBSD: elantech.c,v 1.3.10.3 2009/09/13 22:08:30 snj Exp $ */
2
3 /*-
4 * Copyright (c) 2008 Jared D. McNeill <jmcneill (at) 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.3.10.3 2009/09/13 22:08:30 snj 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 static int elantech_xy_unprecision_nodenum;
51 static int elantech_z_unprecision_nodenum;
52
53 static int elantech_xy_unprecision = 2;
54 static int elantech_z_unprecision = 3;
55
56 struct elantech_packet {
57 int16_t ep_x, ep_y, ep_z;
58 int8_t ep_buttons;
59 uint8_t ep_nfingers;
60 };
61
62 static int
63 pms_sysctl_elantech_verify(SYSCTLFN_ARGS)
64 {
65 int error, t;
66 struct sysctlnode node;
67
68 node = *rnode;
69 t = *(int *)rnode->sysctl_data;
70 node.sysctl_data = &t;
71 error = sysctl_lookup(SYSCTLFN_CALL(&node));
72 if (error || newp == NULL)
73 return error;
74
75 if (node.sysctl_num == elantech_xy_unprecision_nodenum ||
76 node.sysctl_num == elantech_z_unprecision_nodenum) {
77 if (t < 0 || t > 7)
78 return EINVAL;
79 } else
80 return EINVAL;
81
82 *(int *)rnode->sysctl_data = t;
83
84 return 0;
85 }
86
87 static void
88 pms_sysctl_elantech(struct sysctllog **clog)
89 {
90 const struct sysctlnode *node;
91 int rc, root_num;
92
93 if ((rc = sysctl_createv(clog, 0, NULL, NULL,
94 CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL,
95 NULL, 0, NULL, 0, CTL_HW, CTL_EOL)) != 0)
96 goto err;
97
98 if ((rc = sysctl_createv(clog, 0, NULL, &node,
99 CTLFLAG_PERMANENT, CTLTYPE_NODE, "elantech",
100 SYSCTL_DESCR("Elantech touchpad controls"),
101 NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0)
102 goto err;
103
104 root_num = node->sysctl_num;
105
106 if ((rc = sysctl_createv(clog, 0, NULL, &node,
107 CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
108 CTLTYPE_INT, "xy_precision_shift",
109 SYSCTL_DESCR("X/Y-axis precision shift value"),
110 pms_sysctl_elantech_verify, 0,
111 &elantech_xy_unprecision,
112 0, CTL_HW, root_num, CTL_CREATE,
113 CTL_EOL)) != 0)
114 goto err;
115
116 elantech_xy_unprecision_nodenum = node->sysctl_num;
117
118 if ((rc = sysctl_createv(clog, 0, NULL, &node,
119 CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
120 CTLTYPE_INT, "z_precision_shift",
121 SYSCTL_DESCR("Z-axis precision shift value"),
122 pms_sysctl_elantech_verify, 0,
123 &elantech_z_unprecision,
124 0, CTL_HW, root_num, CTL_CREATE,
125 CTL_EOL)) != 0)
126 goto err;
127
128 elantech_z_unprecision_nodenum = node->sysctl_num;
129 return;
130
131 err:
132 aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
133 }
134
135 /* lifted from synaptics.c */
136 static int
137 pms_elantech_send_command(pckbport_tag_t tag, pckbport_slot_t slot,
138 uint8_t syn_cmd)
139 {
140 uint8_t cmd[2];
141 int res;
142
143 cmd[0] = PMS_SET_SCALE11;
144 res = pckbport_poll_cmd(tag, slot, cmd, 1, 0, NULL, 0);
145 if (res)
146 return res;
147
148 /*
149 * Need to send 4 Set Resolution commands, with the argument
150 * encoded in the bottom most 2 bits.
151 */
152 cmd[0] = PMS_SET_RES;
153 cmd[1] = syn_cmd >> 6;
154 res = pckbport_poll_cmd(tag, slot, cmd, 2, 0, NULL, 0);
155
156 cmd[0] = PMS_SET_RES;
157 cmd[1] = (syn_cmd & 0x30) >> 4;
158 res |= pckbport_poll_cmd(tag, slot, cmd, 2, 0, NULL, 0);
159
160 cmd[0] = PMS_SET_RES;
161 cmd[1] = (syn_cmd & 0x0c) >> 2;
162 res |= pckbport_poll_cmd(tag, slot, cmd, 2, 0, NULL, 0);
163
164 cmd[0] = PMS_SET_RES;
165 cmd[1] = (syn_cmd & 0x03);
166 res |= pckbport_poll_cmd(tag, slot, cmd, 2, 0, NULL, 0);
167
168 return res;
169 }
170
171 static int
172 pms_elantech_read_1(pckbport_tag_t tag, pckbport_slot_t slot, uint8_t reg,
173 uint8_t *val)
174 {
175 int res;
176 uint8_t cmd;
177 uint8_t resp[3];
178
179 cmd = ELANTECH_CUSTOM_CMD;
180 res = pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
181 cmd = ELANTECH_REG_READ;
182 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
183 cmd = ELANTECH_CUSTOM_CMD;
184 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
185 cmd = reg;
186 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
187 cmd = PMS_SEND_DEV_STATUS;
188 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 3, resp, 0);
189
190 if (res == 0)
191 *val = resp[0];
192
193 return res;
194 }
195
196 static int
197 pms_elantech_write_1(pckbport_tag_t tag, pckbport_slot_t slot, uint8_t reg,
198 uint8_t val)
199 {
200 int res;
201 uint8_t cmd;
202
203 cmd = ELANTECH_CUSTOM_CMD;
204 res = pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
205 cmd = ELANTECH_REG_WRITE;
206 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
207 cmd = ELANTECH_CUSTOM_CMD;
208 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
209 cmd = reg;
210 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
211 cmd = ELANTECH_CUSTOM_CMD;
212 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
213 cmd = val;
214 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
215 cmd = PMS_SET_SCALE11;
216 res |= pckbport_poll_cmd(tag, slot, &cmd, 1, 0, NULL, 0);
217
218 return res;
219 }
220
221 static int
222 pms_elantech_init(struct pms_softc *psc)
223 {
224 uint8_t val;
225 int res;
226
227 /* set absolute mode */
228 res = pms_elantech_write_1(psc->sc_kbctag, psc->sc_kbcslot, 0x10, 0x54);
229 if (res)
230 return res;
231 res = pms_elantech_write_1(psc->sc_kbctag, psc->sc_kbcslot, 0x11, 0x88);
232 if (res)
233 return res;
234 res = pms_elantech_write_1(psc->sc_kbctag, psc->sc_kbcslot, 0x21, 0x60);
235 if (res)
236 return res;
237
238 res = pms_elantech_read_1(psc->sc_kbctag, psc->sc_kbcslot, 0x10, &val);
239
240 if (res)
241 aprint_error_dev(psc->sc_dev, "couldn't set absolute mode\n");
242
243 return res;
244 }
245
246 static void
247 pms_elantech_input(void *opaque, int data)
248 {
249 struct pms_softc *psc = opaque;
250 struct elantech_softc *sc = &psc->u.elantech;
251 struct elantech_packet ep;
252 int s;
253
254 if (!psc->sc_enabled)
255 return;
256
257 if ((psc->inputstate == 0 && (data & 0x0c) != 0x0c) ||
258 (psc->inputstate == 3 && (data & 0x0f) != 0x08)) {
259 aprint_debug_dev(psc->sc_dev, "waiting for sync..\n");
260 psc->inputstate = 0;
261 return;
262 }
263
264 psc->packet[psc->inputstate++] = data & 0xff;
265 if (psc->inputstate != 6)
266 return;
267
268 psc->inputstate = 0;
269
270 ep.ep_nfingers = (psc->packet[0] & 0xc0) >> 6;
271 ep.ep_buttons = 0;
272 ep.ep_buttons = psc->packet[0] & 1; /* left button */
273 ep.ep_buttons |= (psc->packet[0] & 2) << 1; /* right button */
274
275 if (ep.ep_nfingers == 0 || ep.ep_nfingers != sc->last_nfingers)
276 sc->initializing = true;
277
278 switch (ep.ep_nfingers) {
279 case 0:
280 /* FALLTHROUGH */
281 case 1:
282 ep.ep_x = ((int16_t)psc->packet[1] << 8) | psc->packet[2];
283 ep.ep_y = ((int16_t)psc->packet[4] << 8) | psc->packet[5];
284
285 aprint_debug_dev(psc->sc_dev,
286 "%d finger detected in elantech mode:\n", ep.ep_nfingers);
287 aprint_debug_dev(psc->sc_dev,
288 " X=%d Y=%d\n", ep.ep_x, ep.ep_y);
289 aprint_debug_dev(psc->sc_dev,
290 " %02x %02x %02x %02x %02x %02x\n",
291 psc->packet[0], psc->packet[1], psc->packet[2],
292 psc->packet[3], psc->packet[4], psc->packet[5]);
293
294 s = spltty();
295 wsmouse_input(psc->sc_wsmousedev, ep.ep_buttons,
296 sc->initializing ?
297 0 : (ep.ep_x - sc->last_x) >> elantech_xy_unprecision,
298 sc->initializing ?
299 0 : (ep.ep_y - sc->last_y) >> elantech_xy_unprecision,
300 0, 0,
301 WSMOUSE_INPUT_DELTA);
302 splx(s);
303
304 if (sc->initializing == true ||
305 ((ep.ep_x - sc->last_x) >> elantech_xy_unprecision) != 0)
306 sc->last_x = ep.ep_x;
307 if (sc->initializing == true ||
308 ((ep.ep_y - sc->last_y) >> elantech_xy_unprecision) != 0)
309 sc->last_y = ep.ep_y;
310 break;
311 case 2:
312 /* emulate z axis */
313 ep.ep_z = psc->packet[2];
314 aprint_debug_dev(psc->sc_dev,
315 "2 fingers detected in elantech mode:\n");
316 aprint_debug_dev(psc->sc_dev,
317 " %02x %02x %02x %02x %02x %02x\n",
318 psc->packet[0], psc->packet[1], psc->packet[2],
319 psc->packet[3], psc->packet[4], psc->packet[5]);
320
321 s = spltty();
322 wsmouse_input(psc->sc_wsmousedev, 0,
323 0, 0,
324 sc->initializing ?
325 0 : (sc->last_z - ep.ep_z) >> elantech_z_unprecision,
326 0,
327 WSMOUSE_INPUT_DELTA);
328 splx(s);
329
330 if (sc->initializing == true ||
331 ((sc->last_z - ep.ep_z) >> elantech_z_unprecision) != 0)
332 sc->last_z = ep.ep_z;
333 break;
334 default:
335 aprint_debug_dev(psc->sc_dev, "that's a lot of fingers!\n");
336 return;
337 }
338
339 if (ep.ep_nfingers > 0)
340 sc->initializing = false;
341 sc->last_nfingers = ep.ep_nfingers;
342 }
343
344 int
345 pms_elantech_probe_init(void *opaque)
346 {
347 struct pms_softc *psc = opaque;
348 struct elantech_softc *sc = &psc->u.elantech;
349 struct sysctllog *clog = NULL;
350 u_char cmd[1], resp[3];
351 uint16_t fwversion;
352 int res;
353
354 pckbport_flush(psc->sc_kbctag, psc->sc_kbcslot);
355
356 cmd[0] = PMS_SET_SCALE11;
357 if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
358 cmd, 1, 0, NULL, 0)) != 0)
359 goto doreset;
360 cmd[0] = PMS_SET_SCALE11;
361 if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
362 cmd, 1, 0, NULL, 0)) != 0)
363 goto doreset;
364 cmd[0] = PMS_SET_SCALE11;
365 if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
366 cmd, 1, 0, NULL, 0)) != 0)
367 goto doreset;
368
369 cmd[0] = PMS_SEND_DEV_STATUS;
370 if ((res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
371 cmd, 1, 3, resp, 0)) != 0)
372 goto doreset;
373
374 if (!ELANTECH_MAGIC(resp)) {
375 aprint_error_dev(psc->sc_dev,
376 "bad elantech magic (%X %X %X)\n",
377 resp[0], resp[1], resp[2]);
378 res = 1;
379 goto doreset;
380 }
381
382 res = pms_elantech_send_command(psc->sc_kbctag, psc->sc_kbcslot,
383 ELANTECH_FW_VERSION);
384 cmd[0] = PMS_SEND_DEV_STATUS;
385 res |= pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot,
386 cmd, 1, 3, resp, 0);
387 if (res) {
388 aprint_error_dev(psc->sc_dev,
389 "unable to query elantech firmware version\n");
390 goto doreset;
391 }
392
393 fwversion = (resp[0] << 8) | resp[2];
394 if (fwversion < ELANTECH_MIN_VERSION) {
395 aprint_error_dev(psc->sc_dev,
396 "unsupported Elantech version %d.%d (%X %X %X)\n",
397 resp[0], resp[2], resp[0], resp[1], resp[2]);
398 goto doreset;
399 }
400 sc->version = fwversion;
401 aprint_normal_dev(psc->sc_dev, "Elantech touchpad version %d.%d\n",
402 resp[0], resp[2]);
403
404 res = pms_elantech_init(psc);
405 if (res) {
406 aprint_error_dev(psc->sc_dev,
407 "couldn't initialize elantech touchpad\n");
408 goto doreset;
409 }
410
411 pms_sysctl_elantech(&clog);
412 pckbport_set_inputhandler(psc->sc_kbctag, psc->sc_kbcslot,
413 pms_elantech_input, psc, device_xname(psc->sc_dev));
414
415 return 0;
416
417 doreset:
418 cmd[0] = PMS_RESET;
419 (void)pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot, cmd,
420 1, 2, resp, 1);
421 return res;
422 }
423
424 void
425 pms_elantech_enable(void *opaque)
426 {
427 struct pms_softc *psc = opaque;
428 struct elantech_softc *sc = &psc->u.elantech;
429
430 sc->initializing = true;
431 }
432
433 void
434 pms_elantech_resume(void *opaque)
435 {
436 struct pms_softc *psc = opaque;
437 uint8_t cmd, resp[2];
438 int res;
439
440 cmd = PMS_RESET;
441 res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot, &cmd,
442 1, 2, resp, 1);
443 if (res)
444 aprint_error_dev(psc->sc_dev,
445 "elantech reset on resume failed\n");
446 else {
447 pms_elantech_init(psc);
448 pms_elantech_enable(psc);
449 }
450 }
451