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_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;
|
||||
|
|
Loading…
Reference in a new issue