xref: /openbsd-src/sys/dev/pckbc/pms.c (revision cf372def5ad1eaa4e1dc013c319a7d21b1e7925f)
1*cf372defSderaadt /* $OpenBSD: pms.c,v 1.99 2024/08/18 15:09:49 deraadt Exp $ */
24026eccfSkettenis /* $NetBSD: psm.c,v 1.11 2000/06/05 22:20:57 sommerfeld Exp $ */
34026eccfSkettenis 
44026eccfSkettenis /*-
54026eccfSkettenis  * Copyright (c) 1994 Charles M. Hannum.
64026eccfSkettenis  * Copyright (c) 1992, 1993 Erik Forsberg.
74026eccfSkettenis  * All rights reserved.
84026eccfSkettenis  *
94026eccfSkettenis  * Redistribution and use in source and binary forms, with or without
104026eccfSkettenis  * modification, are permitted provided that the following conditions
114026eccfSkettenis  * are met:
124026eccfSkettenis  * 1. Redistributions of source code must retain the above copyright
134026eccfSkettenis  *    notice, this list of conditions and the following disclaimer.
144026eccfSkettenis  *
154026eccfSkettenis  * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED
164026eccfSkettenis  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
174026eccfSkettenis  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
184026eccfSkettenis  * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
194026eccfSkettenis  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
204026eccfSkettenis  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
214026eccfSkettenis  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
224026eccfSkettenis  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
234026eccfSkettenis  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
244026eccfSkettenis  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
254026eccfSkettenis  */
264026eccfSkettenis 
274026eccfSkettenis #include <sys/param.h>
284026eccfSkettenis #include <sys/systm.h>
290649de23Smpi #include <sys/rwlock.h>
304026eccfSkettenis #include <sys/device.h>
314026eccfSkettenis #include <sys/ioctl.h>
32aaab154dSshadchin #include <sys/malloc.h>
330fcbbc5fSanton #include <sys/task.h>
340fcbbc5fSanton #include <sys/timeout.h>
354026eccfSkettenis 
364026eccfSkettenis #include <machine/bus.h>
374026eccfSkettenis 
384026eccfSkettenis #include <dev/ic/pckbcvar.h>
394026eccfSkettenis 
404026eccfSkettenis #include <dev/pckbc/pmsreg.h>
414026eccfSkettenis 
424026eccfSkettenis #include <dev/wscons/wsconsio.h>
434026eccfSkettenis #include <dev/wscons/wsmousevar.h>
444026eccfSkettenis 
45ac6c6540Sjsg #if defined(__i386__) || defined(__amd64__)
46ac6c6540Sjsg #include "acpi.h"
47ac6c6540Sjsg #endif
48ac6c6540Sjsg 
49ac6c6540Sjsg #if !defined(SMALL_KERNEL) && NACPI > 0
50ac6c6540Sjsg extern int mouse_has_softbtn;
51ac6c6540Sjsg #else
52ac6c6540Sjsg int mouse_has_softbtn;
53ac6c6540Sjsg #endif
54ac6c6540Sjsg 
557327fb1eSmpi #ifdef DEBUG
567327fb1eSmpi #define DPRINTF(x...)	do { printf(x); } while (0);
577327fb1eSmpi #else
587327fb1eSmpi #define DPRINTF(x...)
597327fb1eSmpi #endif
607327fb1eSmpi 
6155fddf67Skrw #define DEVNAME(sc)	((sc)->sc_dev.dv_xname)
6255fddf67Skrw 
63aaab154dSshadchin #define WSMOUSE_BUTTON(x)	(1 << ((x) - 1))
64aaab154dSshadchin 
65f7577b15Sshadchin struct pms_softc;
66f7577b15Sshadchin 
67f7577b15Sshadchin struct pms_protocol {
68f7577b15Sshadchin 	int type;
69f7577b15Sshadchin #define PMS_STANDARD		0
70f7577b15Sshadchin #define PMS_INTELLI		1
71aaab154dSshadchin #define PMS_SYNAPTICS		2
727327fb1eSmpi #define PMS_ALPS		3
7372ec3668Sstsp #define PMS_ELANTECH_V1		4
7472ec3668Sstsp #define PMS_ELANTECH_V2		5
7572ec3668Sstsp #define PMS_ELANTECH_V3		6
767f1c67caSjcs #define PMS_ELANTECH_V4		7
77f7577b15Sshadchin 	u_int packetsize;
78f7577b15Sshadchin 	int (*enable)(struct pms_softc *);
79f7577b15Sshadchin 	int (*ioctl)(struct pms_softc *, u_long, caddr_t, int, struct proc *);
80f7577b15Sshadchin 	int (*sync)(struct pms_softc *, int);
81f7577b15Sshadchin 	void (*proc)(struct pms_softc *);
82f7577b15Sshadchin 	void (*disable)(struct pms_softc *);
83f7577b15Sshadchin };
84f7577b15Sshadchin 
85aaab154dSshadchin struct synaptics_softc {
86aaab154dSshadchin 	int identify;
87dc9b7ba2Sjsg 	int capabilities, ext_capabilities, ext2_capabilities;
88aaab154dSshadchin 	int model, ext_model;
89dc9b7ba2Sjsg 	int modes;
90aaab154dSshadchin 
91aaab154dSshadchin 	int mode;
92aaab154dSshadchin 
9343a3c8cdSshadchin 	int mask;
9443a3c8cdSshadchin #define SYNAPTICS_MASK_NEWABS_STRICT	0xc8
9543a3c8cdSshadchin #define SYNAPTICS_MASK_NEWABS_RELAXED	0xc0
9643a3c8cdSshadchin #define SYNAPTICS_VALID_NEWABS_FIRST	0x80
9743a3c8cdSshadchin #define SYNAPTICS_VALID_NEWABS_NEXT	0xc0
9843a3c8cdSshadchin 
99dc9b7ba2Sjsg 	u_int sec_buttons;
100dc727cfdSbru 
101dc727cfdSbru #define SYNAPTICS_PRESSURE_HI		30
102dc727cfdSbru #define SYNAPTICS_PRESSURE_LO		25
103dc727cfdSbru #define SYNAPTICS_PRESSURE		SYNAPTICS_PRESSURE_HI
104aaab154dSshadchin #define SYNAPTICS_SCALE			4
105dc727cfdSbru #define SYNAPTICS_MAX_FINGERS		3
106aaab154dSshadchin };
107aaab154dSshadchin 
1087327fb1eSmpi struct alps_softc {
1097327fb1eSmpi 	int model;
110b2dcd1e6Sshadchin #define ALPS_GLIDEPOINT		(1 << 1)
111b2dcd1e6Sshadchin #define ALPS_DUALPOINT		(1 << 2)
112b2dcd1e6Sshadchin #define ALPS_PASSTHROUGH	(1 << 3)
113b2dcd1e6Sshadchin #define ALPS_INTERLEAVED	(1 << 4)
114b2dcd1e6Sshadchin 
115dd24e85bSmpi 	int mask;
1167327fb1eSmpi 	int version;
1177327fb1eSmpi 
1182445272cSbru 	u_int gesture;
1197327fb1eSmpi 
120b2dcd1e6Sshadchin 	u_int sec_buttons;	/* trackpoint */
121b2dcd1e6Sshadchin 
1227327fb1eSmpi 	int old_x, old_y;
1237327fb1eSmpi #define ALPS_PRESSURE		40
1247327fb1eSmpi };
1257327fb1eSmpi 
12672ec3668Sstsp struct elantech_softc {
12772ec3668Sstsp 	int flags;
12872ec3668Sstsp #define ELANTECH_F_REPORTS_PRESSURE	0x01
12972ec3668Sstsp #define ELANTECH_F_HAS_ROCKER		0x02
13072ec3668Sstsp #define ELANTECH_F_2FINGER_PACKET	0x04
13172ec3668Sstsp #define ELANTECH_F_HW_V1_OLD		0x08
13240f7e3e4Sstsp #define ELANTECH_F_CRC_ENABLED		0x10
1338e9e9c10Sbru #define ELANTECH_F_TRACKPOINT		0x20
1347b88a336Sstsp 	int fw_version;
13572ec3668Sstsp 
1365ad8c6a9Sbru 	u_int mt_slots;
1373c37ce5cSmpi 
1387f1c67caSjcs 	int width;
13972ec3668Sstsp 
14072ec3668Sstsp 	u_char parity[256];
14172ec3668Sstsp 	u_char p1, p2, p3;
14272ec3668Sstsp 
1434101186eSbru 	int max_x, max_y;
14472ec3668Sstsp 	int old_x, old_y;
1456e9b9891Sbru 	int initial_pkt;
14672ec3668Sstsp };
1479665f110Sbru #define ELANTECH_IS_CLICKPAD(sc) (((sc)->elantech->fw_version & 0x1000) != 0)
14872ec3668Sstsp 
1494026eccfSkettenis struct pms_softc {		/* driver status information */
1504026eccfSkettenis 	struct device sc_dev;
1514026eccfSkettenis 
1524026eccfSkettenis 	pckbc_tag_t sc_kbctag;
1534026eccfSkettenis 
15489d55430Sderaadt 	int sc_state;
15589d55430Sderaadt #define PMS_STATE_DISABLED	0
15689d55430Sderaadt #define PMS_STATE_ENABLED	1
15789d55430Sderaadt #define PMS_STATE_SUSPENDED	2
1588cd42f84Smiod 
1590649de23Smpi 	struct rwlock sc_state_lock;
1600649de23Smpi 
161aaab154dSshadchin 	int sc_dev_enable;
162aaab154dSshadchin #define PMS_DEV_IGNORE		0x00
163aaab154dSshadchin #define PMS_DEV_PRIMARY		0x01
164aaab154dSshadchin #define PMS_DEV_SECONDARY	0x02
165aaab154dSshadchin 
1660fcbbc5fSanton 	struct task sc_rsttask;
1670fcbbc5fSanton 	struct timeout sc_rsttimo;
1680fcbbc5fSanton 	int sc_rststate;
1690fcbbc5fSanton #define PMS_RST_COMMENCE	0x01
1700fcbbc5fSanton #define PMS_RST_ANNOUNCED	0x02
1710fcbbc5fSanton 
17275d69285Skrw 	int poll;
1734026eccfSkettenis 	int inputstate;
174f7577b15Sshadchin 
175f7577b15Sshadchin 	const struct pms_protocol *protocol;
176aaab154dSshadchin 	struct synaptics_softc *synaptics;
1777327fb1eSmpi 	struct alps_softc *alps;
17872ec3668Sstsp 	struct elantech_softc *elantech;
179f7577b15Sshadchin 
180f7577b15Sshadchin 	u_char packet[8];
1814026eccfSkettenis 
1824026eccfSkettenis 	struct device *sc_wsmousedev;
183f96d6147Sshadchin 	struct device *sc_sec_wsmousedev;
1844026eccfSkettenis };
1854026eccfSkettenis 
186f7577b15Sshadchin static const u_int butmap[8] = {
187f7577b15Sshadchin 	0,
188aaab154dSshadchin 	WSMOUSE_BUTTON(1),
189aaab154dSshadchin 	WSMOUSE_BUTTON(3),
190aaab154dSshadchin 	WSMOUSE_BUTTON(1) | WSMOUSE_BUTTON(3),
191aaab154dSshadchin 	WSMOUSE_BUTTON(2),
192aaab154dSshadchin 	WSMOUSE_BUTTON(1) | WSMOUSE_BUTTON(2),
193aaab154dSshadchin 	WSMOUSE_BUTTON(2) | WSMOUSE_BUTTON(3),
194aaab154dSshadchin 	WSMOUSE_BUTTON(1) | WSMOUSE_BUTTON(2) | WSMOUSE_BUTTON(3)
195f7577b15Sshadchin };
196f7577b15Sshadchin 
1977327fb1eSmpi static const struct alps_model {
1987327fb1eSmpi 	int version;
199dd24e85bSmpi 	int mask;
2007327fb1eSmpi 	int model;
2017327fb1eSmpi } alps_models[] = {
202dd24e85bSmpi 	{ 0x2021, 0xf8, ALPS_DUALPOINT | ALPS_PASSTHROUGH },
203dd24e85bSmpi 	{ 0x2221, 0xf8, ALPS_DUALPOINT | ALPS_PASSTHROUGH },
204dd24e85bSmpi 	{ 0x2222, 0xff, ALPS_DUALPOINT | ALPS_PASSTHROUGH },
205dd24e85bSmpi 	{ 0x3222, 0xf8, ALPS_DUALPOINT | ALPS_PASSTHROUGH },
206b2dcd1e6Sshadchin 	{ 0x5212, 0xff, ALPS_DUALPOINT | ALPS_PASSTHROUGH | ALPS_INTERLEAVED },
207dd24e85bSmpi 	{ 0x5321, 0xf8, ALPS_GLIDEPOINT },
208dd24e85bSmpi 	{ 0x5322, 0xf8, ALPS_GLIDEPOINT },
209dd24e85bSmpi 	{ 0x603b, 0xf8, ALPS_GLIDEPOINT },
210b2dcd1e6Sshadchin 	{ 0x6222, 0xcf, ALPS_DUALPOINT | ALPS_PASSTHROUGH | ALPS_INTERLEAVED },
211dd24e85bSmpi 	{ 0x6321, 0xf8, ALPS_GLIDEPOINT },
212dd24e85bSmpi 	{ 0x6322, 0xf8, ALPS_GLIDEPOINT },
213dd24e85bSmpi 	{ 0x6323, 0xf8, ALPS_GLIDEPOINT },
214dd24e85bSmpi 	{ 0x6324, 0x8f, ALPS_GLIDEPOINT },
215dd24e85bSmpi 	{ 0x6325, 0xef, ALPS_GLIDEPOINT },
216dd24e85bSmpi 	{ 0x6326, 0xf8, ALPS_GLIDEPOINT },
217419d60bbSmpi 	{ 0x7301, 0xf8, ALPS_DUALPOINT },
218dd24e85bSmpi 	{ 0x7321, 0xf8, ALPS_GLIDEPOINT },
219dd24e85bSmpi 	{ 0x7322, 0xf8, ALPS_GLIDEPOINT },
220dd24e85bSmpi 	{ 0x7325, 0xcf, ALPS_GLIDEPOINT },
2217327fb1eSmpi #if 0
2222d383e6dSmpi 	/*
2232d383e6dSmpi 	 * This model has a clitpad sending almost compatible PS2
2242d383e6dSmpi 	 * packets but not compatible enough to be used with the
2252d383e6dSmpi 	 * ALPS protocol.
2262d383e6dSmpi 	 */
2272d383e6dSmpi 	{ 0x633b, 0xf8, ALPS_DUALPOINT | ALPS_PASSTHROUGH },
2282d383e6dSmpi 
229dd24e85bSmpi 	{ 0x7326, 0, 0 },	/* XXX Uses unknown v3 protocol */
230529d4085Sbru 
231529d4085Sbru 	{ 0x7331, 0x8f, ALPS_DUALPOINT },	/* not supported */
2327327fb1eSmpi #endif
2337327fb1eSmpi };
2347327fb1eSmpi 
235dc727cfdSbru static struct wsmouse_param synaptics_params[] = {
236dc727cfdSbru 	{ WSMOUSECFG_PRESSURE_LO, SYNAPTICS_PRESSURE_LO },
237dc727cfdSbru 	{ WSMOUSECFG_PRESSURE_HI, SYNAPTICS_PRESSURE_HI }
238dc727cfdSbru };
239dc727cfdSbru 
240995fbf01Sbru static struct wsmouse_param alps_params[] = {
241995fbf01Sbru 	{ WSMOUSECFG_SMOOTHING, 3 }
242995fbf01Sbru };
243995fbf01Sbru 
2444026eccfSkettenis int	pmsprobe(struct device *, void *, void *);
2454026eccfSkettenis void	pmsattach(struct device *, struct device *, void *);
24689d55430Sderaadt int	pmsactivate(struct device *, int);
2474026eccfSkettenis 
24888955b28Skrw void	pmsinput(void *, int);
2494026eccfSkettenis 
250aaab154dSshadchin int	pms_change_state(struct pms_softc *, int, int);
251f96d6147Sshadchin 
2524026eccfSkettenis int	pms_ioctl(void *, u_long, caddr_t, int, struct proc *);
25389d55430Sderaadt int	pms_enable(void *);
2544026eccfSkettenis void	pms_disable(void *);
2554026eccfSkettenis 
256f96d6147Sshadchin int	pms_sec_ioctl(void *, u_long, caddr_t, int, struct proc *);
257f96d6147Sshadchin int	pms_sec_enable(void *);
258f96d6147Sshadchin void	pms_sec_disable(void *);
259f96d6147Sshadchin 
26075d69285Skrw int	pms_cmd(struct pms_softc *, u_char *, int, u_char *, int);
261aaab154dSshadchin int	pms_spec_cmd(struct pms_softc *, int);
26255fddf67Skrw int	pms_get_devid(struct pms_softc *, u_char *);
26355fddf67Skrw int	pms_get_status(struct pms_softc *, u_char *);
26455fddf67Skrw int	pms_set_rate(struct pms_softc *, int);
26555fddf67Skrw int	pms_set_resolution(struct pms_softc *, int);
26655fddf67Skrw int	pms_set_scaling(struct pms_softc *, int);
26755fddf67Skrw int	pms_reset(struct pms_softc *);
26855fddf67Skrw int	pms_dev_enable(struct pms_softc *);
26955fddf67Skrw int	pms_dev_disable(struct pms_softc *);
270f6323c95Smpi void	pms_protocol_lookup(struct pms_softc *);
2710fcbbc5fSanton void	pms_reset_detect(struct pms_softc *, int);
2720fcbbc5fSanton void	pms_reset_task(void *);
2730fcbbc5fSanton void	pms_reset_timo(void *);
27475d69285Skrw 
275f7577b15Sshadchin int	pms_enable_intelli(struct pms_softc *);
276f7577b15Sshadchin 
277f7577b15Sshadchin int	pms_ioctl_mouse(struct pms_softc *, u_long, caddr_t, int, struct proc *);
278f7577b15Sshadchin int	pms_sync_mouse(struct pms_softc *, int);
279f7577b15Sshadchin void	pms_proc_mouse(struct pms_softc *);
2808cd42f84Smiod 
281aaab154dSshadchin int	pms_enable_synaptics(struct pms_softc *);
282aaab154dSshadchin int	pms_ioctl_synaptics(struct pms_softc *, u_long, caddr_t, int, struct proc *);
283aaab154dSshadchin int	pms_sync_synaptics(struct pms_softc *, int);
284aaab154dSshadchin void	pms_proc_synaptics(struct pms_softc *);
285aaab154dSshadchin void	pms_disable_synaptics(struct pms_softc *);
286aaab154dSshadchin 
2877327fb1eSmpi int	pms_enable_alps(struct pms_softc *);
2887327fb1eSmpi int	pms_ioctl_alps(struct pms_softc *, u_long, caddr_t, int, struct proc *);
2897327fb1eSmpi int	pms_sync_alps(struct pms_softc *, int);
2907327fb1eSmpi void	pms_proc_alps(struct pms_softc *);
2917327fb1eSmpi 
29272ec3668Sstsp int	pms_enable_elantech_v1(struct pms_softc *);
29372ec3668Sstsp int	pms_enable_elantech_v2(struct pms_softc *);
29472ec3668Sstsp int	pms_enable_elantech_v3(struct pms_softc *);
2957f1c67caSjcs int	pms_enable_elantech_v4(struct pms_softc *);
29672ec3668Sstsp int	pms_ioctl_elantech(struct pms_softc *, u_long, caddr_t, int,
29772ec3668Sstsp     struct proc *);
29872ec3668Sstsp int	pms_sync_elantech_v1(struct pms_softc *, int);
29972ec3668Sstsp int	pms_sync_elantech_v2(struct pms_softc *, int);
30072ec3668Sstsp int	pms_sync_elantech_v3(struct pms_softc *, int);
3017f1c67caSjcs int	pms_sync_elantech_v4(struct pms_softc *, int);
30272ec3668Sstsp void	pms_proc_elantech_v1(struct pms_softc *);
30372ec3668Sstsp void	pms_proc_elantech_v2(struct pms_softc *);
30472ec3668Sstsp void	pms_proc_elantech_v3(struct pms_softc *);
3057f1c67caSjcs void	pms_proc_elantech_v4(struct pms_softc *);
30672ec3668Sstsp 
307748e7d13Sstsp int	synaptics_knock(struct pms_softc *);
3080329de75Smglocker int	synaptics_set_mode(struct pms_softc *, int, int);
309aaab154dSshadchin int	synaptics_query(struct pms_softc *, int, int *);
310aaab154dSshadchin int	synaptics_get_hwinfo(struct pms_softc *);
311f96d6147Sshadchin void	synaptics_sec_proc(struct pms_softc *);
312aaab154dSshadchin 
313b2dcd1e6Sshadchin int	alps_sec_proc(struct pms_softc *);
3147327fb1eSmpi int	alps_get_hwinfo(struct pms_softc *);
3157327fb1eSmpi 
31672ec3668Sstsp int	elantech_knock(struct pms_softc *);
31772ec3668Sstsp int	elantech_get_hwinfo_v1(struct pms_softc *);
31872ec3668Sstsp int	elantech_get_hwinfo_v2(struct pms_softc *);
31972ec3668Sstsp int	elantech_get_hwinfo_v3(struct pms_softc *);
3207f1c67caSjcs int	elantech_get_hwinfo_v4(struct pms_softc *);
32172ec3668Sstsp int	elantech_ps2_cmd(struct pms_softc *, u_char);
32272ec3668Sstsp int	elantech_set_absolute_mode_v1(struct pms_softc *);
32372ec3668Sstsp int	elantech_set_absolute_mode_v2(struct pms_softc *);
32472ec3668Sstsp int	elantech_set_absolute_mode_v3(struct pms_softc *);
3257f1c67caSjcs int	elantech_set_absolute_mode_v4(struct pms_softc *);
32672ec3668Sstsp 
327471aeecfSnaddy const struct cfattach pms_ca = {
32888955b28Skrw 	sizeof(struct pms_softc), pmsprobe, pmsattach, NULL,
32988955b28Skrw 	pmsactivate
33088955b28Skrw };
33188955b28Skrw 
33288955b28Skrw struct cfdriver pms_cd = {
33388955b28Skrw 	NULL, "pms", DV_DULL
33488955b28Skrw };
33588955b28Skrw 
3364026eccfSkettenis const struct wsmouse_accessops pms_accessops = {
3374026eccfSkettenis 	pms_enable,
3384026eccfSkettenis 	pms_ioctl,
3394026eccfSkettenis 	pms_disable,
3404026eccfSkettenis };
3414026eccfSkettenis 
342f96d6147Sshadchin const struct wsmouse_accessops pms_sec_accessops = {
343f96d6147Sshadchin 	pms_sec_enable,
344f96d6147Sshadchin 	pms_sec_ioctl,
345f96d6147Sshadchin 	pms_sec_disable,
346aaab154dSshadchin };
347aaab154dSshadchin 
348aaab154dSshadchin const struct pms_protocol pms_protocols[] = {
349f7577b15Sshadchin 	/* Generic PS/2 mouse */
350f7577b15Sshadchin 	{
351f7577b15Sshadchin 		PMS_STANDARD, 3,
352f7577b15Sshadchin 		NULL,
353f7577b15Sshadchin 		pms_ioctl_mouse,
354f7577b15Sshadchin 		pms_sync_mouse,
355f7577b15Sshadchin 		pms_proc_mouse,
356f7577b15Sshadchin 		NULL
357f7577b15Sshadchin 	},
358aaab154dSshadchin 	/* Synaptics touchpad */
359aaab154dSshadchin 	{
360aaab154dSshadchin 		PMS_SYNAPTICS, 6,
361aaab154dSshadchin 		pms_enable_synaptics,
362aaab154dSshadchin 		pms_ioctl_synaptics,
363aaab154dSshadchin 		pms_sync_synaptics,
364aaab154dSshadchin 		pms_proc_synaptics,
365aaab154dSshadchin 		pms_disable_synaptics
3667327fb1eSmpi 	},
3677327fb1eSmpi 	/* ALPS touchpad */
3687327fb1eSmpi 	{
3697327fb1eSmpi 		PMS_ALPS, 6,
3707327fb1eSmpi 		pms_enable_alps,
3717327fb1eSmpi 		pms_ioctl_alps,
3727327fb1eSmpi 		pms_sync_alps,
3737327fb1eSmpi 		pms_proc_alps,
3747327fb1eSmpi 		NULL
3757327fb1eSmpi 	},
37672ec3668Sstsp 	/* Elantech touchpad (hardware version 1) */
37772ec3668Sstsp 	{
37872ec3668Sstsp 		PMS_ELANTECH_V1, 4,
37972ec3668Sstsp 		pms_enable_elantech_v1,
38072ec3668Sstsp 		pms_ioctl_elantech,
38172ec3668Sstsp 		pms_sync_elantech_v1,
38272ec3668Sstsp 		pms_proc_elantech_v1,
38372ec3668Sstsp 		NULL
38472ec3668Sstsp 	},
38572ec3668Sstsp 	/* Elantech touchpad (hardware version 2) */
38672ec3668Sstsp 	{
38772ec3668Sstsp 		PMS_ELANTECH_V2, 6,
38872ec3668Sstsp 		pms_enable_elantech_v2,
38972ec3668Sstsp 		pms_ioctl_elantech,
39072ec3668Sstsp 		pms_sync_elantech_v2,
39172ec3668Sstsp 		pms_proc_elantech_v2,
39272ec3668Sstsp 		NULL
39372ec3668Sstsp 	},
39472ec3668Sstsp 	/* Elantech touchpad (hardware version 3) */
39572ec3668Sstsp 	{
39672ec3668Sstsp 		PMS_ELANTECH_V3, 6,
39772ec3668Sstsp 		pms_enable_elantech_v3,
39872ec3668Sstsp 		pms_ioctl_elantech,
39972ec3668Sstsp 		pms_sync_elantech_v3,
40072ec3668Sstsp 		pms_proc_elantech_v3,
40172ec3668Sstsp 		NULL
40272ec3668Sstsp 	},
4037f1c67caSjcs 	/* Elantech touchpad (hardware version 4) */
4047f1c67caSjcs 	{
4057f1c67caSjcs 		PMS_ELANTECH_V4, 6,
4067f1c67caSjcs 		pms_enable_elantech_v4,
4077f1c67caSjcs 		pms_ioctl_elantech,
4087f1c67caSjcs 		pms_sync_elantech_v4,
4097f1c67caSjcs 		pms_proc_elantech_v4,
4107f1c67caSjcs 		NULL
4117f1c67caSjcs 	},
4121a2228f9Smpi 	/* Microsoft IntelliMouse */
4131a2228f9Smpi 	{
4141a2228f9Smpi 		PMS_INTELLI, 4,
4151a2228f9Smpi 		pms_enable_intelli,
4161a2228f9Smpi 		pms_ioctl_mouse,
4171a2228f9Smpi 		pms_sync_mouse,
4181a2228f9Smpi 		pms_proc_mouse,
4191a2228f9Smpi 		NULL
4201a2228f9Smpi 	},
421f7577b15Sshadchin };
422f7577b15Sshadchin 
4234026eccfSkettenis int
42475d69285Skrw pms_cmd(struct pms_softc *sc, u_char *cmd, int len, u_char *resp, int resplen)
42575d69285Skrw {
42675d69285Skrw 	if (sc->poll) {
427ac6d5436Stobias 		return pckbc_poll_cmd(sc->sc_kbctag, PCKBC_AUX_SLOT,
42875d69285Skrw 		    cmd, len, resplen, resp, 1);
42975d69285Skrw 	} else {
430ac6d5436Stobias 		return pckbc_enqueue_cmd(sc->sc_kbctag, PCKBC_AUX_SLOT,
43175d69285Skrw 		    cmd, len, resplen, 1, resp);
43275d69285Skrw 	}
43375d69285Skrw }
43475d69285Skrw 
43575d69285Skrw int
436aaab154dSshadchin pms_spec_cmd(struct pms_softc *sc, int cmd)
437aaab154dSshadchin {
438aaab154dSshadchin 	if (pms_set_scaling(sc, 1) ||
439aaab154dSshadchin 	    pms_set_resolution(sc, (cmd >> 6) & 0x03) ||
440aaab154dSshadchin 	    pms_set_resolution(sc, (cmd >> 4) & 0x03) ||
441aaab154dSshadchin 	    pms_set_resolution(sc, (cmd >> 2) & 0x03) ||
442aaab154dSshadchin 	    pms_set_resolution(sc, (cmd >> 0) & 0x03))
443aaab154dSshadchin 		return (-1);
444aaab154dSshadchin 	return (0);
445aaab154dSshadchin }
446aaab154dSshadchin 
447aaab154dSshadchin int
44855fddf67Skrw pms_get_devid(struct pms_softc *sc, u_char *resp)
4498cd42f84Smiod {
45055fddf67Skrw 	u_char cmd[1];
4518cd42f84Smiod 
4528cd42f84Smiod 	cmd[0] = PMS_SEND_DEV_ID;
45355fddf67Skrw 	return (pms_cmd(sc, cmd, 1, resp, 1));
45455fddf67Skrw }
45555fddf67Skrw 
45655fddf67Skrw int
45755fddf67Skrw pms_get_status(struct pms_softc *sc, u_char *resp)
45855fddf67Skrw {
45955fddf67Skrw 	u_char cmd[1];
46055fddf67Skrw 
46155fddf67Skrw 	cmd[0] = PMS_SEND_DEV_STATUS;
46255fddf67Skrw 	return (pms_cmd(sc, cmd, 1, resp, 3));
46355fddf67Skrw }
46455fddf67Skrw 
46555fddf67Skrw int
46655fddf67Skrw pms_set_rate(struct pms_softc *sc, int value)
46755fddf67Skrw {
46855fddf67Skrw 	u_char cmd[2];
46955fddf67Skrw 
47055fddf67Skrw 	cmd[0] = PMS_SET_SAMPLE;
47155fddf67Skrw 	cmd[1] = value;
47255fddf67Skrw 	return (pms_cmd(sc, cmd, 2, NULL, 0));
47355fddf67Skrw }
47455fddf67Skrw 
47555fddf67Skrw int
47655fddf67Skrw pms_set_resolution(struct pms_softc *sc, int value)
47755fddf67Skrw {
47855fddf67Skrw 	u_char cmd[2];
47955fddf67Skrw 
48055fddf67Skrw 	cmd[0] = PMS_SET_RES;
48155fddf67Skrw 	cmd[1] = value;
48255fddf67Skrw 	return (pms_cmd(sc, cmd, 2, NULL, 0));
48355fddf67Skrw }
48455fddf67Skrw 
48555fddf67Skrw int
48655fddf67Skrw pms_set_scaling(struct pms_softc *sc, int scale)
48755fddf67Skrw {
48855fddf67Skrw 	u_char cmd[1];
48955fddf67Skrw 
49055fddf67Skrw 	switch (scale) {
49155fddf67Skrw 	case 1:
49255fddf67Skrw 	default:
49355fddf67Skrw 		cmd[0] = PMS_SET_SCALE11;
49455fddf67Skrw 		break;
49555fddf67Skrw 	case 2:
49655fddf67Skrw 		cmd[0] = PMS_SET_SCALE21;
49755fddf67Skrw 		break;
49855fddf67Skrw 	}
49955fddf67Skrw 	return (pms_cmd(sc, cmd, 1, NULL, 0));
50055fddf67Skrw }
50155fddf67Skrw 
50255fddf67Skrw int
50355fddf67Skrw pms_reset(struct pms_softc *sc)
50455fddf67Skrw {
50555fddf67Skrw 	u_char cmd[1], resp[2];
50655fddf67Skrw 	int res;
50755fddf67Skrw 
50855fddf67Skrw 	cmd[0] = PMS_RESET;
50955fddf67Skrw 	res = pms_cmd(sc, cmd, 1, resp, 2);
51055fddf67Skrw #ifdef DEBUG
51155fddf67Skrw 	if (res || resp[0] != PMS_RSTDONE || resp[1] != 0)
51255fddf67Skrw 		printf("%s: reset error %d (response 0x%02x, type 0x%02x)\n",
51355fddf67Skrw 		    DEVNAME(sc), res, resp[0], resp[1]);
51455fddf67Skrw #endif
51555fddf67Skrw 	return (res);
51655fddf67Skrw }
51755fddf67Skrw 
51855fddf67Skrw int
51955fddf67Skrw pms_dev_enable(struct pms_softc *sc)
52055fddf67Skrw {
52155fddf67Skrw 	u_char cmd[1];
52255fddf67Skrw 	int res;
52355fddf67Skrw 
52455fddf67Skrw 	cmd[0] = PMS_DEV_ENABLE;
52555fddf67Skrw 	res = pms_cmd(sc, cmd, 1, NULL, 0);
52655fddf67Skrw 	if (res)
52755fddf67Skrw 		printf("%s: enable error\n", DEVNAME(sc));
52855fddf67Skrw 	return (res);
52955fddf67Skrw }
53055fddf67Skrw 
53155fddf67Skrw int
53255fddf67Skrw pms_dev_disable(struct pms_softc *sc)
53355fddf67Skrw {
53455fddf67Skrw 	u_char cmd[1];
53555fddf67Skrw 	int res;
53655fddf67Skrw 
53755fddf67Skrw 	cmd[0] = PMS_DEV_DISABLE;
53855fddf67Skrw 	res = pms_cmd(sc, cmd, 1, NULL, 0);
53955fddf67Skrw 	if (res)
54055fddf67Skrw 		printf("%s: disable error\n", DEVNAME(sc));
54155fddf67Skrw 	return (res);
54255fddf67Skrw }
54355fddf67Skrw 
544f6323c95Smpi void
545f6323c95Smpi pms_protocol_lookup(struct pms_softc *sc)
546f6323c95Smpi {
547f6323c95Smpi 	int i;
548f6323c95Smpi 
549f6323c95Smpi 	sc->protocol = &pms_protocols[0];
550f6323c95Smpi 	for (i = 1; i < nitems(pms_protocols); i++) {
551f6323c95Smpi 		pms_reset(sc);
552f6323c95Smpi 		if (pms_protocols[i].enable(sc)) {
553f6323c95Smpi 			sc->protocol = &pms_protocols[i];
554f6323c95Smpi 			break;
555f6323c95Smpi 		}
556f6323c95Smpi 	}
557f6323c95Smpi 
558f6323c95Smpi 	DPRINTF("%s: protocol type %d\n", DEVNAME(sc), sc->protocol->type);
559f6323c95Smpi }
560f6323c95Smpi 
5610fcbbc5fSanton /*
5620fcbbc5fSanton  * Detect reset announcement ([0xaa, 0x0]).
5630fcbbc5fSanton  * The sequence will be sent as input on rare occasions when the touchpad was
5640fcbbc5fSanton  * reset due to a power failure.
5650fcbbc5fSanton  */
5660fcbbc5fSanton void
5670fcbbc5fSanton pms_reset_detect(struct pms_softc *sc, int data)
5680fcbbc5fSanton {
5690fcbbc5fSanton 	switch (sc->sc_rststate) {
5700fcbbc5fSanton 	case PMS_RST_COMMENCE:
5710fcbbc5fSanton 		if (data == 0x0) {
5720fcbbc5fSanton 			sc->sc_rststate = PMS_RST_ANNOUNCED;
5730fcbbc5fSanton 			timeout_add_msec(&sc->sc_rsttimo, 100);
5740fcbbc5fSanton 		} else if (data != PMS_RSTDONE) {
5750fcbbc5fSanton 			sc->sc_rststate = 0;
5760fcbbc5fSanton 		}
5770fcbbc5fSanton 		break;
5780fcbbc5fSanton 	default:
5790fcbbc5fSanton 		if (data == PMS_RSTDONE)
5800fcbbc5fSanton 			sc->sc_rststate = PMS_RST_COMMENCE;
5810fcbbc5fSanton 		else
5820fcbbc5fSanton 			sc->sc_rststate = 0;
5830fcbbc5fSanton 	}
5840fcbbc5fSanton }
5850fcbbc5fSanton 
5860fcbbc5fSanton void
5870fcbbc5fSanton pms_reset_timo(void *v)
5880fcbbc5fSanton {
5890fcbbc5fSanton 	struct pms_softc *sc = v;
5900fcbbc5fSanton 	int s = spltty();
5910fcbbc5fSanton 
5920fcbbc5fSanton 	/*
5930fcbbc5fSanton 	 * Do nothing if the reset was a false positive or if the device already
5940fcbbc5fSanton 	 * is disabled.
5950fcbbc5fSanton 	 */
5960fcbbc5fSanton 	if (sc->sc_rststate == PMS_RST_ANNOUNCED &&
5970fcbbc5fSanton 	    sc->sc_state != PMS_STATE_DISABLED)
5980fcbbc5fSanton 		task_add(systq, &sc->sc_rsttask);
5990fcbbc5fSanton 
6000fcbbc5fSanton 	splx(s);
6010fcbbc5fSanton }
6020fcbbc5fSanton 
6030fcbbc5fSanton void
6040fcbbc5fSanton pms_reset_task(void *v)
6050fcbbc5fSanton {
6060fcbbc5fSanton 	struct pms_softc *sc = v;
6070fcbbc5fSanton 	int s = spltty();
6080fcbbc5fSanton 
6090fcbbc5fSanton #ifdef DIAGNOSTIC
6100fcbbc5fSanton 	printf("%s: device reset (state = %d)\n", DEVNAME(sc), sc->sc_rststate);
6110fcbbc5fSanton #endif
6120fcbbc5fSanton 
6130fcbbc5fSanton 	rw_enter_write(&sc->sc_state_lock);
6140fcbbc5fSanton 
6150fcbbc5fSanton 	if (sc->sc_sec_wsmousedev != NULL)
6160fcbbc5fSanton 		pms_change_state(sc, PMS_STATE_DISABLED, PMS_DEV_SECONDARY);
6170fcbbc5fSanton 	pms_change_state(sc, PMS_STATE_DISABLED, PMS_DEV_PRIMARY);
6180fcbbc5fSanton 
6190fcbbc5fSanton 	pms_change_state(sc, PMS_STATE_ENABLED, PMS_DEV_PRIMARY);
6200fcbbc5fSanton 	if (sc->sc_sec_wsmousedev != NULL)
6210fcbbc5fSanton 		pms_change_state(sc, PMS_STATE_ENABLED, PMS_DEV_SECONDARY);
6220fcbbc5fSanton 
6230fcbbc5fSanton 	rw_exit_write(&sc->sc_state_lock);
6240fcbbc5fSanton 	splx(s);
6250fcbbc5fSanton }
6260fcbbc5fSanton 
62755fddf67Skrw int
628f7577b15Sshadchin pms_enable_intelli(struct pms_softc *sc)
62955fddf67Skrw {
63055fddf67Skrw 	u_char resp;
63155fddf67Skrw 
63262890a12Sshadchin 	/* the special sequence to enable the third button and the roller */
63362890a12Sshadchin 	if (pms_set_rate(sc, PMS_INTELLI_MAGIC1) ||
63462890a12Sshadchin 	    pms_set_rate(sc, PMS_INTELLI_MAGIC2) ||
63562890a12Sshadchin 	    pms_set_rate(sc, PMS_INTELLI_MAGIC3) ||
63655fddf67Skrw 	    pms_get_devid(sc, &resp) ||
63762890a12Sshadchin 	    resp != PMS_INTELLI_ID)
6388cd42f84Smiod 		return (0);
6398cd42f84Smiod 
6408cd42f84Smiod 	return (1);
6418cd42f84Smiod }
6428cd42f84Smiod 
6438cd42f84Smiod int
644f7577b15Sshadchin pms_ioctl_mouse(struct pms_softc *sc, u_long cmd, caddr_t data, int flag,
645f7577b15Sshadchin     struct proc *p)
646f7577b15Sshadchin {
647f7577b15Sshadchin 	int i;
648f7577b15Sshadchin 
649f7577b15Sshadchin 	switch (cmd) {
650f7577b15Sshadchin 	case WSMOUSEIO_GTYPE:
651f7577b15Sshadchin 		*(u_int *)data = WSMOUSE_TYPE_PS2;
652f7577b15Sshadchin 		break;
653f7577b15Sshadchin 	case WSMOUSEIO_SRES:
654f7577b15Sshadchin 		i = ((int) *(u_int *)data - 12) / 25;
655f7577b15Sshadchin 		/* valid values are {0,1,2,3} */
656f7577b15Sshadchin 		if (i < 0)
657f7577b15Sshadchin 			i = 0;
658f7577b15Sshadchin 		if (i > 3)
659f7577b15Sshadchin 			i = 3;
660f7577b15Sshadchin 
661f7577b15Sshadchin 		if (pms_set_resolution(sc, i))
662f7577b15Sshadchin 			printf("%s: SET_RES command error\n", DEVNAME(sc));
663f7577b15Sshadchin 		break;
664f7577b15Sshadchin 	default:
665f7577b15Sshadchin 		return (-1);
666f7577b15Sshadchin 	}
667f7577b15Sshadchin 	return (0);
668f7577b15Sshadchin }
669f7577b15Sshadchin 
670f7577b15Sshadchin int
671f7577b15Sshadchin pms_sync_mouse(struct pms_softc *sc, int data)
672f7577b15Sshadchin {
673f7577b15Sshadchin 	if (sc->inputstate != 0)
674f7577b15Sshadchin 		return (0);
675f7577b15Sshadchin 
676f7577b15Sshadchin 	switch (sc->protocol->type) {
677f7577b15Sshadchin 	case PMS_STANDARD:
678f7577b15Sshadchin 		if ((data & 0xc0) != 0)
679f7577b15Sshadchin 			return (-1);
680f7577b15Sshadchin 		break;
681f7577b15Sshadchin 	case PMS_INTELLI:
682f7577b15Sshadchin 		if ((data & 0x08) != 0x08)
683f7577b15Sshadchin 			return (-1);
684f7577b15Sshadchin 		break;
685f7577b15Sshadchin 	}
686f7577b15Sshadchin 
687f7577b15Sshadchin 	return (0);
688f7577b15Sshadchin }
689f7577b15Sshadchin 
690f7577b15Sshadchin void
691f7577b15Sshadchin pms_proc_mouse(struct pms_softc *sc)
692f7577b15Sshadchin {
693f7577b15Sshadchin 	u_int buttons;
694f7577b15Sshadchin 	int  dx, dy, dz;
695f7577b15Sshadchin 
696f7577b15Sshadchin 	buttons = butmap[sc->packet[0] & PMS_PS2_BUTTONSMASK];
697f7577b15Sshadchin 	dx = (sc->packet[0] & PMS_PS2_XNEG) ?
698f7577b15Sshadchin 	    (int)sc->packet[1] - 256 : sc->packet[1];
699f7577b15Sshadchin 	dy = (sc->packet[0] & PMS_PS2_YNEG) ?
700f7577b15Sshadchin 	    (int)sc->packet[2] - 256 : sc->packet[2];
701f7577b15Sshadchin 
7028f1a9338Smpi 	if (sc->protocol->type == PMS_INTELLI)
703f7577b15Sshadchin 		dz = (signed char)sc->packet[3];
7048f1a9338Smpi 	else
7058f1a9338Smpi 		dz = 0;
706f7577b15Sshadchin 
707ddac655dSbru 	WSMOUSE_INPUT(sc->sc_wsmousedev, buttons, dx, dy, dz, 0);
708f7577b15Sshadchin }
709f7577b15Sshadchin 
710f7577b15Sshadchin int
711b43e75e7Skrw pmsprobe(struct device *parent, void *match, void *aux)
7124026eccfSkettenis {
7134026eccfSkettenis 	struct pckbc_attach_args *pa = aux;
7144026eccfSkettenis 	u_char cmd[1], resp[2];
7154026eccfSkettenis 	int res;
7164026eccfSkettenis 
717ac6d5436Stobias 	if (pa->pa_slot != PCKBC_AUX_SLOT)
7184026eccfSkettenis 		return (0);
7194026eccfSkettenis 
7204026eccfSkettenis 	/* Flush any garbage. */
7214026eccfSkettenis 	pckbc_flush(pa->pa_tag, pa->pa_slot);
7224026eccfSkettenis 
7234026eccfSkettenis 	/* reset the device */
7244026eccfSkettenis 	cmd[0] = PMS_RESET;
7254026eccfSkettenis 	res = pckbc_poll_cmd(pa->pa_tag, pa->pa_slot, cmd, 1, 2, resp, 1);
72653a48ac9Skrw 	if (res || resp[0] != PMS_RSTDONE || resp[1] != 0) {
7274026eccfSkettenis #ifdef DEBUG
72853a48ac9Skrw 		printf("pms: reset error %d (response 0x%02x, type 0x%02x)\n",
72953a48ac9Skrw 		    res, resp[0], resp[1]);
7304026eccfSkettenis #endif
7314026eccfSkettenis 		return (0);
7324026eccfSkettenis 	}
7334026eccfSkettenis 
734be2abd7eSderaadt 	return (1);
7354026eccfSkettenis }
7364026eccfSkettenis 
7374026eccfSkettenis void
738b43e75e7Skrw pmsattach(struct device *parent, struct device *self, void *aux)
7394026eccfSkettenis {
7404026eccfSkettenis 	struct pms_softc *sc = (void *)self;
7414026eccfSkettenis 	struct pckbc_attach_args *pa = aux;
7424026eccfSkettenis 	struct wsmousedev_attach_args a;
7434026eccfSkettenis 
7444026eccfSkettenis 	sc->sc_kbctag = pa->pa_tag;
7454026eccfSkettenis 
746ac6d5436Stobias 	pckbc_set_inputhandler(sc->sc_kbctag, PCKBC_AUX_SLOT,
74755fddf67Skrw 	    pmsinput, sc, DEVNAME(sc));
7484026eccfSkettenis 
749627a0107Smpi 	printf("\n");
750627a0107Smpi 
7514026eccfSkettenis 	a.accessops = &pms_accessops;
7524026eccfSkettenis 	a.accesscookie = sc;
7534026eccfSkettenis 
7540649de23Smpi 	rw_init(&sc->sc_state_lock, "pmsst");
7550649de23Smpi 
7564026eccfSkettenis 	/*
7574026eccfSkettenis 	 * Attach the wsmouse, saving a handle to it.
7584026eccfSkettenis 	 * Note that we don't need to check this pointer against NULL
7594026eccfSkettenis 	 * here or in pmsintr, because if this fails pms_enable() will
7604026eccfSkettenis 	 * never be called, so pmsinput() will never be called.
7614026eccfSkettenis 	 */
7624026eccfSkettenis 	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
7634026eccfSkettenis 
7640fcbbc5fSanton 	task_set(&sc->sc_rsttask, pms_reset_task, sc);
7650fcbbc5fSanton 	timeout_set(&sc->sc_rsttimo, pms_reset_timo, sc);
7660fcbbc5fSanton 
76775d69285Skrw 	sc->poll = 1;
768aaab154dSshadchin 	sc->sc_dev_enable = 0;
769aaab154dSshadchin 
770f6323c95Smpi 	/* See if the device understands an extended (touchpad) protocol. */
771f6323c95Smpi 	pms_protocol_lookup(sc);
7727327fb1eSmpi 
773aaab154dSshadchin 	/* no interrupts until enabled */
774aaab154dSshadchin 	pms_change_state(sc, PMS_STATE_DISABLED, PMS_DEV_IGNORE);
7754026eccfSkettenis }
7764026eccfSkettenis 
7774026eccfSkettenis int
77889d55430Sderaadt pmsactivate(struct device *self, int act)
7794026eccfSkettenis {
78089d55430Sderaadt 	struct pms_softc *sc = (struct pms_softc *)self;
781*cf372defSderaadt 	int rv;
78289d55430Sderaadt 
78389d55430Sderaadt 	switch (act) {
78489d55430Sderaadt 	case DVACT_SUSPEND:
785*cf372defSderaadt 		rv = config_activate_children(self, act);
78689d55430Sderaadt 		if (sc->sc_state == PMS_STATE_ENABLED)
787aaab154dSshadchin 			pms_change_state(sc, PMS_STATE_SUSPENDED,
788aaab154dSshadchin 			    PMS_DEV_IGNORE);
78989d55430Sderaadt 		break;
79089d55430Sderaadt 	case DVACT_RESUME:
79189d55430Sderaadt 		if (sc->sc_state == PMS_STATE_SUSPENDED)
792aaab154dSshadchin 			pms_change_state(sc, PMS_STATE_ENABLED,
793aaab154dSshadchin 			    PMS_DEV_IGNORE);
794*cf372defSderaadt 		rv = config_activate_children(self, act);
795*cf372defSderaadt 		break;
796*cf372defSderaadt 	default:
797*cf372defSderaadt 		rv = config_activate_children(self, act);
79889d55430Sderaadt 		break;
79989d55430Sderaadt 	}
800*cf372defSderaadt 	return (rv);
80189d55430Sderaadt }
80289d55430Sderaadt 
80389d55430Sderaadt int
804aaab154dSshadchin pms_change_state(struct pms_softc *sc, int newstate, int dev)
80589d55430Sderaadt {
806aaab154dSshadchin 	if (dev != PMS_DEV_IGNORE) {
80789d55430Sderaadt 		switch (newstate) {
80889d55430Sderaadt 		case PMS_STATE_ENABLED:
809aaab154dSshadchin 			if (sc->sc_dev_enable & dev)
810e384b96cSkrw 				return (EBUSY);
811e384b96cSkrw 
812aaab154dSshadchin 			sc->sc_dev_enable |= dev;
813aaab154dSshadchin 
814aaab154dSshadchin 			if (sc->sc_state == PMS_STATE_ENABLED)
815aaab154dSshadchin 				return (0);
816aaab154dSshadchin 
817aaab154dSshadchin 			break;
818aaab154dSshadchin 		case PMS_STATE_DISABLED:
819aaab154dSshadchin 			sc->sc_dev_enable &= ~dev;
820aaab154dSshadchin 
821aaab154dSshadchin 			if (sc->sc_dev_enable)
822aaab154dSshadchin 				return (0);
823aaab154dSshadchin 
824aaab154dSshadchin 			break;
825aaab154dSshadchin 		}
826aaab154dSshadchin 	}
827aaab154dSshadchin 
828aaab154dSshadchin 	switch (newstate) {
829aaab154dSshadchin 	case PMS_STATE_ENABLED:
8304026eccfSkettenis 		sc->inputstate = 0;
8310fcbbc5fSanton 		sc->sc_rststate = 0;
8324026eccfSkettenis 
833ac6d5436Stobias 		pckbc_slot_enable(sc->sc_kbctag, PCKBC_AUX_SLOT, 1);
8344026eccfSkettenis 
835506bb5b0Skrw 		if (sc->poll)
836ac6d5436Stobias 			pckbc_flush(sc->sc_kbctag, PCKBC_AUX_SLOT);
83775d69285Skrw 
83855fddf67Skrw 		pms_reset(sc);
839b72aedf1Smpi 		if (sc->protocol->enable != NULL &&
840aaab154dSshadchin 		    sc->protocol->enable(sc) == 0)
841f6323c95Smpi 			pms_protocol_lookup(sc);
8428cd42f84Smiod 
84355fddf67Skrw 		pms_dev_enable(sc);
84489d55430Sderaadt 		break;
84589d55430Sderaadt 	case PMS_STATE_DISABLED:
84689d55430Sderaadt 	case PMS_STATE_SUSPENDED:
84755fddf67Skrw 		pms_dev_disable(sc);
848f7577b15Sshadchin 
849f6323c95Smpi 		if (sc->protocol->disable)
850f7577b15Sshadchin 			sc->protocol->disable(sc);
851f7577b15Sshadchin 
852ac6d5436Stobias 		pckbc_slot_enable(sc->sc_kbctag, PCKBC_AUX_SLOT, 0);
85389d55430Sderaadt 		break;
85489d55430Sderaadt 	}
855e384b96cSkrw 
856e384b96cSkrw 	sc->sc_state = newstate;
857e384b96cSkrw 	sc->poll = (newstate == PMS_STATE_SUSPENDED) ? 1 : 0;
858e384b96cSkrw 
859e384b96cSkrw 	return (0);
8604026eccfSkettenis }
8614026eccfSkettenis 
86289d55430Sderaadt int
863b43e75e7Skrw pms_enable(void *v)
86489d55430Sderaadt {
86589d55430Sderaadt 	struct pms_softc *sc = v;
8660649de23Smpi 	int rv;
86789d55430Sderaadt 
8680649de23Smpi 	rw_enter_write(&sc->sc_state_lock);
8690649de23Smpi 	rv = pms_change_state(sc, PMS_STATE_ENABLED, PMS_DEV_PRIMARY);
8700649de23Smpi 	rw_exit_write(&sc->sc_state_lock);
8710649de23Smpi 
8720649de23Smpi 	return (rv);
87389d55430Sderaadt }
87489d55430Sderaadt 
8754026eccfSkettenis void
876b43e75e7Skrw pms_disable(void *v)
8774026eccfSkettenis {
8784026eccfSkettenis 	struct pms_softc *sc = v;
8794026eccfSkettenis 
8800649de23Smpi 	rw_enter_write(&sc->sc_state_lock);
881aaab154dSshadchin 	pms_change_state(sc, PMS_STATE_DISABLED, PMS_DEV_PRIMARY);
8820649de23Smpi 	rw_exit_write(&sc->sc_state_lock);
8834026eccfSkettenis }
8844026eccfSkettenis 
8854026eccfSkettenis int
886b43e75e7Skrw pms_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
8874026eccfSkettenis {
8884026eccfSkettenis 	struct pms_softc *sc = v;
8894026eccfSkettenis 
890f6323c95Smpi 	if (sc->protocol->ioctl)
891f7577b15Sshadchin 		return (sc->protocol->ioctl(sc, cmd, data, flag, p));
8925817301aSshadchin 	else
8935817301aSshadchin 		return (-1);
8944026eccfSkettenis }
8954026eccfSkettenis 
896f96d6147Sshadchin int
897f96d6147Sshadchin pms_sec_enable(void *v)
898f96d6147Sshadchin {
899f96d6147Sshadchin 	struct pms_softc *sc = v;
9000649de23Smpi 	int rv;
901f96d6147Sshadchin 
9020649de23Smpi 	rw_enter_write(&sc->sc_state_lock);
9030649de23Smpi 	rv = pms_change_state(sc, PMS_STATE_ENABLED, PMS_DEV_SECONDARY);
9040649de23Smpi 	rw_exit_write(&sc->sc_state_lock);
9050649de23Smpi 
9060649de23Smpi 	return (rv);
907f96d6147Sshadchin }
908f96d6147Sshadchin 
909f96d6147Sshadchin void
910f96d6147Sshadchin pms_sec_disable(void *v)
911f96d6147Sshadchin {
912f96d6147Sshadchin 	struct pms_softc *sc = v;
913f96d6147Sshadchin 
9140649de23Smpi 	rw_enter_write(&sc->sc_state_lock);
915f96d6147Sshadchin 	pms_change_state(sc, PMS_STATE_DISABLED, PMS_DEV_SECONDARY);
9160649de23Smpi 	rw_exit_write(&sc->sc_state_lock);
917f96d6147Sshadchin }
918f96d6147Sshadchin 
919f96d6147Sshadchin int
920f96d6147Sshadchin pms_sec_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
921f96d6147Sshadchin {
922f96d6147Sshadchin 	switch (cmd) {
923f96d6147Sshadchin 	case WSMOUSEIO_GTYPE:
924f96d6147Sshadchin 		*(u_int *)data = WSMOUSE_TYPE_PS2;
925f96d6147Sshadchin 		break;
926f96d6147Sshadchin 	default:
927f96d6147Sshadchin 		return (-1);
928f96d6147Sshadchin 	}
929f96d6147Sshadchin 	return (0);
930f96d6147Sshadchin }
931f96d6147Sshadchin 
9325c7f814cSmpi #ifdef DIAGNOSTIC
9334f06b9beSbru static inline void
9344f06b9beSbru pms_print_packet(struct pms_softc *sc)
9354f06b9beSbru {
9364f06b9beSbru 	int i, state, size;
9374f06b9beSbru 
9384f06b9beSbru 	state = sc->inputstate;
9394f06b9beSbru 	size = sc->protocol->packetsize;
9404f06b9beSbru 	for (i = 0; i < size; i++)
9414f06b9beSbru 		printf(i == state ? " %02x |" : " %02x", sc->packet[i]);
9424f06b9beSbru }
9435c7f814cSmpi #endif
9444f06b9beSbru 
945b43e75e7Skrw void
946b43e75e7Skrw pmsinput(void *vsc, int data)
9474026eccfSkettenis {
9484026eccfSkettenis 	struct pms_softc *sc = vsc;
9494026eccfSkettenis 
95089d55430Sderaadt 	if (sc->sc_state != PMS_STATE_ENABLED) {
9514026eccfSkettenis 		/* Interrupts are not expected.  Discard the byte. */
9524026eccfSkettenis 		return;
9534026eccfSkettenis 	}
9544026eccfSkettenis 
955b2dcd1e6Sshadchin 	sc->packet[sc->inputstate] = data;
9560fcbbc5fSanton 	pms_reset_detect(sc, data);
957f7577b15Sshadchin 	if (sc->protocol->sync(sc, data)) {
958aaab154dSshadchin #ifdef DIAGNOSTIC
95987f59ec1Santon 		printf("%s: not in sync yet, discard input "
9604f06b9beSbru 		    "(state = %d,",
9614f06b9beSbru 		    DEVNAME(sc), sc->inputstate);
9624f06b9beSbru 		pms_print_packet(sc);
9634f06b9beSbru 		printf(")\n");
964f7577b15Sshadchin #endif
96587f59ec1Santon 
9664026eccfSkettenis 		sc->inputstate = 0;
967f7577b15Sshadchin 		return;
9684026eccfSkettenis 	}
9694026eccfSkettenis 
970b2dcd1e6Sshadchin 	sc->inputstate++;
971f96d6147Sshadchin 
972f7577b15Sshadchin 	if (sc->inputstate != sc->protocol->packetsize)
9734026eccfSkettenis 		return;
974f7577b15Sshadchin 
975f7577b15Sshadchin 	sc->inputstate = 0;
976b2dcd1e6Sshadchin 	sc->protocol->proc(sc);
9774026eccfSkettenis }
978aaab154dSshadchin 
979aaab154dSshadchin int
9800329de75Smglocker synaptics_set_mode(struct pms_softc *sc, int mode, int rate)
981aaab154dSshadchin {
982aaab154dSshadchin 	struct synaptics_softc *syn = sc->synaptics;
983aaab154dSshadchin 
984aaab154dSshadchin 	if (pms_spec_cmd(sc, mode) ||
9850329de75Smglocker 	    pms_set_rate(sc, rate == 0 ? SYNAPTICS_CMD_SET_MODE : rate))
986aaab154dSshadchin 		return (-1);
987aaab154dSshadchin 
9887ecc5202Smglocker 	/*
9897ecc5202Smglocker 	 * Make sure that the set mode command has finished.
9907ecc5202Smglocker 	 * Otherwise enabling the device before that will make it fail.
9917ecc5202Smglocker 	 */
9927ecc5202Smglocker 	delay(10000);
9937ecc5202Smglocker 
9940329de75Smglocker 	if (rate == 0)
995aaab154dSshadchin 		syn->mode = mode;
996aaab154dSshadchin 
997aaab154dSshadchin 	return (0);
998aaab154dSshadchin }
999aaab154dSshadchin 
1000aaab154dSshadchin int
1001aaab154dSshadchin synaptics_query(struct pms_softc *sc, int query, int *val)
1002aaab154dSshadchin {
1003aaab154dSshadchin 	u_char resp[3];
1004aaab154dSshadchin 
1005aaab154dSshadchin 	if (pms_spec_cmd(sc, query) ||
1006aaab154dSshadchin 	    pms_get_status(sc, resp))
1007aaab154dSshadchin 		return (-1);
1008aaab154dSshadchin 
1009aaab154dSshadchin 	if (val)
1010aaab154dSshadchin 		*val = (resp[0] << 16) | (resp[1] << 8) | resp[2];
1011aaab154dSshadchin 
1012aaab154dSshadchin 	return (0);
1013aaab154dSshadchin }
1014aaab154dSshadchin 
1015aaab154dSshadchin int
1016aaab154dSshadchin synaptics_get_hwinfo(struct pms_softc *sc)
1017aaab154dSshadchin {
1018aaab154dSshadchin 	struct synaptics_softc *syn = sc->synaptics;
1019dc727cfdSbru 	struct wsmousehw *hw;
1020ceee28bcSbru 	int resolution = 0, max_coords = 0, min_coords = 0;
1021dc727cfdSbru 
1022dc727cfdSbru 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
1023aaab154dSshadchin 
1024aaab154dSshadchin 	if (synaptics_query(sc, SYNAPTICS_QUE_IDENTIFY, &syn->identify))
1025aaab154dSshadchin 		return (-1);
1026aaab154dSshadchin 	if (synaptics_query(sc, SYNAPTICS_QUE_CAPABILITIES,
1027aaab154dSshadchin 	    &syn->capabilities))
1028aaab154dSshadchin 		return (-1);
1029aaab154dSshadchin 	if (synaptics_query(sc, SYNAPTICS_QUE_MODEL, &syn->model))
1030aaab154dSshadchin 		return (-1);
1031aaab154dSshadchin 	if ((SYNAPTICS_CAP_EXTENDED_QUERIES(syn->capabilities) >= 1) &&
1032aaab154dSshadchin 	    synaptics_query(sc, SYNAPTICS_QUE_EXT_MODEL, &syn->ext_model))
1033aaab154dSshadchin 		return (-1);
1034aaab154dSshadchin 	if ((SYNAPTICS_CAP_EXTENDED_QUERIES(syn->capabilities) >= 4) &&
1035aaab154dSshadchin 	    synaptics_query(sc, SYNAPTICS_QUE_EXT_CAPABILITIES,
1036aaab154dSshadchin 		&syn->ext_capabilities))
1037aaab154dSshadchin 		return (-1);
1038aaab154dSshadchin 	if ((SYNAPTICS_ID_MAJOR(syn->identify) >= 4) &&
103932aca7fdSbru 	    synaptics_query(sc, SYNAPTICS_QUE_RESOLUTION, &resolution))
1040aaab154dSshadchin 		return (-1);
1041aaab154dSshadchin 	if ((SYNAPTICS_CAP_EXTENDED_QUERIES(syn->capabilities) >= 5) &&
104232aca7fdSbru 	    (syn->ext_capabilities & SYNAPTICS_EXT_CAP_MAX_COORDS) &&
104332aca7fdSbru 	    synaptics_query(sc, SYNAPTICS_QUE_EXT_MAX_COORDS, &max_coords))
1044aaab154dSshadchin 		return (-1);
104532aca7fdSbru 	if ((SYNAPTICS_CAP_EXTENDED_QUERIES(syn->capabilities) >= 7 ||
104632aca7fdSbru 	    SYNAPTICS_ID_FULL(syn->identify) == 0x801) &&
104732aca7fdSbru 	    (syn->ext_capabilities & SYNAPTICS_EXT_CAP_MIN_COORDS) &&
104832aca7fdSbru 	    synaptics_query(sc, SYNAPTICS_QUE_EXT_MIN_COORDS, &min_coords))
104932aca7fdSbru 		return (-1);
105032aca7fdSbru 
1051dc9b7ba2Sjsg 	if (SYNAPTICS_ID_FULL(syn->identify) >= 0x705) {
1052dc9b7ba2Sjsg 		if (synaptics_query(sc, SYNAPTICS_QUE_MODES, &syn->modes))
1053dc9b7ba2Sjsg 			return (-1);
1054dc9b7ba2Sjsg 		if ((syn->modes & SYNAPTICS_EXT2_CAP) &&
1055dc9b7ba2Sjsg 		    synaptics_query(sc, SYNAPTICS_QUE_EXT2_CAPABILITIES,
1056dc9b7ba2Sjsg 		    &syn->ext2_capabilities))
1057dc9b7ba2Sjsg 			return (-1);
1058dc9b7ba2Sjsg 	}
1059aaab154dSshadchin 
1060dc727cfdSbru 	if ((syn->ext_capabilities & SYNAPTICS_EXT_CAP_CLICKPAD) &&
1061dc727cfdSbru 	    !(syn->ext2_capabilities & SYNAPTICS_EXT2_CAP_BUTTONS_STICK)
1062dc727cfdSbru 	    && mouse_has_softbtn)
1063dc727cfdSbru 		hw->type = WSMOUSE_TYPE_SYNAP_SBTN;
1064dc727cfdSbru 	else
1065dc727cfdSbru 		hw->type = WSMOUSE_TYPE_SYNAPTICS;
1066dc727cfdSbru 
1067dc727cfdSbru 	hw->hw_type = (syn->ext_capabilities & SYNAPTICS_EXT_CAP_CLICKPAD)
1068dc727cfdSbru 	    ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD;
1069dc727cfdSbru 
107032aca7fdSbru 	if (resolution & SYNAPTICS_RESOLUTION_VALID) {
107132aca7fdSbru 		hw->h_res = SYNAPTICS_RESOLUTION_X(resolution);
107232aca7fdSbru 		hw->v_res = SYNAPTICS_RESOLUTION_Y(resolution);
1073a7511290Sbru 	}
107432aca7fdSbru 
107532aca7fdSbru 	hw->x_min = (min_coords ?
107632aca7fdSbru 	    SYNAPTICS_X_LIMIT(min_coords) : SYNAPTICS_XMIN_BEZEL);
107732aca7fdSbru 	hw->y_min = (min_coords ?
107832aca7fdSbru 	    SYNAPTICS_Y_LIMIT(min_coords) : SYNAPTICS_YMIN_BEZEL);
107932aca7fdSbru 	hw->x_max = (max_coords ?
108032aca7fdSbru 	    SYNAPTICS_X_LIMIT(max_coords) : SYNAPTICS_XMAX_BEZEL);
108132aca7fdSbru 	hw->y_max = (max_coords ?
108232aca7fdSbru 	    SYNAPTICS_Y_LIMIT(max_coords) : SYNAPTICS_YMAX_BEZEL);
1083aaab154dSshadchin 
10843eb7743cSbru 	if ((syn->capabilities & SYNAPTICS_CAP_MULTIFINGER) ||
10853eb7743cSbru 	    SYNAPTICS_SUPPORTS_AGM(syn->ext_capabilities))
1086dc727cfdSbru 		hw->contacts_max = SYNAPTICS_MAX_FINGERS;
10873eb7743cSbru 	else
10883eb7743cSbru 		hw->contacts_max = 1;
1089dc727cfdSbru 
1090dc9b7ba2Sjsg 	syn->sec_buttons = 0;
1091dc9b7ba2Sjsg 
1092aaab154dSshadchin 	if (SYNAPTICS_EXT_MODEL_BUTTONS(syn->ext_model) > 8)
1093aaab154dSshadchin 		syn->ext_model &= ~0xf000;
1094aaab154dSshadchin 
1095f96998ceSmpi 	if ((syn->model & SYNAPTICS_MODEL_NEWABS) == 0) {
1096f96998ceSmpi 		printf("%s: don't support Synaptics OLDABS\n", DEVNAME(sc));
1097f96998ceSmpi 		return (-1);
1098f96998ceSmpi 	}
1099f96998ceSmpi 
110043a3c8cdSshadchin 	if ((SYNAPTICS_ID_MAJOR(syn->identify) == 5) &&
110143a3c8cdSshadchin 	    (SYNAPTICS_ID_MINOR(syn->identify) == 9))
110243a3c8cdSshadchin 		syn->mask = SYNAPTICS_MASK_NEWABS_RELAXED;
110343a3c8cdSshadchin 	else
110443a3c8cdSshadchin 		syn->mask = SYNAPTICS_MASK_NEWABS_STRICT;
110543a3c8cdSshadchin 
1106aaab154dSshadchin 	return (0);
1107aaab154dSshadchin }
1108aaab154dSshadchin 
1109aaab154dSshadchin void
1110f96d6147Sshadchin synaptics_sec_proc(struct pms_softc *sc)
1111aaab154dSshadchin {
1112dc9b7ba2Sjsg 	struct synaptics_softc *syn = sc->synaptics;
1113aaab154dSshadchin 	u_int buttons;
1114aaab154dSshadchin 	int dx, dy;
1115aaab154dSshadchin 
1116aaab154dSshadchin 	if ((sc->sc_dev_enable & PMS_DEV_SECONDARY) == 0)
1117aaab154dSshadchin 		return;
1118aaab154dSshadchin 
1119aaab154dSshadchin 	buttons = butmap[sc->packet[1] & PMS_PS2_BUTTONSMASK];
1120dc9b7ba2Sjsg 	buttons |= syn->sec_buttons;
1121aaab154dSshadchin 	dx = (sc->packet[1] & PMS_PS2_XNEG) ?
1122aaab154dSshadchin 	    (int)sc->packet[4] - 256 : sc->packet[4];
1123aaab154dSshadchin 	dy = (sc->packet[1] & PMS_PS2_YNEG) ?
1124aaab154dSshadchin 	    (int)sc->packet[5] - 256 : sc->packet[5];
1125aaab154dSshadchin 
11265ad8c6a9Sbru 	WSMOUSE_INPUT(sc->sc_sec_wsmousedev, buttons, dx, dy, 0, 0);
1127aaab154dSshadchin }
1128aaab154dSshadchin 
1129aaab154dSshadchin int
1130748e7d13Sstsp synaptics_knock(struct pms_softc *sc)
1131aaab154dSshadchin {
1132aaab154dSshadchin 	u_char resp[3];
1133aaab154dSshadchin 
1134aaab154dSshadchin 	if (pms_set_resolution(sc, 0) ||
1135aaab154dSshadchin 	    pms_set_resolution(sc, 0) ||
1136aaab154dSshadchin 	    pms_set_resolution(sc, 0) ||
1137aaab154dSshadchin 	    pms_set_resolution(sc, 0) ||
1138aaab154dSshadchin 	    pms_get_status(sc, resp) ||
1139aaab154dSshadchin 	    resp[1] != SYNAPTICS_ID_MAGIC)
1140748e7d13Sstsp 		return (-1);
1141748e7d13Sstsp 
1142748e7d13Sstsp 	return (0);
1143748e7d13Sstsp }
1144748e7d13Sstsp 
1145748e7d13Sstsp int
1146748e7d13Sstsp pms_enable_synaptics(struct pms_softc *sc)
1147748e7d13Sstsp {
1148748e7d13Sstsp 	struct synaptics_softc *syn = sc->synaptics;
1149748e7d13Sstsp 	struct wsmousedev_attach_args a;
1150748e7d13Sstsp 	int mode, i;
1151748e7d13Sstsp 
1152748e7d13Sstsp 	if (synaptics_knock(sc)) {
1153748e7d13Sstsp 		if (sc->synaptics == NULL)
11546138841fSshadchin 			goto err;
1155748e7d13Sstsp 		/*
1156748e7d13Sstsp 		 * Some synaptics touchpads don't resume quickly.
1157748e7d13Sstsp 		 * Retry a few times.
1158748e7d13Sstsp 		 */
1159748e7d13Sstsp 		for (i = 10; i > 0; --i) {
1160748e7d13Sstsp 			printf("%s: device not resuming, retrying\n",
1161748e7d13Sstsp 			    DEVNAME(sc));
1162748e7d13Sstsp 			pms_reset(sc);
1163748e7d13Sstsp 			if (synaptics_knock(sc) == 0)
1164748e7d13Sstsp 				break;
1165748e7d13Sstsp 			delay(100000);
1166748e7d13Sstsp 		}
1167748e7d13Sstsp 		if (i == 0) {
1168748e7d13Sstsp 			printf("%s: lost device\n", DEVNAME(sc));
1169748e7d13Sstsp 			goto err;
1170748e7d13Sstsp 		}
1171748e7d13Sstsp 	}
1172aaab154dSshadchin 
1173aaab154dSshadchin 	if (sc->synaptics == NULL) {
1174aaab154dSshadchin 		sc->synaptics = syn = malloc(sizeof(struct synaptics_softc),
1175aaab154dSshadchin 		    M_DEVBUF, M_WAITOK | M_ZERO);
1176aaab154dSshadchin 		if (syn == NULL) {
1177aaab154dSshadchin 			printf("%s: synaptics: not enough memory\n",
1178aaab154dSshadchin 			    DEVNAME(sc));
11796138841fSshadchin 			goto err;
1180aaab154dSshadchin 		}
1181aaab154dSshadchin 
1182f96998ceSmpi 		if (synaptics_get_hwinfo(sc)) {
1183a31dcc2eSderaadt 			free(sc->synaptics, M_DEVBUF,
1184a31dcc2eSderaadt 			    sizeof(struct synaptics_softc));
1185f96998ceSmpi 			sc->synaptics = NULL;
11866138841fSshadchin 			goto err;
1187aaab154dSshadchin 		}
1188aaab154dSshadchin 
1189aaab154dSshadchin 		/* enable pass-through PS/2 port if supported */
1190aaab154dSshadchin 		if (syn->capabilities & SYNAPTICS_CAP_PASSTHROUGH) {
1191f96d6147Sshadchin 			a.accessops = &pms_sec_accessops;
1192aaab154dSshadchin 			a.accesscookie = sc;
1193f96d6147Sshadchin 			sc->sc_sec_wsmousedev = config_found((void *)sc, &a,
1194aaab154dSshadchin 			    wsmousedevprint);
1195aaab154dSshadchin 		}
1196aaab154dSshadchin 
11973d300623Smpi 		if (wsmouse_configure(sc->sc_wsmousedev, synaptics_params,
11983d300623Smpi 		    nitems(synaptics_params)))
1199dc727cfdSbru 			goto err;
1200aaab154dSshadchin 
1201db8ad24eSbru 		printf("%s: Synaptics %s, firmware %d.%d, "
1202db8ad24eSbru 		    "0x%x 0x%x 0x%x 0x%x 0x%x\n",
120356fa05d9Sbru 		    DEVNAME(sc),
1204aaab154dSshadchin 		    (syn->ext_capabilities & SYNAPTICS_EXT_CAP_CLICKPAD ?
1205aaab154dSshadchin 			"clickpad" : "touchpad"),
1206aaab154dSshadchin 		    SYNAPTICS_ID_MAJOR(syn->identify),
120756fa05d9Sbru 		    SYNAPTICS_ID_MINOR(syn->identify),
1208db8ad24eSbru 		    syn->model, syn->ext_model, syn->modes,
1209db8ad24eSbru 		    syn->capabilities, syn->ext_capabilities);
1210aaab154dSshadchin 	}
1211aaab154dSshadchin 
1212db8ad24eSbru 	/*
1213db8ad24eSbru 	 * Enable absolute mode, plain W-mode and "advanced gesture mode"
1214db8ad24eSbru 	 * (AGM), if possible.  AGM, which seems to be a prerequisite for the
1215db8ad24eSbru 	 * extended W-mode, might not always be necessary here, but at least
1216db8ad24eSbru 	 * some older Synaptics models do not report finger counts without it.
1217db8ad24eSbru 	 */
1218aaab154dSshadchin 	mode = SYNAPTICS_ABSOLUTE_MODE | SYNAPTICS_HIGH_RATE;
1219aaab154dSshadchin 	if (syn->capabilities & SYNAPTICS_CAP_EXTENDED)
1220aaab154dSshadchin 		mode |= SYNAPTICS_W_MODE;
1221db8ad24eSbru 	else if (SYNAPTICS_ID_MAJOR(syn->identify) >= 4)
1222db8ad24eSbru 		mode |= SYNAPTICS_DISABLE_GESTURE;
12230329de75Smglocker 	if (synaptics_set_mode(sc, mode, 0))
12246138841fSshadchin 		goto err;
1225aaab154dSshadchin 
1226db8ad24eSbru 	if (SYNAPTICS_SUPPORTS_AGM(syn->ext_capabilities) &&
12270329de75Smglocker 	    synaptics_set_mode(sc, SYNAPTICS_QUE_MODEL,
12280329de75Smglocker 	        SYNAPTICS_CMD_SET_ADV_GESTURE_MODE))
12296138841fSshadchin 		goto err;
1230aaab154dSshadchin 
1231aaab154dSshadchin 	return (1);
12326138841fSshadchin 
12336138841fSshadchin err:
12346138841fSshadchin 	pms_reset(sc);
12356138841fSshadchin 
12366138841fSshadchin 	return (0);
1237aaab154dSshadchin }
1238aaab154dSshadchin 
1239aaab154dSshadchin int
1240aaab154dSshadchin pms_ioctl_synaptics(struct pms_softc *sc, u_long cmd, caddr_t data, int flag,
1241aaab154dSshadchin     struct proc *p)
1242aaab154dSshadchin {
1243aaab154dSshadchin 	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
1244dc727cfdSbru 	struct wsmousehw *hw;
1245aaab154dSshadchin 	int wsmode;
1246aaab154dSshadchin 
1247dc727cfdSbru 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
1248aaab154dSshadchin 	switch (cmd) {
1249aaab154dSshadchin 	case WSMOUSEIO_GTYPE:
1250dc727cfdSbru 		*(u_int *)data = hw->type;
1251aaab154dSshadchin 		break;
1252aaab154dSshadchin 	case WSMOUSEIO_GCALIBCOORDS:
1253dc727cfdSbru 		wsmc->minx = hw->x_min;
1254dc727cfdSbru 		wsmc->maxx = hw->x_max;
1255dc727cfdSbru 		wsmc->miny = hw->y_min;
1256dc727cfdSbru 		wsmc->maxy = hw->y_max;
1257aaab154dSshadchin 		wsmc->swapxy = 0;
1258dc727cfdSbru 		wsmc->resx = hw->h_res;
1259dc727cfdSbru 		wsmc->resy = hw->v_res;
1260aaab154dSshadchin 		break;
1261aaab154dSshadchin 	case WSMOUSEIO_SETMODE:
1262aaab154dSshadchin 		wsmode = *(u_int *)data;
1263aaab154dSshadchin 		if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE)
1264aaab154dSshadchin 			return (EINVAL);
1265dc727cfdSbru 		wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
1266aaab154dSshadchin 		break;
1267aaab154dSshadchin 	default:
1268aaab154dSshadchin 		return (-1);
1269aaab154dSshadchin 	}
1270aaab154dSshadchin 	return (0);
1271aaab154dSshadchin }
1272aaab154dSshadchin 
1273aaab154dSshadchin int
1274aaab154dSshadchin pms_sync_synaptics(struct pms_softc *sc, int data)
1275aaab154dSshadchin {
127643a3c8cdSshadchin 	struct synaptics_softc *syn = sc->synaptics;
127743a3c8cdSshadchin 
1278aaab154dSshadchin 	switch (sc->inputstate) {
1279aaab154dSshadchin 	case 0:
128043a3c8cdSshadchin 		if ((data & syn->mask) != SYNAPTICS_VALID_NEWABS_FIRST)
1281aaab154dSshadchin 			return (-1);
1282aaab154dSshadchin 		break;
1283aaab154dSshadchin 	case 3:
128443a3c8cdSshadchin 		if ((data & syn->mask) != SYNAPTICS_VALID_NEWABS_NEXT)
1285aaab154dSshadchin 			return (-1);
1286aaab154dSshadchin 		break;
1287aaab154dSshadchin 	}
1288aaab154dSshadchin 
1289aaab154dSshadchin 	return (0);
1290aaab154dSshadchin }
1291aaab154dSshadchin 
1292aaab154dSshadchin void
1293aaab154dSshadchin pms_proc_synaptics(struct pms_softc *sc)
1294aaab154dSshadchin {
1295aaab154dSshadchin 	struct synaptics_softc *syn = sc->synaptics;
1296aaab154dSshadchin 	u_int buttons;
1297dc727cfdSbru 	int x, y, z, w, fingerwidth;
1298aaab154dSshadchin 
1299aaab154dSshadchin 	w = ((sc->packet[0] & 0x30) >> 2) | ((sc->packet[0] & 0x04) >> 1) |
1300aaab154dSshadchin 	    ((sc->packet[3] & 0x04) >> 2);
1301a7511290Sbru 	z = sc->packet[2];
1302aaab154dSshadchin 
1303a7511290Sbru 	if ((syn->capabilities & SYNAPTICS_CAP_EXTENDED) == 0) {
13049274e18dSkrw 		/*
1305a7511290Sbru 		 * Emulate W mode for models that don't provide it. Bit 3
1306a7511290Sbru 		 * of the w-input signals a touch ("finger"), Bit 2 and
1307a7511290Sbru 		 * the "gesture" bits 1-0 can be ignored.
13089274e18dSkrw 		 */
1309a7511290Sbru 		if (w & 8)
13109274e18dSkrw 			w = 4;
1311a7511290Sbru 		else
1312a7511290Sbru 			z = w = 0;
13139274e18dSkrw 	}
13149274e18dSkrw 
1315a7511290Sbru 
1316db8ad24eSbru 	if (w == 3) {
1317db8ad24eSbru 		if (syn->capabilities & SYNAPTICS_CAP_PASSTHROUGH)
1318f96d6147Sshadchin 			synaptics_sec_proc(sc);
1319aaab154dSshadchin 		return;
1320aaab154dSshadchin 	}
1321aaab154dSshadchin 
1322aaab154dSshadchin 	if ((sc->sc_dev_enable & PMS_DEV_PRIMARY) == 0)
1323aaab154dSshadchin 		return;
1324aaab154dSshadchin 
1325db8ad24eSbru 	if (w == 2)
1326db8ad24eSbru 		return;	/* EW-mode packets are not expected here. */
1327aaab154dSshadchin 
1328aaab154dSshadchin 	x = ((sc->packet[3] & 0x10) << 8) | ((sc->packet[1] & 0x0f) << 8) |
1329aaab154dSshadchin 	    sc->packet[4];
1330aaab154dSshadchin 	y = ((sc->packet[3] & 0x20) << 7) | ((sc->packet[1] & 0xf0) << 4) |
1331aaab154dSshadchin 	    sc->packet[5];
1332aaab154dSshadchin 
1333aaab154dSshadchin 	buttons = ((sc->packet[0] & sc->packet[3]) & 0x01) ?
1334aaab154dSshadchin 	    WSMOUSE_BUTTON(1) : 0;
1335aaab154dSshadchin 	buttons |= ((sc->packet[0] & sc->packet[3]) & 0x02) ?
1336aaab154dSshadchin 	    WSMOUSE_BUTTON(3) : 0;
1337aaab154dSshadchin 
1338aaab154dSshadchin 	if (syn->ext_capabilities & SYNAPTICS_EXT_CAP_CLICKPAD) {
1339aaab154dSshadchin 		buttons |= ((sc->packet[0] ^ sc->packet[3]) & 0x01) ?
1340aaab154dSshadchin 		    WSMOUSE_BUTTON(1) : 0;
1341aaab154dSshadchin 	} else if (syn->capabilities & SYNAPTICS_CAP_MIDDLE_BUTTON) {
1342aaab154dSshadchin 		buttons |= ((sc->packet[0] ^ sc->packet[3]) & 0x01) ?
1343aaab154dSshadchin 		    WSMOUSE_BUTTON(2) : 0;
1344aaab154dSshadchin 	}
1345aaab154dSshadchin 
1346aaab154dSshadchin 	if (syn->capabilities & SYNAPTICS_CAP_FOUR_BUTTON) {
1347aaab154dSshadchin 		buttons |= ((sc->packet[0] ^ sc->packet[3]) & 0x01) ?
1348aaab154dSshadchin 		    WSMOUSE_BUTTON(4) : 0;
1349aaab154dSshadchin 		buttons |= ((sc->packet[0] ^ sc->packet[3]) & 0x02) ?
1350aaab154dSshadchin 		    WSMOUSE_BUTTON(5) : 0;
1351aaab154dSshadchin 	} else if (SYNAPTICS_EXT_MODEL_BUTTONS(syn->ext_model) &&
1352aaab154dSshadchin 	    ((sc->packet[0] ^ sc->packet[3]) & 0x02)) {
1353dc9b7ba2Sjsg 		if (syn->ext2_capabilities & SYNAPTICS_EXT2_CAP_BUTTONS_STICK) {
1354dc9b7ba2Sjsg 			/*
1355dc9b7ba2Sjsg 			 * Trackstick buttons on this machine are wired to the
1356dc9b7ba2Sjsg 			 * trackpad as extra buttons, so route the event
1357dc9b7ba2Sjsg 			 * through the trackstick interface as normal buttons
1358dc9b7ba2Sjsg 			 */
1359dc9b7ba2Sjsg 			syn->sec_buttons =
1360dc9b7ba2Sjsg 			    (sc->packet[4] & 0x01) ? WSMOUSE_BUTTON(1) : 0;
1361dc9b7ba2Sjsg 			syn->sec_buttons |=
1362dc9b7ba2Sjsg 			    (sc->packet[5] & 0x01) ? WSMOUSE_BUTTON(3) : 0;
1363dc9b7ba2Sjsg 			syn->sec_buttons |=
1364dc9b7ba2Sjsg 			    (sc->packet[4] & 0x02) ? WSMOUSE_BUTTON(2) : 0;
13655ad8c6a9Sbru 			wsmouse_buttons(
13665ad8c6a9Sbru 			    sc->sc_sec_wsmousedev, syn->sec_buttons);
13675ad8c6a9Sbru 			wsmouse_input_sync(sc->sc_sec_wsmousedev);
1368dc9b7ba2Sjsg 			return;
1369dc9b7ba2Sjsg 		}
1370dc9b7ba2Sjsg 
1371aaab154dSshadchin 		buttons |= (sc->packet[4] & 0x01) ? WSMOUSE_BUTTON(6) : 0;
1372aaab154dSshadchin 		buttons |= (sc->packet[5] & 0x01) ? WSMOUSE_BUTTON(7) : 0;
1373aaab154dSshadchin 		buttons |= (sc->packet[4] & 0x02) ? WSMOUSE_BUTTON(8) : 0;
1374aaab154dSshadchin 		buttons |= (sc->packet[5] & 0x02) ? WSMOUSE_BUTTON(9) : 0;
1375aaab154dSshadchin 		buttons |= (sc->packet[4] & 0x04) ? WSMOUSE_BUTTON(10) : 0;
1376aaab154dSshadchin 		buttons |= (sc->packet[5] & 0x04) ? WSMOUSE_BUTTON(11) : 0;
1377aaab154dSshadchin 		buttons |= (sc->packet[4] & 0x08) ? WSMOUSE_BUTTON(12) : 0;
1378aaab154dSshadchin 		buttons |= (sc->packet[5] & 0x08) ? WSMOUSE_BUTTON(13) : 0;
1379aaab154dSshadchin 		x &= ~0x0f;
1380aaab154dSshadchin 		y &= ~0x0f;
1381aaab154dSshadchin 	}
1382aaab154dSshadchin 
13835ad8c6a9Sbru 	if (z) {
1384dc727cfdSbru 		fingerwidth = max(w, 4);
13855ad8c6a9Sbru 		w = (w < 2 ? w + 2 : 1);
13865ad8c6a9Sbru 	} else {
1387dc727cfdSbru 		fingerwidth = 0;
1388dc727cfdSbru 		w = 0;
13895ad8c6a9Sbru 	}
1390dc727cfdSbru 	wsmouse_set(sc->sc_wsmousedev, WSMOUSE_TOUCH_WIDTH, fingerwidth, 0);
13915ad8c6a9Sbru 	WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons, x, y, z, w);
1392aaab154dSshadchin }
1393aaab154dSshadchin 
1394aaab154dSshadchin void
1395aaab154dSshadchin pms_disable_synaptics(struct pms_softc *sc)
1396aaab154dSshadchin {
1397aaab154dSshadchin 	struct synaptics_softc *syn = sc->synaptics;
1398aaab154dSshadchin 
1399aaab154dSshadchin 	if (syn->capabilities & SYNAPTICS_CAP_SLEEP)
1400aaab154dSshadchin 		synaptics_set_mode(sc, SYNAPTICS_SLEEP_MODE |
14010329de75Smglocker 		    SYNAPTICS_DISABLE_GESTURE, 0);
1402aaab154dSshadchin }
14037327fb1eSmpi 
14047327fb1eSmpi int
1405b2dcd1e6Sshadchin alps_sec_proc(struct pms_softc *sc)
1406b2dcd1e6Sshadchin {
1407b2dcd1e6Sshadchin 	struct alps_softc *alps = sc->alps;
1408b2dcd1e6Sshadchin 	int dx, dy, pos = 0;
1409b2dcd1e6Sshadchin 
1410b2dcd1e6Sshadchin 	if ((sc->packet[0] & PMS_ALPS_PS2_MASK) == PMS_ALPS_PS2_VALID) {
1411b2dcd1e6Sshadchin 		/*
1412b2dcd1e6Sshadchin 		 * We need to keep buttons states because interleaved
1413b2dcd1e6Sshadchin 		 * packets only signalize x/y movements.
1414b2dcd1e6Sshadchin 		 */
1415b2dcd1e6Sshadchin 		alps->sec_buttons = butmap[sc->packet[0] & PMS_PS2_BUTTONSMASK];
1416b2dcd1e6Sshadchin 	} else if ((sc->packet[3] & PMS_ALPS_INTERLEAVED_MASK) ==
1417b2dcd1e6Sshadchin 	    PMS_ALPS_INTERLEAVED_VALID) {
1418b2dcd1e6Sshadchin 		sc->inputstate = 3;
1419b2dcd1e6Sshadchin 		pos = 3;
1420b2dcd1e6Sshadchin 	} else {
1421b2dcd1e6Sshadchin 		return (0);
1422b2dcd1e6Sshadchin 	}
1423b2dcd1e6Sshadchin 
1424b2dcd1e6Sshadchin 	if ((sc->sc_dev_enable & PMS_DEV_SECONDARY) == 0)
1425b2dcd1e6Sshadchin 		return (1);
1426b2dcd1e6Sshadchin 
1427b2dcd1e6Sshadchin 	dx = (sc->packet[pos] & PMS_PS2_XNEG) ?
1428b2dcd1e6Sshadchin 	    (int)sc->packet[pos + 1] - 256 : sc->packet[pos + 1];
1429b2dcd1e6Sshadchin 	dy = (sc->packet[pos] & PMS_PS2_YNEG) ?
1430b2dcd1e6Sshadchin 	    (int)sc->packet[pos + 2] - 256 : sc->packet[pos + 2];
1431b2dcd1e6Sshadchin 
14325ad8c6a9Sbru 	WSMOUSE_INPUT(sc->sc_sec_wsmousedev, alps->sec_buttons, dx, dy, 0, 0);
1433b2dcd1e6Sshadchin 
1434b2dcd1e6Sshadchin 	return (1);
1435b2dcd1e6Sshadchin }
1436b2dcd1e6Sshadchin 
1437b2dcd1e6Sshadchin int
14387327fb1eSmpi alps_get_hwinfo(struct pms_softc *sc)
14397327fb1eSmpi {
14407327fb1eSmpi 	struct alps_softc *alps = sc->alps;
14417327fb1eSmpi 	u_char resp[3];
14427327fb1eSmpi 	int i;
1443995fbf01Sbru 	struct wsmousehw *hw;
14447327fb1eSmpi 
14457327fb1eSmpi 	if (pms_set_resolution(sc, 0) ||
14467327fb1eSmpi 	    pms_set_scaling(sc, 2) ||
14477327fb1eSmpi 	    pms_set_scaling(sc, 2) ||
14487327fb1eSmpi 	    pms_set_scaling(sc, 2) ||
14497327fb1eSmpi 	    pms_get_status(sc, resp)) {
14507327fb1eSmpi 		DPRINTF("%s: alps: model query error\n", DEVNAME(sc));
14517327fb1eSmpi 		return (-1);
14527327fb1eSmpi 	}
14537327fb1eSmpi 
14547327fb1eSmpi 	alps->version = (resp[0] << 8) | (resp[1] << 4) | (resp[2] / 20 + 1);
14557327fb1eSmpi 
14567327fb1eSmpi 	for (i = 0; i < nitems(alps_models); i++)
14577327fb1eSmpi 		if (alps->version == alps_models[i].version) {
14587327fb1eSmpi 			alps->model = alps_models[i].model;
1459dd24e85bSmpi 			alps->mask = alps_models[i].mask;
1460995fbf01Sbru 
1461995fbf01Sbru 			hw = wsmouse_get_hw(sc->sc_wsmousedev);
1462995fbf01Sbru 			hw->type = WSMOUSE_TYPE_ALPS;
1463995fbf01Sbru 			hw->hw_type = WSMOUSEHW_TOUCHPAD;
1464995fbf01Sbru 			hw->x_min = ALPS_XMIN_BEZEL;
1465995fbf01Sbru 			hw->y_min = ALPS_YMIN_BEZEL;
1466995fbf01Sbru 			hw->x_max = ALPS_XMAX_BEZEL;
1467995fbf01Sbru 			hw->y_max = ALPS_YMAX_BEZEL;
1468529d4085Sbru 			hw->contacts_max = 1;
1469995fbf01Sbru 
14707327fb1eSmpi 			return (0);
14717327fb1eSmpi 		}
14727327fb1eSmpi 
14737327fb1eSmpi 	return (-1);
14747327fb1eSmpi }
14757327fb1eSmpi 
14767327fb1eSmpi int
14777327fb1eSmpi pms_enable_alps(struct pms_softc *sc)
14787327fb1eSmpi {
14797327fb1eSmpi 	struct alps_softc *alps = sc->alps;
1480419d60bbSmpi 	struct wsmousedev_attach_args a;
14817327fb1eSmpi 	u_char resp[3];
14827327fb1eSmpi 
14837327fb1eSmpi 	if (pms_set_resolution(sc, 0) ||
14847327fb1eSmpi 	    pms_set_scaling(sc, 1) ||
14857327fb1eSmpi 	    pms_set_scaling(sc, 1) ||
14867327fb1eSmpi 	    pms_set_scaling(sc, 1) ||
14877327fb1eSmpi 	    pms_get_status(sc, resp) ||
14887327fb1eSmpi 	    resp[0] != PMS_ALPS_MAGIC1 ||
14897327fb1eSmpi 	    resp[1] != PMS_ALPS_MAGIC2 ||
1490fa7039d6Stobias 	    (resp[2] != PMS_ALPS_MAGIC3_1 && resp[2] != PMS_ALPS_MAGIC3_2 &&
1491fa7039d6Stobias 	    resp[2] != PMS_ALPS_MAGIC3_3))
14926138841fSshadchin 		goto err;
14937327fb1eSmpi 
14947327fb1eSmpi 	if (sc->alps == NULL) {
14957327fb1eSmpi 		sc->alps = alps = malloc(sizeof(struct alps_softc),
14967327fb1eSmpi 		    M_DEVBUF, M_WAITOK | M_ZERO);
14977327fb1eSmpi 		if (alps == NULL) {
14987327fb1eSmpi 			printf("%s: alps: not enough memory\n", DEVNAME(sc));
14997327fb1eSmpi 			goto err;
15007327fb1eSmpi 		}
15017327fb1eSmpi 
1502f96998ceSmpi 		if (alps_get_hwinfo(sc)) {
1503a31dcc2eSderaadt 			free(sc->alps, M_DEVBUF, sizeof(struct alps_softc));
1504f96998ceSmpi 			sc->alps = NULL;
15057327fb1eSmpi 			goto err;
1506f96998ceSmpi 		}
15077327fb1eSmpi 
1508995fbf01Sbru 		if (wsmouse_configure(sc->sc_wsmousedev, alps_params,
1509995fbf01Sbru 		    nitems(alps_params))) {
1510995fbf01Sbru 			free(sc->alps, M_DEVBUF, sizeof(struct alps_softc));
1511995fbf01Sbru 			sc->alps = NULL;
1512995fbf01Sbru 			printf("%s: setup failed\n", DEVNAME(sc));
1513995fbf01Sbru 			goto err;
1514995fbf01Sbru 		}
1515995fbf01Sbru 
15167327fb1eSmpi 		printf("%s: ALPS %s, version 0x%04x\n", DEVNAME(sc),
15177327fb1eSmpi 		    (alps->model & ALPS_DUALPOINT ? "Dualpoint" : "Glidepoint"),
15187327fb1eSmpi 		    alps->version);
15197327fb1eSmpi 
1520419d60bbSmpi 
1521419d60bbSmpi 		if (alps->model & ALPS_DUALPOINT) {
1522f96d6147Sshadchin 			a.accessops = &pms_sec_accessops;
1523419d60bbSmpi 			a.accesscookie = sc;
1524f96d6147Sshadchin 			sc->sc_sec_wsmousedev = config_found((void *)sc, &a,
1525419d60bbSmpi 			    wsmousedevprint);
1526419d60bbSmpi 		}
15277327fb1eSmpi 	}
15287327fb1eSmpi 
15297327fb1eSmpi 	if (alps->model == 0)
15307327fb1eSmpi 		goto err;
15317327fb1eSmpi 
15327327fb1eSmpi 	if ((alps->model & ALPS_PASSTHROUGH) &&
15337327fb1eSmpi 	   (pms_set_scaling(sc, 2) ||
15347327fb1eSmpi 	    pms_set_scaling(sc, 2) ||
15357327fb1eSmpi 	    pms_set_scaling(sc, 2) ||
15367327fb1eSmpi 	    pms_dev_disable(sc))) {
15377327fb1eSmpi 		DPRINTF("%s: alps: passthrough on error\n", DEVNAME(sc));
15387327fb1eSmpi 		goto err;
15397327fb1eSmpi 	}
15407327fb1eSmpi 
15417327fb1eSmpi 	if (pms_dev_disable(sc) ||
15427327fb1eSmpi 	    pms_dev_disable(sc) ||
15437327fb1eSmpi 	    pms_set_rate(sc, 0x0a)) {
15447327fb1eSmpi 		DPRINTF("%s: alps: tapping error\n", DEVNAME(sc));
15457327fb1eSmpi 		goto err;
15467327fb1eSmpi 	}
15477327fb1eSmpi 
15487327fb1eSmpi 	if (pms_dev_disable(sc) ||
15497327fb1eSmpi 	    pms_dev_disable(sc) ||
15507327fb1eSmpi 	    pms_dev_disable(sc) ||
15517327fb1eSmpi 	    pms_dev_disable(sc) ||
15527327fb1eSmpi 	    pms_dev_enable(sc)) {
15537327fb1eSmpi 		DPRINTF("%s: alps: absolute mode error\n", DEVNAME(sc));
15547327fb1eSmpi 		goto err;
15557327fb1eSmpi 	}
15567327fb1eSmpi 
15577327fb1eSmpi 	if ((alps->model & ALPS_PASSTHROUGH) &&
15587327fb1eSmpi 	   (pms_set_scaling(sc, 1) ||
15597327fb1eSmpi 	    pms_set_scaling(sc, 1) ||
15607327fb1eSmpi 	    pms_set_scaling(sc, 1) ||
15617327fb1eSmpi 	    pms_dev_disable(sc))) {
15627327fb1eSmpi 		DPRINTF("%s: alps: passthrough off error\n", DEVNAME(sc));
15637327fb1eSmpi 		goto err;
15647327fb1eSmpi 	}
15657327fb1eSmpi 
1566b2dcd1e6Sshadchin 	alps->sec_buttons = 0;
1567b2dcd1e6Sshadchin 
15687327fb1eSmpi 	return (1);
15697327fb1eSmpi 
15707327fb1eSmpi err:
15717327fb1eSmpi 	pms_reset(sc);
15727327fb1eSmpi 
15737327fb1eSmpi 	return (0);
15747327fb1eSmpi }
15757327fb1eSmpi 
15767327fb1eSmpi int
15777327fb1eSmpi pms_ioctl_alps(struct pms_softc *sc, u_long cmd, caddr_t data, int flag,
15787327fb1eSmpi     struct proc *p)
15797327fb1eSmpi {
15807327fb1eSmpi 	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
15817327fb1eSmpi 	int wsmode;
1582995fbf01Sbru 	struct wsmousehw *hw;
15837327fb1eSmpi 
15847327fb1eSmpi 	switch (cmd) {
15857327fb1eSmpi 	case WSMOUSEIO_GTYPE:
15867327fb1eSmpi 		*(u_int *)data = WSMOUSE_TYPE_ALPS;
15877327fb1eSmpi 		break;
15887327fb1eSmpi 	case WSMOUSEIO_GCALIBCOORDS:
1589995fbf01Sbru 		hw = wsmouse_get_hw(sc->sc_wsmousedev);
1590995fbf01Sbru 		wsmc->minx = hw->x_min;
1591995fbf01Sbru 		wsmc->maxx = hw->x_max;
1592995fbf01Sbru 		wsmc->miny = hw->y_min;
1593995fbf01Sbru 		wsmc->maxy = hw->y_max;
15947327fb1eSmpi 		wsmc->swapxy = 0;
15957327fb1eSmpi 		break;
15967327fb1eSmpi 	case WSMOUSEIO_SETMODE:
15977327fb1eSmpi 		wsmode = *(u_int *)data;
15987327fb1eSmpi 		if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE)
15997327fb1eSmpi 			return (EINVAL);
1600995fbf01Sbru 		wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
16017327fb1eSmpi 		break;
16027327fb1eSmpi 	default:
16037327fb1eSmpi 		return (-1);
16047327fb1eSmpi 	}
16057327fb1eSmpi 	return (0);
16067327fb1eSmpi }
16077327fb1eSmpi 
16087327fb1eSmpi int
16097327fb1eSmpi pms_sync_alps(struct pms_softc *sc, int data)
16107327fb1eSmpi {
1611dd24e85bSmpi 	struct alps_softc *alps = sc->alps;
1612dd24e85bSmpi 
1613b2dcd1e6Sshadchin 	if ((alps->model & ALPS_DUALPOINT) &&
1614b2dcd1e6Sshadchin 	    (sc->packet[0] & PMS_ALPS_PS2_MASK) == PMS_ALPS_PS2_VALID) {
1615b2dcd1e6Sshadchin 		if (sc->inputstate == 2)
1616b2dcd1e6Sshadchin 			sc->inputstate += 3;
1617b2dcd1e6Sshadchin 		return (0);
1618b2dcd1e6Sshadchin 	}
1619b2dcd1e6Sshadchin 
16207327fb1eSmpi 	switch (sc->inputstate) {
16217327fb1eSmpi 	case 0:
1622dd24e85bSmpi 		if ((data & alps->mask) != alps->mask)
16237327fb1eSmpi 			return (-1);
16247327fb1eSmpi 		break;
16257327fb1eSmpi 	case 1:
16267327fb1eSmpi 	case 2:
16277327fb1eSmpi 	case 3:
1628b2dcd1e6Sshadchin 		if ((data & PMS_ALPS_MASK) != PMS_ALPS_VALID)
1629b2dcd1e6Sshadchin 			return (-1);
1630b2dcd1e6Sshadchin 		break;
16317327fb1eSmpi 	case 4:
16327327fb1eSmpi 	case 5:
1633b2dcd1e6Sshadchin 		if ((alps->model & ALPS_INTERLEAVED) == 0 &&
1634b2dcd1e6Sshadchin 		    (data & PMS_ALPS_MASK) != PMS_ALPS_VALID)
16357327fb1eSmpi 			return (-1);
16367327fb1eSmpi 		break;
16377327fb1eSmpi 	}
16387327fb1eSmpi 
16397327fb1eSmpi 	return (0);
16407327fb1eSmpi }
16417327fb1eSmpi 
16427327fb1eSmpi void
16437327fb1eSmpi pms_proc_alps(struct pms_softc *sc)
16447327fb1eSmpi {
16457327fb1eSmpi 	struct alps_softc *alps = sc->alps;
16465ad8c6a9Sbru 	int x, y, z, dx, dy;
16472445272cSbru 	u_int buttons, gesture;
16487327fb1eSmpi 
1649b2dcd1e6Sshadchin 	if ((alps->model & ALPS_DUALPOINT) && alps_sec_proc(sc))
1650b2dcd1e6Sshadchin 		return;
1651b2dcd1e6Sshadchin 
16527327fb1eSmpi 	x = sc->packet[1] | ((sc->packet[2] & 0x78) << 4);
16537327fb1eSmpi 	y = sc->packet[4] | ((sc->packet[3] & 0x70) << 3);
16547327fb1eSmpi 	z = sc->packet[5];
16557327fb1eSmpi 
1656419d60bbSmpi 	buttons = ((sc->packet[3] & 1) ? WSMOUSE_BUTTON(1) : 0) |
1657419d60bbSmpi 	    ((sc->packet[3] & 2) ? WSMOUSE_BUTTON(3) : 0) |
1658419d60bbSmpi 	    ((sc->packet[3] & 4) ? WSMOUSE_BUTTON(2) : 0);
1659419d60bbSmpi 
1660419d60bbSmpi 	if ((sc->sc_dev_enable & PMS_DEV_SECONDARY) && z == ALPS_Z_MAGIC) {
1661419d60bbSmpi 		dx = (x > ALPS_XSEC_BEZEL / 2) ? (x - ALPS_XSEC_BEZEL) : x;
1662419d60bbSmpi 		dy = (y > ALPS_YSEC_BEZEL / 2) ? (y - ALPS_YSEC_BEZEL) : y;
1663419d60bbSmpi 
16645ad8c6a9Sbru 		WSMOUSE_INPUT(sc->sc_sec_wsmousedev, buttons, dx, dy, 0, 0);
1665419d60bbSmpi 
1666419d60bbSmpi 		return;
1667419d60bbSmpi 	}
1668419d60bbSmpi 
1669419d60bbSmpi 	if ((sc->sc_dev_enable & PMS_DEV_PRIMARY) == 0)
1670419d60bbSmpi 		return;
1671419d60bbSmpi 
16727327fb1eSmpi 	/*
16737327fb1eSmpi 	 * XXX The Y-axis is in the oposit direction compared to
16747327fb1eSmpi 	 * Synaptics touchpads and PS/2 mouses.
16757327fb1eSmpi 	 * It's why we need to translate the y value here for both
16767327fb1eSmpi 	 * NATIVE and COMPAT modes.
16777327fb1eSmpi 	 */
16787327fb1eSmpi 	y = ALPS_YMAX_BEZEL - y + ALPS_YMIN_BEZEL;
16797327fb1eSmpi 
16802445272cSbru 	if (alps->gesture == ALPS_TAP) {
16812445272cSbru 		/* Report a touch with the tap coordinates. */
16825ad8c6a9Sbru 		WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons,
16835ad8c6a9Sbru 		    alps->old_x, alps->old_y, ALPS_PRESSURE, 0);
16842445272cSbru 		if (z > 0) {
16852445272cSbru 			/*
16862445272cSbru 			 * The hardware doesn't send a null pressure
16872445272cSbru 			 * event when dragging starts.
16882445272cSbru 			 */
16895ad8c6a9Sbru 			WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons,
16905ad8c6a9Sbru 			    alps->old_x, alps->old_y, 0, 0);
16912445272cSbru 		}
16922445272cSbru 	}
16937327fb1eSmpi 
16942445272cSbru 	gesture = sc->packet[2] & 0x03;
16955ad8c6a9Sbru 	if (gesture != ALPS_TAP)
16965ad8c6a9Sbru 		WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons, x, y, z, 0);
16977327fb1eSmpi 
16982445272cSbru 	if (alps->gesture != ALPS_DRAG || gesture != ALPS_TAP)
16992445272cSbru 		alps->gesture = gesture;
17002445272cSbru 
17012445272cSbru 	alps->old_x = x;
17022445272cSbru 	alps->old_y = y;
17037327fb1eSmpi }
170472ec3668Sstsp 
170572ec3668Sstsp int
170672ec3668Sstsp elantech_set_absolute_mode_v1(struct pms_softc *sc)
170772ec3668Sstsp {
170872ec3668Sstsp 	int i;
170972ec3668Sstsp 	u_char resp[3];
171072ec3668Sstsp 
171172ec3668Sstsp 	/* Enable absolute mode. Magic numbers from Linux driver. */
171272ec3668Sstsp 	if (pms_spec_cmd(sc, ELANTECH_CMD_WRITE_REG) ||
171372ec3668Sstsp 	    pms_spec_cmd(sc, 0x10) ||
171472ec3668Sstsp 	    pms_spec_cmd(sc, 0x16) ||
171572ec3668Sstsp 	    pms_set_scaling(sc, 1) ||
171672ec3668Sstsp 	    pms_spec_cmd(sc, ELANTECH_CMD_WRITE_REG) ||
171772ec3668Sstsp 	    pms_spec_cmd(sc, 0x11) ||
171872ec3668Sstsp 	    pms_spec_cmd(sc, 0x8f) ||
171972ec3668Sstsp 	    pms_set_scaling(sc, 1))
172072ec3668Sstsp 		return (-1);
172172ec3668Sstsp 
172272ec3668Sstsp 	/* Read back reg 0x10 to ensure hardware is ready. */
172372ec3668Sstsp 	for (i = 0; i < 5; i++) {
172472ec3668Sstsp 		if (pms_spec_cmd(sc, ELANTECH_CMD_READ_REG) ||
172572ec3668Sstsp 		    pms_spec_cmd(sc, 0x10) ||
172672ec3668Sstsp 		    pms_get_status(sc, resp) == 0)
172772ec3668Sstsp 			break;
172872ec3668Sstsp 		delay(2000);
172972ec3668Sstsp 	}
173072ec3668Sstsp 	if (i == 5)
173172ec3668Sstsp 		return (-1);
173272ec3668Sstsp 
173372ec3668Sstsp 	if ((resp[0] & ELANTECH_ABSOLUTE_MODE) == 0)
173472ec3668Sstsp 		return (-1);
173572ec3668Sstsp 
173672ec3668Sstsp 	return (0);
173772ec3668Sstsp }
173872ec3668Sstsp 
173972ec3668Sstsp int
174072ec3668Sstsp elantech_set_absolute_mode_v2(struct pms_softc *sc)
174172ec3668Sstsp {
174272ec3668Sstsp 	int i;
174372ec3668Sstsp 	u_char resp[3];
17447b88a336Sstsp 	u_char reg10 = (sc->elantech->fw_version == 0x20030 ? 0x54 : 0xc4);
174572ec3668Sstsp 
174672ec3668Sstsp 	/* Enable absolute mode. Magic numbers from Linux driver. */
174772ec3668Sstsp 	if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
174872ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_CMD_WRITE_REG) ||
174972ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
175072ec3668Sstsp 	    elantech_ps2_cmd(sc, 0x10) ||
175172ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
17527b88a336Sstsp 	    elantech_ps2_cmd(sc, reg10) ||
175372ec3668Sstsp 	    pms_set_scaling(sc, 1) ||
175472ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
175572ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_CMD_WRITE_REG) ||
175672ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
175772ec3668Sstsp 	    elantech_ps2_cmd(sc, 0x11) ||
175872ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
175972ec3668Sstsp 	    elantech_ps2_cmd(sc, 0x88) ||
176072ec3668Sstsp 	    pms_set_scaling(sc, 1))
176172ec3668Sstsp 		return (-1);
176272ec3668Sstsp 
176372ec3668Sstsp 	/* Read back reg 0x10 to ensure hardware is ready. */
176472ec3668Sstsp 	for (i = 0; i < 5; i++) {
176572ec3668Sstsp 		if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
176672ec3668Sstsp 		    elantech_ps2_cmd(sc, ELANTECH_CMD_READ_REG) ||
176772ec3668Sstsp 		    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
176872ec3668Sstsp 		    elantech_ps2_cmd(sc, 0x10) ||
176972ec3668Sstsp 		    pms_get_status(sc, resp) == 0)
177072ec3668Sstsp 			break;
177172ec3668Sstsp 		delay(2000);
177272ec3668Sstsp 	}
177372ec3668Sstsp 	if (i == 5)
177472ec3668Sstsp 		return (-1);
177572ec3668Sstsp 
177672ec3668Sstsp 	return (0);
177772ec3668Sstsp }
177872ec3668Sstsp 
177972ec3668Sstsp int
178072ec3668Sstsp elantech_set_absolute_mode_v3(struct pms_softc *sc)
178172ec3668Sstsp {
178272ec3668Sstsp 	int i;
178372ec3668Sstsp 	u_char resp[3];
178472ec3668Sstsp 
178572ec3668Sstsp 	/* Enable absolute mode. Magic numbers from Linux driver. */
178672ec3668Sstsp 	if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
178772ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_CMD_READ_WRITE_REG) ||
178872ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
178972ec3668Sstsp 	    elantech_ps2_cmd(sc, 0x10) ||
179072ec3668Sstsp 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
179172ec3668Sstsp 	    elantech_ps2_cmd(sc, 0x0b) ||
179272ec3668Sstsp 	    pms_set_scaling(sc, 1))
179372ec3668Sstsp 		return (-1);
179472ec3668Sstsp 
179572ec3668Sstsp 	/* Read back reg 0x10 to ensure hardware is ready. */
179672ec3668Sstsp 	for (i = 0; i < 5; i++) {
179772ec3668Sstsp 		if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
179872ec3668Sstsp 		    elantech_ps2_cmd(sc, ELANTECH_CMD_READ_WRITE_REG) ||
179972ec3668Sstsp 		    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
180072ec3668Sstsp 		    elantech_ps2_cmd(sc, 0x10) ||
180172ec3668Sstsp 		    pms_get_status(sc, resp) == 0)
180272ec3668Sstsp 			break;
180372ec3668Sstsp 		delay(2000);
180472ec3668Sstsp 	}
180572ec3668Sstsp 	if (i == 5)
180672ec3668Sstsp 		return (-1);
180772ec3668Sstsp 
180872ec3668Sstsp 	return (0);
180972ec3668Sstsp }
181072ec3668Sstsp 
181172ec3668Sstsp int
18127f1c67caSjcs elantech_set_absolute_mode_v4(struct pms_softc *sc)
18137f1c67caSjcs {
18147f1c67caSjcs 	/* Enable absolute mode. Magic numbers from Linux driver. */
18157f1c67caSjcs 	if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
18167f1c67caSjcs 	    elantech_ps2_cmd(sc, ELANTECH_CMD_READ_WRITE_REG) ||
18177f1c67caSjcs 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
18187f1c67caSjcs 	    elantech_ps2_cmd(sc, 0x07) ||
18197f1c67caSjcs 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
18207f1c67caSjcs 	    elantech_ps2_cmd(sc, ELANTECH_CMD_READ_WRITE_REG) ||
18217f1c67caSjcs 	    elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
18227f1c67caSjcs 	    elantech_ps2_cmd(sc, 0x01) ||
18237f1c67caSjcs 	    pms_set_scaling(sc, 1))
18247f1c67caSjcs 		return (-1);
18257f1c67caSjcs 
18267f1c67caSjcs 	/* v4 has no register 0x10 to read response from */
18277f1c67caSjcs 
18287f1c67caSjcs 	return (0);
18297f1c67caSjcs }
18307f1c67caSjcs 
18317f1c67caSjcs int
183272ec3668Sstsp elantech_get_hwinfo_v1(struct pms_softc *sc)
183372ec3668Sstsp {
183472ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
18354101186eSbru 	struct wsmousehw *hw;
183672ec3668Sstsp 	int fw_version;
183772ec3668Sstsp 	u_char capabilities[3];
183872ec3668Sstsp 
183972ec3668Sstsp 	if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version))
184072ec3668Sstsp 		return (-1);
184172ec3668Sstsp 
184272ec3668Sstsp 	if (fw_version < 0x20030 || fw_version == 0x20600) {
184372ec3668Sstsp 		if (fw_version < 0x20000)
184472ec3668Sstsp 			elantech->flags |= ELANTECH_F_HW_V1_OLD;
184572ec3668Sstsp 	} else
184672ec3668Sstsp 		return (-1);
184772ec3668Sstsp 
18487b88a336Sstsp 	elantech->fw_version = fw_version;
18497b88a336Sstsp 
185072ec3668Sstsp 	if (pms_spec_cmd(sc, ELANTECH_QUE_CAPABILITIES) ||
185172ec3668Sstsp 	    pms_get_status(sc, capabilities))
185272ec3668Sstsp 		return (-1);
185372ec3668Sstsp 
185472ec3668Sstsp 	if (capabilities[0] & ELANTECH_CAP_HAS_ROCKER)
185572ec3668Sstsp 		elantech->flags |= ELANTECH_F_HAS_ROCKER;
185672ec3668Sstsp 
185772ec3668Sstsp 	if (elantech_set_absolute_mode_v1(sc))
185872ec3668Sstsp 		return (-1);
185972ec3668Sstsp 
18604101186eSbru 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
18614101186eSbru 	hw->type = WSMOUSE_TYPE_ELANTECH;
18624101186eSbru 	hw->hw_type = WSMOUSEHW_TOUCHPAD;
18634101186eSbru 	hw->x_min = ELANTECH_V1_X_MIN;
18644101186eSbru 	hw->x_max = ELANTECH_V1_X_MAX;
18654101186eSbru 	hw->y_min = ELANTECH_V1_Y_MIN;
18664101186eSbru 	hw->y_max = ELANTECH_V1_Y_MAX;
186772ec3668Sstsp 
186872ec3668Sstsp 	return (0);
186972ec3668Sstsp }
187072ec3668Sstsp 
187172ec3668Sstsp int
187272ec3668Sstsp elantech_get_hwinfo_v2(struct pms_softc *sc)
187372ec3668Sstsp {
187472ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
18754101186eSbru 	struct wsmousehw *hw;
187672ec3668Sstsp 	int fw_version, ic_ver;
187772ec3668Sstsp 	u_char capabilities[3];
187872ec3668Sstsp 	int i, fixed_dpi;
187972ec3668Sstsp 	u_char resp[3];
188072ec3668Sstsp 
188172ec3668Sstsp 	if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version))
188272ec3668Sstsp 		return (-1);
188372ec3668Sstsp 
188472ec3668Sstsp 	ic_ver = (fw_version & 0x0f0000) >> 16;
188572ec3668Sstsp 	if (ic_ver != 2 && ic_ver != 4)
188672ec3668Sstsp 		return (-1);
188772ec3668Sstsp 
18887b88a336Sstsp 	elantech->fw_version = fw_version;
188972ec3668Sstsp 	if (fw_version >= 0x20800)
189072ec3668Sstsp 		elantech->flags |= ELANTECH_F_REPORTS_PRESSURE;
189172ec3668Sstsp 
189272ec3668Sstsp 	if (pms_spec_cmd(sc, ELANTECH_QUE_CAPABILITIES) ||
189372ec3668Sstsp 	    pms_get_status(sc, capabilities))
189472ec3668Sstsp 		return (-1);
189572ec3668Sstsp 
189672ec3668Sstsp 	if (elantech_set_absolute_mode_v2(sc))
189772ec3668Sstsp 		return (-1);
189872ec3668Sstsp 
18994101186eSbru 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
19004101186eSbru 	hw->type = WSMOUSE_TYPE_ELANTECH;
19014101186eSbru 	hw->hw_type = WSMOUSEHW_TOUCHPAD;
19024101186eSbru 
190372ec3668Sstsp 	if (fw_version == 0x20800 || fw_version == 0x20b00 ||
190472ec3668Sstsp 	    fw_version == 0x20030) {
19054101186eSbru 		hw->x_max = ELANTECH_V2_X_MAX;
19064101186eSbru 		hw->y_max = ELANTECH_V2_Y_MAX;
190772ec3668Sstsp 	} else {
190872ec3668Sstsp 		if (pms_spec_cmd(sc, ELANTECH_QUE_FW_ID) ||
190972ec3668Sstsp 		    pms_get_status(sc, resp))
191072ec3668Sstsp 			return (-1);
191172ec3668Sstsp 		fixed_dpi = resp[1] & 0x10;
191272ec3668Sstsp 		i = (fw_version > 0x20800 && fw_version < 0x20900) ? 1 : 2;
191372ec3668Sstsp 		if ((fw_version >> 16) == 0x14 && fixed_dpi) {
191472ec3668Sstsp 			if (pms_spec_cmd(sc, ELANTECH_QUE_SAMPLE) ||
191572ec3668Sstsp 			    pms_get_status(sc, resp))
191672ec3668Sstsp 				return (-1);
19174101186eSbru 			hw->x_max = (capabilities[1] - i) * resp[1] / 2;
19184101186eSbru 			hw->y_max = (capabilities[2] - i) * resp[2] / 2;
191972ec3668Sstsp 		} else if (fw_version == 0x040216) {
19204101186eSbru 			hw->x_max = 819;
19214101186eSbru 			hw->y_max = 405;
192272ec3668Sstsp 		} else if (fw_version == 0x040219 || fw_version == 0x040215) {
19234101186eSbru 			hw->x_max = 900;
19244101186eSbru 			hw->y_max = 500;
192572ec3668Sstsp 		} else {
19264101186eSbru 			hw->x_max = (capabilities[1] - i) * 64;
19274101186eSbru 			hw->y_max = (capabilities[2] - i) * 64;
192872ec3668Sstsp 		}
192972ec3668Sstsp 	}
193072ec3668Sstsp 
193172ec3668Sstsp 	return (0);
193272ec3668Sstsp }
193372ec3668Sstsp 
193472ec3668Sstsp int
193572ec3668Sstsp elantech_get_hwinfo_v3(struct pms_softc *sc)
193672ec3668Sstsp {
193772ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
19384101186eSbru 	struct wsmousehw *hw;
193972ec3668Sstsp 	int fw_version;
194072ec3668Sstsp 	u_char resp[3];
194172ec3668Sstsp 
194272ec3668Sstsp 	if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version))
194372ec3668Sstsp 		return (-1);
194472ec3668Sstsp 
194572ec3668Sstsp 	if (((fw_version & 0x0f0000) >> 16) != 5)
194672ec3668Sstsp 		return (-1);
194772ec3668Sstsp 
19487b88a336Sstsp 	elantech->fw_version = fw_version;
194972ec3668Sstsp 	elantech->flags |= ELANTECH_F_REPORTS_PRESSURE;
195072ec3668Sstsp 
195140f7e3e4Sstsp 	if ((fw_version & 0x4000) == 0x4000)
195240f7e3e4Sstsp 		elantech->flags |= ELANTECH_F_CRC_ENABLED;
195340f7e3e4Sstsp 
195472ec3668Sstsp 	if (elantech_set_absolute_mode_v3(sc))
195572ec3668Sstsp 		return (-1);
195672ec3668Sstsp 
195772ec3668Sstsp 	if (pms_spec_cmd(sc, ELANTECH_QUE_FW_ID) ||
195872ec3668Sstsp 	    pms_get_status(sc, resp))
195972ec3668Sstsp 		return (-1);
196072ec3668Sstsp 
19614101186eSbru 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
19624101186eSbru 	hw->x_max = elantech->max_x = (resp[0] & 0x0f) << 8 | resp[1];
19634101186eSbru 	hw->y_max = elantech->max_y = (resp[0] & 0xf0) << 4 | resp[2];
19644101186eSbru 
19654101186eSbru 	hw->type = WSMOUSE_TYPE_ELANTECH;
19664101186eSbru 	hw->hw_type = WSMOUSEHW_TOUCHPAD;
196772ec3668Sstsp 
196872ec3668Sstsp 	return (0);
196972ec3668Sstsp }
197072ec3668Sstsp 
197172ec3668Sstsp int
19727f1c67caSjcs elantech_get_hwinfo_v4(struct pms_softc *sc)
19737f1c67caSjcs {
19747f1c67caSjcs 	struct elantech_softc *elantech = sc->elantech;
1975505a3534Sbru 	struct wsmousehw *hw;
19767f1c67caSjcs 	int fw_version;
19777f1c67caSjcs 	u_char capabilities[3];
19787f1c67caSjcs 	u_char resp[3];
19797f1c67caSjcs 
19807f1c67caSjcs 	if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version))
19817f1c67caSjcs 		return (-1);
19827f1c67caSjcs 
19839665f110Sbru 	if ((fw_version & 0x0f0000) >> 16 < 6)
19847f1c67caSjcs 		return (-1);
19857f1c67caSjcs 
19867b88a336Sstsp 	elantech->fw_version = fw_version;
19877f1c67caSjcs 	elantech->flags |= ELANTECH_F_REPORTS_PRESSURE;
19887f1c67caSjcs 
19894f06b9beSbru 	if ((fw_version & 0x4000) == 0x4000)
19904f06b9beSbru 		elantech->flags |= ELANTECH_F_CRC_ENABLED;
19914f06b9beSbru 
19927f1c67caSjcs 	if (elantech_set_absolute_mode_v4(sc))
19937f1c67caSjcs 		return (-1);
19947f1c67caSjcs 
19957f1c67caSjcs 	if (pms_spec_cmd(sc, ELANTECH_QUE_CAPABILITIES) ||
19967f1c67caSjcs 	    pms_get_status(sc, capabilities))
19977f1c67caSjcs 		return (-1);
19987f1c67caSjcs 
19997f1c67caSjcs 	if (pms_spec_cmd(sc, ELANTECH_QUE_FW_ID) ||
20007f1c67caSjcs 	    pms_get_status(sc, resp))
20017f1c67caSjcs 		return (-1);
20027f1c67caSjcs 
2003505a3534Sbru 	hw = wsmouse_get_hw(sc->sc_wsmousedev);
2004505a3534Sbru 	hw->x_max = (resp[0] & 0x0f) << 8 | resp[1];
2005505a3534Sbru 	hw->y_max = (resp[0] & 0xf0) << 4 | resp[2];
20067f1c67caSjcs 
2007505a3534Sbru 	if ((capabilities[1] < 2) || (capabilities[1] > hw->x_max))
20087f1c67caSjcs 		return (-1);
20097f1c67caSjcs 
20108e9e9c10Sbru 	if (capabilities[0] & ELANTECH_CAP_TRACKPOINT)
20118e9e9c10Sbru 		elantech->flags |= ELANTECH_F_TRACKPOINT;
20128e9e9c10Sbru 
2013505a3534Sbru 	hw->type = WSMOUSE_TYPE_ELANTECH;
20149665f110Sbru 	hw->hw_type = (ELANTECH_IS_CLICKPAD(sc)
20159665f110Sbru 	    ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD);
2016505a3534Sbru 	hw->mt_slots = ELANTECH_MAX_FINGERS;
2017505a3534Sbru 
2018505a3534Sbru 	elantech->width = hw->x_max / (capabilities[1] - 1);
20197f1c67caSjcs 
20207f1c67caSjcs 	return (0);
20217f1c67caSjcs }
20227f1c67caSjcs 
20237f1c67caSjcs int
202472ec3668Sstsp elantech_ps2_cmd(struct pms_softc *sc, u_char command)
202572ec3668Sstsp {
202672ec3668Sstsp 	u_char cmd[1];
202772ec3668Sstsp 
202872ec3668Sstsp 	cmd[0] = command;
202972ec3668Sstsp 	return (pms_cmd(sc, cmd, 1, NULL, 0));
203072ec3668Sstsp }
203172ec3668Sstsp 
203272ec3668Sstsp int
203372ec3668Sstsp elantech_knock(struct pms_softc *sc)
203472ec3668Sstsp {
203572ec3668Sstsp 	u_char resp[3];
203672ec3668Sstsp 
203772ec3668Sstsp 	if (pms_dev_disable(sc) ||
203872ec3668Sstsp 	    pms_set_scaling(sc, 1) ||
203972ec3668Sstsp 	    pms_set_scaling(sc, 1) ||
204072ec3668Sstsp 	    pms_set_scaling(sc, 1) ||
204172ec3668Sstsp 	    pms_get_status(sc, resp) ||
204272ec3668Sstsp 	    resp[0] != PMS_ELANTECH_MAGIC1 ||
204372ec3668Sstsp 	    resp[1] != PMS_ELANTECH_MAGIC2 ||
204472ec3668Sstsp 	    (resp[2] != PMS_ELANTECH_MAGIC3_1 &&
204572ec3668Sstsp 	    resp[2] != PMS_ELANTECH_MAGIC3_2))
204672ec3668Sstsp 		return (-1);
204772ec3668Sstsp 
204872ec3668Sstsp 	return (0);
204972ec3668Sstsp }
205072ec3668Sstsp 
205172ec3668Sstsp int
205272ec3668Sstsp pms_enable_elantech_v1(struct pms_softc *sc)
205372ec3668Sstsp {
205472ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
205572ec3668Sstsp 	int i;
205672ec3668Sstsp 
205772ec3668Sstsp 	if (elantech_knock(sc))
20586138841fSshadchin 		goto err;
205972ec3668Sstsp 
206072ec3668Sstsp 	if (sc->elantech == NULL) {
206172ec3668Sstsp 		sc->elantech = elantech = malloc(sizeof(struct elantech_softc),
206272ec3668Sstsp 		    M_DEVBUF, M_WAITOK | M_ZERO);
206372ec3668Sstsp 		if (elantech == NULL) {
206472ec3668Sstsp 			printf("%s: elantech: not enough memory\n",
206572ec3668Sstsp 			    DEVNAME(sc));
206672ec3668Sstsp 			goto err;
206772ec3668Sstsp 		}
206872ec3668Sstsp 
2069f96998ceSmpi 		if (elantech_get_hwinfo_v1(sc)) {
2070a31dcc2eSderaadt 			free(sc->elantech, M_DEVBUF,
2071a31dcc2eSderaadt 			    sizeof(struct elantech_softc));
2072f96998ceSmpi 			sc->elantech = NULL;
207372ec3668Sstsp 			goto err;
2074f96998ceSmpi 		}
20754101186eSbru 		if (wsmouse_configure(sc->sc_wsmousedev, NULL, 0)) {
20764101186eSbru 			free(sc->elantech, M_DEVBUF,
20774101186eSbru 			    sizeof(struct elantech_softc));
20784101186eSbru 			sc->elantech = NULL;
20794101186eSbru 			printf("%s: elantech: setup failed\n", DEVNAME(sc));
20804101186eSbru 			goto err;
20814101186eSbru 		}
208272ec3668Sstsp 
20837b88a336Sstsp 		printf("%s: Elantech Touchpad, version %d, firmware 0x%x\n",
20847b88a336Sstsp 		    DEVNAME(sc), 1, sc->elantech->fw_version);
20856138841fSshadchin 	} else if (elantech_set_absolute_mode_v1(sc))
208672ec3668Sstsp 		goto err;
208772ec3668Sstsp 
208872ec3668Sstsp 	for (i = 0; i < nitems(sc->elantech->parity); i++)
208972ec3668Sstsp 		sc->elantech->parity[i] = sc->elantech->parity[i & (i - 1)] ^ 1;
209072ec3668Sstsp 
209172ec3668Sstsp 	return (1);
209272ec3668Sstsp 
209372ec3668Sstsp err:
209472ec3668Sstsp 	pms_reset(sc);
209572ec3668Sstsp 
209672ec3668Sstsp 	return (0);
209772ec3668Sstsp }
209872ec3668Sstsp 
209972ec3668Sstsp int
210072ec3668Sstsp pms_enable_elantech_v2(struct pms_softc *sc)
210172ec3668Sstsp {
210272ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
210372ec3668Sstsp 
210472ec3668Sstsp 	if (elantech_knock(sc))
21056138841fSshadchin 		goto err;
210672ec3668Sstsp 
210772ec3668Sstsp 	if (sc->elantech == NULL) {
210872ec3668Sstsp 		sc->elantech = elantech = malloc(sizeof(struct elantech_softc),
210972ec3668Sstsp 		    M_DEVBUF, M_WAITOK | M_ZERO);
211072ec3668Sstsp 		if (elantech == NULL) {
211172ec3668Sstsp 			printf("%s: elantech: not enough memory\n",
211272ec3668Sstsp 			    DEVNAME(sc));
211372ec3668Sstsp 			goto err;
211472ec3668Sstsp 		}
211572ec3668Sstsp 
2116f96998ceSmpi 		if (elantech_get_hwinfo_v2(sc)) {
2117a31dcc2eSderaadt 			free(sc->elantech, M_DEVBUF,
2118a31dcc2eSderaadt 			    sizeof(struct elantech_softc));
2119f96998ceSmpi 			sc->elantech = NULL;
212072ec3668Sstsp 			goto err;
2121f96998ceSmpi 		}
21224101186eSbru 		if (wsmouse_configure(sc->sc_wsmousedev, NULL, 0)) {
21234101186eSbru 			free(sc->elantech, M_DEVBUF,
21244101186eSbru 			    sizeof(struct elantech_softc));
21254101186eSbru 			sc->elantech = NULL;
21264101186eSbru 			printf("%s: elantech: setup failed\n", DEVNAME(sc));
21274101186eSbru 			goto err;
21284101186eSbru 		}
212972ec3668Sstsp 
21307b88a336Sstsp 		printf("%s: Elantech Touchpad, version %d, firmware 0x%x\n",
21317b88a336Sstsp 		    DEVNAME(sc), 2, sc->elantech->fw_version);
21326138841fSshadchin 	} else if (elantech_set_absolute_mode_v2(sc))
213372ec3668Sstsp 		goto err;
213472ec3668Sstsp 
213572ec3668Sstsp 	return (1);
213672ec3668Sstsp 
213772ec3668Sstsp err:
213872ec3668Sstsp 	pms_reset(sc);
213972ec3668Sstsp 
214072ec3668Sstsp 	return (0);
214172ec3668Sstsp }
214272ec3668Sstsp 
214372ec3668Sstsp int
214472ec3668Sstsp pms_enable_elantech_v3(struct pms_softc *sc)
214572ec3668Sstsp {
214672ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
214772ec3668Sstsp 
214872ec3668Sstsp 	if (elantech_knock(sc))
21496138841fSshadchin 		goto err;
215072ec3668Sstsp 
215172ec3668Sstsp 	if (sc->elantech == NULL) {
215272ec3668Sstsp 		sc->elantech = elantech = malloc(sizeof(struct elantech_softc),
215372ec3668Sstsp 		    M_DEVBUF, M_WAITOK | M_ZERO);
215472ec3668Sstsp 		if (elantech == NULL) {
215572ec3668Sstsp 			printf("%s: elantech: not enough memory\n",
215672ec3668Sstsp 			    DEVNAME(sc));
215772ec3668Sstsp 			goto err;
215872ec3668Sstsp 		}
215972ec3668Sstsp 
2160f96998ceSmpi 		if (elantech_get_hwinfo_v3(sc)) {
2161a31dcc2eSderaadt 			free(sc->elantech, M_DEVBUF,
2162a31dcc2eSderaadt 			    sizeof(struct elantech_softc));
2163f96998ceSmpi 			sc->elantech = NULL;
216472ec3668Sstsp 			goto err;
2165f96998ceSmpi 		}
21664101186eSbru 		if (wsmouse_configure(sc->sc_wsmousedev, NULL, 0)) {
21674101186eSbru 			free(sc->elantech, M_DEVBUF,
21684101186eSbru 			    sizeof(struct elantech_softc));
21694101186eSbru 			sc->elantech = NULL;
21704101186eSbru 			printf("%s: elantech: setup failed\n", DEVNAME(sc));
21714101186eSbru 			goto err;
21724101186eSbru 		}
217372ec3668Sstsp 
21747b88a336Sstsp 		printf("%s: Elantech Touchpad, version %d, firmware 0x%x\n",
21757b88a336Sstsp 		    DEVNAME(sc), 3, sc->elantech->fw_version);
21766138841fSshadchin 	} else if (elantech_set_absolute_mode_v3(sc))
217772ec3668Sstsp 		goto err;
217872ec3668Sstsp 
217972ec3668Sstsp 	return (1);
218072ec3668Sstsp 
218172ec3668Sstsp err:
218272ec3668Sstsp 	pms_reset(sc);
218372ec3668Sstsp 
218472ec3668Sstsp 	return (0);
218572ec3668Sstsp }
218672ec3668Sstsp 
218772ec3668Sstsp int
21887f1c67caSjcs pms_enable_elantech_v4(struct pms_softc *sc)
21897f1c67caSjcs {
21907f1c67caSjcs 	struct elantech_softc *elantech = sc->elantech;
21918e9e9c10Sbru 	struct wsmousedev_attach_args a;
21927f1c67caSjcs 
21937f1c67caSjcs 	if (elantech_knock(sc))
21947f1c67caSjcs 		goto err;
21957f1c67caSjcs 
21967f1c67caSjcs 	if (sc->elantech == NULL) {
21977f1c67caSjcs 		sc->elantech = elantech = malloc(sizeof(struct elantech_softc),
21987f1c67caSjcs 		    M_DEVBUF, M_WAITOK | M_ZERO);
21997f1c67caSjcs 		if (elantech == NULL) {
22007f1c67caSjcs 			printf("%s: elantech: not enough memory\n",
22017f1c67caSjcs 			    DEVNAME(sc));
22027f1c67caSjcs 			goto err;
22037f1c67caSjcs 		}
22047f1c67caSjcs 
2205f96998ceSmpi 		if (elantech_get_hwinfo_v4(sc)) {
2206a31dcc2eSderaadt 			free(sc->elantech, M_DEVBUF,
2207a31dcc2eSderaadt 			    sizeof(struct elantech_softc));
2208f96998ceSmpi 			sc->elantech = NULL;
22097f1c67caSjcs 			goto err;
2210f96998ceSmpi 		}
2211bc260131Sbru 		if (wsmouse_configure(sc->sc_wsmousedev, NULL, 0)) {
22124101186eSbru 			free(sc->elantech, M_DEVBUF,
22134101186eSbru 			    sizeof(struct elantech_softc));
22145ad8c6a9Sbru 			sc->elantech = NULL;
22154101186eSbru 			printf("%s: elantech: setup failed\n", DEVNAME(sc));
22165ad8c6a9Sbru 			goto err;
22175ad8c6a9Sbru 		}
22185ad8c6a9Sbru 
22199665f110Sbru 		printf("%s: Elantech %s, version 4, firmware 0x%x\n",
22209665f110Sbru 		    DEVNAME(sc), (ELANTECH_IS_CLICKPAD(sc) ?  "Clickpad"
22219665f110Sbru 		    : "Touchpad"), sc->elantech->fw_version);
22228e9e9c10Sbru 
22238e9e9c10Sbru 		if (sc->elantech->flags & ELANTECH_F_TRACKPOINT) {
22248e9e9c10Sbru 			a.accessops = &pms_sec_accessops;
22258e9e9c10Sbru 			a.accesscookie = sc;
22268e9e9c10Sbru 			sc->sc_sec_wsmousedev = config_found((void *) sc, &a,
22278e9e9c10Sbru 			    wsmousedevprint);
22288e9e9c10Sbru 		}
22298e9e9c10Sbru 
22307f1c67caSjcs 	} else if (elantech_set_absolute_mode_v4(sc))
22317f1c67caSjcs 		goto err;
22327f1c67caSjcs 
22337f1c67caSjcs 	return (1);
22347f1c67caSjcs 
22357f1c67caSjcs err:
22367f1c67caSjcs 	pms_reset(sc);
22377f1c67caSjcs 
22387f1c67caSjcs 	return (0);
22397f1c67caSjcs }
22407f1c67caSjcs 
22417f1c67caSjcs int
224272ec3668Sstsp pms_ioctl_elantech(struct pms_softc *sc, u_long cmd, caddr_t data, int flag,
224372ec3668Sstsp     struct proc *p)
224472ec3668Sstsp {
224572ec3668Sstsp 	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
2246505a3534Sbru 	struct wsmousehw *hw;
224772ec3668Sstsp 	int wsmode;
224872ec3668Sstsp 
224972ec3668Sstsp 	switch (cmd) {
225072ec3668Sstsp 	case WSMOUSEIO_GTYPE:
225172ec3668Sstsp 		*(u_int *)data = WSMOUSE_TYPE_ELANTECH;
225272ec3668Sstsp 		break;
225372ec3668Sstsp 	case WSMOUSEIO_GCALIBCOORDS:
2254505a3534Sbru 		hw = wsmouse_get_hw(sc->sc_wsmousedev);
2255505a3534Sbru 		wsmc->minx = hw->x_min;
2256505a3534Sbru 		wsmc->maxx = hw->x_max;
2257505a3534Sbru 		wsmc->miny = hw->y_min;
2258505a3534Sbru 		wsmc->maxy = hw->y_max;
225972ec3668Sstsp 		wsmc->swapxy = 0;
22604101186eSbru 		wsmc->resx = hw->h_res;
22614101186eSbru 		wsmc->resy = hw->v_res;
226272ec3668Sstsp 		break;
226372ec3668Sstsp 	case WSMOUSEIO_SETMODE:
226472ec3668Sstsp 		wsmode = *(u_int *)data;
226572ec3668Sstsp 		if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE)
226672ec3668Sstsp 			return (EINVAL);
22675ad8c6a9Sbru 		wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
226872ec3668Sstsp 		break;
226972ec3668Sstsp 	default:
227072ec3668Sstsp 		return (-1);
227172ec3668Sstsp 	}
227272ec3668Sstsp 	return (0);
227372ec3668Sstsp }
227472ec3668Sstsp 
227572ec3668Sstsp int
227672ec3668Sstsp pms_sync_elantech_v1(struct pms_softc *sc, int data)
227772ec3668Sstsp {
227872ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
227972ec3668Sstsp 	u_char p;
228072ec3668Sstsp 
228172ec3668Sstsp 	switch (sc->inputstate) {
228272ec3668Sstsp 	case 0:
228372ec3668Sstsp 		if (elantech->flags & ELANTECH_F_HW_V1_OLD) {
228472ec3668Sstsp 			elantech->p1 = (data & 0x20) >> 5;
228572ec3668Sstsp 			elantech->p2 = (data & 0x10) >> 4;
228672ec3668Sstsp 		} else {
228772ec3668Sstsp 			elantech->p1 = (data & 0x10) >> 4;
228872ec3668Sstsp 			elantech->p2 = (data & 0x20) >> 5;
228972ec3668Sstsp 		}
229072ec3668Sstsp 		elantech->p3 = (data & 0x04) >> 2;
229172ec3668Sstsp 		return (0);
229272ec3668Sstsp 	case 1:
229372ec3668Sstsp 		p = elantech->p1;
229472ec3668Sstsp 		break;
229572ec3668Sstsp 	case 2:
229672ec3668Sstsp 		p = elantech->p2;
229772ec3668Sstsp 		break;
229872ec3668Sstsp 	case 3:
229972ec3668Sstsp 		p = elantech->p3;
230072ec3668Sstsp 		break;
230172ec3668Sstsp 	default:
230272ec3668Sstsp 		return (-1);
230372ec3668Sstsp 	}
230472ec3668Sstsp 
230572ec3668Sstsp 	if (data < 0 || data >= nitems(elantech->parity) ||
2306c40fe122Smglocker 	/*
2307c40fe122Smglocker 	 * FW 0x20022 sends inverted parity bits on cold boot, returning
2308c40fe122Smglocker 	 * to normal after suspend & resume, so the parity check is
2309c40fe122Smglocker 	 * disabled for this one.
2310c40fe122Smglocker 	 */
2311c40fe122Smglocker 	    (elantech->fw_version != 0x20022 && elantech->parity[data] != p))
231272ec3668Sstsp 		return (-1);
231372ec3668Sstsp 
231472ec3668Sstsp 	return (0);
231572ec3668Sstsp }
231672ec3668Sstsp 
231772ec3668Sstsp int
231872ec3668Sstsp pms_sync_elantech_v2(struct pms_softc *sc, int data)
231972ec3668Sstsp {
232072ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
232172ec3668Sstsp 
232272ec3668Sstsp 	/* Variants reporting pressure always have the same constant bits. */
232372ec3668Sstsp 	if (elantech->flags & ELANTECH_F_REPORTS_PRESSURE) {
232472ec3668Sstsp 		if (sc->inputstate == 0 && (data & 0x0c) != 0x04)
232572ec3668Sstsp 			return (-1);
232672ec3668Sstsp 		if (sc->inputstate == 3 && (data & 0x0f) != 0x02)
232772ec3668Sstsp 			return (-1);
232872ec3668Sstsp 		return (0);
232972ec3668Sstsp 	}
233072ec3668Sstsp 
233172ec3668Sstsp 	/* For variants not reporting pressure, 1 and 3 finger touch packets
233243c56688Smmcc 	 * have different constant bits than 2 finger touch packets. */
233372ec3668Sstsp 	switch (sc->inputstate) {
233472ec3668Sstsp 	case 0:
233572ec3668Sstsp 		if ((data & 0xc0) == 0x80) {
233672ec3668Sstsp 			if ((data & 0x0c) != 0x0c)
233772ec3668Sstsp 				return (-1);
233872ec3668Sstsp 			elantech->flags |= ELANTECH_F_2FINGER_PACKET;
233972ec3668Sstsp 		} else {
234072ec3668Sstsp 			if ((data & 0x3c) != 0x3c)
234172ec3668Sstsp 				return (-1);
234272ec3668Sstsp 			elantech->flags &= ~ELANTECH_F_2FINGER_PACKET;
234372ec3668Sstsp 		}
234472ec3668Sstsp 		break;
234572ec3668Sstsp 	case 1:
234672ec3668Sstsp 	case 4:
234772ec3668Sstsp 		if (elantech->flags & ELANTECH_F_2FINGER_PACKET)
234872ec3668Sstsp 			break;
234972ec3668Sstsp 		if ((data & 0xf0) != 0x00)
235072ec3668Sstsp 			return (-1);
235172ec3668Sstsp 		break;
235272ec3668Sstsp 	case 3:
235372ec3668Sstsp 		if (elantech->flags & ELANTECH_F_2FINGER_PACKET) {
235472ec3668Sstsp 			if ((data & 0x0e) != 0x08)
235572ec3668Sstsp 				return (-1);
235672ec3668Sstsp 		} else {
235772ec3668Sstsp 			if ((data & 0x3e) != 0x38)
235872ec3668Sstsp 				return (-1);
235972ec3668Sstsp 		}
236072ec3668Sstsp 		break;
236172ec3668Sstsp 	default:
236272ec3668Sstsp 		break;
236372ec3668Sstsp 	}
236472ec3668Sstsp 
236572ec3668Sstsp 	return (0);
236672ec3668Sstsp }
236772ec3668Sstsp 
236872ec3668Sstsp int
236972ec3668Sstsp pms_sync_elantech_v3(struct pms_softc *sc, int data)
237072ec3668Sstsp {
237140f7e3e4Sstsp 	struct elantech_softc *elantech = sc->elantech;
237240f7e3e4Sstsp 
237372ec3668Sstsp 	switch (sc->inputstate) {
237472ec3668Sstsp 	case 0:
237540f7e3e4Sstsp 		if (elantech->flags & ELANTECH_F_CRC_ENABLED)
237640f7e3e4Sstsp 			break;
237772ec3668Sstsp 		if ((data & 0x0c) != 0x04 && (data & 0x0c) != 0x0c)
237872ec3668Sstsp 			return (-1);
237972ec3668Sstsp 		break;
238072ec3668Sstsp 	case 3:
238140f7e3e4Sstsp 		if (elantech->flags & ELANTECH_F_CRC_ENABLED) {
238240f7e3e4Sstsp 			if ((data & 0x09) != 0x08 && (data & 0x09) != 0x09)
238340f7e3e4Sstsp 				return (-1);
238440f7e3e4Sstsp 		} else {
238572ec3668Sstsp 			if ((data & 0xcf) != 0x02 && (data & 0xce) != 0x0c)
238672ec3668Sstsp 				return (-1);
238740f7e3e4Sstsp 		}
238872ec3668Sstsp 		break;
238972ec3668Sstsp 	}
239072ec3668Sstsp 
239172ec3668Sstsp 	return (0);
239272ec3668Sstsp }
239372ec3668Sstsp 
23948e9e9c10Sbru /* Extract the type bits from packet[3]. */
23958e9e9c10Sbru static inline int
23964f06b9beSbru elantech_packet_type(struct elantech_softc *elantech, u_char b)
23978e9e9c10Sbru {
23984f06b9beSbru 	/*
23994f06b9beSbru 	 * This looks dubious, but in the "crc-enabled" format bit 2 may
24004f06b9beSbru 	 * be set even in MOTION packets.
24014f06b9beSbru 	 */
24024f06b9beSbru 	if ((elantech->flags & ELANTECH_F_TRACKPOINT) && ((b & 0x0f) == 0x06))
24034f06b9beSbru 		return (ELANTECH_PKT_TRACKPOINT);
24044f06b9beSbru 	else
24054f06b9beSbru 		return (b & 0x03);
24068e9e9c10Sbru }
24078e9e9c10Sbru 
24087f1c67caSjcs int
24097f1c67caSjcs pms_sync_elantech_v4(struct pms_softc *sc, int data)
24107f1c67caSjcs {
24114f06b9beSbru 	if (sc->inputstate == 0)
24124f06b9beSbru 		return ((data & 0x08) == 0 ? 0 : -1);
24134f06b9beSbru 
24148e9e9c10Sbru 	if (sc->inputstate == 3) {
24154f06b9beSbru 		switch (elantech_packet_type(sc->elantech, data)) {
24168e9e9c10Sbru 		case ELANTECH_V4_PKT_STATUS:
24178e9e9c10Sbru 		case ELANTECH_V4_PKT_HEAD:
24188e9e9c10Sbru 		case ELANTECH_V4_PKT_MOTION:
24194f06b9beSbru 			if (sc->elantech->flags & ELANTECH_F_CRC_ENABLED)
24204f06b9beSbru 				return ((data & 0x08) == 0 ? 0 : -1);
24214f06b9beSbru 			else
24224f06b9beSbru 				return ((data & 0x1c) == 0x10 ? 0 : -1);
24238e9e9c10Sbru 		case ELANTECH_PKT_TRACKPOINT:
24248e9e9c10Sbru 			return ((sc->packet[0] & 0xc8) == 0
24258e9e9c10Sbru 			    && sc->packet[1] == ((data & 0x10) << 3)
24268e9e9c10Sbru 			    && sc->packet[2] == ((data & 0x20) << 2)
24278e9e9c10Sbru 			    && (data ^ (sc->packet[0] & 0x30)) == 0x36
24288e9e9c10Sbru 			    ? 0 : -1);
24298e9e9c10Sbru 		}
24308e9e9c10Sbru 		return (-1);
24318e9e9c10Sbru 	}
24327f1c67caSjcs 	return (0);
24337f1c67caSjcs }
24347f1c67caSjcs 
243572ec3668Sstsp void
243672ec3668Sstsp pms_proc_elantech_v1(struct pms_softc *sc)
243772ec3668Sstsp {
243872ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
243972ec3668Sstsp 	int x, y, w, z;
24404101186eSbru 	u_int buttons;
24414101186eSbru 
24424101186eSbru 	buttons = butmap[sc->packet[0] & 3];
24434101186eSbru 
24444101186eSbru 	if (elantech->flags & ELANTECH_F_HAS_ROCKER) {
24454101186eSbru 		if (sc->packet[0] & 0x40) /* up */
24464101186eSbru 			buttons |= WSMOUSE_BUTTON(4);
24474101186eSbru 		if (sc->packet[0] & 0x80) /* down */
24484101186eSbru 			buttons |= WSMOUSE_BUTTON(5);
24494101186eSbru 	}
245072ec3668Sstsp 
245172ec3668Sstsp 	if (elantech->flags & ELANTECH_F_HW_V1_OLD)
245272ec3668Sstsp 		w = ((sc->packet[1] & 0x80) >> 7) +
245372ec3668Sstsp 		    ((sc->packet[1] & 0x30) >> 4);
245472ec3668Sstsp 	else
245572ec3668Sstsp 		w = (sc->packet[0] & 0xc0) >> 6;
245672ec3668Sstsp 
24576e9b9891Sbru 	/*
24586e9b9891Sbru 	 * Firmwares 0x20022 and 0x20600 have a bug, position data in the
24596e9b9891Sbru 	 * first two reports for single-touch contacts may be corrupt.
24606e9b9891Sbru 	 */
24616e9b9891Sbru 	if (elantech->fw_version == 0x20022 ||
24626e9b9891Sbru 	    elantech->fw_version == 0x20600) {
24636e9b9891Sbru 		if (w == 1) {
24646e9b9891Sbru 			if (elantech->initial_pkt < 2) {
24656e9b9891Sbru 				elantech->initial_pkt++;
24666e9b9891Sbru 				return;
24676e9b9891Sbru 			}
24686e9b9891Sbru 		} else if (elantech->initial_pkt) {
24696e9b9891Sbru 			elantech->initial_pkt = 0;
24706e9b9891Sbru 		}
24716e9b9891Sbru 	}
24726e9b9891Sbru 
247372ec3668Sstsp 	/* Hardware version 1 doesn't report pressure. */
247472ec3668Sstsp 	if (w) {
247572ec3668Sstsp 		x = ((sc->packet[1] & 0x0c) << 6) | sc->packet[2];
247672ec3668Sstsp 		y = ((sc->packet[1] & 0x03) << 8) | sc->packet[3];
247772ec3668Sstsp 		z = SYNAPTICS_PRESSURE;
247872ec3668Sstsp 	} else {
24796e9b9891Sbru 		x = y = z = 0;
248072ec3668Sstsp 	}
248172ec3668Sstsp 
24824101186eSbru 	WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons, x, y, z, w);
248372ec3668Sstsp }
248472ec3668Sstsp 
248572ec3668Sstsp void
248672ec3668Sstsp pms_proc_elantech_v2(struct pms_softc *sc)
248772ec3668Sstsp {
248872ec3668Sstsp 	const u_char debounce_pkt[] = { 0x84, 0xff, 0xff, 0x02, 0xff, 0xff };
248972ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
249072ec3668Sstsp 	int x, y, w, z;
24914101186eSbru 	u_int buttons;
249272ec3668Sstsp 
249372ec3668Sstsp 	/*
249472ec3668Sstsp 	 * The hardware sends this packet when in debounce state.
249572ec3668Sstsp 	 * The packet should be ignored.
249672ec3668Sstsp 	 */
249772ec3668Sstsp 	if (!memcmp(sc->packet, debounce_pkt, sizeof(debounce_pkt)))
249872ec3668Sstsp 		return;
249972ec3668Sstsp 
25004101186eSbru 	buttons = butmap[sc->packet[0] & 3];
25014101186eSbru 
250272ec3668Sstsp 	w = (sc->packet[0] & 0xc0) >> 6;
250372ec3668Sstsp 	if (w == 1 || w == 3) {
250472ec3668Sstsp 		x = ((sc->packet[1] & 0x0f) << 8) | sc->packet[2];
250572ec3668Sstsp 		y = ((sc->packet[4] & 0x0f) << 8) | sc->packet[5];
250672ec3668Sstsp 		if (elantech->flags & ELANTECH_F_REPORTS_PRESSURE)
250772ec3668Sstsp 			z = ((sc->packet[1] & 0xf0) |
250872ec3668Sstsp 			    (sc->packet[4] & 0xf0) >> 4);
250972ec3668Sstsp 		else
251072ec3668Sstsp 			z = SYNAPTICS_PRESSURE;
251172ec3668Sstsp 	} else if (w == 2) {
251272ec3668Sstsp 		x = (((sc->packet[0] & 0x10) << 4) | sc->packet[1]) << 2;
251372ec3668Sstsp 		y = (((sc->packet[0] & 0x20) << 3) | sc->packet[2]) << 2;
251472ec3668Sstsp 		z = SYNAPTICS_PRESSURE;
251572ec3668Sstsp 	} else {
25166e9b9891Sbru 		x = y = z = 0;
251772ec3668Sstsp 	}
251872ec3668Sstsp 
25194101186eSbru 	WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons, x, y, z, w);
252072ec3668Sstsp }
252172ec3668Sstsp 
252272ec3668Sstsp void
252372ec3668Sstsp pms_proc_elantech_v3(struct pms_softc *sc)
252472ec3668Sstsp {
252572ec3668Sstsp 	const u_char debounce_pkt[] = { 0xc4, 0xff, 0xff, 0x02, 0xff, 0xff };
252672ec3668Sstsp 	struct elantech_softc *elantech = sc->elantech;
252772ec3668Sstsp 	int x, y, w, z;
25284101186eSbru 	u_int buttons;
25294101186eSbru 
25304101186eSbru 	buttons = butmap[sc->packet[0] & 3];
253172ec3668Sstsp 
253272ec3668Sstsp 	x = ((sc->packet[1] & 0x0f) << 8 | sc->packet[2]);
253372ec3668Sstsp 	y = ((sc->packet[4] & 0x0f) << 8 | sc->packet[5]);
253472ec3668Sstsp 	z = 0;
253572ec3668Sstsp 	w = (sc->packet[0] & 0xc0) >> 6;
253672ec3668Sstsp 	if (w == 2) {
253772ec3668Sstsp 		/*
253872ec3668Sstsp 		 * Two-finger touch causes two packets -- a head packet
253972ec3668Sstsp 		 * and a tail packet. We report a single event and ignore
254072ec3668Sstsp 		 * the tail packet.
254172ec3668Sstsp 		 */
254240f7e3e4Sstsp 		if (elantech->flags & ELANTECH_F_CRC_ENABLED) {
254340f7e3e4Sstsp 			if ((sc->packet[3] & 0x09) != 0x08)
254440f7e3e4Sstsp 				return;
254540f7e3e4Sstsp 		} else {
254640f7e3e4Sstsp 			/* The hardware sends this packet when in debounce state.
254740f7e3e4Sstsp 	 		 * The packet should be ignored. */
254840f7e3e4Sstsp 			if (!memcmp(sc->packet, debounce_pkt, sizeof(debounce_pkt)))
254940f7e3e4Sstsp 				return;
255072ec3668Sstsp 			if ((sc->packet[0] & 0x0c) != 0x04 &&
255185c77397Sstsp 	    		(sc->packet[3] & 0xcf) != 0x02) {
255272ec3668Sstsp 				/* not the head packet -- ignore */
255372ec3668Sstsp 				return;
255472ec3668Sstsp 			}
255572ec3668Sstsp 		}
255640f7e3e4Sstsp 	}
255772ec3668Sstsp 
2558e48cb574Skrw 	/* Prevent jumping cursor if pad isn't touched or reports garbage. */
255972ec3668Sstsp 	if (w == 0 ||
256072ec3668Sstsp 	    ((x == 0 || y == 0 || x == elantech->max_x || y == elantech->max_y)
256172ec3668Sstsp 	    && (x != elantech->old_x || y != elantech->old_y))) {
256272ec3668Sstsp 		x = elantech->old_x;
256372ec3668Sstsp 		y = elantech->old_y;
256472ec3668Sstsp 	}
256572ec3668Sstsp 
256672ec3668Sstsp 	if (elantech->flags & ELANTECH_F_REPORTS_PRESSURE)
256772ec3668Sstsp 		z = (sc->packet[1] & 0xf0) | ((sc->packet[4] & 0xf0) >> 4);
256872ec3668Sstsp 	else if (w)
256972ec3668Sstsp 		z = SYNAPTICS_PRESSURE;
257072ec3668Sstsp 
25714101186eSbru 	WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons, x, y, z, w);
25724101186eSbru 	elantech->old_x = x;
25734101186eSbru 	elantech->old_y = y;
257472ec3668Sstsp }
257572ec3668Sstsp 
257672ec3668Sstsp void
25777f1c67caSjcs pms_proc_elantech_v4(struct pms_softc *sc)
25787f1c67caSjcs {
25797f1c67caSjcs 	struct elantech_softc *elantech = sc->elantech;
25805ad8c6a9Sbru 	struct device *sc_wsmousedev = sc->sc_wsmousedev;
25815ad8c6a9Sbru 	int id, weight, n, x, y, z;
25825ad8c6a9Sbru 	u_int buttons, slots;
25837f1c67caSjcs 
25844f06b9beSbru 	switch (elantech_packet_type(elantech, sc->packet[3])) {
25857f1c67caSjcs 	case ELANTECH_V4_PKT_STATUS:
25865ad8c6a9Sbru 		slots = elantech->mt_slots;
25875ad8c6a9Sbru 		elantech->mt_slots = sc->packet[1] & 0x1f;
25885ad8c6a9Sbru 		slots &= ~elantech->mt_slots;
25895ad8c6a9Sbru 		for (id = 0; slots; id++, slots >>= 1) {
25905ad8c6a9Sbru 			if (slots & 1)
25915ad8c6a9Sbru 				wsmouse_mtstate(sc_wsmousedev, id, 0, 0, 0);
25925ad8c6a9Sbru 		}
25937f1c67caSjcs 		break;
25947f1c67caSjcs 
25957f1c67caSjcs 	case ELANTECH_V4_PKT_HEAD:
25967f1c67caSjcs 		id = ((sc->packet[3] & 0xe0) >> 5) - 1;
25973c37ce5cSmpi 		if (id > -1 && id < ELANTECH_MAX_FINGERS) {
25985ad8c6a9Sbru 			x = ((sc->packet[1] & 0x0f) << 8) | sc->packet[2];
25995ad8c6a9Sbru 			y = ((sc->packet[4] & 0x0f) << 8) | sc->packet[5];
26005ad8c6a9Sbru 			z = (sc->packet[1] & 0xf0)
26013c37ce5cSmpi 			    | ((sc->packet[4] & 0xf0) >> 4);
26025ad8c6a9Sbru 			wsmouse_mtstate(sc_wsmousedev, id, x, y, z);
26033c37ce5cSmpi 		}
26047f1c67caSjcs 		break;
26057f1c67caSjcs 
26067f1c67caSjcs 	case ELANTECH_V4_PKT_MOTION:
26077f1c67caSjcs 		weight = (sc->packet[0] & 0x10) ? ELANTECH_V4_WEIGHT_VALUE : 1;
26083c37ce5cSmpi 		for (n = 0; n < 6; n += 3) {
26093c37ce5cSmpi 			id = ((sc->packet[n] & 0xe0) >> 5) - 1;
26103c37ce5cSmpi 			if (id < 0 || id >= ELANTECH_MAX_FINGERS)
26113c37ce5cSmpi 				continue;
26125ad8c6a9Sbru 			x = weight * (signed char)sc->packet[n + 1];
26135ad8c6a9Sbru 			y = weight * (signed char)sc->packet[n + 2];
26145ad8c6a9Sbru 			z = WSMOUSE_DEFAULT_PRESSURE;
26155ad8c6a9Sbru 			wsmouse_set(sc_wsmousedev, WSMOUSE_MT_REL_X, x, id);
26165ad8c6a9Sbru 			wsmouse_set(sc_wsmousedev, WSMOUSE_MT_REL_Y, y, id);
26175ad8c6a9Sbru 			wsmouse_set(sc_wsmousedev, WSMOUSE_MT_PRESSURE, z, id);
26187f1c67caSjcs 		}
26197f1c67caSjcs 		break;
26207f1c67caSjcs 
26218e9e9c10Sbru 	case ELANTECH_PKT_TRACKPOINT:
26228e9e9c10Sbru 		if (sc->sc_dev_enable & PMS_DEV_SECONDARY) {
2623c7b9c400Ssdk 			/*
2624c7b9c400Ssdk 			* This firmware misreport coordinates for trackpoint
2625c7b9c400Ssdk 			* occasionally. Discard packets outside of [-127, 127] range
2626c7b9c400Ssdk 			* to prevent cursor jumps.
2627c7b9c400Ssdk 			*/
2628c7b9c400Ssdk 			if (sc->packet[4] == 0x80 || sc->packet[5] == 0x80 ||
2629c7b9c400Ssdk 			    sc->packet[1] >> 7 == sc->packet[4] >> 7 ||
2630c7b9c400Ssdk 			    sc->packet[2] >> 7 == sc->packet[5] >> 7)
2631c7b9c400Ssdk 				return;
2632c7b9c400Ssdk 
26338e9e9c10Sbru 			x = sc->packet[4] - 0x100 + (sc->packet[1] << 1);
26348e9e9c10Sbru 			y = sc->packet[5] - 0x100 + (sc->packet[2] << 1);
26358e9e9c10Sbru 			buttons = butmap[sc->packet[0] & 7];
26368e9e9c10Sbru 			WSMOUSE_INPUT(sc->sc_sec_wsmousedev,
26378e9e9c10Sbru 			    buttons, x, y, 0, 0);
26388e9e9c10Sbru 		}
26398e9e9c10Sbru 		return;
26408e9e9c10Sbru 
26417f1c67caSjcs 	default:
26427f1c67caSjcs 		printf("%s: unknown packet type 0x%x\n", DEVNAME(sc),
26437f1c67caSjcs 		    sc->packet[3] & 0x1f);
26447f1c67caSjcs 		return;
26457f1c67caSjcs 	}
26467f1c67caSjcs 
26474101186eSbru 	buttons = butmap[sc->packet[0] & 3];
26485ad8c6a9Sbru 	wsmouse_buttons(sc_wsmousedev, buttons);
26493c37ce5cSmpi 
26505ad8c6a9Sbru 	wsmouse_input_sync(sc_wsmousedev);
26513c37ce5cSmpi }
2652