/* $OpenBSD: wsmouse.c,v 1.70 2022/10/16 18:23:44 bru Exp $ */
/* $NetBSD: wsmouse.c,v 1.35 2005/02/27 00:27:52 perry Exp $ */

/*
 * Copyright (c) 1996, 1997 Christopher G. Demetriou.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Christopher G. Demetriou
 *	for the NetBSD Project.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
 * contributed to Berkeley.
 *
 * All advertising materials mentioning features or use of this software
 * must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Lawrence Berkeley Laboratory.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)ms.c	8.1 (Berkeley) 6/11/93
 */

/*
 * Copyright (c) 2015, 2016 Ulf Brosziewski
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Mouse driver.
 */

#include <sys/param.h>
#include <sys/conf.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/tty.h>
#include <sys/signalvar.h>
#include <sys/device.h>
#include <sys/vnode.h>
#include <sys/malloc.h>

#include <dev/wscons/wscons_features.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsmousevar.h>
#include <dev/wscons/wseventvar.h>
#include <dev/wscons/wsmouseinput.h>

#include "wsmux.h"
#include "wsdisplay.h"
#include "wskbd.h"

#include <dev/wscons/wsmuxvar.h>

#if defined(WSMUX_DEBUG) && NWSMUX > 0
#define	DPRINTF(x)	if (wsmuxdebug) printf x
extern int wsmuxdebug;
#else
#define	DPRINTF(x)
#endif

struct wsmouse_softc {
	struct wsevsrc	sc_base;

	const struct wsmouse_accessops *sc_accessops;
	void		*sc_accesscookie;

	struct wsmouseinput sc_input;

	int		sc_refcnt;
	u_char		sc_dying;	/* device is being detached */
};

int	wsmouse_match(struct device *, void *, void *);
void	wsmouse_attach(struct device *, struct device *, void *);
int	wsmouse_detach(struct device *, int);
int	wsmouse_activate(struct device *, int);

int	wsmouse_do_ioctl(struct wsmouse_softc *, u_long, caddr_t,
			      int, struct proc *);

#if NWSMUX > 0
int	wsmouse_mux_open(struct wsevsrc *, struct wseventvar *);
int	wsmouse_mux_close(struct wsevsrc *);
#endif

int	wsmousedoioctl(struct device *, u_long, caddr_t, int,
			    struct proc *);
int	wsmousedoopen(struct wsmouse_softc *, struct wseventvar *);

struct cfdriver wsmouse_cd = {
	NULL, "wsmouse", DV_TTY
};

const struct cfattach wsmouse_ca = {
	sizeof (struct wsmouse_softc), wsmouse_match, wsmouse_attach,
	wsmouse_detach, wsmouse_activate
};

#if NWSMUX > 0
struct wssrcops wsmouse_srcops = {
	.type		= WSMUX_MOUSE,
	.dopen		= wsmouse_mux_open,
	.dclose		= wsmouse_mux_close,
	.dioctl		= wsmousedoioctl,
	.ddispioctl	= NULL,
	.dsetdisplay	= NULL,
};
#endif

/*
 * Print function (for parent devices).
 */
int
wsmousedevprint(void *aux, const char *pnp)
{

	if (pnp)
		printf("wsmouse at %s", pnp);
	return (UNCONF);
}

int
wsmouse_match(struct device *parent, void *match, void *aux)
{
	return (1);
}

void
wsmouse_attach(struct device *parent, struct device *self, void *aux)
{
	struct wsmouse_softc *sc = (struct wsmouse_softc *)self;
	struct wsmousedev_attach_args *ap = aux;
#if NWSMUX > 0
	int mux, error;
#endif

	sc->sc_accessops = ap->accessops;
	sc->sc_accesscookie = ap->accesscookie;

	sc->sc_input.evar = &sc->sc_base.me_evp;

#if NWSMUX > 0
	sc->sc_base.me_ops = &wsmouse_srcops;
	mux = sc->sc_base.me_dv.dv_cfdata->wsmousedevcf_mux;
	if (mux >= 0) {
		error = wsmux_attach_sc(wsmux_getmux(mux), &sc->sc_base);
		if (error)
			printf(" attach error=%d", error);
		else
			printf(" mux %d", mux);
	}
#else
#if 0	/* not worth keeping, especially since the default value is not -1... */
	if (sc->sc_base.me_dv.dv_cfdata->wsmousedevcf_mux >= 0)
		printf(" (mux ignored)");
#endif
#endif	/* NWSMUX > 0 */

	printf("\n");
}

int
wsmouse_activate(struct device *self, int act)
{
	struct wsmouse_softc *sc = (struct wsmouse_softc *)self;

	if (act == DVACT_DEACTIVATE)
		sc->sc_dying = 1;
	return (0);
}

/*
 * Detach a mouse.  To keep track of users of the softc we keep
 * a reference count that's incremented while inside, e.g., read.
 * If the mouse is active and the reference count is > 0 (0 is the
 * normal state) we post an event and then wait for the process
 * that had the reference to wake us up again.  Then we blow away the
 * vnode and return (which will deallocate the softc).
 */
int
wsmouse_detach(struct device *self, int flags)
{
	struct wsmouse_softc *sc = (struct wsmouse_softc *)self;
	struct wseventvar *evar;
	int maj, mn;
	int s;

#if NWSMUX > 0
	/* Tell parent mux we're leaving. */
	if (sc->sc_base.me_parent != NULL) {
		DPRINTF(("%s\n", __func__));
		wsmux_detach_sc(&sc->sc_base);
	}
#endif

	/* If we're open ... */
	evar = sc->sc_base.me_evp;
	if (evar != NULL) {
		s = spltty();
		if (--sc->sc_refcnt >= 0) {
			/* Wake everyone by generating a dummy event. */
			if (++evar->put >= WSEVENT_QSIZE)
				evar->put = 0;
			WSEVENT_WAKEUP(evar);
			/* Wait for processes to go away. */
			if (tsleep_nsec(sc, PZERO, "wsmdet", SEC_TO_NSEC(60)))
				printf("wsmouse_detach: %s didn't detach\n",
				       sc->sc_base.me_dv.dv_xname);
		}
		splx(s);
	}

	/* locate the major number */
	for (maj = 0; maj < nchrdev; maj++)
		if (cdevsw[maj].d_open == wsmouseopen)
			break;

	/* Nuke the vnodes for any open instances (calls close). */
	mn = self->dv_unit;
	vdevgone(maj, mn, mn, VCHR);

	wsmouse_input_cleanup(&sc->sc_input);

	return (0);
}

int
wsmouseopen(dev_t dev, int flags, int mode, struct proc *p)
{
	struct wsmouse_softc *sc;
	struct wseventvar *evar;
	int error, unit;

	unit = minor(dev);
	if (unit >= wsmouse_cd.cd_ndevs ||	/* make sure it was attached */
	    (sc = wsmouse_cd.cd_devs[unit]) == NULL)
		return (ENXIO);

#if NWSMUX > 0
	DPRINTF(("%s: %s mux=%p\n", __func__, sc->sc_base.me_dv.dv_xname,
		 sc->sc_base.me_parent));
#endif

	if (sc->sc_dying)
		return (EIO);

	if ((flags & (FREAD | FWRITE)) == FWRITE)
		return (0);			/* always allow open for write
						   so ioctl() is possible. */

#if NWSMUX > 0
	if (sc->sc_base.me_parent != NULL) {
		/* Grab the mouse out of the greedy hands of the mux. */
		DPRINTF(("%s: detach\n", __func__));
		wsmux_detach_sc(&sc->sc_base);
	}
#endif

	if (sc->sc_base.me_evp != NULL)
		return (EBUSY);

	evar = &sc->sc_base.me_evar;
	if (wsevent_init(evar))
		return (EBUSY);

	error = wsmousedoopen(sc, evar);
	if (error)
		wsevent_fini(evar);
	return (error);
}

int
wsmouseclose(dev_t dev, int flags, int mode, struct proc *p)
{
	struct wsmouse_softc *sc =
	    (struct wsmouse_softc *)wsmouse_cd.cd_devs[minor(dev)];
	struct wseventvar *evar = sc->sc_base.me_evp;

	if ((flags & (FREAD | FWRITE)) == FWRITE)
		/* Not open for read */
		return (0);

	sc->sc_base.me_evp = NULL;
	(*sc->sc_accessops->disable)(sc->sc_accesscookie);
	wsevent_fini(evar);

#if NWSMUX > 0
	if (sc->sc_base.me_parent == NULL) {
		int mux, error;

		DPRINTF(("%s: attach\n", __func__));
		mux = sc->sc_base.me_dv.dv_cfdata->wsmousedevcf_mux;
		if (mux >= 0) {
			error = wsmux_attach_sc(wsmux_getmux(mux), &sc->sc_base);
			if (error)
				printf("%s: can't attach mux (error=%d)\n",
				    sc->sc_base.me_dv.dv_xname, error);
		}
	}
#endif

	return (0);
}

int
wsmousedoopen(struct wsmouse_softc *sc, struct wseventvar *evp)
{
	int error;

	/* The device could already be attached to a mux. */
	if (sc->sc_base.me_evp != NULL)
		return (EBUSY);
	sc->sc_base.me_evp = evp;

	wsmouse_input_reset(&sc->sc_input);

	/* enable the device, and punt if that's not possible */
	error = (*sc->sc_accessops->enable)(sc->sc_accesscookie);
	if (error)
		sc->sc_base.me_evp = NULL;
	return (error);
}

int
wsmouseread(dev_t dev, struct uio *uio, int flags)
{
	struct wsmouse_softc *sc = wsmouse_cd.cd_devs[minor(dev)];
	int error;

	if (sc->sc_dying)
		return (EIO);

#ifdef DIAGNOSTIC
	if (sc->sc_base.me_evp == NULL) {
		printf("wsmouseread: evp == NULL\n");
		return (EINVAL);
	}
#endif

	sc->sc_refcnt++;
	error = wsevent_read(sc->sc_base.me_evp, uio, flags);
	if (--sc->sc_refcnt < 0) {
		wakeup(sc);
		error = EIO;
	}
	return (error);
}

int
wsmouseioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
	return (wsmousedoioctl(wsmouse_cd.cd_devs[minor(dev)],
	    cmd, data, flag, p));
}

/* A wrapper around the ioctl() workhorse to make reference counting easy. */
int
wsmousedoioctl(struct device *dv, u_long cmd, caddr_t data, int flag,
    struct proc *p)
{
	struct wsmouse_softc *sc = (struct wsmouse_softc *)dv;
	int error;

	sc->sc_refcnt++;
	error = wsmouse_do_ioctl(sc, cmd, data, flag, p);
	if (--sc->sc_refcnt < 0)
		wakeup(sc);
	return (error);
}

int
wsmouse_param_ioctl(struct wsmouse_softc *sc,
    u_long cmd, struct wsmouse_param *params, u_int nparams)
{
	struct wsmouse_param *buf;
	int error, s, size;

	if (params == NULL || nparams > WSMOUSECFG_MAX)
		return (EINVAL);

	size = nparams * sizeof(struct wsmouse_param);
	buf = malloc(size, M_DEVBUF, M_WAITOK);
	if (buf == NULL)
		return (ENOMEM);

	if ((error = copyin(params, buf, size))) {
		free(buf, M_DEVBUF, size);
		return (error);
	}

	s = spltty();
	if (cmd == WSMOUSEIO_SETPARAMS) {
		if (wsmouse_set_params((struct device *) sc, buf, nparams))
			error = EINVAL;
	} else {
		if (wsmouse_get_params((struct device *) sc, buf, nparams))
			error = EINVAL;
		else
			error = copyout(buf, params, size);
	}
	splx(s);
	free(buf, M_DEVBUF, size);
	return (error);
}

int
wsmouse_do_ioctl(struct wsmouse_softc *sc, u_long cmd, caddr_t data, int flag,
    struct proc *p)
{
	struct wseventvar *evar;
	int error;

	if (sc->sc_dying)
		return (EIO);

	/*
	 * Try the generic ioctls that the wsmouse interface supports.
	 */

	switch (cmd) {
	case FIOASYNC:
	case FIOSETOWN:
	case TIOCSPGRP:
		if ((flag & FWRITE) == 0)
			return (EACCES);
	}

	switch (cmd) {
	case FIONBIO:		/* we will remove this someday (soon???) */
		return (0);

	case FIOASYNC:
		if (sc->sc_base.me_evp == NULL)
			return (EINVAL);
		sc->sc_base.me_evp->async = *(int *)data != 0;
		return (0);

	case FIOGETOWN:
	case TIOCGPGRP:
		evar = sc->sc_base.me_evp;
		if (evar == NULL)
			return (EINVAL);
		sigio_getown(&evar->sigio, cmd, data);
		return (0);

	case FIOSETOWN:
	case TIOCSPGRP:
		evar = sc->sc_base.me_evp;
		if (evar == NULL)
			return (EINVAL);
		return (sigio_setown(&evar->sigio, cmd, data));

	case WSMOUSEIO_GETPARAMS:
	case WSMOUSEIO_SETPARAMS:
		return (wsmouse_param_ioctl(sc, cmd,
		    ((struct wsmouse_parameters *) data)->params,
		    ((struct wsmouse_parameters *) data)->nparams));
	}

	/*
	 * Try the mouse driver for WSMOUSEIO ioctls.  It returns -1
	 * if it didn't recognize the request.
	 */
	error = (*sc->sc_accessops->ioctl)(sc->sc_accesscookie, cmd,
	    data, flag, p);
	return (error != -1 ? error : ENOTTY);
}

int
wsmousekqfilter(dev_t dev, struct knote *kn)
{
	struct wsmouse_softc *sc = wsmouse_cd.cd_devs[minor(dev)];

	if (sc->sc_base.me_evp == NULL)
		return (ENXIO);
	return (wsevent_kqfilter(sc->sc_base.me_evp, kn));
}

#if NWSMUX > 0
int
wsmouse_mux_open(struct wsevsrc *me, struct wseventvar *evp)
{
	struct wsmouse_softc *sc = (struct wsmouse_softc *)me;

	return (wsmousedoopen(sc, evp));
}

int
wsmouse_mux_close(struct wsevsrc *me)
{
	struct wsmouse_softc *sc = (struct wsmouse_softc *)me;

	(*sc->sc_accessops->disable)(sc->sc_accesscookie);
	sc->sc_base.me_evp = NULL;

	return (0);
}

int
wsmouse_add_mux(int unit, struct wsmux_softc *muxsc)
{
	struct wsmouse_softc *sc;

	if (unit < 0 || unit >= wsmouse_cd.cd_ndevs ||
	    (sc = wsmouse_cd.cd_devs[unit]) == NULL)
		return (ENXIO);

	if (sc->sc_base.me_parent != NULL || sc->sc_base.me_evp != NULL)
		return (EBUSY);

	return (wsmux_attach_sc(muxsc, &sc->sc_base));
}
#endif	/* NWSMUX > 0 */

void
wsmouse_buttons(struct device *sc, u_int buttons)
{
	struct btn_state *btn = &((struct wsmouse_softc *) sc)->sc_input.btn;

	if (btn->sync)
		/* Restore the old state. */
		btn->buttons ^= btn->sync;

	btn->sync = btn->buttons ^ buttons;
	btn->buttons = buttons;
}

void
wsmouse_motion(struct device *sc, int dx, int dy, int dz, int dw)
{
	struct motion_state *motion =
	    &((struct wsmouse_softc *) sc)->sc_input.motion;

	motion->dx = dx;
	motion->dy = dy;
	motion->dz = dz;
	motion->dw = dw;
	if (dx || dy || dz || dw)
		motion->sync |= SYNC_DELTAS;
}

static inline void
set_x(struct position *pos, int x, u_int *sync, u_int mask)
{
	if (*sync & mask) {
		if (x == pos->x)
			return;
		pos->x -= pos->dx;
		pos->acc_dx -= pos->dx;
	}
	if ((pos->dx = x - pos->x)) {
		pos->x = x;
		if ((pos->dx > 0) == (pos->acc_dx > 0))
			pos->acc_dx += pos->dx;
		else
			pos->acc_dx = pos->dx;
		*sync |= mask;
	}
}

static inline void
set_y(struct position *pos, int y, u_int *sync, u_int mask)
{
	if (*sync & mask) {
		if (y == pos->y)
			return;
		pos->y -= pos->dy;
		pos->acc_dy -= pos->dy;
	}
	if ((pos->dy = y - pos->y)) {
		pos->y = y;
		if ((pos->dy > 0) == (pos->acc_dy > 0))
			pos->acc_dy += pos->dy;
		else
			pos->acc_dy = pos->dy;
		*sync |= mask;
	}
}

static inline void
cleardeltas(struct position *pos)
{
	pos->dx = pos->acc_dx = 0;
	pos->dy = pos->acc_dy = 0;
}

void
wsmouse_position(struct device *sc, int x, int y)
{
	struct motion_state *motion =
	    &((struct wsmouse_softc *) sc)->sc_input.motion;

	set_x(&motion->pos, x, &motion->sync, SYNC_X);
	set_y(&motion->pos, y, &motion->sync, SYNC_Y);
}

static inline int
normalized_pressure(struct wsmouseinput *input, int pressure)
{
	int limit = imax(input->touch.min_pressure, 1);

	if (pressure >= limit)
		return pressure;
	else
		return (pressure < 0 ? limit : 0);
}

void
wsmouse_touch(struct device *sc, int pressure, int contacts)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct touch_state *touch = &input->touch;

	pressure = normalized_pressure(input, pressure);
	contacts = (pressure ? imax(contacts, 1) : 0);

	if (pressure == 0 || pressure != touch->pressure) {
		/*
		 * pressure == 0: Drivers may report possibly arbitrary
		 * coordinates in this case; touch_update will correct them.
		 */
		touch->pressure = pressure;
		touch->sync |= SYNC_PRESSURE;
	}
	if (contacts != touch->contacts) {
		touch->contacts = contacts;
		touch->sync |= SYNC_CONTACTS;
	}
}

void
wsmouse_mtstate(struct device *sc, int slot, int x, int y, int pressure)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct mt_state *mt = &input->mt;
	struct mt_slot *mts;
	u_int bit;

	if (slot < 0 || slot >= mt->num_slots)
		return;

	bit = (1 << slot);
	mt->frame |= bit;

	mts = &mt->slots[slot];

	set_x(&mts->pos, x, mt->sync + MTS_X, bit);
	set_y(&mts->pos, y, mt->sync + MTS_Y, bit);

	/* Is this a new touch? */
	if ((mt->touches & bit) == (mt->sync[MTS_TOUCH] & bit))
		cleardeltas(&mts->pos);

	pressure = normalized_pressure(input, pressure);
	if (pressure != mts->pressure) {
		mts->pressure = pressure;
		mt->sync[MTS_PRESSURE] |= bit;

		if (pressure) {
			if ((mt->touches & bit) == 0) {
				mt->num_touches++;
				mt->touches |= bit;
				mt->sync[MTS_TOUCH] |= bit;

				mt->sync[MTS_X] |= bit;
				mt->sync[MTS_Y] |= bit;
			}
		} else if (mt->touches & bit) {
			mt->num_touches--;
			mt->touches ^= bit;
			mt->sync[MTS_TOUCH] |= bit;
			mt->ptr_mask &= mt->touches;
		}
	}
}

void
wsmouse_set(struct device *sc, enum wsmouseval type, int value, int aux)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct mt_slot *mts;

	if (WSMOUSE_IS_MT_CODE(type)) {
		if (aux < 0 || aux >= input->mt.num_slots)
			return;
		mts = &input->mt.slots[aux];
	}

	switch (type) {
	case WSMOUSE_REL_X:
		value += input->motion.pos.x; /* fall through */
	case WSMOUSE_ABS_X:
		wsmouse_position(sc, value, input->motion.pos.y);
		return;
	case WSMOUSE_REL_Y:
		value += input->motion.pos.y; /* fall through */
	case WSMOUSE_ABS_Y:
		wsmouse_position(sc, input->motion.pos.x, value);
		return;
	case WSMOUSE_PRESSURE:
		wsmouse_touch(sc, value, input->touch.contacts);
		return;
	case WSMOUSE_CONTACTS:
		/* Contact counts can be overridden by wsmouse_touch. */
		if (value != input->touch.contacts) {
			input->touch.contacts = value;
			input->touch.sync |= SYNC_CONTACTS;
		}
		return;
	case WSMOUSE_TOUCH_WIDTH:
		if (value != input->touch.width) {
			input->touch.width = value;
			input->touch.sync |= SYNC_TOUCH_WIDTH;
		}
		return;
	case WSMOUSE_MT_REL_X:
		value += mts->pos.x; /* fall through */
	case WSMOUSE_MT_ABS_X:
		wsmouse_mtstate(sc, aux, value, mts->pos.y, mts->pressure);
		return;
	case WSMOUSE_MT_REL_Y:
		value += mts->pos.y; /* fall through */
	case WSMOUSE_MT_ABS_Y:
		wsmouse_mtstate(sc, aux, mts->pos.x, value, mts->pressure);
		return;
	case WSMOUSE_MT_PRESSURE:
		wsmouse_mtstate(sc, aux, mts->pos.x, mts->pos.y, value);
		return;
	}
}

/* Make touch and motion state consistent. */
void
wsmouse_touch_update(struct wsmouseinput *input)
{
	struct motion_state *motion = &input->motion;
	struct touch_state *touch = &input->touch;

	if (touch->pressure == 0) {
		/*
		 * There may be zero coordinates, or coordinates of
		 * touches with pressure values below min_pressure.
		 */
		if (motion->sync & SYNC_POSITION) {
			/* Restore valid coordinates. */
			motion->pos.x -= motion->pos.dx;
			motion->pos.y -= motion->pos.dy;
			motion->sync &= ~SYNC_POSITION;
		}

		if (touch->prev_contacts == 0)
			touch->sync &= ~SYNC_PRESSURE;

	}

	if (touch->sync & SYNC_CONTACTS)
		/* Suppress pointer movement. */
		cleardeltas(&motion->pos);

	if ((touch->sync & SYNC_PRESSURE) && touch->min_pressure) {
		if (touch->pressure >= input->filter.pressure_hi)
			touch->min_pressure = input->filter.pressure_lo;
		else if (touch->pressure < input->filter.pressure_lo)
			touch->min_pressure = input->filter.pressure_hi;
	}
}

/* Normalize multitouch state. */
void
wsmouse_mt_update(struct wsmouseinput *input)
{
	int i;

	/*
	 * The same as above: There may be arbitrary coordinates if
	 * (pressure == 0). Clear the sync flags for touches that have
	 * been released.
	 */
	if (input->mt.frame & ~input->mt.touches) {
		for (i = MTS_X; i < MTS_SIZE; i++)
			input->mt.sync[i] &= input->mt.touches;
	}
}

/* Return TRUE if a coordinate update may be noise. */
int
wsmouse_hysteresis(struct wsmouseinput *input, struct position *pos)
{
	return (abs(pos->acc_dx) < input->filter.h.hysteresis
	    && abs(pos->acc_dy) < input->filter.v.hysteresis);
}

/*
 * Select the pointer-controlling MT slot.
 *
 * Pointer-control is assigned to slots with non-zero motion deltas if
 * at least one such slot exists. This function doesn't impose any
 * restrictions on the way drivers use wsmouse_mtstate(), it covers
 * partial, unordered, and "delta-filtered" input.
 *
 * The "cycle" is the set of slots with X/Y updates in previous sync
 * operations; it will be cleared and rebuilt whenever a slot that is
 * being updated is already a member. If a cycle ends that doesn't
 * contain the pointer-controlling slot, a new slot will be selected.
 */
void
wsmouse_ptr_ctrl(struct wsmouseinput *input)
{
	struct mt_state *mt = &input->mt;
	u_int updates;
	int select, slot;

	mt->prev_ptr = mt->ptr;

	if (mt->num_touches <= 1) {
		mt->ptr = mt->touches;
		mt->ptr_cycle = mt->ptr;
		return;
	}

	updates = (mt->sync[MTS_X] | mt->sync[MTS_Y]) & ~mt->sync[MTS_TOUCH];
	FOREACHBIT(updates, slot) {
		/*
		 * Touches that just produce noise are no problem if the
		 * frequency of zero deltas is high enough, but there might
		 * be no guarantee for that.
		 */
		if (wsmouse_hysteresis(input, &mt->slots[slot].pos))
			updates ^= (1 << slot);
	}

	/*
	 * If there is no pointer-controlling slot, or if it should be
	 * masked, select a new one.
	 */
	select = ((mt->ptr & mt->touches & ~mt->ptr_mask) == 0);

	/* Remove slots without coordinate deltas from the cycle. */
	mt->ptr_cycle &= ~(mt->frame ^ updates);

	if (mt->ptr_cycle & updates) {
		select |= ((mt->ptr_cycle & mt->ptr) == 0);
		mt->ptr_cycle = updates;
	} else {
		mt->ptr_cycle |= updates;
	}
	if (select) {
		if (mt->ptr_cycle & ~mt->ptr_mask)
			slot = ffs(mt->ptr_cycle & ~mt->ptr_mask) - 1;
		else if (mt->touches & ~mt->ptr_mask)
			slot = ffs(mt->touches & ~mt->ptr_mask) - 1;
		else
			slot = ffs(mt->touches) - 1;
		mt->ptr = (1 << slot);
	}
}

/* Derive touch and motion state from MT state. */
void
wsmouse_mt_convert(struct device *sc)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct mt_state *mt = &input->mt;
	struct mt_slot *mts;
	int slot, pressure;

	wsmouse_ptr_ctrl(input);

	if (mt->ptr) {
		slot = ffs(mt->ptr) - 1;
		mts = &mt->slots[slot];
		if (mts->pos.x != input->motion.pos.x)
			input->motion.sync |= SYNC_X;
		if (mts->pos.y != input->motion.pos.y)
			input->motion.sync |= SYNC_Y;
		if (mt->ptr != mt->prev_ptr)
			/* Suppress pointer movement. */
			mts->pos.dx = mts->pos.dy = 0;
		memcpy(&input->motion.pos, &mts->pos, sizeof(struct position));

		pressure = mts->pressure;
	} else {
		pressure = 0;
	}

	wsmouse_touch(sc, pressure, mt->num_touches);
}

void
wsmouse_evq_put(struct evq_access *evq, int ev_type, int ev_value)
{
	struct wscons_event *ev;
	int space;

	space = evq->evar->get - evq->put;
	if (space != 1 && space != 1 - WSEVENT_QSIZE) {
		ev = &evq->evar->q[evq->put++];
		evq->put %= WSEVENT_QSIZE;
		ev->type = ev_type;
		ev->value = ev_value;
		memcpy(&ev->time, &evq->ts, sizeof(struct timespec));
		evq->result |= EVQ_RESULT_SUCCESS;
	} else {
		evq->result = EVQ_RESULT_OVERFLOW;
	}
}


void
wsmouse_btn_sync(struct btn_state *btn, struct evq_access *evq)
{
	int button, ev_type;
	u_int bit, sync;

	for (sync = btn->sync; sync; sync ^= bit) {
		button = ffs(sync) - 1;
		bit = (1 << button);
		ev_type = (btn->buttons & bit) ? BTN_DOWN_EV : BTN_UP_EV;
		wsmouse_evq_put(evq, ev_type, button);
	}
}

/*
 * Scale with a [*.12] fixed-point factor and a remainder:
 */
static inline int
scale(int val, int factor, int *rmdr)
{
	val = val * factor + *rmdr;
	if (val >= 0) {
		*rmdr = val & 0xfff;
		return (val >> 12);
	} else {
		*rmdr = -(-val & 0xfff);
		return -(-val >> 12);
	}
}

void
wsmouse_motion_sync(struct wsmouseinput *input, struct evq_access *evq)
{
	struct motion_state *motion = &input->motion;
	struct axis_filter *h = &input->filter.h;
	struct axis_filter *v = &input->filter.v;
	int x, y, dx, dy, dz, dw;

	if (motion->sync & SYNC_DELTAS) {
		dx = h->inv ? -motion->dx : motion->dx;
		dy = v->inv ? -motion->dy : motion->dy;
		if (h->scale)
			dx = scale(dx, h->scale, &h->rmdr);
		if (v->scale)
			dy = scale(dy, v->scale, &v->rmdr);
		if (dx)
			wsmouse_evq_put(evq, DELTA_X_EV(input), dx);
		if (dy)
			wsmouse_evq_put(evq, DELTA_Y_EV(input), dy);
		if (motion->dz) {
			dz = (input->flags & REVERSE_SCROLLING)
			    ? -motion->dz : motion->dz;
			if (IS_TOUCHPAD(input))
				wsmouse_evq_put(evq, VSCROLL_EV, dz);
			else
				wsmouse_evq_put(evq, DELTA_Z_EV, dz);
		}
		if (motion->dw) {
			dw = (input->flags & REVERSE_SCROLLING)
			    ? -motion->dw : motion->dw;
			if (IS_TOUCHPAD(input))
				wsmouse_evq_put(evq, HSCROLL_EV, dw);
			else
				wsmouse_evq_put(evq, DELTA_W_EV, dw);
		}
	}
	if (motion->sync & SYNC_POSITION) {
		if (motion->sync & SYNC_X) {
			x = (h->inv ? h->inv - motion->pos.x : motion->pos.x);
			wsmouse_evq_put(evq, ABS_X_EV(input), x);
		}
		if (motion->sync & SYNC_Y) {
			y = (v->inv ? v->inv - motion->pos.y : motion->pos.y);
			wsmouse_evq_put(evq, ABS_Y_EV(input), y);
		}
		if (motion->pos.dx == 0 && motion->pos.dy == 0
		    && (input->flags & TPAD_NATIVE_MODE ))
			/* Suppress pointer motion. */
			wsmouse_evq_put(evq, WSCONS_EVENT_TOUCH_RESET, 0);
	}
}

void
wsmouse_touch_sync(struct wsmouseinput *input, struct evq_access *evq)
{
	struct touch_state *touch = &input->touch;

	if (touch->sync & SYNC_PRESSURE)
		wsmouse_evq_put(evq, ABS_Z_EV, touch->pressure);
	if (touch->sync & SYNC_CONTACTS)
		wsmouse_evq_put(evq, ABS_W_EV, touch->contacts);
	if ((touch->sync & SYNC_TOUCH_WIDTH)
	    && (input->flags & TPAD_NATIVE_MODE))
		wsmouse_evq_put(evq, WSCONS_EVENT_TOUCH_WIDTH, touch->width);
}

void
wsmouse_log_input(struct wsmouseinput *input, struct timespec *ts)
{
	struct motion_state *motion = &input->motion;
	int t_sync, mt_sync;

	t_sync = (input->touch.sync & SYNC_CONTACTS);
	mt_sync = (input->mt.frame && (input->mt.sync[MTS_TOUCH]
	    || input->mt.ptr != input->mt.prev_ptr));

	if (motion->sync || mt_sync || t_sync || input->btn.sync)
		printf("[%s-in][%04d]", DEVNAME(input), LOGTIME(ts));
	else
		return;

	if (motion->sync & SYNC_POSITION)
		printf(" abs:%d,%d", motion->pos.x, motion->pos.y);
	if (motion->sync & SYNC_DELTAS)
		printf(" rel:%d,%d,%d,%d", motion->dx, motion->dy,
		    motion->dz, motion->dw);
	if (mt_sync)
		printf(" mt:0x%02x:%d", input->mt.touches,
		    ffs(input->mt.ptr) - 1);
	else if (t_sync)
		printf(" t:%d", input->touch.contacts);
	if (input->btn.sync)
		printf(" btn:0x%02x", input->btn.buttons);
	printf("\n");
}

void
wsmouse_log_events(struct wsmouseinput *input, struct evq_access *evq)
{
	struct wscons_event *ev;
	int n = evq->evar->put;

	if (n != evq->put) {
		printf("[%s-ev][%04d]", DEVNAME(input), LOGTIME(&evq->ts));
		while (n != evq->put) {
			ev = &evq->evar->q[n++];
			n %= WSEVENT_QSIZE;
			printf(" %d:%d", ev->type, ev->value);
		}
		printf("\n");
	}
}

static inline void
clear_sync_flags(struct wsmouseinput *input)
{
	int i;

	input->btn.sync = 0;
	input->sbtn.sync = 0;
	input->motion.sync = 0;
	input->touch.sync = 0;
	input->touch.prev_contacts = input->touch.contacts;
	if (input->mt.frame) {
		input->mt.frame = 0;
		for (i = 0; i < MTS_SIZE; i++)
			input->mt.sync[i] = 0;
	}
}

void
wsmouse_input_sync(struct device *sc)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct evq_access evq;

	evq.evar = *input->evar;
	if (evq.evar == NULL)
		return;
	evq.put = evq.evar->put;
	evq.result = EVQ_RESULT_NONE;
	getnanotime(&evq.ts);

	enqueue_randomness(input->btn.buttons
	    ^ input->motion.dx ^ input->motion.dy
	    ^ input->motion.pos.x ^ input->motion.pos.y
	    ^ input->motion.dz ^ input->motion.dw);

	if (input->mt.frame) {
		wsmouse_mt_update(input);
		wsmouse_mt_convert(sc);
	}
	if (input->touch.sync)
		wsmouse_touch_update(input);

	if (input->flags & LOG_INPUT)
		wsmouse_log_input(input, &evq.ts);

	if (input->flags & TPAD_COMPAT_MODE)
		wstpad_compat_convert(input, &evq);

	if (input->flags & RESYNC) {
		input->flags &= ~RESYNC;
		input->motion.sync &= SYNC_POSITION;
	}

	if (input->btn.sync)
		wsmouse_btn_sync(&input->btn, &evq);
	if (input->sbtn.sync)
		wsmouse_btn_sync(&input->sbtn, &evq);
	if (input->motion.sync)
		wsmouse_motion_sync(input, &evq);
	if (input->touch.sync)
		wsmouse_touch_sync(input, &evq);
	/* No MT events are generated yet. */

	if (evq.result == EVQ_RESULT_SUCCESS) {
		wsmouse_evq_put(&evq, WSCONS_EVENT_SYNC, 0);
		if (evq.result == EVQ_RESULT_SUCCESS) {
			if (input->flags & LOG_EVENTS) {
				wsmouse_log_events(input, &evq);
			}
			evq.evar->put = evq.put;
			WSEVENT_WAKEUP(evq.evar);
		}
	}

	if (evq.result != EVQ_RESULT_OVERFLOW)
		clear_sync_flags(input);
	else
		input->flags |= RESYNC;
}

int
wsmouse_id_to_slot(struct device *sc, int id)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct mt_state *mt = &input->mt;
	int slot;

	if (mt->num_slots == 0)
		return (-1);

	FOREACHBIT(mt->touches, slot) {
		if (mt->slots[slot].id == id)
			return slot;
	}
	slot = ffs(~(mt->touches | mt->frame)) - 1;
	if (slot >= 0 && slot < mt->num_slots) {
		mt->frame |= 1 << slot;
		mt->slots[slot].id = id;
		return (slot);
	} else {
		return (-1);
	}
}

/*
 * Find a minimum-weight matching for an m-by-n matrix.
 *
 * m must be greater than or equal to n. The size of the buffer must be
 * at least 3m + 3n.
 *
 * On return, the first m elements of the buffer contain the row-to-
 * column mappings, i.e., buffer[i] is the column index for row i, or -1
 * if there is no assignment for that row (which may happen if n < m).
 *
 * Wrong results because of overflows will not occur with input values
 * in the range of 0 to INT_MAX / 2 inclusive.
 *
 * The function applies the Dinic-Kronrod algorithm. It is not modern or
 * popular, but it seems to be a good choice for small matrices at least.
 * The original form of the algorithm is modified as follows: There is no
 * initial search for row minima, the initial assignments are in a
 * "virtual" column with the index -1 and zero values. This permits inputs
 * with n < m, and it simplifies the reassignments.
 */
void
wsmouse_matching(int *matrix, int m, int n, int *buffer)
{
	int i, j, k, d, e, row, col, delta;
	int *p;
	int *r2c = buffer;	/* row-to-column assignments */
	int *red = r2c + m;	/* reduced values of the assignments */
	int *mc = red + m;	/* row-wise minimal elements of cs */
	int *cs = mc + m;	/* the column set */
	int *c2r = cs + n;	/* column-to-row assignments in cs */
	int *cd = c2r + n;	/* column deltas (reduction) */

	for (p = r2c; p < red; *p++ = -1) {}
	for (; p < mc; *p++ = 0) {}
	for (col = 0; col < n; col++) {
		delta = INT_MAX;
		for (i = 0, p = matrix + col; i < m; i++, p += n) {
			d = *p - red[i];
			if (d < delta || (d == delta && r2c[i] < 0)) {
				delta = d;
				row = i;
			}
		}
		cd[col] = delta;
		if (r2c[row] < 0) {
			r2c[row] = col;
			continue;
		}
		for (p = mc; p < cs; *p++ = col) {}
		for (k = 0; (j = r2c[row]) >= 0;) {
			cs[k++] = j;
			c2r[j] = row;
			mc[row] -= n;
			delta = INT_MAX;
			for (i = 0, p = matrix; i < m; i++, p += n)
				if (mc[i] >= 0) {
					d = p[mc[i]] - cd[mc[i]];
					e = p[j] - cd[j];
					if (e < d) {
						d = e;
						mc[i] = j;
					}
					d -= red[i];
					if (d < delta || (d == delta
					    && r2c[i] < 0)) {
						delta = d;
						row = i;
					}
				}
			cd[col] += delta;
			for (i = 0; i < k; i++) {
				cd[cs[i]] += delta;
				red[c2r[cs[i]]] -= delta;
			}
		}
		for (j = mc[row]; (r2c[row] = j) != col;) {
			row = c2r[j];
			j = mc[row] + n;
		}
	}
}

/*
 * Assign slot numbers to the points in the pt array, and update all slots by
 * calling wsmouse_mtstate internally.  The slot numbers are passed to the
 * caller in the pt->slot fields.
 *
 * The slot assignment pairs the points with points of the previous frame in
 * such a way that the sum of the squared distances is minimal.  Using
 * squares instead of simple distances favours assignments with more uniform
 * distances, and it is faster.
 */
void
wsmouse_mtframe(struct device *sc, struct mtpoint *pt, int size)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	struct mt_state *mt = &input->mt;
	int i, j, m, n, dx, dy, slot, maxdist;
	int *p, *r2c, *c2r;
	u_int touches;

	if (mt->num_slots == 0 || mt->matrix == NULL)
		return;

	size = imax(0, imin(size, mt->num_slots));
	p = mt->matrix;
	touches = mt->touches;
	if (mt->num_touches >= size) {
		FOREACHBIT(touches, slot)
			for (i = 0; i < size; i++) {
				dx = pt[i].x - mt->slots[slot].pos.x;
				dy = pt[i].y - mt->slots[slot].pos.y;
				*p++ = dx * dx + dy * dy;
			}
		m = mt->num_touches;
		n = size;
	} else {
		for (i = 0; i < size; i++)
			FOREACHBIT(touches, slot) {
				dx = pt[i].x - mt->slots[slot].pos.x;
				dy = pt[i].y - mt->slots[slot].pos.y;
				*p++ = dx * dx + dy * dy;
			}
		m = size;
		n = mt->num_touches;
	}
	wsmouse_matching(mt->matrix, m, n, p);

	r2c = p;
	c2r = p + m;
	maxdist = input->filter.tracking_maxdist;
	maxdist = (maxdist ? maxdist * maxdist : INT_MAX);
	for (i = 0, p = mt->matrix; i < m; i++, p += n)
		if ((j = r2c[i]) >= 0) {
			if (p[j] <= maxdist)
				c2r[j] = i;
			else
				c2r[j] = r2c[i] = -1;
		}

	p = (n == size ? c2r : r2c);
	for (i = 0; i < size; i++)
		if (*p++ < 0) {
			slot = ffs(~(mt->touches | mt->frame)) - 1;
			if (slot < 0 || slot >= mt->num_slots)
				break;
			wsmouse_mtstate(sc, slot,
			    pt[i].x, pt[i].y, pt[i].pressure);
			pt[i].slot = slot;
		}

	p = (n == size ? r2c : c2r);
	FOREACHBIT(touches, slot)
		if ((i = *p++) >= 0) {
			wsmouse_mtstate(sc, slot,
			    pt[i].x, pt[i].y, pt[i].pressure);
			pt[i].slot = slot;
		} else {
			wsmouse_mtstate(sc, slot, 0, 0, 0);
		}
}

static inline void
free_mt_slots(struct wsmouseinput *input)
{
	int n, size;

	if ((n = input->mt.num_slots)) {
		size = n * sizeof(struct mt_slot);
		if (input->flags & MT_TRACKING)
			size += MATRIX_SIZE(n);
		input->mt.num_slots = 0;
		free(input->mt.slots, M_DEVBUF, size);
		input->mt.slots = NULL;
		input->mt.matrix = NULL;
	}
}

/* Allocate the MT slots and, if necessary, the buffers for MT tracking. */
int
wsmouse_mt_init(struct device *sc, int num_slots, int tracking)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	int n, size;

	if (num_slots == input->mt.num_slots
	    && (!tracking == ((input->flags & MT_TRACKING) == 0)))
		return (0);

	free_mt_slots(input);

	if (tracking)
		input->flags |= MT_TRACKING;
	else
		input->flags &= ~MT_TRACKING;
	n = imin(imax(num_slots, 0), WSMOUSE_MT_SLOTS_MAX);
	if (n) {
		size = n * sizeof(struct mt_slot);
		if (input->flags & MT_TRACKING)
			size += MATRIX_SIZE(n);
		input->mt.slots = malloc(size, M_DEVBUF, M_WAITOK | M_ZERO);
		if (input->mt.slots != NULL) {
			if (input->flags & MT_TRACKING)
				input->mt.matrix = (int *)
				    (input->mt.slots + n);
			input->mt.num_slots = n;
			return (0);
		}
	}
	return (-1);
}

int
wsmouse_get_params(struct device *sc,
    struct wsmouse_param *params, u_int nparams)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	int i, key, error = 0;

	for (i = 0; i < nparams; i++) {
		key = params[i].key;
		switch (key) {
		case WSMOUSECFG_DX_SCALE:
			params[i].value = input->filter.h.scale;
			break;
		case WSMOUSECFG_DY_SCALE:
			params[i].value = input->filter.v.scale;
			break;
		case WSMOUSECFG_PRESSURE_LO:
			params[i].value = input->filter.pressure_lo;
			break;
		case WSMOUSECFG_PRESSURE_HI:
			params[i].value = input->filter.pressure_hi;
			break;
		case WSMOUSECFG_TRKMAXDIST:
			params[i].value = input->filter.tracking_maxdist;
			break;
		case WSMOUSECFG_SWAPXY:
			params[i].value = input->filter.swapxy;
			break;
		case WSMOUSECFG_X_INV:
			params[i].value = input->filter.h.inv;
			break;
		case WSMOUSECFG_Y_INV:
			params[i].value = input->filter.v.inv;
			break;
		case WSMOUSECFG_REVERSE_SCROLLING:
			params[i].value = !!(input->flags & REVERSE_SCROLLING);
			break;
		case WSMOUSECFG_DX_MAX:
			params[i].value = input->filter.h.dmax;
			break;
		case WSMOUSECFG_DY_MAX:
			params[i].value = input->filter.v.dmax;
			break;
		case WSMOUSECFG_X_HYSTERESIS:
			params[i].value = input->filter.h.hysteresis;
			break;
		case WSMOUSECFG_Y_HYSTERESIS:
			params[i].value = input->filter.v.hysteresis;
			break;
		case WSMOUSECFG_DECELERATION:
			params[i].value = input->filter.dclr;
			break;
		case WSMOUSECFG_STRONG_HYSTERESIS:
			params[i].value = 0; /* The feature has been removed. */
			break;
		case WSMOUSECFG_SMOOTHING:
			params[i].value =
			    input->filter.mode & SMOOTHING_MASK;
			break;
		case WSMOUSECFG_LOG_INPUT:
			params[i].value = !!(input->flags & LOG_INPUT);
			break;
		case WSMOUSECFG_LOG_EVENTS:
			params[i].value = !!(input->flags & LOG_EVENTS);
			break;
		default:
			error = wstpad_get_param(input, key, &params[i].value);
			if (error != 0)
				return (error);
			break;
		}
	}

	return (0);
}

int
wsmouse_set_params(struct device *sc,
    const struct wsmouse_param *params, u_int nparams)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	int i, val, key, needreset = 0, error = 0;

	for (i = 0; i < nparams; i++) {
		key = params[i].key;
		val = params[i].value;
		switch (params[i].key) {
		case WSMOUSECFG_PRESSURE_LO:
			input->filter.pressure_lo = val;
			if (val > input->filter.pressure_hi)
				input->filter.pressure_hi = val;
			input->touch.min_pressure = input->filter.pressure_hi;
			break;
		case WSMOUSECFG_PRESSURE_HI:
			input->filter.pressure_hi = val;
			if (val < input->filter.pressure_lo)
				input->filter.pressure_lo = val;
			input->touch.min_pressure = val;
			break;
		case WSMOUSECFG_X_HYSTERESIS:
			input->filter.h.hysteresis = val;
			break;
		case WSMOUSECFG_Y_HYSTERESIS:
			input->filter.v.hysteresis = val;
			break;
		case WSMOUSECFG_DECELERATION:
			input->filter.dclr = val;
			wstpad_init_deceleration(input);
			break;
		case WSMOUSECFG_DX_SCALE:
			input->filter.h.scale = val;
			break;
		case WSMOUSECFG_DY_SCALE:
			input->filter.v.scale = val;
			break;
		case WSMOUSECFG_TRKMAXDIST:
			input->filter.tracking_maxdist = val;
			break;
		case WSMOUSECFG_SWAPXY:
			input->filter.swapxy = val;
			break;
		case WSMOUSECFG_X_INV:
			input->filter.h.inv = val;
			break;
		case WSMOUSECFG_Y_INV:
			input->filter.v.inv = val;
			break;
		case WSMOUSECFG_REVERSE_SCROLLING:
			if (val)
				input->flags |= REVERSE_SCROLLING;
			else
				input->flags &= ~REVERSE_SCROLLING;
			break;
		case WSMOUSECFG_DX_MAX:
			input->filter.h.dmax = val;
			break;
		case WSMOUSECFG_DY_MAX:
			input->filter.v.dmax = val;
			break;
		case WSMOUSECFG_SMOOTHING:
			input->filter.mode &= ~SMOOTHING_MASK;
			input->filter.mode |= (val & SMOOTHING_MASK);
			break;
		case WSMOUSECFG_LOG_INPUT:
			if (val)
				input->flags |= LOG_INPUT;
			else
				input->flags &= ~LOG_INPUT;
			break;
		case WSMOUSECFG_LOG_EVENTS:
			if (val)
				input->flags |= LOG_EVENTS;
			else
				input->flags &= ~LOG_EVENTS;
			break;
		default:
			needreset = 1;
			error = wstpad_set_param(input, key, val);
			if (error != 0)
				return (error);
			break;
		}
	}

	/* Reset soft-states if touchpad parameters changed */
	if (needreset) {
		wstpad_reset(input);
		return (wstpad_configure(input));
	}

	return (0);
}

int
wsmouse_set_mode(struct device *sc, int mode)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;

	if (mode == WSMOUSE_COMPAT) {
		input->flags &= ~TPAD_NATIVE_MODE;
		input->flags |= TPAD_COMPAT_MODE;
		return (0);
	} else if (mode == WSMOUSE_NATIVE) {
		input->flags &= ~TPAD_COMPAT_MODE;
		input->flags |= TPAD_NATIVE_MODE;
		return (0);
	}
	return (-1);
}

struct wsmousehw *wsmouse_get_hw(struct device *sc)
{
	return &((struct wsmouse_softc *) sc)->sc_input.hw;
}

/*
 * Create a default configuration based on the hardware infos in the 'hw'
 * fields. The 'params' argument is optional, hardware drivers can use it
 * to modify the generic defaults. Up to now this function is only useful
 * for touchpads.
 */
int
wsmouse_configure(struct device *sc,
    struct wsmouse_param *params, u_int nparams)
{
	struct wsmouseinput *input = &((struct wsmouse_softc *) sc)->sc_input;
	int error;

	if (!(input->flags & CONFIGURED)) {
		if (input->hw.x_max && input->hw.y_max) {
			if (input->hw.flags & WSMOUSEHW_LR_DOWN) {
				input->filter.v.inv =
				    input->hw.y_max + input->hw.y_min;
			}
		}
		input->filter.ratio = 1 << 12;
		if (input->hw.h_res > 0 && input->hw.v_res > 0) {
			input->filter.ratio *= input->hw.h_res;
			input->filter.ratio /= input->hw.v_res;
		}
		if (wsmouse_mt_init(sc, input->hw.mt_slots,
		    (input->hw.flags & WSMOUSEHW_MT_TRACKING))) {
			printf("wsmouse_configure: "
			    "MT initialization failed.\n");
			return (-1);
		}
		if (IS_TOUCHPAD(input) && wstpad_configure(input)) {
			printf("wstpad_configure: "
			    "Initialization failed.\n");
			return (-1);
		}
		input->flags |= CONFIGURED;
		if (params != NULL) {
			if ((error = wsmouse_set_params(sc, params, nparams)))
				return (error);
		}
	}
	if (IS_TOUCHPAD(input))
		wsmouse_set_mode(sc, WSMOUSE_COMPAT);

	return (0);
}


void
wsmouse_input_reset(struct wsmouseinput *input)
{
	int num_slots, *matrix;
	struct mt_slot *slots;

	memset(&input->btn, 0, sizeof(struct btn_state));
	memset(&input->motion, 0, sizeof(struct motion_state));
	memset(&input->touch, 0, sizeof(struct touch_state));
	input->touch.min_pressure = input->filter.pressure_hi;
	if ((num_slots = input->mt.num_slots)) {
		slots = input->mt.slots;
		matrix = input->mt.matrix;
		memset(&input->mt, 0, sizeof(struct mt_state));
		memset(slots, 0, num_slots * sizeof(struct mt_slot));
		input->mt.num_slots = num_slots;
		input->mt.slots = slots;
		input->mt.matrix = matrix;
	}
	if (input->tp != NULL)
		wstpad_reset(input);
}

void
wsmouse_input_cleanup(struct wsmouseinput *input)
{
	if (input->tp != NULL)
		wstpad_cleanup(input);

	free_mt_slots(input);
}
