mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
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:
parent
a767ffea05
commit
6311d329e1
@ -32,6 +32,7 @@
|
||||
#include <linux/platform_data/x86/asus-wmi.h>
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
@ -61,6 +62,13 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
||||
#define CONTACT_TOUCH_MAJOR_MASK 0x07
|
||||
#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_NO_INIT_REPORTS BIT(1)
|
||||
#define QUIRK_SKIP_INPUT_MAPPING BIT(2)
|
||||
@ -101,12 +109,21 @@ struct asus_touchpad_info {
|
||||
|
||||
struct asus_drvdata {
|
||||
unsigned long quirks;
|
||||
struct hid_device *hdev;
|
||||
struct input_dev *input;
|
||||
struct asus_kbd_leds *kbd_backlight;
|
||||
const struct asus_touchpad_info *tp;
|
||||
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 = {
|
||||
.max_x = 2794,
|
||||
.max_y = 1758,
|
||||
@ -260,6 +277,9 @@ static int asus_raw_event(struct hid_device *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)
|
||||
return asus_report_input(drvdata, data, size);
|
||||
|
||||
@ -429,6 +449,164 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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")) {
|
||||
drvdata->quirks &= ~QUIRK_T100CHI;
|
||||
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)
|
||||
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);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Asus hid parse failed: %d\n", ret);
|
||||
|
Loading…
Reference in New Issue
Block a user