Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[pandora-kernel.git] / drivers / input / mouse / bcm5974.c
index 6f85278..2998a6a 100644 (file)
@@ -63,7 +63,7 @@
 }
 
 /* table of devices that work with this driver */
-static const struct usb_device_id bcm5974_table [] = {
+static const struct usb_device_id bcm5974_table[] = {
        /* MacbookAir1.1 */
        BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
        BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
@@ -105,7 +105,7 @@ struct tp_header {
 
 /* trackpad finger structure */
 struct tp_finger {
-       __le16 origin;          /* left/right origin? */
+       __le16 origin;          /* zero when switching track finger */
        __le16 abs_x;           /* absolute x coodinate */
        __le16 abs_y;           /* absolute y coodinate */
        __le16 rel_x;           /* relative x coodinate */
@@ -150,6 +150,7 @@ struct bcm5974_config {
 struct bcm5974 {
        char phys[64];
        struct usb_device *udev;        /* usb device */
+       struct usb_interface *intf;     /* our interface */
        struct input_dev *input;        /* input dev */
        struct bcm5974_config cfg;      /* device configuration */
        struct mutex pm_mutex;          /* serialize access to open/suspend */
@@ -158,6 +159,7 @@ struct bcm5974 {
        struct bt_data *bt_data;        /* button transferred data */
        struct urb *tp_urb;             /* trackpad usb request block */
        struct tp_data *tp_data;        /* trackpad transferred data */
+       int fingers;                    /* number of fingers on trackpad */
 };
 
 /* logical dimensions */
@@ -171,6 +173,10 @@ struct bcm5974 {
 #define SN_WIDTH       100             /* width signal-to-noise ratio */
 #define SN_COORD       250             /* coordinate signal-to-noise ratio */
 
+/* pressure thresholds */
+#define PRESSURE_LOW   (2 * DIM_PRESSURE / SN_PRESSURE)
+#define PRESSURE_HIGH  (3 * PRESSURE_LOW)
+
 /* device constants */
 static const struct bcm5974_config bcm5974_config_table[] = {
        {
@@ -247,6 +253,7 @@ static void setup_events_to_report(struct input_dev *input_dev,
                                0, cfg->y.dim, cfg->y.fuzz, 0);
 
        __set_bit(EV_KEY, input_dev->evbit);
+       __set_bit(BTN_TOUCH, input_dev->keybit);
        __set_bit(BTN_TOOL_FINGER, input_dev->keybit);
        __set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit);
        __set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit);
@@ -272,32 +279,66 @@ static int report_tp_state(struct bcm5974 *dev, int size)
        const struct tp_finger *f = dev->tp_data->finger;
        struct input_dev *input = dev->input;
        const int fingers = (size - 26) / 28;
-       int p = 0, w, x, y, n = 0;
+       int raw_p, raw_w, raw_x, raw_y;
+       int ptest = 0, origin = 0, nmin = 0, nmax = 0;
+       int abs_p = 0, abs_w = 0, abs_x = 0, abs_y = 0;
 
        if (size < 26 || (size - 26) % 28 != 0)
                return -EIO;
 
+       /* always track the first finger; when detached, start over */
        if (fingers) {
-               p = raw2int(f->force_major);
-               w = raw2int(f->size_major);
-               x = raw2int(f->abs_x);
-               y = raw2int(f->abs_y);
-               n = p > 0 ? fingers : 0;
+               raw_p = raw2int(f->force_major);
+               raw_w = raw2int(f->size_major);
+               raw_x = raw2int(f->abs_x);
+               raw_y = raw2int(f->abs_y);
 
                dprintk(9,
-                       "bcm5974: p: %+05d w: %+05d x: %+05d y: %+05d n: %d\n",
-                       p, w, x, y, n);
+                       "bcm5974: raw: p: %+05d w: %+05d x: %+05d y: %+05d\n",
+                       raw_p, raw_w, raw_x, raw_y);
+
+               ptest = int2bound(&c->p, raw_p);
+               origin = raw2int(f->origin);
+       }
 
-               input_report_abs(input, ABS_TOOL_WIDTH, int2bound(&c->w, w));
-               input_report_abs(input, ABS_X, int2bound(&c->x, x - c->x.devmin));
-               input_report_abs(input, ABS_Y, int2bound(&c->y, c->y.devmax - y));
+       /* while tracking finger still valid, count all fingers */
+       if (ptest > PRESSURE_LOW && origin) {
+               abs_p = ptest;
+               abs_w = int2bound(&c->w, raw_w);
+               abs_x = int2bound(&c->x, raw_x - c->x.devmin);
+               abs_y = int2bound(&c->y, c->y.devmax - raw_y);
+               for (; f != dev->tp_data->finger + fingers; f++) {
+                       ptest = int2bound(&c->p, raw2int(f->force_major));
+                       if (ptest > PRESSURE_LOW)
+                               nmax++;
+                       if (ptest > PRESSURE_HIGH)
+                               nmin++;
+               }
        }
 
-       input_report_abs(input, ABS_PRESSURE, int2bound(&c->p, p));
+       if (dev->fingers < nmin)
+               dev->fingers = nmin;
+       if (dev->fingers > nmax)
+               dev->fingers = nmax;
+
+       input_report_key(input, BTN_TOUCH, dev->fingers > 0);
+       input_report_key(input, BTN_TOOL_FINGER, dev->fingers == 1);
+       input_report_key(input, BTN_TOOL_DOUBLETAP, dev->fingers == 2);
+       input_report_key(input, BTN_TOOL_TRIPLETAP, dev->fingers > 2);
 
-       input_report_key(input, BTN_TOOL_FINGER, n == 1);
-       input_report_key(input, BTN_TOOL_DOUBLETAP, n == 2);
-       input_report_key(input, BTN_TOOL_TRIPLETAP, n > 2);
+       input_report_abs(input, ABS_PRESSURE, abs_p);
+       input_report_abs(input, ABS_TOOL_WIDTH, abs_w);
+
+       if (abs_p) {
+               input_report_abs(input, ABS_X, abs_x);
+               input_report_abs(input, ABS_Y, abs_y);
+
+               dprintk(8,
+                       "bcm5974: abs: p: %+05d w: %+05d x: %+05d y: %+05d "
+                       "nmin: %d nmax: %d n: %d\n",
+                       abs_p, abs_w, abs_x, abs_y, nmin, nmax, dev->fingers);
+
+       }
 
        input_sync(input);
 
@@ -310,8 +351,9 @@ static int report_tp_state(struct bcm5974 *dev, int size)
 #define BCM5974_WELLSPRING_MODE_REQUEST_VALUE          0x300
 #define BCM5974_WELLSPRING_MODE_REQUEST_INDEX          0
 #define BCM5974_WELLSPRING_MODE_VENDOR_VALUE           0x01
+#define BCM5974_WELLSPRING_MODE_NORMAL_VALUE           0x08
 
-static int bcm5974_wellspring_mode(struct bcm5974 *dev)
+static int bcm5974_wellspring_mode(struct bcm5974 *dev, bool on)
 {
        char *data = kmalloc(8, GFP_KERNEL);
        int retval = 0, size;
@@ -336,7 +378,9 @@ static int bcm5974_wellspring_mode(struct bcm5974 *dev)
        }
 
        /* apply the mode switch */
-       data[0] = BCM5974_WELLSPRING_MODE_VENDOR_VALUE;
+       data[0] = on ?
+               BCM5974_WELLSPRING_MODE_VENDOR_VALUE :
+               BCM5974_WELLSPRING_MODE_NORMAL_VALUE;
 
        /* write configuration */
        size = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
@@ -351,7 +395,8 @@ static int bcm5974_wellspring_mode(struct bcm5974 *dev)
                goto out;
        }
 
-       dprintk(2, "bcm5974: switched to wellspring mode.\n");
+       dprintk(2, "bcm5974: switched to %s mode.\n",
+               on ? "wellspring" : "normal");
 
  out:
        kfree(data);
@@ -440,7 +485,7 @@ exit:
  */
 static int bcm5974_start_traffic(struct bcm5974 *dev)
 {
-       if (bcm5974_wellspring_mode(dev)) {
+       if (bcm5974_wellspring_mode(dev, true)) {
                dprintk(1, "bcm5974: mode switch failed\n");
                goto error;
        }
@@ -463,6 +508,7 @@ static void bcm5974_pause_traffic(struct bcm5974 *dev)
 {
        usb_kill_urb(dev->tp_urb);
        usb_kill_urb(dev->bt_urb);
+       bcm5974_wellspring_mode(dev, false);
 }
 
 /*
@@ -478,6 +524,10 @@ static int bcm5974_open(struct input_dev *input)
        struct bcm5974 *dev = input_get_drvdata(input);
        int error;
 
+       error = usb_autopm_get_interface(dev->intf);
+       if (error)
+               return error;
+
        mutex_lock(&dev->pm_mutex);
 
        error = bcm5974_start_traffic(dev);
@@ -486,6 +536,9 @@ static int bcm5974_open(struct input_dev *input)
 
        mutex_unlock(&dev->pm_mutex);
 
+       if (error)
+               usb_autopm_put_interface(dev->intf);
+
        return error;
 }
 
@@ -499,6 +552,8 @@ static void bcm5974_close(struct input_dev *input)
        dev->opened = 0;
 
        mutex_unlock(&dev->pm_mutex);
+
+       usb_autopm_put_interface(dev->intf);
 }
 
 static int bcm5974_suspend(struct usb_interface *iface, pm_message_t message)
@@ -551,6 +606,7 @@ static int bcm5974_probe(struct usb_interface *iface,
        }
 
        dev->udev = udev;
+       dev->intf = iface;
        dev->input = input_dev;
        dev->cfg = *cfg;
        mutex_init(&dev->pm_mutex);
@@ -652,6 +708,7 @@ static struct usb_driver bcm5974_driver = {
        .resume                 = bcm5974_resume,
        .reset_resume           = bcm5974_resume,
        .id_table               = bcm5974_table,
+       .supports_autosuspend   = 1,
 };
 
 static int __init bcm5974_init(void)