From 1f6b5df06ecbfc7d98e20e7b31a8cd687067caf8 Mon Sep 17 00:00:00 2001
From: Michael Neumann <mneumann@ntecs.de>
Date: Sat, 6 Apr 2024 12:40:13 +0200
Subject: [PATCH] Sync psm/evdev/atkbd with FreeBSD
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

psm - Fix choosing wrong mode for synaptic device + trackpoint

With guest trackpoint present trackpoint probing switched synaptics
device to absolute mode with different protocol instead of keeping it
in relative mode.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/dcc414b888bd855d3ad30bea0e20fb35733097cb

psm - Fix calculation for clickpad softbuttons at the top

On laptops like the ThinkPad X240, ClickPad buttons are located at the
top. The hw.psm.synaptics.softbuttons_y sysctl was supposed to allow this
by setting the value to a negative one (e.g. -1700). However, the
condition was wrong (double negative), and doing that placed the buttons
in an unreachable area.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/f3bae0ea25e2f2b76f5dcd8a6a800344803a900f

psm - Fix triple-finger taps reported as double-finger for Elan hw v.4 touchpads

Wait for all advertised head packets after status packet have been received.
This fixes rare but quite annoying issue in Elan hw v.4 touchpads support
when triple-finger taps are reported as double-finger taps under several
circumstances.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/933ef2d3b285966c604d21848963afc9e9a07a0b

psm - Reduce default tap_min_queue size for Elan touchpads

Elan hw v.4 touchpads often sends touchpad release packet right after
touchpad touch one. Most probably this happens due to PS/2 limited bandwith.
Reducing of tap_min_queue size to 1 makes multifinger tap detection
more reliable in this case.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/51de77dd3d64846b8ed8462b366f83f4cfabc1b7

psm - Adjust Elantech palm width threshold to nearly match synaptics defaults

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/8a98c8c412d0c7171054423dced8874f62cd6082

psm - reduce cursor jumping on palm detection

This is done with discarding pointer movements rather then mouse packets

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/848714bcb443af01596a455cad58beda33caf98b

psm - Enable palm detection on two finger touches for multitouch trackpads

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/eb65854355fe829af8c9b0d5c5a2c324a220ed10

psm - Report 3-rd and 4-th fingers as first finger for Elan hw v.2/v.3

as Linux does. It should not affect gesture processing in current state
as it ignores finger coords on 3-finger tap detection but it should make
evdev reports looking more Linux-alike.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/6c85d7cb3230313a90ae2f5fb4fe943d5ad686f0

psm - Set predefined logical touchpad sizes for several ancient Elan hw v.2 models

This change is based on Linux driver. Determine logical trace size. It
used for calculation of touch sizes in surface units for MT-protocol
type B evdev reports.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/21cb8f6288d49b2a274db0fa531306a25220abd8

psm - Reduce synaptics touch sensitivity

Increase hw.psm.synaptics.min_pressure default value from 16 to 32
to nearly match Linux driver (30-35 hysteresis loop).
This makes libinput tap detection more reliable.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/ae3b1e4a342724a1ac2151f2526cd8b8f69c60b5

psm - Add sanity checks to Synaptics touchpad driver resolution parser

This fixes "Kernel has only x or y resolution, not both" libinput error.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/8352f326c3db80e16d43d5b29a9cf55d914becdd

psm - Skip sync check when `PSM_CONFIG_NOCHECKSYNC` is set

In psmprobe(), we set the initial `syncmask` to the vendor default value
if the `PSM_CONFIG_NOCHECKSYNC` bit is unset. However, we currently only
set it for the Elantech touchpad later in psmattach(), thus `syncmask`
is always configured.

Now, we check `PSM_CONFIG_NOCHECKSYNC` and skip sync check if it is set.
This fixes Elantech touchpad support for units which have `hascrc` set.

To clarify that, when we log the `syncmask` and `syncbits` fields, also
mention if they are actually used.

Finally, when we set `PSM_CONFIG_NOCHECKSYNC`, clear `PSM_NEED_SYNCBITS`
flag.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/9402bd96df8000c2ac5d9c239ff746919af0dd66

psm - Don't try to detect trackpoint packets if the Elantech device has none

This fixes a panic when `EVDEV_SUPPORT` is enabled: if a trackpoint
packet was detected but there was no trackpoint, we still tried to emit an
evdev event even though the associated relative evdev device (`evdev_r`)
was not initialized.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/b9b766ae4708627ab5f62377c30ec070b6dd103d

psm - Log syncmask[1], not syncmask[0] twice

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/90b0eb9b4a7b94c81f4183937210371c02fc3f58

psm - Add support for HP EliteBook 1040 ForcePads.

ForcePads do not have any physical buttons, instead they detect click
based on finger pressure. Forcepads erroneously report button click
if there are 2 or more fingers on the touchpad breaking multifinger
gestures. To workaround this start reporting a click only after
4 consecutive single touch packets has been received. Skip these packets
in case more contacts appear.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/7d1460a4b1a0e5cd9fbed912b7d4480d0f769d4a

psm - Reduce psm watchdog verbosity

Modern touchpads do not issue interrupts on inactivity so "lost interrupt"
message became annoying spam nowadays. This change quiets the message
if debug.psm.loglevel=5 (or less) is set in /boot/loader.conf

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/f451e00544de30a6ce00b15bf056bedb880e882f

psm - Fix panic occuring soon after PS/2 packet has been rejected by synaptics or elantech sanity checker

This patch has already been partially applied in commit 906090f3.

After packet has been rejected contents of packet buffer is not cleared
with setting of inputbytes counter to 0. So when this packet buffer is
filled again being an element of circular queue, new data appends to old
data rather than overwrites it. This leads to packet buffer overflow
after 10 rounds.

Fix it with setting of packet's inputbytes counter to 0 after rejection.

While here add extra logging of rejected packets.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/74a53bd1315341bc33f9dd0dc589d0397080f270

psm - Always initialize used values in debug print

'status' array passed to get_mouse_status() is usually uninitialized by
callers.

Fully populating it with values in get_mouse_status() can fail due to
read_aux_data().

Additionally, nothing in API constrains 'len' to be >= 3.  In practice,
every caller passes three, so perhaps that argument should just be removed.
Refactoring is a larger change, though.

Remove use of potentially uninitialized values by:
1. Only printing 3 debug statuses if the passed array was at least
   'len' >= 3;
2. Populating 'status' array up to first three elements, if read_aux_data()
   failed.

No functional change intended.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/893daee680dcb62886ce0c3d74457834a1c2ead9

psm - Initialize variables before use

dxp/dyp could have been used uninitialized in the subsequent debugging log
invocation.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/015ab098439452cb798dbea5dd2d4fe57c72808c

psm - Add minimal support for active AUX port multiplexers

Active PS/2 multiplexing is a method for attaching up to four PS/2
pointing devices to a computer. Enabling of multiplexed mode allows
commands to be directed to individual devices using routing prefixes.
Multiplexed mode reports input with each byte tagged to identify
its source. This method differs from one currently supported by psm(4)
where so called guest device (trackpoint) is attached to special
interface located on the host device (touchpad) and latter performs
guest protocol conversion to special encapsulation packet format.

At present time active PS/2 multiplexing is used in some models of
HP laptops e.g. EliteBook 8560w, 9470m. Enabling of absolute operation
mode on such touchpads is connected with following problems:
1. Touchpad's port priority is lower than trackpoint's. That blocks
   information queries thus prevents touchpad detection and configuration.
2. Touchpad and trackpoint have different protocol packet sizes and
   sync bytes.

As PS/2 usage is on decline only minimal possible set of changes to
support Synaptics touchpad and generic mouses is implemented.
Active multiplexing mode is enabled only at probe stage to scan through
attached PS/2 devices to query and configure Synaptics touchpad.
After touchpad has been configured, mux is switched back to legacy
(hidden multiplexing) mode to perform normal interrupt-driven input
data processing. Overflow bit values rather than tags are used to
separate packets produced by different devices. Switching back to
legacy mode allows to avoid psm(4) and atkbd(4) rework to support
4 instances of mouse driver.

Note: While in hidden multiplexing mode KBC does some editing of the
packet stream. It remembers the button bits from the last packet
received from each device, and replaces the button bits of every
packet with the logical OR of all devices’ most recent button bits.
This sort of button crosstalk results in spurious button events
which are inhibitted with various tricks. E.g. trackpoint middle
button events are suppressed while trackpad surface is touched and
touchpad left and right button events are suppressed if corresponding
trackpoint buttons are pressed.

Not applied is a section related to EVDEV support.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/80203ccefd77badf5dc58a63047a1da2d303472c

psm - do not process gestures when palm is present

Ignoring of gesture processing when the palm is detected helps to reduce
some of the erratic pointer behavior.

This fixes regression introduced in FreeBSD r317814.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/51319286ed34e70b00a709ac36a1120c26d95a5d

psm - respect tap_disabled configuration with enabled Extended support

This fixes a bug where, even when hw.psm.tap_enabled=0, touchpad taps
were processed.
tap_enabled has three states: unconfigured, disabled, and enabled (-1, 0, 1).
To respect PR kern/139272, taps are ignored only when explicity disabled.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/0c8a908463dd601a860c6ec9e52742684df3e63b

psm - give names to synaptics commands

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/bf33f20d962c1b4443324af205170a35acce63f7

psm - Add natural scrolling support to sysmouse protocol

This change enables natural scrolling with two finger scroll enabled
and when user is using a trackpad (mouse and trackpoint are not affected).
Depending on trackpad model it can be activated with setting of
hw.psm.synaptics.natural_scroll or hw.psm.elantech.natural_scroll sysctl
values to 1.

Evdev protocol is not affected by this change too. Tune userland client
e.g. libinput to enable natural scrolling in that case.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/3b11e3b6e195d08f6ca6e32464f49b7495697045

psm - Fix Elantech trackpoint support.

Sign bits for X and Y motion data were taken from wrong places.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/8fa46200391063bd5c15a25d9f9b06354fcd4147

psm - Add extra sanity checks to Elantech trackpoint packet parser

Not included are parts related to evedev.

Add strict checks for unused bit states in Elantech trackpoint packet
parser to filter out spurious events produces by some hardware which
are detected as trackpoint packets. See comment on r328191 for example.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/6c53fea7d62edeeb7d1e0d2d1da922a3a67c0bce

psm - Enable touchpads and trackpads by default

Enable synaptics and elantech touchpads, as well as IBM/Lenovo TrackPoints
by default, instead of having users find and toggle a loader tunable.
This makes things like two finger scroll and other modern features work out
of the box with X.  By enabling these settings by default, we get a better
desktop experience in X, since xserver and evdev can make use of the more
advanced synaptics and elantech features.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/2dd9a967d299a1c0c1363e82a1e3f0c19b4b9846

psm - Add macOS-like three finger drag trackpad gesture

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/5a037b11974c8c74c7df22378633caca71456a53

psm - properly check for atkbdc_open failure

atkbdc_open can return NULL if the unit's out of bounds or the softc isn't
setup. Check it to be safe.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/6d3c9beedb7f100922a61f1e089254be24e48547

psm - Do not disable trackpoint when hw.psm.elantech.touchpad_off is enabled

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/8137fb2e38f7b88d2cce9c29a2f00cda33a89d1c

psm - Workaround active PS/2 multiplexor hang

which happens on some laptops after returning to legacy multiplexing mode
at initialization stage.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/ec45be6c36dcb373103014e16a3623e0c182a5bb

psm - Always initialize Synaptics touchpad report range with defaults

Otherwise libinput refuses to recoginize some Synaptics touchpads with
"kernel bug: device has min == max on ABS_X" message in Xorg.log.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/2ac1c1927258e649e3ca3269aea40fb4c63e2296

psm - Probe Synaptics touchpad with active multiplexing mode enabled

if it is only multiplexed device. Also enable syncbit checks for them.
This fixes touchpad recognition on Panasonic Toughbook CF-MX4 laptop.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/f5998d20ed80fdc1cb3ba0c245cae5f179e22fe2

psm - Disable KVM switch "jitter" clamping for absolute touchpads

FreeBSD commit r123442 [1] introduced solution for clamping of PS/2
mice jitter when using a KVM. Solution is to buffer mouse packets for
0.050ms if mouse activity has not been seen for more than 0.5 seconds.
Then flush that data to driver if no validation errors found or drop the
entire queue otherwise.

While it works well with relative devices it has issues with absolute ones
Depending on history buffering may results in delaying of the touch front
edge for 0.050ms that affects gesture processing (tap detection).

As absolute touchpads usually are built-in devices we can safely disable
bufferization and KVM jitter clamping to avoid such a delays.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/bedf31ad7e15c7d3e3ff9e5295bfd5454fbb42fa

[1]: https://svnweb.freebsd.org/base?view=revision&revision=123442

atkbd - Reduce polling rate from 10Hz to ~1Hz

In my understanding this is only needed to workaround lost interrupts.
I was thinking to remove it completely, but the comment about edge-
triggered interrupt may be true and needs deeper investigation.  ~1Hz
should be often enough to handle the supposedly rare loss cases, but
rare enough to not appear in top.  Add sysctl hw.atkbd.hz to tune it.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/9e007a88d65ba0d23e73c3c052d474a78260d503

psm - Remove write-only variables

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/9b0eb55048e6294c00db70f8ebecf18c1c3c1dbe

psm - Swap the unit member in the softc for a device_t

This also adds a device_log() function to subr_bus.c.

This entails various changes to make this driver more "modern"
(new-bus vs pre-new-bus) using device_log() and device_printf() rather
than psm%d.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/a6c09f20e8e00bec7d9eb088b4f32dd38bf7f490

psm - Fix typo in comment

s/diable/disable/

Taken-from: FreeBSD

psm - recognize post-IBM trackpoints on Thinkpads

Newer Thinkpads (e.g. X1 Carbon 7th Gen) come with trackpoints from
different vendors.  They are mostly compatible with the original one.
Not sure all features are going to work, but at least this fixes resume
operation.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/2bcef59d428a672a9144c574b4b4f42f5f0e5b2a

psm/atkbd - Sync with FreeBSD (partially)

Drop MOUSE_GETVARS and MOUSE_SETVARS ioctls support

These ioctls are not documented and only stubbed in a few drivers:
psm(4) and syscon's sysmouse(4). The only exception is MOUSE_GETVARS
implemented in psm(4)

Given the fact that they were introduced 20 years ago and implementation
has never been completed, remove any related code.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/67580198b70aa58d572a2602a02d1a70d9971350

evdev/psm - update evdev, add evdev-support to psm(4)

* Partially update evdev from FreeBSD and add evdev-support to psm

* Touches ums(4)

atkbd - Disable periodic polling by default

If this cause keyboard/mouse freezes on some hardware, please set
loader tunable hw.atkbd.hz=1 as workaround and report the issue.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/ce881170088c4c98c036fe561f8ee8413c2e2585

psm - use evdev_register_mtx()

This uses psm_softc->lock for evdev operations

evdev - Hide "kern.evdev.rcpt_mask" sysctl if kernel compiled w/o EVDEV_SUPPORT

Now presence of this sysctl can be used for discovery if evdev support
for hybrid devices is compiled into kernel or not.
Hide "kern.evdev.sysmouse_t_axis" sysctl for the same reason.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/233e10741a791bfa570a69804ae40f9fffcdcd43

evdev - Take driver's lock in cdev write handler if necessary

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/f3f8069493a5a9db77a5f660f4f9e613742fdc42

evdev - Disable value normalization and state filtering for SND events

Some events can take sound pitch as a value so can not be represented
as binary on/off events. Tracking for on/off state is left in place
as it is a part of the evdev API.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/c6e809423fde2f5ce02b9fef5910c34e434e7c36

evdev - Do not start/stop softrepeat callout if no clients attached

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/37ff33454873d8639d10dc5c72d76fb3a1eb540f

evdev - Fix evdev codes for slash and asterisk numpad keys of AT-keyboards

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/b12ac17ef1928858ed236a9a5851c30f465f8d26

evdev - Fix pause key release event in AT keyboard set 1 to evdev xlat-or

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/a0e9218c019b30a68fe137f8540a8c8da947480b

evdev - export event device properties through sysctl interface

A big security advantage of Wayland is not allowing applications to read
input devices all the time. Having /dev/input/* accessible to the user
account subverts this advantage.

libudev-devd was opening the evdev devices to detect their types (mouse,
keyboard, touchpad, etc). This don't work if /dev/input/* is inaccessible.
With the kernel exposing this information as sysctls (kern.evdev.input.*),
we can work w/o /dev/input/* access, preserving the Wayland security model.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/f99e7b1aed7ea65ca0dbe5b182f2b9cbfdfe54db
FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/dd3a67a0781261c1fdd670456f492ecb695b8fe5

evdev - return error rather than zero-length data on blocked read()

if blocked process has been woken up by evdev device destruction.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/006eb44926d6949e0b60b21920f1ea93f03efd2f

evdev - Use proper mutex reference in autorepeat callout initialization

This fixes panic occuring when evdev key autorepeat is enabled by driver
which initializes evdev with external mutex.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/68cc053763eec2f2afef8de8a12f6786805c9e24

evdev - Use LIST_FOREACH_MUTABLE

instead of LIST_FOREACH as we are removing elements in the middle.

This fixes a panic when detaching USB mouse.

Note that LIST_FOREACH_SAFE in FreeBSD corresponds to our
LIST_FOREACH_MUTABLE.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/5396856ee0e158a1505dce733aad4f9b42afafbb

evdev - Add AT translated set1 scancodes for F-unlocked F1-12 keys

"F lock" is a switch between two sets of scancodes for function keys F1-F12
found on some Logitech and Microsoft PS/2 keyboards [1]. When "F lock" is
pressed, then F1-F12 act as function keys and produce usual keyscans for
these keys. When "F lock" is depressed, F1-F12 produced the same keyscans
but prefixed with E0.
Some laptops use [2] E0-prefixed F1-F12 scancodes for non-standard keys.

[1] https://www.win.tue.nl/~aeb/linux/kbd/scancodes-6.html
[2] https://reviews.freebsd.org/D21565

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/8701adbeb0d4e29c9aae68b493317fb581ae520e

evdev - Sync event codes with Linux kernel 5.6

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/d316504953cf0890433ac2cb75d10c6fcf0c0263

evdev - Add AT translated set1 scancodes for 'Eisu' & 'Kana' keys

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/94811094f8357824108a57169d1f5b1e2ffb56ca

evdev - recognize the Chromebook menu key as F13 like Linux does

This is the key on the right side of the function keys, with the
"hamburger menu" icon on it.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/54cca285fc3d32e0be4028d17c055424b49f4f7d

evdev - Implement fuzz

Fuzz is used to filter noise from the event stream.
Upcoming gamepad drivers use it.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/3b2175fdb62c7f872e78ce732952f2ac2ff99c8d

evdev - Linux evdev does not terminate truncated strings with 0

It seems that DragonFly ioctl(2) cannot return the actual length of
copyouted data as only error=0 is treated as "no-error".

Based-on-FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/7a810290b8f6c6885fdb9917cf590d46fa270a61

Revert "evdev - Linux evdev does not terminate truncated strings with 0"

This reverts commit 12ac6b7825fba8f53f9cbfe3a6aaed27983c1848.

I think it's safer to properly 0-terminate the strings. The only reason
why this commit landed in FreeBSD was to support a Linux binary Imprivata,
which we can't run anyways.

evdev - Do not check maximal contact number for MT initialization

This allows singletouch devices which use multitouch protocols to work.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/e40fec4ec9a7a6625b3e332d4f856eb1d05f0def

evdev - Multitouch code style changes

1. Move touch count reporting helpers to utils. They are not multitouch.
2. Use evdev_mt prefix for private multitouch support routines.
3. Use int instead of int32_t where fixed size is not required.
4. Export some internal functions.

This change should be no-op.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/98a7606b85e05132f328a2498dccab78df31cb7e

evdev - Use bitsets to track active touches and slots changed in current report

Obtained from:	OpenBSD

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/2dc7188e532f0147b36a44ce5c033d9f59cbd9f4

evdev - Send first active rather than 0-th slot state as ST report

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/fbe17f9017e785dd564ce7fc5553a9136d3a0b03

kern - Add kern.features sysctl and FEATURE macro

Similar to FreeBSD.

xorg-server checks e.g. for kern.features.evdev or
kern.features.evdev_support.

Add evdev_support feature

evdev - force no fuzz for autogenerated single touch compat events

As fuzz has already been applied on multitouch event processing.
This allows to remove existing workaround for double fuzz procesing.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/314913ed7c6e6e1b7c80a1063620f478961419b7

evdev - Normalize width and pressure of single touch compat events

to match Synaptics touchpad reporting range.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/127e54deb6d8899e1dc1bc6251d512e19f21b0f1

evdev - Give short aliases to items of evdev_mt_slot array

with using of unioned anonymous structure.

Access to the same data by using different members of union generally
works despite it is not supported by C specs.

Also add helper function to report entire slot state.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/059360287e3344f48f5a7839e2d6d54016b18b19

evdev - Make MT tracking IDs monotonically increasing sequence

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/66bd52f5e241bd2548015f847f12cdff69176c40

evdev - Import support for touch-tracking

Touch tracking is a process of assignment of unique trackingID to each
initiated contact on the surface.  Keeping the trackingIDs persistent
across multitouch reports requires solving of so called Euclidian
Bipartite Matching problem.

This commit imports EBM-solver implementation based on Dinitz-Kronrod
algorithm to find minimum cost matching between contacts listed in two
consecutive reports.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/4c0a134e32a7f4dec556fea15c8de22f69864492
Obtained-from: OpenBSD

evdev - Add implicit mode for touch tracking

In implicit mode assignment of slot number and tracking id is performed
automatically on each synchronization requested by device driver.

This is done with creation of intermediate buffer for multitouch events.
This buffer holds untracked events until synchronization is requested by
device driver. It is needed as touch assigment requires
knowledges of all touch positions pushed in current and previous reports.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/f76051c7dabe952b75127a8031d87d78b603be20

evdev - Add support for automatic MT protocol type A to type B conversion

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/d056693d7bc6c1b5f2c1612e5b34807f173e21c7

evdev - Do not export multitouch functions which are unused outside evdev

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/35bc295b30ef91f1835a0ee0225e8d957a9ebddd

evdev - Fix a typo in a commit

- s/prefered/preferred/

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/efd8749fe5cccb6c3d6b5f3c3515bf89ad306bcc

evdev - Fix "hancha" and "han/yong" korean keys handling

The Korean keyboard has two keys, the Korean/Chinese and the
Korean/English toggles, that generate scancodes 0xF1 and 0xF2
(respectively) when pressed, and nothing when released. They do not
repeat.
As Hanyong/Hancha keys are generally greater than 0x80, which is
generally considered a release key, add extra preceding press key event
to generate press/release pair.
Swap Hanyong/Hancha key codes to match reality.

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/6a26c99f827ffa9da5e23d466eeb17131fc75238

evdev - Sync event codes with Linux kernel 6.5

FreeBSD-commit: https://github.com/freebsd/freebsd-src/commit/87a53bef41fe769920f925e74fedddafd5187b93

Fix commit "Import support for touch tracking" (afa7dd1f)

- Fix maybe-uninitialized warning
- use bitcount32
- bring in bit_foreach_at from FreeBSD
- reorder "static"

evdev - Sync our copy of freebsd-bitstring.h with FreeBSD

- Mostly using proper functions returning the result
  instead of assigning it to a pointer.

- Specify bit positions using size_t instead of int.

evdev - Conditionalize kern.features.evdev_support
---
 sys/bus/u4b/input/ums.c                |   12 +-
 sys/dev/misc/evdev/cdev.c              |    8 +-
 sys/dev/misc/evdev/evdev.c             |  204 ++-
 sys/dev/misc/evdev/evdev.h             |   54 +-
 sys/dev/misc/evdev/evdev_mt.c          |  682 ++++++--
 sys/dev/misc/evdev/evdev_private.h     |   18 +-
 sys/dev/misc/evdev/evdev_utils.c       |   72 +-
 sys/dev/misc/evdev/freebsd-bitstring.h |  265 ++-
 sys/dev/misc/evdev/input-event-codes.h |  149 +-
 sys/dev/misc/evdev/input.h             |    9 +-
 sys/dev/misc/kbd/atkbd.c               |   77 +-
 sys/dev/misc/kbd/atkbdc.c              |  126 +-
 sys/dev/misc/kbd/atkbdcreg.h           |   22 +
 sys/dev/misc/psm/psm.c                 | 2116 ++++++++++++++++++------
 sys/dev/misc/syscons/sysmouse.c        |    6 -
 sys/kern/kern_mib.c                    |    2 +
 sys/kern/subr_bus.c                    |   42 +
 sys/sys/bus.h                          |    1 +
 sys/sys/mouse.h                        |   17 +-
 sys/sys/sysctl.h                       |    9 +
 20 files changed, 3013 insertions(+), 878 deletions(-)

diff --git a/sys/bus/u4b/input/ums.c b/sys/bus/u4b/input/ums.c
index 6cca5b114c..471bc9c5e9 100644
--- a/sys/bus/u4b/input/ums.c
+++ b/sys/bus/u4b/input/ums.c
@@ -956,9 +956,9 @@ ums_reset_buf(struct ums_softc *sc)
 
 #ifdef EVDEV_SUPPORT
 static int
-ums_ev_open(struct evdev_dev *evdev, void *ev_softc)
+ums_ev_open(struct evdev_dev *evdev)
 {
-	struct ums_softc *sc = (struct ums_softc *)ev_softc;
+	struct ums_softc *sc = (struct ums_softc *)evdev_get_softc(evdev);
 
 	lockmgr(&sc->sc_lock, LK_EXCLUSIVE);
 
@@ -974,10 +974,10 @@ ums_ev_open(struct evdev_dev *evdev, void *ev_softc)
 	return (0);
 }
 
-static void
-ums_ev_close(struct evdev_dev *evdev, void *ev_softc)
+static int
+ums_ev_close(struct evdev_dev *evdev)
 {
-	struct ums_softc *sc = (struct ums_softc *)ev_softc;
+	struct ums_softc *sc = (struct ums_softc *)evdev_get_softc(evdev);
 
 	lockmgr(&sc->sc_lock, LK_EXCLUSIVE);
 
@@ -987,6 +987,8 @@ ums_ev_close(struct evdev_dev *evdev, void *ev_softc)
 		ums_stop_rx(sc);
 
 	lockmgr(&sc->sc_lock, LK_RELEASE);
+
+	return (0);
 }
 #endif
 
diff --git a/sys/dev/misc/evdev/cdev.c b/sys/dev/misc/evdev/cdev.c
index 398cda01ce..9ae1149b50 100644
--- a/sys/dev/misc/evdev/cdev.c
+++ b/sys/dev/misc/evdev/cdev.c
@@ -207,6 +207,8 @@ evdev_read(struct dev_read_args *ap)
 				client->ec_blocked = true;
 				ret = lksleep(client, &client->ec_buffer_mtx,
 				    PCATCH, "evread", 0);
+				if (ret == 0 && client->ec_revoked)
+					ret = ENODEV;
 			}
 		}
 	}
@@ -430,7 +432,7 @@ evdev_ioctl(struct dev_ioctl_args *ap)
 			return (ENOTSUP);
 
 		ke = (struct input_keymap_entry *)data;
-		evdev->ev_methods->ev_get_keycode(evdev, evdev->ev_softc, ke);
+		evdev->ev_methods->ev_get_keycode(evdev, ke);
 		return (0);
 
 	case EVIOCSKEYCODE:
@@ -443,7 +445,7 @@ evdev_ioctl(struct dev_ioctl_args *ap)
 			return (ENOTSUP);
 
 		ke = (struct input_keymap_entry *)data;
-		evdev->ev_methods->ev_set_keycode(evdev, evdev->ev_softc, ke);
+		evdev->ev_methods->ev_set_keycode(evdev, ke);
 		return (0);
 
 	case EVIOCGABS(0) ... EVIOCGABS(ABS_MAX):
@@ -546,7 +548,7 @@ evdev_ioctl(struct dev_ioctl_args *ap)
 		    MIN(len / sizeof(int32_t) - 1, MAXIMAL_MT_SLOT(evdev) + 1);
 		for (int i = 0; i < nvalues; i++)
 			((int32_t *)data)[i + 1] =
-			    evdev_get_mt_value(evdev, i, code);
+			    evdev_mt_get_value(evdev, i, code);
 		return (0);
 
 	case EVIOCGKEY(0):
diff --git a/sys/dev/misc/evdev/evdev.c b/sys/dev/misc/evdev/evdev.c
index 9efac87b79..320cd99248 100644
--- a/sys/dev/misc/evdev/evdev.c
+++ b/sys/dev/misc/evdev/evdev.c
@@ -54,6 +54,9 @@
 
 #ifdef FEATURE
 FEATURE(evdev, "Input event devices support");
+#ifdef EVDEV_SUPPORT
+FEATURE(evdev_support, "Evdev support in hybrid drivers");
+#endif
 #endif
 
 enum evdev_sparse_result
@@ -86,11 +89,15 @@ int evdev_sysmouse_t_axis = 0;
  */
 
 SYSCTL_NODE(_kern, OID_AUTO, evdev, CTLFLAG_RW, 0, "Evdev args");
+#ifdef EVDEV_SUPPORT
 SYSCTL_INT(_kern_evdev, OID_AUTO, rcpt_mask, CTLFLAG_RW, &evdev_rcpt_mask, 0,
     "Who sends events: bit0 - sysmouse, bit1 - kbdmux, "
     "bit2 - mouse hardware, bit3 - keyboard hardware");
 SYSCTL_INT(_kern_evdev, OID_AUTO, sysmouse_t_axis, CTLFLAG_RW,
     &evdev_sysmouse_t_axis, 0, "Extract T-axis from 0-none, 1-ums, 2-psm");
+#endif
+SYSCTL_NODE(_kern_evdev, OID_AUTO, input, CTLFLAG_RD, 0,
+    "Evdev input devices");
 
 static void evdev_start_repeat(struct evdev_dev *, uint16_t);
 static void evdev_stop_repeat(struct evdev_dev *);
@@ -209,6 +216,87 @@ evdev_estimate_report_size(struct evdev_dev *evdev)
 	return (size);
 }
 
+static void
+evdev_sysctl_create(struct evdev_dev *evdev)
+{
+	struct sysctl_oid *ev_sysctl_tree;
+	char ev_unit_str[8];
+
+	ksnprintf(ev_unit_str, sizeof(ev_unit_str), "%d", evdev->ev_unit);
+	sysctl_ctx_init(&evdev->ev_sysctl_ctx);
+
+	ev_sysctl_tree = SYSCTL_ADD_NODE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_STATIC_CHILDREN(_kern_evdev_input), OID_AUTO,
+	    ev_unit_str, CTLFLAG_RD, NULL, "device index");
+
+	SYSCTL_ADD_STRING(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "name", CTLFLAG_RD,
+	    evdev->ev_name, 0,
+	    "Input device name");
+
+	SYSCTL_ADD_STRUCT(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "id", CTLFLAG_RD,
+	    &evdev->ev_id, input_id,
+	    "Input device identification");
+
+	/* ioctl returns ENOENT if phys is not set. sysctl returns "" here */
+	SYSCTL_ADD_STRING(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "phys", CTLFLAG_RD,
+	    evdev->ev_shortname, 0,
+	    "Input device short name");
+
+	/* ioctl returns ENOENT if uniq is not set. sysctl returns "" here */
+	SYSCTL_ADD_STRING(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "uniq", CTLFLAG_RD,
+	    evdev->ev_serial, 0,
+	    "Input device unique number");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "props", CTLFLAG_RD,
+	    evdev->ev_prop_flags, sizeof(evdev->ev_prop_flags), "",
+	    "Input device properties");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "type_bits", CTLFLAG_RD,
+	    evdev->ev_type_flags, sizeof(evdev->ev_type_flags), "",
+	    "Input device supported events types");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "key_bits", CTLFLAG_RD,
+	    evdev->ev_key_flags, sizeof(evdev->ev_key_flags),
+	    "", "Input device supported keys");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "rel_bits", CTLFLAG_RD,
+	    evdev->ev_rel_flags, sizeof(evdev->ev_rel_flags), "",
+	    "Input device supported relative events");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "abs_bits", CTLFLAG_RD,
+	    evdev->ev_abs_flags, sizeof(evdev->ev_abs_flags), "",
+	    "Input device supported absolute events");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "msc_bits", CTLFLAG_RD,
+	    evdev->ev_msc_flags, sizeof(evdev->ev_msc_flags), "",
+	    "Input device supported miscellaneous events");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "led_bits", CTLFLAG_RD,
+	    evdev->ev_led_flags, sizeof(evdev->ev_led_flags), "",
+	    "Input device supported LED events");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "snd_bits", CTLFLAG_RD,
+	    evdev->ev_snd_flags, sizeof(evdev->ev_snd_flags), "",
+	    "Input device supported sound events");
+
+	SYSCTL_ADD_OPAQUE(&evdev->ev_sysctl_ctx,
+	    SYSCTL_CHILDREN(ev_sysctl_tree), OID_AUTO, "sw_bits", CTLFLAG_RD,
+	    evdev->ev_sw_flags, sizeof(evdev->ev_sw_flags), "",
+	    "Input device supported switch events");
+}
+
 static int
 evdev_register_common(struct evdev_dev *evdev)
 {
@@ -223,7 +311,7 @@ evdev_register_common(struct evdev_dev *evdev)
 	if (evdev_event_supported(evdev, EV_REP) &&
 	    bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) {
 		/* Initialize callout */
-		callout_init_lk(&evdev->ev_rep_callout, &evdev->ev_mtx);
+		callout_init_lk(&evdev->ev_rep_callout, evdev->ev_lock);
 
 		if (evdev->ev_rep[REP_DELAY] == 0 &&
 		    evdev->ev_rep[REP_PERIOD] == 0) {
@@ -233,9 +321,9 @@ evdev_register_common(struct evdev_dev *evdev)
 		}
 	}
 
-	/* Initialize multitouch protocol type B states */
-	if (bit_test(evdev->ev_abs_flags, ABS_MT_SLOT) &&
-	    evdev->ev_absinfo != NULL && MAXIMAL_MT_SLOT(evdev) > 0)
+	/* Initialize multitouch protocol type B states or A to B converter */
+	if (bit_test(evdev->ev_abs_flags, ABS_MT_SLOT) ||
+	    bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK))
 		evdev_mt_init(evdev);
 
 	/* Estimate maximum report size */
@@ -248,6 +336,12 @@ evdev_register_common(struct evdev_dev *evdev)
 
 	/* Create char device node */
 	ret = evdev_cdev_create(evdev);
+	if (ret != 0)
+		goto bail_out;
+
+	/* Create sysctls (for device enumeration without /dev/input access rights) */
+	evdev_sysctl_create(evdev);
+
 bail_out:
 	return (ret);
 }
@@ -280,15 +374,17 @@ evdev_register_mtx(struct evdev_dev *evdev, struct lock *mtx)
 int
 evdev_unregister(struct evdev_dev *evdev)
 {
-	struct evdev_client *client;
+	struct evdev_client *client, *tmp;
 	int ret;
 	debugf(evdev, "%s: unregistered evdev provider: %s\n",
 	    evdev->ev_shortname, evdev->ev_name);
 
+	sysctl_ctx_free(&evdev->ev_sysctl_ctx);
+
 	EVDEV_LOCK(evdev);
 	evdev->ev_cdev->si_drv1 = NULL;
 	/* Wake up sleepers */
-	LIST_FOREACH(client, &evdev->ev_clients, ec_link) {
+	LIST_FOREACH_MUTABLE(client, &evdev->ev_clients, ec_link, tmp) {
 		evdev_revoke_client(client);
 		evdev_dispose_client(evdev, client);
 		EVDEV_CLIENT_LOCKQ(client);
@@ -361,6 +457,13 @@ evdev_set_methods(struct evdev_dev *evdev, void *softc,
 	evdev->ev_softc = softc;
 }
 
+inline void *
+evdev_get_softc(struct evdev_dev *evdev)
+{
+
+	return (evdev->ev_softc);
+}
+
 inline void
 evdev_support_prop(struct evdev_dev *evdev, uint16_t prop)
 {
@@ -394,16 +497,15 @@ evdev_support_rel(struct evdev_dev *evdev, uint16_t code)
 }
 
 inline void
-evdev_support_abs(struct evdev_dev *evdev, uint16_t code, int32_t value,
-    int32_t minimum, int32_t maximum, int32_t fuzz, int32_t flat,
-    int32_t resolution)
+evdev_support_abs(struct evdev_dev *evdev, uint16_t code, int32_t minimum,
+    int32_t maximum, int32_t fuzz, int32_t flat, int32_t resolution)
 {
 	struct input_absinfo absinfo;
 
 	KASSERT(code < ABS_CNT, ("invalid evdev abs property"));
 
 	absinfo = (struct input_absinfo) {
-		.value = value,
+		.value = 0,
 		.minimum = minimum,
 		.maximum = maximum,
 		.fuzz = fuzz,
@@ -591,6 +693,7 @@ static void
 evdev_modify_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
     int32_t *value)
 {
+	int32_t fuzz, old_value, abs_change;
 
 	EVDEV_LOCK_ASSERT(evdev);
 
@@ -606,7 +709,8 @@ evdev_modify_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 				*value = KEY_EVENT_REPEAT;
 		} else {
 			/* Start/stop callout for evdev repeats */
-			if (bit_test(evdev->ev_key_states, code) == !*value) {
+			if (bit_test(evdev->ev_key_states, code) == !*value &&
+			    !LIST_EMPTY(&evdev->ev_clients)) {
 				if (*value == KEY_EVENT_DOWN)
 					evdev_start_repeat(evdev, code);
 				else
@@ -616,7 +720,32 @@ evdev_modify_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 		break;
 
 	case EV_ABS:
-		/* TBD: implement fuzz */
+		if (code == ABS_MT_SLOT)
+			break;
+		else if (!ABS_IS_MT(code))
+			old_value = evdev->ev_absinfo[code].value;
+		else if (!bit_test(evdev->ev_abs_flags, ABS_MT_SLOT))
+			/* Pass MT protocol type A events as is */
+			break;
+		else if (code == ABS_MT_TRACKING_ID) {
+			*value = evdev_mt_reassign_id(evdev,
+			    evdev_mt_get_last_slot(evdev), *value);
+			break;
+		} else
+			old_value = evdev_mt_get_value(evdev,
+			    evdev_mt_get_last_slot(evdev), code);
+
+		fuzz = evdev->ev_absinfo[code].fuzz;
+		if (fuzz == 0)
+			break;
+
+		abs_change = abs(*value - old_value);
+		if (abs_change < fuzz / 2)
+			*value = old_value;
+		else if (abs_change < fuzz)
+			*value = (old_value * 3 + *value) / 4;
+		else if (abs_change < fuzz * 2)
+			*value = (old_value + *value) / 2;
 		break;
 	}
 }
@@ -661,8 +790,6 @@ evdev_sparse_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 		break;
 
 	case EV_SND:
-		if (bit_test(evdev->ev_snd_states, code) == value)
-			return (EV_SKIP_EVENT);
 		bit_change(evdev->ev_snd_states, code, value);
 		break;
 
@@ -688,7 +815,7 @@ evdev_sparse_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 		switch (code) {
 		case ABS_MT_SLOT:
 			/* Postpone ABS_MT_SLOT till next event */
-			evdev_set_last_mt_slot(evdev, value);
+			evdev_mt_set_last_slot(evdev, value);
 			return (EV_SKIP_EVENT);
 
 		case ABS_MT_FIRST ... ABS_MT_LAST:
@@ -696,11 +823,11 @@ evdev_sparse_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 			if (!bit_test(evdev->ev_abs_flags, ABS_MT_SLOT))
 				break;
 			/* Don`t repeat MT protocol type B events */
-			last_mt_slot = evdev_get_last_mt_slot(evdev);
-			if (evdev_get_mt_value(evdev, last_mt_slot, code)
+			last_mt_slot = evdev_mt_get_last_slot(evdev);
+			if (evdev_mt_get_value(evdev, last_mt_slot, code)
 			     == value)
 				return (EV_SKIP_EVENT);
-			evdev_set_mt_value(evdev, last_mt_slot, code, value);
+			evdev_mt_set_value(evdev, last_mt_slot, code, value);
 			if (last_mt_slot != CURRENT_MT_SLOT(evdev)) {
 				CURRENT_MT_SLOT(evdev) = last_mt_slot;
 				evdev->ev_report_opened = true;
@@ -766,6 +893,7 @@ evdev_send_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 
 	EVDEV_LOCK_ASSERT(evdev);
 
+	evdev_modify_event(evdev, type, code, &value);
 	sparse =  evdev_sparse_event(evdev, type, code, value);
 	switch (sparse) {
 	case EV_REPORT_MT_SLOT:
@@ -791,15 +919,16 @@ evdev_push_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 
 	EVDEV_ENTER(evdev);
 
-	evdev_modify_event(evdev, type, code, &value);
 	if (type == EV_SYN && code == SYN_REPORT &&
-	     bit_test(evdev->ev_flags, EVDEV_FLAG_MT_AUTOREL))
-		evdev_send_mt_autorel(evdev);
-	if (type == EV_SYN && code == SYN_REPORT && evdev->ev_report_opened &&
-	    bit_test(evdev->ev_flags, EVDEV_FLAG_MT_STCOMPAT))
-		evdev_send_mt_compat(evdev);
-	evdev_send_event(evdev, type, code, value);
+	    bit_test(evdev->ev_abs_flags, ABS_MT_SLOT))
+		evdev_mt_sync_frame(evdev);
+	else
+		if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK) &&
+		    evdev_mt_record_event(evdev, type, code, value))
+			goto exit;
 
+	evdev_send_event(evdev, type, code, value);
+exit:
 	EVDEV_EXIT(evdev);
 
 	return (0);
@@ -823,7 +952,7 @@ evdev_inject_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 	case EV_FF:
 		if (evdev->ev_methods != NULL &&
 		    evdev->ev_methods->ev_event != NULL)
-			evdev->ev_methods->ev_event(evdev, evdev->ev_softc,
+			evdev->ev_methods->ev_event(evdev,
 			    type, code, value);
 		/*
 		 * Leds and driver repeats should be reported in ev_event
@@ -840,7 +969,11 @@ evdev_inject_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
 	case EV_ABS:
 	case EV_SW:
 push:
+		if (evdev->ev_lock_type != EV_LOCK_INTERNAL)
+			EVDEV_LOCK(evdev);
 		ret = evdev_push_event(evdev, type,  code, value);
+		if (evdev->ev_lock_type != EV_LOCK_INTERNAL)
+			EVDEV_UNLOCK(evdev);
 		break;
 
 	default:
@@ -863,7 +996,7 @@ evdev_register_client(struct evdev_dev *evdev, struct evdev_client *client)
 	    evdev->ev_methods->ev_open != NULL) {
 		debugf(evdev, "calling ev_open() on device %s",
 		    evdev->ev_shortname);
-		ret = evdev->ev_methods->ev_open(evdev, evdev->ev_softc);
+		ret = evdev->ev_methods->ev_open(evdev);
 	}
 	if (ret == 0)
 		LIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link);
@@ -881,7 +1014,7 @@ evdev_dispose_client(struct evdev_dev *evdev, struct evdev_client *client)
 	if (LIST_EMPTY(&evdev->ev_clients)) {
 		if (evdev->ev_methods != NULL &&
 		    evdev->ev_methods->ev_close != NULL)
-			evdev->ev_methods->ev_close(evdev, evdev->ev_softc);
+			(void)evdev->ev_methods->ev_close(evdev);
 		if (evdev_event_supported(evdev, EV_REP) &&
 		    bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT))
 			evdev_stop_repeat(evdev);
@@ -917,6 +1050,21 @@ evdev_release_client(struct evdev_dev *evdev, struct evdev_client *client)
 	return (0);
 }
 
+bool
+evdev_is_grabbed(struct evdev_dev *evdev)
+{
+#if 0
+	if (kdb_active || SCHEDULER_STOPPED())
+		return (false);
+#endif
+	/*
+	 * The function is intended to be called from evdev-unrelated parts of
+	 * code like syscons-compatible parts of mouse and keyboard drivers.
+	 * That makes unlocked read-only access acceptable.
+	 */
+	return (evdev->ev_grabber != NULL);
+}
+
 static void
 evdev_repeat_callout(void *arg)
 {
diff --git a/sys/dev/misc/evdev/evdev.h b/sys/dev/misc/evdev/evdev.h
index de177b96bf..0ed0dd5500 100644
--- a/sys/dev/misc/evdev/evdev.h
+++ b/sys/dev/misc/evdev/evdev.h
@@ -38,18 +38,18 @@
 
 struct evdev_dev;
 
-typedef int (evdev_open_t)(struct evdev_dev *, void *);
-typedef void (evdev_close_t)(struct evdev_dev *, void *);
-typedef void (evdev_event_t)(struct evdev_dev *, void *, uint16_t,
+typedef int (evdev_open_t)(struct evdev_dev *);
+typedef int (evdev_close_t)(struct evdev_dev *);
+typedef void (evdev_event_t)(struct evdev_dev *, uint16_t,
     uint16_t, int32_t);
-typedef void (evdev_keycode_t)(struct evdev_dev *, void *,
+typedef void (evdev_keycode_t)(struct evdev_dev *,
     struct input_keymap_entry *);
 
 /*
  * Keyboard and mouse events recipient mask.
  * evdev_rcpt_mask variable should be respected by keyboard and mouse drivers
  * that are able to send events through both evdev and sysmouse/kbdmux
- * interfaces so user can choose prefered one to not receive one event twice.
+ * interfaces so user can choose preferred one to not receive one event twice.
  */
 #define	EVDEV_RCPT_SYSMOUSE	(1<<0)
 #define	EVDEV_RCPT_KBDMUX	(1<<1)
@@ -88,6 +88,8 @@ extern int evdev_sysmouse_t_axis;
 					 * for MT protocol type B reports */
 #define	EVDEV_FLAG_MT_AUTOREL	0x02	/* Autorelease MT-slots not listed in
 					 * current MT protocol type B report */
+#define	EVDEV_FLAG_MT_KEEPID	0x04	/* Do not reassign tracking ID */
+#define	EVDEV_FLAG_MT_TRACK	0x05	/* Assign touch to slot by evdev */
 #define	EVDEV_FLAG_MAX		0x1F
 #define	EVDEV_FLAG_CNT		(EVDEV_FLAG_MAX + 1)
 
@@ -100,6 +102,29 @@ struct evdev_methods
 	evdev_keycode_t		*ev_set_keycode;
 };
 
+union evdev_mt_slot {
+	int32_t         val[MT_CNT];
+	struct {
+		int32_t maj;		/* ABS_MT_TOUCH_MAJOR */
+		int32_t min;		/* ABS_MT_TOUCH_MINOR */
+		int32_t w_maj;		/* ABS_MT_WIDTH_MAJOR */
+		int32_t w_min;		/* ABS_MT_WIDTH_MINOR */
+		int32_t ori;		/* ABS_MT_ORIENTATION */
+		int32_t x;		/* ABS_MT_POSITION_X */
+		int32_t y;		/* ABS_MT_POSITION_Y */
+		int32_t type;		/* ABS_MT_TOOL_TYPE */
+		int32_t blob_id;	/* ABS_MT_BLOB_ID */
+		int32_t id;		/* ABS_MT_TRACKING_ID */
+		int32_t p;		/* ABS_MT_PRESSURE */
+		int32_t dist;		/* ABS_MT_DISTANCE */
+		int32_t tool_x;		/* ABS_MT_TOOL_X */
+		int32_t tool_y;		/* ABS_MT_TOOL_Y */
+	};
+};
+_Static_assert(offsetof(union evdev_mt_slot, tool_y) ==
+    offsetof(union evdev_mt_slot, val[ABS_MT_INDEX(ABS_MT_TOOL_Y)]),
+    "evdev_mt_slot array members does not match their structure aliases");
+
 /* Input device interface: */
 struct evdev_dev *evdev_alloc(void);
 void evdev_free(struct evdev_dev *);
@@ -118,7 +143,7 @@ void evdev_support_event(struct evdev_dev *, uint16_t);
 void evdev_support_key(struct evdev_dev *, uint16_t);
 void evdev_support_rel(struct evdev_dev *, uint16_t);
 void evdev_support_abs(struct evdev_dev *, uint16_t, int32_t, int32_t, int32_t,
-   int32_t, int32_t, int32_t);
+   int32_t, int32_t);
 void evdev_support_msc(struct evdev_dev *, uint16_t);
 void evdev_support_led(struct evdev_dev *, uint16_t);
 void evdev_support_snd(struct evdev_dev *, uint16_t);
@@ -126,13 +151,16 @@ void evdev_support_sw(struct evdev_dev *, uint16_t);
 void evdev_set_repeat_params(struct evdev_dev *, uint16_t, int);
 int evdev_set_report_size(struct evdev_dev *, size_t);
 void evdev_set_flag(struct evdev_dev *, uint16_t);
+void *evdev_get_softc(struct evdev_dev *);
+bool evdev_is_grabbed(struct evdev_dev *);
 
 /* Multitouch related functions: */
-int32_t evdev_get_mt_slot_by_tracking_id(struct evdev_dev *, int32_t);
-void evdev_support_nfingers(struct evdev_dev *, int32_t);
-void evdev_support_mt_compat(struct evdev_dev *);
-void evdev_push_nfingers(struct evdev_dev *, int32_t);
-void evdev_push_mt_compat(struct evdev_dev *);
+int evdev_mt_id_to_slot(struct evdev_dev *, int32_t);
+int evdev_mt_push_slot(struct evdev_dev *, int, union evdev_mt_slot *);
+int evdev_mt_push_frame(struct evdev_dev *, union evdev_mt_slot *, int);
+void evdev_mt_match_frame(struct evdev_dev *, union evdev_mt_slot *, int);
+union evdev_mt_slot *evdev_mt_get_match_slots(struct evdev_dev *);
+void evdev_mt_push_autorel(struct evdev_dev *);
 
 /* Utility functions: */
 uint16_t evdev_hid2key(int);
@@ -141,6 +169,8 @@ uint16_t evdev_scancode2key(int *, int);
 void evdev_push_mouse_btn(struct evdev_dev *, int);
 void evdev_push_leds(struct evdev_dev *, int);
 void evdev_push_repeats(struct evdev_dev *, keyboard_t *);
+void evdev_support_nfingers(struct evdev_dev *, int);
+void evdev_push_nfingers(struct evdev_dev *, int);
 evdev_event_t evdev_ev_kbd_event;
 
 /* Event reporting shortcuts: */
@@ -197,7 +227,7 @@ static __inline int
 evdev_push_snd(struct evdev_dev *evdev, uint16_t code, int32_t value)
 {
 
-	return (evdev_push_event(evdev, EV_SND, code, value != 0));
+	return (evdev_push_event(evdev, EV_SND, code, value));
 }
 
 static __inline int
diff --git a/sys/dev/misc/evdev/evdev_mt.c b/sys/dev/misc/evdev/evdev_mt.c
index 3034bcd139..49e3e2ca36 100644
--- a/sys/dev/misc/evdev/evdev_mt.c
+++ b/sys/dev/misc/evdev/evdev_mt.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2016 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2016, 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -25,12 +25,29 @@
  *
  * $FreeBSD$
  */
+/*-
+ * 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.
+ */
+ 
 #include "opt_evdev.h"
 
 #include <sys/param.h>
 #include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/systm.h>
+#include <sys/limits.h>
 
 #include <dev/misc/evdev/evdev.h>
 #include <dev/misc/evdev/evdev_private.h>
@@ -42,211 +59,594 @@
 #define	debugf(fmt, args...)
 #endif
 
-static uint16_t evdev_fngmap[] = {
-	BTN_TOOL_FINGER,
-	BTN_TOOL_DOUBLETAP,
-	BTN_TOOL_TRIPLETAP,
-	BTN_TOOL_QUADTAP,
-	BTN_TOOL_QUINTTAP,
-};
+typedef	u_int32_t	slotset_t;
 
-static uint16_t evdev_mtstmap[][2] = {
-	{ ABS_MT_POSITION_X, ABS_X },
-	{ ABS_MT_POSITION_Y, ABS_Y },
-	{ ABS_MT_PRESSURE, ABS_PRESSURE },
-	{ ABS_MT_TOUCH_MAJOR, ABS_TOOL_WIDTH },
-};
+_Static_assert(MAX_MT_SLOTS < sizeof(slotset_t) * 8, "MAX_MT_SLOTS too big");
+
+#define FOREACHBIT(v, i) \
+	for ((i) = ffs(v) - 1; (i) != -1; (i) = ffs((v) & (~1 << (i))) - 1)
 
-struct evdev_mt_slot {
-	uint64_t ev_report;
-	int32_t ev_mt_states[MT_CNT];
+static struct {
+	uint16_t	mt;
+	uint16_t	st;
+	int32_t		max;
+} evdev_mtstmap[] = {
+	{ ABS_MT_POSITION_X,	ABS_X,		0 },
+	{ ABS_MT_POSITION_Y,	ABS_Y,		0 },
+	{ ABS_MT_PRESSURE,	ABS_PRESSURE,	255 },
+	{ ABS_MT_TOUCH_MAJOR,	ABS_TOOL_WIDTH,	15 },
 };
 
 struct evdev_mt {
-	int32_t	ev_mt_last_reported_slot;
-	struct evdev_mt_slot ev_mt_slots[];
+	int			last_reported_slot;
+	uint16_t		tracking_id;
+	int32_t			tracking_ids[MAX_MT_SLOTS];
+	bool			type_a;
+	u_int			mtst_events;
+	/* the set of slots with active touches */
+	slotset_t		touches;
+	/* the set of slots with unsynchronized state */
+	slotset_t		frame;
+	/* the set of slots to match with active touches */
+	slotset_t		match_frame;
+	int			match_slot;
+	union evdev_mt_slot	*match_slots;
+	int			*matrix;
+	union evdev_mt_slot	slots[];
 };
 
+static void	evdev_mt_support_st_compat(struct evdev_dev *);
+static void	evdev_mt_send_st_compat(struct evdev_dev *);
+static void	evdev_mt_send_autorel(struct evdev_dev *);
+static void	evdev_mt_replay_events(struct evdev_dev *);
+
+static inline int
+ffc_slot(struct evdev_dev *evdev, slotset_t slots)
+{
+	return (ffs(~slots & ((2U << MAXIMAL_MT_SLOT(evdev)) - 1)) - 1);
+}
+
 void
 evdev_mt_init(struct evdev_dev *evdev)
 {
-	int32_t slot, slots;
+	struct evdev_mt *mt;
+	size_t size = offsetof(struct evdev_mt, slots);
+	int slot, slots;
+	bool type_a;
+
+	type_a = !bit_test(evdev->ev_abs_flags, ABS_MT_SLOT);
+	if (type_a) {
+		/* Add events produced by MT type A to type B converter */
+		evdev_support_abs(evdev,
+		    ABS_MT_SLOT, 0, MAX_MT_SLOTS - 1, 0, 0, 0);
+		evdev_support_abs(evdev,
+		    ABS_MT_TRACKING_ID, -1, MAX_MT_SLOTS - 1, 0, 0, 0);
+	}
 
 	slots = MAXIMAL_MT_SLOT(evdev) + 1;
+	size += sizeof(mt->slots[0]) * slots;
+	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK)) {
+		size += sizeof(mt->match_slots[0]) * slots;
+		size += sizeof(mt->matrix[0]) * (slots + 6) * slots;
+	}
 
-	evdev->ev_mt = kmalloc(offsetof(struct evdev_mt, ev_mt_slots) +
-	     sizeof(struct evdev_mt_slot) * slots, M_EVDEV, M_WAITOK | M_ZERO);
-
-	/* Initialize multitouch protocol type B states */
-	for (slot = 0; slot < slots; slot++) {
-		/*
-		 * .ev_report should not be initialized to initial value of
-		 * report counter (0) as it brokes free slot detection in
-		 * evdev_get_mt_slot_by_tracking_id. So initialize it to -1
-		 */
-		evdev->ev_mt->ev_mt_slots[slot] = (struct evdev_mt_slot) {
-			.ev_report = 0xFFFFFFFFFFFFFFFFULL,
-			.ev_mt_states[ABS_MT_INDEX(ABS_MT_TRACKING_ID)] = -1,
-		};
+	mt = kmalloc(size, M_EVDEV, M_WAITOK | M_ZERO);
+	evdev->ev_mt = mt;
+	mt->type_a = type_a;
+ 
+	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK)) {
+		mt->match_slots = mt->slots + slots;
+		mt->matrix = (int *)(mt->match_slots + slots);
 	}
+ 
+	/* Initialize multitouch protocol type B states */
+	for (slot = 0; slot < slots; slot++)
+		mt->slots[slot].id = -1;
 
+	if (!bit_test(evdev->ev_flags, EVDEV_FLAG_MT_KEEPID))
+		evdev_support_abs(evdev,
+		    ABS_MT_TRACKING_ID, -1, UINT16_MAX, 0, 0, 0);
 	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_STCOMPAT))
-		evdev_support_mt_compat(evdev);
+		evdev_mt_support_st_compat(evdev);
 }
 
 void
 evdev_mt_free(struct evdev_dev *evdev)
 {
-
 	kfree(evdev->ev_mt, M_EVDEV);
 }
 
-int32_t
-evdev_get_last_mt_slot(struct evdev_dev *evdev)
+void
+evdev_mt_sync_frame(struct evdev_dev *evdev)
+{
+	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK))
+		evdev_mt_replay_events(evdev);
+	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_AUTOREL))
+		evdev_mt_send_autorel(evdev);
+	if (evdev->ev_report_opened &&
+	    bit_test(evdev->ev_flags, EVDEV_FLAG_MT_STCOMPAT))
+		evdev_mt_send_st_compat(evdev);
+	evdev->ev_mt->frame = 0;
+}
+
+static void
+evdev_mt_send_slot(struct evdev_dev *evdev, int slot,
+    union evdev_mt_slot *state)
+{
+	int i;
+	bool type_a = !bit_test(evdev->ev_abs_flags, ABS_MT_SLOT);
+
+	EVDEV_LOCK_ASSERT(evdev);
+	KKASSERT(type_a || (slot >= 0 && slot <= MAXIMAL_MT_SLOT(evdev)));
+	KKASSERT(!type_a || state != NULL);
+
+	if (!type_a) {
+		evdev_send_event(evdev, EV_ABS, ABS_MT_SLOT, slot);
+		if (state == NULL) {
+			evdev_send_event(evdev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+			return;
+		}
+	}
+	bit_foreach_at(evdev->ev_abs_flags, ABS_MT_FIRST, ABS_MT_LAST + 1, i)
+		evdev_send_event(evdev, EV_ABS, i,
+		    state->val[ABS_MT_INDEX(i)]);
+	if (type_a)
+		evdev_send_event(evdev, EV_SYN, SYN_MT_REPORT, 1);
+}
+
+int
+evdev_mt_push_slot(struct evdev_dev *evdev, int slot,
+    union evdev_mt_slot *state)
 {
+	struct evdev_mt *mt = evdev->ev_mt;
+	bool type_a = !bit_test(evdev->ev_abs_flags, ABS_MT_SLOT);
 
-	return (evdev->ev_mt->ev_mt_last_reported_slot);
+	if ((type_a || (mt != NULL && mt->type_a)) && state == NULL)
+		return (EINVAL);
+	if (!type_a && (slot < 0 || slot > MAXIMAL_MT_SLOT(evdev)))
+		return (EINVAL);
+
+	EVDEV_ENTER(evdev);
+	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK) && mt->type_a) {
+		mt->match_slots[mt->match_slot] = *state;
+		evdev_mt_record_event(evdev, EV_SYN, SYN_MT_REPORT, 1);
+	} else if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK)) {
+		evdev_mt_record_event(evdev, EV_ABS, ABS_MT_SLOT, slot);
+		if (state != NULL)
+			mt->match_slots[mt->match_slot] = *state;
+		else
+			evdev_mt_record_event(evdev, EV_ABS,
+			    ABS_MT_TRACKING_ID, -1);
+	} else
+		evdev_mt_send_slot(evdev, slot, state);
+	EVDEV_EXIT(evdev);
+
+	return (0);
 }
 
+/*
+ * 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.
+ */
+static void
+evdev_mt_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) */
+
+	row = 0; /* fixes maybe-uninitialized warning */
+
+	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 tracking IDs to the points in the pt array.  The tracking ID
+ * 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.
+ * Set tracking id to -1 for unassigned (new) points.
+ */
 void
-evdev_set_last_mt_slot(struct evdev_dev *evdev, int32_t slot)
+evdev_mt_match_frame(struct evdev_dev *evdev, union evdev_mt_slot *pt,
+    int size)
 {
+	struct evdev_mt *mt = evdev->ev_mt;
+	int i, j, m, n, dx, dy, slot, num_touches;
+	int *p, *r2c, *c2r;
 
-	evdev->ev_mt->ev_mt_slots[slot].ev_report = evdev->ev_report_count;
-	evdev->ev_mt->ev_mt_last_reported_slot = slot;
+	EVDEV_LOCK_ASSERT(evdev);
+	KKASSERT(mt->matrix != NULL);
+	KKASSERT(size >= 0 && size <= MAXIMAL_MT_SLOT(evdev) + 1);
+
+	if (size == 0)
+		return;
+
+	p = mt->matrix;
+	num_touches = bitcount32(mt->touches);
+	if (num_touches >= size) {
+		FOREACHBIT(mt->touches, slot)
+			for (i = 0; i < size; i++) {
+				dx = pt[i].x - mt->slots[slot].x;
+				dy = pt[i].y - mt->slots[slot].y;
+				*p++ = dx * dx + dy * dy;
+			}
+		m = num_touches;
+		n = size;
+	} else {
+		for (i = 0; i < size; i++)
+			FOREACHBIT(mt->touches, slot) {
+				dx = pt[i].x - mt->slots[slot].x;
+				dy = pt[i].y - mt->slots[slot].y;
+				*p++ = dx * dx + dy * dy;
+			}
+		m = size;
+		n = num_touches;
+	}
+	evdev_mt_matching(mt->matrix, m, n, p);
+
+	r2c = p;
+	c2r = p + m;
+	for (i = 0; i < m; i++)
+		if ((j = r2c[i]) >= 0)
+			c2r[j] = i;
+
+	p = (n == size ? c2r : r2c);
+	for (i = 0; i < size; i++)
+		if (*p++ < 0)
+			pt[i].id = -1;
+
+	p = (n == size ? r2c : c2r);
+	FOREACHBIT(mt->touches, slot)
+		if ((i = *p++) >= 0)
+			pt[i].id = mt->tracking_ids[slot];
 }
 
-inline int32_t
-evdev_get_mt_value(struct evdev_dev *evdev, int32_t slot, int16_t code)
+static void
+evdev_mt_send_frame(struct evdev_dev *evdev, union evdev_mt_slot *pt, int size)
 {
+	struct evdev_mt *mt = evdev->ev_mt;
+	union evdev_mt_slot *slot;
 
-	return (evdev->ev_mt->
-	    ev_mt_slots[slot].ev_mt_states[ABS_MT_INDEX(code)]);
+	EVDEV_LOCK_ASSERT(evdev);
+	KKASSERT(size >= 0 && size <= MAXIMAL_MT_SLOT(evdev) + 1);
+
+	/*
+	 * While MT-matching assign tracking IDs of new contacts to be equal
+	 * to a slot number to make things simpler.
+	 */
+	for (slot = pt; slot < pt + size; slot++) {
+		if (slot->id < 0)
+			slot->id = ffc_slot(evdev, mt->touches | mt->frame);
+		if (slot->id >= 0)
+			evdev_mt_send_slot(evdev, slot->id, slot);
+	}
 }
 
-inline void
-evdev_set_mt_value(struct evdev_dev *evdev, int32_t slot, int16_t code,
+int
+evdev_mt_push_frame(struct evdev_dev *evdev, union evdev_mt_slot *pt, int size)
+{
+	if (size < 0 || size > MAXIMAL_MT_SLOT(evdev) + 1)
+		return (EINVAL);
+
+	EVDEV_ENTER(evdev);
+	evdev_mt_send_frame(evdev, pt, size);
+	EVDEV_EXIT(evdev);
+
+	return (0);
+}
+
+bool
+evdev_mt_record_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
     int32_t value)
 {
+	struct evdev_mt *mt = evdev->ev_mt;
+
+	EVDEV_LOCK_ASSERT(evdev);
+
+	switch (type) {
+	case EV_SYN:
+		if (code == SYN_MT_REPORT) {
+			/* MT protocol type A support */
+			KASSERT(mt->type_a, ("Not a MT type A protocol"));
+			mt->match_frame |= 1U << mt->match_slot;
+			mt->match_slot++;
+			return (true);
+		}
+		break;
+	case EV_ABS:
+		if (code == ABS_MT_SLOT) {
+			/* MT protocol type B support */
+			KASSERT(!mt->type_a, ("Not a MT type B protocol"));
+			KASSERT(value >= 0, ("Negative slot number"));
+			mt->match_slot = value;
+			mt->match_frame |= 1U << mt->match_slot;
+			return (true);
+		} else if (code == ABS_MT_TRACKING_ID) {
+			KASSERT(!mt->type_a, ("Not a MT type B protocol"));
+			if (value == -1)
+				mt->match_frame &= ~(1U << mt->match_slot);
+			return (true);
+		} else if (ABS_IS_MT(code)) {
+			KASSERT(mt->match_slot >= 0, ("Negative slot"));
+			KASSERT(mt->match_slot <= MAXIMAL_MT_SLOT(evdev),
+			    ("Slot number too big"));
+			mt->match_slots[mt->match_slot].
+			    val[ABS_MT_INDEX(code)] = value;
+			return (true);
+		}
+		break;
+	default:
+		break;
+	}
 
-	evdev->ev_mt->ev_mt_slots[slot].ev_mt_states[ABS_MT_INDEX(code)] =
-	    value;
+	return (false);
 }
 
-int32_t
-evdev_get_mt_slot_by_tracking_id(struct evdev_dev *evdev, int32_t tracking_id)
+static void
+evdev_mt_replay_events(struct evdev_dev *evdev)
 {
-	int32_t tr_id, slot, free_slot = -1;
+	struct evdev_mt *mt = evdev->ev_mt;
+	int slot, size = 0;
 
-	for (slot = 0; slot <= MAXIMAL_MT_SLOT(evdev); slot++) {
-		tr_id = evdev_get_mt_value(evdev, slot, ABS_MT_TRACKING_ID);
-		if (tr_id == tracking_id)
-			return (slot);
-		/*
-		 * Its possible that slot will be reassigned in a place of just
-		 * released one within the same report. To avoid this compare
-		 * report counter with slot`s report number updated with each
-		 * ABS_MT_TRACKING_ID change.
-		 */
-		if (free_slot == -1 && tr_id == -1 &&
-		    evdev->ev_mt->ev_mt_slots[slot].ev_report !=
-		    evdev->ev_report_count)
-			free_slot = slot;
+	EVDEV_LOCK_ASSERT(evdev);
+
+	FOREACHBIT(mt->match_frame, slot) {
+		if (slot != size)
+			mt->match_slots[size] = mt->match_slots[slot];
+		size++;
 	}
+	evdev_mt_match_frame(evdev, mt->match_slots, size);
+	evdev_mt_send_frame(evdev, mt->match_slots, size);
+	mt->match_slot = 0;
+	mt->match_frame = 0;
+}
 
-	return (free_slot);
+union evdev_mt_slot *
+evdev_mt_get_match_slots(struct evdev_dev *evdev)
+{
+	return (evdev->ev_mt->match_slots);
 }
 
-void
-evdev_support_nfingers(struct evdev_dev *evdev, int32_t nfingers)
+int
+evdev_mt_get_last_slot(struct evdev_dev *evdev)
 {
-	int32_t i;
 
-	for (i = 0; i < MIN(nitems(evdev_fngmap), nfingers); i++)
-		evdev_support_key(evdev, evdev_fngmap[i]);
+	return (evdev->ev_mt->last_reported_slot);
 }
 
 void
-evdev_support_mt_compat(struct evdev_dev *evdev)
+evdev_mt_set_last_slot(struct evdev_dev *evdev, int32_t slot)
 {
-	int32_t i;
+	struct evdev_mt *mt = evdev->ev_mt;
 
-	if (evdev->ev_absinfo == NULL)
-		return;
+	KKASSERT(slot >= 0 && slot <= MAXIMAL_MT_SLOT(evdev));
 
-	evdev_support_event(evdev, EV_KEY);
-	evdev_support_key(evdev, BTN_TOUCH);
+	mt->frame |= 1U << slot;
+	mt->last_reported_slot = slot;
+}
 
-	/* Touchscreens should not advertise tap tool capabilities */
-	if (!bit_test(evdev->ev_prop_flags, INPUT_PROP_DIRECT))
-		evdev_support_nfingers(evdev, MAXIMAL_MT_SLOT(evdev) + 1);
+int32_t
+evdev_mt_get_value(struct evdev_dev *evdev, int32_t slot, int16_t code)
+{
+	struct evdev_mt *mt = evdev->ev_mt;
 
-	/* Echo 0-th MT-slot as ST-slot */
-	for (i = 0; i < nitems(evdev_mtstmap); i++)
-		if (bit_test(evdev->ev_abs_flags, evdev_mtstmap[i][0]))
-			evdev_support_abs(evdev, evdev_mtstmap[i][1],
-			    evdev->ev_absinfo[evdev_mtstmap[i][0]].value,
-			    evdev->ev_absinfo[evdev_mtstmap[i][0]].minimum,
-			    evdev->ev_absinfo[evdev_mtstmap[i][0]].maximum,
-			    evdev->ev_absinfo[evdev_mtstmap[i][0]].fuzz,
-			    evdev->ev_absinfo[evdev_mtstmap[i][0]].flat,
-			    evdev->ev_absinfo[evdev_mtstmap[i][0]].resolution);
+	KKASSERT(slot >= 0 && slot <= MAXIMAL_MT_SLOT(evdev));
+
+	return (mt->slots[slot].val[ABS_MT_INDEX(code)]);
 }
 
-static int32_t
-evdev_count_fingers(struct evdev_dev *evdev)
+void
+evdev_mt_set_value(struct evdev_dev *evdev, int32_t slot, int16_t code,
+    int32_t value)
 {
-	int32_t nfingers = 0, i;
+	struct evdev_mt *mt = evdev->ev_mt;
 
-	for (i = 0; i <= MAXIMAL_MT_SLOT(evdev); i++)
-		if (evdev_get_mt_value(evdev, i, ABS_MT_TRACKING_ID) != -1)
-			nfingers++;
+	KKASSERT(slot >= 0 && slot <= MAXIMAL_MT_SLOT(evdev));
 
-	return (nfingers);
+	if (code == ABS_MT_TRACKING_ID) {
+		if (value != -1)
+			mt->touches |= 1U << slot;
+		else
+			mt->touches &= ~(1U << slot);
+	}
+	mt->slots[slot].val[ABS_MT_INDEX(code)] = value;
 }
 
-static void
-evdev_send_nfingers(struct evdev_dev *evdev, int32_t nfingers)
+int
+evdev_mt_id_to_slot(struct evdev_dev *evdev, int32_t tracking_id)
 {
-	int32_t i;
+	struct evdev_mt *mt = evdev->ev_mt;
+	int slot;
 
-	EVDEV_LOCK_ASSERT(evdev);
+	KASSERT(!mt->type_a, ("Not a MT type B protocol"));
 
-	if (nfingers > nitems(evdev_fngmap))
-		nfingers = nitems(evdev_fngmap);
+	/*
+	 * Ignore tracking_id if slot assignment is performed by evdev.
+	 * Events are written sequentially to temporary matching buffer.
+	 */
+	if (bit_test(evdev->ev_flags, EVDEV_FLAG_MT_TRACK))
+		return (ffc_slot(evdev, mt->match_frame));
 
-	for (i = 0; i < nitems(evdev_fngmap); i++)
-		evdev_send_event(evdev, EV_KEY, evdev_fngmap[i],
-		    nfingers == i + 1);
+	FOREACHBIT(mt->touches, slot)
+		if (mt->tracking_ids[slot] == tracking_id)
+			return (slot);
+	/*
+	 * Do not allow allocation of new slot in a place of just
+	 * released one within the same report.
+	 */
+	return (ffc_slot(evdev, mt->touches | mt->frame));
 }
 
-void
-evdev_push_nfingers(struct evdev_dev *evdev, int32_t nfingers)
+int32_t
+evdev_mt_reassign_id(struct evdev_dev *evdev, int slot, int32_t id)
 {
+	struct evdev_mt *mt = evdev->ev_mt;
+	int32_t nid;
 
-	EVDEV_ENTER(evdev);
-	evdev_send_nfingers(evdev, nfingers);
-	EVDEV_EXIT(evdev);
+	if (id == -1 || bit_test(evdev->ev_flags, EVDEV_FLAG_MT_KEEPID)) {
+		mt->tracking_ids[slot] = id;
+		return (id);
+	}
+
+	nid = evdev_mt_get_value(evdev, slot, ABS_MT_TRACKING_ID);
+	if (nid != -1) {
+		KASSERT(id == mt->tracking_ids[slot],
+		    ("MT-slot tracking id has changed"));
+		return (nid);
+	}
+
+	mt->tracking_ids[slot] = id;
+again:
+	nid = mt->tracking_id++;
+	FOREACHBIT(mt->touches, slot)
+		if (evdev_mt_get_value(evdev, slot, ABS_MT_TRACKING_ID) == nid)
+			goto again;
+
+	return (nid);
 }
 
-void
-evdev_send_mt_compat(struct evdev_dev *evdev)
+static inline int32_t
+evdev_mt_normalize(int32_t value, int32_t mtmin, int32_t mtmax, int32_t stmax)
 {
-	int32_t nfingers, i;
+	if (stmax != 0 && mtmax != mtmin) {
+		value = (value - mtmin) * stmax / (mtmax - mtmin);
+		value = MAX(MIN(value, stmax), 0);
+	}
+	return (value);
+}
+
+static void
+evdev_mt_support_st_compat(struct evdev_dev *evdev)
+{
+	struct input_absinfo *ai;
+	int i;
+
+	if (evdev->ev_absinfo == NULL)
+		return;
+
+	evdev_support_event(evdev, EV_KEY);
+	evdev_support_key(evdev, BTN_TOUCH);
+
+	/* Touchscreens should not advertise tap tool capabilities */
+	if (!bit_test(evdev->ev_prop_flags, INPUT_PROP_DIRECT))
+		evdev_support_nfingers(evdev, MAXIMAL_MT_SLOT(evdev) + 1);
+
+	/* Echo 0-th MT-slot as ST-slot */
+	for (i = 0; i < nitems(evdev_mtstmap); i++) {
+		if (!bit_test(evdev->ev_abs_flags, evdev_mtstmap[i].mt) ||
+		     bit_test(evdev->ev_abs_flags, evdev_mtstmap[i].st))
+			continue;
+		ai = evdev->ev_absinfo + evdev_mtstmap[i].mt;
+		evdev->ev_mt->mtst_events |= 1U << i;
+		if (evdev_mtstmap[i].max != 0)
+			evdev_support_abs(evdev, evdev_mtstmap[i].st,
+			    0,
+			    evdev_mtstmap[i].max,
+			    0,
+			    evdev_mt_normalize(
+			      ai->flat, 0, ai->maximum, evdev_mtstmap[i].max),
+			    0);
+		else
+			evdev_support_abs(evdev, evdev_mtstmap[i].st,
+			    ai->minimum,
+			    ai->maximum,
+			    0,
+			    ai->flat,
+			    ai->resolution);
+	}
+}
+
+static void
+evdev_mt_send_st_compat(struct evdev_dev *evdev)
+{
+	struct evdev_mt *mt = evdev->ev_mt;
+	int nfingers, i, st_slot;
 
 	EVDEV_LOCK_ASSERT(evdev);
 
-	nfingers = evdev_count_fingers(evdev);
+	nfingers = bitcount32(mt->touches);
 	evdev_send_event(evdev, EV_KEY, BTN_TOUCH, nfingers > 0);
 
-	if (evdev_get_mt_value(evdev, 0, ABS_MT_TRACKING_ID) != -1)
-		/* Echo 0-th MT-slot as ST-slot */
-		for (i = 0; i < nitems(evdev_mtstmap); i++)
-			if (bit_test(evdev->ev_abs_flags, evdev_mtstmap[i][1]))
-				evdev_send_event(evdev, EV_ABS,
-				    evdev_mtstmap[i][1],
-				    evdev_get_mt_value(evdev, 0,
-				    evdev_mtstmap[i][0]));
+	/* Send first active MT-slot state as single touch report */
+	st_slot = ffs(mt->touches) - 1;
+	if (st_slot != -1)
+		FOREACHBIT(mt->mtst_events, i)
+			evdev_send_event(evdev, EV_ABS, evdev_mtstmap[i].st,
+			    evdev_mt_normalize(evdev_mt_get_value(evdev,
+			      st_slot, evdev_mtstmap[i].mt),
+			      evdev->ev_absinfo[evdev_mtstmap[i].mt].minimum,
+			      evdev->ev_absinfo[evdev_mtstmap[i].mt].maximum,
+			      evdev_mtstmap[i].max));
 
 	/* Touchscreens should not report tool taps */
 	if (!bit_test(evdev->ev_prop_flags, INPUT_PROP_DIRECT))
@@ -256,29 +656,23 @@ evdev_send_mt_compat(struct evdev_dev *evdev)
 		evdev_send_event(evdev, EV_ABS, ABS_PRESSURE, 0);
 }
 
-void
-evdev_push_mt_compat(struct evdev_dev *evdev)
+static void
+evdev_mt_send_autorel(struct evdev_dev *evdev)
 {
+	struct evdev_mt *mt = evdev->ev_mt;
+	int slot;
 
-	EVDEV_ENTER(evdev);
-	evdev_send_mt_compat(evdev);
-	EVDEV_EXIT(evdev);
+	EVDEV_LOCK_ASSERT(evdev);
+	KASSERT(mt->match_frame == 0, ("Unmatched events exist"));
+
+	FOREACHBIT(mt->touches & ~mt->frame, slot)
+		evdev_mt_send_slot(evdev, slot, NULL);
 }
 
 void
-evdev_send_mt_autorel(struct evdev_dev *evdev)
+evdev_mt_push_autorel(struct evdev_dev *evdev)
 {
-	int32_t slot;
-
-	EVDEV_LOCK_ASSERT(evdev);
-
-	for (slot = 0; slot <= MAXIMAL_MT_SLOT(evdev); slot++) {
-		if (evdev->ev_mt->ev_mt_slots[slot].ev_report !=
-		    evdev->ev_report_count &&
-		    evdev_get_mt_value(evdev, slot, ABS_MT_TRACKING_ID) != -1){
-			evdev_send_event(evdev, EV_ABS, ABS_MT_SLOT, slot);
-			evdev_send_event(evdev, EV_ABS, ABS_MT_TRACKING_ID,
-			    -1);
-		}
-	}
+	EVDEV_ENTER(evdev);
+	evdev_mt_send_autorel(evdev);
+	EVDEV_EXIT(evdev);
 }
diff --git a/sys/dev/misc/evdev/evdev_private.h b/sys/dev/misc/evdev/evdev_private.h
index 76e8004573..0481d1b6f1 100644
--- a/sys/dev/misc/evdev/evdev_private.h
+++ b/sys/dev/misc/evdev/evdev_private.h
@@ -34,6 +34,7 @@
 #include <sys/malloc.h>
 #include <sys/queue.h>
 #include <sys/event.h>
+#include <sys/sysctl.h>
 
 /* Use a local copy of FreeBSD's bitstring.h. */
 #include "freebsd-bitstring.h"
@@ -136,6 +137,9 @@ struct evdev_dev
 	const struct evdev_methods * ev_methods;
 	void *			ev_softc;
 
+	/* Sysctl: */
+	struct sysctl_ctx_list	ev_sysctl_ctx;
+
 	LIST_ENTRY(evdev_dev) ev_link;
 	LIST_HEAD(, evdev_client) ev_clients;
 };
@@ -208,14 +212,16 @@ void evdev_revoke_client(struct evdev_client *);
 /* Multitouch related functions: */
 void evdev_mt_init(struct evdev_dev *);
 void evdev_mt_free(struct evdev_dev *);
-int32_t evdev_get_last_mt_slot(struct evdev_dev *);
-void evdev_set_last_mt_slot(struct evdev_dev *, int32_t);
-int32_t evdev_get_mt_value(struct evdev_dev *, int32_t, int16_t);
-void evdev_set_mt_value(struct evdev_dev *, int32_t, int16_t, int32_t);
-void evdev_send_mt_compat(struct evdev_dev *);
-void evdev_send_mt_autorel(struct evdev_dev *);
+void evdev_mt_sync_frame(struct evdev_dev *);
+int evdev_mt_get_last_slot(struct evdev_dev *);
+void evdev_mt_set_last_slot(struct evdev_dev *, int);
+int32_t evdev_mt_get_value(struct evdev_dev *, int, int16_t);
+void evdev_mt_set_value(struct evdev_dev *, int, int16_t, int32_t);
+int32_t evdev_mt_reassign_id(struct evdev_dev *, int, int32_t);
+bool evdev_mt_record_event(struct evdev_dev *, uint16_t, uint16_t, int32_t);
 
 /* Utility functions: */
 void evdev_client_dumpqueue(struct evdev_client *);
+void evdev_send_nfingers(struct evdev_dev *, int);
 
 #endif	/* _DEV_EVDEV_EVDEV_PRIVATE_H */
diff --git a/sys/dev/misc/evdev/evdev_utils.c b/sys/dev/misc/evdev/evdev_utils.c
index 2fed2e2911..cb84d0cecd 100644
--- a/sys/dev/misc/evdev/evdev_utils.c
+++ b/sys/dev/misc/evdev/evdev_utils.c
@@ -36,6 +36,7 @@
 #include <sys/systm.h>
 
 #include <dev/misc/evdev/evdev.h>
+#include <dev/misc/evdev/evdev_private.h>
 #include <dev/misc/evdev/input.h>
 #include <dev/misc/kbd/kbdreg.h>
 
@@ -128,7 +129,7 @@ static uint16_t evdev_at_set1_scancodes[] = {
 	KEY_APOSTROPHE,	KEY_GRAVE,	KEY_LEFTSHIFT,	KEY_BACKSLASH,
 	KEY_Z,		KEY_X,		KEY_C,		KEY_V,
 	KEY_B,		KEY_N,		KEY_M,		KEY_COMMA,
-	KEY_DOT,	KEY_SLASH,	KEY_RIGHTSHIFT,	NONE,
+	KEY_DOT,	KEY_SLASH,	KEY_RIGHTSHIFT,	KEY_KPASTERISK,
 	KEY_LEFTALT,	KEY_SPACE,	KEY_CAPSLOCK,	KEY_F1,
 	KEY_F2,		KEY_F3,		KEY_F4,		KEY_F5,
 	/* 0x40 - 0x5f */
@@ -139,13 +140,13 @@ static uint16_t evdev_at_set1_scancodes[] = {
 	KEY_KP2,	KEY_KP3,	KEY_KP0,	KEY_KPDOT,
 	NONE,		NONE,		KEY_102ND,	KEY_F11,
 	KEY_F12,	NONE,		NONE,		NONE,
-	NONE,		NONE,		NONE,		NONE,
+	NONE,		KEY_F13,	NONE,		NONE,
 	/* 0x60 - 0x7f */
 	NONE,		NONE,		NONE,		NONE,
 	NONE,		NONE,		NONE,		NONE,
 	NONE,		NONE,		NONE,		NONE,
 	NONE,		NONE,		NONE,		NONE,
-	KEY_KATAKANAHIRAGANA,	NONE,	NONE,		KEY_RO,
+	KEY_KATAKANAHIRAGANA,	KEY_HANJA,	KEY_HANGEUL,	KEY_RO,
 	NONE,		NONE,	KEY_ZENKAKUHANKAKU,	KEY_HIRAGANA,
 	KEY_KATAKANA,	KEY_HENKAN,	NONE,		KEY_MUHENKAN,
 	NONE,		KEY_YEN,	KEY_KPCOMMA,	NONE,
@@ -164,17 +165,17 @@ static uint16_t evdev_at_set1_scancodes[] = {
 	NONE,		NONE,		NONE,		NONE,
 	NONE,		NONE,		KEY_VOLUMEDOWN,	NONE,
 	KEY_VOLUMEUP,	NONE,		KEY_HOMEPAGE,	NONE,
-	NONE,		KEY_KPASTERISK,	NONE,		KEY_SYSRQ,
-	KEY_RIGHTALT,	NONE,		NONE,		NONE,
-	NONE,		NONE,		NONE,		NONE,
+	NONE,		KEY_KPSLASH,	NONE,		KEY_SYSRQ,
+	KEY_RIGHTALT,	NONE,		NONE,		KEY_F13,
+	KEY_F14,	KEY_F15,	KEY_F16,	KEY_F17,
 	/* 0x40 - 0x5f. 0xE0 prefixed */
-	NONE,		NONE,		NONE,		NONE,
-	NONE,		NONE,		KEY_PAUSE,	KEY_HOME,
+	KEY_F18,	KEY_F19,	KEY_F20,	KEY_F21,
+	KEY_F22,	NONE,		KEY_PAUSE,	KEY_HOME,
 	KEY_UP,		KEY_PAGEUP,	NONE,		KEY_LEFT,
 	NONE,		KEY_RIGHT,	NONE,		KEY_END,
 	KEY_DOWN,	KEY_PAGEDOWN,	KEY_INSERT,	KEY_DELETE,
-	NONE,		NONE,		NONE,		NONE,
-	NONE,		NONE,		NONE,		KEY_LEFTMETA,
+	NONE,		NONE,		NONE,		KEY_F23,
+	KEY_F24,	NONE,		NONE,		KEY_LEFTMETA,
 	KEY_RIGHTMETA,	KEY_MENU,	KEY_POWER,	KEY_SLEEP,
 	/* 0x60 - 0x7f. 0xE0 prefixed */
 	NONE,		NONE,		NONE,		KEY_WAKEUP,
@@ -204,6 +205,14 @@ static uint16_t evdev_led_codes[] = {
 	LED_SCROLLL,	/* SLKED */
 };
 
+static uint16_t evdev_nfinger_codes[] = {
+	BTN_TOOL_FINGER,
+	BTN_TOOL_DOUBLETAP,
+	BTN_TOOL_TRIPLETAP,
+	BTN_TOOL_QUADTAP,
+	BTN_TOOL_QUINTTAP,
+};
+
 uint16_t
 evdev_hid2key(int scancode)
 {
@@ -249,12 +258,15 @@ evdev_scancode2key(int *state, int scancode)
 		 */
 		*state = 0;
 		if ((scancode & 0x7f) == 0x1D)
-			*state = 0x1D;
+			*state = scancode;
 		return (NONE);
 		/* NOT REACHED */
 	case 0x1D:	/* pause / break */
+	case 0x9D:
+		if ((*state ^ scancode) & 0x80)
+			return (NONE);
 		*state = 0;
-		if (scancode != 0x45)
+		if ((scancode & 0x7f) != 0x45)
 			return (NONE);
 		keycode = KEY_PAUSE;
 		break;
@@ -298,10 +310,42 @@ evdev_push_repeats(struct evdev_dev *evdev, keyboard_t *kbd)
 }
 
 void
-evdev_ev_kbd_event(struct evdev_dev *evdev, void *softc, uint16_t type,
+evdev_support_nfingers(struct evdev_dev *evdev, int nfingers)
+{
+	int i;
+
+	for (i = 0; i < MIN(nitems(evdev_nfinger_codes), nfingers); i++)
+		evdev_support_key(evdev, evdev_nfinger_codes[i]);
+}
+
+void
+evdev_send_nfingers(struct evdev_dev *evdev, int nfingers)
+{
+	int i;
+
+	EVDEV_LOCK_ASSERT(evdev);
+
+	if (nfingers > nitems(evdev_nfinger_codes))
+		nfingers = nitems(evdev_nfinger_codes);
+
+	for (i = 0; i < nitems(evdev_nfinger_codes); i++)
+		evdev_send_event(evdev, EV_KEY, evdev_nfinger_codes[i],
+			nfingers == i + 1);
+}
+
+void
+evdev_push_nfingers(struct evdev_dev *evdev, int nfingers)
+{
+	EVDEV_ENTER(evdev);
+	evdev_send_nfingers(evdev, nfingers);
+	EVDEV_EXIT(evdev);
+}
+
+void
+evdev_ev_kbd_event(struct evdev_dev *evdev, uint16_t type,
     uint16_t code, int32_t value)
 {
-	keyboard_t *kbd = (keyboard_t *)softc;
+	keyboard_t *kbd = (keyboard_t *)evdev_get_softc(evdev);
 	int delay[2], leds, oleds;
 	size_t i;
 
diff --git a/sys/dev/misc/evdev/freebsd-bitstring.h b/sys/dev/misc/evdev/freebsd-bitstring.h
index 10fb991e35..1388e2aef2 100644
--- a/sys/dev/misc/evdev/freebsd-bitstring.h
+++ b/sys/dev/misc/evdev/freebsd-bitstring.h
@@ -63,6 +63,7 @@
 #define	_SYS_FREEBSD_BITSTRING_H_
 
 #include <sys/types.h>
+#include <sys/malloc.h>
 
 /*
  * Begin: Ad hoc definitions from FreeBSD sys/sys/types.h
@@ -131,35 +132,33 @@ typedef	unsigned long bitstr_t;
 #define	_BITSTR_MASK (~0UL)
 #define	_BITSTR_BITS (sizeof(bitstr_t) * 8)
 
-#ifdef roundup2
-#define        _bit_roundup2 roundup2
-#else
-#define        _bit_roundup2(x, y)        (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */
-#endif
+/* round up x to the next multiple of y if y is a power of two */
+#define _bit_roundup2(x, y)						\
+	(((size_t)(x) + (y) - 1) & ~((size_t)(y) - 1))
 
 /* bitstr_t in bit string containing the bit. */
-static inline int
-_bit_idx(int _bit)
+static inline size_t
+_bit_idx(size_t _bit)
 {
 	return (_bit / _BITSTR_BITS);
 }
 
 /* bit number within bitstr_t at _bit_idx(_bit). */
-static inline int
-_bit_offset(int _bit)
+static inline size_t
+_bit_offset(size_t _bit)
 {
 	return (_bit % _BITSTR_BITS);
 }
 
 /* Mask for the bit within its long. */
 static inline bitstr_t
-_bit_mask(int _bit)
+_bit_mask(size_t _bit)
 {
 	return (1UL << _bit_offset(_bit));
 }
 
 static inline bitstr_t
-_bit_make_mask(int _start, int _stop)
+_bit_make_mask(size_t _start, size_t _stop)
 {
 	return ((_BITSTR_MASK << _bit_offset(_start)) &
 	    (_BITSTR_MASK >> (_BITSTR_BITS - _bit_offset(_stop) - 1)));
@@ -167,18 +166,18 @@ _bit_make_mask(int _start, int _stop)
 
 /*----------------------------- Public Interface -----------------------------*/
 /* Number of bytes allocated for a bit string of nbits bits */
-#define	bitstr_size(_nbits) (_bit_roundup2(_nbits, _BITSTR_BITS) / 8)
+#define	bitstr_size(_nbits) (_bit_roundup2((_nbits), _BITSTR_BITS) / 8)
 
 /* Allocate a bit string initialized with no bits set. */
 #ifdef _KERNEL
 static inline bitstr_t *
-bit_alloc(int _nbits, struct malloc_type *type, int flags)
+bit_alloc(size_t _nbits, struct malloc_type *type, int flags)
 {
 	return ((bitstr_t *)kmalloc(bitstr_size(_nbits), type, flags | M_ZERO));
 }
 #else
 static inline bitstr_t *
-bit_alloc(int _nbits)
+bit_alloc(size_t _nbits)
 {
 	return ((bitstr_t *)calloc(bitstr_size(_nbits), 1));
 }
@@ -190,28 +189,56 @@ bit_alloc(int _nbits)
 
 /* Is bit N of bit string set? */
 static inline int
-bit_test(const bitstr_t *_bitstr, int _bit)
+bit_test(const bitstr_t *_bitstr, size_t _bit)
 {
 	return ((_bitstr[_bit_idx(_bit)] & _bit_mask(_bit)) != 0);
 }
 
 /* Set bit N of bit string. */
 static inline void
-bit_set(bitstr_t *_bitstr, int _bit)
+bit_set(bitstr_t *_bitstr, size_t _bit)
 {
 	_bitstr[_bit_idx(_bit)] |= _bit_mask(_bit);
 }
 
 /* clear bit N of bit string name */
 static inline void
-bit_clear(bitstr_t *_bitstr, int _bit)
+bit_clear(bitstr_t *_bitstr, size_t _bit)
 {
 	_bitstr[_bit_idx(_bit)] &= ~_bit_mask(_bit);
 }
 
+/* Are bits in [start ... stop] in bit string all 0 or all 1? */
+static inline int
+bit_ntest(const bitstr_t *_bitstr, size_t _start, size_t _stop, int _match)
+{
+	const bitstr_t *_stopbitstr;
+	bitstr_t _mask;
+
+	_mask = (_match == 0) ? 0 : _BITSTR_MASK;
+	_stopbitstr = _bitstr + _bit_idx(_stop);
+	_bitstr += _bit_idx(_start);
+
+	if (_bitstr == _stopbitstr)
+		return (0 == ((*_bitstr ^ _mask) &
+		    _bit_make_mask(_start, _stop)));
+	if (_bit_offset(_start) != 0 &&
+	    0 != ((*_bitstr++ ^ _mask) &
+	    _bit_make_mask(_start, _BITSTR_BITS - 1)))
+		return (0);
+	if (_bit_offset(_stop) == _BITSTR_BITS - 1)
+		++_stopbitstr;
+	while (_bitstr < _stopbitstr) {
+		if (*_bitstr++ != _mask)
+			return (0);
+	}
+	return (_bit_offset(_stop) == _BITSTR_BITS - 1 ||
+	    0 == ((*_stopbitstr ^ _mask) & _bit_make_mask(0, _stop)));
+}
+
 /* Set bits start ... stop inclusive in bit string. */
 static inline void
-bit_nset(bitstr_t *_bitstr, int _start, int _stop)
+bit_nset(bitstr_t *_bitstr, size_t _start, size_t _stop)
 {
 	bitstr_t *_stopbitstr;
 
@@ -221,16 +248,20 @@ bit_nset(bitstr_t *_bitstr, int _start, int _stop)
 	if (_bitstr == _stopbitstr) {
 		*_bitstr |= _bit_make_mask(_start, _stop);
 	} else {
-		*_bitstr |= _bit_make_mask(_start, _BITSTR_BITS - 1);
-		while (++_bitstr < _stopbitstr)
-	    		*_bitstr = _BITSTR_MASK;
-		*_stopbitstr |= _bit_make_mask(0, _stop);
+		if (_bit_offset(_start) != 0)
+			*_bitstr++ |= _bit_make_mask(_start, _BITSTR_BITS - 1);
+		if (_bit_offset(_stop) == _BITSTR_BITS - 1)
+			++_stopbitstr;
+		while (_bitstr < _stopbitstr)
+			*_bitstr++ = _BITSTR_MASK;
+		if (_bit_offset(_stop) != _BITSTR_BITS - 1)
+			*_stopbitstr |= _bit_make_mask(0, _stop);
 	}
 }
 
 /* Clear bits start ... stop inclusive in bit string. */
 static inline void
-bit_nclear(bitstr_t *_bitstr, int _start, int _stop)
+bit_nclear(bitstr_t *_bitstr, size_t _start, size_t _stop)
 {
 	bitstr_t *_stopbitstr;
 
@@ -240,94 +271,137 @@ bit_nclear(bitstr_t *_bitstr, int _start, int _stop)
 	if (_bitstr == _stopbitstr) {
 		*_bitstr &= ~_bit_make_mask(_start, _stop);
 	} else {
-		*_bitstr &= ~_bit_make_mask(_start, _BITSTR_BITS - 1);
-		while (++_bitstr < _stopbitstr)
-			*_bitstr = 0;
-		*_stopbitstr &= ~_bit_make_mask(0, _stop);
+		if (_bit_offset(_start) != 0)
+			*_bitstr++ &= ~_bit_make_mask(_start, _BITSTR_BITS - 1);
+		if (_bit_offset(_stop) == _BITSTR_BITS - 1)
+			++_stopbitstr;
+		while (_bitstr < _stopbitstr)
+			*_bitstr++ = 0;
+		if (_bit_offset(_stop) != _BITSTR_BITS - 1)
+			*_stopbitstr &= ~_bit_make_mask(0, _stop);
 	}
 }
 
-/* Find the first bit set in bit string at or after bit start. */
-static inline void
-bit_ffs_at(bitstr_t *_bitstr, int _start, int _nbits, int *_result)
+/* Find the first '_match'-bit in bit string at or after bit start. */
+static inline ssize_t
+bit_ff_at_(bitstr_t *_bitstr, size_t _start, size_t _nbits, int _match)
 {
 	bitstr_t *_curbitstr;
 	bitstr_t *_stopbitstr;
+	bitstr_t _mask;
 	bitstr_t _test;
-	int _value, _offset;
+	ssize_t _value;
 
-	if (_nbits > 0) {
-		_curbitstr = _bitstr + _bit_idx(_start);
-		_stopbitstr = _bitstr + _bit_idx(_nbits - 1);
-
-		_test = *_curbitstr;
-		if (_bit_offset(_start) != 0)
-			_test &= _bit_make_mask(_start, _BITSTR_BITS - 1);
-		while (_test == 0 && _curbitstr < _stopbitstr)
-			_test = *(++_curbitstr);
+	if (_start >= _nbits || _nbits <= 0)
+		return (-1);
 
-		_offset = ffsl(_test);
-		_value = ((_curbitstr - _bitstr) * _BITSTR_BITS) + _offset - 1;
-		if (_offset == 0 || _value >= _nbits)
-			_value = -1;
-	} else {
+	_curbitstr = _bitstr + _bit_idx(_start);
+	_stopbitstr = _bitstr + _bit_idx(_nbits - 1);
+	_mask = _match ? 0 : _BITSTR_MASK;
+
+	_test = _mask ^ *_curbitstr;
+	if (_bit_offset(_start) != 0)
+		_test &= _bit_make_mask(_start, _BITSTR_BITS - 1);
+	while (_test == 0 && _curbitstr < _stopbitstr)
+		_test = _mask ^ *(++_curbitstr);
+
+	_value = ((_curbitstr - _bitstr) * _BITSTR_BITS) + ffsl(_test) - 1;
+	if (_test == 0 ||
+	    (_bit_offset(_nbits) != 0 && (size_t)_value >= _nbits))
 		_value = -1;
-	}
-	*_result = _value;
+	return (_value);
 }
+#define bit_ff_at(_bitstr, _start, _nbits, _match, _resultp)		\
+	*(_resultp) = bit_ff_at_((_bitstr), (_start), (_nbits), (_match))
+
+/* Find the first bit set in bit string at or after bit start. */
+#define bit_ffs_at(_bitstr, _start, _nbits, _resultp) \
+	*(_resultp) = bit_ff_at_((_bitstr), (_start), (_nbits), 1)
 
 /* Find the first bit clear in bit string at or after bit start. */
-static inline void
-bit_ffc_at(bitstr_t *_bitstr, int _start, int _nbits, int *_result)
-{
-	bitstr_t *_curbitstr;
-	bitstr_t *_stopbitstr;
-	bitstr_t _test;
-	int _value, _offset;
+#define bit_ffc_at(_bitstr, _start, _nbits, _resultp) \
+	*(_resultp) = bit_ff_at_((_bitstr), (_start), (_nbits), 0)
 
-	if (_nbits > 0) {
-		_curbitstr = _bitstr + _bit_idx(_start);
-		_stopbitstr = _bitstr + _bit_idx(_nbits - 1);
+/* Find the first bit set in bit string. */
+#define bit_ffs(_bitstr, _nbits, _resultp) \
+	*(_resultp) = bit_ff_at_((_bitstr), 0, (_nbits), 1)
 
-		_test = *_curbitstr;
-		if (_bit_offset(_start) != 0)
-			_test |= _bit_make_mask(0, _start - 1);
-		while (_test == _BITSTR_MASK && _curbitstr < _stopbitstr)
-			_test = *(++_curbitstr);
+/* Find the first bit clear in bit string. */
+#define bit_ffc(_bitstr, _nbits, _resultp) \
+	*(_resultp) = bit_ff_at_((_bitstr), 0, (_nbits), 0)
+
+/* Find contiguous sequence of at least size '_match'-bits at or after start */
+static inline ssize_t
+bit_ff_area_at_(bitstr_t *_bitstr, size_t _start, size_t _nbits, size_t _size,
+    int _match)
+{
+	bitstr_t *_curbitstr, _mask, _test;
+	size_t _last, _shft, _maxshft;
+	ssize_t _value;
+
+	if (_start + _size > _nbits || _nbits <= 0)
+		return (-1);
 
-		_offset = ffsl(~_test);
-		_value = ((_curbitstr - _bitstr) * _BITSTR_BITS) + _offset - 1;
-		if (_offset == 0 || _value >= _nbits)
+	_mask = _match ? _BITSTR_MASK : 0;
+	_maxshft = _bit_idx(_size - 1) == 0 ? _size : (int)_BITSTR_BITS;
+	_value = _start;
+	_curbitstr = _bitstr + _bit_idx(_start);
+	_test = ~(_BITSTR_MASK << _bit_offset(_start));
+	for (_last = _size - 1, _test |= _mask ^ *_curbitstr;
+	    !(_bit_idx(_last) == 0 &&
+	    (_test & _bit_make_mask(0, _last)) == 0);
+	    _last -= _BITSTR_BITS, _test = _mask ^ *++_curbitstr) {
+		if (_test == 0)
+			continue;
+		/* Shrink-left every 0-area in _test by maxshft-1 bits. */
+		for (_shft = _maxshft; _shft > 1 && (_test & (_test + 1)) != 0;
+		     _shft = (_shft + 1) / 2)
+			_test |= _test >> _shft / 2;
+		/* Find the start of the first 0-area in _test. */
+		_last = ffsl(~(_test >> 1));
+		_value = (_curbitstr - _bitstr) * _BITSTR_BITS + _last;
+		/* If there's insufficient space left, give up. */
+		if (_value + _size > _nbits) {
 			_value = -1;
-	} else {
-		_value = -1;
+			break;
+		}
+		_last += _size - 1;
+		/* If a solution is contained in _test, success! */
+		if (_bit_idx(_last) == 0)
+			break;
+		/* A solution here needs bits from the next word. */
 	}
-	*_result = _value;
+	return (_value);
 }
+#define bit_ff_area_at(_bitstr, _start, _nbits, _size, _match, _resultp) \
+	*(_resultp) = bit_ff_area_at_(_bitstr, _start, _nbits, _size, _match);
 
-/* Find the first bit set in bit string. */
-static inline void
-bit_ffs(bitstr_t *_bitstr, int _nbits, int *_result)
-{
-	bit_ffs_at(_bitstr, /*start*/0, _nbits, _result);
-}
+/* Find contiguous sequence of at least size set bits at or after start */
+#define bit_ffs_area_at(_bitstr, _start, _nbits, _size, _resultp)	\
+	*(_resultp) = bit_ff_area_at_((_bitstr), (_start), (_nbits), (_size), 1)
 
-/* Find the first bit clear in bit string. */
-static inline void
-bit_ffc(bitstr_t *_bitstr, int _nbits, int *_result)
-{
-	bit_ffc_at(_bitstr, /*start*/0, _nbits, _result);
-}
+/* Find contiguous sequence of at least size cleared bits at or after start */
+#define bit_ffc_area_at(_bitstr, _start, _nbits, _size, _resultp)	\
+	*(_resultp) = bit_ff_area_at_((_bitstr), (_start), (_nbits), (_size), 0)
+
+/* Find contiguous sequence of at least size set bits in bit string */
+#define bit_ffs_area(_bitstr, _nbits, _size, _resultp)			\
+	*(_resultp) = bit_ff_area_at_((_bitstr), 0, (_nbits), (_size), 1)
+
+/* Find contiguous sequence of at least size cleared bits in bit string */
+#define bit_ffc_area(_bitstr, _nbits, _size, _resultp)			\
+	*(_resultp) = bit_ff_area_at_((_bitstr), 0, (_nbits), (_size), 0)
 
 /* Count the number of bits set in a bitstr of size _nbits at or after _start */
-static inline void
-bit_count(bitstr_t *_bitstr, int _start, int _nbits, int *_result)
+static inline ssize_t
+bit_count_(bitstr_t *_bitstr, size_t _start, size_t _nbits)
 {
 	bitstr_t *_curbitstr, mask;
-	int _value = 0, curbitstr_len;
+	size_t curbitstr_len;
+	ssize_t _value = 0;
 
 	if (_start >= _nbits)
-		goto out;
+		return (0);
 
 	_curbitstr = _bitstr + _bit_idx(_start);
 	_nbits -= _BITSTR_BITS * _bit_idx(_start);
@@ -339,6 +413,8 @@ bit_count(bitstr_t *_bitstr, int _start, int _nbits, int *_result)
 		mask = _bit_make_mask(_start, _bit_offset(curbitstr_len - 1));
 		_value += __bitcountl(*_curbitstr & mask);
 		_curbitstr++;
+		if (_nbits < _BITSTR_BITS)
+			return (_value);
 		_nbits -= _BITSTR_BITS;
 	}
 	while (_nbits >= (int)_BITSTR_BITS) {
@@ -351,8 +427,25 @@ bit_count(bitstr_t *_bitstr, int _start, int _nbits, int *_result)
 		_value += __bitcountl(*_curbitstr & mask);
 	}
 
-out:
-	*_result = _value;
+	return (_value);
 }
+#define bit_count(_bitstr, _start, _nbits, _resultp)			\
+	*(_resultp) = bit_count_((_bitstr), (_start), (_nbits))
+
+/* Traverse all set bits, assigning each location in turn to iter */
+#define	bit_foreach_at(_bitstr, _start, _nbits, _iter)			\
+	for ((_iter) = bit_ff_at_((_bitstr), (_start), (_nbits), 1);	\
+	     (_iter) != -1;						\
+	     (_iter) = bit_ff_at_((_bitstr), (_iter) + 1, (_nbits), 1))
+#define	bit_foreach(_bitstr, _nbits, _iter)				\
+	bit_foreach_at(_bitstr, /*start*/0, _nbits, _iter)
+
+/* Traverse all unset bits, assigning each location in turn to iter */
+#define	bit_foreach_unset_at(_bitstr, _start, _nbits, _iter)		\
+	for ((_iter) = bit_ff_at_((_bitstr), (_start), (_nbits), 0);	\
+	     (_iter) != -1;						\
+	     (_iter) = bit_ff_at_((_bitstr), (_iter) + 1, (_nbits), 0))
+#define	bit_foreach_unset(_bitstr, _nbits, _iter)			\
+	bit_foreach_unset_at(_bitstr, /*start*/0, _nbits, _iter)
 
 #endif	/* _SYS_FREEBSD_BITSTRING_H_ */
diff --git a/sys/dev/misc/evdev/input-event-codes.h b/sys/dev/misc/evdev/input-event-codes.h
index 0792a02c41..de148d5d0c 100644
--- a/sys/dev/misc/evdev/input-event-codes.h
+++ b/sys/dev/misc/evdev/input-event-codes.h
@@ -77,9 +77,9 @@
 
 /*
  * Keys and buttons
- */
-
-/*
+ *
+ * Most of the keys/buttons are modeled after USB HUT 1.12
+ * (see http://www.usb.org/developers/hidpage).
  * Abbreviations in the comments:
  * AC - Application Control
  * AL - Application Launch Button
@@ -292,7 +292,8 @@
 #define	KEY_PAUSECD		201
 #define	KEY_PROG3		202
 #define	KEY_PROG4		203
-#define	KEY_DASHBOARD		204	/* AL Dashboard */
+#define	KEY_ALL_APPLICATIONS	204	/* AC Desktop Show All Applications */
+#define	KEY_DASHBOARD		KEY_ALL_APPLICATIONS
 #define	KEY_SUSPEND		205
 #define	KEY_CLOSE		206	/* AC Close */
 #define	KEY_PLAY		207
@@ -453,10 +454,12 @@
 #define	KEY_TITLE		0x171
 #define	KEY_SUBTITLE		0x172
 #define	KEY_ANGLE		0x173
-#define	KEY_ZOOM		0x174
+#define	KEY_FULL_SCREEN		0x174	/* AC View Toggle */
+#define	KEY_ZOOM		KEY_FULL_SCREEN
 #define	KEY_MODE		0x175
 #define	KEY_KEYBOARD		0x176
-#define	KEY_SCREEN		0x177
+#define	KEY_ASPECT_RATIO	0x177	/* HUTRR37: Aspect */
+#define	KEY_SCREEN		KEY_ASPECT_RATIO
 #define	KEY_PC			0x178	/* Media Select Computer */
 #define	KEY_TV			0x179	/* Media Select TV */
 #define	KEY_TV2			0x17a	/* Media Select Cable */
@@ -527,6 +530,9 @@
 #define	KEY_10CHANNELSUP	0x1b8	/* 10 channels up (10+) */
 #define	KEY_10CHANNELSDOWN	0x1b9	/* 10 channels down (10-) */
 #define	KEY_IMAGES		0x1ba	/* AL Image Browser */
+#define	KEY_NOTIFICATION_CENTER	0x1bc	/* Show/hide the notification center */
+#define	KEY_PICKUP_PHONE	0x1bd	/* Answer incoming call */
+#define	KEY_HANGUP_PHONE	0x1be	/* Decline incoming call */
 
 #define	KEY_DEL_EOL		0x1c0
 #define	KEY_DEL_EOS		0x1c1
@@ -554,6 +560,7 @@
 #define	KEY_FN_F		0x1e2
 #define	KEY_FN_S		0x1e3
 #define	KEY_FN_B		0x1e4
+#define	KEY_FN_RIGHT_SHIFT	0x1e5
 
 #define	KEY_BRL_DOT1		0x1f1
 #define	KEY_BRL_DOT2		0x1f2
@@ -618,6 +625,12 @@
 #define	KEY_SCREENSAVER		0x245	/* AL Screen Saver */
 #define	KEY_VOICECOMMAND		0x246	/* Listening Voice Command */
 #define	KEY_ASSISTANT		0x247	/* AL Context-aware desktop assistant */
+#define	KEY_KBD_LAYOUT_NEXT	0x248	/* AC Next Keyboard Layout Select */
+#define	KEY_EMOJI_PICKER	0x249	/* Show/hide emoji picker (HUTRR101) */
+#define	KEY_DICTATE		0x24a	/* Start or Stop Voice Dictation Session (HUTRR99) */
+#define	KEY_CAMERA_ACCESS_ENABLE	0x24b	/* Enables programmatic access to camera devices. (HUTRR72) */
+#define	KEY_CAMERA_ACCESS_DISABLE	0x24c	/* Disables programmatic access to camera devices. (HUTRR72) */
+#define	KEY_CAMERA_ACCESS_TOGGLE	0x24d	/* Toggles the current state of the camera access control. (HUTRR72) */
 
 #define	KEY_BRIGHTNESS_MIN		0x250	/* Set Brightness to Minimum */
 #define	KEY_BRIGHTNESS_MAX		0x251	/* Set Brightness to Maximum */
@@ -660,6 +673,107 @@
  */
 #define	KEY_DATA			0x277
 #define	KEY_ONSCREEN_KEYBOARD		0x278
+/* Electronic privacy screen control */
+#define	KEY_PRIVACY_SCREEN_TOGGLE	0x279
+
+/* Select an area of screen to be copied */
+#define	KEY_SELECTIVE_SCREENSHOT	0x27a
+
+/* Move the focus to the next or previous user controllable element within a UI container */
+#define	KEY_NEXT_ELEMENT		0x27b
+#define	KEY_PREVIOUS_ELEMENT		0x27c
+
+/* Toggle Autopilot engagement */
+#define	KEY_AUTOPILOT_ENGAGE_TOGGLE	0x27d
+
+/* Shortcut Keys */
+#define	KEY_MARK_WAYPOINT		0x27e
+#define	KEY_SOS				0x27f
+#define	KEY_NAV_CHART			0x280
+#define	KEY_FISHING_CHART		0x281
+#define	KEY_SINGLE_RANGE_RADAR		0x282
+#define	KEY_DUAL_RANGE_RADAR		0x283
+#define	KEY_RADAR_OVERLAY		0x284
+#define	KEY_TRADITIONAL_SONAR		0x285
+#define	KEY_CLEARVU_SONAR		0x286
+#define	KEY_SIDEVU_SONAR		0x287
+#define	KEY_NAV_INFO			0x288
+#define	KEY_BRIGHTNESS_MENU		0x289
+
+/*
+ * Some keyboards have keys which do not have a defined meaning, these keys
+ * are intended to be programmed / bound to macros by the user. For most
+ * keyboards with these macro-keys the key-sequence to inject, or action to
+ * take, is all handled by software on the host side. So from the kernel's
+ * point of view these are just normal keys.
+ *
+ * The KEY_MACRO# codes below are intended for such keys, which may be labeled
+ * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys
+ * where the marking on the key does indicate a defined meaning / purpose.
+ *
+ * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing
+ * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO
+ * define MUST be added.
+ */
+#define	KEY_MACRO1			0x290
+#define	KEY_MACRO2			0x291
+#define	KEY_MACRO3			0x292
+#define	KEY_MACRO4			0x293
+#define	KEY_MACRO5			0x294
+#define	KEY_MACRO6			0x295
+#define	KEY_MACRO7			0x296
+#define	KEY_MACRO8			0x297
+#define	KEY_MACRO9			0x298
+#define	KEY_MACRO10			0x299
+#define	KEY_MACRO11			0x29a
+#define	KEY_MACRO12			0x29b
+#define	KEY_MACRO13			0x29c
+#define	KEY_MACRO14			0x29d
+#define	KEY_MACRO15			0x29e
+#define	KEY_MACRO16			0x29f
+#define	KEY_MACRO17			0x2a0
+#define	KEY_MACRO18			0x2a1
+#define	KEY_MACRO19			0x2a2
+#define	KEY_MACRO20			0x2a3
+#define	KEY_MACRO21			0x2a4
+#define	KEY_MACRO22			0x2a5
+#define	KEY_MACRO23			0x2a6
+#define	KEY_MACRO24			0x2a7
+#define	KEY_MACRO25			0x2a8
+#define	KEY_MACRO26			0x2a9
+#define	KEY_MACRO27			0x2aa
+#define	KEY_MACRO28			0x2ab
+#define	KEY_MACRO29			0x2ac
+#define	KEY_MACRO30			0x2ad
+
+/*
+ * Some keyboards with the macro-keys described above have some extra keys
+ * for controlling the host-side software responsible for the macro handling:
+ * -A macro recording start/stop key. Note that not all keyboards which emit
+ *  KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if
+ *  KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START
+ *  should be interpreted as a recording start/stop toggle;
+ * -Keys for switching between different macro (pre)sets, either a key for
+ *  cycling through the configured presets or keys to directly select a preset.
+ */
+#define	KEY_MACRO_RECORD_START		0x2b0
+#define	KEY_MACRO_RECORD_STOP		0x2b1
+#define	KEY_MACRO_PRESET_CYCLE		0x2b2
+#define	KEY_MACRO_PRESET1		0x2b3
+#define	KEY_MACRO_PRESET2		0x2b4
+#define	KEY_MACRO_PRESET3		0x2b5
+
+/*
+ * Some keyboards have a buildin LCD panel where the contents are controlled
+ * by the host. Often these have a number of keys directly below the LCD
+ * intended for controlling a menu shown on the LCD. These keys often don't
+ * have any labeling so we just name them KEY_KBD_LCD_MENU#
+ */
+#define	KEY_KBD_LCD_MENU1		0x2b8
+#define	KEY_KBD_LCD_MENU2		0x2b9
+#define	KEY_KBD_LCD_MENU3		0x2ba
+#define	KEY_KBD_LCD_MENU4		0x2bb
+#define	KEY_KBD_LCD_MENU5		0x2bc
 
 #define	BTN_TRIGGER_HAPPY		0x2c0
 #define	BTN_TRIGGER_HAPPY1		0x2c0
@@ -722,6 +836,16 @@
 #define	REL_DIAL		0x07
 #define	REL_WHEEL		0x08
 #define	REL_MISC		0x09
+/*
+ * 0x0a is reserved and should not be used in input drivers.
+ * It was used by HID as REL_MISC+1 and userspace needs to detect if
+ * the next REL_* event is correct or is just REL_MISC + n.
+ * We define here REL_RESERVED so userspace can rely on it and detect
+ * the situation described above.
+ */
+#define	REL_RESERVED		0x0a
+#define	REL_WHEEL_HI_RES	0x0b
+#define	REL_HWHEEL_HI_RES	0x0c
 #define	REL_MAX			0x0f
 #define	REL_CNT			(REL_MAX+1)
 
@@ -755,9 +879,19 @@
 #define	ABS_TOOL_WIDTH		0x1c
 
 #define	ABS_VOLUME		0x20
+#define	ABS_PROFILE		0x21
 
 #define	ABS_MISC		0x28
 
+/*
+ * 0x2e is reserved and should not be used in input drivers.
+ * It was used by HID as ABS_MISC+6 and userspace needs to detect if
+ * the next ABS_* event is correct or is just ABS_MISC + n.
+ * We define here ABS_RESERVED so userspace can rely on it and detect
+ * the situation described above.
+ */
+#define	ABS_RESERVED		0x2e
+
 #define	ABS_MT_SLOT		0x2f	/* MT slot being modified */
 #define	ABS_MT_TOUCH_MAJOR	0x30	/* Major axis of touching ellipse */
 #define	ABS_MT_TOUCH_MINOR	0x31	/* Minor axis (omit if circular) */
@@ -799,7 +933,8 @@
 #define	SW_LINEIN_INSERT	0x0d  /* set = inserted */
 #define	SW_MUTE_DEVICE		0x0e  /* set = device disabled */
 #define	SW_PEN_INSERTED		0x0f  /* set = pen inserted */
-#define	SW_MAX			0x0f
+#define	SW_MACHINE_COVER	0x10  /* set = cover closed */
+#define	SW_MAX			0x10
 #define	SW_CNT			(SW_MAX+1)
 
 /*
diff --git a/sys/dev/misc/evdev/input.h b/sys/dev/misc/evdev/input.h
index c3f1a07832..b4c0e286d9 100644
--- a/sys/dev/misc/evdev/input.h
+++ b/sys/dev/misc/evdev/input.h
@@ -146,10 +146,11 @@ struct input_keymap_entry {
 /*
  * MT_TOOL types
  */
-#define	MT_TOOL_FINGER		0
-#define	MT_TOOL_PEN		1
-#define	MT_TOOL_PALM		2
-#define	MT_TOOL_MAX		2
+#define	MT_TOOL_FINGER		0x00
+#define	MT_TOOL_PEN		0x01
+#define	MT_TOOL_PALM		0x02
+#define	MT_TOOL_DIAL		0x0a
+#define	MT_TOOL_MAX		0x0f
 
 /*
  * Values describing the status of a force-feedback effect
diff --git a/sys/dev/misc/kbd/atkbd.c b/sys/dev/misc/kbd/atkbd.c
index 96266962db..30b6288d5e 100644
--- a/sys/dev/misc/kbd/atkbd.c
+++ b/sys/dev/misc/kbd/atkbd.c
@@ -40,6 +40,7 @@
 #include <sys/bus.h>
 #include <sys/proc.h>
 #include <sys/malloc.h>
+#include <sys/sysctl.h>
 #include <sys/thread2.h>
 
 #include <sys/kbio.h>
@@ -49,7 +50,17 @@
 
 #include <bus/isa/isareg.h>
 
-static timeout_t	atkbd_timeout;
+static SYSCTL_NODE(_hw, OID_AUTO, atkbd, CTLFLAG_RD, 0, "AT keyboard");
+
+static int atkbdhz = 0;
+
+TUNABLE_INT("hw.atkbd.hz", &atkbdhz);
+SYSCTL_INT(_hw_atkbd, OID_AUTO, hz, CTLFLAG_RW, &atkbdhz, 0,
+    "Polling frequency (in hz)");
+
+
+static void		atkbd_timeout(void *arg);
+static int		atkbd_reset(KBDC kbdc, int flags, int c);
 
 #if 0
 static int atkbd_setmuxmode(KBDC kbdc, int value, int *mux_version);
@@ -164,7 +175,10 @@ atkbd_timeout(void *arg)
 		if (kbd_check_char(kbd))
 			kbd_intr(kbd, NULL);
 	}
-	callout_reset(&kbd->kb_atkbd_timeout_ch, hz / 10, atkbd_timeout, arg);
+	if (atkbdhz > 0) {
+		callout_reset(&kbd->kb_atkbd_timeout_ch, hz / atkbdhz,
+				atkbd_timeout, arg);
+	}
 	crit_exit();
 }
 
@@ -992,6 +1006,38 @@ atkbd_poll(keyboard_t *kbd, int on)
 	return 0;
 }
 
+static int
+atkbd_reset(KBDC kbdc, int flags, int c)
+{
+	/* reset keyboard hardware */
+	if (!(flags & KB_CONF_NO_RESET) && !reset_kbd(kbdc)) {
+		/*
+		 * KEYBOARD ERROR
+		 * Keyboard reset may fail either because the keyboard
+		 * doen't exist, or because the keyboard doesn't pass
+		 * the self-test, or the keyboard controller on the
+		 * motherboard and the keyboard somehow fail to shake hands.
+		 * It is just possible, particularly in the last case,
+		 * that the keyoard controller may be left in a hung state.
+		 * test_controller() and test_kbd_port() appear to bring
+		 * the keyboard controller back (I don't know why and how,
+		 * though.)
+		 */
+		empty_both_buffers(kbdc, 10);
+		test_controller(kbdc);
+		test_kbd_port(kbdc);
+		/*
+		 * We could disable the keyboard port and interrupt... but,
+		 * the keyboard may still exist (see above).
+		 */
+		set_controller_command_byte(kbdc, KBD_KBD_CONTROL_BITS, c);
+		if (bootverbose)
+			kprintf("atkbd: failed to reset the keyboard.\n");
+		return (EIO);
+	}
+	return (0);
+}
+
 /* local functions */
 
 static int
@@ -1165,32 +1211,9 @@ init_keyboard(KBDC kbdc, int *type, int flags)
 	/* default codeset */
 	codeset = -1;
 
-	/* reset keyboard hardware */
-	if (!(flags & KB_CONF_NO_RESET) && !reset_kbd(kbdc)) {
-		/*
-		 * KEYBOARD ERROR
-		 * Keyboard reset may fail either because the keyboard
-		 * doen't exist, or because the keyboard doesn't pass
-		 * the self-test, or the keyboard controller on the
-		 * motherboard and the keyboard somehow fail to shake hands.
-		 * It is just possible, particularly in the last case,
-		 * that the keyoard controller may be left in a hung state.
-		 * test_controller() and test_kbd_port() appear to bring
-		 * the keyboard controller back (I don't know why and how,
-		 * though.)
-		 */
-		empty_both_buffers(kbdc, 10);
-		test_controller(kbdc);
-		test_kbd_port(kbdc);
-		/*
-		 * We could disable the keyboard port and interrupt... but,
-		 * the keyboard may still exist (see above).
-		 */
-		set_controller_command_byte(kbdc, KBD_KBD_CONTROL_BITS, c);
+	if (atkbd_reset(kbdc, flags, c)) {
 		kbdc_lock(kbdc, FALSE);
-		if (bootverbose)
-			kprintf("atkbd: failed to reset the keyboard.\n");
-		return EIO;
+		return (EIO);
 	}
 
 	/* 
diff --git a/sys/dev/misc/kbd/atkbdc.c b/sys/dev/misc/kbd/atkbdc.c
index 1c8d423d17..42d708f075 100644
--- a/sys/dev/misc/kbd/atkbdc.c
+++ b/sys/dev/misc/kbd/atkbdc.c
@@ -104,6 +104,13 @@ static int wait_for_kbd_ack(atkbdc_softc_t *kbdc);
 static int wait_for_aux_data(atkbdc_softc_t *kbdc);
 static int wait_for_aux_ack(atkbdc_softc_t *kbdc);
 
+static int
+atkbdc_getquirks(void)
+{
+	/* TODO */
+	return (0);
+}
+
 atkbdc_softc_t *
 atkbdc_get_softc(int unit)
 {
@@ -222,9 +229,11 @@ atkbdc_setup(atkbdc_softc_t *sc, bus_space_tag_t tag, bus_space_handle_t h0,
 {
 	if (sc->ioh0 == 0) {	/* XXX */
 	    sc->command_byte = -1;
+	    sc->command_mask = 0;
 	    sc->lock = FALSE;
 	    sc->kbd.head = sc->kbd.tail = 0;
 	    sc->aux.head = sc->aux.tail = 0;
+	    sc->aux_mux_enabled = FALSE;
 #if KBDIO_DEBUG >= 2
 	    sc->kbd.call_count = 0;
 	    sc->kbd.qcount = sc->kbd.max_qcount = 0;
@@ -235,6 +244,9 @@ atkbdc_setup(atkbdc_softc_t *sc, bus_space_tag_t tag, bus_space_handle_t h0,
 	sc->iot = tag;
 	sc->ioh0 = h0;
 	sc->ioh1 = h1;
+
+	sc->quirks = atkbdc_getquirks();
+
 	return 0;
 }
 
@@ -548,7 +560,12 @@ write_kbd_command(KBDC p, int c)
 int
 write_aux_command(KBDC p, int c)
 {
-    if (!write_controller_command(p, KBDC_WRITE_TO_AUX))
+    int f;
+
+    f = aux_mux_is_enabled(p) ?
+        KBDC_WRITE_TO_AUX_MUX + kbdcp(p)->aux_mux_port : KBDC_WRITE_TO_AUX;
+
+    if (!write_controller_command(p, f))
 	return FALSE;
     return write_controller_data(p, c);
 }
@@ -1061,6 +1078,20 @@ test_aux_port(KBDC p)
     return c;
 }
 
+int
+kbdc_get_device_mask(KBDC p)
+{
+    return kbdcp(p)->command_mask;
+}
+
+void
+kbdc_set_device_mask(KBDC p, int mask)
+{
+    kbdcp(p)->command_mask =
+	mask & (((p->quirks & KBDC_QUIRK_KEEP_ACTIVATED)
+	    ? 0 : KBD_KBD_CONTROL_BITS) | KBD_AUX_CONTROL_BITS);
+}
+
 int
 get_controller_command_byte(KBDC p)
 {
@@ -1080,26 +1111,14 @@ set_controller_command_byte(KBDC p, int mask, int command)
 	return FALSE;
 
     command = (kbdcp(p)->command_byte & ~mask) | (command & mask);
-#if 0
-    if (mask & KBD_DISABLE_KBD_PORT) {
-	    if (command & KBD_DISABLE_KBD_PORT) {
-		if (!write_controller_command(p, KBDC_DISABLE_KBD_PORT))
-		    return FALSE;
-	    }
+    if (command & KBD_DISABLE_KBD_PORT) {
+	if (!write_controller_command(p, KBDC_DISABLE_KBD_PORT))
+	    return FALSE;
     }
-#endif
     if (!write_controller_command(p, KBDC_SET_COMMAND_BYTE))
 	return FALSE;
     if (!write_controller_data(p, command))
 	return FALSE;
-#if 0
-    if (mask & KBD_DISABLE_KBD_PORT) {
-	    if ((command & KBD_DISABLE_KBD_PORT) == 0) {
-		if (!write_controller_command(p, KBDC_ENABLE_KBD_PORT))
-		    return FALSE;
-	    }
-    }
-#endif
     kbdcp(p)->command_byte = command;
 
     if (verbose)
@@ -1108,3 +1127,78 @@ set_controller_command_byte(KBDC p, int mask, int command)
 
     return TRUE;
 }
+
+/*
+ * Rudimentary support for active PS/2 AUX port multiplexing.
+ * Only write commands can be routed to a selected AUX port.
+ * Source port of data processed by read commands is totally ignored.
+ */
+static int
+set_aux_mux_state(KBDC p, int enabled)
+{
+	int command, version;
+
+	if (write_controller_command(p, KBDC_FORCE_AUX_OUTPUT) == 0 ||
+	    write_controller_data(p, 0xF0) == 0 ||
+	    read_controller_data(p) != 0xF0)
+		return (-1);
+
+	if (write_controller_command(p, KBDC_FORCE_AUX_OUTPUT) == 0 ||
+	    write_controller_data(p, 0x56) == 0 ||
+	    read_controller_data(p) != 0x56)
+		return (-1);
+
+	command = enabled ? 0xa4 : 0xa5;
+	if (write_controller_command(p, KBDC_FORCE_AUX_OUTPUT) == 0 ||
+	    write_controller_data(p, command) == 0 ||
+	    (version = read_controller_data(p)) == command)
+		return (-1);
+
+	return (version);
+}
+
+int
+set_active_aux_mux_port(KBDC p, int port)
+{
+
+	if (!aux_mux_is_enabled(p))
+		return (FALSE);
+
+	if (port < 0 || port >= KBDC_AUX_MUX_NUM_PORTS)
+		return (FALSE);
+
+	kbdcp(p)->aux_mux_port = port;
+
+	return (TRUE);
+}
+
+/* Checks for active multiplexing support and enables it */
+int
+enable_aux_mux(KBDC p)
+{
+	int version;
+
+	version = set_aux_mux_state(p, TRUE);
+	if (version >= 0) {
+		kbdcp(p)->aux_mux_enabled = TRUE;
+		set_active_aux_mux_port(p, 0);
+	}
+
+	return (version);
+}
+
+int
+disable_aux_mux(KBDC p)
+{
+
+	kbdcp(p)->aux_mux_enabled = FALSE;
+
+	return (set_aux_mux_state(p, FALSE));
+}
+
+int
+aux_mux_is_enabled(KBDC p)
+{
+
+	return (kbdcp(p)->aux_mux_enabled);
+}
diff --git a/sys/dev/misc/kbd/atkbdcreg.h b/sys/dev/misc/kbd/atkbdcreg.h
index edc16ecccf..104498d5af 100644
--- a/sys/dev/misc/kbd/atkbdcreg.h
+++ b/sys/dev/misc/kbd/atkbdcreg.h
@@ -51,6 +51,8 @@
 /* controller commands (sent to KBD_COMMAND_PORT) */
 #define KBDC_SET_COMMAND_BYTE 	0x0060
 #define KBDC_GET_COMMAND_BYTE 	0x0020
+#define KBDC_WRITE_TO_AUX_MUX	0x0090
+#define KBDC_FORCE_AUX_OUTPUT	0x00d3
 #define KBDC_WRITE_TO_AUX    	0x00d4
 #define KBDC_DISABLE_AUX_PORT 	0x00a7
 #define KBDC_ENABLE_AUX_PORT 	0x00a8
@@ -230,10 +232,20 @@ typedef struct atkbdc_softc {
     bus_space_handle_t ioh1;
     int command_byte;		/* current command byte value */
     int command_mask;		/* command byte mask bits for kbd/aux devices */
+#if 0
     int mux_active;		/* multiplexer is active */
+#endif
     int lock;			/* FIXME: XXX not quite a semaphore... */
     kbdkqueue kbd;		/* keyboard data queue */
     kbdkqueue aux;		/* auxiliary data queue */
+    int quirks;			/* controller doesn't like deactivate */
+#define KBDC_QUIRK_KEEP_ACTIVATED	(1 << 0)
+#define KBDC_QUIRK_IGNORE_PROBE_RESULT	(1 << 1)
+#define KBDC_QUIRK_RESET_AFTER_PROBE	(1 << 2)
+#define KBDC_QUIRK_SETLEDS_ON_INIT	(1 << 3)
+#define KBDC_QUIRK_DISABLE_MUX_PROBE	(1 << 4)
+    int aux_mux_enabled;	/* active PS/2 multiplexing is enabled */
+    int aux_mux_port;		/* current aux mux port */
 } atkbdc_softc_t; 
 
 enum kbdc_device_ivar {
@@ -250,6 +262,8 @@ typedef struct atkbdc_softc *KBDC;
 #define KBDC_RID_KBD	 0
 #define KBDC_RID_AUX	 1
 
+#define KBDC_AUX_MUX_NUM_PORTS	4
+
 /* function prototypes */
 
 atkbdc_softc_t *atkbdc_get_softc(int unit);
@@ -290,9 +304,17 @@ int test_controller(KBDC kbdc);
 int test_kbd_port(KBDC kbdc);
 int test_aux_port(KBDC kbdc);
 
+int kbdc_get_device_mask(KBDC kbdc);
+void kbdc_set_device_mask(KBDC kbdc, int mask);
+
 int get_controller_command_byte(KBDC kbdc);
 int set_controller_command_byte(KBDC kbdc, int mask, int command);
 
+int set_active_aux_mux_port(KBDC p, int port);
+int enable_aux_mux(KBDC p);
+int disable_aux_mux(KBDC p);
+int aux_mux_is_enabled(KBDC p);
+
 #endif /* _KERNEL */
 
 #endif /* !_DEV_KBD_ATKBDCREG_H_ */
diff --git a/sys/dev/misc/psm/psm.c b/sys/dev/misc/psm/psm.c
index a5958bec21..e0f7bcbeff 100644
--- a/sys/dev/misc/psm/psm.c
+++ b/sys/dev/misc/psm/psm.c
@@ -62,6 +62,7 @@
  */
 
 #include "opt_psm.h"
+#include "opt_evdev.h"
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -88,6 +89,11 @@
 #include <bus/isa/isavar.h>
 #include <dev/misc/kbd/atkbdcreg.h>
 
+#ifdef EVDEV_SUPPORT
+#include <dev/misc/evdev/evdev.h>
+#include <dev/misc/evdev/input.h>
+#endif
+
 #include <sys/signalvar.h>
 #include <sys/filio.h>
 
@@ -108,6 +114,10 @@
 	if (verbose >= level)		\
 		log args;		\
 } while (0)
+#define	VDLOG(level, ...)	do {		\
+	if (verbose >= level)			\
+		device_log(__VA_ARGS__);	\
+} while (0)
 
 #ifndef PSM_INPUT_TIMEOUT
 #define	PSM_INPUT_TIMEOUT	2000000	/* 2 sec */
@@ -126,6 +136,14 @@
 #define PSM_DRIVER_NAME	      "psm"
 #define	PSMCPNP_DRIVER_NAME	"psmcpnp"
 
+struct psmcpnp_softc {
+	enum {
+		PSMCPNP_GENERIC,
+		PSMCPNP_FORCEPAD,
+		PSMCPNP_TOPBUTTONPAD,
+	} type;		/* Based on PnP ID */
+};
+
 /* input queue */
 #define	PSM_BUFSIZE		960
 #define	PSM_SMALLBUFSIZE	240
@@ -133,8 +151,12 @@
 /* operation levels */
 #define	PSM_LEVEL_BASE		0
 #define	PSM_LEVEL_STANDARD	1
+#define	PSM_LEVEL_NATIVE	2
 #define	PSM_LEVEL_MIN		PSM_LEVEL_BASE
-#define	PSM_LEVEL_MAX		PSM_LEVEL_STANDARD
+#define	PSM_LEVEL_MAX		PSM_LEVEL_NATIVE
+
+/* Active PS/2 multiplexing */
+#define	PSM_NOMUX		(-1)
 
 /* Logitech PS2++ protocol */
 #define	MOUSE_PS2PLUS_CHECKBITS(b)	\
@@ -161,9 +183,25 @@ typedef struct packetbuf {
 #endif
 
 
+/*
+ * Synaptics command definitions.
+ */
+#define	SYNAPTICS_READ_IDENTITY			0x00
+#define	SYNAPTICS_READ_MODES			0x01
+#define	SYNAPTICS_READ_CAPABILITIES		0x02
+#define	SYNAPTICS_READ_MODEL_ID			0x03
+#define	SYNAPTICS_READ_SERIAL_PREFIX		0x06
+#define	SYNAPTICS_READ_SERIAL_SUFFIX		0x07
+#define	SYNAPTICS_READ_RESOLUTIONS		0x08
+#define	SYNAPTICS_READ_EXTENDED			0x09
+#define	SYNAPTICS_READ_CAPABILITIES_CONT	0x0c
+#define	SYNAPTICS_READ_MAX_COORDS		0x0d
+#define	SYNAPTICS_READ_DELUXE_LED		0x0e
+#define	SYNAPTICS_READ_MIN_COORDS		0x0f
+
 typedef struct synapticsinfo {
-	struct sysctl_oid	*sysctl_tree;
 	struct sysctl_ctx_list	 sysctl_ctx;
+	struct sysctl_oid	*sysctl_tree;
 	int			 directional_scrolls;
 	int                      two_finger_scroll;
 	int			 min_pressure;
@@ -202,6 +240,8 @@ typedef struct synapticsinfo {
 	int                      softbutton3_x;
 	int                      max_x;
 	int                      max_y;
+	int			 three_finger_drag;
+	int			 natural_scroll;
 } synapticsinfo_t;
 
 typedef struct synapticspacket {
@@ -261,6 +301,16 @@ enum {
 typedef struct trackpointinfo {
 	struct sysctl_ctx_list sysctl_ctx;
 	struct sysctl_oid *sysctl_tree;
+	enum {
+		TRACKPOINT_VENDOR_IBM	= 0x01,
+		TRACKPOINT_VENDOR_ALPS	= 0x02,
+		TRACKPOINT_VENDOR_ELAN	= 0x03,
+		TRACKPOINT_VENDOR_NXP	= 0x04,
+		TRACKPOINT_VENDOR_JYT	= 0x05,
+		TRACKPOINT_VENDOR_SYNAPTICS = 0x06,
+		TRACKPOINT_VENDOR_UNKNOWN = 0x07,
+	}	vendor;
+	int	firmware;
 	int     sensitivity;
 	int     inertia;
 	int     uplateau;
@@ -301,6 +351,8 @@ typedef struct elantechhw {
 	int                     dpmmy;
 	int                     ntracesx;
 	int                     ntracesy;
+	int			dptracex;
+	int			dptracey;
 	int                     issemimt;
 	int                     isclickpad;
 	int                     hascrc;
@@ -325,8 +377,14 @@ typedef struct elantechhw {
 #define ELANTECH_REG_RDWR       0x00
 #define ELANTECH_CUSTOM_CMD     0xf8
 
+#ifdef EVDEV_SUPPORT
+#define	ELANTECH_MAX_FINGERS	5
+#else
 #define ELANTECH_MAX_FINGERS    PSM_FINGERS
+#endif
 
+#define	ELANTECH_FINGER_MAX_P	255
+#define	ELANTECH_FINGER_MAX_W	15
 #define ELANTECH_FINGER_SET_XYP(pb) (finger_t) {                        \
     .x = (((pb)->ipacket[1] & 0x0f) << 8) | (pb)->ipacket[2],           \
     .y = (((pb)->ipacket[4] & 0x0f) << 8) | (pb)->ipacket[5],           \
@@ -367,11 +425,12 @@ enum {
 typedef struct elantechaction {
 	finger_t                fingers[ELANTECH_MAX_FINGERS];
 	int                     mask;
+	int			mask_v4wait;
 } elantechaction_t;
 
 /* driver control block */
 struct psm_softc {		/* Driver status information */
-	int		unit;
+	device_t	dev;
 	struct kqinfo	rkq;		/* Processes with registered kevents */
 	struct lock	lock;		/* Control lock */
 	u_char		state;		/* Mouse driver state */
@@ -387,7 +446,6 @@ struct psm_softc {		/* Driver status information */
 	gesture_t       gesture;        /* Gesture context */
 	elantechhw_t    elanhw;         /* Elantech hardware information */
 	elantechaction_t elanaction;    /* Elantech action context */
-	int             tphw;           /* TrackPoint hardware information */
 	trackpointinfo_t tpinfo;        /* TrackPoint configuration */
 	mousemode_t	mode;		/* operation mode */
 	mousemode_t	dflt_mode;	/* default operation mode */
@@ -404,6 +462,7 @@ struct psm_softc {		/* Driver status information */
 	int		squelch; /* level to filter movement at low speed */
 	int		syncerrors; /* # of bytes discarded to synchronize */
 	int		pkterrors;  /* # of packets failed during quaranteen. */
+	int		fpcount;	/* forcePad valid packet counter */
 	struct timeval	inputtimeout;
 	struct timeval	lastsoftintr;	/* time of last soft interrupt */
 	struct timeval	lastinputerr;	/* time last sync error happened */
@@ -412,57 +471,31 @@ struct psm_softc {		/* Driver status information */
 	int		watchdog;	/* watchdog timer flag */
 	struct callout	 callout;	/* watchdog timer call out */
 	struct callout	 softcallout; /* buffer timer call out */
-	struct cdev	*dev;
+	struct cdev	*cdev;
 	int		lasterr;
 	int		cmdcount;
 	int             extended_buttons;
+	int		muxport;	/* MUX port with attached Synaptics */
+	u_char		muxsave[3];	/* 3->6 byte proto conversion buffer */
+	int		muxtpbuttons;	/* Touchpad button state */
+	int		muxmsbuttons;	/* Mouse (trackpoint) button state */
+	struct timeval	muxmidtimeout;	/* middle button supression timeout */
+	int		muxsinglesyna;	/* Probe result of single Synaptics */
+#ifdef EVDEV_SUPPORT
+	struct evdev_dev *evdev_a;	/* Absolute reporting device */
+	struct evdev_dev *evdev_r;	/* Relative reporting device */
+#endif
 };
 static devclass_t psm_devclass;
 
-#define SYN_OFFSET(field) offsetof(struct psm_softc, syninfo.field)
-enum {
-	SYNAPTICS_SYSCTL_MIN_PRESSURE =         SYN_OFFSET(min_pressure),
-	SYNAPTICS_SYSCTL_MAX_PRESSURE =         SYN_OFFSET(max_pressure),
-	SYNAPTICS_SYSCTL_MAX_WIDTH =            SYN_OFFSET(max_width),
-	SYNAPTICS_SYSCTL_MARGIN_TOP =           SYN_OFFSET(margin_top),
-	SYNAPTICS_SYSCTL_MARGIN_RIGHT =         SYN_OFFSET(margin_right),
-	SYNAPTICS_SYSCTL_MARGIN_BOTTOM =        SYN_OFFSET(margin_bottom),
-	SYNAPTICS_SYSCTL_MARGIN_LEFT =          SYN_OFFSET(margin_left),
-	SYNAPTICS_SYSCTL_NA_TOP =               SYN_OFFSET(na_top),
-	SYNAPTICS_SYSCTL_NA_RIGHT =             SYN_OFFSET(na_right),
-	SYNAPTICS_SYSCTL_NA_BOTTOM =            SYN_OFFSET(na_bottom),
-	SYNAPTICS_SYSCTL_NA_LEFT =              SYN_OFFSET(na_left),
-	SYNAPTICS_SYSCTL_WINDOW_MIN =           SYN_OFFSET(window_min),
-	SYNAPTICS_SYSCTL_WINDOW_MAX =           SYN_OFFSET(window_max),
-	SYNAPTICS_SYSCTL_MULTIPLICATOR =        SYN_OFFSET(multiplicator),
-	SYNAPTICS_SYSCTL_WEIGHT_CURRENT =       SYN_OFFSET(weight_current),
-	SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS =      SYN_OFFSET(weight_previous),
-	SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA =   SYN_OFFSET(weight_previous_na),
-	SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED =   SYN_OFFSET(weight_len_squared),
-	SYNAPTICS_SYSCTL_DIV_MIN =              SYN_OFFSET(div_min),
-	SYNAPTICS_SYSCTL_DIV_MAX =              SYN_OFFSET(div_max),
-	SYNAPTICS_SYSCTL_DIV_MAX_NA =           SYN_OFFSET(div_max_na),
-	SYNAPTICS_SYSCTL_DIV_LEN =              SYN_OFFSET(div_len),
-	SYNAPTICS_SYSCTL_TAP_MAX_DELTA =        SYN_OFFSET(tap_max_delta),
-	SYNAPTICS_SYSCTL_TAP_MIN_QUEUE =        SYN_OFFSET(tap_min_queue),
-	SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT =      SYN_OFFSET(taphold_timeout),
-	SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA =     SYN_OFFSET(vscroll_hor_area),
-	SYNAPTICS_SYSCTL_VSCROLL_VER_AREA =     SYN_OFFSET(vscroll_ver_area),
-	SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA =    SYN_OFFSET(vscroll_min_delta),
-	SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN =      SYN_OFFSET(vscroll_div_min),
-	SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX =      SYN_OFFSET(vscroll_div_max),
-	SYNAPTICS_SYSCTL_TOUCHPAD_OFF =         SYN_OFFSET(touchpad_off),
-	SYNAPTICS_SYSCTL_SOFTBUTTONS_Y =        SYN_OFFSET(softbuttons_y),
-	SYNAPTICS_SYSCTL_SOFTBUTTON2_X =        SYN_OFFSET(softbutton2_x),
-	SYNAPTICS_SYSCTL_SOFTBUTTON3_X =        SYN_OFFSET(softbutton3_x),
-};
-
 /* driver state flags (state) */
 #define	PSM_VALID		0x80
 #define	PSM_OPEN		1	/* Device is open */
 #define	PSM_ASLP		2	/* Waiting for mouse data */
 #define	PSM_SOFTARMED		4	/* Software interrupt armed */
 #define	PSM_NEED_SYNCBITS	8	/* Set syncbits using next data pkt */
+#define	PSM_EV_OPEN_R		0x10	/* Relative evdev device is open */
+#define	PSM_EV_OPEN_A		0x20	/* Absolute evdev device is open */
 
 /* driver configuration flags (config) */
 #define	PSM_CONFIG_RESOLUTION	0x000f	/* resolution */
@@ -490,23 +523,23 @@ enum {
 #define	PSM_FLAGS_FINGERDOWN	0x0001	/* VersaPad finger down */
 
 #define kbdcp(p)                        ((atkbdc_softc_t *)(p))
+#define ALWAYS_RESTORE_CONTROLLER(kbdc)	!(kbdcp(kbdc)->quirks \
+    & KBDC_QUIRK_KEEP_ACTIVATED)
 
 /* Tunables */
 static int tap_enabled = -1;
-TUNABLE_INT("hw.psm.tap_enabled", &tap_enabled);
-
-static int synaptics_support = 0;
-TUNABLE_INT("hw.psm.synaptics_support", &synaptics_support);
-
 static int verbose = PSM_DEBUG;
-TUNABLE_INT("debug.psm.loglevel", &verbose);
-
-static int trackpoint_support = 0;
-TUNABLE_INT("hw.psm.trackpoint_support",&trackpoint_support);
-
-static int elantech_support = 0;
-TUNABLE_INT("hw.psm.elantech_support",&elantech_support);
+static int synaptics_support = 1;
+static int trackpoint_support = 1;
+static int elantech_support = 1;
+static int mux_disabled = 0;
 
+TUNABLE_INT("hw.psm.tap_enabled", &tap_enabled);
+TUNABLE_INT("debug.psm.loglevel", &verbose);
+TUNABLE_INT("hw.psm.synaptics_support", &synaptics_support);
+TUNABLE_INT("hw.psm.trackpoint_support", &trackpoint_support);
+TUNABLE_INT("hw.psm.elantech_support", &elantech_support);
+TUNABLE_INT("hw.psm.mux_disabled", &mux_disabled);
 
 /* for backward compatibility */
 #define	OLD_MOUSE_GETHWINFO	_IOR('M', 1, old_mousehw_t)
@@ -526,6 +559,49 @@ typedef struct old_mousemode {
 	int	resolution;
 	int	accelfactor;
 } old_mousemode_t;
+
+#define SYN_OFFSET(field) offsetof(struct psm_softc, syninfo.field)
+enum {
+	SYNAPTICS_SYSCTL_MIN_PRESSURE =         SYN_OFFSET(min_pressure),
+	SYNAPTICS_SYSCTL_MAX_PRESSURE =         SYN_OFFSET(max_pressure),
+	SYNAPTICS_SYSCTL_MAX_WIDTH =            SYN_OFFSET(max_width),
+	SYNAPTICS_SYSCTL_MARGIN_TOP =           SYN_OFFSET(margin_top),
+	SYNAPTICS_SYSCTL_MARGIN_RIGHT =         SYN_OFFSET(margin_right),
+	SYNAPTICS_SYSCTL_MARGIN_BOTTOM =        SYN_OFFSET(margin_bottom),
+	SYNAPTICS_SYSCTL_MARGIN_LEFT =          SYN_OFFSET(margin_left),
+	SYNAPTICS_SYSCTL_NA_TOP =               SYN_OFFSET(na_top),
+	SYNAPTICS_SYSCTL_NA_RIGHT =             SYN_OFFSET(na_right),
+	SYNAPTICS_SYSCTL_NA_BOTTOM =            SYN_OFFSET(na_bottom),
+	SYNAPTICS_SYSCTL_NA_LEFT =              SYN_OFFSET(na_left),
+	SYNAPTICS_SYSCTL_WINDOW_MIN =           SYN_OFFSET(window_min),
+	SYNAPTICS_SYSCTL_WINDOW_MAX =           SYN_OFFSET(window_max),
+	SYNAPTICS_SYSCTL_MULTIPLICATOR =        SYN_OFFSET(multiplicator),
+	SYNAPTICS_SYSCTL_WEIGHT_CURRENT =       SYN_OFFSET(weight_current),
+	SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS =      SYN_OFFSET(weight_previous),
+	SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA =   SYN_OFFSET(weight_previous_na),
+	SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED =   SYN_OFFSET(weight_len_squared),
+	SYNAPTICS_SYSCTL_DIV_MIN =              SYN_OFFSET(div_min),
+	SYNAPTICS_SYSCTL_DIV_MAX =              SYN_OFFSET(div_max),
+	SYNAPTICS_SYSCTL_DIV_MAX_NA =           SYN_OFFSET(div_max_na),
+	SYNAPTICS_SYSCTL_DIV_LEN =              SYN_OFFSET(div_len),
+	SYNAPTICS_SYSCTL_TAP_MAX_DELTA =        SYN_OFFSET(tap_max_delta),
+	SYNAPTICS_SYSCTL_TAP_MIN_QUEUE =        SYN_OFFSET(tap_min_queue),
+	SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT =      SYN_OFFSET(taphold_timeout),
+	SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA =     SYN_OFFSET(vscroll_hor_area),
+	SYNAPTICS_SYSCTL_VSCROLL_VER_AREA =     SYN_OFFSET(vscroll_ver_area),
+	SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA =    SYN_OFFSET(vscroll_min_delta),
+	SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN =      SYN_OFFSET(vscroll_div_min),
+	SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX =      SYN_OFFSET(vscroll_div_max),
+	SYNAPTICS_SYSCTL_TOUCHPAD_OFF =         SYN_OFFSET(touchpad_off),
+	SYNAPTICS_SYSCTL_SOFTBUTTONS_Y =        SYN_OFFSET(softbuttons_y),
+	SYNAPTICS_SYSCTL_SOFTBUTTON2_X =        SYN_OFFSET(softbutton2_x),
+	SYNAPTICS_SYSCTL_SOFTBUTTON3_X =        SYN_OFFSET(softbutton3_x),
+	SYNAPTICS_SYSCTL_THREE_FINGER_DRAG = 	SYN_OFFSET(three_finger_drag),
+	SYNAPTICS_SYSCTL_NATURAL_SCROLL =	SYN_OFFSET(natural_scroll),
+#define	SYNAPTICS_SYSCTL_LAST	SYNAPTICS_SYSCTL_NATURAL_SCROLL
+};
+
+
 /* packet formatting function */
 typedef int	packetfunc_t(struct psm_softc *, u_char *, int *, int,
     mousestatus_t *);
@@ -537,12 +613,22 @@ static int	psmattach(device_t);
 static int	psmdetach(device_t);
 static int	psmresume(device_t);
 
-static d_open_t		psmopen;
-static d_close_t	psmclose;
+static d_open_t		psm_cdev_open;
+static d_close_t	psm_cdev_close;
 static d_read_t		psmread;
 static d_ioctl_t	psmioctl;
 static d_kqfilter_t 	psmkqfilter;
 
+static int	psmopen(struct psm_softc *);
+static int	psmclose(struct psm_softc *);
+
+#ifdef EVDEV_SUPPORT
+static evdev_open_t	psm_ev_open_r;
+static evdev_close_t	psm_ev_close_r;
+static evdev_open_t	psm_ev_open_a;
+static evdev_close_t	psm_ev_close_a;
+#endif
+
 static int	enable_aux_dev(KBDC);
 static int	disable_aux_dev(KBDC);
 static int	get_mouse_status(KBDC, int *, int, int);
@@ -552,6 +638,7 @@ static int	set_mouse_scaling(KBDC, int);
 static int	set_mouse_resolution(KBDC, int);
 static int	set_mouse_mode(KBDC);
 static int	get_mouse_buttons(KBDC);
+static int	is_a_mouse(int);
 static void	recover_from_error(KBDC);
 static int	restore_controller(KBDC, int);
 static int	doinitialize(struct psm_softc *, mousemode_t *);
@@ -572,6 +659,7 @@ static void	proc_mmanplus(struct psm_softc *, packetbuf_t *,
 		    mousestatus_t *, int *, int *, int *);
 static int	proc_synaptics(struct psm_softc *, packetbuf_t *,
 		    mousestatus_t *, int *, int *, int *);
+static int	proc_synaptics_mux(struct psm_softc *, packetbuf_t *);
 static void	proc_versapad(struct psm_softc *, packetbuf_t *,
 		    mousestatus_t *, int *, int *, int *);
 static int	proc_elantech(struct psm_softc *, packetbuf_t *,
@@ -591,19 +679,21 @@ typedef int	probefunc_t(struct psm_softc *, enum probearg);
 static int	mouse_id_proc1(KBDC, int, int, int *);
 static int	mouse_ext_command(KBDC, int);
 
-static int	enable_groller(struct psm_softc *sc, enum probearg arg);
-static int	enable_gmouse(struct psm_softc *sc, enum probearg arg);
-static int	enable_aglide(struct psm_softc *sc, enum probearg arg);
-static int	enable_kmouse(struct psm_softc *sc, enum probearg arg);
-static int	enable_msexplorer(struct psm_softc *sc, enum probearg arg);
-static int	enable_msintelli(struct psm_softc *sc, enum probearg arg);
-static int	enable_4dmouse(struct psm_softc *sc, enum probearg arg);
-static int	enable_4dplus(struct psm_softc *sc, enum probearg arg);
-static int	enable_mmanplus(struct psm_softc *sc, enum probearg arg);
-static int	enable_synaptics(struct psm_softc *sc, enum probearg arg);
-static int	enable_trackpoint(struct psm_softc *sc, enum probearg arg);
-static int	enable_versapad(struct psm_softc *sc, enum probearg arg);
-static int	enable_elantech(struct psm_softc *sc, enum probearg arg);
+static probefunc_t	enable_groller;
+static probefunc_t	enable_gmouse;
+static probefunc_t	enable_aglide;
+static probefunc_t	enable_kmouse;
+static probefunc_t	enable_msexplorer;
+static probefunc_t	enable_msintelli;
+static probefunc_t	enable_4dmouse;
+static probefunc_t	enable_4dplus;
+static probefunc_t	enable_mmanplus;
+static probefunc_t	enable_synaptics;
+static probefunc_t	enable_synaptics_mux;
+static probefunc_t	enable_single_synaptics_mux;
+static probefunc_t	enable_trackpoint;
+static probefunc_t	enable_versapad;
+static probefunc_t	enable_elantech;
 
 static void set_trackpoint_parameters(struct psm_softc *sc);
 static void synaptics_passthrough_on(struct psm_softc *sc);
@@ -623,6 +713,10 @@ static struct {
 	 * WARNING: the order of probe is very important.  Don't mess it
 	 * unless you know what you are doing.
 	 */
+	{ MOUSE_MODEL_SYNAPTICS,	/* Synaptics + mouse on Active Mux */
+	  0x00, MOUSE_PS2_PACKETSIZE, enable_synaptics_mux },
+	{ MOUSE_MODEL_SYNAPTICS,	/* Single Synaptics on Active Mux */
+	  0xc0, MOUSE_SYNAPTICS_PACKETSIZE, enable_single_synaptics_mux },
 	{ MOUSE_MODEL_NET,		/* Genius NetMouse */
 	  0x08, MOUSE_PS2INTELLI_PACKETSIZE, enable_gmouse },
 	{ MOUSE_MODEL_NETSCROLL,	/* Genius NetScroll */
@@ -669,18 +763,28 @@ static device_method_t psm_methods[] = {
 static driver_t psm_driver = {
 	PSM_DRIVER_NAME,
 	psm_methods,
-	sizeof(struct psm_softc)
+	sizeof(struct psm_softc),
 };
 
 static struct dev_ops psm_ops = {
 	{ PSM_DRIVER_NAME, 0, 0 },
-	.d_open =   psmopen,
-	.d_close =  psmclose,
+	.d_open =   psm_cdev_open,
+	.d_close =  psm_cdev_close,
 	.d_read =   psmread,
 	.d_ioctl =  psmioctl,
 	.d_kqfilter =	 psmkqfilter
 };
 
+#ifdef EVDEV_SUPPORT
+static const struct evdev_methods psm_ev_methods_r = {
+	.ev_open = psm_ev_open_r,
+	.ev_close = psm_ev_close_r,
+};
+static const struct evdev_methods psm_ev_methods_a = {
+	.ev_open = psm_ev_open_a,
+	.ev_close = psm_ev_close_a,
+};
+#endif
 
 /* device I/O routines */
 static int
@@ -736,9 +840,12 @@ get_mouse_status(KBDC kbdc, int *status, int flag, int len)
 		if (status[i] < 0)
 			break;
 	}
-
-	VLOG(1, (LOG_DEBUG, "psm: %s %02x %02x %02x\n",
-	    (flag == 1) ? "data" : "status", status[0], status[1], status[2]));
+	if (len >= 3) {
+		for (; i < 3; ++i)
+			status[i] = 0;
+		VLOG(1, (LOG_DEBUG, "psm: %s %02x %02x %02x\n",
+		    (flag == 1) ? "data" : "status", status[0], status[1], status[2]));
+	}
 
 	return (i);
 }
@@ -746,8 +853,8 @@ get_mouse_status(KBDC kbdc, int *status, int flag, int len)
 static int
 get_aux_id(KBDC kbdc)
 {
-	int id;
 	int res;
+	int id;
 
 	empty_aux_buffer(kbdc, 5);
 	res = send_aux_command(kbdc, PSMC_SEND_DEV_ID);
@@ -851,6 +958,32 @@ get_mouse_buttons(KBDC kbdc)
 }
 
 /* misc subroutines */
+/*
+ * Someday, I will get the complete list of valid pointing devices and
+ * their IDs... XXX
+ */
+static int
+is_a_mouse(int id)
+{
+#if 0
+	static int valid_ids[] = {
+		PSM_MOUSE_ID,		/* mouse */
+		PSM_BALLPOINT_ID,	/* ballpoint device */
+		PSM_INTELLI_ID,		/* Intellimouse */
+		PSM_EXPLORER_ID,	/* Intellimouse Explorer */
+		-1			/* end of table */
+	};
+	int i;
+
+	for (i = 0; valid_ids[i] >= 0; ++i)
+	if (valid_ids[i] == id)
+		return (TRUE);
+	return (FALSE);
+#else
+	return (TRUE);
+#endif
+}
+
 static char *
 model_name(int model)
 {
@@ -947,9 +1080,8 @@ doinitialize(struct psm_softc *sc, mousemode_t *mode)
 	case 3:
 	case PSM_ACK:
 		if (verbose)
-			log(LOG_DEBUG,
-			    "psm%d: strange result for test aux port (%d).\n",
-			    sc->unit, i);
+			device_log(sc->dev, LOG_DEBUG,
+			    "strange result for test aux port (%d).\n", i);
 		/* FALLTHROUGH */
 	case 0:		/* no error */
 		break;
@@ -958,8 +1090,8 @@ doinitialize(struct psm_softc *sc, mousemode_t *mode)
 		recover_from_error(kbdc);
 		if (sc->config & PSM_CONFIG_IGNPORTERROR)
 			break;
-		log(LOG_ERR, "psm%d: the aux port is not functioning (%d).\n",
-		    sc->unit, i);
+		device_log(sc->dev, LOG_ERR,
+		    "the aux port is not functioning (%d).\n", i);
 		return (FALSE);
 	}
 
@@ -975,8 +1107,8 @@ doinitialize(struct psm_softc *sc, mousemode_t *mode)
 		 */
 		if (!reset_aux_dev(kbdc)) {
 			recover_from_error(kbdc);
-			log(LOG_ERR, "psm%d: failed to reset the aux device.\n",
-			    sc->unit);
+			device_log(sc->dev, LOG_ERR,
+			    "failed to reset the aux device.\n");
 			return (FALSE);
 		}
 	}
@@ -986,8 +1118,8 @@ doinitialize(struct psm_softc *sc, mousemode_t *mode)
 	 * if the device can be enabled.
 	 */
 	if (!enable_aux_dev(kbdc) || !disable_aux_dev(kbdc)) {
-		log(LOG_ERR, "psm%d: failed to enable the aux device.\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_ERR,
+		    "failed to enable the aux device.\n");
 		return (FALSE);
 	}
 	empty_both_buffers(kbdc, 10);	/* remove stray data if any */
@@ -1013,8 +1145,8 @@ doinitialize(struct psm_softc *sc, mousemode_t *mode)
 
 	/* just check the status of the mouse */
 	if (get_mouse_status(kbdc, stat, 0, 3) < 3)
-		log(LOG_DEBUG, "psm%d: failed to get status (doinitialize).\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_DEBUG,
+		    "failed to get status (doinitialize).\n");
 
 	return (TRUE);
 }
@@ -1024,6 +1156,7 @@ doopen(struct psm_softc *sc, int command_byte)
 {
 
 	int stat[3];
+	int mux_enabled = FALSE;
 
 	/*
 	 * FIXME: Synaptics TouchPad seems to go back to Relative Mode with
@@ -1038,17 +1171,25 @@ doopen(struct psm_softc *sc, int command_byte)
 	 * doesn't show any evidence of such a command.
 	 */
 	if (sc->hw.model == MOUSE_MODEL_SYNAPTICS) {
-		mouse_ext_command(sc->kbdc, 1);
+		if (sc->muxport != PSM_NOMUX) {
+			mux_enabled = enable_aux_mux(sc->kbdc) >= 0;
+			if (mux_enabled)
+				set_active_aux_mux_port(sc->kbdc, sc->muxport);
+			else
+				device_log(sc->dev, LOG_ERR, "failed to enable "
+				    "active multiplexing mode.\n");
+		}
+		mouse_ext_command(sc->kbdc, SYNAPTICS_READ_MODES);
 		get_mouse_status(sc->kbdc, stat, 0, 3);
 		if ((SYNAPTICS_VERSION_GE(sc->synhw, 7, 5) ||
 		    stat[1] == 0x47) &&
 		    stat[2] == 0x40) {
 			synaptics_set_mode(sc, synaptics_preferred_mode(sc));
-			VLOG(5, (LOG_DEBUG, "psm%d: Synaptics Absolute Mode "
-			    "hopefully restored\n",
-			    sc->unit));
+			VDLOG(5, sc->dev, LOG_DEBUG, "Synaptics Absolute Mode "
+			    "hopefully restored\n");
 		}
-
+		if (mux_enabled)
+			disable_aux_mux(sc->kbdc);
 	}
 
 	/*
@@ -1057,27 +1198,13 @@ doopen(struct psm_softc *sc, int command_byte)
 	 */
 	if (sc->hw.model == MOUSE_MODEL_GENERIC) {
 		if (tap_enabled > 0) {
-			/*
-			 * Enable tap & drag gestures. We use a Mode Byte
-			 * and clear the DisGest bit (see §2.5 of Synaptics
-			 * TouchPad Interfacing Guide).
-			 */
-			VLOG(2, (LOG_DEBUG,
-			    "psm%d: enable tap and drag gestures\n",
-			    sc->unit));
-			mouse_ext_command(sc->kbdc, 0x00);
-			set_mouse_sampling_rate(sc->kbdc, 20);
+			VDLOG(2, sc->dev, LOG_DEBUG,
+			    "enable tap and drag gestures\n");
+			synaptics_set_mode(sc, synaptics_preferred_mode(sc));
 		} else if (tap_enabled == 0) {
-			/*
-			 * Disable tap & drag gestures. We use a Mode Byte
-			 * and set the DisGest bit (see §2.5 of Synaptics
-			 * TouchPad Interfacing Guide).
-			 */
-			VLOG(2, (LOG_DEBUG,
-			    "psm%d: disable tap and drag gestures\n",
-			    sc->unit));
-			mouse_ext_command(sc->kbdc, 0x04);
-			set_mouse_sampling_rate(sc->kbdc, 20);
+			VDLOG(2, sc->dev, LOG_DEBUG,
+			    "disable tap and drag gestures\n");
+			synaptics_set_mode(sc, synaptics_preferred_mode(sc));
 		}
 	}
 
@@ -1103,27 +1230,26 @@ doopen(struct psm_softc *sc, int command_byte)
 			restore_controller(sc->kbdc, command_byte);
 			/* mark this device is no longer available */
 			sc->state &= ~PSM_VALID;
-			log(LOG_ERR,
-			    "psm%d: failed to enable the device (doopen).\n",
-			sc->unit);
+			device_log(sc->dev, LOG_ERR,
+			    "failed to enable the device (doopen).\n");
 			return (EIO);
 		}
 	}
 
 	if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3)
-		log(LOG_DEBUG, "psm%d: failed to get status (doopen).\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_DEBUG,
+		    "failed to get status (doopen).\n");
 
 	/* enable the aux port and interrupt */
 	if (!set_controller_command_byte(sc->kbdc,
-	    KBD_AUX_CONTROL_BITS,
+	    kbdc_get_device_mask(sc->kbdc),
+	    (command_byte & KBD_KBD_CONTROL_BITS) |
 	    KBD_ENABLE_AUX_PORT | KBD_ENABLE_AUX_INT)) {
 		/* CONTROLLER ERROR */
 		disable_aux_dev(sc->kbdc);
 		restore_controller(sc->kbdc, command_byte);
-		log(LOG_ERR,
-		    "psm%d: failed to enable the aux interrupt (doopen).\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_ERR,
+		    "failed to enable the aux interrupt (doopen).\n");
 		return (EIO);
 	}
 
@@ -1137,7 +1263,6 @@ doopen(struct psm_softc *sc, int command_byte)
 static int
 reinitialize(struct psm_softc *sc, int doinit)
 {
-
 	int err;
 	int c;
 
@@ -1154,21 +1279,19 @@ reinitialize(struct psm_softc *sc, int doinit)
 	/* save the current controller command byte */
 	empty_both_buffers(sc->kbdc, 10);
 	c = get_controller_command_byte(sc->kbdc);
-	VLOG(2, (LOG_DEBUG,
-	    "psm%d: current command byte: %04x (reinitialize).\n",
-	    sc->unit, c));
+	VDLOG(2, sc->dev, LOG_DEBUG,
+	    "current command byte: %04x (reinitialize).\n", c);
 
 	/* enable the aux port but disable the aux interrupt and the keyboard */
-	if ((c == -1) ||
-	    !set_controller_command_byte(sc->kbdc,
-		    KBD_AUX_CONTROL_BITS,
+	if ((c == -1) || !set_controller_command_byte(sc->kbdc,
+		    kbdc_get_device_mask(sc->kbdc),
+		    KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT |
 		    KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 		/* CONTROLLER ERROR */
 		lockmgr(&sc->lock, LK_RELEASE);
 		kbdc_lock(sc->kbdc, FALSE);
-		log(LOG_ERR,
-		    "psm%d: unable to set the command byte (reinitialize).\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_ERR,
+		    "unable to set the command byte (reinitialize).\n");
 		return (EIO);
 	}
 
@@ -1193,29 +1316,30 @@ reinitialize(struct psm_softc *sc, int doinit)
 			/* the device has gone! */
 			restore_controller(sc->kbdc, c);
 			sc->state &= ~PSM_VALID;
-			log(LOG_ERR,
-			    "psm%d: the aux device has gone! (reinitialize).\n",
-			    sc->unit);
+			device_log(sc->dev, LOG_ERR,
+			    "the aux device has gone! (reinitialize).\n");
 			err = ENXIO;
 		}
 	}
 	lockmgr(&sc->lock, LK_RELEASE);
 
 	/* restore the driver state */
-	if ((sc->state & PSM_OPEN) && (err == 0)) {
+	if ((sc->state & (PSM_OPEN | PSM_EV_OPEN_R | PSM_EV_OPEN_A)) &&
+		(err == 0)) {
 		/* enable the aux device and the port again */
 		err = doopen(sc, c);
 		if (err != 0)
-			log(LOG_ERR, "psm%d: failed to enable the device "
-			    "(reinitialize).\n", sc->unit);
+			device_log(sc->dev, LOG_ERR,
+			    "failed to enable the device (reinitialize).\n");
 	} else {
 		/* restore the keyboard port and disable the aux port */
 		if (!set_controller_command_byte(sc->kbdc,
-		    KBD_AUX_CONTROL_BITS,
+		    kbdc_get_device_mask(sc->kbdc),
+		    (c & KBD_KBD_CONTROL_BITS) |
 		    KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 			/* CONTROLLER ERROR */
-			log(LOG_ERR, "psm%d: failed to disable the aux port "
-			    "(reinitialize).\n", sc->unit);
+			device_log(sc->dev, LOG_ERR,
+			    "failed to disable the aux port (reinitialize).\n");
 			err = EIO;
 		}
 	}
@@ -1229,7 +1353,6 @@ reinitialize(struct psm_softc *sc, int doinit)
 static void
 psmidentify(driver_t *driver, device_t parent)
 {
-
 	device_t psmc;
 	device_t psm;
 	u_long irq;
@@ -1240,13 +1363,11 @@ psmidentify(driver_t *driver, device_t parent)
 	/* always add at least one child */
 	psm = BUS_ADD_CHILD(parent, parent, KBDC_RID_AUX, driver->name, unit);
 	if (psm == NULL) {
-
 		return;
 	}
 
 	irq = bus_get_resource_start(psm, SYS_RES_IRQ, KBDC_RID_AUX);
 	if (irq > 0) {
-
 		return;
 	}
 
@@ -1262,7 +1383,6 @@ psmidentify(driver_t *driver, device_t parent)
 
 	irq = bus_get_resource_start(psmc, SYS_RES_IRQ, 0);
 	if (irq <= 0) {
-
 		return;
 	}
 
@@ -1274,8 +1394,7 @@ psmidentify(driver_t *driver, device_t parent)
 #define endprobe(v)	do {			\
 	if (bootverbose)			\
 		--verbose;			\
-	set_controller_command_byte(sc->kbdc,	\
-    KBD_AUX_CONTROL_BITS, KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT); \
+	kbdc_set_device_mask(sc->kbdc, mask);	\
 	kbdc_lock(sc->kbdc, FALSE);		\
 	return (v);				\
 } while (0)
@@ -1283,11 +1402,10 @@ psmidentify(driver_t *driver, device_t parent)
 static int
 psmprobe(device_t dev)
 {
-
-	int unit = device_get_unit(dev);
 	struct psm_softc *sc = device_get_softc(dev);
 	int stat[3];
 	int command_byte;
+	int mask;
 /*	int rid; */
 	int i;
 	uintptr_t irq;
@@ -1300,8 +1418,10 @@ psmprobe(device_t dev)
 	BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_IRQ, &irq);
 	BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_FLAGS, &flags);
 
-	sc->unit = unit;
+	sc->dev = dev;
 	sc->kbdc = atkbdc_open(device_get_unit(device_get_parent(dev)));
+	if (sc->kbdc == NULL)
+		return (ENXIO);
 	sc->config = flags & PSM_CONFIG_FLAGS;
 	/* XXX: for backward compatibility */
 #if defined(PSM_HOOKRESUME)
@@ -1313,13 +1433,14 @@ psmprobe(device_t dev)
 #endif
 #endif /* PSM_HOOKRESUME */
 	sc->flags = 0;
+	sc->muxport = PSM_NOMUX;
 	if (bootverbose)
 		++verbose;
 
 	device_set_desc(dev, "PS/2 Mouse");
 
 	if (!kbdc_lock(sc->kbdc, TRUE)) {
-		kprintf("psm%d: unable to lock the controller.\n", unit);
+		device_printf(dev, "unable to lock the controller.\n");
 		if (bootverbose)
 			--verbose;
 		return (ENXIO);
@@ -1336,15 +1457,14 @@ psmprobe(device_t dev)
 	empty_both_buffers(sc->kbdc, 10);
 
 	/* save the current command byte; it will be used later */
+	mask = kbdc_get_device_mask(sc->kbdc) & ~KBD_AUX_CONTROL_BITS;
 	command_byte = get_controller_command_byte(sc->kbdc);
 	if (verbose)
-		kprintf("psm%d: current command byte:%04x\n", unit,
-		    command_byte);
+		device_printf(dev, "current command byte:%04x\n", command_byte);
 	if (command_byte == -1) {
 		/* CONTROLLER ERROR */
-		kprintf("psm%d: unable to get the current "
-			"command byte value.\n",
-			unit);
+		device_printf(dev,
+			"unable to get the current command byte value.\n");
 		endprobe(ENXIO);
 	}
 
@@ -1368,13 +1488,16 @@ psmprobe(device_t dev)
 	 * enabled during this routine
 	 */
 	if (!set_controller_command_byte(sc->kbdc,
-	    KBD_AUX_CONTROL_BITS,
+	    KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS,
+	    KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT |
 	    KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 		/*
 		 * this is CONTROLLER ERROR; I don't know how to recover
 		 * from this error...
 		 */
-		kprintf("psm%d: unable to set the command byte.\n", unit);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
+		device_printf(dev, "unable to set the command byte.\n");
 		endprobe(ENXIO);
 	}
 
@@ -1392,12 +1515,12 @@ psmprobe(device_t dev)
 	 * be that this is only the case when the controller DOES have the aux
 	 * port but the port is not wired on the motherboard.) The keyboard
 	 * controllers without the port, such as the original AT, are
-	 * supporsed to return with an error code or simply time out. In any
+	 * supposed to return with an error code or simply time out. In any
 	 * case, we have to continue probing the port even when the controller
 	 * passes this test.
 	 *
 	 * XXX: some controllers erroneously return the error code 1, 2 or 3
-	 * when it has the perfectly functional aux port. We have to ignore
+	 * when it has a perfectly functional aux port. We have to ignore
 	 * this error code. Even if the controller HAS error with the aux
 	 * port, it will be detected later...
 	 * XXX: another incompatible controller returns PSM_ACK (0xfa)...
@@ -1409,8 +1532,8 @@ psmprobe(device_t dev)
 	case 3:
 	case PSM_ACK:
 		if (verbose)
-			kprintf("psm%d: strange result for test aux port "
-			    "(%d).\n", unit, i);
+			device_printf(dev, "strange result for test aux port "
+			    "(%d).\n", i);
 		/* FALLTHROUGH */
 	case 0:		/* no error */
 		break;
@@ -1419,18 +1542,18 @@ psmprobe(device_t dev)
 		recover_from_error(sc->kbdc);
 		if (sc->config & PSM_CONFIG_IGNPORTERROR)
 			break;
-
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		if (verbose)
-			kprintf("psm%d: the aux port is not "
-				"functioning (%d).\n",
-				unit, i);
+			device_printf(dev,
+				"the aux port is not functioning (%d).\n", i);
 		endprobe(ENXIO);
 	}
 
 	if (sc->config & PSM_CONFIG_NORESET) {
 		/*
 		 * Don't try to reset the pointing device.  It may possibly be
-		 * left in the unknown state, though...
+		 * left in an unknown state, though...
 		 */
 	} else {
 		/*
@@ -1443,15 +1566,17 @@ psmprobe(device_t dev)
 		 */
 		if (!reset_aux_dev(sc->kbdc)) {
 			recover_from_error(sc->kbdc);
+			if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+				restore_controller(sc->kbdc, command_byte);
 			if (verbose)
-				kprintf("psm%d: failed to reset the aux "
-				    "device.\n", unit);
+				device_printf(dev, "failed to reset the aux "
+				    "device.\n");
 			endprobe(ENXIO);
 		} else if (!reset_aux_dev(sc->kbdc)) {
 			recover_from_error(sc->kbdc);
 			if (verbose >= 2)
-				kprintf("psm%d: failed to reset the aux device "
-				    "(2).\n", unit);
+				device_printf(dev, "failed to reset the aux "
+				    "device (2).\n");
 		}
 	}
 
@@ -1464,9 +1589,10 @@ psmprobe(device_t dev)
 	if (!enable_aux_dev(sc->kbdc) || !disable_aux_dev(sc->kbdc)) {
 		/* MOUSE ERROR */
 		recover_from_error(sc->kbdc);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		if (verbose)
-			kprintf("psm%d: failed to enable the aux device.\n",
-			    unit);
+			device_printf(dev, "failed to enable the aux device.\n");
 		endprobe(ENXIO);
 	}
 
@@ -1482,11 +1608,16 @@ psmprobe(device_t dev)
 	/* hardware information */
 	sc->hw.iftype = MOUSE_IF_PS2;
 
-#if 0
 	/* verify the device is a mouse */
 	sc->hw.hwid = get_aux_id(sc->kbdc);
-	/* XXX Check against a list of PS2 device types. */
-#endif
+	if (!is_a_mouse(sc->hw.hwid)) {
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
+		if (verbose)
+			device_printf(dev, "unknown device type (%d).\n",
+			    sc->hw.hwid);
+		endprobe(ENXIO);
+	}
 
 	switch (sc->hw.hwid) {
 	case PSM_BALLPOINT_ID:
@@ -1515,7 +1646,7 @@ psmprobe(device_t dev)
 		for (i = 0; vendortype[i].probefunc != NULL; ++i)
 			if ((*vendortype[i].probefunc)(sc, PROBE)) {
 				if (verbose >= 2)
-					kprintf("psm%d: found %s\n", unit,
+					device_printf(dev, "found %s\n",
 					    model_name(vendortype[i].model));
 				break;
 			}
@@ -1541,11 +1672,11 @@ psmprobe(device_t dev)
 	/*
 	 * A version of Logitech FirstMouse+ won't report wheel movement,
 	 * if SET_DEFAULTS is sent...  Don't use this command.
-	 * This f}}}ix was found by Takashi Nishida.
+	 * This fix was found by Takashi Nishida.
 	 */
 	i = send_aux_command(sc->kbdc, PSMC_SET_DEFAULTS);
 	if (verbose >= 2)
-		kprintf("psm%d: SET_DEFAULTS return code:%04x\n", unit, i);
+		device_printf(dev, "SET_DEFAULTS return code:%04x\n", i);
 #endif
 	if (sc->config & PSM_CONFIG_RESOLUTION)
 		sc->mode.resolution =
@@ -1569,7 +1700,7 @@ psmprobe(device_t dev)
 	 * after ACK from the mouse.
 	 */
 	if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3)
-		kprintf("psm%d: failed to get status.\n", unit);
+		device_printf(dev, "failed to get status.\n");
 	else {
 		/*
 		 * When in its native mode, some mice operate with different
@@ -1581,25 +1712,290 @@ psmprobe(device_t dev)
 
 	/* disable the aux port for now... */
 	if (!set_controller_command_byte(sc->kbdc,
-	    KBD_AUX_CONTROL_BITS,
+	    KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS,
+	    (command_byte & KBD_KBD_CONTROL_BITS) |
 	    KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 		/*
 		 * this is CONTROLLER ERROR; I don't know the proper way to
 		 * recover from this error...
 		 */
-		kprintf("psm%d: unable to set the command byte.\n", unit);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
+		device_printf(dev, "unable to set the command byte.\n");
 		endprobe(ENXIO);
 	}
 
 	/* done */
+	kbdc_set_device_mask(sc->kbdc, mask | KBD_AUX_CONTROL_BITS);
 	kbdc_lock(sc->kbdc, FALSE);
 	return (0);
 }
 
+#ifdef EVDEV_SUPPORT
+/* Values are taken from Linux drivers for userland software compatibility */
+#define	PS2_MOUSE_VENDOR		0x0002
+#define	PS2_MOUSE_GENERIC_PRODUCT	0x0001
+#define	PS2_MOUSE_SYNAPTICS_NAME	"SynPS/2 Synaptics TouchPad"
+#define	PS2_MOUSE_SYNAPTICS_PRODUCT	0x0007
+#define	PS2_MOUSE_TRACKPOINT_NAME	"TPPS/2 IBM TrackPoint"
+#define	PS2_MOUSE_TRACKPOINT_PRODUCT	0x000A
+#define	PS2_MOUSE_ELANTECH_NAME		"ETPS/2 Elantech Touchpad"
+#define	PS2_MOUSE_ELANTECH_ST_NAME	"ETPS/2 Elantech TrackPoint"
+#define	PS2_MOUSE_ELANTECH_PRODUCT	0x000E
+#define	ABSINFO_END	{ ABS_CNT, 0, 0, 0 }
+
+static void
+psm_support_abs_bulk(struct evdev_dev *evdev, const uint16_t info[][4])
+{
+	size_t i;
+
+	for (i = 0; info[i][0] != ABS_CNT; i++)
+		evdev_support_abs(evdev, info[i][0], info[i][1], info[i][2],
+		    0, 0, info[i][3]);
+}
+
+static void
+psm_push_mt_finger(struct psm_softc *sc, int id, const finger_t *f)
+{
+	int y = sc->synhw.minimumYCoord + sc->synhw.maximumYCoord - f->y;
+
+	evdev_push_abs(sc->evdev_a, ABS_MT_SLOT, id);
+	evdev_push_abs(sc->evdev_a, ABS_MT_TRACKING_ID, id);
+	evdev_push_abs(sc->evdev_a, ABS_MT_POSITION_X, f->x);
+	evdev_push_abs(sc->evdev_a, ABS_MT_POSITION_Y, y);
+	evdev_push_abs(sc->evdev_a, ABS_MT_PRESSURE, f->p);
+}
+
+static void
+psm_push_st_finger(struct psm_softc *sc, const finger_t *f)
+{
+	int y = sc->synhw.minimumYCoord + sc->synhw.maximumYCoord - f->y;
+
+	evdev_push_abs(sc->evdev_a, ABS_X, f->x);
+	evdev_push_abs(sc->evdev_a, ABS_Y, y);
+	evdev_push_abs(sc->evdev_a, ABS_PRESSURE, f->p);
+	if (sc->synhw.capPalmDetect)
+		evdev_push_abs(sc->evdev_a, ABS_TOOL_WIDTH, f->w);
+}
+
 static int
-psmattach(device_t dev)
+psm_register(device_t dev, int model_code)
 {
+	struct psm_softc *sc = device_get_softc(dev);
+	struct evdev_dev *evdev_r;
+	int error, i, nbuttons, nwheels, product;
+	bool is_pointing_stick;
+	const char *name;
+
+	name = model_name(model_code);
+	nbuttons = sc->hw.buttons;
+	product = PS2_MOUSE_GENERIC_PRODUCT;
+	nwheels = 0;
+	is_pointing_stick = false;
+
+	switch (model_code) {
+	case MOUSE_MODEL_TRACKPOINT:
+		name = PS2_MOUSE_TRACKPOINT_NAME;
+		product = PS2_MOUSE_TRACKPOINT_PRODUCT;
+		nbuttons = 3;
+		is_pointing_stick = true;
+		break;
 
+	case MOUSE_MODEL_ELANTECH:
+		name = PS2_MOUSE_ELANTECH_ST_NAME;
+		product = PS2_MOUSE_ELANTECH_PRODUCT;
+		nbuttons = 3;
+		is_pointing_stick = true;
+		break;
+
+	case MOUSE_MODEL_MOUSEMANPLUS:
+	case MOUSE_MODEL_4D:
+		nwheels = 2;
+		break;
+
+	case MOUSE_MODEL_EXPLORER:
+	case MOUSE_MODEL_INTELLI:
+	case MOUSE_MODEL_NET:
+	case MOUSE_MODEL_NETSCROLL:
+	case MOUSE_MODEL_4DPLUS:
+		nwheels = 1;
+		break;
+	}
+
+	evdev_r = evdev_alloc();
+	evdev_set_name(evdev_r, name);
+	evdev_set_phys(evdev_r, device_get_nameunit(dev));
+	evdev_set_id(evdev_r, BUS_I8042, PS2_MOUSE_VENDOR, product, 0);
+	evdev_set_methods(evdev_r, sc, &psm_ev_methods_r);
+
+	evdev_support_prop(evdev_r, INPUT_PROP_POINTER);
+	if (is_pointing_stick)
+		evdev_support_prop(evdev_r, INPUT_PROP_POINTING_STICK);
+	evdev_support_event(evdev_r, EV_SYN);
+	evdev_support_event(evdev_r, EV_KEY);
+	evdev_support_event(evdev_r, EV_REL);
+	evdev_support_rel(evdev_r, REL_X);
+	evdev_support_rel(evdev_r, REL_Y);
+	switch (nwheels) {
+	case 2:
+		evdev_support_rel(evdev_r, REL_HWHEEL);
+		/* FALLTHROUGH */
+	case 1:
+		evdev_support_rel(evdev_r, REL_WHEEL);
+	}
+	for (i = 0; i < nbuttons; i++)
+		evdev_support_key(evdev_r, BTN_MOUSE + i);
+
+	error = evdev_register_mtx(evdev_r, &sc->lock);
+	if (error)
+		evdev_free(evdev_r);
+	else
+		sc->evdev_r = evdev_r;
+	return (error);
+}
+
+static int
+psm_register_synaptics(device_t dev)
+{
+	struct psm_softc *sc = device_get_softc(dev);
+	const uint16_t synaptics_absinfo_st[][4] = {
+		{ ABS_X,		sc->synhw.minimumXCoord,
+		    sc->synhw.maximumXCoord, sc->synhw.infoXupmm },
+		{ ABS_Y,		sc->synhw.minimumYCoord,
+		    sc->synhw.maximumYCoord, sc->synhw.infoYupmm },
+		{ ABS_PRESSURE,		0, ELANTECH_FINGER_MAX_P, 0 },
+		ABSINFO_END,
+	};
+	const uint16_t synaptics_absinfo_mt[][4] = {
+		{ ABS_MT_SLOT,		0, PSM_FINGERS-1, 0},
+		{ ABS_MT_TRACKING_ID,	-1, PSM_FINGERS-1, 0},
+		{ ABS_MT_POSITION_X,	sc->synhw.minimumXCoord,
+		    sc->synhw.maximumXCoord, sc->synhw.infoXupmm },
+		{ ABS_MT_POSITION_Y,	sc->synhw.minimumYCoord,
+		    sc->synhw.maximumYCoord, sc->synhw.infoYupmm },
+		{ ABS_MT_PRESSURE,	0, ELANTECH_FINGER_MAX_P, 0 },
+		ABSINFO_END,
+	};
+	struct evdev_dev *evdev_a;
+	int error, i, guest_model;
+
+	evdev_a = evdev_alloc();
+	evdev_set_name(evdev_a, PS2_MOUSE_SYNAPTICS_NAME);
+	evdev_set_phys(evdev_a, device_get_nameunit(dev));
+	evdev_set_id(evdev_a, BUS_I8042, PS2_MOUSE_VENDOR,
+	    PS2_MOUSE_SYNAPTICS_PRODUCT, 0);
+	evdev_set_methods(evdev_a, sc, &psm_ev_methods_a);
+	if (sc->synhw.capAdvancedGestures || sc->synhw.capReportsV)
+		evdev_set_flag(evdev_a, EVDEV_FLAG_MT_AUTOREL);
+	if (sc->synhw.capReportsV)
+		evdev_set_flag(evdev_a, EVDEV_FLAG_MT_TRACK);
+
+	evdev_support_event(evdev_a, EV_SYN);
+	evdev_support_event(evdev_a, EV_KEY);
+	evdev_support_event(evdev_a, EV_ABS);
+	evdev_support_prop(evdev_a, INPUT_PROP_POINTER);
+	if (sc->synhw.capAdvancedGestures)
+		evdev_support_prop(evdev_a, INPUT_PROP_SEMI_MT);
+	if (sc->synhw.capClickPad)
+		evdev_support_prop(evdev_a, INPUT_PROP_BUTTONPAD);
+	if (sc->synhw.capClickPad && sc->synhw.topButtonPad)
+		evdev_support_prop(evdev_a, INPUT_PROP_TOPBUTTONPAD);
+	evdev_support_key(evdev_a, BTN_TOUCH);
+	evdev_support_nfingers(evdev_a, sc->synhw.capReportsV ? 5 : 3);
+	psm_support_abs_bulk(evdev_a, synaptics_absinfo_st);
+	if (sc->synhw.capAdvancedGestures || sc->synhw.capReportsV)
+		psm_support_abs_bulk(evdev_a, synaptics_absinfo_mt);
+	if (sc->synhw.capPalmDetect)
+		evdev_support_abs(evdev_a, ABS_TOOL_WIDTH, 0, 15, 0, 0, 0);
+	evdev_support_key(evdev_a, BTN_LEFT);
+	if (!sc->synhw.capClickPad) {
+		evdev_support_key(evdev_a, BTN_RIGHT);
+		if (sc->synhw.capExtended && sc->synhw.capMiddle)
+			evdev_support_key(evdev_a, BTN_MIDDLE);
+	}
+	if (sc->synhw.capExtended && sc->synhw.capFourButtons) {
+		evdev_support_key(evdev_a, BTN_BACK);
+		evdev_support_key(evdev_a, BTN_FORWARD);
+	}
+	if (sc->synhw.capExtended && (sc->synhw.nExtendedButtons > 0))
+		for (i = 0; i < sc->synhw.nExtendedButtons; i++)
+			evdev_support_key(evdev_a, BTN_0 + i);
+
+	error = evdev_register_mtx(evdev_a, &sc->lock);
+	if (!error && (sc->synhw.capPassthrough || sc->muxport != PSM_NOMUX)) {
+		guest_model = sc->tpinfo.sysctl_tree != NULL ?
+		    MOUSE_MODEL_TRACKPOINT : MOUSE_MODEL_GENERIC;
+		error = psm_register(dev, guest_model);
+	}
+	if (error)
+		evdev_free(evdev_a);
+	else
+		sc->evdev_a = evdev_a;
+	return (error);
+}
+
+static int
+psm_register_elantech(device_t dev)
+{
+	struct psm_softc *sc = device_get_softc(dev);
+	const uint16_t elantech_absinfo[][4] = {
+		{ ABS_X,		0, sc->elanhw.sizex,
+					   sc->elanhw.dpmmx },
+		{ ABS_Y,		0, sc->elanhw.sizey,
+					   sc->elanhw.dpmmy },
+		{ ABS_PRESSURE,		0, ELANTECH_FINGER_MAX_P, 0 },
+		{ ABS_TOOL_WIDTH,	0, ELANTECH_FINGER_MAX_W, 0 },
+		{ ABS_MT_SLOT,		0, ELANTECH_MAX_FINGERS - 1, 0 },
+		{ ABS_MT_TRACKING_ID,	-1, ELANTECH_MAX_FINGERS - 1, 0 },
+		{ ABS_MT_POSITION_X,	0, sc->elanhw.sizex,
+					   sc->elanhw.dpmmx },
+		{ ABS_MT_POSITION_Y,	0, sc->elanhw.sizey,
+					   sc->elanhw.dpmmy },
+		{ ABS_MT_PRESSURE,	0, ELANTECH_FINGER_MAX_P, 0 },
+		{ ABS_MT_TOUCH_MAJOR,	0, ELANTECH_FINGER_MAX_W *
+					   sc->elanhw.dptracex, 0 },
+		ABSINFO_END,
+	};
+	struct evdev_dev *evdev_a;
+	int error;
+
+	evdev_a = evdev_alloc();
+	evdev_set_name(evdev_a, PS2_MOUSE_ELANTECH_NAME);
+	evdev_set_phys(evdev_a, device_get_nameunit(dev));
+	evdev_set_id(evdev_a, BUS_I8042, PS2_MOUSE_VENDOR,
+	    PS2_MOUSE_ELANTECH_PRODUCT, 0);
+	evdev_set_methods(evdev_a, sc, &psm_ev_methods_a);
+	evdev_set_flag(evdev_a, EVDEV_FLAG_MT_AUTOREL);
+
+	evdev_support_event(evdev_a, EV_SYN);
+	evdev_support_event(evdev_a, EV_KEY);
+	evdev_support_event(evdev_a, EV_ABS);
+	evdev_support_prop(evdev_a, INPUT_PROP_POINTER);
+	if (sc->elanhw.issemimt)
+		evdev_support_prop(evdev_a, INPUT_PROP_SEMI_MT);
+	if (sc->elanhw.isclickpad)
+		evdev_support_prop(evdev_a, INPUT_PROP_BUTTONPAD);
+	evdev_support_key(evdev_a, BTN_TOUCH);
+	evdev_support_nfingers(evdev_a, ELANTECH_MAX_FINGERS);
+	evdev_support_key(evdev_a, BTN_LEFT);
+	if (!sc->elanhw.isclickpad)
+		evdev_support_key(evdev_a, BTN_RIGHT);
+	psm_support_abs_bulk(evdev_a, elantech_absinfo);
+
+	error = evdev_register_mtx(evdev_a, &sc->lock);
+	if (!error && sc->elanhw.hastrackpoint)
+		error = psm_register(dev, MOUSE_MODEL_ELANTECH);
+	if (error)
+		evdev_free(evdev_a);
+	else
+		sc->evdev_a = evdev_a;
+	return (error);
+}
+#endif
+
+static int
+psmattach(device_t dev)
+{
 	int unit = device_get_unit(dev);
 	struct psm_softc *sc = device_get_softc(dev);
 	int error;
@@ -1607,6 +2003,7 @@ psmattach(device_t dev)
 	uintptr_t irq;
 
 	/* Setup initial state */
+	sc->cdev = NULL;
 	sc->state = PSM_VALID;
 	callout_init_lk(&sc->callout, &sc->lock);
 	callout_init_lk(&sc->softcallout, &sc->lock);
@@ -1622,14 +2019,33 @@ psmattach(device_t dev)
 
 	error = BUS_SETUP_INTR(device_get_parent(dev), dev, sc->intr,
 			INTR_NOPOLL, psmintr, sc, &sc->ih, NULL, NULL);
-	if (error) {
-		bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr);
-		return (error);
-	}
+	if (error)
+		goto out;
 
 	/* Done */
-	sc->dev = make_dev(&psm_ops, unit, 0, 0, 0666, "psm%d", unit);
-	sc->dev->si_drv1 = sc;
+	sc->cdev = make_dev(&psm_ops, unit, 0, 0, 0666, "psm%d", unit);
+	if (sc->cdev == NULL)
+		goto out;
+	sc->cdev->si_drv1 = sc;
+
+#ifdef EVDEV_SUPPORT
+	switch (sc->hw.model) {
+	case MOUSE_MODEL_SYNAPTICS:
+		error = psm_register_synaptics(dev);
+		break;
+
+	case MOUSE_MODEL_ELANTECH:
+		error = psm_register_elantech(dev);
+		break;
+
+	default:
+		error = psm_register(dev, sc->hw.model);
+	}
+
+	if (error)
+		goto out;
+#endif
+
 
 	/* Some touchpad devices need full reinitialization after suspend. */
 	switch (sc->hw.model) {
@@ -1640,34 +2056,46 @@ psmattach(device_t dev)
 		sc->config |= PSM_CONFIG_INITAFTERSUSPEND;
 		break;
 	default:
-		if (sc->synhw.infoMajor >= 4 || sc->tphw > 0)
+		if (sc->synhw.infoMajor >= 4 || sc->tpinfo.sysctl_tree != NULL)
 			sc->config |= PSM_CONFIG_INITAFTERSUSPEND;
 		break;
 	}
 
 	/* Elantech trackpad`s sync bit differs from touchpad`s one */
 	if (sc->hw.model == MOUSE_MODEL_ELANTECH &&
-	    (sc->elanhw.hascrc || sc->elanhw.hastrackpoint))
+	    (sc->elanhw.hascrc || sc->elanhw.hastrackpoint)) {
 		sc->config |= PSM_CONFIG_NOCHECKSYNC;
+		sc->flags &= ~PSM_NEED_SYNCBITS;
+	}
 
 
 	if (!verbose)
-		kprintf("psm%d: model %s, device ID %d\n",
-		    unit, model_name(sc->hw.model), sc->hw.hwid & 0x00ff);
+		device_printf(dev, "model %s, device ID %d\n",
+		    model_name(sc->hw.model), sc->hw.hwid & 0x00ff);
 	else {
-		kprintf("psm%d: model %s, device ID %d-%02x, %d buttons\n",
-		    unit, model_name(sc->hw.model), sc->hw.hwid & 0x00ff,
+		device_printf(dev, "model %s, device ID %d-%02x, %d buttons\n",
+		    model_name(sc->hw.model), sc->hw.hwid & 0x00ff,
 		    sc->hw.hwid >> 8, sc->hw.buttons);
-		kprintf("psm%d: config:%08x, flags:%08x, packet size:%d\n",
-		    unit, sc->config, sc->flags, sc->mode.packetsize);
-		kprintf("psm%d: syncmask:%02x, syncbits:%02x\n",
-		    unit, sc->mode.syncmask[0], sc->mode.syncmask[1]);
+		device_printf(dev, "config:%08x, flags:%08x, packet size:%d\n",
+		    sc->config, sc->flags, sc->mode.packetsize);
+		device_printf(dev, "syncmask:%02x, syncbits:%02x%s\n",
+		    sc->mode.syncmask[0], sc->mode.syncmask[1],
+		    sc->config & PSM_CONFIG_NOCHECKSYNC ? " (sync not checked)" : "");
 	}
 
 	if (bootverbose)
 		--verbose;
 
-	return (0);
+out:
+	if (error != 0) {
+		if (sc->ih != NULL)
+			bus_teardown_intr(dev, sc->intr, sc->ih);
+		if (sc->intr != NULL)
+			bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr);
+		if (sc->cdev != NULL)
+			destroy_dev(sc->cdev);
+	}
+	return (error);
 }
 
 static int
@@ -1681,11 +2109,16 @@ psmdetach(device_t dev)
 	if (sc->state & PSM_OPEN)
 		return (EBUSY);
 
+#ifdef EVDEV_SUPPORT
+	evdev_free(sc->evdev_r);
+	evdev_free(sc->evdev_a);
+#endif
+
 	rid = KBDC_RID_AUX;
 	bus_teardown_intr(dev, sc->intr, sc->ih);
 	bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr);
 
-	destroy_dev(sc->dev);
+	destroy_dev(sc->cdev);
 
 	/* XXX: callout_drain in original freebsd11 code */
 	callout_terminate(&sc->callout);
@@ -1694,14 +2127,91 @@ psmdetach(device_t dev)
 	return (0);
 }
 
+#ifdef EVDEV_SUPPORT
+static int
+psm_ev_open_r(struct evdev_dev *evdev)
+{
+	struct psm_softc *sc = evdev_get_softc(evdev);
+	int err = 0;
+
+	/* Get device data */
+	if ((sc->state & PSM_VALID) == 0) {
+		/* the device is no longer valid/functioning */
+		return (ENXIO);
+	}
+
+	if (!(sc->state & (PSM_OPEN | PSM_EV_OPEN_A)))
+		err = psmopen(sc);
+
+	if (err == 0)
+		sc->state |= PSM_EV_OPEN_R;
+
+	return (err);
+}
+
+static int
+psm_ev_close_r(struct evdev_dev *evdev)
+{
+	struct psm_softc *sc = evdev_get_softc(evdev);
+	int err = 0;
+
+	sc->state &= ~PSM_EV_OPEN_R;
+
+	if (sc->state & (PSM_OPEN | PSM_EV_OPEN_A))
+		return (0);
+
+	if (sc->state & PSM_VALID)
+		err = psmclose(sc);
+
+	return (err);
+}
+
+static int
+psm_ev_open_a(struct evdev_dev *evdev)
+{
+	struct psm_softc *sc = evdev_get_softc(evdev);
+	int err = 0;
+
+	/* Get device data */
+	if ((sc->state & PSM_VALID) == 0) {
+		/* the device is no longer valid/functioning */
+		return (ENXIO);
+	}
+
+	if (!(sc->state & (PSM_OPEN | PSM_EV_OPEN_R)))
+		err = psmopen(sc);
+
+	if (err == 0)
+		sc->state |= PSM_EV_OPEN_A;
+
+	return (err);
+}
+
 static int
-psmopen(struct dev_open_args *ap)
+psm_ev_close_a(struct evdev_dev *evdev)
 {
+	struct psm_softc *sc = evdev_get_softc(evdev);
+	int err = 0;
 
+	sc->state &= ~PSM_EV_OPEN_A;
+
+	if (sc->state & (PSM_OPEN | PSM_EV_OPEN_R))
+		return (0);
+
+	if (sc->state & PSM_VALID)
+		err = psmclose(sc);
+
+	return (err);
+}
+#endif
+
+
+static int
+psm_cdev_open(struct dev_open_args *ap)
+{
 	cdev_t dev = ap->a_head.a_dev;
 	struct psm_softc *sc;
-	int command_byte;
-	int err;
+	int err = 0;
 
 	/* Get device data */
 	sc = dev->si_drv1;
@@ -1714,7 +2224,54 @@ psmopen(struct dev_open_args *ap)
 	if (sc->state & PSM_OPEN)
 		return (EBUSY);
 
-	device_busy(devclass_get_device(psm_devclass, sc->unit));
+	device_busy(sc->dev);
+#ifdef EVDEV_SUPPORT
+	/* Already opened by evdev */
+	if (!(sc->state & (PSM_EV_OPEN_R | PSM_EV_OPEN_A)))
+#endif
+		err = psmopen(sc);
+
+	if (err == 0)
+		sc->state |= PSM_OPEN;
+	else
+		device_unbusy(sc->dev);
+
+	return (err);
+}
+
+static int
+psm_cdev_close(struct dev_close_args *ap)
+{
+	cdev_t dev = ap->a_head.a_dev;
+	struct psm_softc *sc;
+	int err = 0;
+	
+	/* Get device data */
+	sc = dev->si_drv1;
+	if ((sc == NULL) || (sc->state & PSM_VALID) == 0) {
+		/* the device is no longer valid/functioning */
+		return (ENXIO);
+	}
+
+#ifdef EVDEV_SUPPORT
+	/* Still opened by evdev */
+	if (!(sc->state & (PSM_EV_OPEN_R | PSM_EV_OPEN_A)))
+#endif
+		err = psmclose(sc);
+
+	if (err == 0) {
+		sc->state &= ~PSM_OPEN;
+		device_unbusy(sc->dev);
+	}
+
+	return (err);
+}
+
+static int
+psmopen(struct psm_softc *sc)
+{
+	int command_byte;
+	int err;
 
 	/* Initialize state */
 	sc->mode.level = sc->dflt_mode.level;
@@ -1748,17 +2305,16 @@ psmopen(struct dev_open_args *ap)
 	lockmgr(&sc->lock, LK_EXCLUSIVE);
 	command_byte = get_controller_command_byte(sc->kbdc);
 
-	/* enable the aux port and temporalily disable the keyboard */
-	if (command_byte == -1 ||
-	    !set_controller_command_byte(sc->kbdc,
-		KBD_AUX_CONTROL_BITS,
+	/* enable the aux port and temporally disable the keyboard */
+	if (command_byte == -1 || !set_controller_command_byte(sc->kbdc,
+	    	kbdc_get_device_mask(sc->kbdc),
+	    	KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT |
 		KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 		/* CONTROLLER ERROR; do you know how to get out of this? */
 		kbdc_lock(sc->kbdc, FALSE);
 		lockmgr(&sc->lock, LK_RELEASE);
-		log(LOG_ERR,
-		    "psm%d: unable to set the command byte (psmopen).\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_ERR,
+		    "unable to set the command byte (psmopen).\n");
 		return (EIO);
 	}
 	/*
@@ -1774,18 +2330,13 @@ psmopen(struct dev_open_args *ap)
 	err = doopen(sc, command_byte);
 
 	/* done */
-	if (err == 0)
-		sc->state |= PSM_OPEN;
 	kbdc_lock(sc->kbdc, FALSE);
 	return (err);
 }
 
 static int
-psmclose(struct dev_close_args *ap)
+psmclose(struct psm_softc *sc)
 {
-
-	cdev_t dev = ap->a_head.a_dev;
-	struct psm_softc *sc = dev->si_drv1;
 	int stat[3];
 	int command_byte;
 
@@ -1802,13 +2353,13 @@ psmclose(struct dev_close_args *ap)
 		return (EIO);
 	}
 
-	/* disable the aux interrupt and temporalily disable the keyboard */
+	/* disable the aux interrupt and temporally disable the keyboard */
 	if (!set_controller_command_byte(sc->kbdc,
-		KBD_AUX_CONTROL_BITS,
-		KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
-		log(LOG_ERR,
-		    "psm%d: failed to disable the aux int (psmclose).\n",
-		    sc->unit);
+	    	kbdc_get_device_mask(sc->kbdc),
+	    	KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT |
+	    	KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
+		device_log(sc->dev, LOG_ERR,
+		    "failed to disable the aux int (psmclose).\n");
 		/* CONTROLLER ERROR;
 		 * NOTE: we shall force our way through. Because the only
 		 * ill effect we shall see is that we may not be able
@@ -1833,36 +2384,32 @@ psmclose(struct dev_close_args *ap)
 			 * It's OK because the interrupt routine will discard
 			 * any data from the mouse hereafter.
 			 */
-			log(LOG_ERR,
-			    "psm%d: failed to disable the device (psmclose).\n",
-			    sc->unit);
+			device_log(sc->dev, LOG_ERR,
+			    "failed to disable the device (psmclose).\n");
 		}
 
 		if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3)
-			log(LOG_DEBUG,
-			    "psm%d: failed to get status (psmclose).\n",
-			    sc->unit);
+			device_log(sc->dev, LOG_DEBUG,
+			    "failed to get status (psmclose).\n");
 	}
 
 	if (!set_controller_command_byte(sc->kbdc,
-		KBD_AUX_CONTROL_BITS,
-		KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
+	    	kbdc_get_device_mask(sc->kbdc),
+	    	(command_byte & KBD_KBD_CONTROL_BITS) |
+	    	KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 		/*
 		 * CONTROLLER ERROR;
 		 * we shall ignore this error; see the above comment.
 		 */
-		log(LOG_ERR,
-		    "psm%d: failed to disable the aux port (psmclose).\n",
-		    sc->unit);
+		device_log(sc->dev, LOG_ERR,
+		    "failed to disable the aux port (psmclose).\n");
 	}
 
 	/* remove anything left in the output buffer */
 	empty_aux_buffer(sc->kbdc, 10);
 
 	/* close is almost always successful */
-	sc->state &= ~PSM_OPEN;
 	kbdc_lock(sc->kbdc, FALSE);
-	device_unbusy(devclass_get_device(psm_devclass, sc->unit));
 	return (0);
 }
 
@@ -1998,10 +2545,10 @@ block_mouse_data(struct psm_softc *sc, int *c)
 
 	lockmgr(&sc->lock, LK_EXCLUSIVE);
 	*c = get_controller_command_byte(sc->kbdc);
-	if ((*c == -1) ||
-	    !set_controller_command_byte(sc->kbdc,
-		    KBD_AUX_CONTROL_BITS,
-		    KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
+	if ((*c == -1) || !set_controller_command_byte(sc->kbdc,
+	    kbdc_get_device_mask(sc->kbdc),
+	    KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT |
+	    KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) {
 		/* this is CONTROLLER ERROR */
 		lockmgr(&sc->lock, LK_RELEASE);
 		kbdc_lock(sc->kbdc, FALSE);
@@ -2064,7 +2611,8 @@ unblock_mouse_data(struct psm_softc *sc, int c)
 
 	/* restore ports and interrupt */
 	if (!set_controller_command_byte(sc->kbdc,
-		KBD_AUX_CONTROL_BITS, c & KBD_AUX_CONTROL_BITS)) {
+	    	kbdc_get_device_mask(sc->kbdc),
+	    	c & (KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS))) {
 		/*
 		 * CONTROLLER ERROR; this is serious, we may have
 		 * been left with the inaccessible keyboard and
@@ -2082,13 +2630,10 @@ static int
 psmioctl(struct dev_ioctl_args *ap)
 {
 	cdev_t dev = ap->a_head.a_dev;
-	caddr_t addr=  ap->a_data;
+	caddr_t addr = ap->a_data;
 	struct psm_softc *sc = dev->si_drv1;
 	mousemode_t mode;
 	mousestatus_t status;
-#if (defined(MOUSE_GETVARS))
-	mousevar_t *var;
-#endif
 	mousedata_t *data;
 	int stat[3];
 	int command_byte;
@@ -2133,6 +2678,9 @@ psmioctl(struct dev_ioctl_args *ap)
 			((old_mousemode_t *)addr)->protocol =
 			    MOUSE_PROTO_SYSMOUSE;
 			break;
+		case PSM_LEVEL_NATIVE:
+			((old_mousemode_t *)addr)->protocol = MOUSE_PROTO_PS2;
+			break;
 		}
 		((old_mousemode_t *)addr)->rate = sc->mode.rate;
 		((old_mousemode_t *)addr)->resolution = sc->mode.resolution;
@@ -2162,6 +2710,10 @@ psmioctl(struct dev_ioctl_args *ap)
 			((mousemode_t *)addr)->syncmask[0] = MOUSE_SYS_SYNCMASK;
 			((mousemode_t *)addr)->syncmask[1] = MOUSE_SYS_SYNC;
 			break;
+		case PSM_LEVEL_NATIVE:
+			/* FIXME: this isn't quite correct... XXX */
+			((mousemode_t *)addr)->protocol = MOUSE_PROTO_PS2;
+			break;
 		}
 		lockmgr(&sc->lock, LK_RELEASE);
 		break;
@@ -2277,21 +2829,6 @@ psmioctl(struct dev_ioctl_args *ap)
 		*(mousestatus_t *)addr = status;
 		break;
 
-#if (defined(MOUSE_GETVARS))
-	case MOUSE_GETVARS:
-		var = (mousevar_t *)addr;
-		bzero(var, sizeof(*var));
-		lockmgr(&sc->lock, LK_EXCLUSIVE);
-		var->var[0] = MOUSE_VARS_PS2_SIG;
-		var->var[1] = sc->config;
-		var->var[2] = sc->flags;
-		lockmgr(&sc->lock, LK_RELEASE);
-		break;
-
-	case MOUSE_SETVARS:
-		return (ENODEV);
-#endif /* MOUSE_GETVARS */
-
 	case MOUSE_READSTATE:
 	case MOUSE_READDATA:
 		data = (mousedata_t *)addr;
@@ -2401,13 +2938,12 @@ psmioctl(struct dev_ioctl_args *ap)
 static void
 psmtimeout(void *arg)
 {
-
 	struct psm_softc *sc;
 
 	sc = (struct psm_softc *)arg;
 	lockmgr(&sc->lock, LK_EXCLUSIVE);
 	if (sc->watchdog && kbdc_lock(sc->kbdc, TRUE)) {
-		VLOG(4, (LOG_DEBUG, "psm%d: lost interrupt?\n", sc->unit));
+		VDLOG(6, sc->dev, LOG_DEBUG, "lost interrupt?\n");
 		psmintr(sc);
 		kbdc_lock(sc->kbdc, FALSE);
 	}
@@ -2461,6 +2997,9 @@ SYSCTL_INT(_hw_psm, OID_AUTO, trackpoint_support, CTLFLAG_RD,
 SYSCTL_INT(_hw_psm, OID_AUTO, elantech_support, CTLFLAG_RD,
     &elantech_support, 0, "Enable support for Elantech touchpads");
 
+SYSCTL_INT(_hw_psm, OID_AUTO, mux_disabled, CTLFLAG_RD,
+    &mux_disabled, 0, "Disable active multiplexing");
+
 static void
 psmintr(void *arg)
 {
@@ -2469,13 +3008,16 @@ psmintr(void *arg)
 	int c;
 	packetbuf_t *pb;
 
+	if (aux_mux_is_enabled(sc->kbdc))
+		VLOG(2, (LOG_DEBUG, "psmintr: active multiplexing mode is not "
+		    "supported!\n"));
 
 	/* read until there is nothing to read */
 	while((c = read_aux_data_no_wait(sc->kbdc)) != -1) {
 		pb = &sc->pqueue[sc->pqueue_end];
 
 		/* discard the byte if the device is not open */
-		if ((sc->state & PSM_OPEN) == 0)
+		if (!(sc->state & (PSM_OPEN | PSM_EV_OPEN_R | PSM_EV_OPEN_A)))
 			continue;
 
 		getmicrouptime(&now);
@@ -2494,13 +3036,20 @@ psmintr(void *arg)
 		pb->ipacket[pb->inputbytes++] = c;
 		KKASSERT(pb->inputbytes <= sizeof(pb->ipacket));
 
-		if (pb->inputbytes < sc->mode.packetsize)
-			continue;
+		if (sc->mode.level == PSM_LEVEL_NATIVE) {
+			VLOG(4, (LOG_DEBUG, "psmintr: %02x\n", pb->ipacket[0]));
+			sc->syncerrors = 0;
+			sc->pkterrors = 0;
+			goto next;
+		} else {
+			if (pb->inputbytes < sc->mode.packetsize)
+				continue;
 
-		VLOG(4, (LOG_DEBUG,
-		    "psmintr: %02x %02x %02x %02x %02x %02x\n",
-		    pb->ipacket[0], pb->ipacket[1], pb->ipacket[2],
-		    pb->ipacket[3], pb->ipacket[4], pb->ipacket[5]));
+			VLOG(4, (LOG_DEBUG,
+		    	    "psmintr: %02x %02x %02x %02x %02x %02x\n",
+		    	    pb->ipacket[0], pb->ipacket[1], pb->ipacket[2],
+	    		    pb->ipacket[3], pb->ipacket[4], pb->ipacket[5]));
+		}
 
 		c = pb->ipacket[0];
 
@@ -2509,8 +3058,9 @@ psmintr(void *arg)
 			sc->flags &= ~PSM_NEED_SYNCBITS;
 			VLOG(2, (LOG_DEBUG,
 			    "psmintr: Sync bytes now %04x,%04x\n",
-			    sc->mode.syncmask[0], sc->mode.syncmask[0]));
-		} else if ((c & sc->mode.syncmask[0]) != sc->mode.syncmask[1]) {
+			    sc->mode.syncmask[0], sc->mode.syncmask[1]));
+		} else if ((sc->config & PSM_CONFIG_NOCHECKSYNC) == 0 &&
+		    (c & sc->mode.syncmask[0]) != sc->mode.syncmask[1]) {
 			VLOG(3, (LOG_DEBUG, "psmintr: out of sync "
 			    "(%04x != %04x) %d cmds since last error.\n",
 			    c & sc->mode.syncmask[0], sc->mode.syncmask[1],
@@ -2518,11 +3068,16 @@ psmintr(void *arg)
 			sc->lasterr = sc->cmdcount;
 			/*
 			 * The sync byte test is a weak measure of packet
-			 * validity.
+			 * validity.  Conservatively discard any input yet
+			 * to be seen by userland when we detect a sync
+			 * error since there is a good chance some of
+			 * the queued packets have undetected errors.
 			 */
+			dropqueue(sc);
 			if (sc->syncerrors == 0)
 				sc->pkterrors++;
 			++sc->syncerrors;
+			sc->lastinputerr = now;
 			if (sc->syncerrors >= sc->mode.packetsize * 2 ||
 			    sc->pkterrors >= pkterrthresh) {
 				/*
@@ -2532,16 +3087,9 @@ psmintr(void *arg)
 				 * validation period, reinitialize the mouse
 				 * in hopes of returning it to the expected
 				 * mode.
-				 *
-				 * reset inputbytes in case reinitialize()
-				 * fails to prevent it from incrementing
-				 * forever.
 				 */
 				VLOG(3, (LOG_DEBUG,
 				    "psmintr: reset the mouse.\n"));
-				sc->lastinputerr = now;
-				dropqueue(sc);
-				pb->inputbytes = 0;
 				reinitialize(sc, TRUE);
 			} else if (sc->syncerrors == sc->mode.packetsize) {
 				/*
@@ -2550,8 +3098,6 @@ psmintr(void *arg)
 				 */
 				VLOG(3, (LOG_DEBUG,
 				    "psmintr: re-enable the mouse.\n"));
-				sc->lastinputerr = now;
-				dropqueue(sc);
 				pb->inputbytes = 0;
 				disable_aux_dev(sc->kbdc);
 				enable_aux_dev(sc->kbdc);
@@ -2592,13 +3138,18 @@ psmintr(void *arg)
 		sc->pkterrors = 0;
 
 		sc->cmdcount++;
+next:
 		if (++sc->pqueue_end >= PSM_PACKETQUEUE)
 			sc->pqueue_end = 0;
 		/*
 		 * If we've filled the queue then call the softintr ourselves,
 		 * otherwise schedule the interrupt for later.
+		 * Do not postpone interrupts for absolute devices as it
+		 * affects tap detection timings.
 		 */
-		if (!timeelapsed(&sc->lastsoftintr, psmsecs, psmusecs, &now) ||
+		if (sc->hw.model == MOUSE_MODEL_SYNAPTICS ||
+		    sc->hw.model == MOUSE_MODEL_ELANTECH ||
+		    !timeelapsed(&sc->lastsoftintr, psmsecs, psmusecs, &now) ||
 		    (sc->pqueue_end == sc->pqueue_start)) {
 			if ((sc->state & PSM_SOFTARMED) != 0) {
 				sc->state &= ~PSM_SOFTARMED;
@@ -2607,8 +3158,8 @@ psmintr(void *arg)
 			psmsoftintr(arg);
 		} else if ((sc->state & PSM_SOFTARMED) == 0) {
 			sc->state |= PSM_SOFTARMED;
-			callout_reset(&sc->softcallout,  psmhz < 1 ? 1 : (hz/psmhz),
-			      psmsoftintr, arg);
+			callout_reset(&sc->softcallout,
+			    psmhz < 1 ? 1 : (hz/psmhz), psmsoftintr, arg);
 		}
 	}
 }
@@ -2619,7 +3170,7 @@ proc_mmanplus(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 {
 
 	/*
-	 * PS2++ protocl packet
+	 * PS2++ protocol packet
 	 *
 	 *          b7 b6 b5 b4 b3 b2 b1 b0
 	 * byte 1:  *  1  p3 p2 1  *  *  *
@@ -2691,14 +3242,8 @@ proc_mmanplus(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 				*z = (pb->ipacket[2] & MOUSE_SPOINT_ZNEG) ?
 				    -1 : 1;
 			break;
-		case 0:
-			/* device type packet - shouldn't happen
-			 *
-			 * XXX: if this shouldn't happen, shouldn't we at least
-			 * add a kprintf indiciating to the user something bad
-			 * happened or is it that prevalent that it'd spam? -htse
-			 *
-			 * */
+		case 0:
+			/* device type packet - shouldn't happen */
 			/* FALLTHROUGH */
 		default:
 			*x = *y = 0;
@@ -2721,8 +3266,9 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 {
 	static int touchpad_buttons;
 	static int guest_buttons;
+	static int ew_finger_count;
 	static finger_t f[PSM_FINGERS];
-	int w, id, nfingers, ewcode, extended_buttons;
+	int w, id, nfingers, palm, ewcode, extended_buttons, clickpad_pressed;
 
 	extended_buttons = 0;
 
@@ -2733,7 +3279,7 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	 *  ipacket[0]:  1   0  W3  W2   0  W1   R   L
 	 *  ipacket[1]: Yb  Ya  Y9  Y8  Xb  Xa  X9  X8
 	 *  ipacket[2]: Z7  Z6  Z5  Z4  Z3  Z2  Z1  Z0
-	 *  ipacket[3]:  1   1  Yc  Xc   0  W0   D   U
+	 *  ipacket[3]:  1   1  Yc  Xc   0  W0 D^R U^L
 	 *  ipacket[4]: X7  X6  X5  X4  X3  X2  X1  X0
 	 *  ipacket[5]: Y7  Y6  Y5  Y4  Y3  Y2  Y1  Y0
 	 *
@@ -2747,7 +3293,6 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	 *  Y: y position
 	 *  Z: pressure
 	 *
-	 *
 	 * Without capFourButtons but with nExtendeButtons and/or capMiddle
 	 *
 	 *  Bits:        7   6   5   4      3      2      1      0 (LSB)
@@ -2824,7 +3369,7 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		 * Handle packets from the guest device. See:
 		 * Synaptics PS/2 TouchPad Interfacing Guide, Section 5.1
 		 */
-		if (sc->synhw.capPassthrough) {
+		if (sc->synhw.capPassthrough || sc->muxport != PSM_NOMUX) {
 			*x = ((pb->ipacket[1] & 0x10) ?
 			    pb->ipacket[4] - 256 : pb->ipacket[4]);
 			*y = ((pb->ipacket[1] & 0x20) ?
@@ -2838,6 +3383,15 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 				guest_buttons |= MOUSE_BUTTON2DOWN;
 			if (pb->ipacket[1] & 0x02)
 				guest_buttons |= MOUSE_BUTTON3DOWN;
+#ifdef EVDEV_SUPPORT
+			if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+				evdev_push_rel(sc->evdev_r, REL_X, *x);
+				evdev_push_rel(sc->evdev_r, REL_Y, -*y);
+				evdev_push_mouse_btn(sc->evdev_r,
+				    guest_buttons | sc->extended_buttons);
+				evdev_sync(sc->evdev_r);
+			}
+#endif
 
 			ms->button = touchpad_buttons | guest_buttons |
 			    sc->extended_buttons;
@@ -2875,6 +3429,9 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 					    (pb->ipacket[1] & 0x01)) + 8,
 					.flags = PSM_FINGER_FUZZY,
 				};
+			break;
+		case 2:
+			ew_finger_count = pb->ipacket[1] & 0x0f;
 		default:
 			break;
 		}
@@ -2882,6 +3439,11 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		goto SYNAPTICS_END;
 
 	case 1:
+		if (sc->synhw.capReportsV && ew_finger_count > 3) {
+			nfingers = ew_finger_count;
+			break;
+		}
+		/* FALLTHROUGH */
 	case 0:
 		nfingers = w + 2;
 		break;
@@ -2949,6 +3511,24 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 			int mask = 0;
 			maskedbits = (sc->synhw.nExtendedButtons + 1) >> 1;
 			mask = (1 << maskedbits) - 1;
+#ifdef EVDEV_SUPPORT
+			int i;
+			if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+				if (sc->synhw.capPassthrough) {
+					evdev_push_mouse_btn(sc->evdev_r,
+						extended_buttons);
+					evdev_sync(sc->evdev_r);
+				}
+				for (i = 0; i < maskedbits; i++) {
+					evdev_push_key(sc->evdev_a,
+					    BTN_0 + i * 2,
+					    pb->ipacket[4] & (1 << i));
+					evdev_push_key(sc->evdev_a,
+					    BTN_0 + i * 2 + 1,
+					    pb->ipacket[5] & (1 << i));
+				}
+			}
+#endif
 			pb->ipacket[4] &= ~(mask);
 			pb->ipacket[5] &= ~(mask);
 		} else  if (!sc->syninfo.directional_scrolls &&
@@ -2960,10 +3540,6 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 			extended_buttons |= sc->extended_buttons;
 		}
 	}
-	/* Handle ClickPad */
-	if (sc->synhw.capClickPad &&
-	    ((pb->ipacket[0] ^ pb->ipacket[3]) & 0x01))
-		touchpad_buttons |= MOUSE_BUTTON1DOWN;
 
 	if (sc->synhw.capReportsV && nfingers > 1)
 		f[0] = (finger_t) {
@@ -2996,19 +3572,77 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	if (f[0].p < sc->syninfo.min_pressure || f[0].x < 2)
 		nfingers = 0;
 
+	/* Handle ClickPad */
+	if (sc->synhw.capClickPad) {
+		clickpad_pressed = (pb->ipacket[0] ^ pb->ipacket[3]) & 0x01;
+		if (sc->synhw.forcePad) {
+			/*
+			 * Forcepads erroneously report button click if there
+			 * are 2 or more fingers on the touchpad breaking
+			 * multifinger gestures. To workaround this start
+			 * reporting a click only after 4 consecutive single
+			 * touch packets has been received.
+			 * Skip these packets in case more contacts appear.
+			 */
+			switch (nfingers) {
+			case 0:
+				sc->fpcount = 0;
+				break;
+			case 1:
+				if (clickpad_pressed && sc->fpcount < INT_MAX)
+					++sc->fpcount;
+				/* FALLTHROUGH */
+			default:
+				if (!clickpad_pressed)
+					sc->fpcount = 0;
+				if (sc->fpcount >= sc->syninfo.window_min)
+					touchpad_buttons |= MOUSE_BUTTON1DOWN;
+			}
+		} else if (clickpad_pressed)
+			touchpad_buttons |= MOUSE_BUTTON1DOWN;
+	}
+
 	for (id = 0; id < PSM_FINGERS; id++)
 		if (id >= nfingers)
 			PSM_FINGER_RESET(f[id]);
 
+#ifdef EVDEV_SUPPORT
+	if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+		for (id = 0; id < PSM_FINGERS; id++)
+			if (PSM_FINGER_IS_SET(f[id]))
+				psm_push_mt_finger(sc, id, &f[id]);
+		evdev_push_key(sc->evdev_a, BTN_TOUCH, nfingers > 0);
+		evdev_push_nfingers(sc->evdev_a, nfingers);
+		if (nfingers > 0)
+			psm_push_st_finger(sc, &f[0]);
+		else
+			evdev_push_abs(sc->evdev_a, ABS_PRESSURE, 0);
+		evdev_push_mouse_btn(sc->evdev_a, touchpad_buttons);
+		if (sc->synhw.capExtended && sc->synhw.capFourButtons) {
+			evdev_push_key(sc->evdev_a, BTN_FORWARD,
+			    touchpad_buttons & MOUSE_BUTTON4DOWN);
+			evdev_push_key(sc->evdev_a, BTN_BACK,
+			    touchpad_buttons & MOUSE_BUTTON5DOWN);
+		}
+		evdev_sync(sc->evdev_a);
+	}
+#endif
+
 	ms->button = touchpad_buttons;
 
+	palm = psmpalmdetect(sc, &f[0], nfingers);
+
 	/* Palm detection doesn't terminate the current action. */
-	if (!psmpalmdetect(sc, &f[0], nfingers)) {
+	if (!palm)
 		psmgestures(sc, &f[0], nfingers, ms);
-		for (id = 0; id < PSM_FINGERS; id++)
-			psmsmoother(sc, &f[id], id, ms, x, y);
-	} else {
-		VLOG(2, (LOG_DEBUG, "synaptics: palm detected! (%d)\n", f[0].w));
+
+	for (id = 0; id < PSM_FINGERS; id++)
+		psmsmoother(sc, &f[id], id, ms, x, y);
+
+	if (palm) {
+		*x = *y = *z = 0;
+		ms->button = ms->obutton;
+		return (0);
 	}
 
 	ms->button |= extended_buttons | guest_buttons;
@@ -3030,15 +3664,15 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	 * That's why the horizontal wheel is disabled by
 	 * default for now.
 	 */
-	if (ms->button & MOUSE_BUTTON4DOWN) {
+	if (ms->button & MOUSE_BUTTON4DOWN)
 		*z = -1;
-	} else if (ms->button & MOUSE_BUTTON5DOWN) {
+	else if (ms->button & MOUSE_BUTTON5DOWN)
 		*z = 1;
-	} else if (ms->button & MOUSE_BUTTON6DOWN) {
+	else if (ms->button & MOUSE_BUTTON6DOWN)
 		*z = -2;
-	} else if (ms->button & MOUSE_BUTTON7DOWN) {
+	else if (ms->button & MOUSE_BUTTON7DOWN)
 		*z = 2;
-	} else
+	else
 		*z = 0;
 	ms->button &= ~(MOUSE_BUTTON4DOWN | MOUSE_BUTTON5DOWN |
 	    MOUSE_BUTTON6DOWN | MOUSE_BUTTON7DOWN);
@@ -3046,12 +3680,90 @@ proc_synaptics(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	return (0);
 }
 
+static int
+proc_synaptics_mux(struct psm_softc *sc, packetbuf_t *pb)
+{
+	int butt;
+
+	/*
+	 * Convert 3-byte interleaved mixture of Synaptics and generic mouse
+	 * packets into plain 6-byte Synaptics packet protocol.
+	 * While in hidden multiplexing mode KBC does some editing of the
+	 * packet stream. It remembers the button bits from the last packet
+	 * received from each device, and replaces the button bits of every
+	 * packet with the logical OR of all devices’ most recent button bits.
+	 * This button crosstalk should be filtered out as Synaptics and
+	 * generic mouse encode middle button presses in a different way.
+	 */
+	switch (pb->ipacket[0] & 0xc0) {
+	case 0x80:	/* First 3 bytes of Synaptics packet */
+		bcopy(pb->ipacket, sc->muxsave, 3);
+		/* Compute middle mouse button supression timeout. */
+		sc->muxmidtimeout.tv_sec  = 0;
+		sc->muxmidtimeout.tv_usec = 50000;	/* ~2-3 ints */
+		timevaladd(&sc->muxmidtimeout, &sc->lastsoftintr);
+		return (1);
+
+	case 0xc0:	/* Second 3 bytes of Synaptics packet */
+		/* Join two 3-bytes absolute packets */
+		bcopy(pb->ipacket, pb->ipacket + 3, 3);
+		bcopy(sc->muxsave, pb->ipacket, 3);
+		/* Prefer trackpoint buttons over touchpad's */
+		pb->ipacket[0] &= ~(0x08 | sc->muxmsbuttons);
+		pb->ipacket[3] &= ~(0x08 | sc->muxmsbuttons);
+		butt = (pb->ipacket[3] & 0x03) << 2 | (pb->ipacket[0] & 0x03);
+		/* Add hysteresis to remove spurious middle button events */
+		if (butt != sc->muxtpbuttons && sc->fpcount < 1) {
+			pb->ipacket[0] &= 0xfc;
+			pb->ipacket[0] |= sc->muxtpbuttons & 0x03;
+			pb->ipacket[3] &= 0xfc;
+			pb->ipacket[3] |= sc->muxtpbuttons >> 2 & 0x03;
+			++sc->fpcount;
+		} else {
+			sc->fpcount = 0;
+			sc->muxtpbuttons = butt;
+		}
+		/* Filter out impossible w induced by middle trackpoint btn */
+		if (sc->synhw.capExtended && !sc->synhw.capPassthrough &&
+		    (pb->ipacket[0] & 0x34) == 0x04 &&
+		    (pb->ipacket[3] & 0x04) == 0x04) {
+			pb->ipacket[0] &= 0xfb;
+			pb->ipacket[3] &= 0xfb;
+		}
+		sc->muxsave[0] &= 0x30;
+		break;
+
+	default:	/* Generic mouse (Trackpoint) packet */
+		/* Filter out middle button events induced by some w values */
+		if (sc->muxmsbuttons & 0x03 || pb->ipacket[0] & 0x03 ||
+		    (timevalcmp(&sc->lastsoftintr, &sc->muxmidtimeout, <=) &&
+		     (sc->muxsave[0] & 0x30 || sc->muxsave[2] > 8)))
+			pb->ipacket[0] &= 0xfb;
+		sc->muxmsbuttons = pb->ipacket[0] & 0x07;
+		/* Convert to Synaptics pass-through protocol */
+		pb->ipacket[4] = pb->ipacket[1];
+		pb->ipacket[5] = pb->ipacket[2];
+		pb->ipacket[1] = pb->ipacket[0];
+		pb->ipacket[2] = 0;
+		pb->ipacket[0] = 0x84 | (sc->muxtpbuttons & 0x03);
+		pb->ipacket[3] = 0xc4 | (sc->muxtpbuttons >> 2 & 0x03);
+	}
+
+	VLOG(4, (LOG_DEBUG, "synaptics: %02x %02x %02x %02x %02x %02x\n",
+	    pb->ipacket[0], pb->ipacket[1], pb->ipacket[2],
+	    pb->ipacket[3], pb->ipacket[4], pb->ipacket[5]));
+
+	pb->inputbytes = MOUSE_SYNAPTICS_PACKETSIZE;
+	return (0);
+}
+
 static int
 psmpalmdetect(struct psm_softc *sc, finger_t *f, int nfingers)
 {
 	if (!(
-	    ((sc->synhw.capMultiFinger ||
-	      sc->synhw.capAdvancedGestures) && nfingers > 1) ||
+	    ((sc->synhw.capMultiFinger || sc->synhw.capAdvancedGestures) &&
+	      !sc->synhw.capReportsV && nfingers > 1) ||
+	    (sc->synhw.capReportsV && nfingers > 2) ||
 	    (sc->synhw.capPalmDetect && f->w <= sc->syninfo.max_width) ||
 	    (!sc->synhw.capPalmDetect && f->p <= sc->syninfo.max_pressure) ||
 	    (sc->synhw.capPen && f->flags & PSM_FINGER_IS_PEN))) {
@@ -3064,6 +3776,7 @@ psmpalmdetect(struct psm_softc *sc, finger_t *f, int nfingers)
 		 *    [min_pressure; max_pressure]
 		 *  - pen aren't supported but PSM_FINGER_IS_PEN is set
 		 */
+		VLOG(2, (LOG_DEBUG, "synaptics: palm detected! (%d)\n", f->w));
 		return (1);
 	}
 	return (0);
@@ -3107,9 +3820,7 @@ psmgestures(struct psm_softc *sc, finger_t *fingers, int nfingers,
 		int vscroll_hor_area, vscroll_ver_area;
 		int two_finger_scroll;
 		int max_x, max_y;
-
-		/* silence gcc warnings for last VLOG call */
-		dxp = dyp = 0;
+		int three_finger_drag;
 
 		/* Read sysctl. */
 		/* XXX Verify values? */
@@ -3124,7 +3835,7 @@ psmgestures(struct psm_softc *sc, finger_t *fingers, int nfingers,
 		two_finger_scroll = sc->syninfo.two_finger_scroll;
 		max_x = sc->syninfo.max_x;
 		max_y = sc->syninfo.max_y;
-
+		three_finger_drag = sc->syninfo.three_finger_drag;
 		/* Read current absolute position. */
 		x0 = f->x;
 		y0 = f->y;
@@ -3165,9 +3876,15 @@ psmgestures(struct psm_softc *sc, finger_t *fingers, int nfingers,
 			gest->in_vscroll = 0;
 
 			/* Compute tap timeout. */
-			gest->taptimeout.tv_sec  = tap_timeout / 1000000;
-			gest->taptimeout.tv_usec = tap_timeout % 1000000;
-			timevaladd(&gest->taptimeout, &sc->lastsoftintr);
+			if (tap_enabled != 0) {
+				gest->taptimeout = (struct timeval) {
+					.tv_sec  = tap_timeout / 1000000,
+					.tv_usec = tap_timeout % 1000000,
+				};
+				timevaladd(
+				    &gest->taptimeout, &sc->lastsoftintr);
+			} else
+				timevalclear(&gest->taptimeout);
 
 			sc->flags |= PSM_FLAGS_FINGERDOWN;
 
@@ -3185,7 +3902,7 @@ psmgestures(struct psm_softc *sc, finger_t *fingers, int nfingers,
 		if (sc->synhw.capClickPad && ms->button & MOUSE_BUTTON1DOWN) {
 			y_ok = sc->syninfo.softbuttons_y >= 0 ?
 			    start_y < sc->syninfo.softbuttons_y :
-			    start_y > max_y - sc->syninfo.softbuttons_y;
+			    start_y > max_y + sc->syninfo.softbuttons_y;
 
 			center_button = MOUSE_BUTTON2DOWN;
 			center_x = sc->syninfo.softbutton2_x;
@@ -3207,8 +3924,8 @@ psmgestures(struct psm_softc *sc, finger_t *fingers, int nfingers,
 				    ~MOUSE_BUTTON1DOWN) | center_button;
 		}
 
-		/* If in tap-hold, add the recorded button. */
-		if (gest->in_taphold)
+		/* If in tap-hold or three fingers, add the recorded button. */
+		if (gest->in_taphold || (nfingers == 3 && three_finger_drag))
 			ms->button |= gest->tap_button;
 
 		/*
@@ -3225,6 +3942,9 @@ psmgestures(struct psm_softc *sc, finger_t *fingers, int nfingers,
 		if (queue_len < gest->window_min)
 			return;
 
+		dyp = -1;
+		dxp = -1;
+
 		/* Is a scrolling action occurring? */
 		if (!gest->in_taphold && !ms->button &&
 		    (!gest->in_vscroll || two_finger_scroll)) {
@@ -3440,24 +4160,21 @@ psmsmoother(struct psm_softc *sc, finger_t *f, int smoother_id,
 		int x0, y0;
 		int cursor, peer, window;
 		int dx, dy, dxp, dyp;
-		int max_width, max_pressure;
 		int margin_top, margin_right, margin_bottom, margin_left;
 		int na_top, na_right, na_bottom, na_left;
-		int window_min, window_max;
+		int window_max;
 		int multiplicator;
 		int weight_current, weight_previous, weight_len_squared;
 		int div_min, div_max, div_len;
-		int vscroll_hor_area, vscroll_ver_area;
 		int two_finger_scroll;
 		int max_x, max_y;
 		int len, weight_prev_x, weight_prev_y;
 		int div_max_x, div_max_y, div_x, div_y;
 		int is_fuzzy;
+		int natural_scroll;
 
 		/* Read sysctl. */
 		/* XXX Verify values? */
-		max_width = sc->syninfo.max_width;
-		max_pressure = sc->syninfo.max_pressure;
 		margin_top = sc->syninfo.margin_top;
 		margin_right = sc->syninfo.margin_right;
 		margin_bottom = sc->syninfo.margin_bottom;
@@ -3466,7 +4183,6 @@ psmsmoother(struct psm_softc *sc, finger_t *f, int smoother_id,
 		na_right = sc->syninfo.na_right;
 		na_bottom = sc->syninfo.na_bottom;
 		na_left = sc->syninfo.na_left;
-		window_min = sc->syninfo.window_min;
 		window_max = sc->syninfo.window_max;
 		multiplicator = sc->syninfo.multiplicator;
 		weight_current = sc->syninfo.weight_current;
@@ -3475,11 +4191,10 @@ psmsmoother(struct psm_softc *sc, finger_t *f, int smoother_id,
 		div_min = sc->syninfo.div_min;
 		div_max = sc->syninfo.div_max;
 		div_len = sc->syninfo.div_len;
-		vscroll_hor_area = sc->syninfo.vscroll_hor_area;
-		vscroll_ver_area = sc->syninfo.vscroll_ver_area;
 		two_finger_scroll = sc->syninfo.two_finger_scroll;
 		max_x = sc->syninfo.max_x;
 		max_y = sc->syninfo.max_y;
+		natural_scroll = sc->syninfo.natural_scroll;
 
 		is_fuzzy = (f->flags & PSM_FINGER_FUZZY) != 0;
 
@@ -3641,14 +4356,24 @@ psmsmoother(struct psm_softc *sc, finger_t *f, int smoother_id,
 			    smoother_id, dx, dy, dxp, dyp));
 			break;
 		case 1: /* Vertical scrolling. */
-			if (dyp != 0)
-				ms->button |= (dyp > 0) ?
-				    MOUSE_BUTTON4DOWN : MOUSE_BUTTON5DOWN;
+			if (dyp != 0) {
+				if (two_finger_scroll && natural_scroll)
+					ms->button |= (dyp > 0) ?
+					    MOUSE_BUTTON5DOWN : MOUSE_BUTTON4DOWN;
+				else
+					ms->button |= (dyp > 0) ?
+					    MOUSE_BUTTON4DOWN : MOUSE_BUTTON5DOWN;
+			}
 			break;
 		case 2: /* Horizontal scrolling. */
-			if (dxp != 0)
-				ms->button |= (dxp > 0) ?
-				    MOUSE_BUTTON7DOWN : MOUSE_BUTTON6DOWN;
+			if (dxp != 0) {
+				if (two_finger_scroll && natural_scroll)
+					ms->button |= (dxp > 0) ?
+					    MOUSE_BUTTON6DOWN : MOUSE_BUTTON7DOWN;
+				else
+					ms->button |= (dxp > 0) ?
+					    MOUSE_BUTTON7DOWN : MOUSE_BUTTON6DOWN;
+			}
 			break;
 		}
 
@@ -3670,7 +4395,7 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 {
 	static int touchpad_button, trackpoint_button;
 	finger_t fn, f[ELANTECH_MAX_FINGERS];
-	int pkt, id, scale, i, nfingers, mask;
+	int pkt, id, scale, i, nfingers, mask, palm;
 
 	if (!elantech_support)
 		return (0);
@@ -3678,7 +4403,7 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	/* Determine packet format and do a sanity check for out of sync packets. */
 	if (ELANTECH_PKT_IS_DEBOUNCE(pb, sc->elanhw.hwversion))
 		pkt = ELANTECH_PKT_NOP;
-	else if (ELANTECH_PKT_IS_TRACKPOINT(pb))
+	else if (sc->elanhw.hastrackpoint && ELANTECH_PKT_IS_TRACKPOINT(pb))
 		pkt = ELANTECH_PKT_TRACKPOINT;
 	else
 	switch (sc->elanhw.hwversion) {
@@ -3726,7 +4451,7 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	*x = *y = *z = 0;
 	ms->button = ms->obutton;
 
-	if (sc->syninfo.touchpad_off)
+	if (sc->syninfo.touchpad_off && pkt != ELANTECH_PKT_TRACKPOINT)
 		return (0);
 
 	/* Common legend
@@ -3758,27 +4483,30 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		nfingers = (pb->ipacket[0] & 0xc0) >> 6;
 		if (nfingers == 3 && (pb->ipacket[3] & 0x80))
 			nfingers = 4;
-		mask = (1 << nfingers) - 1;
 
-		fn = ELANTECH_FINGER_SET_XYP(pb);
+		if (nfingers == 0) {
+			mask = (1 << nfingers) - 1;	/* = 0x00 */
+			break;
+		}
+
+		/* Map 3-rd and 4-th fingers to first finger */
+		mask = (1 << 1) - 1;	/* = 0x01 */
+		f[0] = ELANTECH_FINGER_SET_XYP(pb);
 		if (sc->elanhw.haspressure) {
-			fn.w = ((pb->ipacket[0] & 0x30) >> 2) |
+			f[0].w = ((pb->ipacket[0] & 0x30) >> 2) |
 			    ((pb->ipacket[3] & 0x30) >> 4);
 		} else {
-			fn.p = PSM_FINGER_DEFAULT_P;
-			fn.w = PSM_FINGER_DEFAULT_W;
+			f[0].p = PSM_FINGER_DEFAULT_P;
+			f[0].w = PSM_FINGER_DEFAULT_W;
 		}
 
 		/*
 		 * HW v2 dont report exact finger positions when 3 or more
-		 * fingers are on touchpad. Use reported value as fingers
-		 * position as it is required for tap detection
+		 * fingers are on touchpad.
 		 */
 		if (nfingers > 2)
-			fn.flags = PSM_FINGER_FUZZY;
+			f[0].flags = PSM_FINGER_FUZZY;
 
-		for (id = 0; id < imin(nfingers, ELANTECH_MAX_FINGERS); id++)
-			f[id] = fn;
 		break;
 
 	case ELANTECH_PKT_V2_2FINGER:   /*HW V2. Two finger touch */
@@ -3824,8 +4552,12 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		 * -------------------------------------------
 		 */
 		nfingers = (pb->ipacket[0] & 0xc0) >> 6;
-		mask = (1 << nfingers) - 1;
-		id = nfingers - 1;
+		/* Map 3-rd finger to first finger */
+		id = nfingers > 2 ? 0 : nfingers - 1;
+		mask = (1 << (id + 1)) - 1;
+
+		if (nfingers == 0)
+			break;
 
 		fn = ELANTECH_FINGER_SET_XYP(pb);
 		fn.w = ((pb->ipacket[0] & 0x30) >> 2) |
@@ -3833,15 +4565,11 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 
 		/*
 		 * HW v3 dont report exact finger positions when 3 or more
-		 * fingers are on touchpad. Use reported value as fingers
-		 * position as it is required for tap detection
+		 * fingers are on touchpad.
 		 */
 		if (nfingers > 1)
 			fn.flags = PSM_FINGER_FUZZY;
 
-		for (id = 0; id < imin(nfingers, ELANTECH_MAX_FINGERS); id++)
-			f[id] = fn;
-
 		if (nfingers == 2) {
 			if (ELANTECH_PKT_IS_V3_HEAD(pb, sc->elanhw.hascrc)) {
 				sc->elanaction.fingers[0] = fn;
@@ -3849,6 +4577,7 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 			} else
 				f[0] = sc->elanaction.fingers[0];
 		}
+		f[id] = fn;
 		break;
 
 	case ELANTECH_PKT_V4_STATUS:    /* HW Version 4. Status packet */
@@ -3870,9 +4599,15 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		mask = pb->ipacket[1] & 0x1f;
 		nfingers = bitcount32(mask);
 
+		if (sc->elanaction.mask_v4wait != 0)
+			VLOG(3, (LOG_DEBUG, "elantech: HW v4 status packet"
+			    " when not all previous head packets received\n"));
+
+		/* Bitmap of fingers to receive before gesture processing */
+		sc->elanaction.mask_v4wait = mask & ~sc->elanaction.mask;
+
 		/* Skip "new finger is on touchpad" packets */
-		if ((sc->elanaction.mask & mask) == sc->elanaction.mask &&
-		    (mask & ~sc->elanaction.mask)) {
+		if (sc->elanaction.mask_v4wait) {
 			sc->elanaction.mask = mask;
 			return (0);
 		}
@@ -3897,11 +4632,33 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		mask = sc->elanaction.mask;
 		nfingers = bitcount32(mask);
 		id = ((pb->ipacket[3] & 0xe0) >> 5) - 1;
+		fn = ELANTECH_FINGER_SET_XYP(pb);
+		fn.w = (pb->ipacket[0] & 0xf0) >> 4;
+
+		if (id < 0)
+			return (0);
 
-		if (id >= 0 && id < ELANTECH_MAX_FINGERS) {
-			f[id] = ELANTECH_FINGER_SET_XYP(pb);
-			f[id].w = (pb->ipacket[0] & 0xf0) >> 4;
+		/* Packet is finger position update. Report it */
+		if (sc->elanaction.mask_v4wait == 0) {
+			if (id < ELANTECH_MAX_FINGERS)
+				f[id] = fn;
+			break;
 		}
+
+		/* Remove finger from waiting bitmap and store into context */
+		sc->elanaction.mask_v4wait &= ~(1 << id);
+		if (id < ELANTECH_MAX_FINGERS)
+			sc->elanaction.fingers[id] = fn;
+
+		/* Wait for other fingers if needed */
+		if (sc->elanaction.mask_v4wait != 0)
+			return (0);
+
+		/* All new fingers are received. Report them from context */
+		for (id = 0; id < ELANTECH_MAX_FINGERS; id++)
+			if (sc->elanaction.mask & (1 << id))
+				f[id] =  sc->elanaction.fingers[id];
+
 		break;
 
 	case ELANTECH_PKT_V4_MOTION:    /* HW Version 4. Motion packet */
@@ -3948,7 +4705,7 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	case ELANTECH_PKT_TRACKPOINT:
 		/*               7   6   5   4   3   2   1   0 (LSB)
 		 * -------------------------------------------
-		 * ipacket[0]:   0   0  SX  SY   0   M   R   L
+		 * ipacket[0]:   0   0  SY  SX   0   M   R   L
 		 * ipacket[1]: ~SX   0   0   0   0   0   0   0
 		 * ipacket[2]: ~SY   0   0   0   0   0   0   0
 		 * ipacket[3]:   0   0 ~SY ~SX   0   1   1   0
@@ -3959,17 +4716,32 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		 * over 9 bits with SX/SY the relative top bit and
 		 * X7..X0 and Y7..Y0 the lower bits.
 		 */
-		*x = (pb->ipacket[0] & 0x20) ?
-		    pb->ipacket[4] - 256 : pb->ipacket[4];
-		*y = (pb->ipacket[0] & 0x10) ?
-		    pb->ipacket[5] - 256 : pb->ipacket[5];
-
-		trackpoint_button =
-		    ((pb->ipacket[0] & 0x01) ? MOUSE_BUTTON1DOWN : 0) |
-		    ((pb->ipacket[0] & 0x02) ? MOUSE_BUTTON3DOWN : 0) |
-		    ((pb->ipacket[0] & 0x04) ? MOUSE_BUTTON2DOWN : 0);
-
-		ms->button = touchpad_button | trackpoint_button;
+		if (!(pb->ipacket[0] & 0xC8) && !(pb->ipacket[1] & 0x7F) &&
+		    !(pb->ipacket[2] & 0x7F) && !(pb->ipacket[3] & 0xC9) &&
+		    !(pb->ipacket[0] & 0x10) != !(pb->ipacket[1] & 0x80) &&
+		    !(pb->ipacket[0] & 0x10) != !(pb->ipacket[3] & 0x10) &&
+		    !(pb->ipacket[0] & 0x20) != !(pb->ipacket[2] & 0x80) &&
+		    !(pb->ipacket[0] & 0x20) != !(pb->ipacket[3] & 0x20)) {
+
+			*x = (pb->ipacket[0] & MOUSE_PS2_XNEG) ?
+			    pb->ipacket[4] - 256 : pb->ipacket[4];
+			*y = (pb->ipacket[0] & MOUSE_PS2_YNEG) ?
+			    pb->ipacket[5] - 256 : pb->ipacket[5];
+
+			trackpoint_button =
+			    ((pb->ipacket[0] & 0x01) ? MOUSE_BUTTON1DOWN : 0) |
+			    ((pb->ipacket[0] & 0x02) ? MOUSE_BUTTON3DOWN : 0) |
+			    ((pb->ipacket[0] & 0x04) ? MOUSE_BUTTON2DOWN : 0);
+#ifdef EVDEV_SUPPORT
+			evdev_push_rel(sc->evdev_r, REL_X, *x);
+			evdev_push_rel(sc->evdev_r, REL_Y, -*y);
+			evdev_push_mouse_btn(sc->evdev_r, trackpoint_button);
+			evdev_sync(sc->evdev_r);
+#endif
+			ms->button = touchpad_button | trackpoint_button;
+		} else
+			VLOG(3, (LOG_DEBUG, "elantech: "
+			    "unexpected trackpoint packet skipped\n"));
 		return (0);
 
 	case ELANTECH_PKT_NOP:
@@ -3995,22 +4767,42 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 		    ((pb->ipacket[0] & 0x02) ? MOUSE_BUTTON3DOWN : 0);
 	}
 
+#ifdef EVDEV_SUPPORT
+	if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+		for (id = 0; id < ELANTECH_MAX_FINGERS; id++) {
+			if (PSM_FINGER_IS_SET(f[id])) {
+				psm_push_mt_finger(sc, id, &f[id]);
+				/* Convert touch width to surface units */
+				evdev_push_abs(sc->evdev_a, ABS_MT_TOUCH_MAJOR,
+				    f[id].w * sc->elanhw.dptracex);
+			}
+		}
+		evdev_push_key(sc->evdev_a, BTN_TOUCH, nfingers > 0);
+		evdev_push_nfingers(sc->evdev_a, nfingers);
+		if (nfingers > 0) {
+			if (PSM_FINGER_IS_SET(f[0]))
+				psm_push_st_finger(sc, &f[0]);
+		} else
+			evdev_push_abs(sc->evdev_a, ABS_PRESSURE, 0);
+		evdev_push_mouse_btn(sc->evdev_a, touchpad_button);
+		evdev_sync(sc->evdev_a);
+	}
+#endif
+
 	ms->button = touchpad_button | trackpoint_button;
 
 	/* Palm detection doesn't terminate the current action. */
-	if (!psmpalmdetect(sc, &f[0], nfingers)) {
-		/* Send finger 1 position to gesture processor */
-		if (PSM_FINGER_IS_SET(f[0]) || PSM_FINGER_IS_SET(f[1]) ||
-		    nfingers == 0)
-			psmgestures(sc, &f[0], imin(nfingers, 3), ms);
-		/* Send fingers positions to movement smoothers */
-		for (id = 0; id < PSM_FINGERS; id++)
-			if (PSM_FINGER_IS_SET(f[id]) || !(mask & (1 << id)))
-				psmsmoother(sc, &f[id], id, ms, x, y);
-	} else {
-		VLOG(2, (LOG_DEBUG, "elantech: palm detected! (%d)\n",
-		    f[0].w));
-	}
+	palm = psmpalmdetect(sc, &f[0], nfingers);
+
+	/* Send finger 1 position to gesture processor */
+	if ((PSM_FINGER_IS_SET(f[0]) || PSM_FINGER_IS_SET(f[1]) ||
+	    nfingers == 0) && !palm)
+		psmgestures(sc, &f[0], imin(nfingers, 3), ms);
+
+	/* Send fingers positions to movement smoothers */
+	for (id = 0; id < PSM_FINGERS; id++)
+		if (PSM_FINGER_IS_SET(f[id]) || !(mask & (1 << id)))
+			psmsmoother(sc, &f[id], id, ms, x, y);
 
 	/* Store current finger positions in action context */
 	for (id = 0; id < ELANTECH_MAX_FINGERS; id++) {
@@ -4021,6 +4813,12 @@ proc_elantech(struct psm_softc *sc, packetbuf_t *pb, mousestatus_t *ms,
 	}
 	sc->elanaction.mask = mask;
 
+	if (palm) {
+		*x = *y = *z = 0;
+		ms->button = ms->obutton;
+		return (0);
+	}
+
 	/* Use the extra buttons as a scrollwheel */
 	if (ms->button & MOUSE_BUTTON4DOWN)
 		*z = -1;
@@ -4160,6 +4958,9 @@ psmsoftintr(void *arg)
 	do {
 		pb = &sc->pqueue[sc->pqueue_start];
 
+		if (sc->mode.level == PSM_LEVEL_NATIVE)
+			goto next_native;
+
 		c = pb->ipacket[0];
 		/*
 		 * A kludge for Kensington device!
@@ -4214,7 +5015,8 @@ psmsoftintr(void *arg)
 			/* wheel data is in the fourth byte */
 			z = (char)pb->ipacket[3];
 			/*
-			 * XXX some mice may send 7 when there is no Z movement?			 */
+			 * XXX some mice may send 7 when there is no Z movement?
+			 */
 			if ((z >= 7) || (z <= -7))
 				z = 0;
 			/* some compatible mice have additional buttons */
@@ -4236,7 +5038,7 @@ psmsoftintr(void *arg)
 
 		case MOUSE_MODEL_NETSCROLL:
 			/*
-			 * three addtional bytes encode buttons and
+			 * three additional bytes encode buttons and
 			 * wheel events
 			 */
 			ms.button |= (pb->ipacket[3] & MOUSE_PS2_BUTTON3DOWN) ?
@@ -4317,13 +5119,23 @@ psmsoftintr(void *arg)
 			break;
 
 		case MOUSE_MODEL_SYNAPTICS:
-			if (proc_synaptics(sc, pb, &ms, &x, &y, &z) != 0)
+			if (pb->inputbytes == MOUSE_PS2_PACKETSIZE)
+				if (proc_synaptics_mux(sc, pb))
+					goto next;
+
+			if (proc_synaptics(sc, pb, &ms, &x, &y, &z) != 0) {
+				VLOG(3, (LOG_DEBUG, "synaptics: "
+				    "packet rejected\n"));
 				goto next;
+			}
 			break;
 
 		case MOUSE_MODEL_ELANTECH:
-			if (proc_elantech(sc, pb, &ms, &x, &y, &z) != 0)
+			if (proc_elantech(sc, pb, &ms, &x, &y, &z) != 0) {
+				VLOG(3, (LOG_DEBUG, "elantech: "
+				    "packet rejected\n"));
 				goto next;
+			}
 			break;
 
 		case MOUSE_MODEL_TRACKPOINT:
@@ -4332,6 +5144,54 @@ psmsoftintr(void *arg)
 			break;
 		}
 
+		/*
+		 * Store last packet for reinjection if it has not been
+		 * set already
+		 */
+		if (timevalisset(&sc->idletimeout) &&
+		    sc->idlepacket.inputbytes == 0)
+			sc->idlepacket = *pb;
+
+#ifdef EVDEV_SUPPORT
+	if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE &&
+	    sc->hw.model != MOUSE_MODEL_ELANTECH &&
+	    sc->hw.model != MOUSE_MODEL_SYNAPTICS) {
+		evdev_push_rel(sc->evdev_r, REL_X, x);
+		evdev_push_rel(sc->evdev_r, REL_Y, -y);
+
+		switch (sc->hw.model) {
+		case MOUSE_MODEL_EXPLORER:
+		case MOUSE_MODEL_INTELLI:
+		case MOUSE_MODEL_NET:
+		case MOUSE_MODEL_NETSCROLL:
+		case MOUSE_MODEL_4DPLUS:
+			evdev_push_rel(sc->evdev_r, REL_WHEEL, -z);
+			break;
+		case MOUSE_MODEL_MOUSEMANPLUS:
+		case MOUSE_MODEL_4D:
+			switch (z) {
+			case 1:
+			case -1:
+				evdev_push_rel(sc->evdev_r, REL_WHEEL, -z);
+				break;
+			case 2:
+			case -2:
+				evdev_push_rel(sc->evdev_r, REL_HWHEEL, z / 2);
+				break;
+			}
+			break;
+		}
+
+		evdev_push_mouse_btn(sc->evdev_r, ms.button);
+		evdev_sync(sc->evdev_r);
+	}
+
+	if ((sc->evdev_a != NULL && evdev_is_grabbed(sc->evdev_a)) ||
+	    (sc->evdev_r != NULL && evdev_is_grabbed(sc->evdev_r)))
+		goto next;
+#endif
+
+
 		/* scale values */
 		if (sc->mode.accelfactor >= 1) {
 			if (x != 0) {
@@ -4349,13 +5209,6 @@ psmsoftintr(void *arg)
 					y = -y;
 			}
 		}
-		/*
-		 * Store last packet for reinjection if it has not been
-		 * set already
-		 */
-		if (timevalisset(&sc->idletimeout) &&
-		    sc->idlepacket.inputbytes == 0)
-			sc->idlepacket = *pb;
 
 		ms.dx = x;
 		ms.dy = y;
@@ -4372,6 +5225,7 @@ psmsoftintr(void *arg)
 		sc->status.button = ms.button;
 		sc->button = ms.button;
 
+next_native:
 		sc->watchdog = FALSE;
 
 		/* queue data */
@@ -4634,7 +5488,7 @@ enable_kmouse(struct psm_softc *sc, enum probearg arg)
 	if ((status[1] == PSMD_RES_LOW) || (status[2] == rate[i - 1]))
 		return (FALSE);
 
-	/* the device appears be enabled by this sequence, diable it for now */
+	/* the device appears be enabled by this sequence, disable it for now */
 	disable_aux_dev(kbdc);
 	empty_aux_buffer(kbdc, 5);
 
@@ -4858,7 +5712,7 @@ synaptics_sysctl(SYSCTL_HANDLER_ARGS)
 	int error, arg;
 
 	if (oidp->oid_arg1 == NULL || oidp->oid_arg2 < 0 ||
-	    oidp->oid_arg2 > SYNAPTICS_SYSCTL_SOFTBUTTON3_X)
+	    oidp->oid_arg2 > SYNAPTICS_SYSCTL_LAST)
 		return (EINVAL);
 
 	sc = oidp->oid_arg1;
@@ -4947,6 +5801,8 @@ synaptics_sysctl(SYSCTL_HANDLER_ARGS)
 			return (EINVAL);
 		break;
 	case SYNAPTICS_SYSCTL_TOUCHPAD_OFF:
+	case SYNAPTICS_SYSCTL_THREE_FINGER_DRAG:
+	case SYNAPTICS_SYSCTL_NATURAL_SCROLL:
 		if (arg < 0 || arg > 1)
 			return (EINVAL);
 		break;
@@ -4970,10 +5826,11 @@ synaptics_sysctl_create_softbuttons_tree(struct psm_softc *sc)
 	 */
 
 	/* hw.psm.synaptics.softbuttons_y */
-	sc->syninfo.softbuttons_y = 1700;
+	sc->syninfo.softbuttons_y = sc->synhw.topButtonPad ? -1700 : 1700;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "softbuttons_y", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "softbuttons_y",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_SOFTBUTTONS_Y,
 	    synaptics_sysctl, "I",
 	    "Vertical size of softbuttons area");
@@ -4982,7 +5839,8 @@ synaptics_sysctl_create_softbuttons_tree(struct psm_softc *sc)
 	sc->syninfo.softbutton2_x = 3100;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "softbutton2_x", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "softbutton2_x",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_SOFTBUTTON2_X,
 	    synaptics_sysctl, "I",
 	    "Horisontal position of 2-nd softbutton left edge (0-disable)");
@@ -4991,7 +5849,8 @@ synaptics_sysctl_create_softbuttons_tree(struct psm_softc *sc)
 	sc->syninfo.softbutton3_x = 3900;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "softbutton3_x", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "softbutton3_x",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_SOFTBUTTON3_X,
 	    synaptics_sysctl, "I",
 	    "Horisontal position of 3-rd softbutton left edge (0-disable)");
@@ -5008,8 +5867,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	/* Attach extra synaptics sysctl nodes under hw.psm.synaptics */
 	sysctl_ctx_init(&sc->syninfo.sysctl_ctx);
 	sc->syninfo.sysctl_tree = SYSCTL_ADD_NODE(&sc->syninfo.sysctl_ctx,
-	    SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, name, CTLFLAG_RD,
-	    0, descr);
+	    SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, name,
+	    CTLFLAG_RD, 0, descr);
 
 	/* hw.psm.synaptics.directional_scrolls. */
 	sc->syninfo.directional_scrolls = 0;
@@ -5054,10 +5913,11 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	    "Enable two finger scrolling");
 
 	/* hw.psm.synaptics.min_pressure. */
-	sc->syninfo.min_pressure = 16;
+	sc->syninfo.min_pressure = 32;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "min_pressure", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "min_pressure",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MIN_PRESSURE,
 	    synaptics_sysctl, "I",
 	    "Minimum pressure required to start an action");
@@ -5066,7 +5926,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.max_pressure = 220;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "max_pressure", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "max_pressure",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MAX_PRESSURE,
 	    synaptics_sysctl, "I",
 	    "Maximum pressure to detect palm");
@@ -5075,7 +5936,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.max_width = 10;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "max_width", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "max_width",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MAX_WIDTH,
 	    synaptics_sysctl, "I",
 	    "Maximum finger width to detect palm");
@@ -5084,7 +5946,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.margin_top = 200;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "margin_top", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "margin_top",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MARGIN_TOP,
 	    synaptics_sysctl, "I",
 	    "Top margin");
@@ -5093,7 +5956,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.margin_right = 200;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "margin_right", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "margin_right",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MARGIN_RIGHT,
 	    synaptics_sysctl, "I",
 	    "Right margin");
@@ -5102,7 +5966,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.margin_bottom = 200;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "margin_bottom", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "margin_bottom",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MARGIN_BOTTOM,
 	    synaptics_sysctl, "I",
 	    "Bottom margin");
@@ -5111,7 +5976,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.margin_left = 200;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "margin_left", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "margin_left",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MARGIN_LEFT,
 	    synaptics_sysctl, "I",
 	    "Left margin");
@@ -5120,7 +5986,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.na_top = 1783;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "na_top", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "na_top",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_NA_TOP,
 	    synaptics_sysctl, "I",
 	    "Top noisy area, where weight_previous_na is used instead "
@@ -5130,7 +5997,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.na_right = 563;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "na_right", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "na_right",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_NA_RIGHT,
 	    synaptics_sysctl, "I",
 	    "Right noisy area, where weight_previous_na is used instead "
@@ -5140,7 +6008,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.na_bottom = 1408;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "na_bottom", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "na_bottom",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_NA_BOTTOM,
 	    synaptics_sysctl, "I",
 	    "Bottom noisy area, where weight_previous_na is used instead "
@@ -5150,7 +6019,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.na_left = 1600;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "na_left", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "na_left",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_NA_LEFT,
 	    synaptics_sysctl, "I",
 	    "Left noisy area, where weight_previous_na is used instead "
@@ -5160,7 +6030,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.window_min = 4;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "window_min", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "window_min",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_WINDOW_MIN,
 	    synaptics_sysctl, "I",
 	    "Minimum window size to start an action");
@@ -5169,7 +6040,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.window_max = 10;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "window_max", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "window_max",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_WINDOW_MAX,
 	    synaptics_sysctl, "I",
 	    "Maximum window size");
@@ -5178,7 +6050,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.multiplicator = 10000;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "multiplicator", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "multiplicator",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_MULTIPLICATOR,
 	    synaptics_sysctl, "I",
 	    "Multiplicator to increase precision in averages and divisions");
@@ -5187,7 +6060,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.weight_current = 3;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "weight_current", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "weight_current",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_WEIGHT_CURRENT,
 	    synaptics_sysctl, "I",
 	    "Weight of the current movement in the new average");
@@ -5196,7 +6070,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.weight_previous = 6;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "weight_previous", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "weight_previous",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS,
 	    synaptics_sysctl, "I",
 	    "Weight of the previous average");
@@ -5205,7 +6080,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.weight_previous_na = 20;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "weight_previous_na", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "weight_previous_na",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA,
 	    synaptics_sysctl, "I",
 	    "Weight of the previous average (inside the noisy area)");
@@ -5214,7 +6090,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.weight_len_squared = 2000;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "weight_len_squared", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "weight_len_squared",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED,
 	    synaptics_sysctl, "I",
 	    "Length (squared) of segments where weight_previous "
@@ -5224,7 +6101,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.div_min = 9;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "div_min", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "div_min",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_DIV_MIN,
 	    synaptics_sysctl, "I",
 	    "Divisor for fast movements");
@@ -5233,7 +6111,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.div_max = 17;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "div_max", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "div_max",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_DIV_MAX,
 	    synaptics_sysctl, "I",
 	    "Divisor for slow movements");
@@ -5242,7 +6121,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.div_max_na = 30;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "div_max_na", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "div_max_na",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_DIV_MAX_NA,
 	    synaptics_sysctl, "I",
 	    "Divisor with slow movements (inside the noisy area)");
@@ -5251,7 +6131,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.div_len = 100;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "div_len", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "div_len",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_DIV_LEN,
 	    synaptics_sysctl, "I",
 	    "Length of segments where div_max starts to decrease");
@@ -5260,7 +6141,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.tap_max_delta = 80;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "tap_max_delta", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "tap_max_delta",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_TAP_MAX_DELTA,
 	    synaptics_sysctl, "I",
 	    "Length of segments above which a tap is ignored");
@@ -5269,7 +6151,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.tap_min_queue = 2;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "tap_min_queue", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "tap_min_queue",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_TAP_MIN_QUEUE,
 	    synaptics_sysctl, "I",
 	    "Number of packets required to consider a tap");
@@ -5279,7 +6162,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.taphold_timeout = tap_timeout;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "taphold_timeout", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "taphold_timeout",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT,
 	    synaptics_sysctl, "I",
 	    "Maximum elapsed time between two taps to consider a tap-hold "
@@ -5289,7 +6173,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.vscroll_hor_area = 0; /* 1300 */
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "vscroll_hor_area", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "vscroll_hor_area",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA,
 	    synaptics_sysctl, "I",
 	    "Area reserved for horizontal virtual scrolling");
@@ -5298,7 +6183,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.vscroll_ver_area = -400 - sc->syninfo.margin_right;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "vscroll_ver_area", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "vscroll_ver_area",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_VSCROLL_VER_AREA,
 	    synaptics_sysctl, "I",
 	    "Area reserved for vertical virtual scrolling");
@@ -5307,7 +6193,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.vscroll_min_delta = 50;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "vscroll_min_delta", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "vscroll_min_delta",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA,
 	    synaptics_sysctl, "I",
 	    "Minimum movement to consider virtual scrolling");
@@ -5316,7 +6203,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.vscroll_div_min = 100;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "vscroll_div_min", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "vscroll_div_min",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN,
 	    synaptics_sysctl, "I",
 	    "Divisor for fast scrolling");
@@ -5325,7 +6213,8 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.vscroll_div_max = 150;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "vscroll_div_max", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "vscroll_div_max",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX,
 	    synaptics_sysctl, "I",
 	    "Divisor for slow scrolling");
@@ -5334,11 +6223,31 @@ synaptics_sysctl_create_tree(struct psm_softc *sc, const char *name,
 	sc->syninfo.touchpad_off = 0;
 	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-	    "touchpad_off", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "touchpad_off",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, SYNAPTICS_SYSCTL_TOUCHPAD_OFF,
 	    synaptics_sysctl, "I",
 	    "Turn off touchpad");
 
+	sc->syninfo.three_finger_drag = 0;
+	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
+	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
+	    "three_finger_drag",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
+	    sc, SYNAPTICS_SYSCTL_THREE_FINGER_DRAG,
+	    synaptics_sysctl, "I",
+	    "Enable dragging with three fingers");
+
+	/* hw.psm.synaptics.natural_scroll. */
+	sc->syninfo.natural_scroll = 0;
+	SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
+	    SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
+	    "natural_scroll",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
+	    sc, SYNAPTICS_SYSCTL_NATURAL_SCROLL,
+	    synaptics_sysctl, "I",
+	    "Enable natural scrolling");
+
 	sc->syninfo.softbuttons_y = 0;
 	sc->syninfo.softbutton2_x = 0;
 	sc->syninfo.softbutton3_x = 0;
@@ -5353,6 +6262,24 @@ static int
 synaptics_preferred_mode(struct psm_softc *sc) {
 	int mode_byte;
 
+	/* Check if we are in relative mode */
+	if (sc->hw.model != MOUSE_MODEL_SYNAPTICS) {
+		if (tap_enabled == 0)
+			/*
+			 * Disable tap & drag gestures. We use a Mode Byte
+			 * and set the DisGest bit (see §2.5 of Synaptics
+			 * TouchPad Interfacing Guide).
+			 */
+			return (0x04);
+		else
+			/*
+			 * Enable tap & drag gestures. We use a Mode Byte
+			 * and clear the DisGest bit (see §2.5 of Synaptics
+			 * TouchPad Interfacing Guide).
+			 */
+			return (0x00);
+	}
+
 	mode_byte = 0xc4;
 
 	/* request wmode where available */
@@ -5371,18 +6298,102 @@ synaptics_set_mode(struct psm_softc *sc, int mode_byte) {
 
 	/*
 	 * Enable advanced gestures mode if supported and we are not entering
-	 * passthrough mode.
+	 * passthrough or relative mode.
 	 */
 	if ((sc->synhw.capAdvancedGestures || sc->synhw.capReportsV) &&
-	    !(mode_byte & (1 << 5))) {
-		mouse_ext_command(sc->kbdc, 3);
+	    sc->hw.model == MOUSE_MODEL_SYNAPTICS && !(mode_byte & (1 << 5))) {
+		mouse_ext_command(sc->kbdc, SYNAPTICS_READ_MODEL_ID);
 		set_mouse_sampling_rate(sc->kbdc, 0xc8);
 	}
 }
 
+/*
+ * AUX MUX detection code should be placed at very beginning of probe sequence
+ * at least before 4-byte protocol mouse probes e.g. MS IntelliMouse probe as
+ * latter can trigger switching the MUX to incompatible state.
+ */
+static int
+enable_synaptics_mux(struct psm_softc *sc, enum probearg arg)
+{
+	KBDC kbdc = sc->kbdc;
+	int port, version;
+	int probe = FALSE;
+	int active_ports_count = 0;
+	int active_ports_mask = 0;
+
+	sc->muxsinglesyna = FALSE;
+
+	if (mux_disabled != 0)
+		return (FALSE);
+
+	version = enable_aux_mux(kbdc);
+	if (version == -1)
+		return (FALSE);
+
+	for (port = 0; port < KBDC_AUX_MUX_NUM_PORTS; port++) {
+		VLOG(3, (LOG_DEBUG, "aux_mux: ping port %d\n", port));
+		set_active_aux_mux_port(kbdc, port);
+		if (enable_aux_dev(kbdc) && disable_aux_dev(kbdc)) {
+			active_ports_count++;
+			active_ports_mask |= 1 << port;
+		}
+	}
+
+	if (verbose >= 2)
+		kprintf("Active Multiplexing PS/2 controller v%d.%d with %d "
+		    "active port(s)\n", version >> 4 & 0x0f, version & 0x0f,
+		    active_ports_count);
+
+	/* psm has a special support for GenMouse + SynTouchpad combination */
+	for (port = 0; port < KBDC_AUX_MUX_NUM_PORTS; port++) {
+		if ((active_ports_mask & 1 << port) == 0)
+			continue;
+		VLOG(3, (LOG_DEBUG, "aux_mux: probe port %d\n", port));
+		set_active_aux_mux_port(kbdc, port);
+		probe = enable_synaptics(sc, arg);
+		if (probe) {
+			if (arg == PROBE)
+				sc->muxport = port;
+			break;
+		}
+	}
+
+	/* IRQ handler does not support active multiplexing mode */
+	disable_aux_mux(kbdc);
+
+	/* Is MUX still alive after switching back to legacy mode? */
+	if (!enable_aux_dev(kbdc) || !disable_aux_dev(kbdc)) {
+		/*
+		 * On some laptops e.g. Lenovo X121e dead AUX MUX can be
+		 * brought back to life with resetting of keyboard.
+		 */
+		reset_kbd(kbdc);
+		if (!enable_aux_dev(kbdc) || !disable_aux_dev(kbdc)) {
+			device_printf(sc->dev, "AUX MUX hang detected!\n");
+			kprintf("Consider adding hw.psm.mux_disabled=1 to "
+			    "loader tunables\n");
+		}
+	}
+	empty_both_buffers(kbdc, 10);	/* remove stray data if any */
+
+	/* Don't disable syncbit checks if Synaptics is only device on MUX */
+	if (active_ports_count == 1)
+		sc->muxsinglesyna = probe;
+	return (active_ports_count != 1 ? probe : FALSE);
+}
+
+static int
+enable_single_synaptics_mux(struct psm_softc *sc, enum probearg arg)
+{
+	/* Synaptics device is already initialized in enable_synaptics_mux */
+	return (sc->muxsinglesyna);
+}
+
 static int
 enable_synaptics(struct psm_softc *sc, enum probearg arg)
 {
+	device_t psmcpnp;
+	struct psmcpnp_softc *psmcpnp_sc;
 	KBDC kbdc = sc->kbdc;
 	synapticshw_t synhw;
 	int status[3];
@@ -5399,7 +6410,7 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 	set_mouse_scaling(kbdc, 1);
 
 	/* Identify the Touchpad version. */
-	if (mouse_ext_command(kbdc, 0) == 0)
+	if (mouse_ext_command(kbdc, SYNAPTICS_READ_IDENTITY) == 0)
 		return (FALSE);
 	if (get_mouse_status(kbdc, status, 0, 3) != 3)
 		return (FALSE);
@@ -5420,7 +6431,7 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 	}
 
 	/* Get the Touchpad model information. */
-	if (mouse_ext_command(kbdc, 3) == 0)
+	if (mouse_ext_command(kbdc, SYNAPTICS_READ_MODEL_ID) == 0)
 		return (FALSE);
 	if (get_mouse_status(kbdc, status, 0, 3) != 3)
 		return (FALSE);
@@ -5450,8 +6461,17 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 		kprintf("   infoGeometry: %d\n", synhw.infoGeometry);
 	}
 
+	/*
+	 * Typical bezel limits. Taken from 'Synaptics
+	 * PS/2 * TouchPad Interfacing Guide' p.3.2.3.
+	 */
+	synhw.maximumXCoord = 5472;
+	synhw.maximumYCoord = 4448;
+	synhw.minimumXCoord = 1472;
+	synhw.minimumYCoord = 1408;
+
 	/* Read the extended capability bits. */
-	if (mouse_ext_command(kbdc, 2) == 0)
+	if (mouse_ext_command(kbdc, SYNAPTICS_READ_CAPABILITIES) == 0)
 		return (FALSE);
 	if (get_mouse_status(kbdc, status, 0, 3) != 3)
 		return (FALSE);
@@ -5460,6 +6480,10 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 		return (FALSE);
 	}
 
+	psmcpnp = devclass_get_device(devclass_find(PSMCPNP_DRIVER_NAME),
+	    device_get_unit(sc->dev));
+	psmcpnp_sc = (psmcpnp != NULL) ? device_get_softc(psmcpnp) : NULL;
+
 	/* Set the different capabilities when they exist. */
 	buttons = 0;
 	synhw.capExtended = (status[0] & 0x80) != 0;
@@ -5478,13 +6502,15 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 
 		if (!set_mouse_scaling(kbdc, 1))
 			return (FALSE);
-		if (mouse_ext_command(kbdc, 0x08) == 0)
+		if (mouse_ext_command(kbdc, SYNAPTICS_READ_RESOLUTIONS) == 0)
 			return (FALSE);
 		if (get_mouse_status(kbdc, status, 0, 3) != 3)
 			return (FALSE);
 
-		synhw.infoXupmm = status[0];
-		synhw.infoYupmm = status[2];
+		if (status[0] != 0 && (status[1] & 0x80) && status[2] != 0) {
+			synhw.infoXupmm = status[0];
+			synhw.infoYupmm = status[2];
+		}
 
 		if (verbose >= 2) {
 			kprintf("  Extended capabilities:\n");
@@ -5513,7 +6539,8 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 		if (synhw.nExtendedQueries >= 1) {
 			if (!set_mouse_scaling(kbdc, 1))
 				return (FALSE);
-			if (mouse_ext_command(kbdc, 0x09) == 0)
+			if (mouse_ext_command(kbdc,
+			    SYNAPTICS_READ_EXTENDED) == 0)
 				return (FALSE);
 			if (get_mouse_status(kbdc, status, 0, 3) != 3)
 				return (FALSE);
@@ -5552,7 +6579,8 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 		if (synhw.nExtendedQueries >= 4) {
 			if (!set_mouse_scaling(kbdc, 1))
 				return (FALSE);
-			if (mouse_ext_command(kbdc, 0x0c) == 0)
+			if (mouse_ext_command(kbdc,
+			    SYNAPTICS_READ_CAPABILITIES_CONT) == 0)
 				return (FALSE);
 			if (get_mouse_status(kbdc, status, 0, 3) != 3)
 				return (FALSE);
@@ -5573,7 +6601,8 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 			if (synhw.capReportsMax) {
 				if (!set_mouse_scaling(kbdc, 1))
 					return (FALSE);
-				if (mouse_ext_command(kbdc, 0x0d) == 0)
+				if (mouse_ext_command(kbdc,
+				    SYNAPTICS_READ_MAX_COORDS) == 0)
 					return (FALSE);
 				if (get_mouse_status(kbdc, status, 0, 3) != 3)
 					return (FALSE);
@@ -5582,19 +6611,13 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 						     ((status[1] & 0x0f) << 1);
 				synhw.maximumYCoord = (status[2] << 5) |
 						     ((status[1] & 0xf0) >> 3);
-			} else {
-				/*
-				 * Typical bezel limits. Taken from 'Synaptics
-				 * PS/2 * TouchPad Interfacing Guide' p.3.2.3.
-				 */
-				synhw.maximumXCoord = 5472;
-				synhw.maximumYCoord = 4448;
 			}
 
 			if (synhw.capReportsMin) {
 				if (!set_mouse_scaling(kbdc, 1))
 					return (FALSE);
-				if (mouse_ext_command(kbdc, 0x0f) == 0)
+				if (mouse_ext_command(kbdc,
+				    SYNAPTICS_READ_MIN_COORDS) == 0)
 					return (FALSE);
 				if (get_mouse_status(kbdc, status, 0, 3) != 3)
 					return (FALSE);
@@ -5603,13 +6626,23 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 						     ((status[1] & 0x0f) << 1);
 				synhw.minimumYCoord = (status[2] << 5) |
 						     ((status[1] & 0xf0) >> 3);
-			} else {
-				/*
-				 * Typical bezel limits. Taken from 'Synaptics
-				 * PS/2 * TouchPad Interfacing Guide' p.3.2.3.
-				 */
-				synhw.minimumXCoord = 1472;
-				synhw.minimumYCoord = 1408;
+			}
+
+			/*
+			 * ClickPad properties are not exported through PS/2
+			 * protocol. Detection is based on controller's PnP ID.
+			 */
+			if (synhw.capClickPad && psmcpnp_sc != NULL) {
+				switch (psmcpnp_sc->type) {
+				case PSMCPNP_FORCEPAD:
+					synhw.forcePad = 1;
+					break;
+				case PSMCPNP_TOPBUTTONPAD:
+					synhw.topButtonPad = 1;
+					break;
+				default:
+					break;
+				}
 			}
 
 			if (verbose >= 2) {
@@ -5648,6 +6681,13 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 					kprintf("   minimumYCoord: %d\n",
 					       synhw.minimumYCoord);
 				}
+				if (synhw.capClickPad) {
+					kprintf("  Clickpad capabilities:\n");
+					kprintf("   forcePad: %d\n",
+					       synhw.forcePad);
+					kprintf("   topButtonPad: %d\n",
+					       synhw.topButtonPad);
+				}
 			}
 			buttons += synhw.capClickPad;
 		}
@@ -5673,7 +6713,7 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 	 * byte of the response to this query to be a constant 0x3b, this
 	 * does not appear to be true for Touchpads with guest devices.
 	 */
-	if (mouse_ext_command(kbdc, 1) == 0)
+	if (mouse_ext_command(kbdc, SYNAPTICS_READ_MODES) == 0)
 		return (FALSE);
 	if (get_mouse_status(kbdc, status, 0, 3) != 3)
 		return (FALSE);
@@ -5687,6 +6727,9 @@ enable_synaptics(struct psm_softc *sc, enum probearg arg)
 	if (!synaptics_support)
 		return (FALSE);
 
+	/* Set mouse type just now for synaptics_set_mode() */
+	sc->hw.model = MOUSE_MODEL_SYNAPTICS;
+
 	synaptics_set_mode(sc, synaptics_preferred_mode(sc));
 
 	if (trackpoint_support && synhw.capPassthrough) {
@@ -5808,14 +6851,15 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	/* Attach extra trackpoint sysctl nodes under hw.psm.trackpoint */
 	sysctl_ctx_init(&sc->tpinfo.sysctl_ctx);
 	sc->tpinfo.sysctl_tree = SYSCTL_ADD_NODE(&sc->tpinfo.sysctl_ctx,
-	    SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, "trackpoint", CTLFLAG_RD,
-	    0, "IBM/Lenovo TrackPoint");
+	    SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, "trackpoint",
+	    CTLFLAG_RD, 0, "IBM/Lenovo TrackPoint");
 
 	/* hw.psm.trackpoint.sensitivity */
 	sc->tpinfo.sensitivity = 0x80;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "sensitivity", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "sensitivity",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_SENSITIVITY,
 	    trackpoint_sysctl, "I",
 	    "Sensitivity");
@@ -5824,7 +6868,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.inertia = 0x06;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "negative_inertia", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "negative_inertia",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_NEGATIVE_INERTIA,
 	    trackpoint_sysctl, "I",
 	    "Negative inertia factor");
@@ -5833,7 +6878,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.uplateau = 0x61;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "upper_plateau", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "upper_plateau",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_UPPER_PLATEAU,
 	    trackpoint_sysctl, "I",
 	    "Transfer function upper plateau speed");
@@ -5842,7 +6888,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.reach = 0x0a;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "backup_range", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "backup_range",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_BACKUP_RANGE,
 	    trackpoint_sysctl, "I",
 	    "Backup range");
@@ -5851,7 +6898,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.draghys = 0xff;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "drag_hysteresis", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "drag_hysteresis",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_DRAG_HYSTERESIS,
 	    trackpoint_sysctl, "I",
 	    "Drag hysteresis");
@@ -5860,7 +6908,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.mindrag = 0x14;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "minimum_drag", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "minimum_drag",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_MINIMUM_DRAG,
 	    trackpoint_sysctl, "I",
 	    "Minimum drag");
@@ -5869,7 +6918,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.upthresh = 0xff;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "up_threshold", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "up_threshold",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_UP_THRESHOLD,
 	    trackpoint_sysctl, "I",
 	    "Up threshold for release");
@@ -5878,7 +6928,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.threshold = 0x08;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "threshold", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "threshold",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_THRESHOLD,
 	    trackpoint_sysctl, "I",
 	    "Threshold");
@@ -5887,7 +6938,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.jenks = 0x87;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "jenks_curvature", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "jenks_curvature",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_JENKS_CURVATURE,
 	    trackpoint_sysctl, "I",
 	    "Jenks curvature");
@@ -5896,7 +6948,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.ztime = 0x26;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "z_time", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "z_time",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_Z_TIME,
 	    trackpoint_sysctl, "I",
 	    "Z time constant");
@@ -5905,7 +6958,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.pts = 0x00;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "press_to_select", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "press_to_select",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_PRESS_TO_SELECT,
 	    trackpoint_sysctl, "I",
 	    "Press to Select");
@@ -5914,7 +6968,8 @@ trackpoint_sysctl_create_tree(struct psm_softc *sc)
 	sc->tpinfo.skipback = 0x00;
 	SYSCTL_ADD_PROC(&sc->tpinfo.sysctl_ctx,
 	    SYSCTL_CHILDREN(sc->tpinfo.sysctl_tree), OID_AUTO,
-	    "skip_backups", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+	    "skip_backups",
+	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY,
 	    sc, TRACKPOINT_SYSCTL_SKIP_BACKUPS,
 	    trackpoint_sysctl, "I",
 	    "Skip backups from drags");
@@ -5943,7 +6998,7 @@ static int
 enable_trackpoint(struct psm_softc *sc, enum probearg arg)
 {
 	KBDC kbdc = sc->kbdc;
-	int id;
+	int vendor, firmware;
 
 	/*
 	 * If called from enable_synaptics(), make sure that passthrough
@@ -5955,14 +7010,14 @@ enable_trackpoint(struct psm_softc *sc, enum probearg arg)
 	if (sc->synhw.capPassthrough)
 		synaptics_passthrough_on(sc);
 
-	if (send_aux_command(kbdc, 0xe1) != PSM_ACK ||
-	    read_aux_data(kbdc) != 0x01)
+	if (send_aux_command(kbdc, 0xe1) != PSM_ACK)
 		goto no_trackpoint;
-	id = read_aux_data(kbdc);
-	if (id < 0x01)
+	vendor = read_aux_data(kbdc);
+	if (vendor <= 0 || vendor >= TRACKPOINT_VENDOR_UNKNOWN)
+		goto no_trackpoint;
+	firmware = read_aux_data(kbdc);
+	if (firmware < 0x01)
 		goto no_trackpoint;
-	if (arg == PROBE)
-		sc->tphw = id;
 	if (!trackpoint_support)
 		goto no_trackpoint;
 
@@ -5976,9 +7031,13 @@ enable_trackpoint(struct psm_softc *sc, enum probearg arg)
 		 * a guest device.
 		 */
 		if (!sc->synhw.capPassthrough) {
-			sc->hw.hwid = id;
+			sc->hw.hwid = firmware;
 			sc->hw.buttons = 3;
 		}
+		VDLOG(2, sc->dev, LOG_NOTICE, "Trackpoint v=0x%x f=0x%x",
+		    vendor, firmware);
+		sc->tpinfo.vendor = vendor;
+		sc->tpinfo.firmware = firmware;
 	}
 
 	set_trackpoint_parameters(sc);
@@ -6134,7 +7193,10 @@ elantech_init_synaptics(struct psm_softc *sc)
 	sc->synhw.capPassthrough = sc->elanhw.hastrackpoint;
 	sc->synhw.capClickPad = sc->elanhw.isclickpad;
 	sc->synhw.capMultiFinger = 1;
-	sc->synhw.capAdvancedGestures = 1;
+	if (sc->elanhw.issemimt)
+		sc->synhw.capAdvancedGestures = 1;
+	else
+		sc->synhw.capReportsV = 1;
 	sc->synhw.capPalmDetect = 1;
 	sc->synhw.capPen = 0;
 	sc->synhw.capReportsMax = 1;
@@ -6168,6 +7230,12 @@ elantech_init_synaptics(struct psm_softc *sc)
 		/* Disable finger detection pressure threshold */
 		sc->syninfo.min_pressure = 1;
 
+		/* Adjust palm width to nearly match synaptics w=10 */
+		sc->syninfo.max_width = 7;
+
+		/* Elans often report double & triple taps as single event */
+		sc->syninfo.tap_min_queue = 1;
+
 		/* Use full area of touchpad */
 		sc->syninfo.margin_top = 0;
 		sc->syninfo.margin_right = 0;
@@ -6212,8 +7280,17 @@ enable_elantech(struct psm_softc *sc, enum probearg arg)
 	static const int ic2hw[] =
 	/*IC: 0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f */
 	    { 0, 0, 2, 0, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 };
+	static const int fw_sizes[][3] = {
+		/* FW.vers  MaxX  MaxY */
+		{ 0x020030, 1152,  768 },
+		{ 0x020800, 1152,  768 },
+		{ 0x020b00, 1152,  768 },
+		{ 0x040215,  900,  500 },
+		{ 0x040216,  819,  405 },
+		{ 0x040219,  900,  500 },
+	};
 	elantechhw_t elanhw;
-	int icversion, hwversion, dptracex, dptracey, id, resp[3], dpix, dpiy;
+	int icversion, hwversion, xtr, i, id, resp[3], dpix, dpiy;
 	KBDC kbdc = sc->kbdc;
 
 	VLOG(3, (LOG_DEBUG, "elantech: BEGIN init\n"));
@@ -6264,8 +7341,8 @@ enable_elantech(struct psm_softc *sc, enum probearg arg)
 		return (FALSE);
 	}
 
-	elanhw.ntracesx = resp[1] - 1;
-	elanhw.ntracesy = resp[2] - 1;
+	elanhw.ntracesx = imax(resp[1], 3);
+	elanhw.ntracesy = imax(resp[2], 3);
 	elanhw.hastrackpoint = (resp[0] & 0x80) != 0;
 
 	/* Get the touchpad resolution */
@@ -6299,24 +7376,36 @@ enable_elantech(struct psm_softc *sc, enum probearg arg)
 	 * On HW v.3 touchpads it should be done after switching hardware
 	 * to real resolution mode (by setting bit 3 of reg10)
 	 */
+	elanhw.dptracex = elanhw.dptracey = 64;
+	for (i = 0; i < nitems(fw_sizes); i++) {
+		if (elanhw.fwversion == fw_sizes[i][0]) {
+			elanhw.sizex = fw_sizes[i][1];
+			elanhw.sizey = fw_sizes[i][2];
+			goto found;
+		}
+	}
 	if (elantech_cmd(kbdc, hwversion, ELANTECH_FW_ID, resp) != 0) {
 		kprintf("  Failed to read touchpad size\n");
 		elanhw.sizex = 10000; /* Arbitrary high values to     */
 		elanhw.sizey = 10000; /* prevent clipping in smoother */
 	} else if (hwversion == 2) {
-		dptracex = dptracey = 64;
 		if ((elanhw.fwversion >> 16) == 0x14 && (resp[1] & 0x10) &&
 		    !elantech_cmd(kbdc, hwversion, ELANTECH_SAMPLE, resp)) {
-			dptracex = resp[1] / 2;
-			dptracey = resp[2] / 2;
+			elanhw.dptracex = resp[1] / 2;
+			elanhw.dptracey = resp[2] / 2;
 		}
-		elanhw.sizex = (elanhw.ntracesx - 1) * dptracex;
-		elanhw.sizey = (elanhw.ntracesy - 1) * dptracey;
+		xtr = ((elanhw.fwversion >> 8) == 0x0208) ? 1 : 2;
+		elanhw.sizex = (elanhw.ntracesx - xtr) * elanhw.dptracex;
+		elanhw.sizey = (elanhw.ntracesy - xtr) * elanhw.dptracey;
+
 	} else {
 		elanhw.sizex = (resp[0] & 0x0f) << 8 | resp[1];
 		elanhw.sizey = (resp[0] & 0xf0) << 4 | resp[2];
+		xtr = (elanhw.sizex % (elanhw.ntracesx - 2) == 0) ? 2 : 1;
+		elanhw.dptracex = elanhw.sizex / (elanhw.ntracesx - xtr);
+		elanhw.dptracey = elanhw.sizey / (elanhw.ntracesy - xtr);
 	}
-
+found:
 	if (verbose >= 2) {
 		kprintf("  Model information:\n");
 		kprintf("   MaxX:       %d\n", elanhw.sizex);
@@ -6325,6 +7414,8 @@ enable_elantech(struct psm_softc *sc, enum probearg arg)
 		kprintf("   DpmmY:      %d\n", elanhw.dpmmy);
 		kprintf("   TracesX:    %d\n", elanhw.ntracesx);
 		kprintf("   TracesY:    %d\n", elanhw.ntracesy);
+		kprintf("   DptraceX:   %d\n", elanhw.dptracex);
+		kprintf("   DptraceY:   %d\n", elanhw.dptracey);
 		kprintf("   SemiMT:     %d\n", elanhw.issemimt);
 		kprintf("   Clickpad:   %d\n", elanhw.isclickpad);
 		kprintf("   Trackpoint: %d\n", elanhw.hastrackpoint);
@@ -6355,7 +7446,8 @@ enable_elantech(struct psm_softc *sc, enum probearg arg)
  */
 static int
 timeelapsed(const struct timeval *start, int secs, int usecs,
-	const struct timeval *now) {
+    const struct timeval *now)
+{
 	struct timeval snow, tv;
 
 	/* if there is no 'now' passed in, the get it as a convenience. */
@@ -6375,10 +7467,9 @@ psmresume(device_t dev)
 {
 
 	struct psm_softc *sc = device_get_softc(dev);
-	int unit = device_get_unit(dev);
 	int err;
 
-	VLOG(2, (LOG_NOTICE, "psm%d: system resume hook called.\n", unit));
+	VDLOG(2, dev, LOG_NOTICE, "system resume hook called.\n");
 
 	if ((sc->config &
 	    (PSM_CONFIG_HOOKRESUME | PSM_CONFIG_INITAFTERSUSPEND)) == 0)
@@ -6395,12 +7486,15 @@ psmresume(device_t dev)
 		wakeup(sc);
 	}
 
-	VLOG(2, (LOG_DEBUG, "psm%d: system resume hook exiting.\n", unit));
+	VDLOG(2, dev, LOG_DEBUG, "system resume hook exiting.\n");
 
 	return (err);
 }
 
 DRIVER_MODULE(psm, atkbdc, psm_driver, psm_devclass, NULL, NULL);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(psm, evdev, 1, 1, 1);
+#endif
 
 #ifdef DEV_ISA
 #if 0
@@ -6433,7 +7527,7 @@ static device_method_t psmcpnp_methods[] = {
 static driver_t psmcpnp_driver = {
 	PSMCPNP_DRIVER_NAME,
 	psmcpnp_methods,
-	1,                      /* no softc */
+	sizeof(struct psmcpnp_softc),
 };
 
 static struct isa_pnp_id psmcpnp_ids[] = {
@@ -6453,6 +7547,13 @@ static struct isa_pnp_id psmcpnp_ids[] = {
 	{ 0 }
 };
 
+/* _HID list for quirk detection. Any device below has _CID from psmcpnp_ids */
+static struct isa_pnp_id forcepad_ids[] = {
+	{ 0x0d302e4f, "HP PS/2 forcepad port" },	/* SYN300D, EB 1040 */
+	{ 0x14302e4f, "HP PS/2 forcepad port" },	/* SYN3014, EB 1040 */
+	{ 0 }
+};
+
 static int
 create_a_copy(device_t atkbdc, device_t me)
 {
@@ -6481,11 +7582,16 @@ create_a_copy(device_t atkbdc, device_t me)
 static int
 psmcpnp_probe(device_t dev)
 {
+	struct psmcpnp_softc *sc = device_get_softc(dev);
 	struct resource *res;
 	u_long irq;
 	int rid;
 
-	if (ISA_PNP_PROBE(device_get_parent(dev), dev, psmcpnp_ids))
+	if (ISA_PNP_PROBE(device_get_parent(dev), dev, forcepad_ids) == 0)
+		sc->type = PSMCPNP_FORCEPAD;
+	else if (ISA_PNP_PROBE(device_get_parent(dev), dev, psmcpnp_ids) == 0)
+		sc->type = PSMCPNP_GENERIC;
+	else
 		return (ENXIO);
 
 	/*
diff --git a/sys/dev/misc/syscons/sysmouse.c b/sys/dev/misc/syscons/sysmouse.c
index e9252eff42..acf81b9fb2 100644
--- a/sys/dev/misc/syscons/sysmouse.c
+++ b/sys/dev/misc/syscons/sysmouse.c
@@ -383,12 +383,6 @@ smioctl(struct dev_ioctl_args *ap)
 		lockmgr(&sc->sm_lock, LK_RELEASE);
 		return 0;
 
-#if 0 /* notyet */
-	case MOUSE_GETVARS:	/* get internal mouse variables */
-	case MOUSE_SETVARS:	/* set internal mouse variables */
-		return ENODEV;
-#endif
-
 	case MOUSE_READSTATE:	/* read status from the device */
 	case MOUSE_READDATA:	/* read data from the device */
 		return ENODEV;
diff --git a/sys/kern/kern_mib.c b/sys/kern/kern_mib.c
index 37e54c64fa..eee80e2351 100644
--- a/sys/kern/kern_mib.c
+++ b/sys/kern/kern_mib.c
@@ -282,3 +282,5 @@ SYSCTL_INT(_debug_sizeof, OID_AUTO, proc, CTLFLAG_RD,
 #include <sys/conf.h>
 SYSCTL_INT(_debug_sizeof, OID_AUTO, cdev, CTLFLAG_RD,
     0, sizeof(struct cdev), "sizeof(struct cdev)");
+
+SYSCTL_NODE(_kern, OID_AUTO, features, CTLFLAG_RD, 0, "Kernel Features");
diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c
index 3beada7276..6de36199ea 100644
--- a/sys/kern/subr_bus.c
+++ b/sys/kern/subr_bus.c
@@ -49,6 +49,7 @@
 #include <sys/signalvar.h>
 #include <sys/machintr.h>
 #include <sys/vnode.h>
+#include <sys/sbuf.h>
 
 #include <machine/stdarg.h>	/* for device_printf() */
 
@@ -1693,6 +1694,47 @@ device_printf(device_t dev, const char * fmt, ...)
 	return retval;
 }
 
+/**
+ * @brief Print the name of the device followed by a colon, a space
+ * and the result of calling log() with the value of @p fmt and
+ * the following arguments.
+ *
+ * @returns the number of characters printed
+ */
+int
+device_log(device_t dev, int pri, const char * fmt, ...)
+{
+	char buf[128];
+	struct sbuf sb;
+	const char *name;
+	__va_list ap;
+	size_t retval;
+
+	retval = 0;
+
+	sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN);
+
+	name = device_get_name(dev);
+
+	if (name == NULL)
+		sbuf_cat(&sb, "unknown: ");
+	else
+		sbuf_printf(&sb, "%s%d: ", name, device_get_unit(dev));
+
+	__va_start(ap, fmt);
+	sbuf_vprintf(&sb, fmt, ap);
+	__va_end(ap);
+
+	sbuf_finish(&sb);
+
+	log(pri, "%.*s", (int) sbuf_len(&sb), sbuf_data(&sb));
+	retval = sbuf_len(&sb);
+
+	sbuf_delete(&sb);
+
+	return (retval);
+}
+
 static void
 device_set_desc_internal(device_t dev, const char* desc, int copy)
 {
diff --git a/sys/sys/bus.h b/sys/sys/bus.h
index a13f092f5b..350ef926ee 100644
--- a/sys/sys/bus.h
+++ b/sys/sys/bus.h
@@ -383,6 +383,7 @@ int	device_is_enabled(device_t dev);
 int	device_is_quiet(device_t dev);
 int	device_print_prettyname(device_t dev);
 int	device_printf(device_t dev, const char *, ...) __printflike(2, 3);
+int	device_log(device_t dev, int pri, const char *, ...) __printflike(3, 4);
 int	device_probe_and_attach(device_t dev);
 int	device_probe_and_attach_gpri(device_t dev, u_int gpri);
 int	device_probe_child(device_t dev, device_t child);
diff --git a/sys/sys/mouse.h b/sys/sys/mouse.h
index 5654aeeda3..37436f717f 100644
--- a/sys/sys/mouse.h
+++ b/sys/sys/mouse.h
@@ -37,8 +37,6 @@
 #define MOUSE_SETMODE		_IOW('M', 3, mousemode_t)
 #define MOUSE_GETLEVEL		_IOR('M', 4, int)
 #define MOUSE_SETLEVEL		_IOW('M', 5, int)
-#define MOUSE_GETVARS		_IOR('M', 6, mousevar_t)
-#define MOUSE_SETVARS		_IOW('M', 7, mousevar_t)
 #define MOUSE_READSTATE		_IOWR('M', 8, mousedata_t)
 #define MOUSE_READDATA		_IOWR('M', 9, mousedata_t)
 
@@ -136,6 +134,8 @@ typedef struct synapticshw {
 	int maximumYCoord;
 	int infoXupmm;
 	int infoYupmm;
+	int forcePad;
+	int topButtonPad;
 } synapticshw_t;
 
 /* iftype */
@@ -226,19 +226,6 @@ typedef struct mousedata {
 	int buf[16];		/* data buffer */
 } mousedata_t;
 
-#if (defined(MOUSE_GETVARS))
-
-typedef struct mousevar {
-	int var[16];
-} mousevar_t;
-
-/* magic numbers in var[0] */
-#define MOUSE_VARS_PS2_SIG	0x00325350	/* 'PS2' */
-#define MOUSE_VARS_BUS_SIG	0x00535542	/* 'BUS' */
-#define MOUSE_VARS_INPORT_SIG	0x00504e49	/* 'INP' */
-
-#endif /* MOUSE_GETVARS */
-
 /* Synaptics Touchpad */
 #define MOUSE_SYNAPTICS_PACKETSIZE 6	/* '3' works better */
 
diff --git a/sys/sys/sysctl.h b/sys/sys/sysctl.h
index c31f023701..727c8acb14 100644
--- a/sys/sys/sysctl.h
+++ b/sys/sys/sysctl.h
@@ -758,6 +758,7 @@ TAILQ_HEAD(sysctl_ctx_list, sysctl_ctx_entry);
  */
 extern struct sysctl_oid_list sysctl__children;
 SYSCTL_DECL(_kern);
+SYSCTL_DECL(_kern_features);
 SYSCTL_DECL(_sysctl);
 SYSCTL_DECL(_vm);
 SYSCTL_DECL(_vfs);
@@ -822,6 +823,14 @@ struct sbuf;
 struct sbuf *sbuf_new_for_sysctl(struct sbuf *, char *, int,
 	    struct sysctl_req *);
 
+/*
+ * A macro to generate a read-only sysctl to indicate the presence of optional
+ * kernel features.
+ */
+#define FEATURE(name, desc)						\
+	SYSCTL_INT(_kern_features, OID_AUTO, name,		\
+	    CTLFLAG_RD, NULL, 1, desc)
+
 #endif	/* _KERNEL */
 
 #if !defined(_KERNEL) || defined(_KERNEL_VIRTUAL)
-- 
2.42.0

