Author: dumbbell
Date: Tue Oct 14 17:48:36 2008
New Revision: 183888
URL: http://svn.freebsd.org/changeset/base/183888

Log:
  Rewrite Synaptics touchpads support with the following goals in mind:
      o  better quality of the movement smoothing
      o  more features such as tap-hold and virtual scrolling
  
  Support must still be enabled with this line in your /boot/loader.conf:
      hw.psm.synaptics_support="1"
  
  The following sysctls were removed:
      hw.psm.synaptics.low_speed_threshold
      hw.psm.synaptics.min_movement
      hw.psm.synaptics.squelch_level
  
  An overview of this new driver and a short documentation about the added
  sysctls is available on the wiki:
  http://wiki.freebsd.org/SynapticsTouchpad

Modified:
  head/sys/dev/atkbdc/psm.c

Modified: head/sys/dev/atkbdc/psm.c
==============================================================================
--- head/sys/dev/atkbdc/psm.c   Tue Oct 14 17:47:29 2008        (r183887)
+++ head/sys/dev/atkbdc/psm.c   Tue Oct 14 17:48:36 2008        (r183888)
@@ -163,15 +163,101 @@ typedef struct packetbuf {
 #define        PSM_PACKETQUEUE 128
 #endif
 
+enum {
+       SYNAPTICS_SYSCTL_MIN_PRESSURE,
+       SYNAPTICS_SYSCTL_MAX_PRESSURE,
+       SYNAPTICS_SYSCTL_MAX_WIDTH,
+       SYNAPTICS_SYSCTL_MARGIN_TOP,
+       SYNAPTICS_SYSCTL_MARGIN_RIGHT,
+       SYNAPTICS_SYSCTL_MARGIN_BOTTOM,
+       SYNAPTICS_SYSCTL_MARGIN_LEFT,
+       SYNAPTICS_SYSCTL_NA_TOP,
+       SYNAPTICS_SYSCTL_NA_RIGHT,
+       SYNAPTICS_SYSCTL_NA_BOTTOM,
+       SYNAPTICS_SYSCTL_NA_LEFT,
+       SYNAPTICS_SYSCTL_WINDOW_MIN,
+       SYNAPTICS_SYSCTL_WINDOW_MAX,
+       SYNAPTICS_SYSCTL_MULTIPLICATOR,
+       SYNAPTICS_SYSCTL_WEIGHT_CURRENT,
+       SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS,
+       SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA,
+       SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED,
+       SYNAPTICS_SYSCTL_DIV_MIN,
+       SYNAPTICS_SYSCTL_DIV_MAX,
+       SYNAPTICS_SYSCTL_DIV_MAX_NA,
+       SYNAPTICS_SYSCTL_DIV_LEN,
+       SYNAPTICS_SYSCTL_TAP_MAX_DELTA,
+       SYNAPTICS_SYSCTL_TAP_MIN_QUEUE,
+       SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT,
+       SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA,
+       SYNAPTICS_SYSCTL_VSCROLL_VER_AREA,
+       SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA,
+       SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN,
+       SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX
+};
+
 typedef struct synapticsinfo {
-       struct sysctl_ctx_list  sysctl_ctx;
+       struct sysctl_ctx_list   sysctl_ctx;
        struct sysctl_oid       *sysctl_tree;
-       int                     directional_scrolls;
-       int                     low_speed_threshold;
-       int                     min_movement;
-       int                     squelch_level;
+       int                      directional_scrolls;
+       int                      min_pressure;
+       int                      max_pressure;
+       int                      max_width;
+       int                      margin_top;
+       int                      margin_right;
+       int                      margin_bottom;
+       int                      margin_left;
+       int                      na_top;
+       int                      na_right;
+       int                      na_bottom;
+       int                      na_left;
+       int                      window_min;
+       int                      window_max;
+       int                      multiplicator;
+       int                      weight_current;
+       int                      weight_previous;
+       int                      weight_previous_na;
+       int                      weight_len_squared;
+       int                      div_min;
+       int                      div_max;
+       int                      div_max_na;
+       int                      div_len;
+       int                      tap_max_delta;
+       int                      tap_min_queue;
+       int                      taphold_timeout;
+       int                      vscroll_ver_area;
+       int                      vscroll_hor_area;
+       int                      vscroll_min_delta;
+       int                      vscroll_div_min;
+       int                      vscroll_div_max;
 } synapticsinfo_t;
 
+typedef struct synapticspacket {
+       int                     x;
+       int                     y;
+} synapticspacket_t;
+
+#define        SYNAPTICS_PACKETQUEUE 10
+#define SYNAPTICS_QUEUE_CURSOR(x)                                      \
+       (x + SYNAPTICS_PACKETQUEUE) % SYNAPTICS_PACKETQUEUE
+
+typedef struct synapticsaction {
+       synapticspacket_t       queue[SYNAPTICS_PACKETQUEUE];
+       int                     queue_len;
+       int                     queue_cursor;
+       int                     window_min;
+       int                     start_x;
+       int                     start_y;
+       int                     avg_dx;
+       int                     avg_dy;
+       int                     squelch_x;
+       int                     squelch_y;
+       int                     fingers_nb;
+       int                     tap_button;
+       int                     in_taphold;
+       int                     in_vscroll;
+} synapticsaction_t;
+
 /* driver control block */
 struct psm_softc {             /* Driver status information */
        int             unit;
@@ -185,6 +271,7 @@ struct psm_softc {          /* Driver status inf
        mousehw_t       hw;             /* hardware information */
        synapticshw_t   synhw;          /* Synaptics hardware information */
        synapticsinfo_t syninfo;        /* Synaptics configuration */
+       synapticsaction_t synaction;    /* Synaptics action context */
        mousemode_t     mode;           /* operation mode */
        mousemode_t     dflt_mode;      /* default operation mode */
        mousestatus_t   status;         /* accumulated mouse movement */
@@ -781,6 +868,35 @@ doopen(struct psm_softc *sc, int command
 {
        int stat[3];
 
+       /*
+        * FIXME: Synaptics TouchPad seems to go back to Relative Mode with
+        * no obvious reason. Thus we check the current mode and restore the
+        * Absolute Mode if it was cleared.
+        *
+        * The previous hack at the end of psmprobe() wasn't efficient when
+        * moused(8) was restarted.
+        *
+        * A Reset (FF) or Set Defaults (F6) command would clear the
+        * Absolute Mode bit. But a verbose boot or debug.psm.loglevel=5
+        * doesn't show any evidence of such a command.
+        */
+       if (sc->hw.model == MOUSE_MODEL_SYNAPTICS) {
+               mouse_ext_command(sc->kbdc, 1);
+               get_mouse_status(sc->kbdc, stat, 0, 3);
+               if (stat[1] == 0x47 && stat[2] == 0x40) {
+                       /* Set the mode byte -- request wmode where
+                        * available */
+                       if (sc->synhw.capExtended)
+                               mouse_ext_command(sc->kbdc, 0xc1);
+                       else
+                               mouse_ext_command(sc->kbdc, 0xc0);
+                       set_mouse_sampling_rate(sc->kbdc, 20);
+                       VLOG(5, (LOG_DEBUG, "psm%d: Synaptis Absolute Mode "
+                           "hopefully restored\n",
+                           sc->unit));
+               }
+       }
+
        /* enable the mouse device */
        if (!enable_aux_dev(sc->kbdc)) {
                /* MOUSE ERROR: failed to enable the mouse because:
@@ -1272,15 +1388,6 @@ psmprobe(device_t dev)
                endprobe(ENXIO);
        }
 
-       /*
-        * Synaptics TouchPad seems to go back to Relative Mode after
-        * the previous set_controller_command_byte() call; by issueing
-        * a Read Mode Byte command, the touchpad is in Absolute Mode
-        * again.
-        */
-       if (sc->hw.model == MOUSE_MODEL_SYNAPTICS)
-               mouse_ext_command(sc->kbdc, 1);
-
        /* done */
        kbdc_set_device_mask(sc->kbdc, mask | KBD_AUX_CONTROL_BITS);
        kbdc_lock(sc->kbdc, FALSE);
@@ -2398,7 +2505,7 @@ proc_synaptics(struct psm_softc *sc, pac
 {
        static int touchpad_buttons;
        static int guest_buttons;
-       int w, x0, y0, xavg, yavg, xsensitivity, ysensitivity, sensitivity = 0;
+       int w, x0, y0;
 
        /* TouchPad PS/2 absolute mode message format
         *
@@ -2418,7 +2525,7 @@ proc_synaptics(struct psm_softc *sc, pac
         *  U: up button
         *  W: "wrist" value
         *  X: x position
-        *  Y: x position
+        *  Y: y position
         *  Z: pressure
         *
         * Absolute reportable limits:    0 - 6143.
@@ -2438,22 +2545,44 @@ proc_synaptics(struct psm_softc *sc, pac
            (pb->ipacket[3] & 0xc8) != 0xc0)
                return (-1);
 
-       *x = *y = x0 = y0 = 0;
+       *x = *y = 0;
 
-       /* Pressure value. */
+       /*
+        * Pressure value.
+        * Interpretation:
+        *   z = 0      No finger contact
+        *   z = 10     Finger hovering near the pad
+        *   z = 30     Very light finger contact
+        *   z = 80     Normal finger contact
+        *   z = 110    Very heavy finger contact
+        *   z = 200    Finger lying flat on pad surface
+        *   z = 255    Maximum reportable Z
+        */
        *z = pb->ipacket[2];
 
-       /* Finger width value */
+       /*
+        * Finger width value
+        * Interpretation:
+        *   w = 0      Two finger on the pad (capMultiFinger needed)
+        *   w = 1      Three or more fingers (capMultiFinger needed)
+        *   w = 2      Pen (instead of finger) (capPen needed)
+        *   w = 3      Reserved (passthrough?)
+        *   w = 4-7    Finger of normal width (capPalmDetect needed)
+        *   w = 8-14   Very wide finger or palm (capPalmDetect needed)
+        *   w = 15     Maximum reportable width (capPalmDetect needed)
+        */
+       /* XXX Is checking capExtended enough? */
        if (sc->synhw.capExtended)
                w = ((pb->ipacket[0] & 0x30) >> 2) |
                    ((pb->ipacket[0] & 0x04) >> 1) |
                    ((pb->ipacket[3] & 0x04) >> 2);
        else {
-               /* Assume a finger of regular width */
+               /* Assume a finger of regular width. */
                w = 4;
        }
 
        /* Handle packets from the guest device */
+       /* XXX Documentation? */
        if (w == 3 && sc->synhw.capPassthrough) {
                *x = ((pb->ipacket[1] & 0x10) ?
                    pb->ipacket[4] - 256 : pb->ipacket[4]);
@@ -2470,7 +2599,7 @@ proc_synaptics(struct psm_softc *sc, pac
                        guest_buttons |= MOUSE_BUTTON3DOWN;
 
                ms->button = touchpad_buttons | guest_buttons;
-               return (0);
+               goto SYNAPTICS_END;
        }
 
        /* Button presses */
@@ -2492,6 +2621,7 @@ proc_synaptics(struct psm_softc *sc, pac
         * the packet indicates that we have an extended
         * button press.
         */
+       /* XXX Documentation? */
        if (pb->ipacket[3] & 0x02) {
                /*
                 * if directional_scrolls is not 1, we treat any of
@@ -2507,151 +2637,495 @@ proc_synaptics(struct psm_softc *sc, pac
                        if (pb->ipacket[5] & 0x02)
                                touchpad_buttons |= MOUSE_BUTTON7DOWN;
                } else {
-                       if ((pb->ipacket[4] & 0x0F) || (pb->ipacket[5] & 0x0F))
+                       if ((pb->ipacket[4] & 0x0F) ||
+                           (pb->ipacket[5] & 0x0F))
                                touchpad_buttons |= MOUSE_BUTTON2DOWN;
                }
        }
 
        ms->button = touchpad_buttons | guest_buttons;
 
-       /* There is a finger on the pad. */
-       if ((w >= 4 && w <= 7) && (*z >= 16 && *z < 200)) {
+       /* Check pressure to detect a real wanted action on the
+        * touchpad. */
+       if (*z >= sc->syninfo.min_pressure) {
+               synapticsaction_t *synaction;
+               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 multiplicator;
+               int weight_current, weight_previous, weight_len_squared;
+               int div_min, div_max, div_len;
+               int vscroll_hor_area, vscroll_ver_area;
+
+               int len, weight_prev_x, weight_prev_y;
+               int div_max_x, div_max_y, div_x, div_y;
+
+               /* 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;
+               margin_left = sc->syninfo.margin_left;
+               na_top = sc->syninfo.na_top;
+               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;
+               weight_previous = sc->syninfo.weight_previous;
+               weight_len_squared = sc->syninfo.weight_len_squared;
+               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;
+
+               /* Palm detection. */
+               if (!(
+                   (sc->synhw.capMultiFinger && (w == 0 || w == 1)) ||
+                   (sc->synhw.capPalmDetect && w >= 4 && w <= max_width) ||
+                   (!sc->synhw.capPalmDetect && *z <= max_pressure) ||
+                   (sc->synhw.capPen && w == 2))) {
+                       /*
+                        * We consider the packet irrelevant for the current
+                        * action when:
+                        *  - the width isn't comprised in:
+                        *    [4; max_width]
+                        *  - the pressure isn't comprised in:
+                        *    [min_pressure; max_pressure]
+                        *  - pen aren't supported but w is 2
+                        *
+                        *  Note that this doesn't terminate the current action.
+                        */
+                       VLOG(2, (LOG_DEBUG,
+                           "synaptics: palm detected! (%d)\n", w));
+                       goto SYNAPTICS_END;
+               }
+
+               /* Read current absolute position. */
                x0 = ((pb->ipacket[3] & 0x10) << 8) |
-                   ((pb->ipacket[1] & 0x0f) << 8) | pb->ipacket[4];
+                   ((pb->ipacket[1] & 0x0f) << 8) |
+                   pb->ipacket[4];
                y0 = ((pb->ipacket[3] & 0x20) << 7) |
-                   ((pb->ipacket[1] & 0xf0) << 4) | pb->ipacket[5];
+                   ((pb->ipacket[1] & 0xf0) << 4) |
+                   pb->ipacket[5];
 
-               if (sc->flags & PSM_FLAGS_FINGERDOWN) {
-                       *x = x0 - sc->xold;
-                       *y = y0 - sc->yold;
+               synaction = &(sc->synaction);
 
-                       /*
-                        * we compute averages of x and y
-                        * movement
-                        */
-                       if (sc->xaverage == 0)
-                               sc->xaverage = *x;
+               /*
+                * If the action is just beginning, init the structure and
+                * compute tap timeout.
+                */
+               if (!(sc->flags & PSM_FLAGS_FINGERDOWN)) {
+                       VLOG(3, (LOG_DEBUG, "synaptics: ----\n"));
 
-                       if (sc->yaverage == 0)
-                               sc->yaverage = *y;
+                       /* Store the first point of this action. */
+                       synaction->start_x = x0;
+                       synaction->start_y = y0;
+                       dx = dy = 0;
+
+                       /* Initialize queue. */
+                       synaction->queue_cursor = SYNAPTICS_PACKETQUEUE;
+                       synaction->queue_len = 0;
+                       synaction->window_min = window_min;
+
+                       /* Reset average. */
+                       synaction->avg_dx = 0;
+                       synaction->avg_dy = 0;
+
+                       /* Reset squelch. */
+                       synaction->squelch_x = 0;
+                       synaction->squelch_y = 0;
+
+                       /* Reset pressure peak. */
+                       sc->zmax = 0;
+
+                       /* Reset fingers count. */
+                       synaction->fingers_nb = 0;
+
+                       /* Reset virtual scrolling state. */
+                       synaction->in_vscroll = 0;
+
+                       /* Compute tap timeout. */
+                       sc->taptimeout.tv_sec  = tap_timeout / 1000000;
+                       sc->taptimeout.tv_usec = tap_timeout % 1000000;
+                       timevaladd(&sc->taptimeout, &sc->lastsoftintr);
 
-                       xavg = sc->xaverage;
-                       yavg = sc->yaverage;
+                       sc->flags |= PSM_FLAGS_FINGERDOWN;
+               } else {
+                       /* Calculate the current delta. */
+                       cursor = synaction->queue_cursor;
+                       dx = x0 - synaction->queue[cursor].x;
+                       dy = y0 - synaction->queue[cursor].y;
+               }
 
-                       sc->xaverage = (xavg + *x) >> 1;
-                       sc->yaverage = (yavg + *y) >> 1;
+               /* If in tap-hold, add the recorded button. */
+               if (synaction->in_taphold)
+                       ms->button |= synaction->tap_button;
 
-                       /*
-                        * then use the averages to compute
-                        * a sensitivity level in each dimension
-                        */
-                       xsensitivity = (sc->xaverage - xavg);
-                       if (xsensitivity < 0)
-                               xsensitivity = -xsensitivity;
-
-                       ysensitivity = (sc->yaverage - yavg);
-                       if (ysensitivity < 0)
-                               ysensitivity = -ysensitivity;
+               /*
+                * From now on, we can use the SYNAPTICS_END label to skip
+                * the current packet.
+                */
+
+               /*
+                * Limit the coordinates to the specified margins because
+                * this area isn't very reliable.
+                */
+               if (x0 <= margin_left)
+                       x0 = margin_left;
+               else if (x0 >= 6143 - margin_right)
+                       x0 = 6143 - margin_right;
+               if (y0 <= margin_bottom)
+                       y0 = margin_bottom;
+               else if (y0 >= 6143 - margin_top)
+                       y0 = 6143 - margin_top;
+
+               VLOG(3, (LOG_DEBUG, "synaptics: ipacket: [%d, %d], %d, %d\n",
+                   x0, y0, *z, w));
+
+               /* Queue this new packet. */
+               cursor = SYNAPTICS_QUEUE_CURSOR(synaction->queue_cursor - 1);
+               synaction->queue[cursor].x = x0;
+               synaction->queue[cursor].y = y0;
+               synaction->queue_cursor = cursor;
+               if (synaction->queue_len < SYNAPTICS_PACKETQUEUE)
+                       synaction->queue_len++;
+               VLOG(5, (LOG_DEBUG,
+                   "synaptics: cursor[%d]: x=%d, y=%d, dx=%d, dy=%d\n",
+                   cursor, x0, y0, dx, dy));
 
+               /*
+                * For tap, we keep the maximum number of fingers and the
+                * pressure peak. Also with multiple fingers, we increase
+                * the minimum window.
+                */
+               switch (w) {
+               case 1: /* Three or more fingers. */
+                       synaction->fingers_nb = imax(3, synaction->fingers_nb);
+                       synaction->window_min = window_max;
+                       break;
+               case 0: /* Two fingers. */
+                       synaction->fingers_nb = imax(2, synaction->fingers_nb);
+                       synaction->window_min = window_max;
+                       break;
+               default: /* One finger or undetectable. */
+                       synaction->fingers_nb = imax(1, synaction->fingers_nb);
+               }
+               sc->zmax = imax(*z, sc->zmax);
+
+               /* Do we have enough packets to consider this a movement? */
+               if (synaction->queue_len < synaction->window_min)
+                       goto SYNAPTICS_END;
+
+               /* Is a scrolling action occuring? */
+               if (!synaction->in_taphold && !synaction->in_vscroll) {
                        /*
-                        * The sensitivity level is higher the faster
-                        * the finger is moving.  It also tends to be
-                        * higher in the middle of a touchpad motion
-                        * than on either end
-                        * Note - sensitivity gets to 0 when moving slowly -
-                        * so we add 1 to it to give it a meaningful value
-                        * in that case.
+                        * A scrolling action must not conflict with a tap
+                        * action. Here are the conditions to consider a
+                        * scrolling action:
+                        *  - the action in a configurable area
+                        *  - one of the following:
+                        *     . the distance between the last packet and the
+                        *       first should be above a configurable minimum
+                        *     . tap timed out
                         */
-                       sensitivity = (xsensitivity & ysensitivity) + 1;
+                       dxp = abs(synaction->queue[synaction->queue_cursor].x -
+                           synaction->start_x);
+                       dyp = abs(synaction->queue[synaction->queue_cursor].y -
+                           synaction->start_y);
+
+                       if (timevalcmp(&sc->lastsoftintr, &sc->taptimeout, >) ||
+                           dxp >= sc->syninfo.vscroll_min_delta ||
+                           dyp >= sc->syninfo.vscroll_min_delta) {
+                               /* Check for horizontal scrolling. */
+                               if ((vscroll_hor_area > 0 &&
+                                   synaction->start_y <= vscroll_hor_area) ||
+                                   (vscroll_hor_area < 0 &&
+                                    synaction->start_y >=
+                                    6143 + vscroll_hor_area))
+                                       synaction->in_vscroll += 2;
+
+                               /* Check for vertical scrolling. */
+                               if ((vscroll_ver_area > 0 &&
+                                   synaction->start_x <= vscroll_ver_area) ||
+                                   (vscroll_ver_area < 0 &&
+                                    synaction->start_x >=
+                                    6143 + vscroll_ver_area))
+                                       synaction->in_vscroll += 1;
+
+                               /* Avoid conflicts if area overlaps. */
+                               if (synaction->in_vscroll == 3)
+                                       synaction->in_vscroll =
+                                           (dxp > dyp) ? 2 : 1;
+                       }
+                       VLOG(5, (LOG_DEBUG,
+                           "synaptics: virtual scrolling: %s "
+                           "(direction=%d, dxp=%d, dyp=%d)\n",
+                           synaction->in_vscroll ? "YES" : "NO",
+                           synaction->in_vscroll, dxp, dyp));
+               }
 
+               weight_prev_x = weight_prev_y = weight_previous;
+               div_max_x = div_max_y = div_max;
+
+               if (synaction->in_vscroll) {
+                       /* Dividers are different with virtual scrolling. */
+                       div_min = sc->syninfo.vscroll_div_min;
+                       div_max_x = div_max_y = sc->syninfo.vscroll_div_max;
+               } else {
                        /*
-                        * If either our x or y change is greater than
-                        * our hi/low speed threshold - we do the high-speed
-                        * absolute to relative calculation otherwise
-                        * we do the low-speed calculation.
+                        * There's a lot of noise in coordinates when
+                        * the finger is on the touchpad's borders. When
+                        * using this area, we apply a special weight and
+                        * div.
                         */
-                       if ((*x > sc->syninfo.low_speed_threshold ||
-                           *x < -sc->syninfo.low_speed_threshold) ||
-                           (*y > sc->syninfo.low_speed_threshold ||
-                           *y < -sc->syninfo.low_speed_threshold)) {
-                               x0 = (x0 + sc->xold * 3) / 4;
-                               y0 = (y0 + sc->yold * 3) / 4;
-                               *x = (x0 - sc->xold) * 10 / 85;
-                               *y = (y0 - sc->yold) * 10 / 85;
-                       } else {
-                               /*
-                                * This is the low speed calculation.
-                                * We simply check to see if our movement is
-                                * more than our minimum movement threshold
-                                * and if it is - set the movement to 1
-                                * in the correct direction.
-                                * NOTE - Normally this would result
-                                * in pointer movement that was WAY too fast.
-                                * This works due to the movement squelch
-                                * we do later.
-                                */
-                               if (*x < -sc->syninfo.min_movement)
-                                       *x = -1;
-                               else if (*x > sc->syninfo.min_movement)
-                                       *x = 1;
-                               else
-                                       *x = 0;
-                               if (*y < -sc->syninfo.min_movement)
-                                       *y = -1;
-                               else if (*y > sc->syninfo.min_movement)
-                                       *y = 1;
-                               else
-                                       *y = 0;
+                       if (x0 <= na_left || x0 >= 6143 - na_right) {
+                               weight_prev_x = sc->syninfo.weight_previous_na;
+                               div_max_x = sc->syninfo.div_max_na;
+                       }
 
+                       if (y0 <= na_bottom || y0 >= 6143 - na_top) {
+                               weight_prev_y = sc->syninfo.weight_previous_na;
+                               div_max_y = sc->syninfo.div_max_na;
                        }
-               } else
-                       sc->flags |= PSM_FLAGS_FINGERDOWN;
+               }
 
                /*
-                * The squelch process.  Take our sensitivity value and
-                * add it to the current squelch value - if squelch is
-                * less than our squelch threshold we kill the movement,
-                * otherwise we reset squelch and pass the movement through.
-                * Since squelch is cumulative - when mouse movement is slow
-                * (around sensitivity 1) the net result is that only 1
-                * out of every squelch_level packets is delivered,
-                * effectively slowing down the movement.
-                */
-               sc->squelch += sensitivity;
-               if (sc->squelch < sc->syninfo.squelch_level) {
-                       *x = 0;
-                       *y = 0;
-               } else
-                       sc->squelch = 0;
+                * Calculate weights for the average operands and
+                * the divisor. Both depend on the distance between
+                * the current packet and a previous one (based on the
+                * window width).
+                */
+               window = imin(synaction->queue_len, window_max);
+               peer = SYNAPTICS_QUEUE_CURSOR(cursor + window - 1);
+               dxp = abs(x0 - synaction->queue[peer].x) + 1;
+               dyp = abs(y0 - synaction->queue[peer].y) + 1;
+               len = (dxp * dxp) + (dyp * dyp);
+               weight_prev_x = imin(weight_prev_x,
+                   weight_len_squared * weight_prev_x / len);
+               weight_prev_y = imin(weight_prev_y,
+                   weight_len_squared * weight_prev_y / len);
+
+               len = (dxp + dyp) / 2;
+               div_x = div_len * div_max_x / len;
+               div_x = imin(div_max_x, div_x);
+               div_x = imax(div_min, div_x);
+               div_y = div_len * div_max_y / len;
+               div_y = imin(div_max_y, div_y);
+               div_y = imax(div_min, div_y);
+
+               VLOG(3, (LOG_DEBUG,
+                   "synaptics: peer=%d, len=%d, weight=%d/%d, div=%d/%d\n",
+                   peer, len, weight_prev_x, weight_prev_y, div_x, div_y));
+
+               /* Compute averages. */
+               synaction->avg_dx =
+                   (weight_current * dx * multiplicator +
+                    weight_prev_x * synaction->avg_dx) /
+                   (weight_current + weight_prev_x);
+
+               synaction->avg_dy =
+                   (weight_current * dy * multiplicator +
+                    weight_prev_y * synaction->avg_dy) /
+                   (weight_current + weight_prev_y);
+
+               VLOG(5, (LOG_DEBUG,
+                   "synaptics: avg_dx~=%d, avg_dy~=%d\n",
+                   synaction->avg_dx / multiplicator,
+                   synaction->avg_dy / multiplicator));
+
+               /* Use these averages to calculate x & y. */
+               synaction->squelch_x += synaction->avg_dx;
+               *x = synaction->squelch_x / (div_x * multiplicator);
+               synaction->squelch_x = synaction->squelch_x %
+                   (div_x * multiplicator);
+
+               synaction->squelch_y += synaction->avg_dy;
+               *y = synaction->squelch_y / (div_y * multiplicator);
+               synaction->squelch_y = synaction->squelch_y %
+                   (div_y * multiplicator);
+
+               if (synaction->in_vscroll) {
+                       switch(synaction->in_vscroll) {
+                       case 1: /* Vertical scrolling. */
+                               if (*y != 0)
+                                       ms->button |= (*y > 0) ?
+                                           MOUSE_BUTTON4DOWN :
+                                           MOUSE_BUTTON5DOWN;
+                               break;
+                       case 2: /* Horizontal scrolling. */
+                               if (*x != 0)
+                                       ms->button |= (*x > 0) ?
+                                           MOUSE_BUTTON7DOWN :
+                                           MOUSE_BUTTON6DOWN;
+                               break;
+                       }
+
+                       /* The pointer is not moved. */
+                       *x = *y = 0;
+               } else {
+                       VLOG(3, (LOG_DEBUG, "synaptics: [%d, %d] -> [%d, %d]\n",
+                           dx, dy, *x, *y));
+               }
+       } else if (sc->flags & PSM_FLAGS_FINGERDOWN) {
+               /*
+                * An action is currently taking place but the pressure
+                * dropped under the minimum, putting an end to it.
+                */
+               synapticsaction_t *synaction;
+               int taphold_timeout, dx, dy, tap_max_delta;
+
+               synaction = &(sc->synaction);
+               dx = abs(synaction->queue[synaction->queue_cursor].x -
+                   synaction->start_x);
+               dy = abs(synaction->queue[synaction->queue_cursor].y -
+                   synaction->start_y);
+
+               /* Max delta is disabled for multi-fingers tap. */
+               if (synaction->fingers_nb > 1)
+                       tap_max_delta = imax(dx, dy);
+               else
+                       tap_max_delta = sc->syninfo.tap_max_delta;
 
-               sc->xold = x0;
-               sc->yold = y0;
-               sc->zmax = imax(*z, sc->zmax);
-       } else {
                sc->flags &= ~PSM_FLAGS_FINGERDOWN;
 
-               if (sc->zmax > tap_threshold &&
-                   timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=)) {
-                       if (w == 0)
-                               ms->button |= MOUSE_BUTTON3DOWN;
-                       else if (w == 1)
-                               ms->button |= MOUSE_BUTTON2DOWN;
-                       else
-                               ms->button |= MOUSE_BUTTON1DOWN;
-               }
+               /* Check for tap. */
+               VLOG(3, (LOG_DEBUG,
+                   "synaptics: zmax=%d, dx=%d, dy=%d, "
+                   "delta=%d, fingers=%d, queue=%d\n",
+                   sc->zmax, dx, dy, tap_max_delta, synaction->fingers_nb,
+                   synaction->queue_len));
+               if (!synaction->in_vscroll && sc->zmax >= tap_threshold &&
+                   timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=) &&
+                   dx <= tap_max_delta && dy <= tap_max_delta &&
+                   synaction->queue_len >= sc->syninfo.tap_min_queue) {
+                       /*
+                        * We have a tap if:
+                        *   - the maximum pressure went over tap_threshold
+                        *   - the action ended before tap_timeout
+                        *
+                        * To handle tap-hold, we must delay any button push to
+                        * the next action.
+                        */
+                       if (synaction->in_taphold) {
+                               /*
+                                * This is the second and last tap of a
+                                * double tap action, not a tap-hold.
+                                */
+                               synaction->in_taphold = 0;
 
-               sc->zmax = 0;
-               sc->taptimeout.tv_sec = tap_timeout / 1000000;
-               sc->taptimeout.tv_usec = tap_timeout % 1000000;
-               timevaladd(&sc->taptimeout, &sc->lastsoftintr);
+                               /*
+                                * For double-tap to work:
+                                *   - no button press is emitted (to
+                                *     simulate a button release)
+                                *   - PSM_FLAGS_FINGERDOWN is set to
+                                *     force the next packet to emit a
+                                *     button press)
+                                */
+                               VLOG(2, (LOG_DEBUG,
+                                   "synaptics: button RELEASE: %d\n",
+                                   synaction->tap_button));
+                               sc->flags |= PSM_FLAGS_FINGERDOWN;
+                       } else {
+                               /*
+                                * This is the first tap: we set the
+                                * tap-hold state and notify the button
+                                * down event.
+                                */
+                               synaction->in_taphold = 1;
+                               taphold_timeout = sc->syninfo.taphold_timeout;
+                               sc->taptimeout.tv_sec  = taphold_timeout /
+                                   1000000;
+                               sc->taptimeout.tv_usec = taphold_timeout %
+                                   1000000;
+                               timevaladd(&sc->taptimeout, &sc->lastsoftintr);
+
+                               switch (synaction->fingers_nb) {
+                               case 3:
+                                       synaction->tap_button =
+                                           MOUSE_BUTTON2DOWN;
+                                       break;
+                               case 2:
+                                       synaction->tap_button =
+                                           MOUSE_BUTTON3DOWN;
+                                       break;
+                               default:
+                                       synaction->tap_button =
+                                           MOUSE_BUTTON1DOWN;
+                               }
+                               VLOG(2, (LOG_DEBUG,
+                                   "synaptics: button PRESS: %d\n",
+                                   synaction->tap_button));
+                               ms->button |= synaction->tap_button;
+                       }
+               } else {
+                       /*
+                        * Not enough pressure or timeout: reset
+                        * tap-hold state.
+                        */
+                       if (synaction->in_taphold) {
+                               VLOG(2, (LOG_DEBUG,
+                                   "synaptics: button RELEASE: %d\n",
+                                   synaction->tap_button));
+                               synaction->in_taphold = 0;
+                       } else {
+                               VLOG(2, (LOG_DEBUG,
+                                   "synaptics: not a tap-hold\n"));
+                       }
+               }
+       } else if (!(sc->flags & PSM_FLAGS_FINGERDOWN) &&
+           sc->synaction.in_taphold) {
+               /*
+                * For a tap-hold to work, the button must remain down at
+                * least until timeout (where the in_taphold flags will be
+                * cleared) or during the next action.
+                */
+               if (timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=)) {
+                       ms->button |= sc->synaction.tap_button;
+               } else {
+                       VLOG(2, (LOG_DEBUG,
+                           "synaptics: button RELEASE: %d\n",
+                           sc->synaction.tap_button));
+                       sc->synaction.in_taphold = 0;
+               }
        }
 
-       /* Use the extra buttons as a scrollwheel */
-       if (ms->button & MOUSE_BUTTON4DOWN)
+SYNAPTICS_END:
+       /*
+        * Use the extra buttons as a scrollwheel
+        *
+        * XXX X.Org uses the Z axis for vertical wheel only,
+        * whereas moused(8) understands special values to differ
+        * vertical and horizontal wheels.
+        *
+        * xf86-input-mouse needs therefore a small patch to
+        * understand these special values. Without it, the
+        * horizontal wheel acts as a vertical wheel in X.Org.
+        *
+        * That's why the horizontal wheel is disabled by
+        * default for now.
+        */
+       if (ms->button & MOUSE_BUTTON4DOWN) {
                *z = -1;
-       else if (ms->button & MOUSE_BUTTON5DOWN)
+               ms->button &= ~MOUSE_BUTTON4DOWN;
+       } else if (ms->button & MOUSE_BUTTON5DOWN) {
                *z = 1;
-       else
+               ms->button &= ~MOUSE_BUTTON5DOWN;
+       } else if (ms->button & MOUSE_BUTTON6DOWN) {
+               *z = -2;
+               ms->button &= ~MOUSE_BUTTON6DOWN;
+       } else if (ms->button & MOUSE_BUTTON7DOWN) {
+               *z = 2;
+               ms->button &= ~MOUSE_BUTTON7DOWN;
+       } else
                *z = 0;
 
        return (0);
@@ -3400,13 +3874,87 @@ enable_4dplus(struct psm_softc *sc)
 
 /* Synaptics Touchpad */
 static int
-enable_synaptics(struct psm_softc *sc)
+synaptics_sysctl(SYSCTL_HANDLER_ARGS)
 {
-       int status[3];
-       KBDC kbdc;
+       int error, arg;
 
-       if (!synaptics_support)
-               return (FALSE);
+       /* Read the current value. */
+       arg = *(int *)oidp->oid_arg1;
+       error = sysctl_handle_int(oidp, &arg, 0, req);
+
+       /* Sanity check. */
+       if (error || !req->newptr)
+               return (error);
+
+       /*
+        * Check that the new value is in the concerned node's range
+        * of values.
+        */
+       switch (oidp->oid_arg2) {
+       case SYNAPTICS_SYSCTL_MIN_PRESSURE:
+       case SYNAPTICS_SYSCTL_MAX_PRESSURE:
+               if (arg < 0 || arg > 255)
+                       return (EINVAL);
+               break;
+       case SYNAPTICS_SYSCTL_MAX_WIDTH:
+               if (arg < 4 || arg > 15)
+                       return (EINVAL);
+               break;
+       case SYNAPTICS_SYSCTL_MARGIN_TOP:
+       case SYNAPTICS_SYSCTL_MARGIN_RIGHT:
+       case SYNAPTICS_SYSCTL_MARGIN_BOTTOM:
+       case SYNAPTICS_SYSCTL_MARGIN_LEFT:
+       case SYNAPTICS_SYSCTL_NA_TOP:
+       case SYNAPTICS_SYSCTL_NA_RIGHT:
+       case SYNAPTICS_SYSCTL_NA_BOTTOM:
+       case SYNAPTICS_SYSCTL_NA_LEFT:
+               if (arg < 0 || arg > 6143)
+                       return (EINVAL);
+               break;
+       case SYNAPTICS_SYSCTL_WINDOW_MIN:
+       case SYNAPTICS_SYSCTL_WINDOW_MAX:
+       case SYNAPTICS_SYSCTL_TAP_MIN_QUEUE:
+               if (arg < 1 || arg > SYNAPTICS_PACKETQUEUE)
+                       return (EINVAL);
+               break;
+       case SYNAPTICS_SYSCTL_MULTIPLICATOR:
+       case SYNAPTICS_SYSCTL_WEIGHT_CURRENT:
+       case SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS:
+       case SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA:
+       case SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED:
+       case SYNAPTICS_SYSCTL_DIV_MIN:
+       case SYNAPTICS_SYSCTL_DIV_MAX:
+       case SYNAPTICS_SYSCTL_DIV_MAX_NA:
+       case SYNAPTICS_SYSCTL_DIV_LEN:
+       case SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN:
+       case SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX:
+               if (arg < 1)
+                       return (EINVAL);
+               break;
+       case SYNAPTICS_SYSCTL_TAP_MAX_DELTA:
+       case SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT:
+       case SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA:
+               if (arg < 0)
+                       return (EINVAL);
+               break;
+       case SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA:
+       case SYNAPTICS_SYSCTL_VSCROLL_VER_AREA:
+               if (arg < -6143 || arg > 6143)
+                       return (EINVAL);
+               break;
+       default:
+               return (EINVAL);
+       }
+
+       /* Update. */
+       *(int *)oidp->oid_arg1 = arg;
+
+       return (error);
+}
+
+static void
+synaptics_sysctl_create_tree(struct psm_softc *sc)
+{
 
        /* Attach extra synaptics sysctl nodes under hw.psm.synaptics */
        sysctl_ctx_init(&sc->syninfo.sysctl_ctx);
@@ -3414,54 +3962,307 @@ enable_synaptics(struct psm_softc *sc)
            SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, "synaptics", CTLFLAG_RD,
            0, "Synaptics TouchPad");
 
-       /*
-        * synaptics_directional_scrolls - if non-zero, the directional
-        * pad scrolls, otherwise it registers as a middle-click.
-        */
+       /* hw.psm.synaptics.directional_scrolls. */
        sc->syninfo.directional_scrolls = 1;
        SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx,
            SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-           "directional_scrolls", CTLFLAG_RW,
+           "directional_scrolls", CTLFLAG_RW|CTLFLAG_ANYBODY,
            &sc->syninfo.directional_scrolls, 0,
-           "directional pad scrolls (1=yes  0=3rd button)");
+           "Enable hardware scrolling pad (if non-zero) or register it as "
+           "a middle-click (if 0)");
 
-       /*
-        * Synaptics_low_speed_threshold - the number of touchpad units
-        * below-which we go into low-speed tracking mode.
-        */
-       sc->syninfo.low_speed_threshold = 20;
-       SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx,
+       /* hw.psm.synaptics.min_pressure. */
+       sc->syninfo.min_pressure = 16;
+       SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx,
            SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO,
-           "low_speed_threshold", CTLFLAG_RW,
-           &sc->syninfo.low_speed_threshold, 0,
-           "threshold between low and hi speed positioning");
-
-       /*
-        * Synaptics_min_movement - the number of touchpad units below
-        * which we ignore altogether.
-        */
-       sc->syninfo.min_movement = 2;
-       SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx,
+           "min_pressure", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
+           &sc->syninfo.min_pressure, SYNAPTICS_SYSCTL_MIN_PRESSURE,
+           synaptics_sysctl, "I",
+           "Minimum pressure required to start an action");
+
+       /* hw.psm.synaptics.max_pressure. */

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "[EMAIL PROTECTED]"

Reply via email to