]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/hid/hid-logitech-hidpp.c
Merge tag 'efi-urgent-2020-02-09' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux.git] / drivers / hid / hid-logitech-hidpp.c
index cd9193078525be7a653fed0e09851880e2ad110c..70e1cb928bf038876cd603e99302ea7e5687c426 100644 (file)
@@ -49,6 +49,10 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_REPORT_LONG_LENGTH               20
 #define HIDPP_REPORT_VERY_LONG_MAX_LENGTH      64
 
+#define HIDPP_REPORT_SHORT_SUPPORTED           BIT(0)
+#define HIDPP_REPORT_LONG_SUPPORTED            BIT(1)
+#define HIDPP_REPORT_VERY_LONG_SUPPORTED       BIT(2)
+
 #define HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS      0x03
 #define HIDPP_SUB_ID_ROLLER                    0x05
 #define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS          0x06
@@ -87,6 +91,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_CAPABILITY_HIDPP20_BATTERY       BIT(1)
 #define HIDPP_CAPABILITY_BATTERY_MILEAGE       BIT(2)
 #define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS  BIT(3)
+#define HIDPP_CAPABILITY_BATTERY_VOLTAGE       BIT(4)
 
 /*
  * There are two hidpp protocols in use, the first version hidpp10 is known
@@ -135,12 +140,15 @@ struct hidpp_report {
 struct hidpp_battery {
        u8 feature_index;
        u8 solar_feature_index;
+       u8 voltage_feature_index;
        struct power_supply_desc desc;
        struct power_supply *ps;
        char name[64];
        int status;
        int capacity;
        int level;
+       int voltage;
+       int charge_type;
        bool online;
 };
 
@@ -183,9 +191,12 @@ struct hidpp_device {
 
        unsigned long quirks;
        unsigned long capabilities;
+       u8 supported_reports;
 
        struct hidpp_battery battery;
        struct hidpp_scroll_counter vertical_wheel_counter;
+
+       u8 wireless_feature_index;
 };
 
 /* HID++ 1.0 error codes */
@@ -340,6 +351,11 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
        struct hidpp_report *message;
        int ret, max_count;
 
+       /* Send as long report if short reports are not supported. */
+       if (report_id == REPORT_ID_HIDPP_SHORT &&
+           !(hidpp_dev->supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
+               report_id = REPORT_ID_HIDPP_LONG;
+
        switch (report_id) {
        case REPORT_ID_HIDPP_SHORT:
                max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
@@ -393,10 +409,13 @@ static inline bool hidpp_match_error(struct hidpp_report *question,
            (answer->fap.params[0] == question->fap.funcindex_clientid);
 }
 
-static inline bool hidpp_report_is_connect_event(struct hidpp_report *report)
+static inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp,
+               struct hidpp_report *report)
 {
-       return (report->report_id == REPORT_ID_HIDPP_SHORT) &&
-               (report->rap.sub_id == 0x41);
+       return (hidpp->wireless_feature_index &&
+               (report->fap.feature_index == hidpp->wireless_feature_index)) ||
+               ((report->report_id == REPORT_ID_HIDPP_SHORT) &&
+               (report->rap.sub_id == 0x41));
 }
 
 /**
@@ -1222,6 +1241,144 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp,
        return 0;
 }
 
+/* -------------------------------------------------------------------------- */
+/* 0x1001: Battery voltage                                                    */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_BATTERY_VOLTAGE 0x1001
+
+#define CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE 0x00
+
+#define EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST 0x00
+
+static int hidpp20_battery_map_status_voltage(u8 data[3], int *voltage,
+                                               int *level, int *charge_type)
+{
+       int status;
+
+       long charge_sts = (long)data[2];
+
+       *level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+       switch (data[2] & 0xe0) {
+       case 0x00:
+               status = POWER_SUPPLY_STATUS_CHARGING;
+               break;
+       case 0x20:
+               status = POWER_SUPPLY_STATUS_FULL;
+               *level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+               break;
+       case 0x40:
+               status = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case 0xe0:
+               status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               break;
+       default:
+               status = POWER_SUPPLY_STATUS_UNKNOWN;
+       }
+
+       *charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+       if (test_bit(3, &charge_sts)) {
+               *charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
+       }
+       if (test_bit(4, &charge_sts)) {
+               *charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+       }
+
+       if (test_bit(5, &charge_sts)) {
+               *level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+       }
+
+       *voltage = get_unaligned_be16(data);
+
+       return status;
+}
+
+static int hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp,
+                                                u8 feature_index,
+                                                int *status, int *voltage,
+                                                int *level, int *charge_type)
+{
+       struct hidpp_report response;
+       int ret;
+       u8 *params = (u8 *)response.fap.params;
+
+       ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+                                         CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE,
+                                         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;
+
+       hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_VOLTAGE;
+
+       *status = hidpp20_battery_map_status_voltage(params, voltage,
+                                                    level, charge_type);
+
+       return 0;
+}
+
+static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
+{
+       u8 feature_type;
+       int ret;
+       int status, voltage, level, charge_type;
+
+       if (hidpp->battery.voltage_feature_index == 0xff) {
+               ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
+                                            &hidpp->battery.voltage_feature_index,
+                                            &feature_type);
+               if (ret)
+                       return ret;
+       }
+
+       ret = hidpp20_battery_get_battery_voltage(hidpp,
+                                                 hidpp->battery.voltage_feature_index,
+                                                 &status, &voltage, &level, &charge_type);
+
+       if (ret)
+               return ret;
+
+       hidpp->battery.status = status;
+       hidpp->battery.voltage = voltage;
+       hidpp->battery.level = level;
+       hidpp->battery.charge_type = charge_type;
+       hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+       return 0;
+}
+
+static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp,
+                                           u8 *data, int size)
+{
+       struct hidpp_report *report = (struct hidpp_report *)data;
+       int status, voltage, level, charge_type;
+
+       if (report->fap.feature_index != hidpp->battery.voltage_feature_index ||
+               report->fap.funcindex_clientid != EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST)
+               return 0;
+
+       status = hidpp20_battery_map_status_voltage(report->fap.params, &voltage,
+                                                   &level, &charge_type);
+
+       hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+       if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
+               hidpp->battery.voltage = voltage;
+               hidpp->battery.status = status;
+               hidpp->battery.level = level;
+               hidpp->battery.charge_type = charge_type;
+               if (hidpp->battery.ps)
+                       power_supply_changed(hidpp->battery.ps);
+       }
+       return 0;
+}
+
 static enum power_supply_property hidpp_battery_props[] = {
        POWER_SUPPLY_PROP_ONLINE,
        POWER_SUPPLY_PROP_STATUS,
@@ -1231,6 +1388,7 @@ static enum power_supply_property hidpp_battery_props[] = {
        POWER_SUPPLY_PROP_SERIAL_NUMBER,
        0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */
        0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */
+       0, /* placeholder for POWER_SUPPLY_PROP_VOLTAGE_NOW, */
 };
 
 static int hidpp_battery_get_property(struct power_supply *psy,
@@ -1268,6 +1426,13 @@ static int hidpp_battery_get_property(struct power_supply *psy,
                case POWER_SUPPLY_PROP_SERIAL_NUMBER:
                        val->strval = hidpp->hid_dev->uniq;
                        break;
+               case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+                       /* hardware reports voltage in in mV. sysfs expects uV */
+                       val->intval = hidpp->battery.voltage * 1000;
+                       break;
+               case POWER_SUPPLY_PROP_CHARGE_TYPE:
+                       val->intval = hidpp->battery.charge_type;
+                       break;
                default:
                        ret = -EINVAL;
                        break;
@@ -1276,6 +1441,24 @@ static int hidpp_battery_get_property(struct power_supply *psy,
        return ret;
 }
 
+/* -------------------------------------------------------------------------- */
+/* 0x1d4b: Wireless device status                                             */
+/* -------------------------------------------------------------------------- */
+#define HIDPP_PAGE_WIRELESS_DEVICE_STATUS                      0x1d4b
+
+static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
+{
+       u8 feature_type;
+       int ret;
+
+       ret = hidpp_root_get_feature(hidpp,
+                                    HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
+                                    &hidpp->wireless_feature_index,
+                                    &feature_type);
+
+       return ret;
+}
+
 /* -------------------------------------------------------------------------- */
 /* 0x2120: Hi-resolution scrolling                                            */
 /* -------------------------------------------------------------------------- */
@@ -3091,7 +3274,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
                }
        }
 
-       if (unlikely(hidpp_report_is_connect_event(report))) {
+       if (unlikely(hidpp_report_is_connect_event(hidpp, report))) {
                atomic_set(&hidpp->connected,
                                !(report->rap.params[0] & (1 << 6)));
                if (schedule_work(&hidpp->work) == 0)
@@ -3106,6 +3289,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
                ret = hidpp_solar_battery_event(hidpp, data, size);
                if (ret != 0)
                        return ret;
+               ret = hidpp20_battery_voltage_event(hidpp, data, size);
+               if (ret != 0)
+                       return ret;
        }
 
        if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3227,12 +3413,16 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
 
        hidpp->battery.feature_index = 0xff;
        hidpp->battery.solar_feature_index = 0xff;
+       hidpp->battery.voltage_feature_index = 0xff;
 
        if (hidpp->protocol_major >= 2) {
                if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
                        ret = hidpp_solar_request_battery_event(hidpp);
-               else
-                       ret = hidpp20_query_battery_info(hidpp);
+               else {
+                       ret = hidpp20_query_battery_voltage_info(hidpp);
+                       if (ret)
+                               ret = hidpp20_query_battery_info(hidpp);
+               }
 
                if (ret)
                        return ret;
@@ -3257,7 +3447,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
        if (!battery_props)
                return -ENOMEM;
 
-       num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 2;
+       num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3;
 
        if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
                battery_props[num_battery_props++] =
@@ -3267,6 +3457,10 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
                battery_props[num_battery_props++] =
                                POWER_SUPPLY_PROP_CAPACITY_LEVEL;
 
+       if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
+               battery_props[num_battery_props++] =
+                       POWER_SUPPLY_PROP_VOLTAGE_NOW;
+
        battery = &hidpp->battery;
 
        n = atomic_inc_return(&battery_no) - 1;
@@ -3430,7 +3624,10 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
                else
                        hidpp10_query_battery_status(hidpp);
        } else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
-               hidpp20_query_battery_info(hidpp);
+               if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
+                       hidpp20_query_battery_voltage_info(hidpp);
+               else
+                       hidpp20_query_battery_info(hidpp);
        }
        if (hidpp->battery.ps)
                power_supply_changed(hidpp->battery.ps);
@@ -3481,10 +3678,11 @@ static int hidpp_get_report_length(struct hid_device *hdev, int id)
        return report->field[0]->report_count + 1;
 }
 
-static bool hidpp_validate_device(struct hid_device *hdev)
+static u8 hidpp_validate_device(struct hid_device *hdev)
 {
        struct hidpp_device *hidpp = hid_get_drvdata(hdev);
-       int id, report_length, supported_reports = 0;
+       int id, report_length;
+       u8 supported_reports = 0;
 
        id = REPORT_ID_HIDPP_SHORT;
        report_length = hidpp_get_report_length(hdev, id);
@@ -3492,7 +3690,7 @@ static bool hidpp_validate_device(struct hid_device *hdev)
                if (report_length < HIDPP_REPORT_SHORT_LENGTH)
                        goto bad_device;
 
-               supported_reports++;
+               supported_reports |= HIDPP_REPORT_SHORT_SUPPORTED;
        }
 
        id = REPORT_ID_HIDPP_LONG;
@@ -3501,7 +3699,7 @@ static bool hidpp_validate_device(struct hid_device *hdev)
                if (report_length < HIDPP_REPORT_LONG_LENGTH)
                        goto bad_device;
 
-               supported_reports++;
+               supported_reports |= HIDPP_REPORT_LONG_SUPPORTED;
        }
 
        id = REPORT_ID_HIDPP_VERY_LONG;
@@ -3511,7 +3709,7 @@ static bool hidpp_validate_device(struct hid_device *hdev)
                    report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH)
                        goto bad_device;
 
-               supported_reports++;
+               supported_reports |= HIDPP_REPORT_VERY_LONG_SUPPORTED;
                hidpp->very_long_report_length = report_length;
        }
 
@@ -3560,7 +3758,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        /*
         * Make sure the device is HID++ capable, otherwise treat as generic HID
         */
-       if (!hidpp_validate_device(hdev)) {
+       hidpp->supported_reports = hidpp_validate_device(hdev);
+
+       if (!hidpp->supported_reports) {
                hid_set_drvdata(hdev, NULL);
                devm_kfree(&hdev->dev, hidpp);
                return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
@@ -3617,7 +3817,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        if (ret < 0) {
                dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
                        __func__, ret);
-               hid_hw_stop(hdev);
                goto hid_hw_open_fail;
        }
 
@@ -3639,6 +3838,14 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                hidpp_overwrite_name(hdev);
        }
 
+       if (connected && hidpp->protocol_major >= 2) {
+               ret = hidpp_set_wireless_feature_index(hidpp);
+               if (ret == -ENOENT)
+                       hidpp->wireless_feature_index = 0;
+               else if (ret)
+                       goto hid_hw_init_fail;
+       }
+
        if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
                ret = wtp_get_config(hidpp);
                if (ret)
@@ -3752,6 +3959,8 @@ static const struct hid_device_id hidpp_devices[] = {
        { LDJ_DEVICE(0x4071), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
        { /* Mouse Logitech MX Master 2S */
          LDJ_DEVICE(0x4069), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+       { /* Mouse Logitech MX Master 3 */
+         LDJ_DEVICE(0x4082), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
        { /* Mouse Logitech Performance MX */
          LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
        { /* Keyboard logitech K400 */
@@ -3808,6 +4017,14 @@ static const struct hid_device_id hidpp_devices[] = {
        { /* MX5500 keyboard over Bluetooth */
          HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb30b),
          .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
+       { /* MX Master mouse over Bluetooth */
+         HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012),
+         .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e),
+         .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
+       { /* MX Master 3 mouse over Bluetooth */
+         HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023),
+         .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
        {}
 };