Skip to content

Commit

Permalink
HID: sony: Save and restore the controller state on suspend and resume
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Frank Praznik authored and Jiri Kosina committed Nov 19, 2015
1 parent d8aaccd commit decd946
Showing 1 changed file with 64 additions and 1 deletion.
65 changes: 64 additions & 1 deletion drivers/hid/hid-sony.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit decd946

Please sign in to comment.