HID: hid-asus: Add BT keyboard dock battery monitoring support

Add battery monitoring support for Transbook T100CHI/T90CHI's Bluetooth
keyboard dock.  They report rest battery level and charging status.

Signed-off-by: NOGUCHI Hiroshi <drvlabo@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
NOGUCHI Hiroshi 2019-01-29 13:31:29 +09:00 committed by Jiri Kosina
parent a767ffea05
commit 6311d329e1

View File

@ -32,6 +32,7 @@
#include <linux/platform_data/x86/asus-wmi.h> #include <linux/platform_data/x86/asus-wmi.h>
#include <linux/input/mt.h> #include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */ #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
#include "hid-ids.h" #include "hid-ids.h"
@ -61,6 +62,13 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define CONTACT_TOUCH_MAJOR_MASK 0x07 #define CONTACT_TOUCH_MAJOR_MASK 0x07
#define CONTACT_PRESSURE_MASK 0x7f #define CONTACT_PRESSURE_MASK 0x7f
#define BATTERY_REPORT_ID (0x03)
#define BATTERY_REPORT_SIZE (1 + 8)
#define BATTERY_LEVEL_MAX ((u8)255)
#define BATTERY_STAT_DISCONNECT (0)
#define BATTERY_STAT_CHARGING (1)
#define BATTERY_STAT_FULL (2)
#define QUIRK_FIX_NOTEBOOK_REPORT BIT(0) #define QUIRK_FIX_NOTEBOOK_REPORT BIT(0)
#define QUIRK_NO_INIT_REPORTS BIT(1) #define QUIRK_NO_INIT_REPORTS BIT(1)
#define QUIRK_SKIP_INPUT_MAPPING BIT(2) #define QUIRK_SKIP_INPUT_MAPPING BIT(2)
@ -101,12 +109,21 @@ struct asus_touchpad_info {
struct asus_drvdata { struct asus_drvdata {
unsigned long quirks; unsigned long quirks;
struct hid_device *hdev;
struct input_dev *input; struct input_dev *input;
struct asus_kbd_leds *kbd_backlight; struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp; const struct asus_touchpad_info *tp;
bool enable_backlight; bool enable_backlight;
struct power_supply *battery;
struct power_supply_desc battery_desc;
int battery_capacity;
int battery_stat;
bool battery_in_query;
unsigned long battery_next_query;
}; };
static int asus_report_battery(struct asus_drvdata *, u8 *, int);
static const struct asus_touchpad_info asus_i2c_tp = { static const struct asus_touchpad_info asus_i2c_tp = {
.max_x = 2794, .max_x = 2794,
.max_y = 1758, .max_y = 1758,
@ -260,6 +277,9 @@ static int asus_raw_event(struct hid_device *hdev,
{ {
struct asus_drvdata *drvdata = hid_get_drvdata(hdev); struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
return asus_report_battery(drvdata, data, size);
if (drvdata->tp && data[0] == INPUT_REPORT_ID) if (drvdata->tp && data[0] == INPUT_REPORT_ID)
return asus_report_input(drvdata, data, size); return asus_report_input(drvdata, data, size);
@ -429,6 +449,164 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
return ret; return ret;
} }
/*
* [0] REPORT_ID (same value defined in report descriptor)
* [1] rest battery level. range [0..255]
* [2]..[7] Bluetooth hardware address (MAC address)
* [8] charging status
* = 0 : AC offline / discharging
* = 1 : AC online / charging
* = 2 : AC online / fully charged
*/
static int asus_parse_battery(struct asus_drvdata *drvdata, u8 *data, int size)
{
u8 sts;
u8 lvl;
int val;
lvl = data[1];
sts = data[8];
drvdata->battery_capacity = ((int)lvl * 100) / (int)BATTERY_LEVEL_MAX;
switch (sts) {
case BATTERY_STAT_CHARGING:
val = POWER_SUPPLY_STATUS_CHARGING;
break;
case BATTERY_STAT_FULL:
val = POWER_SUPPLY_STATUS_FULL;
break;
case BATTERY_STAT_DISCONNECT:
default:
val = POWER_SUPPLY_STATUS_DISCHARGING;
break;
}
drvdata->battery_stat = val;
return 0;
}
static int asus_report_battery(struct asus_drvdata *drvdata, u8 *data, int size)
{
/* notify only the autonomous event by device */
if ((drvdata->battery_in_query == false) &&
(size == BATTERY_REPORT_SIZE))
power_supply_changed(drvdata->battery);
return 0;
}
static int asus_battery_query(struct asus_drvdata *drvdata)
{
u8 *buf;
int ret = 0;
buf = kmalloc(BATTERY_REPORT_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
drvdata->battery_in_query = true;
ret = hid_hw_raw_request(drvdata->hdev, BATTERY_REPORT_ID,
buf, BATTERY_REPORT_SIZE,
HID_INPUT_REPORT, HID_REQ_GET_REPORT);
drvdata->battery_in_query = false;
if (ret == BATTERY_REPORT_SIZE)
ret = asus_parse_battery(drvdata, buf, BATTERY_REPORT_SIZE);
else
ret = -ENODATA;
kfree(buf);
return ret;
}
static enum power_supply_property asus_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_MODEL_NAME,
};
#define QUERY_MIN_INTERVAL (60 * HZ) /* 60[sec] */
static int asus_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct asus_drvdata *drvdata = power_supply_get_drvdata(psy);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CAPACITY:
if (time_before(drvdata->battery_next_query, jiffies)) {
drvdata->battery_next_query =
jiffies + QUERY_MIN_INTERVAL;
ret = asus_battery_query(drvdata);
if (ret)
return ret;
}
if (psp == POWER_SUPPLY_PROP_STATUS)
val->intval = drvdata->battery_stat;
else
val->intval = drvdata->battery_capacity;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = drvdata->hdev->name;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int asus_battery_probe(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
struct power_supply_config pscfg = { .drv_data = drvdata };
int ret = 0;
drvdata->battery_capacity = 0;
drvdata->battery_stat = POWER_SUPPLY_STATUS_UNKNOWN;
drvdata->battery_in_query = false;
drvdata->battery_desc.properties = asus_battery_props;
drvdata->battery_desc.num_properties = ARRAY_SIZE(asus_battery_props);
drvdata->battery_desc.get_property = asus_battery_get_property;
drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
drvdata->battery_desc.use_for_apm = 0;
drvdata->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
"asus-keyboard-%s-battery",
strlen(hdev->uniq) ?
hdev->uniq : dev_name(&hdev->dev));
if (!drvdata->battery_desc.name)
return -ENOMEM;
drvdata->battery_next_query = jiffies;
drvdata->battery = devm_power_supply_register(&hdev->dev,
&(drvdata->battery_desc), &pscfg);
if (IS_ERR(drvdata->battery)) {
ret = PTR_ERR(drvdata->battery);
drvdata->battery = NULL;
hid_err(hdev, "Unable to register battery device\n");
return ret;
}
power_supply_powers(drvdata->battery, &hdev->dev);
return ret;
}
static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
{ {
struct input_dev *input = hi->input; struct input_dev *input = hi->input;
@ -661,6 +839,10 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
drvdata->quirks = id->driver_data; drvdata->quirks = id->driver_data;
/*
* T90CHI's keyboard dock returns same ID values as T100CHI's dock.
* Thus, identify T90CHI dock with product name string.
*/
if (strstr(hdev->name, "T90CHI")) { if (strstr(hdev->name, "T90CHI")) {
drvdata->quirks &= ~QUIRK_T100CHI; drvdata->quirks &= ~QUIRK_T100CHI;
drvdata->quirks |= QUIRK_T90CHI; drvdata->quirks |= QUIRK_T90CHI;
@ -700,6 +882,17 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (drvdata->quirks & QUIRK_NO_INIT_REPORTS) if (drvdata->quirks & QUIRK_NO_INIT_REPORTS)
hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS; hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
drvdata->hdev = hdev;
if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
ret = asus_battery_probe(hdev);
if (ret) {
hid_err(hdev,
"Asus hid battery_probe failed: %d\n", ret);
return ret;
}
}
ret = hid_parse(hdev); ret = hid_parse(hdev);
if (ret) { if (ret) {
hid_err(hdev, "Asus hid parse failed: %d\n", ret); hid_err(hdev, "Asus hid parse failed: %d\n", ret);