xref: /openbmc/linux/drivers/hwmon/hp-wmi-sensors.c (revision 153c9a023b1f455887a3fb7913207794eee3c6c2)
123902f98SJames Seo // SPDX-License-Identifier: GPL-2.0-or-later
223902f98SJames Seo /*
323902f98SJames Seo  * hwmon driver for HP (and some HP Compaq) business-class computers that
423902f98SJames Seo  * report numeric sensor data via Windows Management Instrumentation (WMI).
523902f98SJames Seo  *
623902f98SJames Seo  * Copyright (C) 2023 James Seo <james@equiv.tech>
723902f98SJames Seo  *
823902f98SJames Seo  * References:
923902f98SJames Seo  * [1] Hewlett-Packard Development Company, L.P.,
1023902f98SJames Seo  *     "HP Client Management Interface Technical White Paper", 2005. [Online].
1123902f98SJames Seo  *     Available: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf
1223902f98SJames Seo  * [2] Hewlett-Packard Development Company, L.P.,
1323902f98SJames Seo  *     "HP Retail Manageability", 2012. [Online].
1423902f98SJames Seo  *     Available: http://h10032.www1.hp.com/ctg/Manual/c03291135.pdf
1523902f98SJames Seo  * [3] Linux Hardware Project, A. Ponomarenko et al.,
1623902f98SJames Seo  *     "linuxhw/ACPI - Collect ACPI table dumps", 2018. [Online].
1723902f98SJames Seo  *     Available: https://github.com/linuxhw/ACPI
1823902f98SJames Seo  * [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
1923902f98SJames Seo  *     2017. [Online]. Available: https://github.com/pali/bmfdec
2023902f98SJames Seo  */
2123902f98SJames Seo 
2223902f98SJames Seo #include <linux/acpi.h>
2323902f98SJames Seo #include <linux/debugfs.h>
2423902f98SJames Seo #include <linux/hwmon.h>
2523902f98SJames Seo #include <linux/jiffies.h>
2623902f98SJames Seo #include <linux/mutex.h>
2723902f98SJames Seo #include <linux/units.h>
2823902f98SJames Seo #include <linux/wmi.h>
2923902f98SJames Seo 
3023902f98SJames Seo #define HP_WMI_EVENT_NAMESPACE		"root\\WMI"
3123902f98SJames Seo #define HP_WMI_EVENT_CLASS		"HPBIOS_BIOSEvent"
3223902f98SJames Seo #define HP_WMI_EVENT_GUID		"95F24279-4D7B-4334-9387-ACCDC67EF61C"
3323902f98SJames Seo #define HP_WMI_NUMERIC_SENSOR_GUID	"8F1F6435-9F42-42C8-BADC-0E9424F20C9A"
3423902f98SJames Seo #define HP_WMI_PLATFORM_EVENTS_GUID	"41227C2D-80E1-423F-8B8E-87E32755A0EB"
3523902f98SJames Seo 
3623902f98SJames Seo /* Patterns for recognizing sensors and matching events to channels. */
3723902f98SJames Seo 
3823902f98SJames Seo #define HP_WMI_PATTERN_SYS_TEMP		"Chassis Thermal Index"
3923902f98SJames Seo #define HP_WMI_PATTERN_SYS_TEMP2	"System Ambient Temperature"
4023902f98SJames Seo #define HP_WMI_PATTERN_CPU_TEMP		"CPU Thermal Index"
4123902f98SJames Seo #define HP_WMI_PATTERN_CPU_TEMP2	"CPU Temperature"
4223902f98SJames Seo #define HP_WMI_PATTERN_TEMP_SENSOR	"Thermal Index"
4323902f98SJames Seo #define HP_WMI_PATTERN_TEMP_ALARM	"Thermal Critical"
4423902f98SJames Seo #define HP_WMI_PATTERN_INTRUSION_ALARM	"Hood Intrusion"
4523902f98SJames Seo #define HP_WMI_PATTERN_FAN_ALARM	"Stall"
4623902f98SJames Seo #define HP_WMI_PATTERN_TEMP		"Temperature"
4723902f98SJames Seo #define HP_WMI_PATTERN_CPU		"CPU"
4823902f98SJames Seo 
4923902f98SJames Seo /* These limits are arbitrary. The WMI implementation may vary by system. */
5023902f98SJames Seo 
5123902f98SJames Seo #define HP_WMI_MAX_STR_SIZE		128U
5223902f98SJames Seo #define HP_WMI_MAX_PROPERTIES		32U
5323902f98SJames Seo #define HP_WMI_MAX_INSTANCES		32U
5423902f98SJames Seo 
5523902f98SJames Seo enum hp_wmi_type {
5623902f98SJames Seo 	HP_WMI_TYPE_OTHER			= 1,
5723902f98SJames Seo 	HP_WMI_TYPE_TEMPERATURE			= 2,
5823902f98SJames Seo 	HP_WMI_TYPE_VOLTAGE			= 3,
5923902f98SJames Seo 	HP_WMI_TYPE_CURRENT			= 4,
6023902f98SJames Seo 	HP_WMI_TYPE_AIR_FLOW			= 12,
6123902f98SJames Seo 	HP_WMI_TYPE_INTRUSION			= 0xabadb01, /* Custom. */
6223902f98SJames Seo };
6323902f98SJames Seo 
6423902f98SJames Seo enum hp_wmi_category {
6523902f98SJames Seo 	HP_WMI_CATEGORY_SENSOR			= 3,
6623902f98SJames Seo };
6723902f98SJames Seo 
6823902f98SJames Seo enum hp_wmi_severity {
6923902f98SJames Seo 	HP_WMI_SEVERITY_UNKNOWN			= 0,
7023902f98SJames Seo 	HP_WMI_SEVERITY_OK			= 5,
7123902f98SJames Seo 	HP_WMI_SEVERITY_DEGRADED_WARNING	= 10,
7223902f98SJames Seo 	HP_WMI_SEVERITY_MINOR_FAILURE		= 15,
7323902f98SJames Seo 	HP_WMI_SEVERITY_MAJOR_FAILURE		= 20,
7423902f98SJames Seo 	HP_WMI_SEVERITY_CRITICAL_FAILURE	= 25,
7523902f98SJames Seo 	HP_WMI_SEVERITY_NON_RECOVERABLE_ERROR	= 30,
7623902f98SJames Seo };
7723902f98SJames Seo 
7823902f98SJames Seo enum hp_wmi_status {
7923902f98SJames Seo 	HP_WMI_STATUS_OK			= 2,
8023902f98SJames Seo 	HP_WMI_STATUS_DEGRADED			= 3,
8123902f98SJames Seo 	HP_WMI_STATUS_STRESSED			= 4,
8223902f98SJames Seo 	HP_WMI_STATUS_PREDICTIVE_FAILURE	= 5,
8323902f98SJames Seo 	HP_WMI_STATUS_ERROR			= 6,
8423902f98SJames Seo 	HP_WMI_STATUS_NON_RECOVERABLE_ERROR	= 7,
8523902f98SJames Seo 	HP_WMI_STATUS_NO_CONTACT		= 12,
8623902f98SJames Seo 	HP_WMI_STATUS_LOST_COMMUNICATION	= 13,
8723902f98SJames Seo 	HP_WMI_STATUS_ABORTED			= 14,
8823902f98SJames Seo 	HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR = 16,
8923902f98SJames Seo 
9023902f98SJames Seo 	/* Occurs combined with one of "OK", "Degraded", and "Error" [1]. */
9123902f98SJames Seo 	HP_WMI_STATUS_COMPLETED			= 17,
9223902f98SJames Seo };
9323902f98SJames Seo 
9423902f98SJames Seo enum hp_wmi_units {
9523902f98SJames Seo 	HP_WMI_UNITS_OTHER			= 1,
9623902f98SJames Seo 	HP_WMI_UNITS_DEGREES_C			= 2,
9723902f98SJames Seo 	HP_WMI_UNITS_DEGREES_F			= 3,
9823902f98SJames Seo 	HP_WMI_UNITS_DEGREES_K			= 4,
9923902f98SJames Seo 	HP_WMI_UNITS_VOLTS			= 5,
10023902f98SJames Seo 	HP_WMI_UNITS_AMPS			= 6,
10123902f98SJames Seo 	HP_WMI_UNITS_RPM			= 19,
10223902f98SJames Seo };
10323902f98SJames Seo 
10423902f98SJames Seo enum hp_wmi_property {
10523902f98SJames Seo 	HP_WMI_PROPERTY_NAME			= 0,
10623902f98SJames Seo 	HP_WMI_PROPERTY_DESCRIPTION		= 1,
10723902f98SJames Seo 	HP_WMI_PROPERTY_SENSOR_TYPE		= 2,
10823902f98SJames Seo 	HP_WMI_PROPERTY_OTHER_SENSOR_TYPE	= 3,
10923902f98SJames Seo 	HP_WMI_PROPERTY_OPERATIONAL_STATUS	= 4,
11023902f98SJames Seo 	HP_WMI_PROPERTY_SIZE			= 5,
11123902f98SJames Seo 	HP_WMI_PROPERTY_POSSIBLE_STATES		= 6,
11223902f98SJames Seo 	HP_WMI_PROPERTY_CURRENT_STATE		= 7,
11323902f98SJames Seo 	HP_WMI_PROPERTY_BASE_UNITS		= 8,
11423902f98SJames Seo 	HP_WMI_PROPERTY_UNIT_MODIFIER		= 9,
11523902f98SJames Seo 	HP_WMI_PROPERTY_CURRENT_READING		= 10,
11623902f98SJames Seo 	HP_WMI_PROPERTY_RATE_UNITS		= 11,
11723902f98SJames Seo };
11823902f98SJames Seo 
11923902f98SJames Seo static const acpi_object_type hp_wmi_property_map[] = {
12023902f98SJames Seo 	[HP_WMI_PROPERTY_NAME]			= ACPI_TYPE_STRING,
12123902f98SJames Seo 	[HP_WMI_PROPERTY_DESCRIPTION]		= ACPI_TYPE_STRING,
12223902f98SJames Seo 	[HP_WMI_PROPERTY_SENSOR_TYPE]		= ACPI_TYPE_INTEGER,
12323902f98SJames Seo 	[HP_WMI_PROPERTY_OTHER_SENSOR_TYPE]	= ACPI_TYPE_STRING,
12423902f98SJames Seo 	[HP_WMI_PROPERTY_OPERATIONAL_STATUS]	= ACPI_TYPE_INTEGER,
12523902f98SJames Seo 	[HP_WMI_PROPERTY_SIZE]			= ACPI_TYPE_INTEGER,
12623902f98SJames Seo 	[HP_WMI_PROPERTY_POSSIBLE_STATES]	= ACPI_TYPE_STRING,
12723902f98SJames Seo 	[HP_WMI_PROPERTY_CURRENT_STATE]		= ACPI_TYPE_STRING,
12823902f98SJames Seo 	[HP_WMI_PROPERTY_BASE_UNITS]		= ACPI_TYPE_INTEGER,
12923902f98SJames Seo 	[HP_WMI_PROPERTY_UNIT_MODIFIER]		= ACPI_TYPE_INTEGER,
13023902f98SJames Seo 	[HP_WMI_PROPERTY_CURRENT_READING]	= ACPI_TYPE_INTEGER,
13123902f98SJames Seo 	[HP_WMI_PROPERTY_RATE_UNITS]		= ACPI_TYPE_INTEGER,
13223902f98SJames Seo };
13323902f98SJames Seo 
13423902f98SJames Seo enum hp_wmi_platform_events_property {
13523902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME		    = 0,
13623902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION	    = 1,
13723902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE    = 2,
13823902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS	    = 3,
13923902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY	    = 4,
14023902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY   = 5,
14123902f98SJames Seo 	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS	    = 6,
14223902f98SJames Seo };
14323902f98SJames Seo 
14423902f98SJames Seo static const acpi_object_type hp_wmi_platform_events_property_map[] = {
14523902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME]		    = ACPI_TYPE_STRING,
14623902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION]	    = ACPI_TYPE_STRING,
14723902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE]  = ACPI_TYPE_STRING,
14823902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS]	    = ACPI_TYPE_STRING,
14923902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY]	    = ACPI_TYPE_INTEGER,
15023902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY] = ACPI_TYPE_INTEGER,
15123902f98SJames Seo 	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS]   = ACPI_TYPE_INTEGER,
15223902f98SJames Seo };
15323902f98SJames Seo 
15423902f98SJames Seo enum hp_wmi_event_property {
15523902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_NAME		= 0,
15623902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_DESCRIPTION	= 1,
15723902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_CATEGORY		= 2,
15823902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_SEVERITY		= 3,
15923902f98SJames Seo 	HP_WMI_EVENT_PROPERTY_STATUS		= 4,
16023902f98SJames Seo };
16123902f98SJames Seo 
16223902f98SJames Seo static const acpi_object_type hp_wmi_event_property_map[] = {
16323902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_NAME]		= ACPI_TYPE_STRING,
16423902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_DESCRIPTION]	= ACPI_TYPE_STRING,
16523902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_CATEGORY]	= ACPI_TYPE_INTEGER,
16623902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_SEVERITY]	= ACPI_TYPE_INTEGER,
16723902f98SJames Seo 	[HP_WMI_EVENT_PROPERTY_STATUS]		= ACPI_TYPE_INTEGER,
16823902f98SJames Seo };
16923902f98SJames Seo 
17023902f98SJames Seo static const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = {
17123902f98SJames Seo 	[HP_WMI_TYPE_TEMPERATURE]		= hwmon_temp,
17223902f98SJames Seo 	[HP_WMI_TYPE_VOLTAGE]			= hwmon_in,
17323902f98SJames Seo 	[HP_WMI_TYPE_CURRENT]			= hwmon_curr,
17423902f98SJames Seo 	[HP_WMI_TYPE_AIR_FLOW]			= hwmon_fan,
17523902f98SJames Seo };
17623902f98SJames Seo 
17723902f98SJames Seo static const u32 hp_wmi_hwmon_attributes[hwmon_max] = {
17823902f98SJames Seo 	[hwmon_chip]	  = HWMON_C_REGISTER_TZ,
17923902f98SJames Seo 	[hwmon_temp]	  = HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT,
18023902f98SJames Seo 	[hwmon_in]	  = HWMON_I_INPUT | HWMON_I_LABEL,
18123902f98SJames Seo 	[hwmon_curr]	  = HWMON_C_INPUT | HWMON_C_LABEL,
18223902f98SJames Seo 	[hwmon_fan]	  = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_FAULT,
18323902f98SJames Seo 	[hwmon_intrusion] = HWMON_INTRUSION_ALARM,
18423902f98SJames Seo };
18523902f98SJames Seo 
18623902f98SJames Seo /*
18723902f98SJames Seo  * struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance
18823902f98SJames Seo  *
18923902f98SJames Seo  * Two variants of HPBIOS_BIOSNumericSensor are known. The first is specified
19023902f98SJames Seo  * in [1] and appears to be much more widespread. The second was discovered by
19123902f98SJames Seo  * decoding BMOF blobs [4], seems to be found only in some newer ZBook systems
19223902f98SJames Seo  * [3], and has two new properties and a slightly different property order.
19323902f98SJames Seo  *
19423902f98SJames Seo  * These differences don't matter on Windows, where WMI object properties are
19523902f98SJames Seo  * accessed by name. For us, supporting both variants gets ugly and hacky at
19623902f98SJames Seo  * times. The fun begins now; this struct is defined as per the new variant.
19723902f98SJames Seo  *
19823902f98SJames Seo  * Effective MOF definition:
19923902f98SJames Seo  *
20023902f98SJames Seo  *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
20123902f98SJames Seo  *   class HPBIOS_BIOSNumericSensor {
20223902f98SJames Seo  *     [read] string Name;
20323902f98SJames Seo  *     [read] string Description;
20423902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
20523902f98SJames Seo  *      "10","11","12"}, Values {"Unknown","Other","Temperature",
20623902f98SJames Seo  *      "Voltage","Current","Tachometer","Counter","Switch","Lock",
20723902f98SJames Seo  *      "Humidity","Smoke Detection","Presence","Air Flow"}]
20823902f98SJames Seo  *     uint32 SensorType;
20923902f98SJames Seo  *     [read] string OtherSensorType;
21023902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
21123902f98SJames Seo  *      "10","11","12","13","14","15","16","17","18","..",
21223902f98SJames Seo  *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
21323902f98SJames Seo  *      "Stressed","Predictive Failure","Error",
21423902f98SJames Seo  *      "Non-Recoverable Error","Starting","Stopping","Stopped",
21523902f98SJames Seo  *      "In Service","No Contact","Lost Communication","Aborted",
21623902f98SJames Seo  *      "Dormant","Supporting Entity in Error","Completed",
21723902f98SJames Seo  *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
21823902f98SJames Seo  *     uint32 OperationalStatus;
21923902f98SJames Seo  *     [read] uint32 Size;
22023902f98SJames Seo  *     [read] string PossibleStates[];
22123902f98SJames Seo  *     [read] string CurrentState;
22223902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
22323902f98SJames Seo  *      "10","11","12","13","14","15","16","17","18","19","20",
22423902f98SJames Seo  *      "21","22","23","24","25","26","27","28","29","30","31",
22523902f98SJames Seo  *      "32","33","34","35","36","37","38","39","40","41","42",
22623902f98SJames Seo  *      "43","44","45","46","47","48","49","50","51","52","53",
22723902f98SJames Seo  *      "54","55","56","57","58","59","60","61","62","63","64",
22823902f98SJames Seo  *      "65"}, Values {"Unknown","Other","Degrees C","Degrees F",
22923902f98SJames Seo  *      "Degrees K","Volts","Amps","Watts","Joules","Coulombs",
23023902f98SJames Seo  *      "VA","Nits","Lumens","Lux","Candelas","kPa","PSI",
23123902f98SJames Seo  *      "Newtons","CFM","RPM","Hertz","Seconds","Minutes",
23223902f98SJames Seo  *      "Hours","Days","Weeks","Mils","Inches","Feet",
23323902f98SJames Seo  *      "Cubic Inches","Cubic Feet","Meters","Cubic Centimeters",
23423902f98SJames Seo  *      "Cubic Meters","Liters","Fluid Ounces","Radians",
23523902f98SJames Seo  *      "Steradians","Revolutions","Cycles","Gravities","Ounces",
23623902f98SJames Seo  *      "Pounds","Foot-Pounds","Ounce-Inches","Gauss","Gilberts",
23723902f98SJames Seo  *      "Henries","Farads","Ohms","Siemens","Moles","Becquerels",
23823902f98SJames Seo  *      "PPM (parts/million)","Decibels","DbA","DbC","Grays",
23923902f98SJames Seo  *      "Sieverts","Color Temperature Degrees K","Bits","Bytes",
24023902f98SJames Seo  *      "Words (data)","DoubleWords","QuadWords","Percentage"}]
24123902f98SJames Seo  *     uint32 BaseUnits;
24223902f98SJames Seo  *     [read] sint32 UnitModifier;
24323902f98SJames Seo  *     [read] uint32 CurrentReading;
24423902f98SJames Seo  *     [read] uint32 RateUnits;
24523902f98SJames Seo  *   };
24623902f98SJames Seo  *
24723902f98SJames Seo  * Effective MOF definition of old variant [1] (sans redundant info):
24823902f98SJames Seo  *
24923902f98SJames Seo  *   class HPBIOS_BIOSNumericSensor {
25023902f98SJames Seo  *     [read] string Name;
25123902f98SJames Seo  *     [read] string Description;
25223902f98SJames Seo  *     [read] uint32 SensorType;
25323902f98SJames Seo  *     [read] string OtherSensorType;
25423902f98SJames Seo  *     [read] uint32 OperationalStatus;
25523902f98SJames Seo  *     [read] string CurrentState;
25623902f98SJames Seo  *     [read] string PossibleStates[];
25723902f98SJames Seo  *     [read] uint32 BaseUnits;
25823902f98SJames Seo  *     [read] sint32 UnitModifier;
25923902f98SJames Seo  *     [read] uint32 CurrentReading;
26023902f98SJames Seo  *   };
26123902f98SJames Seo  */
26223902f98SJames Seo struct hp_wmi_numeric_sensor {
26323902f98SJames Seo 	const char *name;
26423902f98SJames Seo 	const char *description;
26523902f98SJames Seo 	u32 sensor_type;
26623902f98SJames Seo 	const char *other_sensor_type;	/* Explains "Other" SensorType. */
26723902f98SJames Seo 	u32 operational_status;
26823902f98SJames Seo 	u8 size;			/* Count of PossibleStates[]. */
26923902f98SJames Seo 	const char **possible_states;
27023902f98SJames Seo 	const char *current_state;
27123902f98SJames Seo 	u32 base_units;
27223902f98SJames Seo 	s32 unit_modifier;
27323902f98SJames Seo 	u32 current_reading;
27423902f98SJames Seo 	u32 rate_units;
27523902f98SJames Seo };
27623902f98SJames Seo 
27723902f98SJames Seo /*
27823902f98SJames Seo  * struct hp_wmi_platform_events - a HPBIOS_PlatformEvents instance
27923902f98SJames Seo  *
28023902f98SJames Seo  * Instances of this object reveal the set of possible HPBIOS_BIOSEvent
28123902f98SJames Seo  * instances for the current system, but it may not always be present.
28223902f98SJames Seo  *
28323902f98SJames Seo  * Effective MOF definition:
28423902f98SJames Seo  *
28523902f98SJames Seo  *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
28623902f98SJames Seo  *   class HPBIOS_PlatformEvents {
28723902f98SJames Seo  *     [read] string Name;
28823902f98SJames Seo  *     [read] string Description;
28923902f98SJames Seo  *     [read] string SourceNamespace;
29023902f98SJames Seo  *     [read] string SourceClass;
29123902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4",".."}, Values {
29223902f98SJames Seo  *      "Unknown","Configuration Change","Button Pressed",
29323902f98SJames Seo  *      "Sensor","BIOS Settings","Reserved"}]
29423902f98SJames Seo  *     uint32 Category;
29523902f98SJames Seo  *     [read, ValueMap{"0","5","10","15","20","25","30",".."},
29623902f98SJames Seo  *      Values{"Unknown","OK","Degraded/Warning","Minor Failure",
29723902f98SJames Seo  *      "Major Failure","Critical Failure","Non-recoverable Error",
29823902f98SJames Seo  *      "DMTF Reserved"}]
29923902f98SJames Seo  *     uint32 PossibleSeverity;
30023902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
30123902f98SJames Seo  *      "10","11","12","13","14","15","16","17","18","..",
30223902f98SJames Seo  *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
30323902f98SJames Seo  *      "Stressed","Predictive Failure","Error",
30423902f98SJames Seo  *      "Non-Recoverable Error","Starting","Stopping","Stopped",
30523902f98SJames Seo  *      "In Service","No Contact","Lost Communication","Aborted",
30623902f98SJames Seo  *      "Dormant","Supporting Entity in Error","Completed",
30723902f98SJames Seo  *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
30823902f98SJames Seo  *     uint32 PossibleStatus;
30923902f98SJames Seo  *   };
31023902f98SJames Seo  */
31123902f98SJames Seo struct hp_wmi_platform_events {
31223902f98SJames Seo 	const char *name;
31323902f98SJames Seo 	const char *description;
31423902f98SJames Seo 	const char *source_namespace;
31523902f98SJames Seo 	const char *source_class;
31623902f98SJames Seo 	u32 category;
31723902f98SJames Seo 	u32 possible_severity;
31823902f98SJames Seo 	u32 possible_status;
31923902f98SJames Seo };
32023902f98SJames Seo 
32123902f98SJames Seo /*
32223902f98SJames Seo  * struct hp_wmi_event - a HPBIOS_BIOSEvent instance
32323902f98SJames Seo  *
32423902f98SJames Seo  * Effective MOF definition [1] (corrected below from original):
32523902f98SJames Seo  *
32623902f98SJames Seo  *   #pragma namespace("\\\\.\\root\\WMI");
32723902f98SJames Seo  *   class HPBIOS_BIOSEvent : WMIEvent {
32823902f98SJames Seo  *     [read] string Name;
32923902f98SJames Seo  *     [read] string Description;
33023902f98SJames Seo  *     [read ValueMap {"0","1","2","3","4"}, Values {"Unknown",
33123902f98SJames Seo  *      "Configuration Change","Button Pressed","Sensor",
33223902f98SJames Seo  *      "BIOS Settings"}]
33323902f98SJames Seo  *     uint32 Category;
33423902f98SJames Seo  *     [read, ValueMap {"0","5","10","15","20","25","30"},
33523902f98SJames Seo  *      Values {"Unknown","OK","Degraded/Warning",
33623902f98SJames Seo  *      "Minor Failure","Major Failure","Critical Failure",
33723902f98SJames Seo  *      "Non-recoverable Error"}]
33823902f98SJames Seo  *     uint32 Severity;
33923902f98SJames Seo  *     [read, ValueMap {"0","1","2","3","4","5","6","7","8",
34023902f98SJames Seo  *      "9","10","11","12","13","14","15","16","17","18","..",
34123902f98SJames Seo  *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
34223902f98SJames Seo  *      "Stressed","Predictive Failure","Error",
34323902f98SJames Seo  *      "Non-Recoverable Error","Starting","Stopping","Stopped",
34423902f98SJames Seo  *      "In Service","No Contact","Lost Communication","Aborted",
34523902f98SJames Seo  *      "Dormant","Supporting Entity in Error","Completed",
34623902f98SJames Seo  *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
34723902f98SJames Seo  *     uint32 Status;
34823902f98SJames Seo  *   };
34923902f98SJames Seo  */
35023902f98SJames Seo struct hp_wmi_event {
35123902f98SJames Seo 	const char *name;
35223902f98SJames Seo 	const char *description;
35323902f98SJames Seo 	u32 category;
35423902f98SJames Seo };
35523902f98SJames Seo 
35623902f98SJames Seo /*
35723902f98SJames Seo  * struct hp_wmi_info - sensor info
35823902f98SJames Seo  * @nsensor: numeric sensor properties
35923902f98SJames Seo  * @instance: its WMI instance number
36023902f98SJames Seo  * @state: pointer to driver state
36123902f98SJames Seo  * @has_alarm: whether sensor has an alarm flag
36223902f98SJames Seo  * @alarm: alarm flag
36323902f98SJames Seo  * @type: its hwmon sensor type
36423902f98SJames Seo  * @cached_val: current sensor reading value, scaled for hwmon
36523902f98SJames Seo  * @last_updated: when these readings were last updated
36623902f98SJames Seo  */
36723902f98SJames Seo struct hp_wmi_info {
36823902f98SJames Seo 	struct hp_wmi_numeric_sensor nsensor;
36923902f98SJames Seo 	u8 instance;
37023902f98SJames Seo 	void *state;			/* void *: Avoid forward declaration. */
37123902f98SJames Seo 	bool has_alarm;
37223902f98SJames Seo 	bool alarm;
37323902f98SJames Seo 	enum hwmon_sensor_types type;
37423902f98SJames Seo 	long cached_val;
37523902f98SJames Seo 	unsigned long last_updated;	/* In jiffies. */
37623902f98SJames Seo 
37723902f98SJames Seo };
37823902f98SJames Seo 
37923902f98SJames Seo /*
38023902f98SJames Seo  * struct hp_wmi_sensors - driver state
38123902f98SJames Seo  * @wdev: pointer to the parent WMI device
38223902f98SJames Seo  * @info_map: sensor info structs by hwmon type and channel number
38323902f98SJames Seo  * @channel_count: count of hwmon channels by hwmon type
38423902f98SJames Seo  * @has_intrusion: whether an intrusion sensor is present
38523902f98SJames Seo  * @intrusion: intrusion flag
38623902f98SJames Seo  * @lock: mutex to lock polling WMI and changes to driver state
38723902f98SJames Seo  */
38823902f98SJames Seo struct hp_wmi_sensors {
38923902f98SJames Seo 	struct wmi_device *wdev;
39023902f98SJames Seo 	struct hp_wmi_info **info_map[hwmon_max];
39123902f98SJames Seo 	u8 channel_count[hwmon_max];
39223902f98SJames Seo 	bool has_intrusion;
39323902f98SJames Seo 	bool intrusion;
39423902f98SJames Seo 
39523902f98SJames Seo 	struct mutex lock;	/* Lock polling WMI and driver state changes. */
39623902f98SJames Seo };
39723902f98SJames Seo 
39823902f98SJames Seo /* hp_wmi_strdup - devm_kstrdup, but length-limited */
39923902f98SJames Seo static char *hp_wmi_strdup(struct device *dev, const char *src)
40023902f98SJames Seo {
40123902f98SJames Seo 	char *dst;
40223902f98SJames Seo 	size_t len;
40323902f98SJames Seo 
40423902f98SJames Seo 	len = strnlen(src, HP_WMI_MAX_STR_SIZE - 1);
40523902f98SJames Seo 
40623902f98SJames Seo 	dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
40723902f98SJames Seo 	if (!dst)
40823902f98SJames Seo 		return NULL;
40923902f98SJames Seo 
41023902f98SJames Seo 	strscpy(dst, src, len + 1);
41123902f98SJames Seo 
41223902f98SJames Seo 	return dst;
41323902f98SJames Seo }
41423902f98SJames Seo 
41523902f98SJames Seo /*
41623902f98SJames Seo  * hp_wmi_get_wobj - poll WMI for a WMI object instance
41723902f98SJames Seo  * @guid: WMI object GUID
41823902f98SJames Seo  * @instance: WMI object instance number
41923902f98SJames Seo  *
42023902f98SJames Seo  * Returns a new WMI object instance on success, or NULL on error.
42123902f98SJames Seo  * Caller must kfree() the result.
42223902f98SJames Seo  */
42323902f98SJames Seo static union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance)
42423902f98SJames Seo {
42523902f98SJames Seo 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
42623902f98SJames Seo 	acpi_status err;
42723902f98SJames Seo 
42823902f98SJames Seo 	err = wmi_query_block(guid, instance, &out);
42923902f98SJames Seo 	if (ACPI_FAILURE(err))
43023902f98SJames Seo 		return NULL;
43123902f98SJames Seo 
43223902f98SJames Seo 	return out.pointer;
43323902f98SJames Seo }
43423902f98SJames Seo 
43523902f98SJames Seo /* hp_wmi_wobj_instance_count - find count of WMI object instances */
43623902f98SJames Seo static u8 hp_wmi_wobj_instance_count(const char *guid)
43723902f98SJames Seo {
43823902f98SJames Seo 	u8 hi = HP_WMI_MAX_INSTANCES;
43923902f98SJames Seo 	union acpi_object *wobj;
44023902f98SJames Seo 	u8 lo = 0;
44123902f98SJames Seo 	u8 mid;
44223902f98SJames Seo 
44323902f98SJames Seo 	while (lo < hi) {
44423902f98SJames Seo 		mid = (lo + hi) / 2;
44523902f98SJames Seo 
44623902f98SJames Seo 		wobj = hp_wmi_get_wobj(guid, mid);
44723902f98SJames Seo 		if (!wobj) {
44823902f98SJames Seo 			hi = mid;
44923902f98SJames Seo 			continue;
45023902f98SJames Seo 		}
45123902f98SJames Seo 
45223902f98SJames Seo 		lo = mid + 1;
45323902f98SJames Seo 		kfree(wobj);
45423902f98SJames Seo 	}
45523902f98SJames Seo 
45623902f98SJames Seo 	return lo;
45723902f98SJames Seo }
45823902f98SJames Seo 
45923902f98SJames Seo static int check_wobj(const union acpi_object *wobj,
46023902f98SJames Seo 		      const acpi_object_type property_map[], int last_prop)
46123902f98SJames Seo {
46223902f98SJames Seo 	acpi_object_type type = wobj->type;
46323902f98SJames Seo 	acpi_object_type valid_type;
46423902f98SJames Seo 	union acpi_object *elements;
46523902f98SJames Seo 	u32 elem_count;
46623902f98SJames Seo 	int prop;
46723902f98SJames Seo 
46823902f98SJames Seo 	if (type != ACPI_TYPE_PACKAGE)
46923902f98SJames Seo 		return -EINVAL;
47023902f98SJames Seo 
47123902f98SJames Seo 	elem_count = wobj->package.count;
47223902f98SJames Seo 	if (elem_count != last_prop + 1)
47323902f98SJames Seo 		return -EINVAL;
47423902f98SJames Seo 
47523902f98SJames Seo 	elements = wobj->package.elements;
47623902f98SJames Seo 	for (prop = 0; prop <= last_prop; prop++) {
47723902f98SJames Seo 		type = elements[prop].type;
47823902f98SJames Seo 		valid_type = property_map[prop];
47923902f98SJames Seo 		if (type != valid_type)
48023902f98SJames Seo 			return -EINVAL;
48123902f98SJames Seo 	}
48223902f98SJames Seo 
48323902f98SJames Seo 	return 0;
48423902f98SJames Seo }
48523902f98SJames Seo 
48623902f98SJames Seo static int extract_acpi_value(struct device *dev,
48723902f98SJames Seo 			      union acpi_object *element,
48823902f98SJames Seo 			      acpi_object_type type,
48923902f98SJames Seo 			      u32 *out_value, char **out_string)
49023902f98SJames Seo {
49123902f98SJames Seo 	switch (type) {
49223902f98SJames Seo 	case ACPI_TYPE_INTEGER:
49323902f98SJames Seo 		*out_value = element->integer.value;
49423902f98SJames Seo 		break;
49523902f98SJames Seo 
49623902f98SJames Seo 	case ACPI_TYPE_STRING:
49723902f98SJames Seo 		*out_string = hp_wmi_strdup(dev, strim(element->string.pointer));
49823902f98SJames Seo 		if (!*out_string)
49923902f98SJames Seo 			return -ENOMEM;
50023902f98SJames Seo 		break;
50123902f98SJames Seo 
50223902f98SJames Seo 	default:
50323902f98SJames Seo 		return -EINVAL;
50423902f98SJames Seo 	}
50523902f98SJames Seo 
50623902f98SJames Seo 	return 0;
50723902f98SJames Seo }
50823902f98SJames Seo 
50923902f98SJames Seo /*
51023902f98SJames Seo  * check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance
51123902f98SJames Seo  * @wobj: pointer to WMI object instance to check
51223902f98SJames Seo  * @out_size: out pointer to count of possible states
51323902f98SJames Seo  * @out_is_new: out pointer to whether this is a "new" variant object
51423902f98SJames Seo  *
51523902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
51623902f98SJames Seo  */
51723902f98SJames Seo static int check_numeric_sensor_wobj(const union acpi_object *wobj,
51823902f98SJames Seo 				     u8 *out_size, bool *out_is_new)
51923902f98SJames Seo {
52023902f98SJames Seo 	acpi_object_type type = wobj->type;
52123902f98SJames Seo 	int prop = HP_WMI_PROPERTY_NAME;
52223902f98SJames Seo 	acpi_object_type valid_type;
52323902f98SJames Seo 	union acpi_object *elements;
52423902f98SJames Seo 	u32 elem_count;
52523902f98SJames Seo 	int last_prop;
52623902f98SJames Seo 	bool is_new;
52723902f98SJames Seo 	u8 count;
52823902f98SJames Seo 	u32 j;
52923902f98SJames Seo 	u32 i;
53023902f98SJames Seo 
53123902f98SJames Seo 	if (type != ACPI_TYPE_PACKAGE)
53223902f98SJames Seo 		return -EINVAL;
53323902f98SJames Seo 
53423902f98SJames Seo 	/*
53523902f98SJames Seo 	 * elements is a variable-length array of ACPI objects, one for
53623902f98SJames Seo 	 * each property of the WMI object instance, except that the
53723902f98SJames Seo 	 * strings in PossibleStates[] are flattened into this array
53823902f98SJames Seo 	 * as if each individual string were a property by itself.
53923902f98SJames Seo 	 */
54023902f98SJames Seo 	elements = wobj->package.elements;
54123902f98SJames Seo 
54223902f98SJames Seo 	elem_count = wobj->package.count;
54323902f98SJames Seo 	if (elem_count <= HP_WMI_PROPERTY_SIZE ||
54423902f98SJames Seo 	    elem_count > HP_WMI_MAX_PROPERTIES)
54523902f98SJames Seo 		return -EINVAL;
54623902f98SJames Seo 
54723902f98SJames Seo 	type = elements[HP_WMI_PROPERTY_SIZE].type;
54823902f98SJames Seo 	switch (type) {
54923902f98SJames Seo 	case ACPI_TYPE_INTEGER:
55023902f98SJames Seo 		is_new = true;
55123902f98SJames Seo 		last_prop = HP_WMI_PROPERTY_RATE_UNITS;
55223902f98SJames Seo 		break;
55323902f98SJames Seo 
55423902f98SJames Seo 	case ACPI_TYPE_STRING:
55523902f98SJames Seo 		is_new = false;
55623902f98SJames Seo 		last_prop = HP_WMI_PROPERTY_CURRENT_READING;
55723902f98SJames Seo 		break;
55823902f98SJames Seo 
55923902f98SJames Seo 	default:
56023902f98SJames Seo 		return -EINVAL;
56123902f98SJames Seo 	}
56223902f98SJames Seo 
56323902f98SJames Seo 	/*
56423902f98SJames Seo 	 * In general, the count of PossibleStates[] must be > 0.
56523902f98SJames Seo 	 * Also, the old variant lacks the Size property, so we may need to
56623902f98SJames Seo 	 * reduce the value of last_prop by 1 when doing arithmetic with it.
56723902f98SJames Seo 	 */
56823902f98SJames Seo 	if (elem_count < last_prop - !is_new + 1)
56923902f98SJames Seo 		return -EINVAL;
57023902f98SJames Seo 
57123902f98SJames Seo 	count = elem_count - (last_prop - !is_new);
57223902f98SJames Seo 
57323902f98SJames Seo 	for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) {
57423902f98SJames Seo 		type = elements[i].type;
57523902f98SJames Seo 		valid_type = hp_wmi_property_map[prop];
57623902f98SJames Seo 		if (type != valid_type)
57723902f98SJames Seo 			return -EINVAL;
57823902f98SJames Seo 
57923902f98SJames Seo 		switch (prop) {
58023902f98SJames Seo 		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
58123902f98SJames Seo 			/* Old variant: CurrentState follows OperationalStatus. */
58223902f98SJames Seo 			if (!is_new)
58323902f98SJames Seo 				prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
58423902f98SJames Seo 			break;
58523902f98SJames Seo 
58623902f98SJames Seo 		case HP_WMI_PROPERTY_SIZE:
58723902f98SJames Seo 			/* New variant: Size == count of PossibleStates[]. */
58823902f98SJames Seo 			if (count != elements[i].integer.value)
58923902f98SJames Seo 				return -EINVAL;
59023902f98SJames Seo 			break;
59123902f98SJames Seo 
59223902f98SJames Seo 		case HP_WMI_PROPERTY_POSSIBLE_STATES:
59323902f98SJames Seo 			/* PossibleStates[0] has already been type-checked. */
59423902f98SJames Seo 			for (j = 0; i + 1 < elem_count && j + 1 < count; j++) {
59523902f98SJames Seo 				type = elements[++i].type;
59623902f98SJames Seo 				if (type != valid_type)
59723902f98SJames Seo 					return -EINVAL;
59823902f98SJames Seo 			}
59923902f98SJames Seo 
60023902f98SJames Seo 			/* Old variant: BaseUnits follows PossibleStates[]. */
60123902f98SJames Seo 			if (!is_new)
60223902f98SJames Seo 				prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
60323902f98SJames Seo 			break;
60423902f98SJames Seo 
60523902f98SJames Seo 		case HP_WMI_PROPERTY_CURRENT_STATE:
60623902f98SJames Seo 			/* Old variant: PossibleStates[] follows CurrentState. */
60723902f98SJames Seo 			if (!is_new)
60823902f98SJames Seo 				prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
60923902f98SJames Seo 			break;
61023902f98SJames Seo 		}
61123902f98SJames Seo 	}
61223902f98SJames Seo 
61323902f98SJames Seo 	if (prop != last_prop + 1)
61423902f98SJames Seo 		return -EINVAL;
61523902f98SJames Seo 
61623902f98SJames Seo 	*out_size = count;
61723902f98SJames Seo 	*out_is_new = is_new;
61823902f98SJames Seo 
61923902f98SJames Seo 	return 0;
62023902f98SJames Seo }
62123902f98SJames Seo 
62223902f98SJames Seo static int
62323902f98SJames Seo numeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor)
62423902f98SJames Seo {
62523902f98SJames Seo 	u32 operational_status = nsensor->operational_status;
62623902f98SJames Seo 
62723902f98SJames Seo 	return operational_status != HP_WMI_STATUS_NO_CONTACT;
62823902f98SJames Seo }
62923902f98SJames Seo 
63023902f98SJames Seo static int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor)
63123902f98SJames Seo {
63223902f98SJames Seo 	u32 operational_status = nsensor->operational_status;
63323902f98SJames Seo 
63423902f98SJames Seo 	switch (operational_status) {
63523902f98SJames Seo 	case HP_WMI_STATUS_DEGRADED:
63623902f98SJames Seo 	case HP_WMI_STATUS_STRESSED:		/* e.g. Overload, overtemp. */
63723902f98SJames Seo 	case HP_WMI_STATUS_PREDICTIVE_FAILURE:	/* e.g. Fan removed. */
63823902f98SJames Seo 	case HP_WMI_STATUS_ERROR:
63923902f98SJames Seo 	case HP_WMI_STATUS_NON_RECOVERABLE_ERROR:
64023902f98SJames Seo 	case HP_WMI_STATUS_NO_CONTACT:
64123902f98SJames Seo 	case HP_WMI_STATUS_LOST_COMMUNICATION:
64223902f98SJames Seo 	case HP_WMI_STATUS_ABORTED:
64323902f98SJames Seo 	case HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR:
64423902f98SJames Seo 
64523902f98SJames Seo 	/* Assume combination by addition; bitwise OR doesn't make sense. */
64623902f98SJames Seo 	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_DEGRADED:
64723902f98SJames Seo 	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_ERROR:
64823902f98SJames Seo 		return true;
64923902f98SJames Seo 	}
65023902f98SJames Seo 
65123902f98SJames Seo 	return false;
65223902f98SJames Seo }
65323902f98SJames Seo 
65423902f98SJames Seo /* scale_numeric_sensor - scale sensor reading for hwmon */
65523902f98SJames Seo static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
65623902f98SJames Seo {
65723902f98SJames Seo 	u32 current_reading = nsensor->current_reading;
65823902f98SJames Seo 	s32 unit_modifier = nsensor->unit_modifier;
65923902f98SJames Seo 	u32 sensor_type = nsensor->sensor_type;
66023902f98SJames Seo 	u32 base_units = nsensor->base_units;
66123902f98SJames Seo 	s32 target_modifier;
66223902f98SJames Seo 	long val;
66323902f98SJames Seo 
66423902f98SJames Seo 	/* Fan readings are in RPM units; others are in milliunits. */
66523902f98SJames Seo 	target_modifier = sensor_type == HP_WMI_TYPE_AIR_FLOW ? 0 : -3;
66623902f98SJames Seo 
66723902f98SJames Seo 	val = current_reading;
66823902f98SJames Seo 
66923902f98SJames Seo 	for (; unit_modifier < target_modifier; unit_modifier++)
67023902f98SJames Seo 		val = DIV_ROUND_CLOSEST(val, 10);
67123902f98SJames Seo 
67223902f98SJames Seo 	for (; unit_modifier > target_modifier; unit_modifier--) {
67323902f98SJames Seo 		if (val > LONG_MAX / 10) {
67423902f98SJames Seo 			val = LONG_MAX;
67523902f98SJames Seo 			break;
67623902f98SJames Seo 		}
67723902f98SJames Seo 		val *= 10;
67823902f98SJames Seo 	}
67923902f98SJames Seo 
68023902f98SJames Seo 	if (sensor_type == HP_WMI_TYPE_TEMPERATURE) {
68123902f98SJames Seo 		switch (base_units) {
68223902f98SJames Seo 		case HP_WMI_UNITS_DEGREES_F:
68323902f98SJames Seo 			val -= MILLI * 32;
68423902f98SJames Seo 			val = val <= LONG_MAX / 5 ?
68523902f98SJames Seo 				      DIV_ROUND_CLOSEST(val * 5, 9) :
68623902f98SJames Seo 				      DIV_ROUND_CLOSEST(val, 9) * 5;
68723902f98SJames Seo 			break;
68823902f98SJames Seo 
68923902f98SJames Seo 		case HP_WMI_UNITS_DEGREES_K:
69023902f98SJames Seo 			val = milli_kelvin_to_millicelsius(val);
69123902f98SJames Seo 			break;
69223902f98SJames Seo 		}
69323902f98SJames Seo 	}
69423902f98SJames Seo 
69523902f98SJames Seo 	return val;
69623902f98SJames Seo }
69723902f98SJames Seo 
69823902f98SJames Seo /*
69923902f98SJames Seo  * classify_numeric_sensor - classify a numeric sensor
70023902f98SJames Seo  * @nsensor: pointer to numeric sensor struct
70123902f98SJames Seo  *
70223902f98SJames Seo  * Returns an enum hp_wmi_type value on success,
70323902f98SJames Seo  * or a negative value if the sensor type is unsupported.
70423902f98SJames Seo  */
70523902f98SJames Seo static int classify_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
70623902f98SJames Seo {
70723902f98SJames Seo 	u32 sensor_type = nsensor->sensor_type;
70823902f98SJames Seo 	u32 base_units = nsensor->base_units;
70923902f98SJames Seo 	const char *name = nsensor->name;
71023902f98SJames Seo 
71123902f98SJames Seo 	switch (sensor_type) {
71223902f98SJames Seo 	case HP_WMI_TYPE_TEMPERATURE:
71323902f98SJames Seo 		/*
71423902f98SJames Seo 		 * Some systems have sensors named "X Thermal Index" in "Other"
71523902f98SJames Seo 		 * units. Tested CPU sensor examples were found to be in °C,
71623902f98SJames Seo 		 * albeit perhaps "differently" accurate; e.g. readings were
71723902f98SJames Seo 		 * reliably -6°C vs. coretemp on a HP Compaq Elite 8300, and
71823902f98SJames Seo 		 * +8°C on an EliteOne G1 800. But this is still within the
71923902f98SJames Seo 		 * realm of plausibility for cheaply implemented motherboard
72023902f98SJames Seo 		 * sensors, and chassis readings were about as expected.
72123902f98SJames Seo 		 */
72223902f98SJames Seo 		if ((base_units == HP_WMI_UNITS_OTHER &&
72323902f98SJames Seo 		     strstr(name, HP_WMI_PATTERN_TEMP_SENSOR)) ||
72423902f98SJames Seo 		    base_units == HP_WMI_UNITS_DEGREES_C ||
72523902f98SJames Seo 		    base_units == HP_WMI_UNITS_DEGREES_F ||
72623902f98SJames Seo 		    base_units == HP_WMI_UNITS_DEGREES_K)
72723902f98SJames Seo 			return HP_WMI_TYPE_TEMPERATURE;
72823902f98SJames Seo 		break;
72923902f98SJames Seo 
73023902f98SJames Seo 	case HP_WMI_TYPE_VOLTAGE:
73123902f98SJames Seo 		if (base_units == HP_WMI_UNITS_VOLTS)
73223902f98SJames Seo 			return HP_WMI_TYPE_VOLTAGE;
73323902f98SJames Seo 		break;
73423902f98SJames Seo 
73523902f98SJames Seo 	case HP_WMI_TYPE_CURRENT:
73623902f98SJames Seo 		if (base_units == HP_WMI_UNITS_AMPS)
73723902f98SJames Seo 			return HP_WMI_TYPE_CURRENT;
73823902f98SJames Seo 		break;
73923902f98SJames Seo 
74023902f98SJames Seo 	case HP_WMI_TYPE_AIR_FLOW:
74123902f98SJames Seo 		/*
74223902f98SJames Seo 		 * Strangely, HP considers fan RPM sensor type to be
74323902f98SJames Seo 		 * "Air Flow" instead of the more intuitive "Tachometer".
74423902f98SJames Seo 		 */
74523902f98SJames Seo 		if (base_units == HP_WMI_UNITS_RPM)
74623902f98SJames Seo 			return HP_WMI_TYPE_AIR_FLOW;
74723902f98SJames Seo 		break;
74823902f98SJames Seo 	}
74923902f98SJames Seo 
75023902f98SJames Seo 	return -EINVAL;
75123902f98SJames Seo }
75223902f98SJames Seo 
75323902f98SJames Seo static int
75423902f98SJames Seo populate_numeric_sensor_from_wobj(struct device *dev,
75523902f98SJames Seo 				  struct hp_wmi_numeric_sensor *nsensor,
75623902f98SJames Seo 				  union acpi_object *wobj, bool *out_is_new)
75723902f98SJames Seo {
75823902f98SJames Seo 	int last_prop = HP_WMI_PROPERTY_RATE_UNITS;
75923902f98SJames Seo 	int prop = HP_WMI_PROPERTY_NAME;
76023902f98SJames Seo 	const char **possible_states;
76123902f98SJames Seo 	union acpi_object *element;
76223902f98SJames Seo 	acpi_object_type type;
76323902f98SJames Seo 	char *string;
76423902f98SJames Seo 	bool is_new;
76523902f98SJames Seo 	u32 value;
76623902f98SJames Seo 	u8 size;
76723902f98SJames Seo 	int err;
76823902f98SJames Seo 
76923902f98SJames Seo 	err = check_numeric_sensor_wobj(wobj, &size, &is_new);
77023902f98SJames Seo 	if (err)
77123902f98SJames Seo 		return err;
77223902f98SJames Seo 
77323902f98SJames Seo 	possible_states = devm_kcalloc(dev, size, sizeof(*possible_states),
77423902f98SJames Seo 				       GFP_KERNEL);
77523902f98SJames Seo 	if (!possible_states)
77623902f98SJames Seo 		return -ENOMEM;
77723902f98SJames Seo 
77823902f98SJames Seo 	element = wobj->package.elements;
77923902f98SJames Seo 	nsensor->possible_states = possible_states;
78023902f98SJames Seo 	nsensor->size = size;
78123902f98SJames Seo 
78223902f98SJames Seo 	if (!is_new)
78323902f98SJames Seo 		last_prop = HP_WMI_PROPERTY_CURRENT_READING;
78423902f98SJames Seo 
78523902f98SJames Seo 	for (; prop <= last_prop; prop++) {
78623902f98SJames Seo 		type = hp_wmi_property_map[prop];
78723902f98SJames Seo 
78823902f98SJames Seo 		err = extract_acpi_value(dev, element, type, &value, &string);
78923902f98SJames Seo 		if (err)
79023902f98SJames Seo 			return err;
79123902f98SJames Seo 
79223902f98SJames Seo 		element++;
79323902f98SJames Seo 
79423902f98SJames Seo 		switch (prop) {
79523902f98SJames Seo 		case HP_WMI_PROPERTY_NAME:
79623902f98SJames Seo 			nsensor->name = string;
79723902f98SJames Seo 			break;
79823902f98SJames Seo 
79923902f98SJames Seo 		case HP_WMI_PROPERTY_DESCRIPTION:
80023902f98SJames Seo 			nsensor->description = string;
80123902f98SJames Seo 			break;
80223902f98SJames Seo 
80323902f98SJames Seo 		case HP_WMI_PROPERTY_SENSOR_TYPE:
80423902f98SJames Seo 			if (value > HP_WMI_TYPE_AIR_FLOW)
80523902f98SJames Seo 				return -EINVAL;
80623902f98SJames Seo 
80723902f98SJames Seo 			nsensor->sensor_type = value;
80823902f98SJames Seo 			break;
80923902f98SJames Seo 
81023902f98SJames Seo 		case HP_WMI_PROPERTY_OTHER_SENSOR_TYPE:
81123902f98SJames Seo 			nsensor->other_sensor_type = string;
81223902f98SJames Seo 			break;
81323902f98SJames Seo 
81423902f98SJames Seo 		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
81523902f98SJames Seo 			nsensor->operational_status = value;
81623902f98SJames Seo 
81723902f98SJames Seo 			/* Old variant: CurrentState follows OperationalStatus. */
81823902f98SJames Seo 			if (!is_new)
81923902f98SJames Seo 				prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
82023902f98SJames Seo 			break;
82123902f98SJames Seo 
82223902f98SJames Seo 		case HP_WMI_PROPERTY_SIZE:
82323902f98SJames Seo 			break;			/* Already set. */
82423902f98SJames Seo 
82523902f98SJames Seo 		case HP_WMI_PROPERTY_POSSIBLE_STATES:
82623902f98SJames Seo 			*possible_states++ = string;
82723902f98SJames Seo 			if (--size)
82823902f98SJames Seo 				prop--;
82923902f98SJames Seo 
83023902f98SJames Seo 			/* Old variant: BaseUnits follows PossibleStates[]. */
83123902f98SJames Seo 			if (!is_new && !size)
83223902f98SJames Seo 				prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
83323902f98SJames Seo 			break;
83423902f98SJames Seo 
83523902f98SJames Seo 		case HP_WMI_PROPERTY_CURRENT_STATE:
83623902f98SJames Seo 			nsensor->current_state = string;
83723902f98SJames Seo 
83823902f98SJames Seo 			/* Old variant: PossibleStates[] follows CurrentState. */
83923902f98SJames Seo 			if (!is_new)
84023902f98SJames Seo 				prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
84123902f98SJames Seo 			break;
84223902f98SJames Seo 
84323902f98SJames Seo 		case HP_WMI_PROPERTY_BASE_UNITS:
84423902f98SJames Seo 			nsensor->base_units = value;
84523902f98SJames Seo 			break;
84623902f98SJames Seo 
84723902f98SJames Seo 		case HP_WMI_PROPERTY_UNIT_MODIFIER:
84823902f98SJames Seo 			/* UnitModifier is signed. */
84923902f98SJames Seo 			nsensor->unit_modifier = (s32)value;
85023902f98SJames Seo 			break;
85123902f98SJames Seo 
85223902f98SJames Seo 		case HP_WMI_PROPERTY_CURRENT_READING:
85323902f98SJames Seo 			nsensor->current_reading = value;
85423902f98SJames Seo 			break;
85523902f98SJames Seo 
85623902f98SJames Seo 		case HP_WMI_PROPERTY_RATE_UNITS:
85723902f98SJames Seo 			nsensor->rate_units = value;
85823902f98SJames Seo 			break;
85923902f98SJames Seo 
86023902f98SJames Seo 		default:
86123902f98SJames Seo 			return -EINVAL;
86223902f98SJames Seo 		}
86323902f98SJames Seo 	}
86423902f98SJames Seo 
86523902f98SJames Seo 	*out_is_new = is_new;
86623902f98SJames Seo 
86723902f98SJames Seo 	return 0;
86823902f98SJames Seo }
86923902f98SJames Seo 
87023902f98SJames Seo /* update_numeric_sensor_from_wobj - update fungible sensor properties */
87123902f98SJames Seo static void
87223902f98SJames Seo update_numeric_sensor_from_wobj(struct device *dev,
87323902f98SJames Seo 				struct hp_wmi_numeric_sensor *nsensor,
87423902f98SJames Seo 				const union acpi_object *wobj)
87523902f98SJames Seo {
87623902f98SJames Seo 	const union acpi_object *elements;
87723902f98SJames Seo 	const union acpi_object *element;
87823902f98SJames Seo 	const char *string;
87923902f98SJames Seo 	bool is_new;
88023902f98SJames Seo 	int offset;
88123902f98SJames Seo 	u8 size;
88223902f98SJames Seo 	int err;
88323902f98SJames Seo 
88423902f98SJames Seo 	err = check_numeric_sensor_wobj(wobj, &size, &is_new);
88523902f98SJames Seo 	if (err)
88623902f98SJames Seo 		return;
88723902f98SJames Seo 
88823902f98SJames Seo 	elements = wobj->package.elements;
88923902f98SJames Seo 
89023902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_OPERATIONAL_STATUS];
89123902f98SJames Seo 	nsensor->operational_status = element->integer.value;
89223902f98SJames Seo 
89323902f98SJames Seo 	/*
89423902f98SJames Seo 	 * In general, an index offset is needed after PossibleStates[0].
89523902f98SJames Seo 	 * On a new variant, CurrentState is after PossibleStates[]. This is
89623902f98SJames Seo 	 * not the case on an old variant, but we still need to offset the
89723902f98SJames Seo 	 * read because CurrentState is where Size would be on a new variant.
89823902f98SJames Seo 	 */
89923902f98SJames Seo 	offset = is_new ? size - 1 : -2;
90023902f98SJames Seo 
90123902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
90223902f98SJames Seo 	string = strim(element->string.pointer);
90323902f98SJames Seo 
90423902f98SJames Seo 	if (strcmp(string, nsensor->current_state)) {
90523902f98SJames Seo 		devm_kfree(dev, nsensor->current_state);
90623902f98SJames Seo 		nsensor->current_state = hp_wmi_strdup(dev, string);
90723902f98SJames Seo 	}
90823902f98SJames Seo 
90923902f98SJames Seo 	/* Old variant: -2 (not -1) because it lacks the Size property. */
91023902f98SJames Seo 	if (!is_new)
91123902f98SJames Seo 		offset = (int)size - 2;	/* size is > 0, i.e. may be 1. */
91223902f98SJames Seo 
91323902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_UNIT_MODIFIER + offset];
91423902f98SJames Seo 	nsensor->unit_modifier = (s32)element->integer.value;
91523902f98SJames Seo 
91623902f98SJames Seo 	element = &elements[HP_WMI_PROPERTY_CURRENT_READING + offset];
91723902f98SJames Seo 	nsensor->current_reading = element->integer.value;
91823902f98SJames Seo }
91923902f98SJames Seo 
92023902f98SJames Seo /*
92123902f98SJames Seo  * check_platform_events_wobj - validate a HPBIOS_PlatformEvents instance
92223902f98SJames Seo  * @wobj: pointer to WMI object instance to check
92323902f98SJames Seo  *
92423902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
92523902f98SJames Seo  */
92623902f98SJames Seo static int check_platform_events_wobj(const union acpi_object *wobj)
92723902f98SJames Seo {
92823902f98SJames Seo 	return check_wobj(wobj, hp_wmi_platform_events_property_map,
92923902f98SJames Seo 			  HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS);
93023902f98SJames Seo }
93123902f98SJames Seo 
93223902f98SJames Seo static int
93323902f98SJames Seo populate_platform_events_from_wobj(struct device *dev,
93423902f98SJames Seo 				   struct hp_wmi_platform_events *pevents,
93523902f98SJames Seo 				   union acpi_object *wobj)
93623902f98SJames Seo {
93723902f98SJames Seo 	int last_prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS;
93823902f98SJames Seo 	int prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME;
93923902f98SJames Seo 	union acpi_object *element;
94023902f98SJames Seo 	acpi_object_type type;
94123902f98SJames Seo 	char *string;
94223902f98SJames Seo 	u32 value;
94323902f98SJames Seo 	int err;
94423902f98SJames Seo 
94523902f98SJames Seo 	err = check_platform_events_wobj(wobj);
94623902f98SJames Seo 	if (err)
94723902f98SJames Seo 		return err;
94823902f98SJames Seo 
94923902f98SJames Seo 	element = wobj->package.elements;
95023902f98SJames Seo 
95123902f98SJames Seo 	for (; prop <= last_prop; prop++, element++) {
95223902f98SJames Seo 		type = hp_wmi_platform_events_property_map[prop];
95323902f98SJames Seo 
95423902f98SJames Seo 		err = extract_acpi_value(dev, element, type, &value, &string);
95523902f98SJames Seo 		if (err)
95623902f98SJames Seo 			return err;
95723902f98SJames Seo 
95823902f98SJames Seo 		switch (prop) {
95923902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME:
96023902f98SJames Seo 			pevents->name = string;
96123902f98SJames Seo 			break;
96223902f98SJames Seo 
96323902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION:
96423902f98SJames Seo 			pevents->description = string;
96523902f98SJames Seo 			break;
96623902f98SJames Seo 
96723902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE:
96823902f98SJames Seo 			if (strcasecmp(HP_WMI_EVENT_NAMESPACE, string))
96923902f98SJames Seo 				return -EINVAL;
97023902f98SJames Seo 
97123902f98SJames Seo 			pevents->source_namespace = string;
97223902f98SJames Seo 			break;
97323902f98SJames Seo 
97423902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS:
97523902f98SJames Seo 			if (strcasecmp(HP_WMI_EVENT_CLASS, string))
97623902f98SJames Seo 				return -EINVAL;
97723902f98SJames Seo 
97823902f98SJames Seo 			pevents->source_class = string;
97923902f98SJames Seo 			break;
98023902f98SJames Seo 
98123902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY:
98223902f98SJames Seo 			pevents->category = value;
98323902f98SJames Seo 			break;
98423902f98SJames Seo 
98523902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY:
98623902f98SJames Seo 			pevents->possible_severity = value;
98723902f98SJames Seo 			break;
98823902f98SJames Seo 
98923902f98SJames Seo 		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS:
99023902f98SJames Seo 			pevents->possible_status = value;
99123902f98SJames Seo 			break;
99223902f98SJames Seo 
99323902f98SJames Seo 		default:
99423902f98SJames Seo 			return -EINVAL;
99523902f98SJames Seo 		}
99623902f98SJames Seo 	}
99723902f98SJames Seo 
99823902f98SJames Seo 	return 0;
99923902f98SJames Seo }
100023902f98SJames Seo 
100123902f98SJames Seo /*
100223902f98SJames Seo  * check_event_wobj - validate a HPBIOS_BIOSEvent instance
100323902f98SJames Seo  * @wobj: pointer to WMI object instance to check
100423902f98SJames Seo  *
100523902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
100623902f98SJames Seo  */
100723902f98SJames Seo static int check_event_wobj(const union acpi_object *wobj)
100823902f98SJames Seo {
100923902f98SJames Seo 	return check_wobj(wobj, hp_wmi_event_property_map,
101023902f98SJames Seo 			  HP_WMI_EVENT_PROPERTY_STATUS);
101123902f98SJames Seo }
101223902f98SJames Seo 
101323902f98SJames Seo static int populate_event_from_wobj(struct hp_wmi_event *event,
101423902f98SJames Seo 				    union acpi_object *wobj)
101523902f98SJames Seo {
101623902f98SJames Seo 	int prop = HP_WMI_EVENT_PROPERTY_NAME;
101723902f98SJames Seo 	union acpi_object *element;
101823902f98SJames Seo 	int err;
101923902f98SJames Seo 
102023902f98SJames Seo 	err = check_event_wobj(wobj);
102123902f98SJames Seo 	if (err)
102223902f98SJames Seo 		return err;
102323902f98SJames Seo 
102423902f98SJames Seo 	element = wobj->package.elements;
102523902f98SJames Seo 
102623902f98SJames Seo 	/* Extracted strings are NOT device-managed copies. */
102723902f98SJames Seo 
102823902f98SJames Seo 	for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
102923902f98SJames Seo 		switch (prop) {
103023902f98SJames Seo 		case HP_WMI_EVENT_PROPERTY_NAME:
103123902f98SJames Seo 			event->name = strim(element->string.pointer);
103223902f98SJames Seo 			break;
103323902f98SJames Seo 
103423902f98SJames Seo 		case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
103523902f98SJames Seo 			event->description = strim(element->string.pointer);
103623902f98SJames Seo 			break;
103723902f98SJames Seo 
103823902f98SJames Seo 		case HP_WMI_EVENT_PROPERTY_CATEGORY:
103923902f98SJames Seo 			event->category = element->integer.value;
104023902f98SJames Seo 			break;
104123902f98SJames Seo 
104223902f98SJames Seo 		default:
104323902f98SJames Seo 			return -EINVAL;
104423902f98SJames Seo 		}
104523902f98SJames Seo 	}
104623902f98SJames Seo 
104723902f98SJames Seo 	return 0;
104823902f98SJames Seo }
104923902f98SJames Seo 
105023902f98SJames Seo /*
105123902f98SJames Seo  * classify_event - classify an event
105223902f98SJames Seo  * @name: event name
105323902f98SJames Seo  * @category: event category
105423902f98SJames Seo  *
105523902f98SJames Seo  * Classify instances of both HPBIOS_PlatformEvents and HPBIOS_BIOSEvent from
105623902f98SJames Seo  * property values. Recognition criteria are based on multiple ACPI dumps [3].
105723902f98SJames Seo  *
105823902f98SJames Seo  * Returns an enum hp_wmi_type value on success,
105923902f98SJames Seo  * or a negative value if the event type is unsupported.
106023902f98SJames Seo  */
106123902f98SJames Seo static int classify_event(const char *event_name, u32 category)
106223902f98SJames Seo {
106323902f98SJames Seo 	if (category != HP_WMI_CATEGORY_SENSOR)
106423902f98SJames Seo 		return -EINVAL;
106523902f98SJames Seo 
106623902f98SJames Seo 	/* Fan events have Name "X Stall". */
106723902f98SJames Seo 	if (strstr(event_name, HP_WMI_PATTERN_FAN_ALARM))
106823902f98SJames Seo 		return HP_WMI_TYPE_AIR_FLOW;
106923902f98SJames Seo 
107023902f98SJames Seo 	/* Intrusion events have Name "Hood Intrusion". */
107123902f98SJames Seo 	if (!strcmp(event_name, HP_WMI_PATTERN_INTRUSION_ALARM))
107223902f98SJames Seo 		return HP_WMI_TYPE_INTRUSION;
107323902f98SJames Seo 
107423902f98SJames Seo 	/*
107523902f98SJames Seo 	 * Temperature events have Name either "Thermal Caution" or
107623902f98SJames Seo 	 * "Thermal Critical". Deal only with "Thermal Critical" events.
107723902f98SJames Seo 	 *
107823902f98SJames Seo 	 * "Thermal Caution" events have Status "Stressed", informing us that
107923902f98SJames Seo 	 * the OperationalStatus of the related sensor has become "Stressed".
108023902f98SJames Seo 	 * However, this is already a fault condition that will clear itself
108123902f98SJames Seo 	 * when the sensor recovers, so we have no further interest in them.
108223902f98SJames Seo 	 */
108323902f98SJames Seo 	if (!strcmp(event_name, HP_WMI_PATTERN_TEMP_ALARM))
108423902f98SJames Seo 		return HP_WMI_TYPE_TEMPERATURE;
108523902f98SJames Seo 
108623902f98SJames Seo 	return -EINVAL;
108723902f98SJames Seo }
108823902f98SJames Seo 
108923902f98SJames Seo /*
109023902f98SJames Seo  * interpret_info - interpret sensor for hwmon
109123902f98SJames Seo  * @info: pointer to sensor info struct
109223902f98SJames Seo  *
109323902f98SJames Seo  * Should be called after the numeric sensor member has been updated.
109423902f98SJames Seo  */
109523902f98SJames Seo static void interpret_info(struct hp_wmi_info *info)
109623902f98SJames Seo {
109723902f98SJames Seo 	const struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
109823902f98SJames Seo 
109923902f98SJames Seo 	info->cached_val = scale_numeric_sensor(nsensor);
110023902f98SJames Seo 	info->last_updated = jiffies;
110123902f98SJames Seo }
110223902f98SJames Seo 
110323902f98SJames Seo /*
110423902f98SJames Seo  * hp_wmi_update_info - poll WMI to update sensor info
110523902f98SJames Seo  * @state: pointer to driver state
110623902f98SJames Seo  * @info: pointer to sensor info struct
110723902f98SJames Seo  *
110823902f98SJames Seo  * Returns 0 on success, or a negative error code on error.
110923902f98SJames Seo  */
111023902f98SJames Seo static int hp_wmi_update_info(struct hp_wmi_sensors *state,
111123902f98SJames Seo 			      struct hp_wmi_info *info)
111223902f98SJames Seo {
111323902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
111423902f98SJames Seo 	struct device *dev = &state->wdev->dev;
111523902f98SJames Seo 	const union acpi_object *wobj;
111623902f98SJames Seo 	u8 instance = info->instance;
111723902f98SJames Seo 	int ret = 0;
111823902f98SJames Seo 
111923902f98SJames Seo 	if (time_after(jiffies, info->last_updated + HZ)) {
112023902f98SJames Seo 		mutex_lock(&state->lock);
112123902f98SJames Seo 
112223902f98SJames Seo 		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
112323902f98SJames Seo 		if (!wobj) {
112423902f98SJames Seo 			ret = -EIO;
112523902f98SJames Seo 			goto out_unlock;
112623902f98SJames Seo 		}
112723902f98SJames Seo 
112823902f98SJames Seo 		update_numeric_sensor_from_wobj(dev, nsensor, wobj);
112923902f98SJames Seo 
113023902f98SJames Seo 		interpret_info(info);
113123902f98SJames Seo 
113223902f98SJames Seo 		kfree(wobj);
113323902f98SJames Seo 
113423902f98SJames Seo out_unlock:
113523902f98SJames Seo 		mutex_unlock(&state->lock);
113623902f98SJames Seo 	}
113723902f98SJames Seo 
113823902f98SJames Seo 	return ret;
113923902f98SJames Seo }
114023902f98SJames Seo 
114123902f98SJames Seo static int basic_string_show(struct seq_file *seqf, void *ignored)
114223902f98SJames Seo {
114323902f98SJames Seo 	const char *str = seqf->private;
114423902f98SJames Seo 
114523902f98SJames Seo 	seq_printf(seqf, "%s\n", str);
114623902f98SJames Seo 
114723902f98SJames Seo 	return 0;
114823902f98SJames Seo }
114923902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(basic_string);
115023902f98SJames Seo 
115123902f98SJames Seo static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop)
115223902f98SJames Seo {
115323902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor;
115423902f98SJames Seo 	struct hp_wmi_sensors *state;
115523902f98SJames Seo 	struct hp_wmi_info *info;
115623902f98SJames Seo 	int err;
115723902f98SJames Seo 
115823902f98SJames Seo 	info = seqf->private;
115923902f98SJames Seo 	state = info->state;
116023902f98SJames Seo 	nsensor = &info->nsensor;
116123902f98SJames Seo 
116223902f98SJames Seo 	err = hp_wmi_update_info(state, info);
116323902f98SJames Seo 	if (err)
116423902f98SJames Seo 		return err;
116523902f98SJames Seo 
116623902f98SJames Seo 	switch (prop) {
116723902f98SJames Seo 	case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
116823902f98SJames Seo 		seq_printf(seqf, "%u\n", nsensor->operational_status);
116923902f98SJames Seo 		break;
117023902f98SJames Seo 
117123902f98SJames Seo 	case HP_WMI_PROPERTY_CURRENT_STATE:
117223902f98SJames Seo 		seq_printf(seqf, "%s\n", nsensor->current_state);
117323902f98SJames Seo 		break;
117423902f98SJames Seo 
117523902f98SJames Seo 	case HP_WMI_PROPERTY_UNIT_MODIFIER:
117623902f98SJames Seo 		seq_printf(seqf, "%d\n", nsensor->unit_modifier);
117723902f98SJames Seo 		break;
117823902f98SJames Seo 
117923902f98SJames Seo 	case HP_WMI_PROPERTY_CURRENT_READING:
118023902f98SJames Seo 		seq_printf(seqf, "%u\n", nsensor->current_reading);
118123902f98SJames Seo 		break;
118223902f98SJames Seo 
118323902f98SJames Seo 	default:
118423902f98SJames Seo 		return -EOPNOTSUPP;
118523902f98SJames Seo 	}
118623902f98SJames Seo 
118723902f98SJames Seo 	return 0;
118823902f98SJames Seo }
118923902f98SJames Seo 
119023902f98SJames Seo static int operational_status_show(struct seq_file *seqf, void *ignored)
119123902f98SJames Seo {
119223902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_OPERATIONAL_STATUS);
119323902f98SJames Seo }
119423902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(operational_status);
119523902f98SJames Seo 
119623902f98SJames Seo static int current_state_show(struct seq_file *seqf, void *ignored)
119723902f98SJames Seo {
119823902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_STATE);
119923902f98SJames Seo }
120023902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(current_state);
120123902f98SJames Seo 
120223902f98SJames Seo static int possible_states_show(struct seq_file *seqf, void *ignored)
120323902f98SJames Seo {
120423902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor = seqf->private;
120523902f98SJames Seo 	u8 i;
120623902f98SJames Seo 
120723902f98SJames Seo 	for (i = 0; i < nsensor->size; i++)
120823902f98SJames Seo 		seq_printf(seqf, "%s%s", i ? "," : "",
120923902f98SJames Seo 			   nsensor->possible_states[i]);
121023902f98SJames Seo 
121123902f98SJames Seo 	seq_puts(seqf, "\n");
121223902f98SJames Seo 
121323902f98SJames Seo 	return 0;
121423902f98SJames Seo }
121523902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(possible_states);
121623902f98SJames Seo 
121723902f98SJames Seo static int unit_modifier_show(struct seq_file *seqf, void *ignored)
121823902f98SJames Seo {
121923902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_UNIT_MODIFIER);
122023902f98SJames Seo }
122123902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(unit_modifier);
122223902f98SJames Seo 
122323902f98SJames Seo static int current_reading_show(struct seq_file *seqf, void *ignored)
122423902f98SJames Seo {
122523902f98SJames Seo 	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_READING);
122623902f98SJames Seo }
122723902f98SJames Seo DEFINE_SHOW_ATTRIBUTE(current_reading);
122823902f98SJames Seo 
122923902f98SJames Seo /* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */
123023902f98SJames Seo static void hp_wmi_devm_debugfs_remove(void *res)
123123902f98SJames Seo {
123223902f98SJames Seo 	debugfs_remove_recursive(res);
123323902f98SJames Seo }
123423902f98SJames Seo 
123523902f98SJames Seo /* hp_wmi_debugfs_init - create and populate debugfs directory tree */
123623902f98SJames Seo static void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info,
123723902f98SJames Seo 				struct hp_wmi_platform_events *pevents,
123823902f98SJames Seo 				u8 icount, u8 pcount, bool is_new)
123923902f98SJames Seo {
124023902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor;
124123902f98SJames Seo 	char buf[HP_WMI_MAX_STR_SIZE];
124223902f98SJames Seo 	struct dentry *debugfs;
124323902f98SJames Seo 	struct dentry *entries;
124423902f98SJames Seo 	struct dentry *dir;
124523902f98SJames Seo 	int err;
124623902f98SJames Seo 	u8 i;
124723902f98SJames Seo 
124823902f98SJames Seo 	/* dev_name() gives a not-very-friendly GUID for WMI devices. */
124923902f98SJames Seo 	scnprintf(buf, sizeof(buf), "hp-wmi-sensors-%u", dev->id);
125023902f98SJames Seo 
125123902f98SJames Seo 	debugfs = debugfs_create_dir(buf, NULL);
125223902f98SJames Seo 	if (IS_ERR(debugfs))
125323902f98SJames Seo 		return;
125423902f98SJames Seo 
125523902f98SJames Seo 	err = devm_add_action_or_reset(dev, hp_wmi_devm_debugfs_remove,
125623902f98SJames Seo 				       debugfs);
125723902f98SJames Seo 	if (err)
125823902f98SJames Seo 		return;
125923902f98SJames Seo 
126023902f98SJames Seo 	entries = debugfs_create_dir("sensor", debugfs);
126123902f98SJames Seo 
126223902f98SJames Seo 	for (i = 0; i < icount; i++, info++) {
126323902f98SJames Seo 		nsensor = &info->nsensor;
126423902f98SJames Seo 
126523902f98SJames Seo 		scnprintf(buf, sizeof(buf), "%u", i);
126623902f98SJames Seo 		dir = debugfs_create_dir(buf, entries);
126723902f98SJames Seo 
126823902f98SJames Seo 		debugfs_create_file("name", 0444, dir,
126923902f98SJames Seo 				    (void *)nsensor->name,
127023902f98SJames Seo 				    &basic_string_fops);
127123902f98SJames Seo 
127223902f98SJames Seo 		debugfs_create_file("description", 0444, dir,
127323902f98SJames Seo 				    (void *)nsensor->description,
127423902f98SJames Seo 				    &basic_string_fops);
127523902f98SJames Seo 
127623902f98SJames Seo 		debugfs_create_u32("sensor_type", 0444, dir,
127723902f98SJames Seo 				   &nsensor->sensor_type);
127823902f98SJames Seo 
127923902f98SJames Seo 		debugfs_create_file("other_sensor_type", 0444, dir,
128023902f98SJames Seo 				    (void *)nsensor->other_sensor_type,
128123902f98SJames Seo 				    &basic_string_fops);
128223902f98SJames Seo 
128323902f98SJames Seo 		debugfs_create_file("operational_status", 0444, dir,
128423902f98SJames Seo 				    info, &operational_status_fops);
128523902f98SJames Seo 
128623902f98SJames Seo 		debugfs_create_file("possible_states", 0444, dir,
128723902f98SJames Seo 				    nsensor, &possible_states_fops);
128823902f98SJames Seo 
128923902f98SJames Seo 		debugfs_create_file("current_state", 0444, dir,
129023902f98SJames Seo 				    info, &current_state_fops);
129123902f98SJames Seo 
129223902f98SJames Seo 		debugfs_create_u32("base_units", 0444, dir,
129323902f98SJames Seo 				   &nsensor->base_units);
129423902f98SJames Seo 
129523902f98SJames Seo 		debugfs_create_file("unit_modifier", 0444, dir,
129623902f98SJames Seo 				    info, &unit_modifier_fops);
129723902f98SJames Seo 
129823902f98SJames Seo 		debugfs_create_file("current_reading", 0444, dir,
129923902f98SJames Seo 				    info, &current_reading_fops);
130023902f98SJames Seo 
130123902f98SJames Seo 		if (is_new)
130223902f98SJames Seo 			debugfs_create_u32("rate_units", 0444, dir,
130323902f98SJames Seo 					   &nsensor->rate_units);
130423902f98SJames Seo 	}
130523902f98SJames Seo 
130623902f98SJames Seo 	if (!pcount)
130723902f98SJames Seo 		return;
130823902f98SJames Seo 
130923902f98SJames Seo 	entries = debugfs_create_dir("platform_events", debugfs);
131023902f98SJames Seo 
131123902f98SJames Seo 	for (i = 0; i < pcount; i++, pevents++) {
131223902f98SJames Seo 		scnprintf(buf, sizeof(buf), "%u", i);
131323902f98SJames Seo 		dir = debugfs_create_dir(buf, entries);
131423902f98SJames Seo 
131523902f98SJames Seo 		debugfs_create_file("name", 0444, dir,
131623902f98SJames Seo 				    (void *)pevents->name,
131723902f98SJames Seo 				    &basic_string_fops);
131823902f98SJames Seo 
131923902f98SJames Seo 		debugfs_create_file("description", 0444, dir,
132023902f98SJames Seo 				    (void *)pevents->description,
132123902f98SJames Seo 				    &basic_string_fops);
132223902f98SJames Seo 
132323902f98SJames Seo 		debugfs_create_file("source_namespace", 0444, dir,
132423902f98SJames Seo 				    (void *)pevents->source_namespace,
132523902f98SJames Seo 				    &basic_string_fops);
132623902f98SJames Seo 
132723902f98SJames Seo 		debugfs_create_file("source_class", 0444, dir,
132823902f98SJames Seo 				    (void *)pevents->source_class,
132923902f98SJames Seo 				    &basic_string_fops);
133023902f98SJames Seo 
133123902f98SJames Seo 		debugfs_create_u32("category", 0444, dir,
133223902f98SJames Seo 				   &pevents->category);
133323902f98SJames Seo 
133423902f98SJames Seo 		debugfs_create_u32("possible_severity", 0444, dir,
133523902f98SJames Seo 				   &pevents->possible_severity);
133623902f98SJames Seo 
133723902f98SJames Seo 		debugfs_create_u32("possible_status", 0444, dir,
133823902f98SJames Seo 				   &pevents->possible_status);
133923902f98SJames Seo 	}
134023902f98SJames Seo }
134123902f98SJames Seo 
134223902f98SJames Seo static umode_t hp_wmi_hwmon_is_visible(const void *drvdata,
134323902f98SJames Seo 				       enum hwmon_sensor_types type,
134423902f98SJames Seo 				       u32 attr, int channel)
134523902f98SJames Seo {
134623902f98SJames Seo 	const struct hp_wmi_sensors *state = drvdata;
134723902f98SJames Seo 	const struct hp_wmi_info *info;
134823902f98SJames Seo 
134923902f98SJames Seo 	if (type == hwmon_intrusion)
135023902f98SJames Seo 		return state->has_intrusion ? 0644 : 0;
135123902f98SJames Seo 
135223902f98SJames Seo 	if (!state->info_map[type] || !state->info_map[type][channel])
135323902f98SJames Seo 		return 0;
135423902f98SJames Seo 
135523902f98SJames Seo 	info = state->info_map[type][channel];
135623902f98SJames Seo 
135723902f98SJames Seo 	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
135823902f98SJames Seo 	    (type == hwmon_fan  && attr == hwmon_fan_alarm))
135923902f98SJames Seo 		return info->has_alarm ? 0444 : 0;
136023902f98SJames Seo 
136123902f98SJames Seo 	return 0444;
136223902f98SJames Seo }
136323902f98SJames Seo 
136423902f98SJames Seo static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
136523902f98SJames Seo 			     u32 attr, int channel, long *out_val)
136623902f98SJames Seo {
136723902f98SJames Seo 	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
136823902f98SJames Seo 	const struct hp_wmi_numeric_sensor *nsensor;
136923902f98SJames Seo 	struct hp_wmi_info *info;
137023902f98SJames Seo 	int err;
137123902f98SJames Seo 
137223902f98SJames Seo 	if (type == hwmon_intrusion) {
137323902f98SJames Seo 		*out_val = state->intrusion ? 1 : 0;
137423902f98SJames Seo 
137523902f98SJames Seo 		return 0;
137623902f98SJames Seo 	}
137723902f98SJames Seo 
137823902f98SJames Seo 	info = state->info_map[type][channel];
137923902f98SJames Seo 
138023902f98SJames Seo 	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
138123902f98SJames Seo 	    (type == hwmon_fan  && attr == hwmon_fan_alarm)) {
138223902f98SJames Seo 		*out_val = info->alarm ? 1 : 0;
138323902f98SJames Seo 		info->alarm = false;
138423902f98SJames Seo 
138523902f98SJames Seo 		return 0;
138623902f98SJames Seo 	}
138723902f98SJames Seo 
138823902f98SJames Seo 	nsensor = &info->nsensor;
138923902f98SJames Seo 
139023902f98SJames Seo 	err = hp_wmi_update_info(state, info);
139123902f98SJames Seo 	if (err)
139223902f98SJames Seo 		return err;
139323902f98SJames Seo 
139423902f98SJames Seo 	if ((type == hwmon_temp && attr == hwmon_temp_fault) ||
139523902f98SJames Seo 	    (type == hwmon_fan  && attr == hwmon_fan_fault))
139623902f98SJames Seo 		*out_val = numeric_sensor_has_fault(nsensor);
139723902f98SJames Seo 	else
139823902f98SJames Seo 		*out_val = info->cached_val;
139923902f98SJames Seo 
140023902f98SJames Seo 	return 0;
140123902f98SJames Seo }
140223902f98SJames Seo 
140323902f98SJames Seo static int hp_wmi_hwmon_read_string(struct device *dev,
140423902f98SJames Seo 				    enum hwmon_sensor_types type, u32 attr,
140523902f98SJames Seo 				    int channel, const char **out_str)
140623902f98SJames Seo {
140723902f98SJames Seo 	const struct hp_wmi_sensors *state = dev_get_drvdata(dev);
140823902f98SJames Seo 	const struct hp_wmi_info *info;
140923902f98SJames Seo 
141023902f98SJames Seo 	info = state->info_map[type][channel];
141123902f98SJames Seo 	*out_str = info->nsensor.name;
141223902f98SJames Seo 
141323902f98SJames Seo 	return 0;
141423902f98SJames Seo }
141523902f98SJames Seo 
141623902f98SJames Seo static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
141723902f98SJames Seo 			      u32 attr, int channel, long val)
141823902f98SJames Seo {
141923902f98SJames Seo 	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
142023902f98SJames Seo 
142123902f98SJames Seo 	if (val)
142223902f98SJames Seo 		return -EINVAL;
142323902f98SJames Seo 
142423902f98SJames Seo 	mutex_lock(&state->lock);
142523902f98SJames Seo 
142623902f98SJames Seo 	state->intrusion = false;
142723902f98SJames Seo 
142823902f98SJames Seo 	mutex_unlock(&state->lock);
142923902f98SJames Seo 
143023902f98SJames Seo 	return 0;
143123902f98SJames Seo }
143223902f98SJames Seo 
143323902f98SJames Seo static const struct hwmon_ops hp_wmi_hwmon_ops = {
143423902f98SJames Seo 	.is_visible  = hp_wmi_hwmon_is_visible,
143523902f98SJames Seo 	.read	     = hp_wmi_hwmon_read,
143623902f98SJames Seo 	.read_string = hp_wmi_hwmon_read_string,
143723902f98SJames Seo 	.write	     = hp_wmi_hwmon_write,
143823902f98SJames Seo };
143923902f98SJames Seo 
144023902f98SJames Seo static struct hwmon_chip_info hp_wmi_chip_info = {
144123902f98SJames Seo 	.ops         = &hp_wmi_hwmon_ops,
144223902f98SJames Seo 	.info        = NULL,
144323902f98SJames Seo };
144423902f98SJames Seo 
144523902f98SJames Seo static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state,
144623902f98SJames Seo 					   const char *event_description)
144723902f98SJames Seo {
144823902f98SJames Seo 	struct hp_wmi_info **ptr_info = state->info_map[hwmon_fan];
144923902f98SJames Seo 	u8 fan_count = state->channel_count[hwmon_fan];
145023902f98SJames Seo 	struct hp_wmi_info *info;
145123902f98SJames Seo 	const char *name;
145223902f98SJames Seo 	u8 i;
145323902f98SJames Seo 
145423902f98SJames Seo 	/* Fan event has Description "X Speed". Sensor has Name "X[ Speed]". */
145523902f98SJames Seo 
145623902f98SJames Seo 	for (i = 0; i < fan_count; i++, ptr_info++) {
145723902f98SJames Seo 		info = *ptr_info;
145823902f98SJames Seo 		name = info->nsensor.name;
145923902f98SJames Seo 
146023902f98SJames Seo 		if (strstr(event_description, name))
146123902f98SJames Seo 			return info;
146223902f98SJames Seo 	}
146323902f98SJames Seo 
146423902f98SJames Seo 	return NULL;
146523902f98SJames Seo }
146623902f98SJames Seo 
146723902f98SJames Seo static u8 match_temp_events(struct hp_wmi_sensors *state,
146823902f98SJames Seo 			    const char *event_description,
146923902f98SJames Seo 			    struct hp_wmi_info *temp_info[])
147023902f98SJames Seo {
147123902f98SJames Seo 	struct hp_wmi_info **ptr_info = state->info_map[hwmon_temp];
147223902f98SJames Seo 	u8 temp_count = state->channel_count[hwmon_temp];
147323902f98SJames Seo 	struct hp_wmi_info *info;
147423902f98SJames Seo 	const char *name;
147523902f98SJames Seo 	u8 count = 0;
147623902f98SJames Seo 	bool is_cpu;
147723902f98SJames Seo 	bool is_sys;
147823902f98SJames Seo 	u8 i;
147923902f98SJames Seo 
148023902f98SJames Seo 	/* Description is either "CPU Thermal Index" or "Chassis Thermal Index". */
148123902f98SJames Seo 
148223902f98SJames Seo 	is_cpu = !strcmp(event_description, HP_WMI_PATTERN_CPU_TEMP);
148323902f98SJames Seo 	is_sys = !strcmp(event_description, HP_WMI_PATTERN_SYS_TEMP);
148423902f98SJames Seo 	if (!is_cpu && !is_sys)
148523902f98SJames Seo 		return 0;
148623902f98SJames Seo 
148723902f98SJames Seo 	/*
148823902f98SJames Seo 	 * CPU event: Match one sensor with Name either "CPU Thermal Index" or
148923902f98SJames Seo 	 * "CPU Temperature", or multiple with Name(s) "CPU[#] Temperature".
149023902f98SJames Seo 	 *
149123902f98SJames Seo 	 * Chassis event: Match one sensor with Name either
149223902f98SJames Seo 	 * "Chassis Thermal Index" or "System Ambient Temperature".
149323902f98SJames Seo 	 */
149423902f98SJames Seo 
149523902f98SJames Seo 	for (i = 0; i < temp_count; i++, ptr_info++) {
149623902f98SJames Seo 		info = *ptr_info;
149723902f98SJames Seo 		name = info->nsensor.name;
149823902f98SJames Seo 
149923902f98SJames Seo 		if ((is_cpu && (!strcmp(name, HP_WMI_PATTERN_CPU_TEMP) ||
150023902f98SJames Seo 				!strcmp(name, HP_WMI_PATTERN_CPU_TEMP2))) ||
150123902f98SJames Seo 		    (is_sys && (!strcmp(name, HP_WMI_PATTERN_SYS_TEMP) ||
150223902f98SJames Seo 				!strcmp(name, HP_WMI_PATTERN_SYS_TEMP2)))) {
150323902f98SJames Seo 			temp_info[0] = info;
150423902f98SJames Seo 			return 1;
150523902f98SJames Seo 		}
150623902f98SJames Seo 
150723902f98SJames Seo 		if (is_cpu && (strstr(name, HP_WMI_PATTERN_CPU) &&
150823902f98SJames Seo 			       strstr(name, HP_WMI_PATTERN_TEMP)))
150923902f98SJames Seo 			temp_info[count++] = info;
151023902f98SJames Seo 	}
151123902f98SJames Seo 
151223902f98SJames Seo 	return count;
151323902f98SJames Seo }
151423902f98SJames Seo 
151523902f98SJames Seo /* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */
151623902f98SJames Seo static void hp_wmi_devm_notify_remove(void *ignored)
151723902f98SJames Seo {
151823902f98SJames Seo 	wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
151923902f98SJames Seo }
152023902f98SJames Seo 
152123902f98SJames Seo /* hp_wmi_notify - WMI event notification handler */
152223902f98SJames Seo static void hp_wmi_notify(u32 value, void *context)
152323902f98SJames Seo {
152423902f98SJames Seo 	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
152523902f98SJames Seo 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
152623902f98SJames Seo 	struct hp_wmi_sensors *state = context;
152723902f98SJames Seo 	struct device *dev = &state->wdev->dev;
152823902f98SJames Seo 	struct hp_wmi_info *fan_info;
152923902f98SJames Seo 	struct hp_wmi_event event;
153023902f98SJames Seo 	union acpi_object *wobj;
153123902f98SJames Seo 	acpi_status err;
153223902f98SJames Seo 	int event_type;
153323902f98SJames Seo 	u8 count;
153423902f98SJames Seo 
153523902f98SJames Seo 	/*
153623902f98SJames Seo 	 * The following warning may occur in the kernel log:
153723902f98SJames Seo 	 *
153823902f98SJames Seo 	 *   ACPI Warning: \_SB.WMID._WED: Return type mismatch -
153923902f98SJames Seo 	 *     found Package, expected Integer/String/Buffer
154023902f98SJames Seo 	 *
154123902f98SJames Seo 	 * After using [4] to decode BMOF blobs found in [3], careless copying
154223902f98SJames Seo 	 * of BIOS code seems the most likely explanation for this warning.
154323902f98SJames Seo 	 * HP_WMI_EVENT_GUID refers to \\.\root\WMI\HPBIOS_BIOSEvent on
154423902f98SJames Seo 	 * business-class systems, but it refers to \\.\root\WMI\hpqBEvnt on
154523902f98SJames Seo 	 * non-business-class systems. Per the existing hp-wmi driver, it
154623902f98SJames Seo 	 * looks like an instance of hpqBEvnt delivered as event data may
154723902f98SJames Seo 	 * indeed take the form of a raw ACPI_BUFFER on non-business-class
154823902f98SJames Seo 	 * systems ("may" because ASL shows some BIOSes do strange things).
154923902f98SJames Seo 	 *
155023902f98SJames Seo 	 * In any case, we can ignore this warning, because we always validate
155123902f98SJames Seo 	 * the event data to ensure it is an ACPI_PACKAGE containing a
155223902f98SJames Seo 	 * HPBIOS_BIOSEvent instance.
155323902f98SJames Seo 	 */
155423902f98SJames Seo 
155523902f98SJames Seo 	mutex_lock(&state->lock);
155623902f98SJames Seo 
155723902f98SJames Seo 	err = wmi_get_event_data(value, &out);
155823902f98SJames Seo 	if (ACPI_FAILURE(err))
155923902f98SJames Seo 		goto out_unlock;
156023902f98SJames Seo 
156123902f98SJames Seo 	wobj = out.pointer;
156223902f98SJames Seo 
156323902f98SJames Seo 	err = populate_event_from_wobj(&event, wobj);
156423902f98SJames Seo 	if (err) {
156523902f98SJames Seo 		dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
156623902f98SJames Seo 		goto out_free_wobj;
156723902f98SJames Seo 	}
156823902f98SJames Seo 
156923902f98SJames Seo 	event_type = classify_event(event.name, event.category);
157023902f98SJames Seo 	switch (event_type) {
157123902f98SJames Seo 	case HP_WMI_TYPE_AIR_FLOW:
157223902f98SJames Seo 		fan_info = match_fan_event(state, event.description);
157323902f98SJames Seo 		if (fan_info)
157423902f98SJames Seo 			fan_info->alarm = true;
157523902f98SJames Seo 		break;
157623902f98SJames Seo 
157723902f98SJames Seo 	case HP_WMI_TYPE_INTRUSION:
157823902f98SJames Seo 		state->intrusion = true;
157923902f98SJames Seo 		break;
158023902f98SJames Seo 
158123902f98SJames Seo 	case HP_WMI_TYPE_TEMPERATURE:
158223902f98SJames Seo 		count = match_temp_events(state, event.description, temp_info);
158323902f98SJames Seo 		while (count)
158423902f98SJames Seo 			temp_info[--count]->alarm = true;
158523902f98SJames Seo 		break;
158623902f98SJames Seo 
158723902f98SJames Seo 	default:
158823902f98SJames Seo 		break;
158923902f98SJames Seo 	}
159023902f98SJames Seo 
159123902f98SJames Seo out_free_wobj:
159223902f98SJames Seo 	kfree(wobj);
159323902f98SJames Seo 
159423902f98SJames Seo out_unlock:
159523902f98SJames Seo 	mutex_unlock(&state->lock);
159623902f98SJames Seo }
159723902f98SJames Seo 
159823902f98SJames Seo static int init_platform_events(struct device *dev,
159923902f98SJames Seo 				struct hp_wmi_platform_events **out_pevents,
160023902f98SJames Seo 				u8 *out_pcount)
160123902f98SJames Seo {
160223902f98SJames Seo 	struct hp_wmi_platform_events *pevents_arr;
160323902f98SJames Seo 	struct hp_wmi_platform_events *pevents;
160423902f98SJames Seo 	union acpi_object *wobj;
160523902f98SJames Seo 	u8 count;
160623902f98SJames Seo 	int err;
160723902f98SJames Seo 	u8 i;
160823902f98SJames Seo 
160923902f98SJames Seo 	count = hp_wmi_wobj_instance_count(HP_WMI_PLATFORM_EVENTS_GUID);
161023902f98SJames Seo 	if (!count) {
161123902f98SJames Seo 		*out_pcount = 0;
161223902f98SJames Seo 
161323902f98SJames Seo 		dev_dbg(dev, "No platform events\n");
161423902f98SJames Seo 
161523902f98SJames Seo 		return 0;
161623902f98SJames Seo 	}
161723902f98SJames Seo 
161823902f98SJames Seo 	pevents_arr = devm_kcalloc(dev, count, sizeof(*pevents), GFP_KERNEL);
161923902f98SJames Seo 	if (!pevents_arr)
162023902f98SJames Seo 		return -ENOMEM;
162123902f98SJames Seo 
162223902f98SJames Seo 	for (i = 0, pevents = pevents_arr; i < count; i++, pevents++) {
162323902f98SJames Seo 		wobj = hp_wmi_get_wobj(HP_WMI_PLATFORM_EVENTS_GUID, i);
162423902f98SJames Seo 		if (!wobj)
162523902f98SJames Seo 			return -EIO;
162623902f98SJames Seo 
162723902f98SJames Seo 		err = populate_platform_events_from_wobj(dev, pevents, wobj);
162823902f98SJames Seo 
162923902f98SJames Seo 		kfree(wobj);
163023902f98SJames Seo 
163123902f98SJames Seo 		if (err)
163223902f98SJames Seo 			return err;
163323902f98SJames Seo 	}
163423902f98SJames Seo 
163523902f98SJames Seo 	*out_pevents = pevents_arr;
163623902f98SJames Seo 	*out_pcount = count;
163723902f98SJames Seo 
163823902f98SJames Seo 	dev_dbg(dev, "Found %u platform events\n", count);
163923902f98SJames Seo 
164023902f98SJames Seo 	return 0;
164123902f98SJames Seo }
164223902f98SJames Seo 
164323902f98SJames Seo static int init_numeric_sensors(struct hp_wmi_sensors *state,
164423902f98SJames Seo 				struct hp_wmi_info *connected[],
164523902f98SJames Seo 				struct hp_wmi_info **out_info,
164623902f98SJames Seo 				u8 *out_icount, u8 *out_count,
164723902f98SJames Seo 				bool *out_is_new)
164823902f98SJames Seo {
164923902f98SJames Seo 	struct hp_wmi_info ***info_map = state->info_map;
165023902f98SJames Seo 	u8 *channel_count = state->channel_count;
165123902f98SJames Seo 	struct device *dev = &state->wdev->dev;
165223902f98SJames Seo 	struct hp_wmi_numeric_sensor *nsensor;
165323902f98SJames Seo 	u8 channel_index[hwmon_max] = {};
165423902f98SJames Seo 	enum hwmon_sensor_types type;
165523902f98SJames Seo 	struct hp_wmi_info *info_arr;
165623902f98SJames Seo 	struct hp_wmi_info *info;
165723902f98SJames Seo 	union acpi_object *wobj;
165823902f98SJames Seo 	u8 count = 0;
165923902f98SJames Seo 	bool is_new;
166023902f98SJames Seo 	u8 icount;
166123902f98SJames Seo 	int wtype;
166223902f98SJames Seo 	int err;
166323902f98SJames Seo 	u8 c;
166423902f98SJames Seo 	u8 i;
166523902f98SJames Seo 
166623902f98SJames Seo 	icount = hp_wmi_wobj_instance_count(HP_WMI_NUMERIC_SENSOR_GUID);
166723902f98SJames Seo 	if (!icount)
166823902f98SJames Seo 		return -ENODATA;
166923902f98SJames Seo 
167023902f98SJames Seo 	info_arr = devm_kcalloc(dev, icount, sizeof(*info), GFP_KERNEL);
167123902f98SJames Seo 	if (!info_arr)
167223902f98SJames Seo 		return -ENOMEM;
167323902f98SJames Seo 
167423902f98SJames Seo 	for (i = 0, info = info_arr; i < icount; i++, info++) {
167523902f98SJames Seo 		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
167623902f98SJames Seo 		if (!wobj)
167723902f98SJames Seo 			return -EIO;
167823902f98SJames Seo 
167923902f98SJames Seo 		info->instance = i;
168023902f98SJames Seo 		info->state = state;
168123902f98SJames Seo 		nsensor = &info->nsensor;
168223902f98SJames Seo 
168323902f98SJames Seo 		err = populate_numeric_sensor_from_wobj(dev, nsensor, wobj,
168423902f98SJames Seo 							&is_new);
168523902f98SJames Seo 
168623902f98SJames Seo 		kfree(wobj);
168723902f98SJames Seo 
168823902f98SJames Seo 		if (err)
168923902f98SJames Seo 			return err;
169023902f98SJames Seo 
169123902f98SJames Seo 		if (!numeric_sensor_is_connected(nsensor))
169223902f98SJames Seo 			continue;
169323902f98SJames Seo 
169423902f98SJames Seo 		wtype = classify_numeric_sensor(nsensor);
169523902f98SJames Seo 		if (wtype < 0)
169623902f98SJames Seo 			continue;
169723902f98SJames Seo 
169823902f98SJames Seo 		type = hp_wmi_hwmon_type_map[wtype];
169923902f98SJames Seo 
170023902f98SJames Seo 		channel_count[type]++;
170123902f98SJames Seo 
170223902f98SJames Seo 		info->type = type;
170323902f98SJames Seo 
170423902f98SJames Seo 		interpret_info(info);
170523902f98SJames Seo 
170623902f98SJames Seo 		connected[count++] = info;
170723902f98SJames Seo 	}
170823902f98SJames Seo 
170923902f98SJames Seo 	dev_dbg(dev, "Found %u sensors (%u connected)\n", i, count);
171023902f98SJames Seo 
171123902f98SJames Seo 	for (i = 0; i < count; i++) {
171223902f98SJames Seo 		info = connected[i];
171323902f98SJames Seo 		type = info->type;
171423902f98SJames Seo 		c = channel_index[type]++;
171523902f98SJames Seo 
171623902f98SJames Seo 		if (!info_map[type]) {
171723902f98SJames Seo 			info_map[type] = devm_kcalloc(dev, channel_count[type],
171823902f98SJames Seo 						      sizeof(*info_map),
171923902f98SJames Seo 						      GFP_KERNEL);
172023902f98SJames Seo 			if (!info_map[type])
172123902f98SJames Seo 				return -ENOMEM;
172223902f98SJames Seo 		}
172323902f98SJames Seo 
172423902f98SJames Seo 		info_map[type][c] = info;
172523902f98SJames Seo 	}
172623902f98SJames Seo 
172723902f98SJames Seo 	*out_info = info_arr;
172823902f98SJames Seo 	*out_icount = icount;
172923902f98SJames Seo 	*out_count = count;
173023902f98SJames Seo 	*out_is_new = is_new;
173123902f98SJames Seo 
173223902f98SJames Seo 	return 0;
173323902f98SJames Seo }
173423902f98SJames Seo 
173523902f98SJames Seo static bool find_event_attributes(struct hp_wmi_sensors *state,
173623902f98SJames Seo 				  struct hp_wmi_platform_events *pevents,
173723902f98SJames Seo 				  u8 pevents_count)
173823902f98SJames Seo {
173923902f98SJames Seo 	/*
174023902f98SJames Seo 	 * The existence of this HPBIOS_PlatformEvents instance:
174123902f98SJames Seo 	 *
174223902f98SJames Seo 	 *   {
174323902f98SJames Seo 	 *     Name = "Rear Chassis Fan0 Stall";
174423902f98SJames Seo 	 *     Description = "Rear Chassis Fan0 Speed";
174523902f98SJames Seo 	 *     Category = 3;           // "Sensor"
174623902f98SJames Seo 	 *     PossibleSeverity = 25;  // "Critical Failure"
174723902f98SJames Seo 	 *     PossibleStatus = 5;     // "Predictive Failure"
174823902f98SJames Seo 	 *     [...]
174923902f98SJames Seo 	 *   }
175023902f98SJames Seo 	 *
175123902f98SJames Seo 	 * means that this HPBIOS_BIOSEvent instance may occur:
175223902f98SJames Seo 	 *
175323902f98SJames Seo 	 *   {
175423902f98SJames Seo 	 *     Name = "Rear Chassis Fan0 Stall";
175523902f98SJames Seo 	 *     Description = "Rear Chassis Fan0 Speed";
175623902f98SJames Seo 	 *     Category = 3;           // "Sensor"
175723902f98SJames Seo 	 *     Severity = 25;          // "Critical Failure"
175823902f98SJames Seo 	 *     Status = 5;             // "Predictive Failure"
175923902f98SJames Seo 	 *   }
176023902f98SJames Seo 	 *
176123902f98SJames Seo 	 * After the event occurs (e.g. because the fan was unplugged),
176223902f98SJames Seo 	 * polling the related HPBIOS_BIOSNumericSensor instance gives:
176323902f98SJames Seo 	 *
176423902f98SJames Seo 	 *   {
176523902f98SJames Seo 	 *      Name = "Rear Chassis Fan0";
176623902f98SJames Seo 	 *      Description = "Reports rear chassis fan0 speed";
176723902f98SJames Seo 	 *      OperationalStatus = 5; // "Predictive Failure", was 3 ("OK")
176823902f98SJames Seo 	 *      CurrentReading = 0;
176923902f98SJames Seo 	 *      [...]
177023902f98SJames Seo 	 *   }
177123902f98SJames Seo 	 *
177223902f98SJames Seo 	 * In this example, the hwmon fan channel for "Rear Chassis Fan0"
177323902f98SJames Seo 	 * should support the alarm flag and have it be set if the related
177423902f98SJames Seo 	 * HPBIOS_BIOSEvent instance occurs.
177523902f98SJames Seo 	 *
177623902f98SJames Seo 	 * In addition to fan events, temperature (CPU/chassis) and intrusion
177723902f98SJames Seo 	 * events are relevant to hwmon [2]. Note that much information in [2]
177823902f98SJames Seo 	 * is unreliable; it is referenced in addition to ACPI dumps [3] merely
177923902f98SJames Seo 	 * to support the conclusion that sensor and event names/descriptions
178023902f98SJames Seo 	 * are systematic enough to allow this driver to match them.
178123902f98SJames Seo 	 *
178223902f98SJames Seo 	 * Complications and limitations:
178323902f98SJames Seo 	 *
178423902f98SJames Seo 	 * - Strings are freeform and may vary, cf. sensor Name "CPU0 Fan"
178523902f98SJames Seo 	 *   on a Z420 vs. "CPU Fan Speed" on an EliteOne 800 G1.
178623902f98SJames Seo 	 * - Leading/trailing whitespace is a rare but real possibility [3].
178723902f98SJames Seo 	 * - The HPBIOS_PlatformEvents object may not exist or its instances
178823902f98SJames Seo 	 *   may show that the system only has e.g. BIOS setting-related
178923902f98SJames Seo 	 *   events (cf. the ProBook 4540s and ProBook 470 G0 [3]).
179023902f98SJames Seo 	 */
179123902f98SJames Seo 
179223902f98SJames Seo 	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
179323902f98SJames Seo 	const char *event_description;
179423902f98SJames Seo 	struct hp_wmi_info *fan_info;
179523902f98SJames Seo 	bool has_events = false;
179623902f98SJames Seo 	const char *event_name;
179723902f98SJames Seo 	u32 event_category;
179823902f98SJames Seo 	int event_type;
179923902f98SJames Seo 	u8 count;
180023902f98SJames Seo 	u8 i;
180123902f98SJames Seo 
180223902f98SJames Seo 	for (i = 0; i < pevents_count; i++, pevents++) {
180323902f98SJames Seo 		event_name = pevents->name;
180423902f98SJames Seo 		event_description = pevents->description;
180523902f98SJames Seo 		event_category = pevents->category;
180623902f98SJames Seo 
180723902f98SJames Seo 		event_type = classify_event(event_name, event_category);
180823902f98SJames Seo 		switch (event_type) {
180923902f98SJames Seo 		case HP_WMI_TYPE_AIR_FLOW:
181023902f98SJames Seo 			fan_info = match_fan_event(state, event_description);
181123902f98SJames Seo 			if (!fan_info)
181223902f98SJames Seo 				break;
181323902f98SJames Seo 
181423902f98SJames Seo 			fan_info->has_alarm = true;
181523902f98SJames Seo 			has_events = true;
181623902f98SJames Seo 			break;
181723902f98SJames Seo 
181823902f98SJames Seo 		case HP_WMI_TYPE_INTRUSION:
181923902f98SJames Seo 			state->has_intrusion = true;
182023902f98SJames Seo 			has_events = true;
182123902f98SJames Seo 			break;
182223902f98SJames Seo 
182323902f98SJames Seo 		case HP_WMI_TYPE_TEMPERATURE:
182423902f98SJames Seo 			count = match_temp_events(state, event_description,
182523902f98SJames Seo 						  temp_info);
182623902f98SJames Seo 			if (!count)
182723902f98SJames Seo 				break;
182823902f98SJames Seo 
182923902f98SJames Seo 			while (count)
183023902f98SJames Seo 				temp_info[--count]->has_alarm = true;
183123902f98SJames Seo 			has_events = true;
183223902f98SJames Seo 			break;
183323902f98SJames Seo 
183423902f98SJames Seo 		default:
183523902f98SJames Seo 			break;
183623902f98SJames Seo 		}
183723902f98SJames Seo 	}
183823902f98SJames Seo 
183923902f98SJames Seo 	return has_events;
184023902f98SJames Seo }
184123902f98SJames Seo 
184223902f98SJames Seo static int make_chip_info(struct hp_wmi_sensors *state, bool has_events)
184323902f98SJames Seo {
184423902f98SJames Seo 	const struct hwmon_channel_info **ptr_channel_info;
184523902f98SJames Seo 	struct hp_wmi_info ***info_map = state->info_map;
184623902f98SJames Seo 	u8 *channel_count = state->channel_count;
184723902f98SJames Seo 	struct hwmon_channel_info *channel_info;
184823902f98SJames Seo 	struct device *dev = &state->wdev->dev;
184923902f98SJames Seo 	enum hwmon_sensor_types type;
185023902f98SJames Seo 	u8 type_count = 0;
185123902f98SJames Seo 	u32 *config;
185223902f98SJames Seo 	u32 attr;
185323902f98SJames Seo 	u8 count;
185423902f98SJames Seo 	u8 i;
185523902f98SJames Seo 
185623902f98SJames Seo 	if (channel_count[hwmon_temp])
185723902f98SJames Seo 		channel_count[hwmon_chip] = 1;
185823902f98SJames Seo 
185923902f98SJames Seo 	if (has_events && state->has_intrusion)
186023902f98SJames Seo 		channel_count[hwmon_intrusion] = 1;
186123902f98SJames Seo 
186223902f98SJames Seo 	for (type = hwmon_chip; type < hwmon_max; type++)
186323902f98SJames Seo 		if (channel_count[type])
186423902f98SJames Seo 			type_count++;
186523902f98SJames Seo 
186623902f98SJames Seo 	channel_info = devm_kcalloc(dev, type_count,
186723902f98SJames Seo 				    sizeof(*channel_info), GFP_KERNEL);
186823902f98SJames Seo 	if (!channel_info)
186923902f98SJames Seo 		return -ENOMEM;
187023902f98SJames Seo 
187123902f98SJames Seo 	ptr_channel_info = devm_kcalloc(dev, type_count + 1,
187223902f98SJames Seo 					sizeof(*ptr_channel_info), GFP_KERNEL);
187323902f98SJames Seo 	if (!ptr_channel_info)
187423902f98SJames Seo 		return -ENOMEM;
187523902f98SJames Seo 
187623902f98SJames Seo 	hp_wmi_chip_info.info = ptr_channel_info;
187723902f98SJames Seo 
187823902f98SJames Seo 	for (type = hwmon_chip; type < hwmon_max; type++) {
187923902f98SJames Seo 		count = channel_count[type];
188023902f98SJames Seo 		if (!count)
188123902f98SJames Seo 			continue;
188223902f98SJames Seo 
188323902f98SJames Seo 		config = devm_kcalloc(dev, count + 1,
188423902f98SJames Seo 				      sizeof(*config), GFP_KERNEL);
188523902f98SJames Seo 		if (!config)
188623902f98SJames Seo 			return -ENOMEM;
188723902f98SJames Seo 
188823902f98SJames Seo 		attr = hp_wmi_hwmon_attributes[type];
188923902f98SJames Seo 		channel_info->type = type;
189023902f98SJames Seo 		channel_info->config = config;
189123902f98SJames Seo 		memset32(config, attr, count);
189223902f98SJames Seo 
189323902f98SJames Seo 		*ptr_channel_info++ = channel_info++;
189423902f98SJames Seo 
189523902f98SJames Seo 		if (!has_events || (type != hwmon_temp && type != hwmon_fan))
189623902f98SJames Seo 			continue;
189723902f98SJames Seo 
189823902f98SJames Seo 		attr = type == hwmon_temp ? HWMON_T_ALARM : HWMON_F_ALARM;
189923902f98SJames Seo 
190023902f98SJames Seo 		for (i = 0; i < count; i++)
190123902f98SJames Seo 			if (info_map[type][i]->has_alarm)
190223902f98SJames Seo 				config[i] |= attr;
190323902f98SJames Seo 	}
190423902f98SJames Seo 
190523902f98SJames Seo 	return 0;
190623902f98SJames Seo }
190723902f98SJames Seo 
190823902f98SJames Seo static bool add_event_handler(struct hp_wmi_sensors *state)
190923902f98SJames Seo {
191023902f98SJames Seo 	struct device *dev = &state->wdev->dev;
191123902f98SJames Seo 	int err;
191223902f98SJames Seo 
191323902f98SJames Seo 	err = wmi_install_notify_handler(HP_WMI_EVENT_GUID,
191423902f98SJames Seo 					 hp_wmi_notify, state);
191523902f98SJames Seo 	if (err) {
191623902f98SJames Seo 		dev_info(dev, "Failed to subscribe to WMI event\n");
191723902f98SJames Seo 		return false;
191823902f98SJames Seo 	}
191923902f98SJames Seo 
192023902f98SJames Seo 	err = devm_add_action_or_reset(dev, hp_wmi_devm_notify_remove, NULL);
192123902f98SJames Seo 	if (err)
192223902f98SJames Seo 		return false;
192323902f98SJames Seo 
192423902f98SJames Seo 	return true;
192523902f98SJames Seo }
192623902f98SJames Seo 
192723902f98SJames Seo static int hp_wmi_sensors_init(struct hp_wmi_sensors *state)
192823902f98SJames Seo {
192923902f98SJames Seo 	struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES];
193023902f98SJames Seo 	struct hp_wmi_platform_events *pevents;
193123902f98SJames Seo 	struct device *dev = &state->wdev->dev;
193223902f98SJames Seo 	struct hp_wmi_info *info;
193323902f98SJames Seo 	struct device *hwdev;
193423902f98SJames Seo 	bool has_events;
193523902f98SJames Seo 	bool is_new;
193623902f98SJames Seo 	u8 icount;
193723902f98SJames Seo 	u8 pcount;
193823902f98SJames Seo 	u8 count;
193923902f98SJames Seo 	int err;
194023902f98SJames Seo 
194123902f98SJames Seo 	err = init_platform_events(dev, &pevents, &pcount);
194223902f98SJames Seo 	if (err)
194323902f98SJames Seo 		return err;
194423902f98SJames Seo 
194523902f98SJames Seo 	err = init_numeric_sensors(state, connected, &info,
194623902f98SJames Seo 				   &icount, &count, &is_new);
194723902f98SJames Seo 	if (err)
194823902f98SJames Seo 		return err;
194923902f98SJames Seo 
1950*153c9a02SArnd Bergmann 	if (IS_ENABLED(CONFIG_DEBUG_FS))
195123902f98SJames Seo 		hp_wmi_debugfs_init(dev, info, pevents, icount, pcount, is_new);
195223902f98SJames Seo 
195323902f98SJames Seo 	if (!count)
195423902f98SJames Seo 		return 0;	/* No connected sensors; debugfs only. */
195523902f98SJames Seo 
195623902f98SJames Seo 	has_events = find_event_attributes(state, pevents, pcount);
195723902f98SJames Seo 
195823902f98SJames Seo 	/* Survive failure to install WMI event handler. */
195923902f98SJames Seo 	if (has_events && !add_event_handler(state))
196023902f98SJames Seo 		has_events = false;
196123902f98SJames Seo 
196223902f98SJames Seo 	err = make_chip_info(state, has_events);
196323902f98SJames Seo 	if (err)
196423902f98SJames Seo 		return err;
196523902f98SJames Seo 
196623902f98SJames Seo 	hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors",
196723902f98SJames Seo 						     state, &hp_wmi_chip_info,
196823902f98SJames Seo 						     NULL);
196923902f98SJames Seo 	return PTR_ERR_OR_ZERO(hwdev);
197023902f98SJames Seo }
197123902f98SJames Seo 
197223902f98SJames Seo static int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context)
197323902f98SJames Seo {
197423902f98SJames Seo 	struct device *dev = &wdev->dev;
197523902f98SJames Seo 	struct hp_wmi_sensors *state;
197623902f98SJames Seo 
197723902f98SJames Seo 	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
197823902f98SJames Seo 	if (!state)
197923902f98SJames Seo 		return -ENOMEM;
198023902f98SJames Seo 
198123902f98SJames Seo 	state->wdev = wdev;
198223902f98SJames Seo 
198323902f98SJames Seo 	mutex_init(&state->lock);
198423902f98SJames Seo 
198523902f98SJames Seo 	dev_set_drvdata(dev, state);
198623902f98SJames Seo 
198723902f98SJames Seo 	return hp_wmi_sensors_init(state);
198823902f98SJames Seo }
198923902f98SJames Seo 
199023902f98SJames Seo static const struct wmi_device_id hp_wmi_sensors_id_table[] = {
199123902f98SJames Seo 	{ HP_WMI_NUMERIC_SENSOR_GUID, NULL },
199223902f98SJames Seo 	{},
199323902f98SJames Seo };
199423902f98SJames Seo 
199523902f98SJames Seo static struct wmi_driver hp_wmi_sensors_driver = {
199623902f98SJames Seo 	.driver   = { .name = "hp-wmi-sensors" },
199723902f98SJames Seo 	.id_table = hp_wmi_sensors_id_table,
199823902f98SJames Seo 	.probe    = hp_wmi_sensors_probe,
199923902f98SJames Seo };
200023902f98SJames Seo module_wmi_driver(hp_wmi_sensors_driver);
200123902f98SJames Seo 
200223902f98SJames Seo MODULE_AUTHOR("James Seo <james@equiv.tech>");
200323902f98SJames Seo MODULE_DESCRIPTION("HP WMI Sensors driver");
200423902f98SJames Seo MODULE_LICENSE("GPL");
2005