/*
* Copyright (c) 2005, Steve C. Woodford
* Copyright (c) 2004, Ales Krenek
* Copyright (c) 2004, Kentaro A. Kurahone
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the authors nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* TODO:
* - Make the sysctl values per-instance instead of global.
* - Consider setting initial scaling factors at runtime according
* to the values returned by the 'Read Resolutions' command.
* - Support the serial protocol (we only support PS/2 for now)
* - Support auto-repeat for up/down button Z-axis emulation.
* - Maybe add some more gestures (can we use Palm support somehow?)
*/
/*
* Absolute-mode packets are decoded and passed around using
* the following structure.
*/
struct synaptics_packet {
signed short sp_x; /* Unscaled absolute X/Y coordinates */
signed short sp_y;
u_char sp_z; /* Z (pressure) */
signed short sp_sx; /* Unscaled absolute X/Y coordinates */
signed short sp_sy; /* for secondary finger */
u_char sp_sz; /* Z (pressure) */
u_char sp_w; /* W (contact patch width) */
u_char sp_primary; /* seen primary finger packet */
u_char sp_secondary; /* seen secondary finger packet */
u_char sp_finger_status; /* seen extended finger packet */
u_char sp_finger_count; /* number of fingers seen */
char sp_left; /* Left mouse button status */
char sp_right; /* Right mouse button status */
char sp_middle; /* Middle button status (possibly emulated) */
char sp_up; /* Up button status */
char sp_down; /* Down button status */
};
/* Sysctl nodes. */
static int synaptics_button_boundary_nodenum;
static int synaptics_button2_nodenum;
static int synaptics_button3_nodenum;
static int synaptics_up_down_emul_nodenum;
static int synaptics_up_down_motion_delta_nodenum;
static int synaptics_gesture_move_nodenum;
static int synaptics_gesture_length_nodenum;
static int synaptics_edge_left_nodenum;
static int synaptics_edge_right_nodenum;
static int synaptics_edge_top_nodenum;
static int synaptics_edge_bottom_nodenum;
static int synaptics_edge_motion_delta_nodenum;
static int synaptics_finger_high_nodenum;
static int synaptics_finger_low_nodenum;
static int synaptics_two_fingers_emul_nodenum;
static int synaptics_scale_x_nodenum;
static int synaptics_scale_y_nodenum;
static int synaptics_scale_z_nodenum;
static int synaptics_max_speed_x_nodenum;
static int synaptics_max_speed_y_nodenum;
static int synaptics_max_speed_z_nodenum;
static int synaptics_movement_threshold_nodenum;
static int synaptics_movement_enable_nodenum;
static int synaptics_button_region_movement_nodenum;
static int synaptics_aux_mid_button_scroll_nodenum;
static int synaptics_hscroll_pct_nodenum;
static int synaptics_vscroll_pct_nodenum;
static int synaptics_button_pct_nodenum;
/*
* copy of edges so we can recalculate edge limit if there is
* vertical scroll region
*/
static int synaptics_true_edge_right;
static int synaptics_true_edge_bottom;
/*
* invalid old values, recalculate everything
*/
static int synaptics_old_vscroll_pct = -1;
static int synaptics_old_hscroll_pct = -1;
static int synaptics_old_button_pct = -1;
static int synaptics_old_button_boundary = -1;
static int synaptics_old_edge_right = -1;
static int synaptics_old_edge_bottom = -1;
/*
* This holds the processed packet data, it is global because multiple
* packets from the trackpad may be processed when handling multiple
* fingers on the trackpad to gather all the data.
*/
static struct synaptics_packet packet;
for (i = 0; i < __arraycount(cmd); i++)
if ((cmd[i] = (u_char)va_arg(ap, int)) == 0)
break;
va_end(ap);
int res = pckbport_poll_cmd(psc->sc_kbctag, psc->sc_kbcslot, cmd, i, 0,
NULL, 0);
if (res)
device_printf(psc->sc_dev, "command error %#x\n", cmd[0]);
return res;
}
static int
synaptics_poll_reset(struct pms_softc *psc)
{
u_char resp[2];
int res;
static int
synaptics_special_write(struct pms_softc *psc, u_char command, u_char arg)
{
int res = pms_sliced_command(psc->sc_kbctag, psc->sc_kbcslot, arg);
if (res)
return res;
/* Ask about click pad */
if ((__SHIFTOUT(sc->caps, SYNAPTICS_CAP_EXTNUM) + 0x08) >=
SYNAPTICS_CONTINUED_CAPABILITIES)
{
res = synaptics_special_read(psc,
SYNAPTICS_CONTINUED_CAPABILITIES, resp);
/*
* The following describes response for the
* SYNAPTICS_CONTINUED_CAPABILITIES query.
*
* byte mask name meaning
* ---- ---- ------- ------------
* 0 0x01 adjustable threshold capacitive button sensitivity
* can be adjusted
* 0 0x02 report max query 0x0d gives max coord reported
* 0 0x04 clearpad sensor is ClearPad product
* 0 0x08 advanced gesture not particularly meaningful
* 0 0x10 clickpad bit 0 1-button ClickPad
* 0 0x60 multifinger mode identifies firmware finger counting
* (not reporting!) algorithm.
* Not particularly meaningful
* 0 0x80 covered pad W clipped to 14, 15 == pad mostly covered
* 1 0x01 clickpad bit 1 2-button ClickPad
* 1 0x02 deluxe LED controls touchpad support LED commands
* ala multimedia control bar
* 1 0x04 reduced filtering firmware does less filtering on
* position data, driver should watch
* for noise.
* 1 0x08 image sensor image sensor tracks 5 fingers, but only
* reports 2.
* 1 0x10 uniform clickpad whole clickpad moves instead of being
* hinged at the top.
* 1 0x20 report min query 0x0f gives min coord reported
*/
if (res == 0) {
uint val = SYN_CCAP_VALUE(resp);
DPRINTF(10, sc, "Continued "
"Capabilities 0x%02x 0x%02x 0x%02x.\n",
resp[0], resp[1], resp[2]);
switch (SYN_CCAP_CLICKPAD_TYPE(val)) {
case 0: /* not a clickpad */
break;
case 1:
sc->flags |= SYN_FLAG_HAS_ONE_BUTTON_CLICKPAD;
break;
case 2:
sc->flags |= SYN_FLAG_HAS_TWO_BUTTON_CLICKPAD;
break;
case 3: /* reserved */
default:
/* unreached */
break;
}
if ((val & SYN_CCAP_HAS_ADV_GESTURE_MODE))
sc->flags |= SYN_FLAG_HAS_ADV_GESTURE_MODE;
if ((val & SYN_CCAP_REPORT_MAX))
sc->flags |= SYN_FLAG_HAS_MAX_REPORT;
if ((val & SYN_CCAP_REPORT_MIN))
sc->flags |= SYN_FLAG_HAS_MIN_REPORT;
}
}
}
/* Check for minimum version and print a nice message. */
ver_major = resp[2] & 0x0f;
ver_minor = resp[0];
aprint_normal_dev(psc->sc_dev, "Synaptics touchpad version %d.%d\n",
ver_major, ver_minor);
if (ver_major * 10 + ver_minor < SYNAPTICS_MIN_VERSION) {
/* No capability query support. */
sc->caps = 0;
goto done;
}
/* Query the hardware capabilities. */
res = synaptics_special_read(psc, SYNAPTICS_READ_CAPABILITIES, resp);
if (res) {
/* Hmm, failed to get capabilities. */
aprint_error_dev(psc->sc_dev,
"synaptics_probe: Failed to query capabilities.\n");
goto doreset;
}
sc->caps = SYNAPTICS_CAP_VALUE(resp);
if (sc->caps & SYNAPTICS_CAP_MBUTTON)
sc->flags |= SYN_FLAG_HAS_MIDDLE_BUTTON;
if (sc->caps & SYNAPTICS_CAP_4BUTTON)
sc->flags |= SYN_FLAG_HAS_BUTTONS_4_5;
if (sc->caps & SYNAPTICS_CAP_EXTENDED) {
pms_synaptics_probe_extended(psc);
}
if (sc->flags) {
const char comma[] = ", ";
const char *sep = "";
aprint_normal_dev(psc->sc_dev, "");
for (size_t f = 0; f < __arraycount(syn_flags); f++) {
if (sc->flags & syn_flags[f].bit) {
aprint_normal("%s%s", sep, syn_flags[f].desc);
sep = comma;
}
}
aprint_normal("\n");
}
if (sc->flags & SYN_FLAG_HAS_MAX_REPORT) {
res = synaptics_special_read(psc, SYNAPTICS_READ_MAX_COORDS,
resp);
if (res) {
aprint_error_dev(psc->sc_dev,
"synaptics_probe: Failed to query max coords.\n");
} else {
synaptics_edge_right = (resp[0] << 5) +
((resp[1] & 0x0f) << 1);
synaptics_edge_top = (resp[2] << 5) +
((resp[1] & 0xf0) >> 3);
synaptics_true_edge_right = synaptics_edge_right;
/*
* If we have vertical scroll then steal 10%
* for that region.
*/
if (sc->flags & SYN_FLAG_HAS_VERTICAL_SCROLL)
synaptics_edge_right -=
synaptics_edge_right / 10;
if (sc->flags & SYN_FLAG_HAS_PASSTHROUGH) {
/*
* Extended capability probes can confuse the passthrough
* device; reset the touchpad now to cure that.
*/
res = synaptics_poll_reset(psc);
}
/*
* Enable Absolute mode with W (width) reporting, and set
* the packet rate to maximum (80 packets per second). Enable
* extended W mode if supported so we can report second finger
* position.
*/
enable_modes =
SYNAPTICS_MODE_ABSOLUTE | SYNAPTICS_MODE_W | SYNAPTICS_MODE_RATE;
if (sc->flags & SYN_FLAG_HAS_EXTENDED_WMODE)
enable_modes |= SYNAPTICS_MODE_EXTENDED_W;
/*
* Synaptics documentation says to disable device before
* setting mode.
*/
synaptics_poll_cmd(psc, PMS_DEV_DISABLE, 0);
/* a couple of set scales to clear out pending commands */
for (i = 0; i < 2; i++)
synaptics_poll_cmd(psc, PMS_SET_SCALE11, 0);
res = synaptics_special_write(psc, SYNAPTICS_CMD_SET_MODE2, enable_modes);
if (res)
device_printf(psc->sc_dev, "set mode error\n");
/* a couple of set scales to clear out pending commands */
for (i = 0; i < 2; i++)
synaptics_poll_cmd(psc, PMS_SET_SCALE11, 0);
/* Set advanced gesture mode */
if ((sc->flags & SYN_FLAG_HAS_EXTENDED_WMODE) ||
(sc->flags & SYN_FLAG_HAS_ADV_GESTURE_MODE))
synaptics_special_write(psc, SYNAPTICS_WRITE_DELUXE_3, 0x3);
/* Disable motion in the button region for clickpads */
if(sc->flags & SYN_FLAG_HAS_ONE_BUTTON_CLICKPAD)
synaptics_button_region_movement = 0;
if ((rc = sysctl_createv(clog, 0, NULL, &node,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
CTLTYPE_INT, "gesture_length",
SYSCTL_DESCR("Time period in which tap is recognised as a gesture"),
pms_sysctl_synaptics_verify, 0,
&synaptics_gesture_length,
0, CTL_HW, root_num, CTL_CREATE,
CTL_EOL)) != 0)
goto err;
if ((rc = sysctl_createv(clog, 0, NULL, &node,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
CTLTYPE_BOOL, "aux_mid_button_scroll",
SYSCTL_DESCR("Interpret Y-Axis movement with the middle button held as scrolling on the passthrough device (e.g. TrackPoint)"),
pms_sysctl_synaptics_verify, 0,
&synaptics_aux_mid_button_scroll,
0, CTL_HW, root_num, CTL_CREATE,
CTL_EOL)) != 0)
goto err;
/* Sanity check the params. */
if (node.sysctl_num == synaptics_up_down_emul_nodenum) {
if (t < 0 || t > 3)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_two_fingers_emul_nodenum) {
if (t < 0 || t > 2)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_gesture_length_nodenum ||
node.sysctl_num == synaptics_edge_motion_delta_nodenum ||
node.sysctl_num == synaptics_up_down_motion_delta_nodenum ||
node.sysctl_num == synaptics_max_speed_x_nodenum ||
node.sysctl_num == synaptics_max_speed_y_nodenum ||
node.sysctl_num == synaptics_max_speed_z_nodenum) {
if (t < 0)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_edge_left_nodenum ||
node.sysctl_num == synaptics_edge_bottom_nodenum) {
if (t < 0 || t > (SYNAPTICS_EDGE_MAX / 2))
return (EINVAL);
} else
if (node.sysctl_num == synaptics_edge_right_nodenum ||
node.sysctl_num == synaptics_edge_top_nodenum) {
if (t < (SYNAPTICS_EDGE_MAX / 2))
return (EINVAL);
} else
if (node.sysctl_num == synaptics_scale_x_nodenum ||
node.sysctl_num == synaptics_scale_y_nodenum ||
node.sysctl_num == synaptics_scale_z_nodenum) {
if (t < 1 || t > (SYNAPTICS_EDGE_MAX / 4))
return (EINVAL);
} else
if (node.sysctl_num == synaptics_finger_high_nodenum) {
if (t < 0 || t > SYNAPTICS_FINGER_PALM ||
t < synaptics_finger_low)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_finger_low_nodenum) {
if (t < 0 || t > SYNAPTICS_FINGER_PALM ||
t > synaptics_finger_high)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_gesture_move_nodenum ||
node.sysctl_num == synaptics_movement_threshold_nodenum) {
if (t < 0 || t > (SYNAPTICS_EDGE_MAX / 4))
return (EINVAL);
} else
if (node.sysctl_num == synaptics_button_boundary_nodenum) {
if (t < 0 || t < synaptics_edge_bottom ||
t > synaptics_edge_top)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_button2_nodenum ||
node.sysctl_num == synaptics_button3_nodenum) {
if (t < synaptics_edge_left || t > synaptics_edge_right)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_movement_enable_nodenum) {
if (t < 0 || t > 1)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_button_region_movement_nodenum) {
if (t < 0 || t > 1)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_aux_mid_button_scroll_nodenum) {
if (t < 0 || t > 1)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_vscroll_pct_nodenum) {
if (t < 0 || t > 100)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_hscroll_pct_nodenum) {
if (t < 0 || t > 100)
return (EINVAL);
} else
if (node.sysctl_num == synaptics_button_pct_nodenum) {
if (t < 0 || t > 100)
return (EINVAL);
} else
return (EINVAL);
*(int *)rnode->sysctl_data = t;
pms_synaptics_set_boundaries();
return (0);
}
/*
* Extract the number of fingers from the current packet and return
* it to the caller.
*/
static unsigned
pms_synaptics_get_fingers(struct pms_softc *psc, u_char w, short z)
{
struct synaptics_softc *sc = &psc->u.synaptics;
unsigned short ew_mode;
unsigned fingers;
fingers = 0;
/*
* If w is zero and z says no fingers then return
* no fingers, w == can also mean 2 fingers... confusing.
*/
if (w == 0 && z == SYNAPTICS_FINGER_NONE)
return 0;
if ((sc->flags & SYN_FLAG_HAS_EXTENDED_WMODE) &&
(w == SYNAPTICS_WIDTH_EXTENDED_W)) {
ew_mode = psc->packet[5] >> 4;
switch (ew_mode)
{
case SYNAPTICS_EW_WHEEL:
break;
case SYNAPTICS_EW_SECONDARY_FINGER:
/* to get here we must have 2 fingers at least */
fingers = 2;
break;
case SYNAPTICS_EW_FINGER_STATUS:
fingers = psc->packet[1] & 0x0f;
break;
default:
device_printf(psc->sc_dev,
"invalid extended w mode %d\n",
ew_mode);
return 0; /* pretend there are no fingers */
}
} else {
fingers = 1;
/*
* If SYN_FLAG_HAS_MULTI_FINGER is set then check
* sp_w is below SYNAPTICS_WIDTH_FINGER_MIN, if it is
* then this will be the finger count.
*
* There are a couple of "special" values otherwise
* just punt with one finger, if this really is a palm
* then it will be caught later.
*/
if (sc->flags & (SYN_FLAG_HAS_MULTI_FINGER | SYN_FLAG_HAS_MULTI_FINGER_REPORT)) {
if (w == SYNAPTICS_WIDTH_TWO_FINGERS)
fingers = 2;
else if (w == SYNAPTICS_WIDTH_THREE_OR_MORE)
fingers = 3;
}
}
return fingers;
}
/* Masks for the first byte of a packet */
#define PMS_LBUTMASK 0x01
#define PMS_RBUTMASK 0x02
#define PMS_MBUTMASK 0x04
/*
* Check if the x and y are non-zero that they
* are within the bounds of the trackpad
* otherwise ignore the packet.
*/
if (((nsp.sp_sx != 0) &&
((nsp.sp_sx < synaptics_edge_left) ||
(nsp.sp_sx > synaptics_edge_right))) ||
((nsp.sp_sy != 0) &&
((nsp.sp_sy < synaptics_edge_bottom) ||
(nsp.sp_sy > synaptics_edge_top)))) {
sc->gesture_type = 0;
sc->gesture_buttons = 0;
sc->total_packets--;
DPRINTF(20, sc,
"synaptics_parse: dropping out of bounds "
"packet sp_sx %d sp_sy %d\n",
nsp.sp_sx, nsp.sp_sy);
return;
}
/* work out the virtual finger width */
v = 8 + (psc->packet[1] & 0x01) +
((psc->packet[2] & 0x01) << 1) +
((psc->packet[5] & 0x01) << 2);
/* keep same buttons down as primary */
nsp.sp_left = sc->button_history & PMS_LBUTMASK;
nsp.sp_middle = sc->button_history & PMS_MBUTMASK;
nsp.sp_right = sc->button_history & PMS_RBUTMASK;
break;
case SYNAPTICS_EW_FINGER_STATUS:
/* This works but what is it good for?
* it gives us an index of the primary/secondary
* fingers but no other packets pass this
* index.
*
* XXX Park this, possibly handle a finger
* XXX change if indexes change.
*/
primary_finger = psc->packet[2];
secondary_finger = psc->packet[4];
nsp.sp_finger_status = 1;
nsp.sp_finger_count = pms_synaptics_get_fingers(psc,
nsp.sp_w, nsp.sp_z);
goto skip_position;
/*
* If the trackpad has external buttons and one of
* those buttons is pressed then the lower bits of
* x and y are "stolen" for button status. We can tell
* this has happened by doing an xor of the two right
* button status bits residing in byte 0 and 3, if the
* result is non-zero then there is an external button
* report and the position bytes need to be masked.
*/
btn_mask = 0xff;
if ((sc->num_buttons > 0) &&
((psc->packet[0] & PMS_RBUTMASK) ^
(psc->packet[3] & PMS_RBUTMASK))) {
btn_mask = sc->button_mask;
}
/*
* If SYN_FLAG_HAS_MULTI_FINGER is set then check
* sp_w is below SYNAPTICS_WIDTH_FINGER_MIN, if it is
* then this will be the finger count.
*
* There are a couple of "special" values otherwise
* just punt with one finger, if this really is a palm
* then it will be caught later.
*/
if ((sc->flags & SYN_FLAG_HAS_MULTI_FINGER) &&
((nsp.sp_w == SYNAPTICS_WIDTH_TWO_FINGERS) ||
(nsp.sp_w == SYNAPTICS_WIDTH_THREE_OR_MORE))) {
/*
* To make life interesting if there are
* two or more fingers on the touchpad then
* the coordinate reporting changes and an extra
* "virtual" finger width is reported.
*/
nsp.sp_x = (packet4 & 0xfc) +
((packet4 & 0x02) << 1) +
((psc->packet[1] & 0x0f) << 8) +
((psc->packet[3] & 0x10) << 8);
/*
* Check if the x and y are non-zero that they
* are within the bounds of the trackpad
* otherwise ignore the packet.
*/
if (((nsp.sp_x != 0) &&
((nsp.sp_x < synaptics_edge_left) ||
(nsp.sp_x > synaptics_edge_right))) ||
((nsp.sp_y != 0) &&
((nsp.sp_y < synaptics_edge_bottom) ||
(nsp.sp_y > synaptics_edge_top)))) {
sc->gesture_type = 0;
sc->gesture_buttons = 0;
sc->total_packets--;
DPRINTF(20, sc,
"synaptics_parse: dropping out of bounds packet "
"sp_x %d sp_y %d\n",
nsp.sp_x, nsp.sp_y);
return;
}
/*
* We don't have extended W so we only know if there
* are multiple fingers on the touchpad, only the primary
* location is reported so just pretend we have an
* unmoving second finger.
*/
if (((sc->flags & SYN_FLAG_HAS_EXTENDED_WMODE)
!= SYN_FLAG_HAS_EXTENDED_WMODE) &&
(nsp.sp_finger_count > 1)) {
nsp.sp_secondary = 1;
nsp.sp_sx = 0;
nsp.sp_sy = 0;
nsp.sp_sz = 0;
}
new_buttons = 0;
if(sc->flags & SYN_FLAG_HAS_ONE_BUTTON_CLICKPAD) {
/* This is not correctly specified. Read this button press
* from L/U bit. Emulate 3 buttons by checking the
* coordinates of the click and returning the appropriate
* button code. Outside the button region default to a
* left click.
*/
u_char bstate = (psc->packet[0] ^ psc->packet[3])
& 0x01;
if (nsp.sp_y < synaptics_button_boundary) {
if (nsp.sp_x > synaptics_button3) {
nsp.sp_right =
bstate ? PMS_RBUTMASK : 0;
} else if (nsp.sp_x > synaptics_button2) {
nsp.sp_middle =
bstate ? PMS_MBUTMASK : 0;
} else {
nsp.sp_left = bstate ? PMS_LBUTMASK : 0;
}
} else
nsp.sp_left = bstate ? 1 : 0;
new_buttons = nsp.sp_left | nsp.sp_middle | nsp.sp_right;
if (new_buttons != sc->button_history) {
if (sc->button_history == 0)
sc->button_history = new_buttons;
else if (new_buttons == 0) {
sc->button_history = 0;
/* ensure all buttons are cleared just in
* case finger comes off in a different
* region.
*/
nsp.sp_left = 0;
nsp.sp_middle = 0;
nsp.sp_right = 0;
} else {
/* make sure we keep the same button even
* if the finger moves to a different
* region. This precludes chording
* but, oh well.
*/
nsp.sp_left = sc->button_history & PMS_LBUTMASK;
nsp.sp_middle = sc->button_history
& PMS_MBUTMASK;
nsp.sp_right = sc->button_history & PMS_RBUTMASK;
}
}
} else if (sc->flags & SYN_FLAG_HAS_MIDDLE_BUTTON) {
/* Old style Middle Button. */
nsp.sp_middle = (psc->packet[0] & PMS_LBUTMASK) ^
(psc->packet[3] & PMS_LBUTMASK);
} else {
nsp.sp_middle = 0;
}
/*
* Overlay extended button state if anything changed,
* preserve the state if a button is being held.
*/
if (ext_left != -1)
nsp.sp_left = sc->ext_left = ext_left;
else if (sc->ext_left != 0)
nsp.sp_left = sc->ext_left;
if (ext_right != -1)
nsp.sp_right = sc->ext_right = ext_right;
else if (sc->ext_right != 0)
nsp.sp_right = sc->ext_right;
if (ext_middle != -1)
nsp.sp_middle = sc->ext_middle = ext_middle;
else if (sc->ext_middle != 0)
nsp.sp_middle = sc->ext_middle;
if (ext_up != -1)
nsp.sp_up = sc->ext_up = ext_up;
else if (sc->ext_up != 0)
nsp.sp_up = sc->ext_up;
if (ext_down != -1)
nsp.sp_down = sc->ext_down = ext_down;
else if (sc->ext_down != 0)
nsp.sp_down = sc->ext_down;
switch (synaptics_up_down_emul) {
case 1:
/* Do middle button emulation using up/down buttons */
nsp.sp_middle = nsp.sp_up | nsp.sp_down;
nsp.sp_up = nsp.sp_down = 0;
break;
case 3:
/* Do left/right button emulation using up/down buttons */
nsp.sp_left = nsp.sp_left | nsp.sp_up;
nsp.sp_right = nsp.sp_right | nsp.sp_down;
nsp.sp_up = nsp.sp_down = 0;
break;
default:
/*
* Don't do any remapping...
* Z-axis emulation is handled in pms_synaptics_process_packet
*/
break;
}
}
/* set the finger count only if we haven't seen an extended-w
* finger count status
*/
if (nsp.sp_finger_status == 0)
nsp.sp_finger_count = pms_synaptics_get_fingers(psc, nsp.sp_w,
nsp.sp_z);
/*
* Passthrough is used for e.g. TrackPoints and additional pointing
* devices connected to a Synaptics touchpad.
*/
static void
pms_synaptics_passthrough(struct pms_softc *psc)
{
int dx, dy, dz;
int buttons, changed;
int s;
switch (psc->inputstate) {
case -5:
case -4:
case -3:
case -2:
case -1:
case 0:
if ((data & 0xc8) != 0x80) {
device_printf(psc->sc_dev,
"pms_synaptics_input: 0x%02x out of sync\n", data);
/* use negative counts to limit resync phase */
psc->inputstate--;
return; /* not in sync yet, discard input */
}
psc->inputstate = 0;
/*FALLTHROUGH*/
case -6:
case 3:
if ((data & 8) == 8) {
device_printf(psc->sc_dev,
"pms_synaptics_input: dropped in relative mode, reset\n");
psc->inputstate = 0;
psc->sc_enabled = 0;
wakeup(&psc->sc_enabled);
return;
}
}
if (psc->inputstate >= sizeof(psc->packet))
panic("inputstate should never be %d", psc->inputstate);
psc->packet[psc->inputstate++] = data & 0xff;
if (psc->inputstate == 6) {
struct synaptics_softc *sc = &psc->u.synaptics;
DPRINTF(10, sc, "synaptics: packet: 0x%02x%02x%02x%02x%02x%02x\n",
psc->packet[0], psc->packet[1], psc->packet[2],
psc->packet[3], psc->packet[4], psc->packet[5]);
/*
* We have a complete packet.
* Extract the pertinent details.
*/
psc->inputstate = 0;
if ((psc->packet[0] & 0xfc) == 0x84 &&
(psc->packet[3] & 0xcc) == 0xc4) {
/* W = SYNAPTICS_WIDTH_PASSTHROUGH, PS/2 passthrough */
pms_synaptics_passthrough(psc);
} else {
pms_synaptics_parse(psc);
}
}
}
static inline int
synaptics_finger_detect(struct synaptics_softc *sc, struct synaptics_packet *sp,
int *palmp)
{
int fingers;
/* Assume no palm */
*palmp = 0;
/*
* Apply some hysteresis when checking for a finger.
* When the finger is first applied, we ignore it until the
* pressure exceeds the 'high' threshold. The finger is considered
* removed only when pressure falls beneath the 'low' threshold.
*/
if ((sc->prev_fingers == 0 && sp->sp_z > synaptics_finger_high) ||
(sc->prev_fingers != 0 && sp->sp_z > synaptics_finger_low))
fingers = 1;
else
fingers = 0;
/*
* If the pad can't do palm detection, skip the rest.
*/
if (fingers == 0 || (sc->flags & SYN_FLAG_HAS_PALM_DETECT) == 0)
return (fingers);
if (sc->prev_fingers == 0 &&
(sp->sp_z > SYNAPTICS_FINGER_FLAT ||
sp->sp_w >= SYNAPTICS_WIDTH_PALM_MIN)) {
/*
* Contact area or pressure is too great to be a finger.
* Just ignore it for now.
*/
return (0);
}
/*
* Detect 2 and 3 fingers if supported, but only if multiple
* fingers appear within the tap gesture time period.
*/
if ((sc->flags & SYN_FLAG_HAS_MULTI_FINGER) &&
((SYN_TIME(sc, sc->gesture_start_packet)
< synaptics_gesture_length) ||
SYN_TIME(sc, sc->gesture_start_packet)
< synaptics_gesture_length)) {
switch (sp->sp_w) {
case SYNAPTICS_WIDTH_TWO_FINGERS:
fingers = 2;
break;
case SYNAPTICS_WIDTH_THREE_OR_MORE:
fingers = 3;
break;
default:
/*
* The width value can report spurious single-finger
* events after a multi-finger event.
*/
fingers = sc->prev_fingers <= 1 ? 1 : sc->prev_fingers;
break;
}
}
return (fingers);
}
static inline void
synaptics_gesture_detect(struct synaptics_softc *sc,
struct synaptics_packet *sp, int fingers)
{
int gesture_len, gesture_buttons;
int set_buttons;
if (fingers > 0 && (fingers == sc->prev_fingers)) {
/* Finger is still present */
sc->gesture_move_x = abs(sc->gesture_start_x - sp->sp_x);
sc->gesture_move_y = abs(sc->gesture_start_y - sp->sp_y);
} else
if (fingers && sc->prev_fingers == 0) {
/*
* Finger was just applied.
* If the previous gesture was a single-click, set things
* up to deal with a possible drag or double-click gesture.
* Basically, if the finger is removed again within
* 'synaptics_gesture_length' packets, this is treated
* as a double-click. Otherwise we will emulate holding
* the left button down whilst dragging the mouse.
*/
if (SYN_IS_SINGLE_TAP(sc->gesture_type))
sc->gesture_type |= SYN_GESTURE_DRAG;
DPRINTF(10, sc, "Finger applied:"
" gesture_start_x: %d"
" gesture_start_y: %d\n",
sc->gesture_start_x, sc->gesture_start_y);
} else
if (fingers == 0 && sc->prev_fingers != 0) {
/*
* Finger was just removed.
* Check if the contact time and finger movement were
* small enough to qualify as a gesture.
* Ignore finger movement if multiple fingers were
* detected (the pad may report coordinates for any
* of the fingers).
*/
if (gesture_len < synaptics_gesture_length &&
((sc->gesture_move_x < synaptics_gesture_move &&
sc->gesture_move_y < synaptics_gesture_move))) {
/*
* Looking good so far.
*/
if (SYN_IS_DRAG(sc->gesture_type)) {
/*
* Promote this gesture to double-click.
*/
sc->gesture_type |= SYN_GESTURE_DOUBLE;
sc->gesture_type &= ~SYN_GESTURE_SINGLE;
} else {
/*
* Single tap gesture. Set the tap length timer
* and flag a single-click.
*/
sc->gesture_tap_packet = sc->total_packets;
sc->gesture_type |= SYN_GESTURE_SINGLE;
/*
* The gesture can be modified depending on
* the number of fingers detected.
*
* 1: Normal left button emulation.
* 2: Either middle button or right button
* depending on the value of the two_fingers
* sysctl variable.
* 3: Right button.
*/
switch (sc->prev_fingers) {
case 2:
if (synaptics_two_fingers_emul == 1)
gesture_buttons |= PMS_RBUTMASK;
else
if (synaptics_two_fingers_emul == 2)
gesture_buttons |= PMS_MBUTMASK;
break;
case 3:
gesture_buttons |= PMS_RBUTMASK;
break;
default:
gesture_buttons |= PMS_LBUTMASK;
break;
}
}
}
/*
* Always clear drag state when the finger is removed.
*/
sc->gesture_type &= ~SYN_GESTURE_DRAG;
}
if (sc->gesture_type == 0) {
/*
* There is no gesture in progress.
* Clear emulated button state.
*/
sc->gesture_buttons = 0;
return;
}
/*
* A gesture is in progress.
*/
set_buttons = 0;
if (SYN_IS_SINGLE_TAP(sc->gesture_type)) {
/*
* Single-click.
* Activate the relevant button(s) until the
* gesture tap timer has expired.
*/
if (SYN_TIME(sc, sc->gesture_tap_packet) <
synaptics_gesture_length)
set_buttons = 1;
else
sc->gesture_type &= ~SYN_GESTURE_SINGLE;
DPRINTF(10, sc, "synaptics_gesture: single tap, buttons %d\n",
set_buttons);
DPRINTF(10, sc, "synaptics_gesture: single tap, tap at %d, current %d\n",
sc->gesture_tap_packet, sc->total_packets);
DPRINTF(10, sc, "synaptics_gesture: single tap, tap_time %d, gesture len %d\n",
SYN_TIME(sc, sc->gesture_tap_packet), synaptics_gesture_length);
} else
if (SYN_IS_DOUBLE_TAP(sc->gesture_type) && sc->prev_fingers == 0) {
/*
* Double-click.
* Activate the relevant button(s) once.
*/
set_buttons = 1;
sc->gesture_type &= ~SYN_GESTURE_DOUBLE;
}
if (set_buttons || SYN_IS_DRAG(sc->gesture_type)) {
/*
* Single-click and drag.
* Maintain button state until the finger is removed.
*/
sp->sp_left |= gesture_buttons & PMS_LBUTMASK;
sp->sp_right |= gesture_buttons & PMS_RBUTMASK;
sp->sp_middle |= gesture_buttons & PMS_MBUTMASK;
}
sc->gesture_buttons = gesture_buttons;
}
static inline int
synaptics_filter_policy(struct synaptics_softc *sc, int finger, int *history,
int value, u_int count)
{
int a, b, rv;
/*
* Once we've accumulated at least SYN_HIST_SIZE values, combine
* each new value with the previous two and return the average.
*
* This is necessary when the touchpad is operating in 80 packets
* per second mode, as it performs little internal filtering on
* reported values.
*
* Using a rolling average helps to filter out jitter caused by
* tiny finger movements.
*/
if (count >= SYN_HIST_SIZE) {
a = (history[(count - 2) % SYN_HIST_SIZE] +
history[(count - 1) % SYN_HIST_SIZE]) / 2;
static inline int
synaptics_check_edge(int x, int y)
{
int rv = 0;
if (x < synaptics_edge_left)
rv |= SYN_EDGE_LEFT;
else
if (x > synaptics_edge_right)
rv |= SYN_EDGE_RIGHT;
if (y < synaptics_edge_bottom)
rv |= SYN_EDGE_BOTTOM;
else
if (y > synaptics_edge_top)
rv |= SYN_EDGE_TOP;
return (rv);
}
static inline int
synaptics_edge_motion(struct synaptics_softc *sc, int delta, int dir)
{
/*
* When edge motion is enabled, synaptics_edge_motion_delta is
* combined with the current delta, together with the direction
* in which to simulate the motion. The result is added to
* the delta derived from finger movement. This provides a smooth
* transition from finger movement to edge motion.
*/
delta = synaptics_edge_motion_delta + (dir * delta);
if (delta < 0)
return (0);
if (delta > synaptics_edge_motion_delta)
return (synaptics_edge_motion_delta);
return (delta);
}
static inline int
synaptics_scale(int delta, int scale, int *remp)
{
int rv;
/*
* Scale the raw delta in Synaptics coordinates (0-6143) into
* something more reasonable by dividing the raw delta by a
* scale factor. Any remainder from the previous scale result
* is added to the current delta before scaling.
* This prevents loss of resolution for very small/slow
* movements of the finger.
*/
delta += *remp;
rv = delta / scale;
*remp = delta % scale;
return (rv);
}
static inline void
synaptics_movement(struct synaptics_softc *sc, struct synaptics_packet *sp,
int *dxp, int *dyp, int *dzp, int *sdxp, int *sdyp, int *sdzp)
{
int dx, dy, dz, sdx, sdy, sdz, edge;
dx = dy = dz = sdx = sdy = sdz = 0;
/*
* Compute the next values of dx, dy, dz, sdx, sdy, sdz.
*/
dx = synaptics_filter_policy(sc, 0,
sc->history_x[SYN_PRIMARY_FINGER], sp->sp_x,
sc->packet_count[SYN_PRIMARY_FINGER]);
dy = synaptics_filter_policy(sc, 0,
sc->history_y[SYN_PRIMARY_FINGER], sp->sp_y,
sc->packet_count[SYN_PRIMARY_FINGER]);
sc->packet_count[SYN_PRIMARY_FINGER]++;
/*
* If we're dealing with a drag gesture, and the finger moves to
* the edge of the touchpad, apply edge motion emulation if it
* is enabled.
*/
if (synaptics_edge_motion_delta && SYN_IS_DRAG(sc->gesture_type)) {
edge = synaptics_check_edge(sp->sp_x, sp->sp_y);
if (edge & SYN_EDGE_LEFT)
dx -= synaptics_edge_motion(sc, dx, 1);
if (edge & SYN_EDGE_RIGHT)
dx += synaptics_edge_motion(sc, dx, -1);
if (edge & SYN_EDGE_BOTTOM)
dy -= synaptics_edge_motion(sc, dy, 1);
if (edge & SYN_EDGE_TOP)
dy += synaptics_edge_motion(sc, dy, -1);
if (sp->sp_finger_count > 1) {
edge = synaptics_check_edge(sp->sp_sx, sp->sp_sy);
if (edge & SYN_EDGE_LEFT)
sdx -= synaptics_edge_motion(sc, sdx, 1);
if (edge & SYN_EDGE_RIGHT)
sdx += synaptics_edge_motion(sc, sdx, -1);
if (edge & SYN_EDGE_BOTTOM)
sdy -= synaptics_edge_motion(sc, sdy, 1);
if (edge & SYN_EDGE_TOP)
sdy += synaptics_edge_motion(sc, sdy, -1);
}
}
/*
* Apply scaling to the deltas
*/
dx = synaptics_scale(dx, synaptics_scale_x,
&sc->rem_x[SYN_PRIMARY_FINGER]);
dy = synaptics_scale(dy, synaptics_scale_y,
&sc->rem_y[SYN_PRIMARY_FINGER]);
dz = synaptics_scale(dz, synaptics_scale_z,
&sc->rem_z[SYN_PRIMARY_FINGER]);
static void
pms_synaptics_process_packet(struct pms_softc *psc, struct synaptics_packet *sp)
{
struct synaptics_softc *sc = &psc->u.synaptics;
int dx, dy, dz, sdx, sdy, sdz;
int fingers, palm, buttons, changed;
int s;
sdx = sdy = sdz = 0;
/*
* Do Z-axis emulation using up/down buttons if required.
* Note that the pad will send a one second burst of packets
* when an up/down button is pressed and held. At the moment
* we don't deal with auto-repeat, so convert the burst into
* a one-shot.
*/
dz = 0;
if (synaptics_up_down_emul == 2) {
if (sc->up_down == 0) {
if (sp->sp_up && sp->sp_down) {
sp->sp_middle = 1;
} else if (sp->sp_up) {
dz = -synaptics_up_down_motion_delta;
} else if (sp->sp_down) {
dz = synaptics_up_down_motion_delta;
}
}
/*
* Determine whether or not a finger is on the pad.
* On some pads, this will return the number of fingers
* detected.
*/
fingers = synaptics_finger_detect(sc, sp, &palm);
/*
* Do gesture processing only if we didn't detect a palm.
*/
if (palm == 0)
synaptics_gesture_detect(sc, sp, fingers);
else
sc->gesture_type = sc->gesture_buttons = 0;
/*
* Do movement processing IFF we have a single finger and no palm or
* a secondary finger and no palm.
*/
if (palm == 0 && synaptics_movement_enable) {
if (fingers > 0) {
synaptics_movement(sc, sp, &dx, &dy, &dz, &sdx, &sdy,
&sdz);
/*
* Check if there are two fingers, if there are then
* check if we have a clickpad, if we do then we
* don't scroll iff one of the fingers is in the
* button region. Otherwise interpret as a scroll
*/
if (sp->sp_finger_count >= 2 && sc->gesture_type == 0 ) {
if (!(sc->flags & SYN_FLAG_HAS_ONE_BUTTON_CLICKPAD) ||
((sc->flags & SYN_FLAG_HAS_ONE_BUTTON_CLICKPAD) &&
(sp->sp_y > synaptics_button_boundary) &&
(sp->sp_sy > synaptics_button_boundary))) {
s = spltty();
wsmouse_precision_scroll(
psc->sc_wsmousedev, dx, dy);
splx(s);
return;
}
}
/*
* Allow user to turn off movements in the button
* region of a click pad.
*/
if (sc->flags & SYN_FLAG_HAS_ONE_BUTTON_CLICKPAD) {
if ((sp->sp_y < synaptics_button_boundary) &&
(!synaptics_button_region_movement)) {
dx = dy = dz = 0;
}
/* Now work out which finger to report, just
* go with the one that has moved the most.
*/
if ((abs(dx) + abs(dy) + abs(dz)) <
(abs(sdx) + abs(sdy) + abs(sdz))) {
/* secondary finger wins */
dx = sdx;
dy = sdy;
dz = sdz;
}
} else {
/*
* No valid finger. Therefore no movement.
*/
sc->rem_x[SYN_PRIMARY_FINGER] = 0;
sc->rem_y[SYN_PRIMARY_FINGER] = 0;
sc->rem_x[SYN_SECONDARY_FINGER] = 0;
sc->rem_y[SYN_SECONDARY_FINGER] = 0;
dx = dy = 0;