HID: logitech-hidpp: add battery support for HID++ 2.0 devices
If the 0x1000 Unified Battery Level Status feature exists, expose the battery level. The main drawback is that while a device is plugged in its battery level is 0. To avoid exposing that as 0% charge we make up a number based on the charging status. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
parent
595d9e34ee
commit
5a2b190cdd
@ -62,6 +62,8 @@ MODULE_PARM_DESC(disable_tap_to_click,
|
|||||||
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
|
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
|
||||||
#define HIDPP_QUIRK_NO_HIDINPUT BIT(23)
|
#define HIDPP_QUIRK_NO_HIDINPUT BIT(23)
|
||||||
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
|
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
|
||||||
|
#define HIDPP_QUIRK_HIDPP20_BATTERY BIT(25)
|
||||||
|
#define HIDPP_QUIRK_HIDPP10_BATTERY BIT(26)
|
||||||
|
|
||||||
#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \
|
#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \
|
||||||
HIDPP_QUIRK_CONNECT_EVENTS)
|
HIDPP_QUIRK_CONNECT_EVENTS)
|
||||||
@ -110,6 +112,15 @@ struct hidpp_report {
|
|||||||
};
|
};
|
||||||
} __packed;
|
} __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 hidpp_device {
|
||||||
struct hid_device *hid_dev;
|
struct hid_device *hid_dev;
|
||||||
struct mutex send_mutex;
|
struct mutex send_mutex;
|
||||||
@ -128,8 +139,9 @@ struct hidpp_device {
|
|||||||
struct input_dev *delayed_input;
|
struct input_dev *delayed_input;
|
||||||
|
|
||||||
unsigned long quirks;
|
unsigned long quirks;
|
||||||
};
|
|
||||||
|
|
||||||
|
struct hidpp_battery battery;
|
||||||
|
};
|
||||||
|
|
||||||
/* HID++ 1.0 error codes */
|
/* HID++ 1.0 error codes */
|
||||||
#define HIDPP_ERROR 0x8f
|
#define HIDPP_ERROR 0x8f
|
||||||
@ -606,6 +618,222 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
|
|||||||
return name;
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
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->protocol_major >= 2) {
|
||||||
|
ret = hidpp20_initialize_battery(hidpp);
|
||||||
|
if (ret == 0)
|
||||||
|
hidpp->quirks |= HIDPP_QUIRK_HIDPP20_BATTERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* 0x6010: Touchpad FW items */
|
/* 0x6010: Touchpad FW items */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -2050,6 +2278,12 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
if (hidpp->quirks & HIDPP_QUIRK_HIDPP20_BATTERY) {
|
||||||
|
ret = hidpp20_battery_event(hidpp, data, size);
|
||||||
|
if (ret != 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
|
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
|
||||||
return wtp_raw_event(hdev, data, size);
|
return wtp_raw_event(hdev, data, size);
|
||||||
else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
|
else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
|
||||||
@ -2158,6 +2392,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
|
|||||||
hidpp->protocol_major, hidpp->protocol_minor);
|
hidpp->protocol_major, hidpp->protocol_minor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hidpp_initialize_battery(hidpp);
|
||||||
|
|
||||||
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
|
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
|
||||||
/* if HID created the input nodes for us, we can stop now */
|
/* if HID created the input nodes for us, we can stop now */
|
||||||
return;
|
return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user