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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | |