Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d902f472 authored by Frank Praznik's avatar Frank Praznik Committed by Jiri Kosina
Browse files

HID: sony: add battery status reporting for the Sixaxis and Dualshock 4



Add battery status reporting for the Sixaxis and Dualshock 4 controllers.

Signed-off-by: default avatarFrank Praznik <frank.praznik@oh.rr.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 4988abf1
Loading
Loading
Loading
Loading
+185 −1
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/leds.h>
#include <linux/power_supply.h>
#include <linux/spinlock.h>

#include "hid-ids.h"

@@ -42,6 +44,7 @@
#define DUALSHOCK4_CONTROLLER_BT  BIT(6)

#define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER_USB | BUZZ_CONTROLLER | DUALSHOCK4_CONTROLLER_USB)
#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT | DUALSHOCK4_CONTROLLER_USB)

#define MAX_LEDS 4

@@ -487,18 +490,30 @@ static const unsigned int buzz_keymap[] = {
	[20] = BTN_TRIGGER_HAPPY20,
};

static enum power_supply_property sony_battery_props[] = {
	POWER_SUPPLY_PROP_PRESENT,
	POWER_SUPPLY_PROP_CAPACITY,
	POWER_SUPPLY_PROP_SCOPE,
	POWER_SUPPLY_PROP_STATUS,
};

struct sony_sc {
	spinlock_t lock;
	struct hid_device *hdev;
	struct led_classdev *leds[MAX_LEDS];
	struct hid_report *output_report;
	unsigned long quirks;
	struct work_struct state_worker;
	struct power_supply battery;

#ifdef CONFIG_SONY_FF
	__u8 left;
	__u8 right;
#endif

	__u8 cable_state;
	__u8 battery_charging;
	__u8 battery_capacity;
	__u8 led_state[MAX_LEDS];
	__u8 led_count;
};
@@ -599,6 +614,63 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
	return rdesc;
}

static void sixaxis_parse_report(struct sony_sc *sc, __u8 *rd, int size)
{
	static const __u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 };
	unsigned long flags;
	__u8 cable_state, battery_capacity, battery_charging;

	/* The sixaxis is charging if the battery value is 0xee
	 * and it is fully charged if the value is 0xef.
	 * It does not report the actual level while charging so it
	 * is set to 100% while charging is in progress.
	 */
	if (rd[30] >= 0xee) {
		battery_capacity = 100;
		battery_charging = rd[30] & 0x01;
	} else {
		battery_capacity = sixaxis_battery_capacity[rd[30]];
		battery_charging = 0;
	}
	cable_state = (rd[31] >> 4) & 0x01;

	spin_lock_irqsave(&sc->lock, flags);
	sc->cable_state = cable_state;
	sc->battery_capacity = battery_capacity;
	sc->battery_charging = battery_charging;
	spin_unlock_irqrestore(&sc->lock, flags);
}

static void dualshock4_parse_report(struct sony_sc *sc, __u8 *rd, int size)
{
	unsigned long flags;
	__u8 cable_state, battery_capacity, battery_charging;

	/* The lower 4 bits of byte 30 contain the battery level
	 * and the 5th bit contains the USB cable state.
	 */
	cable_state = (rd[30] >> 4) & 0x01;
	battery_capacity = rd[30] & 0x0F;

	/* On USB the Dualshock 4 battery level goes from 0 to 11.
	 * A battery level of 11 means fully charged.
	 */
	if (cable_state && battery_capacity == 11)
		battery_charging = 0;
	else
		battery_charging = 1;

	if (battery_capacity > 10)
		battery_capacity--;
	battery_capacity *= 10;

	spin_lock_irqsave(&sc->lock, flags);
	sc->cable_state = cable_state;
	sc->battery_capacity = battery_capacity;
	sc->battery_charging = battery_charging;
	spin_unlock_irqrestore(&sc->lock, flags);
}

static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
		__u8 *rd, int size)
{
@@ -613,6 +685,11 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
		swap(rd[43], rd[44]);
		swap(rd[45], rd[46]);
		swap(rd[47], rd[48]);

		sixaxis_parse_report(sc, rd, size);
	} else if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 &&
			size == 64) {
		dualshock4_parse_report(sc, rd, size);
	}

	return 0;
@@ -1011,6 +1088,91 @@ static void sony_destroy_ff(struct hid_device *hdev)
}
#endif

static int sony_battery_get_property(struct power_supply *psy,
				     enum power_supply_property psp,
				     union power_supply_propval *val)
{
	struct sony_sc *sc = container_of(psy, struct sony_sc, battery);
	unsigned long flags;
	int ret = 0;
	u8 battery_charging, battery_capacity, cable_state;

	spin_lock_irqsave(&sc->lock, flags);
	battery_charging = sc->battery_charging;
	battery_capacity = sc->battery_capacity;
	cable_state = sc->cable_state;
	spin_unlock_irqrestore(&sc->lock, flags);

	switch (psp) {
	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_CAPACITY:
		val->intval = battery_capacity;
		break;
	case POWER_SUPPLY_PROP_STATUS:
		if (battery_charging)
			val->intval = POWER_SUPPLY_STATUS_CHARGING;
		else
			if (battery_capacity == 100 && cable_state)
				val->intval = POWER_SUPPLY_STATUS_FULL;
			else
				val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

static int sony_battery_probe(struct sony_sc *sc)
{
	static atomic_t power_id_seq = ATOMIC_INIT(0);
	unsigned long power_id;
	struct hid_device *hdev = sc->hdev;
	int ret;

	power_id = (unsigned long)atomic_inc_return(&power_id_seq);

	sc->battery.properties = sony_battery_props;
	sc->battery.num_properties = ARRAY_SIZE(sony_battery_props);
	sc->battery.get_property = sony_battery_get_property;
	sc->battery.type = POWER_SUPPLY_TYPE_BATTERY;
	sc->battery.use_for_apm = 0;
	sc->battery.name = kasprintf(GFP_KERNEL, "sony_controller_battery_%lu",
				     power_id);
	if (!sc->battery.name)
		return -ENOMEM;

	ret = power_supply_register(&hdev->dev, &sc->battery);
	if (ret) {
		hid_err(hdev, "Unable to register battery device\n");
		goto err_free;
	}

	power_supply_powers(&sc->battery, &hdev->dev);
	return 0;

err_free:
	kfree(sc->battery.name);
	sc->battery.name = NULL;
	return ret;
}

static void sony_battery_remove(struct sony_sc *sc)
{
	if (!sc->battery.name)
		return;

	power_supply_unregister(&sc->battery);
	kfree(sc->battery.name);
	sc->battery.name = NULL;
}

static int sony_set_output_report(struct sony_sc *sc, int req_id, int req_size)
{
	struct list_head *head, *list;
@@ -1101,14 +1263,31 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
			goto err_stop;
	}

	ret = sony_init_ff(hdev);
	if (sc->quirks & SONY_BATTERY_SUPPORT) {
		ret = sony_battery_probe(sc);
		if (ret < 0)
			goto err_stop;

		/* Open the device to receive reports with battery info */
		ret = hid_hw_open(hdev);
		if (ret < 0) {
			hid_err(hdev, "hw open failed\n");
			goto err_stop;
		}
	}

	ret = sony_init_ff(hdev);
	if (ret < 0)
		goto err_close;

	return 0;
err_close:
	hid_hw_close(hdev);
err_stop:
	if (sc->quirks & SONY_LED_SUPPORT)
		sony_leds_remove(hdev);
	if (sc->quirks & SONY_BATTERY_SUPPORT)
		sony_battery_remove(sc);
	hid_hw_stop(hdev);
	return ret;
}
@@ -1120,6 +1299,11 @@ static void sony_remove(struct hid_device *hdev)
	if (sc->quirks & SONY_LED_SUPPORT)
		sony_leds_remove(hdev);

	if (sc->quirks & SONY_BATTERY_SUPPORT) {
		hid_hw_close(hdev);
		sony_battery_remove(sc);
	}

	sony_destroy_ff(hdev);

	hid_hw_stop(hdev);