Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/x86: dell-ddv: Expose the battery health to userspace

The health of a given battery is exposed over the Dell DDV WMI
interface using the "BatteryManufactureAceess" WMI method. The
resulting data contains, among other data, the health status of
the battery.

Expose this value to userspace using the power supply extension
interface.

Tested on a Dell Inspiron 3505.

Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/20250429003606.303870-4-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Armin Wolf and committed by
Ilpo Järvinen
2bd1870a 303ecf69

+123 -1
+34 -1
Documentation/wmi/devices/dell-wmi-ddv.rst
··· 150 150 WMI method BatteryManufactureAccess() 151 151 ------------------------------------- 152 152 153 - Returns a manufacture-defined value as an u16. 153 + Returns the health status of the battery as a u16. 154 + The health status encoded in the following manner: 155 + 156 + - the third nibble contains the general failure mode 157 + - the fourth nibble contains the specific failure code 158 + 159 + Valid failure modes are: 160 + 161 + - permanent failure (``0x9``) 162 + - overheat failure (``0xa``) 163 + - overcurrent failure (``0xb``) 164 + 165 + All other failure modes are to be considered normal. 166 + 167 + The following failure codes are valid for a permanent failure: 168 + 169 + - fuse blown (``0x0``) 170 + - cell imbalance (``0x1``) 171 + - overvoltage (``0x2``) 172 + - fet failure (``0x3``) 173 + 174 + The last two bits of the failure code are to be ignored when the battery 175 + signals a permanent failure. 176 + 177 + The following failure codes a valid for a overheat failure: 178 + 179 + - overheat at start of charging (``0x5``) 180 + - overheat during charging (``0x7``) 181 + - overheat during discharging (``0x8``) 182 + 183 + The following failure codes are valid for a overcurrent failure: 184 + 185 + - overcurrent during charging (``0x6``) 186 + - overcurrent during discharging (``0xb``) 154 187 155 188 WMI method BatteryRelativeStateOfCharge() 156 189 -----------------------------------------
+89
drivers/platform/x86/dell/dell-wmi-ddv.c
··· 47 47 #define SBS_MANUFACTURE_MONTH_MASK GENMASK(8, 5) 48 48 #define SBS_MANUFACTURE_DAY_MASK GENMASK(4, 0) 49 49 50 + #define MA_FAILURE_MODE_MASK GENMASK(11, 8) 51 + #define MA_FAILURE_MODE_PERMANENT 0x9 52 + #define MA_FAILURE_MODE_OVERHEAT 0xA 53 + #define MA_FAILURE_MODE_OVERCURRENT 0xB 54 + 55 + #define MA_PERMANENT_FAILURE_CODE_MASK GENMASK(13, 12) 56 + #define MA_PERMANENT_FAILURE_FUSE_BLOWN 0x0 57 + #define MA_PERMANENT_FAILURE_CELL_IMBALANCE 0x1 58 + #define MA_PERMANENT_FAILURE_OVERVOLTAGE 0x2 59 + #define MA_PERMANENT_FAILURE_FET_FAILURE 0x3 60 + 61 + #define MA_OVERHEAT_FAILURE_CODE_MASK GENMASK(15, 12) 62 + #define MA_OVERHEAT_FAILURE_START 0x5 63 + #define MA_OVERHEAT_FAILURE_CHARGING 0x7 64 + #define MA_OVERHEAT_FAILURE_DISCHARGING 0x8 65 + 66 + #define MA_OVERCURRENT_FAILURE_CODE_MASK GENMASK(15, 12) 67 + #define MA_OVERCURRENT_FAILURE_CHARGING 0x6 68 + #define MA_OVERCURRENT_FAILURE_DISCHARGING 0xB 69 + 50 70 #define DELL_EPPID_LENGTH 20 51 71 #define DELL_EPPID_EXT_LENGTH 23 52 72 ··· 769 749 return ret; 770 750 } 771 751 752 + static int dell_wmi_ddv_get_health(struct dell_wmi_ddv_data *data, u32 index, 753 + union power_supply_propval *val) 754 + { 755 + u32 value, code; 756 + int ret; 757 + 758 + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURER_ACCESS, index, 759 + &value); 760 + if (ret < 0) 761 + return ret; 762 + 763 + switch (FIELD_GET(MA_FAILURE_MODE_MASK, value)) { 764 + case MA_FAILURE_MODE_PERMANENT: 765 + code = FIELD_GET(MA_PERMANENT_FAILURE_CODE_MASK, value); 766 + switch (code) { 767 + case MA_PERMANENT_FAILURE_FUSE_BLOWN: 768 + val->intval = POWER_SUPPLY_HEALTH_BLOWN_FUSE; 769 + return 0; 770 + case MA_PERMANENT_FAILURE_CELL_IMBALANCE: 771 + val->intval = POWER_SUPPLY_HEALTH_CELL_IMBALANCE; 772 + return 0; 773 + case MA_PERMANENT_FAILURE_OVERVOLTAGE: 774 + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 775 + return 0; 776 + case MA_PERMANENT_FAILURE_FET_FAILURE: 777 + val->intval = POWER_SUPPLY_HEALTH_DEAD; 778 + return 0; 779 + default: 780 + dev_notice_once(&data->wdev->dev, "Unknown permanent failure code %u\n", 781 + code); 782 + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 783 + return 0; 784 + } 785 + case MA_FAILURE_MODE_OVERHEAT: 786 + code = FIELD_GET(MA_OVERHEAT_FAILURE_CODE_MASK, value); 787 + switch (code) { 788 + case MA_OVERHEAT_FAILURE_START: 789 + case MA_OVERHEAT_FAILURE_CHARGING: 790 + case MA_OVERHEAT_FAILURE_DISCHARGING: 791 + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; 792 + return 0; 793 + default: 794 + dev_notice_once(&data->wdev->dev, "Unknown overheat failure code %u\n", 795 + code); 796 + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 797 + return 0; 798 + } 799 + case MA_FAILURE_MODE_OVERCURRENT: 800 + code = FIELD_GET(MA_OVERCURRENT_FAILURE_CODE_MASK, value); 801 + switch (code) { 802 + case MA_OVERCURRENT_FAILURE_CHARGING: 803 + case MA_OVERCURRENT_FAILURE_DISCHARGING: 804 + val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; 805 + return 0; 806 + default: 807 + dev_notice_once(&data->wdev->dev, "Unknown overcurrent failure code %u\n", 808 + code); 809 + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 810 + return 0; 811 + } 812 + default: 813 + val->intval = POWER_SUPPLY_HEALTH_GOOD; 814 + return 0; 815 + } 816 + } 817 + 772 818 static int dell_wmi_ddv_get_manufacture_date(struct dell_wmi_ddv_data *data, u32 index, 773 819 enum power_supply_property psp, 774 820 union power_supply_propval *val) ··· 892 806 return ret; 893 807 894 808 switch (psp) { 809 + case POWER_SUPPLY_PROP_HEALTH: 810 + return dell_wmi_ddv_get_health(data, index, val); 895 811 case POWER_SUPPLY_PROP_TEMP: 896 812 ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_TEMPERATURE, index, 897 813 &value); ··· 915 827 } 916 828 917 829 static const enum power_supply_property dell_wmi_ddv_properties[] = { 830 + POWER_SUPPLY_PROP_HEALTH, 918 831 POWER_SUPPLY_PROP_TEMP, 919 832 POWER_SUPPLY_PROP_MANUFACTURE_YEAR, 920 833 POWER_SUPPLY_PROP_MANUFACTURE_MONTH,