1 /* $NetBSD: elantech.c,v 1.2 2008/12/14 00:53:52 jmcneill Exp $ */
4 * Copyright (c) 2008 Jared D. McNeill <jmcneill@invisible.ca>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
31 #include <sys/cdefs.h>
32 __KERNEL_RCSID(0, "$NetBSD: elantech.c,v 1.2 2008/12/14 00:53:52 jmcneill Exp $");
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>
41 #include <dev/wscons/wsconsio.h>
42 #include <dev/wscons/wsmousevar.h>
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>
50 /* #define ELANTECH_DEBUG */
52 static int elantech_xy_unprecision_nodenum
;
53 static int elantech_z_unprecision_nodenum
;
55 static int elantech_xy_unprecision
= 2;
56 static int elantech_z_unprecision
= 3;
58 struct elantech_packet
{
59 int16_t ep_x
, ep_y
, ep_z
;
65 pms_sysctl_elantech_verify(SYSCTLFN_ARGS
)
68 struct sysctlnode node
;
71 t
= *(int *)rnode
->sysctl_data
;
72 node
.sysctl_data
= &t
;
73 error
= sysctl_lookup(SYSCTLFN_CALL(&node
));
74 if (error
|| newp
== NULL
)
77 if (node
.sysctl_num
== elantech_xy_unprecision_nodenum
||
78 node
.sysctl_num
== elantech_z_unprecision_nodenum
) {
84 *(int *)rnode
->sysctl_data
= t
;
90 pms_sysctl_elantech(struct sysctllog
**clog
)
92 const struct sysctlnode
*node
;
95 if ((rc
= sysctl_createv(clog
, 0, NULL
, NULL
,
96 CTLFLAG_PERMANENT
, CTLTYPE_NODE
, "hw", NULL
,
97 NULL
, 0, NULL
, 0, CTL_HW
, CTL_EOL
)) != 0)
100 if ((rc
= sysctl_createv(clog
, 0, NULL
, &node
,
101 CTLFLAG_PERMANENT
, CTLTYPE_NODE
, "elantech",
102 SYSCTL_DESCR("Elantech touchpad controls"),
103 NULL
, 0, NULL
, 0, CTL_HW
, CTL_CREATE
, CTL_EOL
)) != 0)
106 root_num
= node
->sysctl_num
;
108 if ((rc
= sysctl_createv(clog
, 0, NULL
, &node
,
109 CTLFLAG_PERMANENT
| CTLFLAG_READWRITE
,
110 CTLTYPE_INT
, "xy_precision_shift",
111 SYSCTL_DESCR("X/Y-axis precision shift value"),
112 pms_sysctl_elantech_verify
, 0,
113 &elantech_xy_unprecision
,
114 0, CTL_HW
, root_num
, CTL_CREATE
,
118 elantech_xy_unprecision_nodenum
= node
->sysctl_num
;
120 if ((rc
= sysctl_createv(clog
, 0, NULL
, &node
,
121 CTLFLAG_PERMANENT
| CTLFLAG_READWRITE
,
122 CTLTYPE_INT
, "z_precision_shift",
123 SYSCTL_DESCR("Z-axis precision shift value"),
124 pms_sysctl_elantech_verify
, 0,
125 &elantech_z_unprecision
,
126 0, CTL_HW
, root_num
, CTL_CREATE
,
130 elantech_z_unprecision_nodenum
= node
->sysctl_num
;
134 aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__
, rc
);
137 /* lifted from synaptics.c */
139 pms_elantech_send_command(pckbport_tag_t tag
, pckbport_slot_t slot
,
145 cmd
[0] = PMS_SET_SCALE11
;
146 res
= pckbport_poll_cmd(tag
, slot
, cmd
, 1, 0, NULL
, 0);
151 * Need to send 4 Set Resolution commands, with the argument
152 * encoded in the bottom most 2 bits.
154 cmd
[0] = PMS_SET_RES
;
155 cmd
[1] = syn_cmd
>> 6;
156 res
= pckbport_poll_cmd(tag
, slot
, cmd
, 2, 0, NULL
, 0);
158 cmd
[0] = PMS_SET_RES
;
159 cmd
[1] = (syn_cmd
& 0x30) >> 4;
160 res
|= pckbport_poll_cmd(tag
, slot
, cmd
, 2, 0, NULL
, 0);
162 cmd
[0] = PMS_SET_RES
;
163 cmd
[1] = (syn_cmd
& 0x0c) >> 2;
164 res
|= pckbport_poll_cmd(tag
, slot
, cmd
, 2, 0, NULL
, 0);
166 cmd
[0] = PMS_SET_RES
;
167 cmd
[1] = (syn_cmd
& 0x03);
168 res
|= pckbport_poll_cmd(tag
, slot
, cmd
, 2, 0, NULL
, 0);
174 pms_elantech_read_1(pckbport_tag_t tag
, pckbport_slot_t slot
, uint8_t reg
,
181 cmd
= ELANTECH_CUSTOM_CMD
;
182 res
= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
183 cmd
= ELANTECH_REG_READ
;
184 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
185 cmd
= ELANTECH_CUSTOM_CMD
;
186 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
188 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
189 cmd
= PMS_SEND_DEV_STATUS
;
190 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 3, resp
, 0);
199 pms_elantech_write_1(pckbport_tag_t tag
, pckbport_slot_t slot
, uint8_t reg
,
205 cmd
= ELANTECH_CUSTOM_CMD
;
206 res
= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
207 cmd
= ELANTECH_REG_WRITE
;
208 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
209 cmd
= ELANTECH_CUSTOM_CMD
;
210 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
212 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
213 cmd
= ELANTECH_CUSTOM_CMD
;
214 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
216 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
217 cmd
= PMS_SET_SCALE11
;
218 res
|= pckbport_poll_cmd(tag
, slot
, &cmd
, 1, 0, NULL
, 0);
224 pms_elantech_init(struct pms_softc
*psc
)
229 /* set absolute mode */
230 res
= pms_elantech_write_1(psc
->sc_kbctag
, psc
->sc_kbcslot
, 0x10, 0x54);
233 res
= pms_elantech_write_1(psc
->sc_kbctag
, psc
->sc_kbcslot
, 0x11, 0x88);
236 res
= pms_elantech_write_1(psc
->sc_kbctag
, psc
->sc_kbcslot
, 0x21, 0x60);
240 res
= pms_elantech_read_1(psc
->sc_kbctag
, psc
->sc_kbcslot
, 0x10, &val
);
243 aprint_error_dev(psc
->sc_dev
, "couldn't set absolute mode\n");
249 pms_elantech_input(void *opaque
, int data
)
251 struct pms_softc
*psc
= opaque
;
252 struct elantech_softc
*sc
= &psc
->u
.elantech
;
253 struct elantech_packet ep
;
256 if (!psc
->sc_enabled
)
259 if ((psc
->inputstate
== 0 && (data
& 0x0c) != 0x0c) ||
260 (psc
->inputstate
== 3 && (data
& 0x0f) != 0x08)) {
261 aprint_debug_dev(psc
->sc_dev
, "waiting for sync..\n");
266 psc
->packet
[psc
->inputstate
++] = data
& 0xff;
267 if (psc
->inputstate
!= 6)
272 ep
.ep_nfingers
= (psc
->packet
[0] & 0xc0) >> 6;
274 ep
.ep_buttons
= psc
->packet
[0] & 1; /* left button */
275 ep
.ep_buttons
|= (psc
->packet
[0] & 2) << 1; /* right button */
277 if (ep
.ep_nfingers
== 0 || ep
.ep_nfingers
!= sc
->last_nfingers
)
278 sc
->initializing
= true;
280 switch (ep
.ep_nfingers
) {
284 ep
.ep_x
= ((int16_t)psc
->packet
[1] << 8) | psc
->packet
[2];
285 ep
.ep_y
= ((int16_t)psc
->packet
[4] << 8) | psc
->packet
[5];
287 aprint_debug_dev(psc
->sc_dev
,
288 "%d finger detected in elantech mode:\n", ep
.ep_nfingers
);
289 aprint_debug_dev(psc
->sc_dev
,
290 " X=%d Y=%d\n", ep
.ep_x
, ep
.ep_y
);
291 aprint_debug_dev(psc
->sc_dev
,
292 " %02x %02x %02x %02x %02x %02x\n",
293 psc
->packet
[0], psc
->packet
[1], psc
->packet
[2],
294 psc
->packet
[3], psc
->packet
[4], psc
->packet
[5]);
297 wsmouse_input(psc
->sc_wsmousedev
, ep
.ep_buttons
,
299 0 : (ep
.ep_x
- sc
->last_x
) >> elantech_xy_unprecision
,
301 0 : (ep
.ep_y
- sc
->last_y
) >> elantech_xy_unprecision
,
303 WSMOUSE_INPUT_DELTA
);
306 if (sc
->initializing
== true ||
307 ((ep
.ep_x
- sc
->last_x
) >> elantech_xy_unprecision
) != 0)
308 sc
->last_x
= ep
.ep_x
;
309 if (sc
->initializing
== true ||
310 ((ep
.ep_y
- sc
->last_y
) >> elantech_xy_unprecision
) != 0)
311 sc
->last_y
= ep
.ep_y
;
315 ep
.ep_z
= psc
->packet
[2];
316 aprint_debug_dev(psc
->sc_dev
,
317 "2 fingers detected in elantech mode:\n");
318 aprint_debug_dev(psc
->sc_dev
,
319 " %02x %02x %02x %02x %02x %02x\n",
320 psc
->packet
[0], psc
->packet
[1], psc
->packet
[2],
321 psc
->packet
[3], psc
->packet
[4], psc
->packet
[5]);
324 wsmouse_input(psc
->sc_wsmousedev
, 0,
327 0 : (sc
->last_z
- ep
.ep_z
) >> elantech_z_unprecision
,
329 WSMOUSE_INPUT_DELTA
);
332 if (sc
->initializing
== true ||
333 ((sc
->last_z
- ep
.ep_z
) >> elantech_z_unprecision
) != 0)
334 sc
->last_z
= ep
.ep_z
;
337 aprint_debug_dev(psc
->sc_dev
, "that's a lot of fingers!\n");
341 if (ep
.ep_nfingers
> 0)
342 sc
->initializing
= false;
343 sc
->last_nfingers
= ep
.ep_nfingers
;
347 pms_elantech_probe_init(void *opaque
)
349 struct pms_softc
*psc
= opaque
;
350 struct elantech_softc
*sc
= &psc
->u
.elantech
;
351 struct sysctllog
*clog
= NULL
;
352 u_char cmd
[1], resp
[3];
356 pckbport_flush(psc
->sc_kbctag
, psc
->sc_kbcslot
);
358 cmd
[0] = PMS_SET_SCALE11
;
359 if ((res
= pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
,
360 cmd
, 1, 0, NULL
, 0)) != 0)
362 cmd
[0] = PMS_SET_SCALE11
;
363 if ((res
= pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
,
364 cmd
, 1, 0, NULL
, 0)) != 0)
366 cmd
[0] = PMS_SET_SCALE11
;
367 if ((res
= pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
,
368 cmd
, 1, 0, NULL
, 0)) != 0)
371 cmd
[0] = PMS_SEND_DEV_STATUS
;
372 if ((res
= pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
,
373 cmd
, 1, 3, resp
, 0)) != 0)
376 if (!ELANTECH_MAGIC(resp
)) {
377 #ifdef ELANTECH_DEBUG
378 aprint_error_dev(psc
->sc_dev
,
379 "bad elantech magic (%X %X %X)\n",
380 resp
[0], resp
[1], resp
[2]);
386 res
= pms_elantech_send_command(psc
->sc_kbctag
, psc
->sc_kbcslot
,
387 ELANTECH_FW_VERSION
);
388 cmd
[0] = PMS_SEND_DEV_STATUS
;
389 res
|= pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
,
392 aprint_error_dev(psc
->sc_dev
,
393 "unable to query elantech firmware version\n");
397 fwversion
= (resp
[0] << 8) | resp
[2];
398 if (fwversion
< ELANTECH_MIN_VERSION
) {
399 aprint_error_dev(psc
->sc_dev
,
400 "unsupported Elantech version %d.%d (%X %X %X)\n",
401 resp
[0], resp
[2], resp
[0], resp
[1], resp
[2]);
404 sc
->version
= fwversion
;
405 aprint_normal_dev(psc
->sc_dev
, "Elantech touchpad version %d.%d\n",
408 res
= pms_elantech_init(psc
);
410 aprint_error_dev(psc
->sc_dev
,
411 "couldn't initialize elantech touchpad\n");
415 pms_sysctl_elantech(&clog
);
416 pckbport_set_inputhandler(psc
->sc_kbctag
, psc
->sc_kbcslot
,
417 pms_elantech_input
, psc
, device_xname(psc
->sc_dev
));
423 (void)pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
, cmd
,
429 pms_elantech_enable(void *opaque
)
431 struct pms_softc
*psc
= opaque
;
432 struct elantech_softc
*sc
= &psc
->u
.elantech
;
434 sc
->initializing
= true;
438 pms_elantech_resume(void *opaque
)
440 struct pms_softc
*psc
= opaque
;
441 uint8_t cmd
, resp
[2];
445 res
= pckbport_poll_cmd(psc
->sc_kbctag
, psc
->sc_kbcslot
, &cmd
,
448 aprint_error_dev(psc
->sc_dev
,
449 "elantech reset on resume failed\n");
451 pms_elantech_init(psc
);
452 pms_elantech_enable(psc
);