diff --git a/drivers/hid/hid-nintendo-wiiu.c b/drivers/hid/hid-nintendo-wiiu.c
index 813abb104275..b18fa403eb42 100644
--- a/drivers/hid/hid-nintendo-wiiu.c
+++ b/drivers/hid/hid-nintendo-wiiu.c
@@ -17,6 +17,11 @@
#include <linux/input.h>
#include <linux/minmax.h>
#include <linux/module.h>
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+#include <linux/fixp-arith.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#endif
#include "hid-ids.h"
#include "hid-nintendo.h"
@@ -72,6 +77,13 @@
#define MAGNET_MIN -(1 << 15)
#define MAGNET_MAX ((1 << 15) - 1)
+/* ADC constants for the battery */
+#define BATTERY_CHARGING_BIT BIT(6)
+#define BATTERY_MIN 142
+#define BATTERY_MAX 178
+#define VOLTAGE_MIN 3270000
+#define VOLTAGE_MAX 4100000
+
/*
* The device is setup with multiple input devices:
* - A joypad with the buttons and sticks.
@@ -85,6 +97,14 @@ struct drc {
struct input_dev *joy_input_dev;
struct input_dev *touch_input_dev;
struct input_dev *accel_input_dev;
+
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+ spinlock_t battery_lock;
+ u8 battery_energy;
+ int battery_status;
+#endif
};
/*@@ -101,6 +121,9 @@ int wiiu_hid_event(struct hid_device *hdev, struct hid_report *report,
struct drc *drc = hid_get_drvdata(hdev);
int i, x, y, z, pressure, base;
u32 buttons;
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+ unsigned long flags;
+#endif
if (len != 128)
return -EINVAL;
@@ -219,6 +242,19 @@ int wiiu_hid_event(struct hid_device *hdev, struct hid_report *report,
input_report_abs(drc->accel_input_dev, ABS_WHEEL, (int16_t)z);
input_sync(drc->accel_input_dev);
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+ /* battery */
+ spin_lock_irqsave(&drc->battery_lock, flags);
+ drc->battery_energy = data[5];
+ if (drc->battery_energy == BATTERY_MAX)
+ drc->battery_status = POWER_SUPPLY_STATUS_FULL;
+ else if (data[4] & BATTERY_CHARGING_BIT)
+ drc->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ drc->battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ spin_unlock_irqrestore(&drc->battery_lock, flags);
+#endif
+
/* let hidraw and hiddev handle the report */
return 0;
}
@@ -368,6 +404,98 @@ static bool drc_setup_accel(struct drc *drc,
return true;
}
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+static enum power_supply_property drc_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+
+static int drc_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct drc *drc = power_supply_get_drvdata(psy);
+ unsigned long flags;
+ int ret = 0;
+ u8 battery_energy;
+ int battery_status;
+
+ spin_lock_irqsave(&drc->battery_lock, flags);
+ battery_energy = drc->battery_energy;
+ battery_status = drc->battery_status;
+ spin_unlock_irqrestore(&drc->battery_lock, flags);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = battery_status;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = VOLTAGE_MAX;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = VOLTAGE_MIN;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = fixp_linear_interpolate(BATTERY_MIN, VOLTAGE_MIN,
+ BATTERY_MAX, VOLTAGE_MAX,
+ battery_energy);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = fixp_linear_interpolate(BATTERY_MIN, 0,
+ BATTERY_MAX, 100,
+ battery_energy);
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int drc_setup_battery(struct drc *drc,
+ struct hid_device *hdev)
+{
+ struct power_supply_config psy_cfg = { .drv_data = drc, };
+ static atomic_t drc_num = ATOMIC_INIT(0);
+ int ret;
+
+ spin_lock_init(&drc->battery_lock);
+
+ drc->battery_desc.properties = drc_battery_props;
+ drc->battery_desc.num_properties = ARRAY_SIZE(drc_battery_props);
+ drc->battery_desc.get_property = drc_battery_get_property;
+ drc->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drc->battery_desc.use_for_apm = 0;
+
+ drc->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "wiiu-drc-%i-battery", atomic_fetch_inc(&drc_num));
+ if (!drc->battery_desc.name)
+ return -ENOMEM;
+
+ drc->battery = devm_power_supply_register(&hdev->dev, &drc->battery_desc, &psy_cfg);
+ if (IS_ERR(drc->battery)) {
+ ret = PTR_ERR(drc->battery);
+ hid_err(hdev, "Unable to register battery device\n");
+ return ret;
+ }
+
+ power_supply_powers(drc->battery, &hdev->dev);
+
+ return 0;
+}
+#endif
+
int wiiu_hid_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{@@ -396,6 +524,14 @@ int wiiu_hid_probe(struct hid_device *hdev,
return -ENOMEM;
}
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+ ret = drc_setup_battery(drc, hdev);
+ if (ret) {
+ hid_err(hdev, "could not allocate battery interface\n");
+ return ret;
+ }
+#endif
+
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER);
if (ret) {
hid_err(hdev, "hw start failed\n");--
2.33.1