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

Commit 72dbb852 authored by Roderick Colenbrander's avatar Roderick Colenbrander Committed by Kim Low
Browse files

UPSTREAM: HID: sony: Support motion sensor calibration on dongle



The DualShock 4 dongle isn't connected to a real DualShock 4 at
time of driver loading. When a DualShock 4 is plugged in, we
need to obtain calibration data (the dongle would have zeros).

This patch adds calibration logic, which we schedule on a hotplug
from sony_raw_event. In addition this patch adds dongle state
handling.

Signed-off-by: default avatarRoderick Colenbrander <roderick.colenbrander@sony.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
(cherry picked from commit f2f47c385d2babf44cbd2ec2be4f2b29b88041bc)

Bug: 111431828
Signed-off-by: default avatarKim Low <kim-huei.low@sony.com>
Change-Id: Ic720f5855591c34514cf33a545a7575866ebae17
parent 3a60a133
Loading
Loading
Loading
Loading
+89 −7
Original line number Diff line number Diff line
@@ -624,8 +624,16 @@ struct ds4_calibration_data {
	int sens_denom;
};

enum ds4_dongle_state {
	DONGLE_DISCONNECTED,
	DONGLE_CALIBRATING,
	DONGLE_CONNECTED,
	DONGLE_DISABLED
};

enum sony_worker {
	SONY_WORKER_STATE
	SONY_WORKER_STATE,
	SONY_WORKER_HOTPLUG
};

struct sony_sc {
@@ -636,6 +644,7 @@ struct sony_sc {
	struct input_dev *sensor_dev;
	struct led_classdev *leds[MAX_LEDS];
	unsigned long quirks;
	struct work_struct hotplug_worker;
	struct work_struct state_worker;
	void (*send_output_report)(struct sony_sc *);
	struct power_supply *battery;
@@ -649,6 +658,7 @@ struct sony_sc {
#endif

	u8 mac_address[6];
	u8 hotplug_worker_initialized;
	u8 state_worker_initialized;
	u8 defer_initialization;
	u8 cable_state;
@@ -663,7 +673,7 @@ struct sony_sc {
	u16 prev_timestamp;
	unsigned int timestamp_us;

	bool ds4_dongle_connected;
	enum ds4_dongle_state ds4_dongle_state;
	/* DS4 calibration data */
	struct ds4_calibration_data ds4_calib_data[6];
};
@@ -677,6 +687,11 @@ static inline void sony_schedule_work(struct sony_sc *sc,
	case SONY_WORKER_STATE:
		if (!sc->defer_initialization)
			schedule_work(&sc->state_worker);
		break;
	case SONY_WORKER_HOTPLUG:
		if (sc->hotplug_worker_initialized)
			schedule_work(&sc->hotplug_worker);
		break;
	}
}

@@ -1085,6 +1100,9 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
		dualshock4_parse_report(sc, rd, size);
	} else if ((sc->quirks & DUALSHOCK4_DONGLE) && rd[0] == 0x01 &&
			size == 64) {
		unsigned long flags;
		enum ds4_dongle_state dongle_state;

		/*
		 * In the case of a DS4 USB dongle, bit[2] of byte 31 indicates
		 * if a DS4 is actually connected (indicated by '0').
@@ -1092,16 +1110,45 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
		 */
		bool connected = (rd[31] & 0x04) ? false : true;

		if (!sc->ds4_dongle_connected && connected) {
		spin_lock_irqsave(&sc->lock, flags);
		dongle_state = sc->ds4_dongle_state;
		spin_unlock_irqrestore(&sc->lock, flags);

		/*
		 * The dongle always sends input reports even when no
		 * DS4 is attached. When a DS4 is connected, we need to
		 * obtain calibration data before we can use it.
		 * The code below tracks dongle state and kicks of
		 * calibration when needed and only allows us to process
		 * input if a DS4 is actually connected.
		 */
		if (dongle_state == DONGLE_DISCONNECTED && connected) {
			hid_info(sc->hdev, "DualShock 4 USB dongle: controller connected\n");
			sony_set_leds(sc);
			sc->ds4_dongle_connected = true;
		} else if (sc->ds4_dongle_connected && !connected) {

			spin_lock_irqsave(&sc->lock, flags);
			sc->ds4_dongle_state = DONGLE_CALIBRATING;
			spin_unlock_irqrestore(&sc->lock, flags);

			sony_schedule_work(sc, SONY_WORKER_HOTPLUG);

			/* Don't process the report since we don't have
			 * calibration data, but let hidraw have it anyway.
			 */
			return 0;
		} else if ((dongle_state == DONGLE_CONNECTED ||
			    dongle_state == DONGLE_DISABLED) && !connected) {
			hid_info(sc->hdev, "DualShock 4 USB dongle: controller disconnected\n");
			sc->ds4_dongle_connected = false;

			spin_lock_irqsave(&sc->lock, flags);
			sc->ds4_dongle_state = DONGLE_DISCONNECTED;
			spin_unlock_irqrestore(&sc->lock, flags);

			/* Return 0, so hidraw can get the report. */
			return 0;
		} else if (!sc->ds4_dongle_connected) {
		} else if (dongle_state == DONGLE_CALIBRATING ||
			   dongle_state == DONGLE_DISABLED ||
			   dongle_state == DONGLE_DISCONNECTED) {
			/* Return 0, so hidraw can get the report. */
			return 0;
		}
@@ -1518,6 +1565,33 @@ static int dualshock4_get_calibration_data(struct sony_sc *sc)
	return ret;
}

static void dualshock4_calibration_work(struct work_struct *work)
{
	struct sony_sc *sc = container_of(work, struct sony_sc, hotplug_worker);
	unsigned long flags;
	enum ds4_dongle_state dongle_state;
	int ret;

	ret = dualshock4_get_calibration_data(sc);
	if (ret < 0) {
		/* This call is very unlikely to fail for the dongle. When it
		 * fails we are probably in a very bad state, so mark the
		 * dongle as disabled. We will re-enable the dongle if a new
		 * DS4 hotplug is detect from sony_raw_event as any issues
		 * are likely resolved then (the dongle is quite stupid).
		 */
		hid_err(sc->hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
		dongle_state = DONGLE_DISABLED;
	} else {
		hid_info(sc->hdev, "DualShock 4 USB dongle: calibration completed\n");
		dongle_state = DONGLE_CONNECTED;
	}

	spin_lock_irqsave(&sc->lock, flags);
	sc->ds4_dongle_state = dongle_state;
	spin_unlock_irqrestore(&sc->lock, flags);
}

static void sixaxis_set_leds_from_id(struct sony_sc *sc)
{
	static const u8 sixaxis_leds[10][4] = {
@@ -2362,6 +2436,8 @@ static inline void sony_init_output_report(struct sony_sc *sc,

static inline void sony_cancel_work_sync(struct sony_sc *sc)
{
	if (sc->hotplug_worker_initialized)
		cancel_work_sync(&sc->hotplug_worker);
	if (sc->state_worker_initialized)
		cancel_work_sync(&sc->state_worker);
}
@@ -2443,6 +2519,12 @@ static int sony_input_configured(struct hid_device *hdev,
			goto err_stop;
		}

		if (sc->quirks & DUALSHOCK4_DONGLE) {
			INIT_WORK(&sc->hotplug_worker, dualshock4_calibration_work);
			sc->hotplug_worker_initialized = 1;
			sc->ds4_dongle_state = DONGLE_DISCONNECTED;
		}

		sony_init_output_report(sc, dualshock4_send_output_report);
	} else if (sc->quirks & MOTION_CONTROLLER) {
		sony_init_output_report(sc, motion_send_output_report);