Skip to content

Commit

Permalink
HID: input: map digitizer battery usage
Browse files Browse the repository at this point in the history
We already mapped battery strength reports from the generic device
control page, but we did not update capacity from input reports, nor we
mapped the battery strength report from the digitizer page, so let's
implement this now.

Batteries driven by the input reports will now start in "unknown" state,
and will get updated once we receive first report containing battery
strength from the device.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information
Dmitry Torokhov authored and Jiri Kosina committed Aug 2, 2017
1 parent a9d0683 commit 581c448
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 59 deletions.
184 changes: 125 additions & 59 deletions drivers/hid/hid-input.c
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,45 @@ static unsigned find_battery_quirk(struct hid_device *hdev)
return quirks;
}

static int hidinput_scale_battery_capacity(struct hid_device *dev,
int value)
{
if (dev->battery_min < dev->battery_max &&
value >= dev->battery_min && value <= dev->battery_max)
value = ((value - dev->battery_min) * 100) /
(dev->battery_max - dev->battery_min);

return value;
}

static int hidinput_query_battery_capacity(struct hid_device *dev)
{
u8 *buf;
int ret;

buf = kmalloc(2, GFP_KERNEL);
if (!buf)
return -ENOMEM;

ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 2,
dev->battery_report_type, HID_REQ_GET_REPORT);
if (ret != 2) {
kfree(buf);
return -ENODATA;
}

ret = hidinput_scale_battery_capacity(dev, buf[1]);
kfree(buf);
return ret;
}

static int hidinput_get_battery_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct hid_device *dev = power_supply_get_drvdata(psy);
int value;
int ret = 0;
__u8 *buf;

switch (prop) {
case POWER_SUPPLY_PROP_PRESENT:
Expand All @@ -355,37 +387,38 @@ static int hidinput_get_battery_property(struct power_supply *psy,
break;

case POWER_SUPPLY_PROP_CAPACITY:

buf = kmalloc(2 * sizeof(__u8), GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
break;
}
ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 2,
dev->battery_report_type,
HID_REQ_GET_REPORT);

if (ret != 2) {
ret = -ENODATA;
kfree(buf);
break;
if (dev->battery_report_type == HID_FEATURE_REPORT) {
value = hidinput_query_battery_capacity(dev);
if (value < 0)
return value;
} else {
value = dev->battery_capacity;
}
ret = 0;

if (dev->battery_min < dev->battery_max &&
buf[1] >= dev->battery_min &&
buf[1] <= dev->battery_max)
val->intval = (100 * (buf[1] - dev->battery_min)) /
(dev->battery_max - dev->battery_min);
kfree(buf);
val->intval = value;
break;

case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = dev->name;
break;

case POWER_SUPPLY_PROP_STATUS:
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
if (!dev->battery_reported &&
dev->battery_report_type == HID_FEATURE_REPORT) {
value = hidinput_query_battery_capacity(dev);
if (value < 0)
return value;

dev->battery_capacity = value;
dev->battery_reported = true;
}

if (!dev->battery_reported)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else if (dev->battery_capacity == 100)
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;

case POWER_SUPPLY_PROP_SCOPE:
Expand All @@ -400,35 +433,33 @@ static int hidinput_get_battery_property(struct power_supply *psy,
return ret;
}

static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field)
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field)
{
struct power_supply_desc *psy_desc = NULL;
struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = { .drv_data = dev, };
unsigned quirks;
s32 min, max;
int error;

if (field->usage->hid != HID_DC_BATTERYSTRENGTH)
return false; /* no match */

if (dev->battery != NULL)
goto out; /* already initialized? */
if (dev->battery)
return 0; /* already initialized? */

quirks = find_battery_quirk(dev);

hid_dbg(dev, "device %x:%x:%x %d quirks %d\n",
dev->bus, dev->vendor, dev->product, dev->version, quirks);

if (quirks & HID_BATTERY_QUIRK_IGNORE)
goto out;
return 0;

psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL);
if (psy_desc == NULL)
goto out;
if (!psy_desc)
return -ENOMEM;

psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery", dev->uniq);
if (psy_desc->name == NULL) {
kfree(psy_desc);
goto out;
if (!psy_desc->name) {
error = -ENOMEM;
goto err_free_mem;
}

psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
Expand All @@ -455,17 +486,20 @@ static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type,

dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
if (IS_ERR(dev->battery)) {
hid_warn(dev, "can't register power supply: %ld\n",
PTR_ERR(dev->battery));
kfree(psy_desc->name);
kfree(psy_desc);
dev->battery = NULL;
} else {
power_supply_powers(dev->battery, &dev->dev);
error = PTR_ERR(dev->battery);
hid_warn(dev, "can't register power supply: %d\n", error);
goto err_free_name;
}

out:
return true;
power_supply_powers(dev->battery, &dev->dev);
return 0;

err_free_name:
kfree(psy_desc->name);
err_free_mem:
kfree(psy_desc);
dev->battery = NULL;
return error;
}

static void hidinput_cleanup_battery(struct hid_device *dev)
Expand All @@ -481,16 +515,33 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
kfree(psy_desc);
dev->battery = NULL;
}

static void hidinput_update_battery(struct hid_device *dev, int value)
{
if (!dev->battery)
return;

if (value == 0 || value < dev->battery_min || value > dev->battery_max)
return;

dev->battery_capacity = hidinput_scale_battery_capacity(dev, value);
dev->battery_reported = true;
power_supply_changed(dev->battery);
}
#else /* !CONFIG_HID_BATTERY_STRENGTH */
static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field)
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field)
{
return false;
return 0;
}

static void hidinput_cleanup_battery(struct hid_device *dev)
{
}

static void hidinput_update_battery(struct hid_device *dev, int value)
{
}
#endif /* CONFIG_HID_BATTERY_STRENGTH */

static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
Expand Down Expand Up @@ -710,6 +761,11 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
}
break;

case 0x3b: /* Battery Strength */
hidinput_setup_battery(device, HID_INPUT_REPORT, field);
usage->type = EV_PWR;
goto ignore;

case 0x3c: /* Invert */
map_key_clear(BTN_TOOL_RUBBER);
break;
Expand Down Expand Up @@ -944,11 +1000,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
break;

case HID_UP_GENDEVCTRLS:
if (hidinput_setup_battery(device, HID_INPUT_REPORT, field))
switch (usage->hid) {
case HID_DC_BATTERYSTRENGTH:
hidinput_setup_battery(device, HID_INPUT_REPORT, field);
usage->type = EV_PWR;
goto ignore;
else
goto unknown;
break;
}
goto unknown;

case HID_UP_HPVENDOR: /* Reported on a Dutch layout HP5308 */
set_bit(EV_REP, input->evbit);
Expand Down Expand Up @@ -1031,7 +1089,6 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
if (usage->code > max)
goto ignore;


if (usage->type == EV_ABS) {

int a = field->logical_minimum;
Expand Down Expand Up @@ -1090,14 +1147,19 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
struct input_dev *input;
unsigned *quirks = &hid->quirks;

if (!field->hidinput)
if (!usage->type)
return;

input = field->hidinput->input;
if (usage->type == EV_PWR) {
hidinput_update_battery(hid, value);
return;
}

if (!usage->type)
if (!field->hidinput)
return;

input = field->hidinput->input;

if (usage->hat_min < usage->hat_max || usage->hat_dir) {
int hat_dir = usage->hat_dir;
if (!hat_dir)
Expand Down Expand Up @@ -1373,6 +1435,7 @@ static void report_features(struct hid_device *hid)
struct hid_driver *drv = hid->driver;
struct hid_report_enum *rep_enum;
struct hid_report *rep;
struct hid_usage *usage;
int i, j;

rep_enum = &hid->report_enum[HID_FEATURE_REPORT];
Expand All @@ -1383,12 +1446,15 @@ static void report_features(struct hid_device *hid)
continue;

for (j = 0; j < rep->field[i]->maxusage; j++) {
usage = &rep->field[i]->usage[j];

/* Verify if Battery Strength feature is available */
hidinput_setup_battery(hid, HID_FEATURE_REPORT, rep->field[i]);
if (usage->hid == HID_DC_BATTERYSTRENGTH)
hidinput_setup_battery(hid, HID_FEATURE_REPORT,
rep->field[i]);

if (drv->feature_mapping)
drv->feature_mapping(hid, rep->field[i],
rep->field[i]->usage + j);
drv->feature_mapping(hid, rep->field[i], usage);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions include/linux/hid.h
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,12 @@ struct hid_device { /* device report descriptor */
* battery is non-NULL.
*/
struct power_supply *battery;
__s32 battery_capacity;
__s32 battery_min;
__s32 battery_max;
__s32 battery_report_type;
__s32 battery_report_id;
bool battery_reported;
#endif

unsigned int status; /* see STAT flags above */
Expand Down

0 comments on commit 581c448

Please sign in to comment.