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:
Peter Hutterer 2016-06-29 19:28:01 +10:00 committed by Jiri Kosina
parent 595d9e34ee
commit 5a2b190cdd

View File

@ -62,6 +62,8 @@ MODULE_PARM_DESC(disable_tap_to_click,
#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_HIDPP20_BATTERY BIT(25)
#define HIDPP_QUIRK_HIDPP10_BATTERY BIT(26)
#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \
HIDPP_QUIRK_CONNECT_EVENTS)
@ -110,6 +112,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 +139,9 @@ struct hidpp_device {
struct input_dev *delayed_input;
unsigned long quirks;
};
struct hidpp_battery battery;
};
/* HID++ 1.0 error codes */
#define HIDPP_ERROR 0x8f
@ -606,6 +618,222 @@ 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,
};
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 */
/* -------------------------------------------------------------------------- */
@ -2050,6 +2278,12 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
if (ret != 0)
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)
return wtp_raw_event(hdev, data, size);
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_initialize_battery(hidpp);
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
/* if HID created the input nodes for us, we can stop now */
return;