Input: elan_i2c - add trackstick report
authorBenjamin Tissoires <benjamin.tissoires@redhat.com>
Wed, 23 May 2018 00:23:04 +0000 (17:23 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Wed, 23 May 2018 23:46:36 +0000 (16:46 -0700)
The Elan touchpads over I2C/SMBus also can handle a trackstick.
Unfortunately, nothing tells us if the device supports trackstick (the
information lies in the PS/2 node), so rely on device properties to
determine whether to enable the trackstick node.

Link: https://bugzilla.redhat.com/show_bug.cgi?id=1313939
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Acked-by: Rob Herring <robh@kernel.org>
Acked-by: KT Liao <kt.liao@emc.com.tw>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Documentation/devicetree/bindings/input/elan_i2c.txt
drivers/input/mouse/elan_i2c_core.c

index ee3242c..d80a835 100644 (file)
@@ -14,6 +14,7 @@ Optional properties:
 - pinctrl-0: a phandle pointing to the pin settings for the device (see
   pinctrl binding [1]).
 - vcc-supply: a phandle for the regulator supplying 3.3V power.
+- elan,trackpoint: touchpad can support a trackpoint (boolean)
 
 [0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
 [1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
index 75e7575..2690a4b 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/jiffies.h>
 #include <linux/completion.h>
 #include <linux/of.h>
+#include <linux/property.h>
 #include <linux/regulator/consumer.h>
 #include <asm/unaligned.h>
 
@@ -51,6 +52,7 @@
 #define ETP_MAX_FINGERS                5
 #define ETP_FINGER_DATA_LEN    5
 #define ETP_REPORT_ID          0x5D
+#define ETP_TP_REPORT_ID       0x5E
 #define ETP_REPORT_ID_OFFSET   2
 #define ETP_TOUCH_INFO_OFFSET  3
 #define ETP_FINGER_DATA_OFFSET 4
@@ -61,6 +63,7 @@
 struct elan_tp_data {
        struct i2c_client       *client;
        struct input_dev        *input;
+       struct input_dev        *tp_input; /* trackpoint input node */
        struct regulator        *vcc;
 
        const struct elan_transport_ops *ops;
@@ -930,6 +933,33 @@ static void elan_report_absolute(struct elan_tp_data *data, u8 *packet)
        input_sync(input);
 }
 
+static void elan_report_trackpoint(struct elan_tp_data *data, u8 *report)
+{
+       struct input_dev *input = data->tp_input;
+       u8 *packet = &report[ETP_REPORT_ID_OFFSET + 1];
+       int x, y;
+
+       if (!data->tp_input) {
+               dev_warn_once(&data->client->dev,
+                             "received a trackpoint report while no trackpoint device has been created. Please report upstream.\n");
+               return;
+       }
+
+       input_report_key(input, BTN_LEFT, packet[0] & 0x01);
+       input_report_key(input, BTN_RIGHT, packet[0] & 0x02);
+       input_report_key(input, BTN_MIDDLE, packet[0] & 0x04);
+
+       if ((packet[3] & 0x0F) == 0x06) {
+               x = packet[4] - (int)((packet[1] ^ 0x80) << 1);
+               y = (int)((packet[2] ^ 0x80) << 1) - packet[5];
+
+               input_report_rel(input, REL_X, x);
+               input_report_rel(input, REL_Y, y);
+       }
+
+       input_sync(input);
+}
+
 static irqreturn_t elan_isr(int irq, void *dev_id)
 {
        struct elan_tp_data *data = dev_id;
@@ -951,11 +981,17 @@ static irqreturn_t elan_isr(int irq, void *dev_id)
        if (error)
                goto out;
 
-       if (report[ETP_REPORT_ID_OFFSET] != ETP_REPORT_ID)
+       switch (report[ETP_REPORT_ID_OFFSET]) {
+       case ETP_REPORT_ID:
+               elan_report_absolute(data, report);
+               break;
+       case ETP_TP_REPORT_ID:
+               elan_report_trackpoint(data, report);
+               break;
+       default:
                dev_err(dev, "invalid report id data (%x)\n",
                        report[ETP_REPORT_ID_OFFSET]);
-       else
-               elan_report_absolute(data, report);
+       }
 
 out:
        return IRQ_HANDLED;
@@ -966,6 +1002,36 @@ out:
  * Elan initialization functions
  ******************************************************************
  */
+
+static int elan_setup_trackpoint_input_device(struct elan_tp_data *data)
+{
+       struct device *dev = &data->client->dev;
+       struct input_dev *input;
+
+       input = devm_input_allocate_device(dev);
+       if (!input)
+               return -ENOMEM;
+
+       input->name = "Elan TrackPoint";
+       input->id.bustype = BUS_I2C;
+       input->id.vendor = ELAN_VENDOR_ID;
+       input->id.product = data->product_id;
+       input_set_drvdata(input, data);
+
+       input_set_capability(input, EV_REL, REL_X);
+       input_set_capability(input, EV_REL, REL_Y);
+       input_set_capability(input, EV_KEY, BTN_LEFT);
+       input_set_capability(input, EV_KEY, BTN_RIGHT);
+       input_set_capability(input, EV_KEY, BTN_MIDDLE);
+
+       __set_bit(INPUT_PROP_POINTER, input->propbit);
+       __set_bit(INPUT_PROP_POINTING_STICK, input->propbit);
+
+       data->tp_input = input;
+
+       return 0;
+}
+
 static int elan_setup_input_device(struct elan_tp_data *data)
 {
        struct device *dev = &data->client->dev;
@@ -1140,6 +1206,12 @@ static int elan_probe(struct i2c_client *client,
        if (error)
                return error;
 
+       if (device_property_read_bool(&client->dev, "elan,trackpoint")) {
+               error = elan_setup_trackpoint_input_device(data);
+               if (error)
+                       return error;
+       }
+
        /*
         * Platform code (ACPI, DTS) should normally set up interrupt
         * for us, but in case it did not let's fall back to using falling
@@ -1177,6 +1249,16 @@ static int elan_probe(struct i2c_client *client,
                return error;
        }
 
+       if (data->tp_input) {
+               error = input_register_device(data->tp_input);
+               if (error) {
+                       dev_err(&client->dev,
+                               "failed to register TrackPoint input device: %d\n",
+                               error);
+                       return error;
+               }
+       }
+
        /*
         * Systems using device tree should set up wakeup via DTS,
         * the rest will configure device as wakeup source by default.