Commit fef684af authored by Jiri Kosina's avatar Jiri Kosina

Merge branch 'for-5.6/logitech' into for-linus

- voltage support for newer Logitech HID++ devices, from Pedro Vanzella.

- MX Master support improvements, from Adrian Freund and Mazin Rezk
parents a09ac40e 8f2828d7
......@@ -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 },
{}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment