Merge branches 'for-4.4/upstream-fixes', 'for-4.5/async-suspend', 'for-4.5/container...
[cascardo/linux.git] / drivers / hid / hid-sony.c
index 774cd22..9b8db0e 100644 (file)
@@ -1028,6 +1028,7 @@ struct sony_sc {
        struct led_classdev *leds[MAX_LEDS];
        unsigned long quirks;
        struct work_struct state_worker;
+       void(*send_output_report)(struct sony_sc*);
        struct power_supply *battery;
        struct power_supply_desc battery_desc;
        int device_id;
@@ -1044,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;
@@ -1137,11 +1139,11 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
         * the gyroscope values to corresponding axes so we need a
         * modified one.
         */
-       if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && *rsize == 467) {
+       if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) {
                hid_info(hdev, "Using modified Dualshock 4 report descriptor with gyroscope axes\n");
                rdesc = dualshock4_usb_rdesc;
                *rsize = sizeof(dualshock4_usb_rdesc);
-       } else if ((sc->quirks & DUALSHOCK4_CONTROLLER_BT) && *rsize == 357) {
+       } else if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) {
                hid_info(hdev, "Using modified Dualshock 4 Bluetooth report descriptor\n");
                rdesc = dualshock4_bt_rdesc;
                *rsize = sizeof(dualshock4_bt_rdesc);
@@ -1549,7 +1551,7 @@ static void sony_led_set_brightness(struct led_classdev *led,
                                    enum led_brightness value)
 {
        struct device *dev = led->dev->parent;
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct hid_device *hdev = to_hid_device(dev);
        struct sony_sc *drv_data;
 
        int n;
@@ -1591,7 +1593,7 @@ static void sony_led_set_brightness(struct led_classdev *led,
 static enum led_brightness sony_led_get_brightness(struct led_classdev *led)
 {
        struct device *dev = led->dev->parent;
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct hid_device *hdev = to_hid_device(dev);
        struct sony_sc *drv_data;
 
        int n;
@@ -1614,7 +1616,7 @@ static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
                                unsigned long *delay_off)
 {
        struct device *dev = led->dev->parent;
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct hid_device *hdev = to_hid_device(dev);
        struct sony_sc *drv_data = hid_get_drvdata(hdev);
        int n;
        __u8 new_on, new_off;
@@ -1789,7 +1791,7 @@ error_leds:
        return ret;
 }
 
-static void sixaxis_state_worker(struct work_struct *work)
+static void sixaxis_send_output_report(struct sony_sc *sc)
 {
        static const union sixaxis_output_report_01 default_report = {
                .buf = {
@@ -1803,7 +1805,6 @@ static void sixaxis_state_worker(struct work_struct *work)
                        0x00, 0x00, 0x00, 0x00, 0x00
                }
        };
-       struct sony_sc *sc = container_of(work, struct sony_sc, state_worker);
        struct sixaxis_output_report *report =
                (struct sixaxis_output_report *)sc->output_report_dmabuf;
        int n;
@@ -1846,9 +1847,8 @@ static void sixaxis_state_worker(struct work_struct *work)
                        HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
 }
 
-static void dualshock4_state_worker(struct work_struct *work)
+static void dualshock4_send_output_report(struct sony_sc *sc)
 {
-       struct sony_sc *sc = container_of(work, struct sony_sc, state_worker);
        struct hid_device *hdev = sc->hdev;
        __u8 *buf = sc->output_report_dmabuf;
        int offset;
@@ -1893,9 +1893,8 @@ static void dualshock4_state_worker(struct work_struct *work)
                                HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
 }
 
-static void motion_state_worker(struct work_struct *work)
+static void motion_send_output_report(struct sony_sc *sc)
 {
-       struct sony_sc *sc = container_of(work, struct sony_sc, state_worker);
        struct hid_device *hdev = sc->hdev;
        struct motion_output_report_02 *report =
                (struct motion_output_report_02 *)sc->output_report_dmabuf;
@@ -1914,6 +1913,18 @@ static void motion_state_worker(struct work_struct *work)
        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);
+       sc->send_output_report(sc);
+}
+
 static int sony_allocate_output_report(struct sony_sc *sc)
 {
        if ((sc->quirks & SIXAXIS_CONTROLLER) ||
@@ -2241,11 +2252,13 @@ static void sony_release_device_id(struct sony_sc *sc)
        }
 }
 
-static inline void sony_init_work(struct sony_sc *sc,
-                                       void (*worker)(struct work_struct *))
+static inline void sony_init_output_report(struct sony_sc *sc,
+                               void(*send_output_report)(struct sony_sc*))
 {
+       sc->send_output_report = send_output_report;
+
        if (!sc->worker_initialized)
-               INIT_WORK(&sc->state_worker, worker);
+               INIT_WORK(&sc->state_worker, sony_state_worker);
 
        sc->worker_initialized = 1;
 }
@@ -2319,7 +2332,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
                hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
                hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
                ret = sixaxis_set_operational_usb(hdev);
-               sony_init_work(sc, sixaxis_state_worker);
+               sony_init_output_report(sc, sixaxis_send_output_report);
        } else if ((sc->quirks & SIXAXIS_CONTROLLER_BT) ||
                        (sc->quirks & NAVIGATION_CONTROLLER_BT)) {
                /*
@@ -2328,7 +2341,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
                 */
                hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
                ret = sixaxis_set_operational_bt(hdev);
-               sony_init_work(sc, sixaxis_state_worker);
+               sony_init_output_report(sc, sixaxis_send_output_report);
        } else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
                if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) {
                        /*
@@ -2343,9 +2356,9 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
                        }
                }
 
-               sony_init_work(sc, dualshock4_state_worker);
+               sony_init_output_report(sc, dualshock4_send_output_report);
        } else if (sc->quirks & MOTION_CONTROLLER) {
-               sony_init_work(sc, motion_state_worker);
+               sony_init_output_report(sc, motion_send_output_report);
        } else {
                ret = 0;
        }
@@ -2421,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 },
@@ -2470,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)