]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - drivers/hid/hid-logitech-hidpp.c
HID: logitech-hidpp: rework probe path for unifying devices
[mirror_ubuntu-artful-kernel.git] / drivers / hid / hid-logitech-hidpp.c
index 2e2515a4c070eac305a834c9229d0e1fa27f722f..db15cfc622c49dca1b2e2d300dee6a0badb4d42f 100644 (file)
@@ -58,13 +58,16 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_CLASS_G920                 BIT(3)
 
 /* bits 2..20 are reserved for classes */
-#define HIDPP_QUIRK_CONNECT_EVENTS             BIT(21)
+/* #define HIDPP_QUIRK_CONNECT_EVENTS          BIT(21) disabled */
 #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS       BIT(22)
 #define HIDPP_QUIRK_NO_HIDINPUT                        BIT(23)
 #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS       BIT(24)
+#define HIDPP_QUIRK_UNIFYING                   BIT(25)
 
-#define HIDPP_QUIRK_DELAYED_INIT               (HIDPP_QUIRK_NO_HIDINPUT | \
-                                                HIDPP_QUIRK_CONNECT_EVENTS)
+#define HIDPP_QUIRK_DELAYED_INIT               HIDPP_QUIRK_NO_HIDINPUT
+
+#define HIDPP_CAPABILITY_HIDPP10_BATTERY       BIT(0)
+#define HIDPP_CAPABILITY_HIDPP20_BATTERY       BIT(1)
 
 /*
  * There are two hidpp protocols in use, the first version hidpp10 is known
@@ -110,6 +113,15 @@ struct hidpp_report {
        };
 } __packed;
 
+struct hidpp_battery {
+       u8 feature_index;
+       struct power_supply_desc desc;
+       struct power_supply *ps;
+       char name[64];
+       int status;
+       int level;
+};
+
 struct hidpp_device {
        struct hid_device *hid_dev;
        struct mutex send_mutex;
@@ -128,8 +140,10 @@ struct hidpp_device {
        struct input_dev *delayed_input;
 
        unsigned long quirks;
-};
+       unsigned long capabilities;
 
+       struct hidpp_battery battery;
+};
 
 /* HID++ 1.0 error codes */
 #define HIDPP_ERROR                            0x8f
@@ -381,14 +395,14 @@ static void hidpp_prefix_name(char **name, int name_length)
 #define HIDPP_GET_LONG_REGISTER                                0x83
 
 #define HIDPP_REG_PAIRING_INFORMATION                  0xB5
-#define DEVICE_NAME                                    0x40
+#define HIDPP_EXTENDED_PAIRING                         0x30
+#define HIDPP_DEVICE_NAME                              0x40
 
-static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev)
+static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev)
 {
        struct hidpp_report response;
        int ret;
-       /* hid-logitech-dj is in charge of setting the right device index */
-       u8 params[1] = { DEVICE_NAME };
+       u8 params[1] = { HIDPP_DEVICE_NAME };
        char *name;
        int len;
 
@@ -417,6 +431,54 @@ static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev)
        return name;
 }
 
+static int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial)
+{
+       struct hidpp_report response;
+       int ret;
+       u8 params[1] = { HIDPP_EXTENDED_PAIRING };
+
+       ret = hidpp_send_rap_command_sync(hidpp,
+                                       REPORT_ID_HIDPP_SHORT,
+                                       HIDPP_GET_LONG_REGISTER,
+                                       HIDPP_REG_PAIRING_INFORMATION,
+                                       params, 1, &response);
+       if (ret)
+               return ret;
+
+       /*
+        * We don't care about LE or BE, we will output it as a string
+        * with %4phD, so we need to keep the order.
+        */
+       *serial = *((u32 *)&response.rap.params[1]);
+       return 0;
+}
+
+static int hidpp_unifying_init(struct hidpp_device *hidpp)
+{
+       struct hid_device *hdev = hidpp->hid_dev;
+       const char *name;
+       u32 serial;
+       int ret;
+
+       ret = hidpp_unifying_get_serial(hidpp, &serial);
+       if (ret)
+               return ret;
+
+       snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD",
+                hdev->product, &serial);
+       dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
+
+       name = hidpp_unifying_get_name(hidpp);
+       if (!name)
+               return -EIO;
+
+       snprintf(hdev->name, sizeof(hdev->name), "%s", name);
+       dbg_hid("HID++ Unifying: Got name: %s\n", name);
+
+       kfree(name);
+       return 0;
+}
+
 /* -------------------------------------------------------------------------- */
 /* 0x0000: Root                                                               */
 /* -------------------------------------------------------------------------- */
@@ -606,6 +668,229 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
        return name;
 }
 
+/* -------------------------------------------------------------------------- */
+/* 0x1000: Battery level status                                               */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_BATTERY_LEVEL_STATUS                                0x1000
+
+#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS      0x00
+#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY                0x10
+
+#define EVENT_BATTERY_LEVEL_STATUS_BROADCAST                   0x00
+
+static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level,
+                                                int *next_level)
+{
+       int status;
+       int level_override;
+
+       *level = data[0];
+       *next_level = data[1];
+
+       /* When discharging, we can rely on the device reported level.
+        * For all other states the device reports level 0 (unknown). Make up
+        * a number instead
+        */
+       switch (data[2]) {
+               case 0: /* discharging (in use) */
+                       status = POWER_SUPPLY_STATUS_DISCHARGING;
+                       level_override = 0;
+                       break;
+               case 1: /* recharging */
+                       status = POWER_SUPPLY_STATUS_CHARGING;
+                       level_override = 80;
+                       break;
+               case 2: /* charge in final stage */
+                       status = POWER_SUPPLY_STATUS_CHARGING;
+                       level_override = 90;
+                       break;
+               case 3: /* charge complete */
+                       status = POWER_SUPPLY_STATUS_FULL;
+                       level_override = 100;
+                       break;
+               case 4: /* recharging below optimal speed */
+                       status = POWER_SUPPLY_STATUS_CHARGING;
+                       level_override = 50;
+                       break;
+               /* 5 = invalid battery type
+                  6 = thermal error
+                  7 = other charging error */
+               default:
+                       status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+                       level_override = 0;
+                       break;
+       }
+
+       if (level_override != 0 && *level == 0)
+               *level = level_override;
+
+       return status;
+}
+
+static int hidpp20_batterylevel_get_battery_level(struct hidpp_device *hidpp,
+                                                 u8 feature_index,
+                                                 int *status,
+                                                 int *level,
+                                                 int *next_level)
+{
+       struct hidpp_report response;
+       int ret;
+       u8 *params = (u8 *)response.fap.params;
+
+       ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+                                         CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
+                                         NULL, 0, &response);
+       if (ret > 0) {
+               hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+                       __func__, ret);
+               return -EPROTO;
+       }
+       if (ret)
+               return ret;
+
+       *status = hidpp20_batterylevel_map_status_level(params, level,
+                                                       next_level);
+
+       return 0;
+}
+
+static int hidpp20_query_battery_info(struct hidpp_device *hidpp)
+{
+       u8 feature_type;
+       int ret;
+       int status, level, next_level;
+
+       if (hidpp->battery.feature_index == 0) {
+               ret = hidpp_root_get_feature(hidpp,
+                                            HIDPP_PAGE_BATTERY_LEVEL_STATUS,
+                                            &hidpp->battery.feature_index,
+                                            &feature_type);
+               if (ret)
+                       return ret;
+       }
+
+       ret = hidpp20_batterylevel_get_battery_level(hidpp,
+                                                    hidpp->battery.feature_index,
+                                                    &status, &level, &next_level);
+       if (ret)
+               return ret;
+
+       hidpp->battery.status = status;
+       hidpp->battery.level = level;
+
+       return 0;
+}
+
+static int hidpp20_battery_event(struct hidpp_device *hidpp,
+                                u8 *data, int size)
+{
+       struct hidpp_report *report = (struct hidpp_report *)data;
+       int status, level, next_level;
+       bool changed;
+
+       if (report->fap.feature_index != hidpp->battery.feature_index ||
+           report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST)
+               return 0;
+
+       status = hidpp20_batterylevel_map_status_level(report->fap.params,
+                                                      &level, &next_level);
+
+       changed = level != hidpp->battery.level ||
+                 status != hidpp->battery.status;
+
+       if (changed) {
+               hidpp->battery.level = level;
+               hidpp->battery.status = status;
+               if (hidpp->battery.ps)
+                       power_supply_changed(hidpp->battery.ps);
+       }
+
+       return 0;
+}
+
+static enum power_supply_property hidpp_battery_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_SCOPE,
+};
+
+static int hidpp_battery_get_property(struct power_supply *psy,
+                                     enum power_supply_property psp,
+                                     union power_supply_propval *val)
+{
+       struct hidpp_device *hidpp = power_supply_get_drvdata(psy);
+       int ret = 0;
+
+       switch(psp) {
+               case POWER_SUPPLY_PROP_STATUS:
+                       val->intval = hidpp->battery.status;
+                       break;
+               case POWER_SUPPLY_PROP_CAPACITY:
+                       val->intval = hidpp->battery.level;
+                       break;
+               case POWER_SUPPLY_PROP_SCOPE:
+                       val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+                       break;
+               default:
+                       ret = -EINVAL;
+                       break;
+       }
+
+       return ret;
+}
+
+static int hidpp20_initialize_battery(struct hidpp_device *hidpp)
+{
+       static atomic_t battery_no = ATOMIC_INIT(0);
+       struct power_supply_config cfg = { .drv_data = hidpp };
+       struct power_supply_desc *desc = &hidpp->battery.desc;
+       struct hidpp_battery *battery;
+       unsigned long n;
+       int ret;
+
+       ret = hidpp20_query_battery_info(hidpp);
+       if (ret)
+               return ret;
+
+       battery = &hidpp->battery;
+
+       n = atomic_inc_return(&battery_no) - 1;
+       desc->properties = hidpp_battery_props;
+       desc->num_properties = ARRAY_SIZE(hidpp_battery_props);
+       desc->get_property = hidpp_battery_get_property;
+       sprintf(battery->name, "hidpp_battery_%ld", n);
+       desc->name = battery->name;
+       desc->type = POWER_SUPPLY_TYPE_BATTERY;
+       desc->use_for_apm = 0;
+
+       battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev,
+                                                &battery->desc,
+                                                &cfg);
+       if (IS_ERR(battery->ps))
+               return PTR_ERR(battery->ps);
+
+       power_supply_powers(battery->ps, &hidpp->hid_dev->dev);
+
+       return 0;
+}
+
+static int hidpp_initialize_battery(struct hidpp_device *hidpp)
+{
+       int ret;
+
+       if (hidpp->battery.ps)
+               return 0;
+
+       if (hidpp->protocol_major >= 2) {
+               ret = hidpp20_initialize_battery(hidpp);
+               if (ret == 0)
+                       hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY;
+       }
+
+       return ret;
+}
+
 /* -------------------------------------------------------------------------- */
 /* 0x6010: Touchpad FW items                                                  */
 /* -------------------------------------------------------------------------- */
@@ -2002,8 +2287,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
        if (unlikely(hidpp_report_is_connect_event(report))) {
                atomic_set(&hidpp->connected,
                                !(report->rap.params[0] & (1 << 6)));
-               if ((hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) &&
-                   (schedule_work(&hidpp->work) == 0))
+               if (schedule_work(&hidpp->work) == 0)
                        dbg_hid("%s: connect event already queued\n", __func__);
                return 1;
        }
@@ -2050,6 +2334,12 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
        if (ret != 0)
                return ret;
 
+       if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
+               ret = hidpp20_battery_event(hidpp, data, size);
+               if (ret != 0)
+                       return ret;
+       }
+
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hdev, data, size);
        else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
@@ -2058,20 +2348,15 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
        return 0;
 }
 
-static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying)
+static void hidpp_overwrite_name(struct hid_device *hdev)
 {
        struct hidpp_device *hidpp = hid_get_drvdata(hdev);
        char *name;
 
-       if (use_unifying)
-               /*
-                * the device is connected through an Unifying receiver, and
-                * might not be already connected.
-                * Ask the receiver for its name.
-                */
-               name = hidpp_get_unifying_name(hidpp);
-       else
-               name = hidpp_get_device_name(hidpp);
+       if (hidpp->protocol_major < 2)
+               return;
+
+       name = hidpp_get_device_name(hidpp);
 
        if (!name) {
                hid_err(hdev, "unable to retrieve the name of the device");
@@ -2158,6 +2443,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
                         hidpp->protocol_major, hidpp->protocol_minor);
        }
 
+       hidpp_initialize_battery(hidpp);
+
        if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
                /* if HID created the input nodes for us, we can stop now */
                return;
@@ -2211,9 +2498,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
        hidpp->quirks = id->driver_data;
 
+       if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
+               hidpp->quirks |= HIDPP_QUIRK_UNIFYING;
+
        if (disable_raw_mode) {
                hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
-               hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS;
                hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
        }
 
@@ -2263,8 +2552,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        /* Allow incoming packets */
        hid_device_io_start(hdev);
 
+       if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
+               hidpp_unifying_init(hidpp);
+
        connected = hidpp_is_connected(hidpp);
-       if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
+       atomic_set(&hidpp->connected, connected);
+       if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) {
                if (!connected) {
                        ret = -ENODEV;
                        hid_err(hdev, "Device not connected");
@@ -2273,10 +2566,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
                hid_info(hdev, "HID++ %u.%u device connected.\n",
                         hidpp->protocol_major, hidpp->protocol_minor);
-       }
 
-       hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE);
-       atomic_set(&hidpp->connected, connected);
+               hidpp_overwrite_name(hdev);
+       }
 
        if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
                ret = wtp_get_config(hidpp);
@@ -2299,12 +2591,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                }
        }
 
-       if (hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) {
-               /* Allow incoming packets */
-               hid_device_io_start(hdev);
+       /* Allow incoming packets */
+       hid_device_io_start(hdev);
 
-               hidpp_connect_event(hidpp);
-       }
+       hidpp_connect_event(hidpp);
 
        return ret;
 
@@ -2357,7 +2647,7 @@ static const struct hid_device_id hidpp_devices[] = {
        { /* Keyboard logitech K400 */
          HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
                USB_VENDOR_ID_LOGITECH, 0x4024),
-         .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_CLASS_K400 },
+         .driver_data = HIDPP_QUIRK_CLASS_K400 },
 
        { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
                USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},