diff options
author | Frank Praznik <frank.praznik@oh.rr.com> | 2014-01-27 10:17:36 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2014-01-28 14:37:45 -0500 |
commit | d902f4724ccd0c6b8f2b0a7d22af428c637b6dda (patch) | |
tree | 6c10a4adbefaf51b07265713de5b772bf32c01fa /drivers/hid | |
parent | 4988abf1749241bc80600a6b3283d03898d2717c (diff) |
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: Frank Praznik <frank.praznik@oh.rr.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r-- | drivers/hid/hid-sony.c | 186 |
1 files changed, 185 insertions, 1 deletions
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 12354055d474..04fd611d3099 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c | |||
@@ -30,6 +30,8 @@ | |||
30 | #include <linux/slab.h> | 30 | #include <linux/slab.h> |
31 | #include <linux/usb.h> | 31 | #include <linux/usb.h> |
32 | #include <linux/leds.h> | 32 | #include <linux/leds.h> |
33 | #include <linux/power_supply.h> | ||
34 | #include <linux/spinlock.h> | ||
33 | 35 | ||
34 | #include "hid-ids.h" | 36 | #include "hid-ids.h" |
35 | 37 | ||
@@ -42,6 +44,7 @@ | |||
42 | #define DUALSHOCK4_CONTROLLER_BT BIT(6) | 44 | #define DUALSHOCK4_CONTROLLER_BT BIT(6) |
43 | 45 | ||
44 | #define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER_USB | BUZZ_CONTROLLER | DUALSHOCK4_CONTROLLER_USB) | 46 | #define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER_USB | BUZZ_CONTROLLER | DUALSHOCK4_CONTROLLER_USB) |
47 | #define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT | DUALSHOCK4_CONTROLLER_USB) | ||
45 | 48 | ||
46 | #define MAX_LEDS 4 | 49 | #define MAX_LEDS 4 |
47 | 50 | ||
@@ -487,18 +490,30 @@ static const unsigned int buzz_keymap[] = { | |||
487 | [20] = BTN_TRIGGER_HAPPY20, | 490 | [20] = BTN_TRIGGER_HAPPY20, |
488 | }; | 491 | }; |
489 | 492 | ||
493 | static enum power_supply_property sony_battery_props[] = { | ||
494 | POWER_SUPPLY_PROP_PRESENT, | ||
495 | POWER_SUPPLY_PROP_CAPACITY, | ||
496 | POWER_SUPPLY_PROP_SCOPE, | ||
497 | POWER_SUPPLY_PROP_STATUS, | ||
498 | }; | ||
499 | |||
490 | struct sony_sc { | 500 | struct sony_sc { |
501 | spinlock_t lock; | ||
491 | struct hid_device *hdev; | 502 | struct hid_device *hdev; |
492 | struct led_classdev *leds[MAX_LEDS]; | 503 | struct led_classdev *leds[MAX_LEDS]; |
493 | struct hid_report *output_report; | 504 | struct hid_report *output_report; |
494 | unsigned long quirks; | 505 | unsigned long quirks; |
495 | struct work_struct state_worker; | 506 | struct work_struct state_worker; |
507 | struct power_supply battery; | ||
496 | 508 | ||
497 | #ifdef CONFIG_SONY_FF | 509 | #ifdef CONFIG_SONY_FF |
498 | __u8 left; | 510 | __u8 left; |
499 | __u8 right; | 511 | __u8 right; |
500 | #endif | 512 | #endif |
501 | 513 | ||
514 | __u8 cable_state; | ||
515 | __u8 battery_charging; | ||
516 | __u8 battery_capacity; | ||
502 | __u8 led_state[MAX_LEDS]; | 517 | __u8 led_state[MAX_LEDS]; |
503 | __u8 led_count; | 518 | __u8 led_count; |
504 | }; | 519 | }; |
@@ -599,6 +614,63 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, | |||
599 | return rdesc; | 614 | return rdesc; |
600 | } | 615 | } |
601 | 616 | ||
617 | static void sixaxis_parse_report(struct sony_sc *sc, __u8 *rd, int size) | ||
618 | { | ||
619 | static const __u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 }; | ||
620 | unsigned long flags; | ||
621 | __u8 cable_state, battery_capacity, battery_charging; | ||
622 | |||
623 | /* The sixaxis is charging if the battery value is 0xee | ||
624 | * and it is fully charged if the value is 0xef. | ||
625 | * It does not report the actual level while charging so it | ||
626 | * is set to 100% while charging is in progress. | ||
627 | */ | ||
628 | if (rd[30] >= 0xee) { | ||
629 | battery_capacity = 100; | ||
630 | battery_charging = rd[30] & 0x01; | ||
631 | } else { | ||
632 | battery_capacity = sixaxis_battery_capacity[rd[30]]; | ||
633 | battery_charging = 0; | ||
634 | } | ||
635 | cable_state = (rd[31] >> 4) & 0x01; | ||
636 | |||
637 | spin_lock_irqsave(&sc->lock, flags); | ||
638 | sc->cable_state = cable_state; | ||
639 | sc->battery_capacity = battery_capacity; | ||
640 | sc->battery_charging = battery_charging; | ||
641 | spin_unlock_irqrestore(&sc->lock, flags); | ||
642 | } | ||
643 | |||
644 | static void dualshock4_parse_report(struct sony_sc *sc, __u8 *rd, int size) | ||
645 | { | ||
646 | unsigned long flags; | ||
647 | __u8 cable_state, battery_capacity, battery_charging; | ||
648 | |||
649 | /* The lower 4 bits of byte 30 contain the battery level | ||
650 | * and the 5th bit contains the USB cable state. | ||
651 | */ | ||
652 | cable_state = (rd[30] >> 4) & 0x01; | ||
653 | battery_capacity = rd[30] & 0x0F; | ||
654 | |||
655 | /* On USB the Dualshock 4 battery level goes from 0 to 11. | ||
656 | * A battery level of 11 means fully charged. | ||
657 | */ | ||
658 | if (cable_state && battery_capacity == 11) | ||
659 | battery_charging = 0; | ||
660 | else | ||
661 | battery_charging = 1; | ||
662 | |||
663 | if (battery_capacity > 10) | ||
664 | battery_capacity--; | ||
665 | battery_capacity *= 10; | ||
666 | |||
667 | spin_lock_irqsave(&sc->lock, flags); | ||
668 | sc->cable_state = cable_state; | ||
669 | sc->battery_capacity = battery_capacity; | ||
670 | sc->battery_charging = battery_charging; | ||
671 | spin_unlock_irqrestore(&sc->lock, flags); | ||
672 | } | ||
673 | |||
602 | static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, | 674 | static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, |
603 | __u8 *rd, int size) | 675 | __u8 *rd, int size) |
604 | { | 676 | { |
@@ -613,6 +685,11 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, | |||
613 | swap(rd[43], rd[44]); | 685 | swap(rd[43], rd[44]); |
614 | swap(rd[45], rd[46]); | 686 | swap(rd[45], rd[46]); |
615 | swap(rd[47], rd[48]); | 687 | swap(rd[47], rd[48]); |
688 | |||
689 | sixaxis_parse_report(sc, rd, size); | ||
690 | } else if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 && | ||
691 | size == 64) { | ||
692 | dualshock4_parse_report(sc, rd, size); | ||
616 | } | 693 | } |
617 | 694 | ||
618 | return 0; | 695 | return 0; |
@@ -1011,6 +1088,91 @@ static void sony_destroy_ff(struct hid_device *hdev) | |||
1011 | } | 1088 | } |
1012 | #endif | 1089 | #endif |
1013 | 1090 | ||
1091 | static int sony_battery_get_property(struct power_supply *psy, | ||
1092 | enum power_supply_property psp, | ||
1093 | union power_supply_propval *val) | ||
1094 | { | ||
1095 | struct sony_sc *sc = container_of(psy, struct sony_sc, battery); | ||
1096 | unsigned long flags; | ||
1097 | int ret = 0; | ||
1098 | u8 battery_charging, battery_capacity, cable_state; | ||
1099 | |||
1100 | spin_lock_irqsave(&sc->lock, flags); | ||
1101 | battery_charging = sc->battery_charging; | ||
1102 | battery_capacity = sc->battery_capacity; | ||
1103 | cable_state = sc->cable_state; | ||
1104 | spin_unlock_irqrestore(&sc->lock, flags); | ||
1105 | |||
1106 | switch (psp) { | ||
1107 | case POWER_SUPPLY_PROP_PRESENT: | ||
1108 | val->intval = 1; | ||
1109 | break; | ||
1110 | case POWER_SUPPLY_PROP_SCOPE: | ||
1111 | val->intval = POWER_SUPPLY_SCOPE_DEVICE; | ||
1112 | break; | ||
1113 | case POWER_SUPPLY_PROP_CAPACITY: | ||
1114 | val->intval = battery_capacity; | ||
1115 | break; | ||
1116 | case POWER_SUPPLY_PROP_STATUS: | ||
1117 | if (battery_charging) | ||
1118 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | ||
1119 | else | ||
1120 | if (battery_capacity == 100 && cable_state) | ||
1121 | val->intval = POWER_SUPPLY_STATUS_FULL; | ||
1122 | else | ||
1123 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | ||
1124 | break; | ||
1125 | default: | ||
1126 | ret = -EINVAL; | ||
1127 | break; | ||
1128 | } | ||
1129 | return ret; | ||
1130 | } | ||
1131 | |||
1132 | static int sony_battery_probe(struct sony_sc *sc) | ||
1133 | { | ||
1134 | static atomic_t power_id_seq = ATOMIC_INIT(0); | ||
1135 | unsigned long power_id; | ||
1136 | struct hid_device *hdev = sc->hdev; | ||
1137 | int ret; | ||
1138 | |||
1139 | power_id = (unsigned long)atomic_inc_return(&power_id_seq); | ||
1140 | |||
1141 | sc->battery.properties = sony_battery_props; | ||
1142 | sc->battery.num_properties = ARRAY_SIZE(sony_battery_props); | ||
1143 | sc->battery.get_property = sony_battery_get_property; | ||
1144 | sc->battery.type = POWER_SUPPLY_TYPE_BATTERY; | ||
1145 | sc->battery.use_for_apm = 0; | ||
1146 | sc->battery.name = kasprintf(GFP_KERNEL, "sony_controller_battery_%lu", | ||
1147 | power_id); | ||
1148 | if (!sc->battery.name) | ||
1149 | return -ENOMEM; | ||
1150 | |||
1151 | ret = power_supply_register(&hdev->dev, &sc->battery); | ||
1152 | if (ret) { | ||
1153 | hid_err(hdev, "Unable to register battery device\n"); | ||
1154 | goto err_free; | ||
1155 | } | ||
1156 | |||
1157 | power_supply_powers(&sc->battery, &hdev->dev); | ||
1158 | return 0; | ||
1159 | |||
1160 | err_free: | ||
1161 | kfree(sc->battery.name); | ||
1162 | sc->battery.name = NULL; | ||
1163 | return ret; | ||
1164 | } | ||
1165 | |||
1166 | static void sony_battery_remove(struct sony_sc *sc) | ||
1167 | { | ||
1168 | if (!sc->battery.name) | ||
1169 | return; | ||
1170 | |||
1171 | power_supply_unregister(&sc->battery); | ||
1172 | kfree(sc->battery.name); | ||
1173 | sc->battery.name = NULL; | ||
1174 | } | ||
1175 | |||
1014 | static int sony_set_output_report(struct sony_sc *sc, int req_id, int req_size) | 1176 | static int sony_set_output_report(struct sony_sc *sc, int req_id, int req_size) |
1015 | { | 1177 | { |
1016 | struct list_head *head, *list; | 1178 | struct list_head *head, *list; |
@@ -1101,14 +1263,31 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) | |||
1101 | goto err_stop; | 1263 | goto err_stop; |
1102 | } | 1264 | } |
1103 | 1265 | ||
1266 | if (sc->quirks & SONY_BATTERY_SUPPORT) { | ||
1267 | ret = sony_battery_probe(sc); | ||
1268 | if (ret < 0) | ||
1269 | goto err_stop; | ||
1270 | |||
1271 | /* Open the device to receive reports with battery info */ | ||
1272 | ret = hid_hw_open(hdev); | ||
1273 | if (ret < 0) { | ||
1274 | hid_err(hdev, "hw open failed\n"); | ||
1275 | goto err_stop; | ||
1276 | } | ||
1277 | } | ||
1278 | |||
1104 | ret = sony_init_ff(hdev); | 1279 | ret = sony_init_ff(hdev); |
1105 | if (ret < 0) | 1280 | if (ret < 0) |
1106 | goto err_stop; | 1281 | goto err_close; |
1107 | 1282 | ||
1108 | return 0; | 1283 | return 0; |
1284 | err_close: | ||
1285 | hid_hw_close(hdev); | ||
1109 | err_stop: | 1286 | err_stop: |
1110 | if (sc->quirks & SONY_LED_SUPPORT) | 1287 | if (sc->quirks & SONY_LED_SUPPORT) |
1111 | sony_leds_remove(hdev); | 1288 | sony_leds_remove(hdev); |
1289 | if (sc->quirks & SONY_BATTERY_SUPPORT) | ||
1290 | sony_battery_remove(sc); | ||
1112 | hid_hw_stop(hdev); | 1291 | hid_hw_stop(hdev); |
1113 | return ret; | 1292 | return ret; |
1114 | } | 1293 | } |
@@ -1120,6 +1299,11 @@ static void sony_remove(struct hid_device *hdev) | |||
1120 | if (sc->quirks & SONY_LED_SUPPORT) | 1299 | if (sc->quirks & SONY_LED_SUPPORT) |
1121 | sony_leds_remove(hdev); | 1300 | sony_leds_remove(hdev); |
1122 | 1301 | ||
1302 | if (sc->quirks & SONY_BATTERY_SUPPORT) { | ||
1303 | hid_hw_close(hdev); | ||
1304 | sony_battery_remove(sc); | ||
1305 | } | ||
1306 | |||
1123 | sony_destroy_ff(hdev); | 1307 | sony_destroy_ff(hdev); |
1124 | 1308 | ||
1125 | hid_hw_stop(hdev); | 1309 | hid_hw_stop(hdev); |