HID: wacom: Add support for Express Key Remote.
authorAaron Skomra <skomra@gmail.com>
Thu, 20 Aug 2015 23:05:17 +0000 (16:05 -0700)
committerJiri Kosina <jkosina@suse.cz>
Fri, 28 Aug 2015 18:43:20 +0000 (20:43 +0200)
This device is pad (buttons) only, there is no stylus or touch. Up to
five remotes can pair with the device's associated USB dongle.

Signed-off-by: Aaron Skomra <aaron.skomra@wacom.com>
Reviewed-by: Jason Gerecke <jason.gerecke@wacom.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Documentation/ABI/testing/sysfs-driver-wacom
drivers/hid/wacom.h
drivers/hid/wacom_sys.c
drivers/hid/wacom_wac.c
drivers/hid/wacom_wac.h

index c4f0fed64a6e5ebca402b340757f6a3eb4b0c376..dca4293407726654d4a5da02f1911c644d052972 100644 (file)
@@ -77,3 +77,22 @@ Description:
                The format is also scrambled, like in the USB mode, and it can
                be summarized by converting 76543210 into GECA6420.
                                            HGFEDCBA      HFDB7531
+
+What:          /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_remote/unpair_remote
+Date:          July 2015
+Contact:       linux-input@vger.kernel.org
+Description:
+               Writing the character sequence '*' followed by a newline to
+               this file will delete all of the current pairings on the
+               device. Other character sequences are reserved. This file is
+               write only.
+
+What:          /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_remote/<serial_number>/remote_mode
+Date:          July 2015
+Contact:       linux-input@vger.kernel.org
+Description:
+               Reading from this file reports the mode status of the
+               remote as indicated by the LED lights on the device. If no
+               reports have been received from the paired device, reading
+               from this file will report '-1'. The mode is read-only
+               and cannot be set through the driver.
index a533787a6d857f815bcf816aefcde3c6d7ad7a4f..4681a65a4579cf1b9fbcae830f583e4e9c3d4d73 100644 (file)
@@ -113,7 +113,7 @@ struct wacom {
        struct mutex lock;
        struct work_struct work;
        struct wacom_led {
-               u8 select[2]; /* status led selector (0..3) */
+               u8 select[5]; /* status led selector (0..3) */
                u8 llv;       /* status led brightness no button (1..127) */
                u8 hlv;       /* status led brightness button pressed (1..127) */
                u8 img_lum;   /* OLED matrix display brightness */
@@ -123,6 +123,8 @@ struct wacom {
        struct power_supply *ac;
        struct power_supply_desc battery_desc;
        struct power_supply_desc ac_desc;
+       struct kobject *remote_dir;
+       struct attribute_group remote_group[5];
 };
 
 static inline void wacom_schedule_work(struct wacom_wac *wacom_wac)
@@ -147,4 +149,7 @@ int wacom_wac_event(struct hid_device *hdev, struct hid_field *field,
                struct hid_usage *usage, __s32 value);
 void wacom_wac_report(struct hid_device *hdev, struct hid_report *report);
 void wacom_battery_work(struct work_struct *work);
+int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial,
+                                  int index);
+void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial);
 #endif
index 6edb7d1364762002af86355126d4de5ea8d65ab8..5f6e48e55df9b0ad305a934890e895408417d758 100644 (file)
 #define WAC_CMD_ICON_XFER      0x23
 #define WAC_CMD_ICON_BT_XFER   0x26
 #define WAC_CMD_RETRIES                10
+#define WAC_CMD_DELETE_PAIRING 0x20
+#define WAC_CMD_UNPAIR_ALL     0xFF
+#define WAC_REMOTE_SERIAL_MAX_STRLEN   9
 
 #define DEV_ATTR_RW_PERM (S_IRUGO | S_IWUSR | S_IWGRP)
 #define DEV_ATTR_WO_PERM (S_IWUSR | S_IWGRP)
+#define DEV_ATTR_RO_PERM (S_IRUSR | S_IRGRP)
 
 static int wacom_get_report(struct hid_device *hdev, u8 type, u8 *buf,
                            size_t size, unsigned int retries)
@@ -1119,6 +1123,189 @@ static ssize_t wacom_store_speed(struct device *dev,
 static DEVICE_ATTR(speed, DEV_ATTR_RW_PERM,
                wacom_show_speed, wacom_store_speed);
 
+
+static ssize_t wacom_show_remote_mode(struct kobject *kobj,
+                                     struct kobj_attribute *kattr,
+                                     char *buf, int index)
+{
+       struct device *dev = container_of(kobj->parent, struct device, kobj);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct wacom *wacom = hid_get_drvdata(hdev);
+       u8 mode;
+
+       mode = wacom->led.select[index];
+       if (mode >= 0 && mode < 3)
+               return snprintf(buf, PAGE_SIZE, "%d\n", mode);
+       else
+               return snprintf(buf, PAGE_SIZE, "%d\n", -1);
+}
+
+#define DEVICE_EKR_ATTR_GROUP(SET_ID)                                  \
+static ssize_t wacom_show_remote##SET_ID##_mode(struct kobject *kobj,  \
+                              struct kobj_attribute *kattr, char *buf) \
+{                                                                      \
+       return wacom_show_remote_mode(kobj, kattr, buf, SET_ID);        \
+}                                                                      \
+static struct kobj_attribute remote##SET_ID##_mode_attr = {            \
+       .attr = {.name = "remote_mode",                                 \
+               .mode = DEV_ATTR_RO_PERM},                              \
+       .show = wacom_show_remote##SET_ID##_mode,                       \
+};                                                                     \
+static struct attribute *remote##SET_ID##_serial_attrs[] = {           \
+       &remote##SET_ID##_mode_attr.attr,                               \
+       NULL                                                            \
+};                                                                     \
+static struct attribute_group remote##SET_ID##_serial_group = {                \
+       .name = NULL,                                                   \
+       .attrs = remote##SET_ID##_serial_attrs,                         \
+}
+
+DEVICE_EKR_ATTR_GROUP(0);
+DEVICE_EKR_ATTR_GROUP(1);
+DEVICE_EKR_ATTR_GROUP(2);
+DEVICE_EKR_ATTR_GROUP(3);
+DEVICE_EKR_ATTR_GROUP(4);
+
+int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int index)
+{
+       int error = 0;
+       char *buf;
+       struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+       wacom_wac->serial[index] = serial;
+
+       buf = kzalloc(WAC_REMOTE_SERIAL_MAX_STRLEN, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+       snprintf(buf, WAC_REMOTE_SERIAL_MAX_STRLEN, "%d", serial);
+       wacom->remote_group[index].name = buf;
+
+       error = sysfs_create_group(wacom->remote_dir,
+                                  &wacom->remote_group[index]);
+       if (error) {
+               hid_err(wacom->hdev,
+                       "cannot create sysfs group err: %d\n", error);
+               kobject_put(wacom->remote_dir);
+               return error;
+       }
+
+       return 0;
+}
+
+void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial)
+{
+       struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+       int i;
+
+       if (!serial)
+               return;
+
+       for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+               if (wacom_wac->serial[i] == serial) {
+                       wacom_wac->serial[i] = 0;
+                       wacom->led.select[i] = WACOM_STATUS_UNKNOWN;
+                       if (wacom->remote_group[i].name) {
+                               sysfs_remove_group(wacom->remote_dir,
+                                                  &wacom->remote_group[i]);
+                               kfree(wacom->remote_group[i].name);
+                               wacom->remote_group[i].name = NULL;
+                       }
+               }
+       }
+}
+
+static int wacom_cmd_unpair_remote(struct wacom *wacom, unsigned char selector)
+{
+       const size_t buf_size = 2;
+       unsigned char *buf;
+       int retval;
+
+       buf = kzalloc(buf_size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       buf[0] = WAC_CMD_DELETE_PAIRING;
+       buf[1] = selector;
+
+       retval = wacom_set_report(wacom->hdev, HID_OUTPUT_REPORT, buf,
+                                 buf_size, WAC_CMD_RETRIES);
+       kfree(buf);
+
+       return retval;
+}
+
+static ssize_t wacom_store_unpair_remote(struct kobject *kobj,
+                                        struct kobj_attribute *attr,
+                                        const char *buf, size_t count)
+{
+       unsigned char selector = 0;
+       struct device *dev = container_of(kobj->parent, struct device, kobj);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct wacom *wacom = hid_get_drvdata(hdev);
+       int err;
+
+       if (!strncmp(buf, "*\n", 2)) {
+               selector = WAC_CMD_UNPAIR_ALL;
+       } else {
+               hid_info(wacom->hdev, "remote: unrecognized unpair code: %s\n",
+                        buf);
+               return -1;
+       }
+
+       mutex_lock(&wacom->lock);
+
+       err = wacom_cmd_unpair_remote(wacom, selector);
+       mutex_unlock(&wacom->lock);
+
+       return err < 0 ? err : count;
+}
+
+static struct kobj_attribute unpair_remote_attr = {
+       .attr = {.name = "unpair_remote", .mode = 0200},
+       .store = wacom_store_unpair_remote,
+};
+
+static const struct attribute *remote_unpair_attrs[] = {
+       &unpair_remote_attr.attr,
+       NULL
+};
+
+static int wacom_initialize_remote(struct wacom *wacom)
+{
+       int error = 0;
+       struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+       int i;
+
+       if (wacom->wacom_wac.features.type != REMOTE)
+               return 0;
+
+       wacom->remote_group[0] = remote0_serial_group;
+       wacom->remote_group[1] = remote1_serial_group;
+       wacom->remote_group[2] = remote2_serial_group;
+       wacom->remote_group[3] = remote3_serial_group;
+       wacom->remote_group[4] = remote4_serial_group;
+
+       wacom->remote_dir = kobject_create_and_add("wacom_remote",
+                                                  &wacom->hdev->dev.kobj);
+       if (!wacom->remote_dir)
+               return -ENOMEM;
+
+       error = sysfs_create_files(wacom->remote_dir, remote_unpair_attrs);
+
+       if (error) {
+               hid_err(wacom->hdev,
+                       "cannot create sysfs group err: %d\n", error);
+               return error;
+       }
+
+       for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+               wacom->led.select[i] = WACOM_STATUS_UNKNOWN;
+               wacom_wac->serial[i] = 0;
+       }
+
+       return 0;
+}
+
 static struct input_dev *wacom_allocate_input(struct wacom *wacom)
 {
        struct input_dev *input_dev;
@@ -1164,6 +1351,8 @@ static void wacom_clean_inputs(struct wacom *wacom)
                else
                        input_free_device(wacom->wacom_wac.pad_input);
        }
+       if (wacom->remote_dir)
+               kobject_put(wacom->remote_dir);
        wacom->wacom_wac.pen_input = NULL;
        wacom->wacom_wac.touch_input = NULL;
        wacom->wacom_wac.pad_input = NULL;
@@ -1243,10 +1432,16 @@ static int wacom_register_inputs(struct wacom *wacom)
                error = wacom_initialize_leds(wacom);
                if (error)
                        goto fail_leds;
+
+               error = wacom_initialize_remote(wacom);
+               if (error)
+                       goto fail_remote;
        }
 
        return 0;
 
+fail_remote:
+       wacom_destroy_leds(wacom);
 fail_leds:
        input_unregister_device(pad_input_dev);
        pad_input_dev = NULL;
index ee5d278afa3f789ef60b9dde57d99fe2954e3b4f..391a68731fe345be600771059a165184d36f8b21 100644 (file)
@@ -631,6 +631,130 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
        return 0;
 }
 
+static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+       unsigned char *data = wacom_wac->data;
+       struct input_dev *input = wacom_wac->pad_input;
+       struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+       struct wacom_features *features = &wacom_wac->features;
+       int bat_charging, bat_percent, touch_ring_mode;
+       __u32 serial;
+       int i;
+
+       if (data[0] != WACOM_REPORT_REMOTE) {
+               dev_dbg(input->dev.parent,
+                       "%s: received unknown report #%d", __func__, data[0]);
+               return 0;
+       }
+
+       serial = data[3] + (data[4] << 8) + (data[5] << 16);
+       wacom_wac->id[0] = PAD_DEVICE_ID;
+
+       input_report_key(input, BTN_0, (data[9] & 0x01));
+       input_report_key(input, BTN_1, (data[9] & 0x02));
+       input_report_key(input, BTN_2, (data[9] & 0x04));
+       input_report_key(input, BTN_3, (data[9] & 0x08));
+       input_report_key(input, BTN_4, (data[9] & 0x10));
+       input_report_key(input, BTN_5, (data[9] & 0x20));
+       input_report_key(input, BTN_6, (data[9] & 0x40));
+       input_report_key(input, BTN_7, (data[9] & 0x80));
+
+       input_report_key(input, BTN_8, (data[10] & 0x01));
+       input_report_key(input, BTN_9, (data[10] & 0x02));
+       input_report_key(input, BTN_A, (data[10] & 0x04));
+       input_report_key(input, BTN_B, (data[10] & 0x08));
+       input_report_key(input, BTN_C, (data[10] & 0x10));
+       input_report_key(input, BTN_X, (data[10] & 0x20));
+       input_report_key(input, BTN_Y, (data[10] & 0x40));
+       input_report_key(input, BTN_Z, (data[10] & 0x80));
+
+       input_report_key(input, BTN_BASE, (data[11] & 0x01));
+       input_report_key(input, BTN_BASE2, (data[11] & 0x02));
+
+       if (data[12] & 0x80)
+               input_report_abs(input, ABS_WHEEL, (data[12] & 0x7f));
+       else
+               input_report_abs(input, ABS_WHEEL, 0);
+
+       bat_percent = data[7] & 0x7f;
+       bat_charging = !!(data[7] & 0x80);
+
+       if (data[9] | data[10] | (data[11] & 0x03) | data[12])
+               input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+       else
+               input_report_abs(input, ABS_MISC, 0);
+
+       input_event(input, EV_MSC, MSC_SERIAL, serial);
+
+       /*Which mode select (LED light) is currently on?*/
+       touch_ring_mode = (data[11] & 0xC0) >> 6;
+
+       for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+               if (wacom_wac->serial[i] == serial)
+                       wacom->led.select[i] = touch_ring_mode;
+       }
+
+       if (!wacom->battery &&
+           !(features->quirks & WACOM_QUIRK_BATTERY)) {
+               features->quirks |= WACOM_QUIRK_BATTERY;
+               INIT_WORK(&wacom->work, wacom_battery_work);
+               wacom_schedule_work(wacom_wac);
+       }
+
+       wacom_notify_battery(wacom_wac, bat_percent, bat_charging, 1,
+                            bat_charging);
+
+       return 1;
+}
+
+static int wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+       struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+       unsigned char *data = wacom_wac->data;
+       int i;
+
+       if (data[0] != WACOM_REPORT_DEVICE_LIST)
+               return 0;
+
+       for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+               int j = i * 6;
+               int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4];
+               bool connected = data[j+2];
+
+               if (connected) {
+                       int k;
+
+                       if (wacom_wac->serial[i] == serial)
+                               continue;
+
+                       if (wacom_wac->serial[i]) {
+                               wacom_remote_destroy_attr_group(wacom,
+                                                       wacom_wac->serial[i]);
+                       }
+
+                       /* A remote can pair more than once with an EKR,
+                        * check to make sure this serial isn't already paired.
+                        */
+                       for (k = 0; k < WACOM_MAX_REMOTES; k++) {
+                               if (wacom_wac->serial[k] == serial)
+                                       break;
+                       }
+
+                       if (k < WACOM_MAX_REMOTES) {
+                               wacom_wac->serial[i] = serial;
+                               continue;
+                       }
+                       wacom_remote_create_attr_group(wacom, serial, i);
+
+               } else if (wacom_wac->serial[i]) {
+                       wacom_remote_destroy_attr_group(wacom,
+                                                       wacom_wac->serial[i]);
+               }
+       }
+
+       return 0;
+}
+
 static void wacom_intuos_general(struct wacom_wac *wacom)
 {
        struct wacom_features *features = &wacom->features;
@@ -2191,6 +2315,13 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
                sync = wacom_wireless_irq(wacom_wac, len);
                break;
 
+       case REMOTE:
+               if (wacom_wac->data[0] == WACOM_REPORT_DEVICE_LIST)
+                       sync = wacom_remote_status_irq(wacom_wac, len);
+               else
+                       sync = wacom_remote_irq(wacom_wac, len);
+               break;
+
        default:
                sync = false;
                break;
@@ -2298,6 +2429,9 @@ void wacom_setup_device_quirks(struct wacom *wacom)
        if (features->type == BAMBOO_PAD)
                features->device_type = WACOM_DEVICETYPE_TOUCH;
 
+       if (features->type == REMOTE)
+               features->device_type = WACOM_DEVICETYPE_PAD;
+
        if (wacom->hdev->bus == BUS_BLUETOOTH)
                features->quirks |= WACOM_QUIRK_BATTERY;
 
@@ -2717,6 +2851,11 @@ int wacom_setup_pad_input_capabilities(struct input_dev *input_dev,
 
                break;
 
+       case REMOTE:
+               input_set_capability(input_dev, EV_MSC, MSC_SERIAL);
+               input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+               break;
+
        default:
                /* no pad supported */
                return -ENODEV;
@@ -3186,6 +3325,10 @@ static const struct wacom_features wacom_features_0x323 =
        { "Wacom Intuos P M", 21600, 13500, 1023, 31,
          INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
          .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x331 =
+       { "Wacom Express Key Remote", 0, 0, 0, 0,
+         REMOTE, 0, 0, 18, .check_for_hid_type = true,
+         .hid_type = HID_TYPE_USBNONE };
 
 static const struct wacom_features wacom_features_HID_ANY_ID =
        { "Wacom HID", .type = HID_GENERIC };
@@ -3341,6 +3484,7 @@ const struct hid_device_id wacom_ids[] = {
        { USB_DEVICE_WACOM(0x32B) },
        { USB_DEVICE_WACOM(0x32C) },
        { USB_DEVICE_WACOM(0x32F) },
+       { USB_DEVICE_WACOM(0x331) },
        { USB_DEVICE_WACOM(0x333) },
        { USB_DEVICE_WACOM(0x335) },
        { USB_DEVICE_WACOM(0x336) },
index 4ee5c13b4e75968e64a649fb374354ee6bd8205f..1e270d401e181e45802cb869d30998d1017ff735 100644 (file)
@@ -16,6 +16,8 @@
 #define WACOM_PKGLEN_MAX       192
 
 #define WACOM_NAME_MAX         64
+#define WACOM_MAX_REMOTES      5
+#define WACOM_STATUS_UNKNOWN   255
 
 /* packet length for individual models */
 #define WACOM_PKGLEN_BBFUN      9
@@ -65,6 +67,8 @@
 #define WACOM_REPORT_USB               192
 #define WACOM_REPORT_BPAD_PEN          3
 #define WACOM_REPORT_BPAD_TOUCH                16
+#define WACOM_REPORT_DEVICE_LIST       16
+#define WACOM_REPORT_REMOTE            17
 
 /* device quirks */
 #define WACOM_QUIRK_BBTOUCH_LOWRES     0x0001
@@ -129,6 +133,7 @@ enum {
        WACOM_24HDT,
        WACOM_27QHDT,
        BAMBOO_PAD,
+       REMOTE,
        TABLETPC,   /* add new TPC below */
        TABLETPCE,
        TABLETPC2FG,
@@ -208,7 +213,7 @@ struct wacom_wac {
        unsigned char data[WACOM_PKGLEN_MAX];
        int tool[2];
        int id[2];
-       __u32 serial[2];
+       __u32 serial[5];
        bool reporting_data;
        struct wacom_features features;
        struct wacom_shared *shared;