HID: sony: Save and restore the controller state on suspend and resume
authorFrank Praznik <frank.praznik@gmail.com>
Wed, 11 Nov 2015 14:49:38 +0000 (09:49 -0500)
committerJiri Kosina <jkosina@suse.cz>
Thu, 19 Nov 2015 10:16:13 +0000 (11:16 +0100)
On hardware which provides standby power for charging devices the state of the
LEDs and force-feedback on controllers can persist even when the system is in
standby.  Additionally, the state of the controllers on resume may be different
from the state they were in at the time when they were suspended (ie. LEDs are
cleared on resume).

This implements the suspend and resume callbacks which saves and clears the
state of the LEDs on suspend and restores them on resume.  Force-feedback is
stopped on suspend but not automatically restored on resume until a new event is
received to avoid potentially damaging hardware.

USB Sixaxis and navigation controllers must be reinitialized when the hardware
is reset on resume or they won't send any input reports.

Signed-off-by: Frank Praznik <frank.praznik@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/hid-sony.c

index 4ba904b02bc26b3cca424df91aaaa88bc74dfa76..1041c44765e1719882bb043235e4f7490e081a5f 100644 (file)
@@ -1045,6 +1045,7 @@ struct sony_sc {
        __u8 battery_charging;
        __u8 battery_capacity;
        __u8 led_state[MAX_LEDS];
+       __u8 resume_led_state[MAX_LEDS];
        __u8 led_delay_on[MAX_LEDS];
        __u8 led_delay_off[MAX_LEDS];
        __u8 led_count;
@@ -1912,6 +1913,12 @@ static void motion_send_output_report(struct sony_sc *sc)
        hid_hw_output_report(hdev, (__u8 *)report, MOTION_REPORT_0x02_SIZE);
 }
 
+static inline void sony_send_output_report(struct sony_sc *sc)
+{
+       if (sc->send_output_report)
+               sc->send_output_report(sc);
+}
+
 static void sony_state_worker(struct work_struct *work)
 {
        struct sony_sc *sc = container_of(work, struct sony_sc, state_worker);
@@ -2427,6 +2434,56 @@ static void sony_remove(struct hid_device *hdev)
        hid_hw_stop(hdev);
 }
 
+#ifdef CONFIG_PM
+
+static int sony_suspend(struct hid_device *hdev, pm_message_t message)
+{
+       /*
+        * On suspend save the current LED state,
+        * stop running force-feedback and blank the LEDS.
+         */
+       if (SONY_LED_SUPPORT || SONY_FF_SUPPORT) {
+               struct sony_sc *sc = hid_get_drvdata(hdev);
+
+#ifdef CONFIG_SONY_FF
+               sc->left = sc->right = 0;
+#endif
+
+               memcpy(sc->resume_led_state, sc->led_state,
+                       sizeof(sc->resume_led_state));
+               memset(sc->led_state, 0, sizeof(sc->led_state));
+
+               sony_send_output_report(sc);
+       }
+
+       return 0;
+}
+
+static int sony_resume(struct hid_device *hdev)
+{
+       /* Restore the state of controller LEDs on resume */
+       if (SONY_LED_SUPPORT) {
+               struct sony_sc *sc = hid_get_drvdata(hdev);
+
+               memcpy(sc->led_state, sc->resume_led_state,
+                       sizeof(sc->led_state));
+
+               /*
+                * The Sixaxis and navigation controllers on USB need to be
+                * reinitialized on resume or they won't behave properly.
+                */
+               if ((sc->quirks & SIXAXIS_CONTROLLER_USB) ||
+                       (sc->quirks & NAVIGATION_CONTROLLER_USB))
+                       sixaxis_set_operational_usb(sc->hdev);
+
+               sony_set_leds(sc);
+       }
+
+       return 0;
+}
+
+#endif
+
 static const struct hid_device_id sony_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER),
                .driver_data = SIXAXIS_CONTROLLER_USB },
@@ -2476,7 +2533,13 @@ static struct hid_driver sony_driver = {
        .probe            = sony_probe,
        .remove           = sony_remove,
        .report_fixup     = sony_report_fixup,
-       .raw_event        = sony_raw_event
+       .raw_event        = sony_raw_event,
+
+#ifdef CONFIG_PM
+       .suspend          = sony_suspend,
+       .resume           = sony_resume,
+       .reset_resume     = sony_resume,
+#endif
 };
 
 static int __init sony_init(void)