diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2016-06-29 05:28:01 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2016-07-07 05:25:50 -0400 |
commit | 5a2b190cddb9aa69b9037f5b1fd1c2cc8a1d68b9 (patch) | |
tree | b032a0cfcfc1a2de9460213dfbf4612296ce9d33 | |
parent | 595d9e34eedc4b8d0631260ce93bbeb08e5b3bd7 (diff) |
HID: logitech-hidpp: add battery support for HID++ 2.0 devices
If the 0x1000 Unified Battery Level Status feature exists, expose the battery
level.
The main drawback is that while a device is plugged in its battery level is 0.
To avoid exposing that as 0% charge we make up a number based on the charging
status.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | drivers/hid/hid-logitech-hidpp.c | 238 |
1 files changed, 237 insertions, 1 deletions
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 2e2515a4c070..1ead9f697d8c 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c | |||
@@ -62,6 +62,8 @@ MODULE_PARM_DESC(disable_tap_to_click, | |||
62 | #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) | 62 | #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) |
63 | #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) | 63 | #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) |
64 | #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) | 64 | #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) |
65 | #define HIDPP_QUIRK_HIDPP20_BATTERY BIT(25) | ||
66 | #define HIDPP_QUIRK_HIDPP10_BATTERY BIT(26) | ||
65 | 67 | ||
66 | #define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ | 68 | #define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ |
67 | HIDPP_QUIRK_CONNECT_EVENTS) | 69 | HIDPP_QUIRK_CONNECT_EVENTS) |
@@ -110,6 +112,15 @@ struct hidpp_report { | |||
110 | }; | 112 | }; |
111 | } __packed; | 113 | } __packed; |
112 | 114 | ||
115 | struct hidpp_battery { | ||
116 | u8 feature_index; | ||
117 | struct power_supply_desc desc; | ||
118 | struct power_supply *ps; | ||
119 | char name[64]; | ||
120 | int status; | ||
121 | int level; | ||
122 | }; | ||
123 | |||
113 | struct hidpp_device { | 124 | struct hidpp_device { |
114 | struct hid_device *hid_dev; | 125 | struct hid_device *hid_dev; |
115 | struct mutex send_mutex; | 126 | struct mutex send_mutex; |
@@ -128,8 +139,9 @@ struct hidpp_device { | |||
128 | struct input_dev *delayed_input; | 139 | struct input_dev *delayed_input; |
129 | 140 | ||
130 | unsigned long quirks; | 141 | unsigned long quirks; |
131 | }; | ||
132 | 142 | ||
143 | struct hidpp_battery battery; | ||
144 | }; | ||
133 | 145 | ||
134 | /* HID++ 1.0 error codes */ | 146 | /* HID++ 1.0 error codes */ |
135 | #define HIDPP_ERROR 0x8f | 147 | #define HIDPP_ERROR 0x8f |
@@ -607,6 +619,222 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp) | |||
607 | } | 619 | } |
608 | 620 | ||
609 | /* -------------------------------------------------------------------------- */ | 621 | /* -------------------------------------------------------------------------- */ |
622 | /* 0x1000: Battery level status */ | ||
623 | /* -------------------------------------------------------------------------- */ | ||
624 | |||
625 | #define HIDPP_PAGE_BATTERY_LEVEL_STATUS 0x1000 | ||
626 | |||
627 | #define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS 0x00 | ||
628 | #define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY 0x10 | ||
629 | |||
630 | #define EVENT_BATTERY_LEVEL_STATUS_BROADCAST 0x00 | ||
631 | |||
632 | static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, | ||
633 | int *next_level) | ||
634 | { | ||
635 | int status; | ||
636 | int level_override; | ||
637 | |||
638 | *level = data[0]; | ||
639 | *next_level = data[1]; | ||
640 | |||
641 | /* When discharging, we can rely on the device reported level. | ||
642 | * For all other states the device reports level 0 (unknown). Make up | ||
643 | * a number instead | ||
644 | */ | ||
645 | switch (data[2]) { | ||
646 | case 0: /* discharging (in use) */ | ||
647 | status = POWER_SUPPLY_STATUS_DISCHARGING; | ||
648 | level_override = 0; | ||
649 | break; | ||
650 | case 1: /* recharging */ | ||
651 | status = POWER_SUPPLY_STATUS_CHARGING; | ||
652 | level_override = 80; | ||
653 | break; | ||
654 | case 2: /* charge in final stage */ | ||
655 | status = POWER_SUPPLY_STATUS_CHARGING; | ||
656 | level_override = 90; | ||
657 | break; | ||
658 | case 3: /* charge complete */ | ||
659 | status = POWER_SUPPLY_STATUS_FULL; | ||
660 | level_override = 100; | ||
661 | break; | ||
662 | case 4: /* recharging below optimal speed */ | ||
663 | status = POWER_SUPPLY_STATUS_CHARGING; | ||
664 | level_override = 50; | ||
665 | break; | ||
666 | /* 5 = invalid battery type | ||
667 | 6 = thermal error | ||
668 | 7 = other charging error */ | ||
669 | default: | ||
670 | status = POWER_SUPPLY_STATUS_NOT_CHARGING; | ||
671 | level_override = 0; | ||
672 | break; | ||
673 | } | ||
674 | |||
675 | if (level_override != 0 && *level == 0) | ||
676 | *level = level_override; | ||
677 | |||
678 | return status; | ||
679 | } | ||
680 | |||
681 | static int hidpp20_batterylevel_get_battery_level(struct hidpp_device *hidpp, | ||
682 | u8 feature_index, | ||
683 | int *status, | ||
684 | int *level, | ||
685 | int *next_level) | ||
686 | { | ||
687 | struct hidpp_report response; | ||
688 | int ret; | ||
689 | u8 *params = (u8 *)response.fap.params; | ||
690 | |||
691 | ret = hidpp_send_fap_command_sync(hidpp, feature_index, | ||
692 | CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS, | ||
693 | NULL, 0, &response); | ||
694 | if (ret > 0) { | ||
695 | hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", | ||
696 | __func__, ret); | ||
697 | return -EPROTO; | ||
698 | } | ||
699 | if (ret) | ||
700 | return ret; | ||
701 | |||
702 | *status = hidpp20_batterylevel_map_status_level(params, level, | ||
703 | next_level); | ||
704 | |||
705 | return 0; | ||
706 | } | ||
707 | |||
708 | static int hidpp20_query_battery_info(struct hidpp_device *hidpp) | ||
709 | { | ||
710 | u8 feature_type; | ||
711 | int ret; | ||
712 | int status, level, next_level; | ||
713 | |||
714 | if (hidpp->battery.feature_index == 0) { | ||
715 | ret = hidpp_root_get_feature(hidpp, | ||
716 | HIDPP_PAGE_BATTERY_LEVEL_STATUS, | ||
717 | &hidpp->battery.feature_index, | ||
718 | &feature_type); | ||
719 | if (ret) | ||
720 | return ret; | ||
721 | } | ||
722 | |||
723 | ret = hidpp20_batterylevel_get_battery_level(hidpp, | ||
724 | hidpp->battery.feature_index, | ||
725 | &status, &level, &next_level); | ||
726 | if (ret) | ||
727 | return ret; | ||
728 | |||
729 | hidpp->battery.status = status; | ||
730 | hidpp->battery.level = level; | ||
731 | |||
732 | return 0; | ||
733 | } | ||
734 | |||
735 | static int hidpp20_battery_event(struct hidpp_device *hidpp, | ||
736 | u8 *data, int size) | ||
737 | { | ||
738 | struct hidpp_report *report = (struct hidpp_report *)data; | ||
739 | int status, level, next_level; | ||
740 | bool changed; | ||
741 | |||
742 | if (report->fap.feature_index != hidpp->battery.feature_index || | ||
743 | report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST) | ||
744 | return 0; | ||
745 | |||
746 | status = hidpp20_batterylevel_map_status_level(report->fap.params, | ||
747 | &level, &next_level); | ||
748 | |||
749 | changed = level != hidpp->battery.level || | ||
750 | status != hidpp->battery.status; | ||
751 | |||
752 | if (changed) { | ||
753 | hidpp->battery.level = level; | ||
754 | hidpp->battery.status = status; | ||
755 | if (hidpp->battery.ps) | ||
756 | power_supply_changed(hidpp->battery.ps); | ||
757 | } | ||
758 | |||
759 | return 0; | ||
760 | } | ||
761 | |||
762 | static enum power_supply_property hidpp_battery_props[] = { | ||
763 | POWER_SUPPLY_PROP_STATUS, | ||
764 | POWER_SUPPLY_PROP_CAPACITY, | ||
765 | }; | ||
766 | |||
767 | static int hidpp_battery_get_property(struct power_supply *psy, | ||
768 | enum power_supply_property psp, | ||
769 | union power_supply_propval *val) | ||
770 | { | ||
771 | struct hidpp_device *hidpp = power_supply_get_drvdata(psy); | ||
772 | int ret = 0; | ||
773 | |||
774 | switch(psp) { | ||
775 | case POWER_SUPPLY_PROP_STATUS: | ||
776 | val->intval = hidpp->battery.status; | ||
777 | break; | ||
778 | case POWER_SUPPLY_PROP_CAPACITY: | ||
779 | val->intval = hidpp->battery.level; | ||
780 | break; | ||
781 | default: | ||
782 | ret = -EINVAL; | ||
783 | break; | ||
784 | } | ||
785 | |||
786 | return ret; | ||
787 | } | ||
788 | |||
789 | static int hidpp20_initialize_battery(struct hidpp_device *hidpp) | ||
790 | { | ||
791 | static atomic_t battery_no = ATOMIC_INIT(0); | ||
792 | struct power_supply_config cfg = { .drv_data = hidpp }; | ||
793 | struct power_supply_desc *desc = &hidpp->battery.desc; | ||
794 | struct hidpp_battery *battery; | ||
795 | unsigned long n; | ||
796 | int ret; | ||
797 | |||
798 | ret = hidpp20_query_battery_info(hidpp); | ||
799 | if (ret) | ||
800 | return ret; | ||
801 | |||
802 | battery = &hidpp->battery; | ||
803 | |||
804 | n = atomic_inc_return(&battery_no) - 1; | ||
805 | desc->properties = hidpp_battery_props; | ||
806 | desc->num_properties = ARRAY_SIZE(hidpp_battery_props); | ||
807 | desc->get_property = hidpp_battery_get_property; | ||
808 | sprintf(battery->name, "hidpp_battery_%ld", n); | ||
809 | desc->name = battery->name; | ||
810 | desc->type = POWER_SUPPLY_TYPE_BATTERY; | ||
811 | desc->use_for_apm = 0; | ||
812 | |||
813 | battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev, | ||
814 | &battery->desc, | ||
815 | &cfg); | ||
816 | if (IS_ERR(battery->ps)) | ||
817 | return PTR_ERR(battery->ps); | ||
818 | |||
819 | power_supply_powers(battery->ps, &hidpp->hid_dev->dev); | ||
820 | |||
821 | return 0; | ||
822 | } | ||
823 | |||
824 | static int hidpp_initialize_battery(struct hidpp_device *hidpp) | ||
825 | { | ||
826 | int ret; | ||
827 | |||
828 | if (hidpp->protocol_major >= 2) { | ||
829 | ret = hidpp20_initialize_battery(hidpp); | ||
830 | if (ret == 0) | ||
831 | hidpp->quirks |= HIDPP_QUIRK_HIDPP20_BATTERY; | ||
832 | } | ||
833 | |||
834 | return ret; | ||
835 | } | ||
836 | |||
837 | /* -------------------------------------------------------------------------- */ | ||
610 | /* 0x6010: Touchpad FW items */ | 838 | /* 0x6010: Touchpad FW items */ |
611 | /* -------------------------------------------------------------------------- */ | 839 | /* -------------------------------------------------------------------------- */ |
612 | 840 | ||
@@ -2050,6 +2278,12 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, | |||
2050 | if (ret != 0) | 2278 | if (ret != 0) |
2051 | return ret; | 2279 | return ret; |
2052 | 2280 | ||
2281 | if (hidpp->quirks & HIDPP_QUIRK_HIDPP20_BATTERY) { | ||
2282 | ret = hidpp20_battery_event(hidpp, data, size); | ||
2283 | if (ret != 0) | ||
2284 | return ret; | ||
2285 | } | ||
2286 | |||
2053 | if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) | 2287 | if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) |
2054 | return wtp_raw_event(hdev, data, size); | 2288 | return wtp_raw_event(hdev, data, size); |
2055 | else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) | 2289 | else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) |
@@ -2158,6 +2392,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) | |||
2158 | hidpp->protocol_major, hidpp->protocol_minor); | 2392 | hidpp->protocol_major, hidpp->protocol_minor); |
2159 | } | 2393 | } |
2160 | 2394 | ||
2395 | hidpp_initialize_battery(hidpp); | ||
2396 | |||
2161 | if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) | 2397 | if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) |
2162 | /* if HID created the input nodes for us, we can stop now */ | 2398 | /* if HID created the input nodes for us, we can stop now */ |
2163 | return; | 2399 | return; |