162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * hwmon driver for HP (and some HP Compaq) business-class computers that
462306a36Sopenharmony_ci * report numeric sensor data via Windows Management Instrumentation (WMI).
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2023 James Seo <james@equiv.tech>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * References:
962306a36Sopenharmony_ci * [1] Hewlett-Packard Development Company, L.P.,
1062306a36Sopenharmony_ci *     "HP Client Management Interface Technical White Paper", 2005. [Online].
1162306a36Sopenharmony_ci *     Available: https://h20331.www2.hp.com/hpsub/downloads/cmi_whitepaper.pdf
1262306a36Sopenharmony_ci * [2] Hewlett-Packard Development Company, L.P.,
1362306a36Sopenharmony_ci *     "HP Retail Manageability", 2012. [Online].
1462306a36Sopenharmony_ci *     Available: http://h10032.www1.hp.com/ctg/Manual/c03291135.pdf
1562306a36Sopenharmony_ci * [3] Linux Hardware Project, A. Ponomarenko et al.,
1662306a36Sopenharmony_ci *     "linuxhw/ACPI - Collect ACPI table dumps", 2018. [Online].
1762306a36Sopenharmony_ci *     Available: https://github.com/linuxhw/ACPI
1862306a36Sopenharmony_ci * [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
1962306a36Sopenharmony_ci *     2017. [Online]. Available: https://github.com/pali/bmfdec
2062306a36Sopenharmony_ci * [5] Microsoft Corporation, "Driver-Defined WMI Data Items", 2017. [Online].
2162306a36Sopenharmony_ci *     Available: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <linux/acpi.h>
2562306a36Sopenharmony_ci#include <linux/debugfs.h>
2662306a36Sopenharmony_ci#include <linux/hwmon.h>
2762306a36Sopenharmony_ci#include <linux/jiffies.h>
2862306a36Sopenharmony_ci#include <linux/mutex.h>
2962306a36Sopenharmony_ci#include <linux/nls.h>
3062306a36Sopenharmony_ci#include <linux/units.h>
3162306a36Sopenharmony_ci#include <linux/wmi.h>
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define HP_WMI_EVENT_NAMESPACE		"root\\WMI"
3462306a36Sopenharmony_ci#define HP_WMI_EVENT_CLASS		"HPBIOS_BIOSEvent"
3562306a36Sopenharmony_ci#define HP_WMI_EVENT_GUID		"95F24279-4D7B-4334-9387-ACCDC67EF61C"
3662306a36Sopenharmony_ci#define HP_WMI_NUMERIC_SENSOR_GUID	"8F1F6435-9F42-42C8-BADC-0E9424F20C9A"
3762306a36Sopenharmony_ci#define HP_WMI_PLATFORM_EVENTS_GUID	"41227C2D-80E1-423F-8B8E-87E32755A0EB"
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* Patterns for recognizing sensors and matching events to channels. */
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define HP_WMI_PATTERN_SYS_TEMP		"Chassis Thermal Index"
4262306a36Sopenharmony_ci#define HP_WMI_PATTERN_SYS_TEMP2	"System Ambient Temperature"
4362306a36Sopenharmony_ci#define HP_WMI_PATTERN_CPU_TEMP		"CPU Thermal Index"
4462306a36Sopenharmony_ci#define HP_WMI_PATTERN_CPU_TEMP2	"CPU Temperature"
4562306a36Sopenharmony_ci#define HP_WMI_PATTERN_TEMP_SENSOR	"Thermal Index"
4662306a36Sopenharmony_ci#define HP_WMI_PATTERN_TEMP_ALARM	"Thermal Critical"
4762306a36Sopenharmony_ci#define HP_WMI_PATTERN_INTRUSION_ALARM	"Hood Intrusion"
4862306a36Sopenharmony_ci#define HP_WMI_PATTERN_FAN_ALARM	"Stall"
4962306a36Sopenharmony_ci#define HP_WMI_PATTERN_TEMP		"Temperature"
5062306a36Sopenharmony_ci#define HP_WMI_PATTERN_CPU		"CPU"
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* These limits are arbitrary. The WMI implementation may vary by system. */
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#define HP_WMI_MAX_STR_SIZE		128U
5562306a36Sopenharmony_ci#define HP_WMI_MAX_PROPERTIES		32U
5662306a36Sopenharmony_ci#define HP_WMI_MAX_INSTANCES		32U
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cienum hp_wmi_type {
5962306a36Sopenharmony_ci	HP_WMI_TYPE_OTHER			= 1,
6062306a36Sopenharmony_ci	HP_WMI_TYPE_TEMPERATURE			= 2,
6162306a36Sopenharmony_ci	HP_WMI_TYPE_VOLTAGE			= 3,
6262306a36Sopenharmony_ci	HP_WMI_TYPE_CURRENT			= 4,
6362306a36Sopenharmony_ci	HP_WMI_TYPE_AIR_FLOW			= 12,
6462306a36Sopenharmony_ci	HP_WMI_TYPE_INTRUSION			= 0xabadb01, /* Custom. */
6562306a36Sopenharmony_ci};
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cienum hp_wmi_category {
6862306a36Sopenharmony_ci	HP_WMI_CATEGORY_SENSOR			= 3,
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cienum hp_wmi_severity {
7262306a36Sopenharmony_ci	HP_WMI_SEVERITY_UNKNOWN			= 0,
7362306a36Sopenharmony_ci	HP_WMI_SEVERITY_OK			= 5,
7462306a36Sopenharmony_ci	HP_WMI_SEVERITY_DEGRADED_WARNING	= 10,
7562306a36Sopenharmony_ci	HP_WMI_SEVERITY_MINOR_FAILURE		= 15,
7662306a36Sopenharmony_ci	HP_WMI_SEVERITY_MAJOR_FAILURE		= 20,
7762306a36Sopenharmony_ci	HP_WMI_SEVERITY_CRITICAL_FAILURE	= 25,
7862306a36Sopenharmony_ci	HP_WMI_SEVERITY_NON_RECOVERABLE_ERROR	= 30,
7962306a36Sopenharmony_ci};
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cienum hp_wmi_status {
8262306a36Sopenharmony_ci	HP_WMI_STATUS_OK			= 2,
8362306a36Sopenharmony_ci	HP_WMI_STATUS_DEGRADED			= 3,
8462306a36Sopenharmony_ci	HP_WMI_STATUS_STRESSED			= 4,
8562306a36Sopenharmony_ci	HP_WMI_STATUS_PREDICTIVE_FAILURE	= 5,
8662306a36Sopenharmony_ci	HP_WMI_STATUS_ERROR			= 6,
8762306a36Sopenharmony_ci	HP_WMI_STATUS_NON_RECOVERABLE_ERROR	= 7,
8862306a36Sopenharmony_ci	HP_WMI_STATUS_NO_CONTACT		= 12,
8962306a36Sopenharmony_ci	HP_WMI_STATUS_LOST_COMMUNICATION	= 13,
9062306a36Sopenharmony_ci	HP_WMI_STATUS_ABORTED			= 14,
9162306a36Sopenharmony_ci	HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR = 16,
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/* Occurs combined with one of "OK", "Degraded", and "Error" [1]. */
9462306a36Sopenharmony_ci	HP_WMI_STATUS_COMPLETED			= 17,
9562306a36Sopenharmony_ci};
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cienum hp_wmi_units {
9862306a36Sopenharmony_ci	HP_WMI_UNITS_OTHER			= 1,
9962306a36Sopenharmony_ci	HP_WMI_UNITS_DEGREES_C			= 2,
10062306a36Sopenharmony_ci	HP_WMI_UNITS_DEGREES_F			= 3,
10162306a36Sopenharmony_ci	HP_WMI_UNITS_DEGREES_K			= 4,
10262306a36Sopenharmony_ci	HP_WMI_UNITS_VOLTS			= 5,
10362306a36Sopenharmony_ci	HP_WMI_UNITS_AMPS			= 6,
10462306a36Sopenharmony_ci	HP_WMI_UNITS_RPM			= 19,
10562306a36Sopenharmony_ci};
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cienum hp_wmi_property {
10862306a36Sopenharmony_ci	HP_WMI_PROPERTY_NAME			= 0,
10962306a36Sopenharmony_ci	HP_WMI_PROPERTY_DESCRIPTION		= 1,
11062306a36Sopenharmony_ci	HP_WMI_PROPERTY_SENSOR_TYPE		= 2,
11162306a36Sopenharmony_ci	HP_WMI_PROPERTY_OTHER_SENSOR_TYPE	= 3,
11262306a36Sopenharmony_ci	HP_WMI_PROPERTY_OPERATIONAL_STATUS	= 4,
11362306a36Sopenharmony_ci	HP_WMI_PROPERTY_SIZE			= 5,
11462306a36Sopenharmony_ci	HP_WMI_PROPERTY_POSSIBLE_STATES		= 6,
11562306a36Sopenharmony_ci	HP_WMI_PROPERTY_CURRENT_STATE		= 7,
11662306a36Sopenharmony_ci	HP_WMI_PROPERTY_BASE_UNITS		= 8,
11762306a36Sopenharmony_ci	HP_WMI_PROPERTY_UNIT_MODIFIER		= 9,
11862306a36Sopenharmony_ci	HP_WMI_PROPERTY_CURRENT_READING		= 10,
11962306a36Sopenharmony_ci	HP_WMI_PROPERTY_RATE_UNITS		= 11,
12062306a36Sopenharmony_ci};
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic const acpi_object_type hp_wmi_property_map[] = {
12362306a36Sopenharmony_ci	[HP_WMI_PROPERTY_NAME]			= ACPI_TYPE_STRING,
12462306a36Sopenharmony_ci	[HP_WMI_PROPERTY_DESCRIPTION]		= ACPI_TYPE_STRING,
12562306a36Sopenharmony_ci	[HP_WMI_PROPERTY_SENSOR_TYPE]		= ACPI_TYPE_INTEGER,
12662306a36Sopenharmony_ci	[HP_WMI_PROPERTY_OTHER_SENSOR_TYPE]	= ACPI_TYPE_STRING,
12762306a36Sopenharmony_ci	[HP_WMI_PROPERTY_OPERATIONAL_STATUS]	= ACPI_TYPE_INTEGER,
12862306a36Sopenharmony_ci	[HP_WMI_PROPERTY_SIZE]			= ACPI_TYPE_INTEGER,
12962306a36Sopenharmony_ci	[HP_WMI_PROPERTY_POSSIBLE_STATES]	= ACPI_TYPE_STRING,
13062306a36Sopenharmony_ci	[HP_WMI_PROPERTY_CURRENT_STATE]		= ACPI_TYPE_STRING,
13162306a36Sopenharmony_ci	[HP_WMI_PROPERTY_BASE_UNITS]		= ACPI_TYPE_INTEGER,
13262306a36Sopenharmony_ci	[HP_WMI_PROPERTY_UNIT_MODIFIER]		= ACPI_TYPE_INTEGER,
13362306a36Sopenharmony_ci	[HP_WMI_PROPERTY_CURRENT_READING]	= ACPI_TYPE_INTEGER,
13462306a36Sopenharmony_ci	[HP_WMI_PROPERTY_RATE_UNITS]		= ACPI_TYPE_INTEGER,
13562306a36Sopenharmony_ci};
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cienum hp_wmi_platform_events_property {
13862306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME		    = 0,
13962306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION	    = 1,
14062306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE    = 2,
14162306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS	    = 3,
14262306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY	    = 4,
14362306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY   = 5,
14462306a36Sopenharmony_ci	HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS	    = 6,
14562306a36Sopenharmony_ci};
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic const acpi_object_type hp_wmi_platform_events_property_map[] = {
14862306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME]		    = ACPI_TYPE_STRING,
14962306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION]	    = ACPI_TYPE_STRING,
15062306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE]  = ACPI_TYPE_STRING,
15162306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS]	    = ACPI_TYPE_STRING,
15262306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY]	    = ACPI_TYPE_INTEGER,
15362306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY] = ACPI_TYPE_INTEGER,
15462306a36Sopenharmony_ci	[HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS]   = ACPI_TYPE_INTEGER,
15562306a36Sopenharmony_ci};
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cienum hp_wmi_event_property {
15862306a36Sopenharmony_ci	HP_WMI_EVENT_PROPERTY_NAME		= 0,
15962306a36Sopenharmony_ci	HP_WMI_EVENT_PROPERTY_DESCRIPTION	= 1,
16062306a36Sopenharmony_ci	HP_WMI_EVENT_PROPERTY_CATEGORY		= 2,
16162306a36Sopenharmony_ci	HP_WMI_EVENT_PROPERTY_SEVERITY		= 3,
16262306a36Sopenharmony_ci	HP_WMI_EVENT_PROPERTY_STATUS		= 4,
16362306a36Sopenharmony_ci};
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic const acpi_object_type hp_wmi_event_property_map[] = {
16662306a36Sopenharmony_ci	[HP_WMI_EVENT_PROPERTY_NAME]		= ACPI_TYPE_STRING,
16762306a36Sopenharmony_ci	[HP_WMI_EVENT_PROPERTY_DESCRIPTION]	= ACPI_TYPE_STRING,
16862306a36Sopenharmony_ci	[HP_WMI_EVENT_PROPERTY_CATEGORY]	= ACPI_TYPE_INTEGER,
16962306a36Sopenharmony_ci	[HP_WMI_EVENT_PROPERTY_SEVERITY]	= ACPI_TYPE_INTEGER,
17062306a36Sopenharmony_ci	[HP_WMI_EVENT_PROPERTY_STATUS]		= ACPI_TYPE_INTEGER,
17162306a36Sopenharmony_ci};
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic const enum hwmon_sensor_types hp_wmi_hwmon_type_map[] = {
17462306a36Sopenharmony_ci	[HP_WMI_TYPE_TEMPERATURE]		= hwmon_temp,
17562306a36Sopenharmony_ci	[HP_WMI_TYPE_VOLTAGE]			= hwmon_in,
17662306a36Sopenharmony_ci	[HP_WMI_TYPE_CURRENT]			= hwmon_curr,
17762306a36Sopenharmony_ci	[HP_WMI_TYPE_AIR_FLOW]			= hwmon_fan,
17862306a36Sopenharmony_ci};
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic const u32 hp_wmi_hwmon_attributes[hwmon_max] = {
18162306a36Sopenharmony_ci	[hwmon_chip]	  = HWMON_C_REGISTER_TZ,
18262306a36Sopenharmony_ci	[hwmon_temp]	  = HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT,
18362306a36Sopenharmony_ci	[hwmon_in]	  = HWMON_I_INPUT | HWMON_I_LABEL,
18462306a36Sopenharmony_ci	[hwmon_curr]	  = HWMON_C_INPUT | HWMON_C_LABEL,
18562306a36Sopenharmony_ci	[hwmon_fan]	  = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_FAULT,
18662306a36Sopenharmony_ci	[hwmon_intrusion] = HWMON_INTRUSION_ALARM,
18762306a36Sopenharmony_ci};
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci/*
19062306a36Sopenharmony_ci * struct hp_wmi_numeric_sensor - a HPBIOS_BIOSNumericSensor instance
19162306a36Sopenharmony_ci *
19262306a36Sopenharmony_ci * Two variants of HPBIOS_BIOSNumericSensor are known. The first is specified
19362306a36Sopenharmony_ci * in [1] and appears to be much more widespread. The second was discovered by
19462306a36Sopenharmony_ci * decoding BMOF blobs [4], seems to be found only in some newer ZBook systems
19562306a36Sopenharmony_ci * [3], and has two new properties and a slightly different property order.
19662306a36Sopenharmony_ci *
19762306a36Sopenharmony_ci * These differences don't matter on Windows, where WMI object properties are
19862306a36Sopenharmony_ci * accessed by name. For us, supporting both variants gets ugly and hacky at
19962306a36Sopenharmony_ci * times. The fun begins now; this struct is defined as per the new variant.
20062306a36Sopenharmony_ci *
20162306a36Sopenharmony_ci * Effective MOF definition:
20262306a36Sopenharmony_ci *
20362306a36Sopenharmony_ci *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
20462306a36Sopenharmony_ci *   class HPBIOS_BIOSNumericSensor {
20562306a36Sopenharmony_ci *     [read] string Name;
20662306a36Sopenharmony_ci *     [read] string Description;
20762306a36Sopenharmony_ci *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
20862306a36Sopenharmony_ci *      "10","11","12"}, Values {"Unknown","Other","Temperature",
20962306a36Sopenharmony_ci *      "Voltage","Current","Tachometer","Counter","Switch","Lock",
21062306a36Sopenharmony_ci *      "Humidity","Smoke Detection","Presence","Air Flow"}]
21162306a36Sopenharmony_ci *     uint32 SensorType;
21262306a36Sopenharmony_ci *     [read] string OtherSensorType;
21362306a36Sopenharmony_ci *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
21462306a36Sopenharmony_ci *      "10","11","12","13","14","15","16","17","18","..",
21562306a36Sopenharmony_ci *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
21662306a36Sopenharmony_ci *      "Stressed","Predictive Failure","Error",
21762306a36Sopenharmony_ci *      "Non-Recoverable Error","Starting","Stopping","Stopped",
21862306a36Sopenharmony_ci *      "In Service","No Contact","Lost Communication","Aborted",
21962306a36Sopenharmony_ci *      "Dormant","Supporting Entity in Error","Completed",
22062306a36Sopenharmony_ci *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
22162306a36Sopenharmony_ci *     uint32 OperationalStatus;
22262306a36Sopenharmony_ci *     [read] uint32 Size;
22362306a36Sopenharmony_ci *     [read] string PossibleStates[];
22462306a36Sopenharmony_ci *     [read] string CurrentState;
22562306a36Sopenharmony_ci *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
22662306a36Sopenharmony_ci *      "10","11","12","13","14","15","16","17","18","19","20",
22762306a36Sopenharmony_ci *      "21","22","23","24","25","26","27","28","29","30","31",
22862306a36Sopenharmony_ci *      "32","33","34","35","36","37","38","39","40","41","42",
22962306a36Sopenharmony_ci *      "43","44","45","46","47","48","49","50","51","52","53",
23062306a36Sopenharmony_ci *      "54","55","56","57","58","59","60","61","62","63","64",
23162306a36Sopenharmony_ci *      "65"}, Values {"Unknown","Other","Degrees C","Degrees F",
23262306a36Sopenharmony_ci *      "Degrees K","Volts","Amps","Watts","Joules","Coulombs",
23362306a36Sopenharmony_ci *      "VA","Nits","Lumens","Lux","Candelas","kPa","PSI",
23462306a36Sopenharmony_ci *      "Newtons","CFM","RPM","Hertz","Seconds","Minutes",
23562306a36Sopenharmony_ci *      "Hours","Days","Weeks","Mils","Inches","Feet",
23662306a36Sopenharmony_ci *      "Cubic Inches","Cubic Feet","Meters","Cubic Centimeters",
23762306a36Sopenharmony_ci *      "Cubic Meters","Liters","Fluid Ounces","Radians",
23862306a36Sopenharmony_ci *      "Steradians","Revolutions","Cycles","Gravities","Ounces",
23962306a36Sopenharmony_ci *      "Pounds","Foot-Pounds","Ounce-Inches","Gauss","Gilberts",
24062306a36Sopenharmony_ci *      "Henries","Farads","Ohms","Siemens","Moles","Becquerels",
24162306a36Sopenharmony_ci *      "PPM (parts/million)","Decibels","DbA","DbC","Grays",
24262306a36Sopenharmony_ci *      "Sieverts","Color Temperature Degrees K","Bits","Bytes",
24362306a36Sopenharmony_ci *      "Words (data)","DoubleWords","QuadWords","Percentage"}]
24462306a36Sopenharmony_ci *     uint32 BaseUnits;
24562306a36Sopenharmony_ci *     [read] sint32 UnitModifier;
24662306a36Sopenharmony_ci *     [read] uint32 CurrentReading;
24762306a36Sopenharmony_ci *     [read] uint32 RateUnits;
24862306a36Sopenharmony_ci *   };
24962306a36Sopenharmony_ci *
25062306a36Sopenharmony_ci * Effective MOF definition of old variant [1] (sans redundant info):
25162306a36Sopenharmony_ci *
25262306a36Sopenharmony_ci *   class HPBIOS_BIOSNumericSensor {
25362306a36Sopenharmony_ci *     [read] string Name;
25462306a36Sopenharmony_ci *     [read] string Description;
25562306a36Sopenharmony_ci *     [read] uint32 SensorType;
25662306a36Sopenharmony_ci *     [read] string OtherSensorType;
25762306a36Sopenharmony_ci *     [read] uint32 OperationalStatus;
25862306a36Sopenharmony_ci *     [read] string CurrentState;
25962306a36Sopenharmony_ci *     [read] string PossibleStates[];
26062306a36Sopenharmony_ci *     [read] uint32 BaseUnits;
26162306a36Sopenharmony_ci *     [read] sint32 UnitModifier;
26262306a36Sopenharmony_ci *     [read] uint32 CurrentReading;
26362306a36Sopenharmony_ci *   };
26462306a36Sopenharmony_ci */
26562306a36Sopenharmony_cistruct hp_wmi_numeric_sensor {
26662306a36Sopenharmony_ci	const char *name;
26762306a36Sopenharmony_ci	const char *description;
26862306a36Sopenharmony_ci	u32 sensor_type;
26962306a36Sopenharmony_ci	const char *other_sensor_type;	/* Explains "Other" SensorType. */
27062306a36Sopenharmony_ci	u32 operational_status;
27162306a36Sopenharmony_ci	u8 size;			/* Count of PossibleStates[]. */
27262306a36Sopenharmony_ci	const char **possible_states;
27362306a36Sopenharmony_ci	const char *current_state;
27462306a36Sopenharmony_ci	u32 base_units;
27562306a36Sopenharmony_ci	s32 unit_modifier;
27662306a36Sopenharmony_ci	u32 current_reading;
27762306a36Sopenharmony_ci	u32 rate_units;
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci/*
28162306a36Sopenharmony_ci * struct hp_wmi_platform_events - a HPBIOS_PlatformEvents instance
28262306a36Sopenharmony_ci *
28362306a36Sopenharmony_ci * Instances of this object reveal the set of possible HPBIOS_BIOSEvent
28462306a36Sopenharmony_ci * instances for the current system, but it may not always be present.
28562306a36Sopenharmony_ci *
28662306a36Sopenharmony_ci * Effective MOF definition:
28762306a36Sopenharmony_ci *
28862306a36Sopenharmony_ci *   #pragma namespace("\\\\.\\root\\HP\\InstrumentedBIOS");
28962306a36Sopenharmony_ci *   class HPBIOS_PlatformEvents {
29062306a36Sopenharmony_ci *     [read] string Name;
29162306a36Sopenharmony_ci *     [read] string Description;
29262306a36Sopenharmony_ci *     [read] string SourceNamespace;
29362306a36Sopenharmony_ci *     [read] string SourceClass;
29462306a36Sopenharmony_ci *     [read, ValueMap {"0","1","2","3","4",".."}, Values {
29562306a36Sopenharmony_ci *      "Unknown","Configuration Change","Button Pressed",
29662306a36Sopenharmony_ci *      "Sensor","BIOS Settings","Reserved"}]
29762306a36Sopenharmony_ci *     uint32 Category;
29862306a36Sopenharmony_ci *     [read, ValueMap{"0","5","10","15","20","25","30",".."},
29962306a36Sopenharmony_ci *      Values{"Unknown","OK","Degraded/Warning","Minor Failure",
30062306a36Sopenharmony_ci *      "Major Failure","Critical Failure","Non-recoverable Error",
30162306a36Sopenharmony_ci *      "DMTF Reserved"}]
30262306a36Sopenharmony_ci *     uint32 PossibleSeverity;
30362306a36Sopenharmony_ci *     [read, ValueMap {"0","1","2","3","4","5","6","7","8","9",
30462306a36Sopenharmony_ci *      "10","11","12","13","14","15","16","17","18","..",
30562306a36Sopenharmony_ci *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
30662306a36Sopenharmony_ci *      "Stressed","Predictive Failure","Error",
30762306a36Sopenharmony_ci *      "Non-Recoverable Error","Starting","Stopping","Stopped",
30862306a36Sopenharmony_ci *      "In Service","No Contact","Lost Communication","Aborted",
30962306a36Sopenharmony_ci *      "Dormant","Supporting Entity in Error","Completed",
31062306a36Sopenharmony_ci *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
31162306a36Sopenharmony_ci *     uint32 PossibleStatus;
31262306a36Sopenharmony_ci *   };
31362306a36Sopenharmony_ci */
31462306a36Sopenharmony_cistruct hp_wmi_platform_events {
31562306a36Sopenharmony_ci	const char *name;
31662306a36Sopenharmony_ci	const char *description;
31762306a36Sopenharmony_ci	const char *source_namespace;
31862306a36Sopenharmony_ci	const char *source_class;
31962306a36Sopenharmony_ci	u32 category;
32062306a36Sopenharmony_ci	u32 possible_severity;
32162306a36Sopenharmony_ci	u32 possible_status;
32262306a36Sopenharmony_ci};
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci/*
32562306a36Sopenharmony_ci * struct hp_wmi_event - a HPBIOS_BIOSEvent instance
32662306a36Sopenharmony_ci *
32762306a36Sopenharmony_ci * Effective MOF definition [1] (corrected below from original):
32862306a36Sopenharmony_ci *
32962306a36Sopenharmony_ci *   #pragma namespace("\\\\.\\root\\WMI");
33062306a36Sopenharmony_ci *   class HPBIOS_BIOSEvent : WMIEvent {
33162306a36Sopenharmony_ci *     [read] string Name;
33262306a36Sopenharmony_ci *     [read] string Description;
33362306a36Sopenharmony_ci *     [read ValueMap {"0","1","2","3","4"}, Values {"Unknown",
33462306a36Sopenharmony_ci *      "Configuration Change","Button Pressed","Sensor",
33562306a36Sopenharmony_ci *      "BIOS Settings"}]
33662306a36Sopenharmony_ci *     uint32 Category;
33762306a36Sopenharmony_ci *     [read, ValueMap {"0","5","10","15","20","25","30"},
33862306a36Sopenharmony_ci *      Values {"Unknown","OK","Degraded/Warning",
33962306a36Sopenharmony_ci *      "Minor Failure","Major Failure","Critical Failure",
34062306a36Sopenharmony_ci *      "Non-recoverable Error"}]
34162306a36Sopenharmony_ci *     uint32 Severity;
34262306a36Sopenharmony_ci *     [read, ValueMap {"0","1","2","3","4","5","6","7","8",
34362306a36Sopenharmony_ci *      "9","10","11","12","13","14","15","16","17","18","..",
34462306a36Sopenharmony_ci *      "0x8000.."}, Values {"Unknown","Other","OK","Degraded",
34562306a36Sopenharmony_ci *      "Stressed","Predictive Failure","Error",
34662306a36Sopenharmony_ci *      "Non-Recoverable Error","Starting","Stopping","Stopped",
34762306a36Sopenharmony_ci *      "In Service","No Contact","Lost Communication","Aborted",
34862306a36Sopenharmony_ci *      "Dormant","Supporting Entity in Error","Completed",
34962306a36Sopenharmony_ci *      "Power Mode","DMTF Reserved","Vendor Reserved"}]
35062306a36Sopenharmony_ci *     uint32 Status;
35162306a36Sopenharmony_ci *   };
35262306a36Sopenharmony_ci */
35362306a36Sopenharmony_cistruct hp_wmi_event {
35462306a36Sopenharmony_ci	const char *name;
35562306a36Sopenharmony_ci	const char *description;
35662306a36Sopenharmony_ci	u32 category;
35762306a36Sopenharmony_ci};
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci/*
36062306a36Sopenharmony_ci * struct hp_wmi_info - sensor info
36162306a36Sopenharmony_ci * @nsensor: numeric sensor properties
36262306a36Sopenharmony_ci * @instance: its WMI instance number
36362306a36Sopenharmony_ci * @state: pointer to driver state
36462306a36Sopenharmony_ci * @has_alarm: whether sensor has an alarm flag
36562306a36Sopenharmony_ci * @alarm: alarm flag
36662306a36Sopenharmony_ci * @type: its hwmon sensor type
36762306a36Sopenharmony_ci * @cached_val: current sensor reading value, scaled for hwmon
36862306a36Sopenharmony_ci * @last_updated: when these readings were last updated
36962306a36Sopenharmony_ci */
37062306a36Sopenharmony_cistruct hp_wmi_info {
37162306a36Sopenharmony_ci	struct hp_wmi_numeric_sensor nsensor;
37262306a36Sopenharmony_ci	u8 instance;
37362306a36Sopenharmony_ci	void *state;			/* void *: Avoid forward declaration. */
37462306a36Sopenharmony_ci	bool has_alarm;
37562306a36Sopenharmony_ci	bool alarm;
37662306a36Sopenharmony_ci	enum hwmon_sensor_types type;
37762306a36Sopenharmony_ci	long cached_val;
37862306a36Sopenharmony_ci	unsigned long last_updated;	/* In jiffies. */
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci};
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci/*
38362306a36Sopenharmony_ci * struct hp_wmi_sensors - driver state
38462306a36Sopenharmony_ci * @wdev: pointer to the parent WMI device
38562306a36Sopenharmony_ci * @info_map: sensor info structs by hwmon type and channel number
38662306a36Sopenharmony_ci * @channel_count: count of hwmon channels by hwmon type
38762306a36Sopenharmony_ci * @has_intrusion: whether an intrusion sensor is present
38862306a36Sopenharmony_ci * @intrusion: intrusion flag
38962306a36Sopenharmony_ci * @lock: mutex to lock polling WMI and changes to driver state
39062306a36Sopenharmony_ci */
39162306a36Sopenharmony_cistruct hp_wmi_sensors {
39262306a36Sopenharmony_ci	struct wmi_device *wdev;
39362306a36Sopenharmony_ci	struct hp_wmi_info **info_map[hwmon_max];
39462306a36Sopenharmony_ci	u8 channel_count[hwmon_max];
39562306a36Sopenharmony_ci	bool has_intrusion;
39662306a36Sopenharmony_ci	bool intrusion;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	struct mutex lock;	/* Lock polling WMI and driver state changes. */
39962306a36Sopenharmony_ci};
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic bool is_raw_wmi_string(const u8 *pointer, u32 length)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	const u16 *ptr;
40462306a36Sopenharmony_ci	u16 len;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	/* WMI strings are length-prefixed UTF-16 [5]. */
40762306a36Sopenharmony_ci	if (length <= sizeof(*ptr))
40862306a36Sopenharmony_ci		return false;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	length -= sizeof(*ptr);
41162306a36Sopenharmony_ci	ptr = (const u16 *)pointer;
41262306a36Sopenharmony_ci	len = *ptr;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	return len <= length && !(len & 1);
41562306a36Sopenharmony_ci}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic char *convert_raw_wmi_string(const u8 *buf)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	const wchar_t *src;
42062306a36Sopenharmony_ci	unsigned int cps;
42162306a36Sopenharmony_ci	unsigned int len;
42262306a36Sopenharmony_ci	char *dst;
42362306a36Sopenharmony_ci	int i;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	src = (const wchar_t *)buf;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	/* Count UTF-16 code points. Exclude trailing null padding. */
42862306a36Sopenharmony_ci	cps = *src / sizeof(*src);
42962306a36Sopenharmony_ci	while (cps && !src[cps])
43062306a36Sopenharmony_ci		cps--;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	/* Each code point becomes up to 3 UTF-8 characters. */
43362306a36Sopenharmony_ci	len = min(cps * 3, HP_WMI_MAX_STR_SIZE - 1);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	dst = kmalloc((len + 1) * sizeof(*dst), GFP_KERNEL);
43662306a36Sopenharmony_ci	if (!dst)
43762306a36Sopenharmony_ci		return NULL;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len);
44062306a36Sopenharmony_ci	dst[i] = '\0';
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	return dst;
44362306a36Sopenharmony_ci}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci/* hp_wmi_strdup - devm_kstrdup, but length-limited */
44662306a36Sopenharmony_cistatic char *hp_wmi_strdup(struct device *dev, const char *src)
44762306a36Sopenharmony_ci{
44862306a36Sopenharmony_ci	char *dst;
44962306a36Sopenharmony_ci	size_t len;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	len = strnlen(src, HP_WMI_MAX_STR_SIZE - 1);
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	dst = devm_kmalloc(dev, (len + 1) * sizeof(*dst), GFP_KERNEL);
45462306a36Sopenharmony_ci	if (!dst)
45562306a36Sopenharmony_ci		return NULL;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	strscpy(dst, src, len + 1);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	return dst;
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci/* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */
46362306a36Sopenharmony_cistatic char *hp_wmi_wstrdup(struct device *dev, const u8 *buf)
46462306a36Sopenharmony_ci{
46562306a36Sopenharmony_ci	char *src;
46662306a36Sopenharmony_ci	char *dst;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	src = convert_raw_wmi_string(buf);
46962306a36Sopenharmony_ci	if (!src)
47062306a36Sopenharmony_ci		return NULL;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	dst = hp_wmi_strdup(dev, strim(src));	/* Note: Copy is trimmed. */
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	kfree(src);
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	return dst;
47762306a36Sopenharmony_ci}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci/*
48062306a36Sopenharmony_ci * hp_wmi_get_wobj - poll WMI for a WMI object instance
48162306a36Sopenharmony_ci * @guid: WMI object GUID
48262306a36Sopenharmony_ci * @instance: WMI object instance number
48362306a36Sopenharmony_ci *
48462306a36Sopenharmony_ci * Returns a new WMI object instance on success, or NULL on error.
48562306a36Sopenharmony_ci * Caller must kfree() the result.
48662306a36Sopenharmony_ci */
48762306a36Sopenharmony_cistatic union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance)
48862306a36Sopenharmony_ci{
48962306a36Sopenharmony_ci	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
49062306a36Sopenharmony_ci	acpi_status err;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	err = wmi_query_block(guid, instance, &out);
49362306a36Sopenharmony_ci	if (ACPI_FAILURE(err))
49462306a36Sopenharmony_ci		return NULL;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	return out.pointer;
49762306a36Sopenharmony_ci}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci/* hp_wmi_wobj_instance_count - find count of WMI object instances */
50062306a36Sopenharmony_cistatic u8 hp_wmi_wobj_instance_count(const char *guid)
50162306a36Sopenharmony_ci{
50262306a36Sopenharmony_ci	int count;
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	count = wmi_instance_count(guid);
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	return clamp(count, 0, (int)HP_WMI_MAX_INSTANCES);
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cistatic int check_wobj(const union acpi_object *wobj,
51062306a36Sopenharmony_ci		      const acpi_object_type property_map[], int last_prop)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	acpi_object_type type = wobj->type;
51362306a36Sopenharmony_ci	acpi_object_type valid_type;
51462306a36Sopenharmony_ci	union acpi_object *elements;
51562306a36Sopenharmony_ci	u32 elem_count;
51662306a36Sopenharmony_ci	int prop;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	if (type != ACPI_TYPE_PACKAGE)
51962306a36Sopenharmony_ci		return -EINVAL;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	elem_count = wobj->package.count;
52262306a36Sopenharmony_ci	if (elem_count != last_prop + 1)
52362306a36Sopenharmony_ci		return -EINVAL;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	elements = wobj->package.elements;
52662306a36Sopenharmony_ci	for (prop = 0; prop <= last_prop; prop++) {
52762306a36Sopenharmony_ci		type = elements[prop].type;
52862306a36Sopenharmony_ci		valid_type = property_map[prop];
52962306a36Sopenharmony_ci		if (type != valid_type) {
53062306a36Sopenharmony_ci			if (type == ACPI_TYPE_BUFFER &&
53162306a36Sopenharmony_ci			    valid_type == ACPI_TYPE_STRING &&
53262306a36Sopenharmony_ci			    is_raw_wmi_string(elements[prop].buffer.pointer,
53362306a36Sopenharmony_ci					      elements[prop].buffer.length))
53462306a36Sopenharmony_ci				continue;
53562306a36Sopenharmony_ci			return -EINVAL;
53662306a36Sopenharmony_ci		}
53762306a36Sopenharmony_ci	}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	return 0;
54062306a36Sopenharmony_ci}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_cistatic int extract_acpi_value(struct device *dev,
54362306a36Sopenharmony_ci			      union acpi_object *element,
54462306a36Sopenharmony_ci			      acpi_object_type type,
54562306a36Sopenharmony_ci			      u32 *out_value, char **out_string)
54662306a36Sopenharmony_ci{
54762306a36Sopenharmony_ci	switch (type) {
54862306a36Sopenharmony_ci	case ACPI_TYPE_INTEGER:
54962306a36Sopenharmony_ci		*out_value = element->integer.value;
55062306a36Sopenharmony_ci		break;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	case ACPI_TYPE_STRING:
55362306a36Sopenharmony_ci		*out_string = element->type == ACPI_TYPE_BUFFER ?
55462306a36Sopenharmony_ci			hp_wmi_wstrdup(dev, element->buffer.pointer) :
55562306a36Sopenharmony_ci			hp_wmi_strdup(dev, strim(element->string.pointer));
55662306a36Sopenharmony_ci		if (!*out_string)
55762306a36Sopenharmony_ci			return -ENOMEM;
55862306a36Sopenharmony_ci		break;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	default:
56162306a36Sopenharmony_ci		return -EINVAL;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	return 0;
56562306a36Sopenharmony_ci}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci/*
56862306a36Sopenharmony_ci * check_numeric_sensor_wobj - validate a HPBIOS_BIOSNumericSensor instance
56962306a36Sopenharmony_ci * @wobj: pointer to WMI object instance to check
57062306a36Sopenharmony_ci * @out_size: out pointer to count of possible states
57162306a36Sopenharmony_ci * @out_is_new: out pointer to whether this is a "new" variant object
57262306a36Sopenharmony_ci *
57362306a36Sopenharmony_ci * Returns 0 on success, or a negative error code on error.
57462306a36Sopenharmony_ci */
57562306a36Sopenharmony_cistatic int check_numeric_sensor_wobj(const union acpi_object *wobj,
57662306a36Sopenharmony_ci				     u8 *out_size, bool *out_is_new)
57762306a36Sopenharmony_ci{
57862306a36Sopenharmony_ci	acpi_object_type type = wobj->type;
57962306a36Sopenharmony_ci	int prop = HP_WMI_PROPERTY_NAME;
58062306a36Sopenharmony_ci	acpi_object_type valid_type;
58162306a36Sopenharmony_ci	union acpi_object *elements;
58262306a36Sopenharmony_ci	u32 elem_count;
58362306a36Sopenharmony_ci	int last_prop;
58462306a36Sopenharmony_ci	bool is_new;
58562306a36Sopenharmony_ci	u8 count;
58662306a36Sopenharmony_ci	u32 j;
58762306a36Sopenharmony_ci	u32 i;
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	if (type != ACPI_TYPE_PACKAGE)
59062306a36Sopenharmony_ci		return -EINVAL;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	/*
59362306a36Sopenharmony_ci	 * elements is a variable-length array of ACPI objects, one for
59462306a36Sopenharmony_ci	 * each property of the WMI object instance, except that the
59562306a36Sopenharmony_ci	 * strings in PossibleStates[] are flattened into this array
59662306a36Sopenharmony_ci	 * as if each individual string were a property by itself.
59762306a36Sopenharmony_ci	 */
59862306a36Sopenharmony_ci	elements = wobj->package.elements;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	elem_count = wobj->package.count;
60162306a36Sopenharmony_ci	if (elem_count <= HP_WMI_PROPERTY_SIZE ||
60262306a36Sopenharmony_ci	    elem_count > HP_WMI_MAX_PROPERTIES)
60362306a36Sopenharmony_ci		return -EINVAL;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	type = elements[HP_WMI_PROPERTY_SIZE].type;
60662306a36Sopenharmony_ci	switch (type) {
60762306a36Sopenharmony_ci	case ACPI_TYPE_INTEGER:
60862306a36Sopenharmony_ci		is_new = true;
60962306a36Sopenharmony_ci		last_prop = HP_WMI_PROPERTY_RATE_UNITS;
61062306a36Sopenharmony_ci		break;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	case ACPI_TYPE_STRING:
61362306a36Sopenharmony_ci		is_new = false;
61462306a36Sopenharmony_ci		last_prop = HP_WMI_PROPERTY_CURRENT_READING;
61562306a36Sopenharmony_ci		break;
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	default:
61862306a36Sopenharmony_ci		return -EINVAL;
61962306a36Sopenharmony_ci	}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	/*
62262306a36Sopenharmony_ci	 * In general, the count of PossibleStates[] must be > 0.
62362306a36Sopenharmony_ci	 * Also, the old variant lacks the Size property, so we may need to
62462306a36Sopenharmony_ci	 * reduce the value of last_prop by 1 when doing arithmetic with it.
62562306a36Sopenharmony_ci	 */
62662306a36Sopenharmony_ci	if (elem_count < last_prop - !is_new + 1)
62762306a36Sopenharmony_ci		return -EINVAL;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	count = elem_count - (last_prop - !is_new);
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) {
63262306a36Sopenharmony_ci		type = elements[i].type;
63362306a36Sopenharmony_ci		valid_type = hp_wmi_property_map[prop];
63462306a36Sopenharmony_ci		if (type != valid_type)
63562306a36Sopenharmony_ci			return -EINVAL;
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci		switch (prop) {
63862306a36Sopenharmony_ci		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
63962306a36Sopenharmony_ci			/* Old variant: CurrentState follows OperationalStatus. */
64062306a36Sopenharmony_ci			if (!is_new)
64162306a36Sopenharmony_ci				prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
64262306a36Sopenharmony_ci			break;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci		case HP_WMI_PROPERTY_SIZE:
64562306a36Sopenharmony_ci			/* New variant: Size == count of PossibleStates[]. */
64662306a36Sopenharmony_ci			if (count != elements[i].integer.value)
64762306a36Sopenharmony_ci				return -EINVAL;
64862306a36Sopenharmony_ci			break;
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci		case HP_WMI_PROPERTY_POSSIBLE_STATES:
65162306a36Sopenharmony_ci			/* PossibleStates[0] has already been type-checked. */
65262306a36Sopenharmony_ci			for (j = 0; i + 1 < elem_count && j + 1 < count; j++) {
65362306a36Sopenharmony_ci				type = elements[++i].type;
65462306a36Sopenharmony_ci				if (type != valid_type)
65562306a36Sopenharmony_ci					return -EINVAL;
65662306a36Sopenharmony_ci			}
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci			/* Old variant: BaseUnits follows PossibleStates[]. */
65962306a36Sopenharmony_ci			if (!is_new)
66062306a36Sopenharmony_ci				prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
66162306a36Sopenharmony_ci			break;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci		case HP_WMI_PROPERTY_CURRENT_STATE:
66462306a36Sopenharmony_ci			/* Old variant: PossibleStates[] follows CurrentState. */
66562306a36Sopenharmony_ci			if (!is_new)
66662306a36Sopenharmony_ci				prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
66762306a36Sopenharmony_ci			break;
66862306a36Sopenharmony_ci		}
66962306a36Sopenharmony_ci	}
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	if (prop != last_prop + 1)
67262306a36Sopenharmony_ci		return -EINVAL;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	*out_size = count;
67562306a36Sopenharmony_ci	*out_is_new = is_new;
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	return 0;
67862306a36Sopenharmony_ci}
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_cistatic int
68162306a36Sopenharmony_cinumeric_sensor_is_connected(const struct hp_wmi_numeric_sensor *nsensor)
68262306a36Sopenharmony_ci{
68362306a36Sopenharmony_ci	u32 operational_status = nsensor->operational_status;
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	return operational_status != HP_WMI_STATUS_NO_CONTACT;
68662306a36Sopenharmony_ci}
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_cistatic int numeric_sensor_has_fault(const struct hp_wmi_numeric_sensor *nsensor)
68962306a36Sopenharmony_ci{
69062306a36Sopenharmony_ci	u32 operational_status = nsensor->operational_status;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci	switch (operational_status) {
69362306a36Sopenharmony_ci	case HP_WMI_STATUS_DEGRADED:
69462306a36Sopenharmony_ci	case HP_WMI_STATUS_STRESSED:		/* e.g. Overload, overtemp. */
69562306a36Sopenharmony_ci	case HP_WMI_STATUS_PREDICTIVE_FAILURE:	/* e.g. Fan removed. */
69662306a36Sopenharmony_ci	case HP_WMI_STATUS_ERROR:
69762306a36Sopenharmony_ci	case HP_WMI_STATUS_NON_RECOVERABLE_ERROR:
69862306a36Sopenharmony_ci	case HP_WMI_STATUS_NO_CONTACT:
69962306a36Sopenharmony_ci	case HP_WMI_STATUS_LOST_COMMUNICATION:
70062306a36Sopenharmony_ci	case HP_WMI_STATUS_ABORTED:
70162306a36Sopenharmony_ci	case HP_WMI_STATUS_SUPPORTING_ENTITY_IN_ERROR:
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	/* Assume combination by addition; bitwise OR doesn't make sense. */
70462306a36Sopenharmony_ci	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_DEGRADED:
70562306a36Sopenharmony_ci	case HP_WMI_STATUS_COMPLETED + HP_WMI_STATUS_ERROR:
70662306a36Sopenharmony_ci		return true;
70762306a36Sopenharmony_ci	}
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci	return false;
71062306a36Sopenharmony_ci}
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci/* scale_numeric_sensor - scale sensor reading for hwmon */
71362306a36Sopenharmony_cistatic long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
71462306a36Sopenharmony_ci{
71562306a36Sopenharmony_ci	u32 current_reading = nsensor->current_reading;
71662306a36Sopenharmony_ci	s32 unit_modifier = nsensor->unit_modifier;
71762306a36Sopenharmony_ci	u32 sensor_type = nsensor->sensor_type;
71862306a36Sopenharmony_ci	u32 base_units = nsensor->base_units;
71962306a36Sopenharmony_ci	s32 target_modifier;
72062306a36Sopenharmony_ci	long val;
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	/* Fan readings are in RPM units; others are in milliunits. */
72362306a36Sopenharmony_ci	target_modifier = sensor_type == HP_WMI_TYPE_AIR_FLOW ? 0 : -3;
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	val = current_reading;
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_ci	for (; unit_modifier < target_modifier; unit_modifier++)
72862306a36Sopenharmony_ci		val = DIV_ROUND_CLOSEST(val, 10);
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	for (; unit_modifier > target_modifier; unit_modifier--) {
73162306a36Sopenharmony_ci		if (val > LONG_MAX / 10) {
73262306a36Sopenharmony_ci			val = LONG_MAX;
73362306a36Sopenharmony_ci			break;
73462306a36Sopenharmony_ci		}
73562306a36Sopenharmony_ci		val *= 10;
73662306a36Sopenharmony_ci	}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	if (sensor_type == HP_WMI_TYPE_TEMPERATURE) {
73962306a36Sopenharmony_ci		switch (base_units) {
74062306a36Sopenharmony_ci		case HP_WMI_UNITS_DEGREES_F:
74162306a36Sopenharmony_ci			val -= MILLI * 32;
74262306a36Sopenharmony_ci			val = val <= LONG_MAX / 5 ?
74362306a36Sopenharmony_ci				      DIV_ROUND_CLOSEST(val * 5, 9) :
74462306a36Sopenharmony_ci				      DIV_ROUND_CLOSEST(val, 9) * 5;
74562306a36Sopenharmony_ci			break;
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci		case HP_WMI_UNITS_DEGREES_K:
74862306a36Sopenharmony_ci			val = milli_kelvin_to_millicelsius(val);
74962306a36Sopenharmony_ci			break;
75062306a36Sopenharmony_ci		}
75162306a36Sopenharmony_ci	}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	return val;
75462306a36Sopenharmony_ci}
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci/*
75762306a36Sopenharmony_ci * classify_numeric_sensor - classify a numeric sensor
75862306a36Sopenharmony_ci * @nsensor: pointer to numeric sensor struct
75962306a36Sopenharmony_ci *
76062306a36Sopenharmony_ci * Returns an enum hp_wmi_type value on success,
76162306a36Sopenharmony_ci * or a negative value if the sensor type is unsupported.
76262306a36Sopenharmony_ci */
76362306a36Sopenharmony_cistatic int classify_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor)
76462306a36Sopenharmony_ci{
76562306a36Sopenharmony_ci	u32 sensor_type = nsensor->sensor_type;
76662306a36Sopenharmony_ci	u32 base_units = nsensor->base_units;
76762306a36Sopenharmony_ci	const char *name = nsensor->name;
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	switch (sensor_type) {
77062306a36Sopenharmony_ci	case HP_WMI_TYPE_TEMPERATURE:
77162306a36Sopenharmony_ci		/*
77262306a36Sopenharmony_ci		 * Some systems have sensors named "X Thermal Index" in "Other"
77362306a36Sopenharmony_ci		 * units. Tested CPU sensor examples were found to be in °C,
77462306a36Sopenharmony_ci		 * albeit perhaps "differently" accurate; e.g. readings were
77562306a36Sopenharmony_ci		 * reliably -6°C vs. coretemp on a HP Compaq Elite 8300, and
77662306a36Sopenharmony_ci		 * +8°C on an EliteOne G1 800. But this is still within the
77762306a36Sopenharmony_ci		 * realm of plausibility for cheaply implemented motherboard
77862306a36Sopenharmony_ci		 * sensors, and chassis readings were about as expected.
77962306a36Sopenharmony_ci		 */
78062306a36Sopenharmony_ci		if ((base_units == HP_WMI_UNITS_OTHER &&
78162306a36Sopenharmony_ci		     strstr(name, HP_WMI_PATTERN_TEMP_SENSOR)) ||
78262306a36Sopenharmony_ci		    base_units == HP_WMI_UNITS_DEGREES_C ||
78362306a36Sopenharmony_ci		    base_units == HP_WMI_UNITS_DEGREES_F ||
78462306a36Sopenharmony_ci		    base_units == HP_WMI_UNITS_DEGREES_K)
78562306a36Sopenharmony_ci			return HP_WMI_TYPE_TEMPERATURE;
78662306a36Sopenharmony_ci		break;
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	case HP_WMI_TYPE_VOLTAGE:
78962306a36Sopenharmony_ci		if (base_units == HP_WMI_UNITS_VOLTS)
79062306a36Sopenharmony_ci			return HP_WMI_TYPE_VOLTAGE;
79162306a36Sopenharmony_ci		break;
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	case HP_WMI_TYPE_CURRENT:
79462306a36Sopenharmony_ci		if (base_units == HP_WMI_UNITS_AMPS)
79562306a36Sopenharmony_ci			return HP_WMI_TYPE_CURRENT;
79662306a36Sopenharmony_ci		break;
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	case HP_WMI_TYPE_AIR_FLOW:
79962306a36Sopenharmony_ci		/*
80062306a36Sopenharmony_ci		 * Strangely, HP considers fan RPM sensor type to be
80162306a36Sopenharmony_ci		 * "Air Flow" instead of the more intuitive "Tachometer".
80262306a36Sopenharmony_ci		 */
80362306a36Sopenharmony_ci		if (base_units == HP_WMI_UNITS_RPM)
80462306a36Sopenharmony_ci			return HP_WMI_TYPE_AIR_FLOW;
80562306a36Sopenharmony_ci		break;
80662306a36Sopenharmony_ci	}
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	return -EINVAL;
80962306a36Sopenharmony_ci}
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_cistatic int
81262306a36Sopenharmony_cipopulate_numeric_sensor_from_wobj(struct device *dev,
81362306a36Sopenharmony_ci				  struct hp_wmi_numeric_sensor *nsensor,
81462306a36Sopenharmony_ci				  union acpi_object *wobj, bool *out_is_new)
81562306a36Sopenharmony_ci{
81662306a36Sopenharmony_ci	int last_prop = HP_WMI_PROPERTY_RATE_UNITS;
81762306a36Sopenharmony_ci	int prop = HP_WMI_PROPERTY_NAME;
81862306a36Sopenharmony_ci	const char **possible_states;
81962306a36Sopenharmony_ci	union acpi_object *element;
82062306a36Sopenharmony_ci	acpi_object_type type;
82162306a36Sopenharmony_ci	char *string;
82262306a36Sopenharmony_ci	bool is_new;
82362306a36Sopenharmony_ci	u32 value;
82462306a36Sopenharmony_ci	u8 size;
82562306a36Sopenharmony_ci	int err;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	err = check_numeric_sensor_wobj(wobj, &size, &is_new);
82862306a36Sopenharmony_ci	if (err)
82962306a36Sopenharmony_ci		return err;
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci	possible_states = devm_kcalloc(dev, size, sizeof(*possible_states),
83262306a36Sopenharmony_ci				       GFP_KERNEL);
83362306a36Sopenharmony_ci	if (!possible_states)
83462306a36Sopenharmony_ci		return -ENOMEM;
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	element = wobj->package.elements;
83762306a36Sopenharmony_ci	nsensor->possible_states = possible_states;
83862306a36Sopenharmony_ci	nsensor->size = size;
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	if (!is_new)
84162306a36Sopenharmony_ci		last_prop = HP_WMI_PROPERTY_CURRENT_READING;
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	for (; prop <= last_prop; prop++) {
84462306a36Sopenharmony_ci		type = hp_wmi_property_map[prop];
84562306a36Sopenharmony_ci
84662306a36Sopenharmony_ci		err = extract_acpi_value(dev, element, type, &value, &string);
84762306a36Sopenharmony_ci		if (err)
84862306a36Sopenharmony_ci			return err;
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci		element++;
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_ci		switch (prop) {
85362306a36Sopenharmony_ci		case HP_WMI_PROPERTY_NAME:
85462306a36Sopenharmony_ci			nsensor->name = string;
85562306a36Sopenharmony_ci			break;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci		case HP_WMI_PROPERTY_DESCRIPTION:
85862306a36Sopenharmony_ci			nsensor->description = string;
85962306a36Sopenharmony_ci			break;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci		case HP_WMI_PROPERTY_SENSOR_TYPE:
86262306a36Sopenharmony_ci			if (value > HP_WMI_TYPE_AIR_FLOW)
86362306a36Sopenharmony_ci				return -EINVAL;
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_ci			nsensor->sensor_type = value;
86662306a36Sopenharmony_ci			break;
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci		case HP_WMI_PROPERTY_OTHER_SENSOR_TYPE:
86962306a36Sopenharmony_ci			nsensor->other_sensor_type = string;
87062306a36Sopenharmony_ci			break;
87162306a36Sopenharmony_ci
87262306a36Sopenharmony_ci		case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
87362306a36Sopenharmony_ci			nsensor->operational_status = value;
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_ci			/* Old variant: CurrentState follows OperationalStatus. */
87662306a36Sopenharmony_ci			if (!is_new)
87762306a36Sopenharmony_ci				prop = HP_WMI_PROPERTY_CURRENT_STATE - 1;
87862306a36Sopenharmony_ci			break;
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci		case HP_WMI_PROPERTY_SIZE:
88162306a36Sopenharmony_ci			break;			/* Already set. */
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci		case HP_WMI_PROPERTY_POSSIBLE_STATES:
88462306a36Sopenharmony_ci			*possible_states++ = string;
88562306a36Sopenharmony_ci			if (--size)
88662306a36Sopenharmony_ci				prop--;
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci			/* Old variant: BaseUnits follows PossibleStates[]. */
88962306a36Sopenharmony_ci			if (!is_new && !size)
89062306a36Sopenharmony_ci				prop = HP_WMI_PROPERTY_BASE_UNITS - 1;
89162306a36Sopenharmony_ci			break;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci		case HP_WMI_PROPERTY_CURRENT_STATE:
89462306a36Sopenharmony_ci			nsensor->current_state = string;
89562306a36Sopenharmony_ci
89662306a36Sopenharmony_ci			/* Old variant: PossibleStates[] follows CurrentState. */
89762306a36Sopenharmony_ci			if (!is_new)
89862306a36Sopenharmony_ci				prop = HP_WMI_PROPERTY_POSSIBLE_STATES - 1;
89962306a36Sopenharmony_ci			break;
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci		case HP_WMI_PROPERTY_BASE_UNITS:
90262306a36Sopenharmony_ci			nsensor->base_units = value;
90362306a36Sopenharmony_ci			break;
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ci		case HP_WMI_PROPERTY_UNIT_MODIFIER:
90662306a36Sopenharmony_ci			/* UnitModifier is signed. */
90762306a36Sopenharmony_ci			nsensor->unit_modifier = (s32)value;
90862306a36Sopenharmony_ci			break;
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci		case HP_WMI_PROPERTY_CURRENT_READING:
91162306a36Sopenharmony_ci			nsensor->current_reading = value;
91262306a36Sopenharmony_ci			break;
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci		case HP_WMI_PROPERTY_RATE_UNITS:
91562306a36Sopenharmony_ci			nsensor->rate_units = value;
91662306a36Sopenharmony_ci			break;
91762306a36Sopenharmony_ci
91862306a36Sopenharmony_ci		default:
91962306a36Sopenharmony_ci			return -EINVAL;
92062306a36Sopenharmony_ci		}
92162306a36Sopenharmony_ci	}
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	*out_is_new = is_new;
92462306a36Sopenharmony_ci
92562306a36Sopenharmony_ci	return 0;
92662306a36Sopenharmony_ci}
92762306a36Sopenharmony_ci
92862306a36Sopenharmony_ci/* update_numeric_sensor_from_wobj - update fungible sensor properties */
92962306a36Sopenharmony_cistatic void
93062306a36Sopenharmony_ciupdate_numeric_sensor_from_wobj(struct device *dev,
93162306a36Sopenharmony_ci				struct hp_wmi_numeric_sensor *nsensor,
93262306a36Sopenharmony_ci				const union acpi_object *wobj)
93362306a36Sopenharmony_ci{
93462306a36Sopenharmony_ci	const union acpi_object *elements;
93562306a36Sopenharmony_ci	const union acpi_object *element;
93662306a36Sopenharmony_ci	const char *new_string;
93762306a36Sopenharmony_ci	char *trimmed;
93862306a36Sopenharmony_ci	char *string;
93962306a36Sopenharmony_ci	bool is_new;
94062306a36Sopenharmony_ci	int offset;
94162306a36Sopenharmony_ci	u8 size;
94262306a36Sopenharmony_ci	int err;
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci	err = check_numeric_sensor_wobj(wobj, &size, &is_new);
94562306a36Sopenharmony_ci	if (err)
94662306a36Sopenharmony_ci		return;
94762306a36Sopenharmony_ci
94862306a36Sopenharmony_ci	elements = wobj->package.elements;
94962306a36Sopenharmony_ci
95062306a36Sopenharmony_ci	element = &elements[HP_WMI_PROPERTY_OPERATIONAL_STATUS];
95162306a36Sopenharmony_ci	nsensor->operational_status = element->integer.value;
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	/*
95462306a36Sopenharmony_ci	 * In general, an index offset is needed after PossibleStates[0].
95562306a36Sopenharmony_ci	 * On a new variant, CurrentState is after PossibleStates[]. This is
95662306a36Sopenharmony_ci	 * not the case on an old variant, but we still need to offset the
95762306a36Sopenharmony_ci	 * read because CurrentState is where Size would be on a new variant.
95862306a36Sopenharmony_ci	 */
95962306a36Sopenharmony_ci	offset = is_new ? size - 1 : -2;
96062306a36Sopenharmony_ci
96162306a36Sopenharmony_ci	element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
96262306a36Sopenharmony_ci	string = element->type == ACPI_TYPE_BUFFER ?
96362306a36Sopenharmony_ci		convert_raw_wmi_string(element->buffer.pointer) :
96462306a36Sopenharmony_ci		element->string.pointer;
96562306a36Sopenharmony_ci
96662306a36Sopenharmony_ci	if (string) {
96762306a36Sopenharmony_ci		trimmed = strim(string);
96862306a36Sopenharmony_ci		if (strcmp(trimmed, nsensor->current_state)) {
96962306a36Sopenharmony_ci			new_string = hp_wmi_strdup(dev, trimmed);
97062306a36Sopenharmony_ci			if (new_string) {
97162306a36Sopenharmony_ci				devm_kfree(dev, nsensor->current_state);
97262306a36Sopenharmony_ci				nsensor->current_state = new_string;
97362306a36Sopenharmony_ci			}
97462306a36Sopenharmony_ci		}
97562306a36Sopenharmony_ci		if (element->type == ACPI_TYPE_BUFFER)
97662306a36Sopenharmony_ci			kfree(string);
97762306a36Sopenharmony_ci	}
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_ci	/* Old variant: -2 (not -1) because it lacks the Size property. */
98062306a36Sopenharmony_ci	if (!is_new)
98162306a36Sopenharmony_ci		offset = (int)size - 2;	/* size is > 0, i.e. may be 1. */
98262306a36Sopenharmony_ci
98362306a36Sopenharmony_ci	element = &elements[HP_WMI_PROPERTY_UNIT_MODIFIER + offset];
98462306a36Sopenharmony_ci	nsensor->unit_modifier = (s32)element->integer.value;
98562306a36Sopenharmony_ci
98662306a36Sopenharmony_ci	element = &elements[HP_WMI_PROPERTY_CURRENT_READING + offset];
98762306a36Sopenharmony_ci	nsensor->current_reading = element->integer.value;
98862306a36Sopenharmony_ci}
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci/*
99162306a36Sopenharmony_ci * check_platform_events_wobj - validate a HPBIOS_PlatformEvents instance
99262306a36Sopenharmony_ci * @wobj: pointer to WMI object instance to check
99362306a36Sopenharmony_ci *
99462306a36Sopenharmony_ci * Returns 0 on success, or a negative error code on error.
99562306a36Sopenharmony_ci */
99662306a36Sopenharmony_cistatic int check_platform_events_wobj(const union acpi_object *wobj)
99762306a36Sopenharmony_ci{
99862306a36Sopenharmony_ci	return check_wobj(wobj, hp_wmi_platform_events_property_map,
99962306a36Sopenharmony_ci			  HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS);
100062306a36Sopenharmony_ci}
100162306a36Sopenharmony_ci
100262306a36Sopenharmony_cistatic int
100362306a36Sopenharmony_cipopulate_platform_events_from_wobj(struct device *dev,
100462306a36Sopenharmony_ci				   struct hp_wmi_platform_events *pevents,
100562306a36Sopenharmony_ci				   union acpi_object *wobj)
100662306a36Sopenharmony_ci{
100762306a36Sopenharmony_ci	int last_prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS;
100862306a36Sopenharmony_ci	int prop = HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME;
100962306a36Sopenharmony_ci	union acpi_object *element;
101062306a36Sopenharmony_ci	acpi_object_type type;
101162306a36Sopenharmony_ci	char *string;
101262306a36Sopenharmony_ci	u32 value;
101362306a36Sopenharmony_ci	int err;
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_ci	err = check_platform_events_wobj(wobj);
101662306a36Sopenharmony_ci	if (err)
101762306a36Sopenharmony_ci		return err;
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	element = wobj->package.elements;
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci	for (; prop <= last_prop; prop++, element++) {
102262306a36Sopenharmony_ci		type = hp_wmi_platform_events_property_map[prop];
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_ci		err = extract_acpi_value(dev, element, type, &value, &string);
102562306a36Sopenharmony_ci		if (err)
102662306a36Sopenharmony_ci			return err;
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci		switch (prop) {
102962306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_NAME:
103062306a36Sopenharmony_ci			pevents->name = string;
103162306a36Sopenharmony_ci			break;
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_DESCRIPTION:
103462306a36Sopenharmony_ci			pevents->description = string;
103562306a36Sopenharmony_ci			break;
103662306a36Sopenharmony_ci
103762306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_NAMESPACE:
103862306a36Sopenharmony_ci			if (strcasecmp(HP_WMI_EVENT_NAMESPACE, string))
103962306a36Sopenharmony_ci				return -EINVAL;
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci			pevents->source_namespace = string;
104262306a36Sopenharmony_ci			break;
104362306a36Sopenharmony_ci
104462306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_SOURCE_CLASS:
104562306a36Sopenharmony_ci			if (strcasecmp(HP_WMI_EVENT_CLASS, string))
104662306a36Sopenharmony_ci				return -EINVAL;
104762306a36Sopenharmony_ci
104862306a36Sopenharmony_ci			pevents->source_class = string;
104962306a36Sopenharmony_ci			break;
105062306a36Sopenharmony_ci
105162306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_CATEGORY:
105262306a36Sopenharmony_ci			pevents->category = value;
105362306a36Sopenharmony_ci			break;
105462306a36Sopenharmony_ci
105562306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_SEVERITY:
105662306a36Sopenharmony_ci			pevents->possible_severity = value;
105762306a36Sopenharmony_ci			break;
105862306a36Sopenharmony_ci
105962306a36Sopenharmony_ci		case HP_WMI_PLATFORM_EVENTS_PROPERTY_POSSIBLE_STATUS:
106062306a36Sopenharmony_ci			pevents->possible_status = value;
106162306a36Sopenharmony_ci			break;
106262306a36Sopenharmony_ci
106362306a36Sopenharmony_ci		default:
106462306a36Sopenharmony_ci			return -EINVAL;
106562306a36Sopenharmony_ci		}
106662306a36Sopenharmony_ci	}
106762306a36Sopenharmony_ci
106862306a36Sopenharmony_ci	return 0;
106962306a36Sopenharmony_ci}
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_ci/*
107262306a36Sopenharmony_ci * check_event_wobj - validate a HPBIOS_BIOSEvent instance
107362306a36Sopenharmony_ci * @wobj: pointer to WMI object instance to check
107462306a36Sopenharmony_ci *
107562306a36Sopenharmony_ci * Returns 0 on success, or a negative error code on error.
107662306a36Sopenharmony_ci */
107762306a36Sopenharmony_cistatic int check_event_wobj(const union acpi_object *wobj)
107862306a36Sopenharmony_ci{
107962306a36Sopenharmony_ci	return check_wobj(wobj, hp_wmi_event_property_map,
108062306a36Sopenharmony_ci			  HP_WMI_EVENT_PROPERTY_STATUS);
108162306a36Sopenharmony_ci}
108262306a36Sopenharmony_ci
108362306a36Sopenharmony_cistatic int populate_event_from_wobj(struct device *dev,
108462306a36Sopenharmony_ci				    struct hp_wmi_event *event,
108562306a36Sopenharmony_ci				    union acpi_object *wobj)
108662306a36Sopenharmony_ci{
108762306a36Sopenharmony_ci	int prop = HP_WMI_EVENT_PROPERTY_NAME;
108862306a36Sopenharmony_ci	union acpi_object *element;
108962306a36Sopenharmony_ci	acpi_object_type type;
109062306a36Sopenharmony_ci	char *string;
109162306a36Sopenharmony_ci	u32 value;
109262306a36Sopenharmony_ci	int err;
109362306a36Sopenharmony_ci
109462306a36Sopenharmony_ci	err = check_event_wobj(wobj);
109562306a36Sopenharmony_ci	if (err)
109662306a36Sopenharmony_ci		return err;
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_ci	element = wobj->package.elements;
109962306a36Sopenharmony_ci
110062306a36Sopenharmony_ci	for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
110162306a36Sopenharmony_ci		type = hp_wmi_event_property_map[prop];
110262306a36Sopenharmony_ci
110362306a36Sopenharmony_ci		err = extract_acpi_value(dev, element, type, &value, &string);
110462306a36Sopenharmony_ci		if (err)
110562306a36Sopenharmony_ci			return err;
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_ci		switch (prop) {
110862306a36Sopenharmony_ci		case HP_WMI_EVENT_PROPERTY_NAME:
110962306a36Sopenharmony_ci			event->name = string;
111062306a36Sopenharmony_ci			break;
111162306a36Sopenharmony_ci
111262306a36Sopenharmony_ci		case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
111362306a36Sopenharmony_ci			event->description = string;
111462306a36Sopenharmony_ci			break;
111562306a36Sopenharmony_ci
111662306a36Sopenharmony_ci		case HP_WMI_EVENT_PROPERTY_CATEGORY:
111762306a36Sopenharmony_ci			event->category = value;
111862306a36Sopenharmony_ci			break;
111962306a36Sopenharmony_ci
112062306a36Sopenharmony_ci		default:
112162306a36Sopenharmony_ci			return -EINVAL;
112262306a36Sopenharmony_ci		}
112362306a36Sopenharmony_ci	}
112462306a36Sopenharmony_ci
112562306a36Sopenharmony_ci	return 0;
112662306a36Sopenharmony_ci}
112762306a36Sopenharmony_ci
112862306a36Sopenharmony_ci/*
112962306a36Sopenharmony_ci * classify_event - classify an event
113062306a36Sopenharmony_ci * @name: event name
113162306a36Sopenharmony_ci * @category: event category
113262306a36Sopenharmony_ci *
113362306a36Sopenharmony_ci * Classify instances of both HPBIOS_PlatformEvents and HPBIOS_BIOSEvent from
113462306a36Sopenharmony_ci * property values. Recognition criteria are based on multiple ACPI dumps [3].
113562306a36Sopenharmony_ci *
113662306a36Sopenharmony_ci * Returns an enum hp_wmi_type value on success,
113762306a36Sopenharmony_ci * or a negative value if the event type is unsupported.
113862306a36Sopenharmony_ci */
113962306a36Sopenharmony_cistatic int classify_event(const char *event_name, u32 category)
114062306a36Sopenharmony_ci{
114162306a36Sopenharmony_ci	if (category != HP_WMI_CATEGORY_SENSOR)
114262306a36Sopenharmony_ci		return -EINVAL;
114362306a36Sopenharmony_ci
114462306a36Sopenharmony_ci	/* Fan events have Name "X Stall". */
114562306a36Sopenharmony_ci	if (strstr(event_name, HP_WMI_PATTERN_FAN_ALARM))
114662306a36Sopenharmony_ci		return HP_WMI_TYPE_AIR_FLOW;
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_ci	/* Intrusion events have Name "Hood Intrusion". */
114962306a36Sopenharmony_ci	if (!strcmp(event_name, HP_WMI_PATTERN_INTRUSION_ALARM))
115062306a36Sopenharmony_ci		return HP_WMI_TYPE_INTRUSION;
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci	/*
115362306a36Sopenharmony_ci	 * Temperature events have Name either "Thermal Caution" or
115462306a36Sopenharmony_ci	 * "Thermal Critical". Deal only with "Thermal Critical" events.
115562306a36Sopenharmony_ci	 *
115662306a36Sopenharmony_ci	 * "Thermal Caution" events have Status "Stressed", informing us that
115762306a36Sopenharmony_ci	 * the OperationalStatus of the related sensor has become "Stressed".
115862306a36Sopenharmony_ci	 * However, this is already a fault condition that will clear itself
115962306a36Sopenharmony_ci	 * when the sensor recovers, so we have no further interest in them.
116062306a36Sopenharmony_ci	 */
116162306a36Sopenharmony_ci	if (!strcmp(event_name, HP_WMI_PATTERN_TEMP_ALARM))
116262306a36Sopenharmony_ci		return HP_WMI_TYPE_TEMPERATURE;
116362306a36Sopenharmony_ci
116462306a36Sopenharmony_ci	return -EINVAL;
116562306a36Sopenharmony_ci}
116662306a36Sopenharmony_ci
116762306a36Sopenharmony_ci/*
116862306a36Sopenharmony_ci * interpret_info - interpret sensor for hwmon
116962306a36Sopenharmony_ci * @info: pointer to sensor info struct
117062306a36Sopenharmony_ci *
117162306a36Sopenharmony_ci * Should be called after the numeric sensor member has been updated.
117262306a36Sopenharmony_ci */
117362306a36Sopenharmony_cistatic void interpret_info(struct hp_wmi_info *info)
117462306a36Sopenharmony_ci{
117562306a36Sopenharmony_ci	const struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
117662306a36Sopenharmony_ci
117762306a36Sopenharmony_ci	info->cached_val = scale_numeric_sensor(nsensor);
117862306a36Sopenharmony_ci	info->last_updated = jiffies;
117962306a36Sopenharmony_ci}
118062306a36Sopenharmony_ci
118162306a36Sopenharmony_ci/*
118262306a36Sopenharmony_ci * hp_wmi_update_info - poll WMI to update sensor info
118362306a36Sopenharmony_ci * @state: pointer to driver state
118462306a36Sopenharmony_ci * @info: pointer to sensor info struct
118562306a36Sopenharmony_ci *
118662306a36Sopenharmony_ci * Returns 0 on success, or a negative error code on error.
118762306a36Sopenharmony_ci */
118862306a36Sopenharmony_cistatic int hp_wmi_update_info(struct hp_wmi_sensors *state,
118962306a36Sopenharmony_ci			      struct hp_wmi_info *info)
119062306a36Sopenharmony_ci{
119162306a36Sopenharmony_ci	struct hp_wmi_numeric_sensor *nsensor = &info->nsensor;
119262306a36Sopenharmony_ci	struct device *dev = &state->wdev->dev;
119362306a36Sopenharmony_ci	const union acpi_object *wobj;
119462306a36Sopenharmony_ci	u8 instance = info->instance;
119562306a36Sopenharmony_ci	int ret = 0;
119662306a36Sopenharmony_ci
119762306a36Sopenharmony_ci	if (time_after(jiffies, info->last_updated + HZ)) {
119862306a36Sopenharmony_ci		mutex_lock(&state->lock);
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
120162306a36Sopenharmony_ci		if (!wobj) {
120262306a36Sopenharmony_ci			ret = -EIO;
120362306a36Sopenharmony_ci			goto out_unlock;
120462306a36Sopenharmony_ci		}
120562306a36Sopenharmony_ci
120662306a36Sopenharmony_ci		update_numeric_sensor_from_wobj(dev, nsensor, wobj);
120762306a36Sopenharmony_ci
120862306a36Sopenharmony_ci		interpret_info(info);
120962306a36Sopenharmony_ci
121062306a36Sopenharmony_ci		kfree(wobj);
121162306a36Sopenharmony_ci
121262306a36Sopenharmony_ciout_unlock:
121362306a36Sopenharmony_ci		mutex_unlock(&state->lock);
121462306a36Sopenharmony_ci	}
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_ci	return ret;
121762306a36Sopenharmony_ci}
121862306a36Sopenharmony_ci
121962306a36Sopenharmony_cistatic int basic_string_show(struct seq_file *seqf, void *ignored)
122062306a36Sopenharmony_ci{
122162306a36Sopenharmony_ci	const char *str = seqf->private;
122262306a36Sopenharmony_ci
122362306a36Sopenharmony_ci	seq_printf(seqf, "%s\n", str);
122462306a36Sopenharmony_ci
122562306a36Sopenharmony_ci	return 0;
122662306a36Sopenharmony_ci}
122762306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(basic_string);
122862306a36Sopenharmony_ci
122962306a36Sopenharmony_cistatic int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop)
123062306a36Sopenharmony_ci{
123162306a36Sopenharmony_ci	struct hp_wmi_numeric_sensor *nsensor;
123262306a36Sopenharmony_ci	struct hp_wmi_sensors *state;
123362306a36Sopenharmony_ci	struct hp_wmi_info *info;
123462306a36Sopenharmony_ci	int err;
123562306a36Sopenharmony_ci
123662306a36Sopenharmony_ci	info = seqf->private;
123762306a36Sopenharmony_ci	state = info->state;
123862306a36Sopenharmony_ci	nsensor = &info->nsensor;
123962306a36Sopenharmony_ci
124062306a36Sopenharmony_ci	err = hp_wmi_update_info(state, info);
124162306a36Sopenharmony_ci	if (err)
124262306a36Sopenharmony_ci		return err;
124362306a36Sopenharmony_ci
124462306a36Sopenharmony_ci	switch (prop) {
124562306a36Sopenharmony_ci	case HP_WMI_PROPERTY_OPERATIONAL_STATUS:
124662306a36Sopenharmony_ci		seq_printf(seqf, "%u\n", nsensor->operational_status);
124762306a36Sopenharmony_ci		break;
124862306a36Sopenharmony_ci
124962306a36Sopenharmony_ci	case HP_WMI_PROPERTY_CURRENT_STATE:
125062306a36Sopenharmony_ci		seq_printf(seqf, "%s\n", nsensor->current_state);
125162306a36Sopenharmony_ci		break;
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	case HP_WMI_PROPERTY_UNIT_MODIFIER:
125462306a36Sopenharmony_ci		seq_printf(seqf, "%d\n", nsensor->unit_modifier);
125562306a36Sopenharmony_ci		break;
125662306a36Sopenharmony_ci
125762306a36Sopenharmony_ci	case HP_WMI_PROPERTY_CURRENT_READING:
125862306a36Sopenharmony_ci		seq_printf(seqf, "%u\n", nsensor->current_reading);
125962306a36Sopenharmony_ci		break;
126062306a36Sopenharmony_ci
126162306a36Sopenharmony_ci	default:
126262306a36Sopenharmony_ci		return -EOPNOTSUPP;
126362306a36Sopenharmony_ci	}
126462306a36Sopenharmony_ci
126562306a36Sopenharmony_ci	return 0;
126662306a36Sopenharmony_ci}
126762306a36Sopenharmony_ci
126862306a36Sopenharmony_cistatic int operational_status_show(struct seq_file *seqf, void *ignored)
126962306a36Sopenharmony_ci{
127062306a36Sopenharmony_ci	return fungible_show(seqf, HP_WMI_PROPERTY_OPERATIONAL_STATUS);
127162306a36Sopenharmony_ci}
127262306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(operational_status);
127362306a36Sopenharmony_ci
127462306a36Sopenharmony_cistatic int current_state_show(struct seq_file *seqf, void *ignored)
127562306a36Sopenharmony_ci{
127662306a36Sopenharmony_ci	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_STATE);
127762306a36Sopenharmony_ci}
127862306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(current_state);
127962306a36Sopenharmony_ci
128062306a36Sopenharmony_cistatic int possible_states_show(struct seq_file *seqf, void *ignored)
128162306a36Sopenharmony_ci{
128262306a36Sopenharmony_ci	struct hp_wmi_numeric_sensor *nsensor = seqf->private;
128362306a36Sopenharmony_ci	u8 i;
128462306a36Sopenharmony_ci
128562306a36Sopenharmony_ci	for (i = 0; i < nsensor->size; i++)
128662306a36Sopenharmony_ci		seq_printf(seqf, "%s%s", i ? "," : "",
128762306a36Sopenharmony_ci			   nsensor->possible_states[i]);
128862306a36Sopenharmony_ci
128962306a36Sopenharmony_ci	seq_puts(seqf, "\n");
129062306a36Sopenharmony_ci
129162306a36Sopenharmony_ci	return 0;
129262306a36Sopenharmony_ci}
129362306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(possible_states);
129462306a36Sopenharmony_ci
129562306a36Sopenharmony_cistatic int unit_modifier_show(struct seq_file *seqf, void *ignored)
129662306a36Sopenharmony_ci{
129762306a36Sopenharmony_ci	return fungible_show(seqf, HP_WMI_PROPERTY_UNIT_MODIFIER);
129862306a36Sopenharmony_ci}
129962306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(unit_modifier);
130062306a36Sopenharmony_ci
130162306a36Sopenharmony_cistatic int current_reading_show(struct seq_file *seqf, void *ignored)
130262306a36Sopenharmony_ci{
130362306a36Sopenharmony_ci	return fungible_show(seqf, HP_WMI_PROPERTY_CURRENT_READING);
130462306a36Sopenharmony_ci}
130562306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(current_reading);
130662306a36Sopenharmony_ci
130762306a36Sopenharmony_ci/* hp_wmi_devm_debugfs_remove - devm callback for debugfs cleanup */
130862306a36Sopenharmony_cistatic void hp_wmi_devm_debugfs_remove(void *res)
130962306a36Sopenharmony_ci{
131062306a36Sopenharmony_ci	debugfs_remove_recursive(res);
131162306a36Sopenharmony_ci}
131262306a36Sopenharmony_ci
131362306a36Sopenharmony_ci/* hp_wmi_debugfs_init - create and populate debugfs directory tree */
131462306a36Sopenharmony_cistatic void hp_wmi_debugfs_init(struct device *dev, struct hp_wmi_info *info,
131562306a36Sopenharmony_ci				struct hp_wmi_platform_events *pevents,
131662306a36Sopenharmony_ci				u8 icount, u8 pcount, bool is_new)
131762306a36Sopenharmony_ci{
131862306a36Sopenharmony_ci	struct hp_wmi_numeric_sensor *nsensor;
131962306a36Sopenharmony_ci	char buf[HP_WMI_MAX_STR_SIZE];
132062306a36Sopenharmony_ci	struct dentry *debugfs;
132162306a36Sopenharmony_ci	struct dentry *entries;
132262306a36Sopenharmony_ci	struct dentry *dir;
132362306a36Sopenharmony_ci	int err;
132462306a36Sopenharmony_ci	u8 i;
132562306a36Sopenharmony_ci
132662306a36Sopenharmony_ci	/* dev_name() gives a not-very-friendly GUID for WMI devices. */
132762306a36Sopenharmony_ci	scnprintf(buf, sizeof(buf), "hp-wmi-sensors-%u", dev->id);
132862306a36Sopenharmony_ci
132962306a36Sopenharmony_ci	debugfs = debugfs_create_dir(buf, NULL);
133062306a36Sopenharmony_ci	if (IS_ERR(debugfs))
133162306a36Sopenharmony_ci		return;
133262306a36Sopenharmony_ci
133362306a36Sopenharmony_ci	err = devm_add_action_or_reset(dev, hp_wmi_devm_debugfs_remove,
133462306a36Sopenharmony_ci				       debugfs);
133562306a36Sopenharmony_ci	if (err)
133662306a36Sopenharmony_ci		return;
133762306a36Sopenharmony_ci
133862306a36Sopenharmony_ci	entries = debugfs_create_dir("sensor", debugfs);
133962306a36Sopenharmony_ci
134062306a36Sopenharmony_ci	for (i = 0; i < icount; i++, info++) {
134162306a36Sopenharmony_ci		nsensor = &info->nsensor;
134262306a36Sopenharmony_ci
134362306a36Sopenharmony_ci		scnprintf(buf, sizeof(buf), "%u", i);
134462306a36Sopenharmony_ci		dir = debugfs_create_dir(buf, entries);
134562306a36Sopenharmony_ci
134662306a36Sopenharmony_ci		debugfs_create_file("name", 0444, dir,
134762306a36Sopenharmony_ci				    (void *)nsensor->name,
134862306a36Sopenharmony_ci				    &basic_string_fops);
134962306a36Sopenharmony_ci
135062306a36Sopenharmony_ci		debugfs_create_file("description", 0444, dir,
135162306a36Sopenharmony_ci				    (void *)nsensor->description,
135262306a36Sopenharmony_ci				    &basic_string_fops);
135362306a36Sopenharmony_ci
135462306a36Sopenharmony_ci		debugfs_create_u32("sensor_type", 0444, dir,
135562306a36Sopenharmony_ci				   &nsensor->sensor_type);
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci		debugfs_create_file("other_sensor_type", 0444, dir,
135862306a36Sopenharmony_ci				    (void *)nsensor->other_sensor_type,
135962306a36Sopenharmony_ci				    &basic_string_fops);
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_ci		debugfs_create_file("operational_status", 0444, dir,
136262306a36Sopenharmony_ci				    info, &operational_status_fops);
136362306a36Sopenharmony_ci
136462306a36Sopenharmony_ci		debugfs_create_file("possible_states", 0444, dir,
136562306a36Sopenharmony_ci				    nsensor, &possible_states_fops);
136662306a36Sopenharmony_ci
136762306a36Sopenharmony_ci		debugfs_create_file("current_state", 0444, dir,
136862306a36Sopenharmony_ci				    info, &current_state_fops);
136962306a36Sopenharmony_ci
137062306a36Sopenharmony_ci		debugfs_create_u32("base_units", 0444, dir,
137162306a36Sopenharmony_ci				   &nsensor->base_units);
137262306a36Sopenharmony_ci
137362306a36Sopenharmony_ci		debugfs_create_file("unit_modifier", 0444, dir,
137462306a36Sopenharmony_ci				    info, &unit_modifier_fops);
137562306a36Sopenharmony_ci
137662306a36Sopenharmony_ci		debugfs_create_file("current_reading", 0444, dir,
137762306a36Sopenharmony_ci				    info, &current_reading_fops);
137862306a36Sopenharmony_ci
137962306a36Sopenharmony_ci		if (is_new)
138062306a36Sopenharmony_ci			debugfs_create_u32("rate_units", 0444, dir,
138162306a36Sopenharmony_ci					   &nsensor->rate_units);
138262306a36Sopenharmony_ci	}
138362306a36Sopenharmony_ci
138462306a36Sopenharmony_ci	if (!pcount)
138562306a36Sopenharmony_ci		return;
138662306a36Sopenharmony_ci
138762306a36Sopenharmony_ci	entries = debugfs_create_dir("platform_events", debugfs);
138862306a36Sopenharmony_ci
138962306a36Sopenharmony_ci	for (i = 0; i < pcount; i++, pevents++) {
139062306a36Sopenharmony_ci		scnprintf(buf, sizeof(buf), "%u", i);
139162306a36Sopenharmony_ci		dir = debugfs_create_dir(buf, entries);
139262306a36Sopenharmony_ci
139362306a36Sopenharmony_ci		debugfs_create_file("name", 0444, dir,
139462306a36Sopenharmony_ci				    (void *)pevents->name,
139562306a36Sopenharmony_ci				    &basic_string_fops);
139662306a36Sopenharmony_ci
139762306a36Sopenharmony_ci		debugfs_create_file("description", 0444, dir,
139862306a36Sopenharmony_ci				    (void *)pevents->description,
139962306a36Sopenharmony_ci				    &basic_string_fops);
140062306a36Sopenharmony_ci
140162306a36Sopenharmony_ci		debugfs_create_file("source_namespace", 0444, dir,
140262306a36Sopenharmony_ci				    (void *)pevents->source_namespace,
140362306a36Sopenharmony_ci				    &basic_string_fops);
140462306a36Sopenharmony_ci
140562306a36Sopenharmony_ci		debugfs_create_file("source_class", 0444, dir,
140662306a36Sopenharmony_ci				    (void *)pevents->source_class,
140762306a36Sopenharmony_ci				    &basic_string_fops);
140862306a36Sopenharmony_ci
140962306a36Sopenharmony_ci		debugfs_create_u32("category", 0444, dir,
141062306a36Sopenharmony_ci				   &pevents->category);
141162306a36Sopenharmony_ci
141262306a36Sopenharmony_ci		debugfs_create_u32("possible_severity", 0444, dir,
141362306a36Sopenharmony_ci				   &pevents->possible_severity);
141462306a36Sopenharmony_ci
141562306a36Sopenharmony_ci		debugfs_create_u32("possible_status", 0444, dir,
141662306a36Sopenharmony_ci				   &pevents->possible_status);
141762306a36Sopenharmony_ci	}
141862306a36Sopenharmony_ci}
141962306a36Sopenharmony_ci
142062306a36Sopenharmony_cistatic umode_t hp_wmi_hwmon_is_visible(const void *drvdata,
142162306a36Sopenharmony_ci				       enum hwmon_sensor_types type,
142262306a36Sopenharmony_ci				       u32 attr, int channel)
142362306a36Sopenharmony_ci{
142462306a36Sopenharmony_ci	const struct hp_wmi_sensors *state = drvdata;
142562306a36Sopenharmony_ci	const struct hp_wmi_info *info;
142662306a36Sopenharmony_ci
142762306a36Sopenharmony_ci	if (type == hwmon_intrusion)
142862306a36Sopenharmony_ci		return state->has_intrusion ? 0644 : 0;
142962306a36Sopenharmony_ci
143062306a36Sopenharmony_ci	if (!state->info_map[type] || !state->info_map[type][channel])
143162306a36Sopenharmony_ci		return 0;
143262306a36Sopenharmony_ci
143362306a36Sopenharmony_ci	info = state->info_map[type][channel];
143462306a36Sopenharmony_ci
143562306a36Sopenharmony_ci	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
143662306a36Sopenharmony_ci	    (type == hwmon_fan  && attr == hwmon_fan_alarm))
143762306a36Sopenharmony_ci		return info->has_alarm ? 0444 : 0;
143862306a36Sopenharmony_ci
143962306a36Sopenharmony_ci	return 0444;
144062306a36Sopenharmony_ci}
144162306a36Sopenharmony_ci
144262306a36Sopenharmony_cistatic int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
144362306a36Sopenharmony_ci			     u32 attr, int channel, long *out_val)
144462306a36Sopenharmony_ci{
144562306a36Sopenharmony_ci	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
144662306a36Sopenharmony_ci	const struct hp_wmi_numeric_sensor *nsensor;
144762306a36Sopenharmony_ci	struct hp_wmi_info *info;
144862306a36Sopenharmony_ci	int err;
144962306a36Sopenharmony_ci
145062306a36Sopenharmony_ci	if (type == hwmon_intrusion) {
145162306a36Sopenharmony_ci		*out_val = state->intrusion ? 1 : 0;
145262306a36Sopenharmony_ci
145362306a36Sopenharmony_ci		return 0;
145462306a36Sopenharmony_ci	}
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_ci	info = state->info_map[type][channel];
145762306a36Sopenharmony_ci
145862306a36Sopenharmony_ci	if ((type == hwmon_temp && attr == hwmon_temp_alarm) ||
145962306a36Sopenharmony_ci	    (type == hwmon_fan  && attr == hwmon_fan_alarm)) {
146062306a36Sopenharmony_ci		*out_val = info->alarm ? 1 : 0;
146162306a36Sopenharmony_ci		info->alarm = false;
146262306a36Sopenharmony_ci
146362306a36Sopenharmony_ci		return 0;
146462306a36Sopenharmony_ci	}
146562306a36Sopenharmony_ci
146662306a36Sopenharmony_ci	nsensor = &info->nsensor;
146762306a36Sopenharmony_ci
146862306a36Sopenharmony_ci	err = hp_wmi_update_info(state, info);
146962306a36Sopenharmony_ci	if (err)
147062306a36Sopenharmony_ci		return err;
147162306a36Sopenharmony_ci
147262306a36Sopenharmony_ci	if ((type == hwmon_temp && attr == hwmon_temp_fault) ||
147362306a36Sopenharmony_ci	    (type == hwmon_fan  && attr == hwmon_fan_fault))
147462306a36Sopenharmony_ci		*out_val = numeric_sensor_has_fault(nsensor);
147562306a36Sopenharmony_ci	else
147662306a36Sopenharmony_ci		*out_val = info->cached_val;
147762306a36Sopenharmony_ci
147862306a36Sopenharmony_ci	return 0;
147962306a36Sopenharmony_ci}
148062306a36Sopenharmony_ci
148162306a36Sopenharmony_cistatic int hp_wmi_hwmon_read_string(struct device *dev,
148262306a36Sopenharmony_ci				    enum hwmon_sensor_types type, u32 attr,
148362306a36Sopenharmony_ci				    int channel, const char **out_str)
148462306a36Sopenharmony_ci{
148562306a36Sopenharmony_ci	const struct hp_wmi_sensors *state = dev_get_drvdata(dev);
148662306a36Sopenharmony_ci	const struct hp_wmi_info *info;
148762306a36Sopenharmony_ci
148862306a36Sopenharmony_ci	info = state->info_map[type][channel];
148962306a36Sopenharmony_ci	*out_str = info->nsensor.name;
149062306a36Sopenharmony_ci
149162306a36Sopenharmony_ci	return 0;
149262306a36Sopenharmony_ci}
149362306a36Sopenharmony_ci
149462306a36Sopenharmony_cistatic int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
149562306a36Sopenharmony_ci			      u32 attr, int channel, long val)
149662306a36Sopenharmony_ci{
149762306a36Sopenharmony_ci	struct hp_wmi_sensors *state = dev_get_drvdata(dev);
149862306a36Sopenharmony_ci
149962306a36Sopenharmony_ci	if (val)
150062306a36Sopenharmony_ci		return -EINVAL;
150162306a36Sopenharmony_ci
150262306a36Sopenharmony_ci	mutex_lock(&state->lock);
150362306a36Sopenharmony_ci
150462306a36Sopenharmony_ci	state->intrusion = false;
150562306a36Sopenharmony_ci
150662306a36Sopenharmony_ci	mutex_unlock(&state->lock);
150762306a36Sopenharmony_ci
150862306a36Sopenharmony_ci	return 0;
150962306a36Sopenharmony_ci}
151062306a36Sopenharmony_ci
151162306a36Sopenharmony_cistatic const struct hwmon_ops hp_wmi_hwmon_ops = {
151262306a36Sopenharmony_ci	.is_visible  = hp_wmi_hwmon_is_visible,
151362306a36Sopenharmony_ci	.read	     = hp_wmi_hwmon_read,
151462306a36Sopenharmony_ci	.read_string = hp_wmi_hwmon_read_string,
151562306a36Sopenharmony_ci	.write	     = hp_wmi_hwmon_write,
151662306a36Sopenharmony_ci};
151762306a36Sopenharmony_ci
151862306a36Sopenharmony_cistatic struct hwmon_chip_info hp_wmi_chip_info = {
151962306a36Sopenharmony_ci	.ops         = &hp_wmi_hwmon_ops,
152062306a36Sopenharmony_ci	.info        = NULL,
152162306a36Sopenharmony_ci};
152262306a36Sopenharmony_ci
152362306a36Sopenharmony_cistatic struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state,
152462306a36Sopenharmony_ci					   const char *event_description)
152562306a36Sopenharmony_ci{
152662306a36Sopenharmony_ci	struct hp_wmi_info **ptr_info = state->info_map[hwmon_fan];
152762306a36Sopenharmony_ci	u8 fan_count = state->channel_count[hwmon_fan];
152862306a36Sopenharmony_ci	struct hp_wmi_info *info;
152962306a36Sopenharmony_ci	const char *name;
153062306a36Sopenharmony_ci	u8 i;
153162306a36Sopenharmony_ci
153262306a36Sopenharmony_ci	/* Fan event has Description "X Speed". Sensor has Name "X[ Speed]". */
153362306a36Sopenharmony_ci
153462306a36Sopenharmony_ci	for (i = 0; i < fan_count; i++, ptr_info++) {
153562306a36Sopenharmony_ci		info = *ptr_info;
153662306a36Sopenharmony_ci		name = info->nsensor.name;
153762306a36Sopenharmony_ci
153862306a36Sopenharmony_ci		if (strstr(event_description, name))
153962306a36Sopenharmony_ci			return info;
154062306a36Sopenharmony_ci	}
154162306a36Sopenharmony_ci
154262306a36Sopenharmony_ci	return NULL;
154362306a36Sopenharmony_ci}
154462306a36Sopenharmony_ci
154562306a36Sopenharmony_cistatic u8 match_temp_events(struct hp_wmi_sensors *state,
154662306a36Sopenharmony_ci			    const char *event_description,
154762306a36Sopenharmony_ci			    struct hp_wmi_info *temp_info[])
154862306a36Sopenharmony_ci{
154962306a36Sopenharmony_ci	struct hp_wmi_info **ptr_info = state->info_map[hwmon_temp];
155062306a36Sopenharmony_ci	u8 temp_count = state->channel_count[hwmon_temp];
155162306a36Sopenharmony_ci	struct hp_wmi_info *info;
155262306a36Sopenharmony_ci	const char *name;
155362306a36Sopenharmony_ci	u8 count = 0;
155462306a36Sopenharmony_ci	bool is_cpu;
155562306a36Sopenharmony_ci	bool is_sys;
155662306a36Sopenharmony_ci	u8 i;
155762306a36Sopenharmony_ci
155862306a36Sopenharmony_ci	/* Description is either "CPU Thermal Index" or "Chassis Thermal Index". */
155962306a36Sopenharmony_ci
156062306a36Sopenharmony_ci	is_cpu = !strcmp(event_description, HP_WMI_PATTERN_CPU_TEMP);
156162306a36Sopenharmony_ci	is_sys = !strcmp(event_description, HP_WMI_PATTERN_SYS_TEMP);
156262306a36Sopenharmony_ci	if (!is_cpu && !is_sys)
156362306a36Sopenharmony_ci		return 0;
156462306a36Sopenharmony_ci
156562306a36Sopenharmony_ci	/*
156662306a36Sopenharmony_ci	 * CPU event: Match one sensor with Name either "CPU Thermal Index" or
156762306a36Sopenharmony_ci	 * "CPU Temperature", or multiple with Name(s) "CPU[#] Temperature".
156862306a36Sopenharmony_ci	 *
156962306a36Sopenharmony_ci	 * Chassis event: Match one sensor with Name either
157062306a36Sopenharmony_ci	 * "Chassis Thermal Index" or "System Ambient Temperature".
157162306a36Sopenharmony_ci	 */
157262306a36Sopenharmony_ci
157362306a36Sopenharmony_ci	for (i = 0; i < temp_count; i++, ptr_info++) {
157462306a36Sopenharmony_ci		info = *ptr_info;
157562306a36Sopenharmony_ci		name = info->nsensor.name;
157662306a36Sopenharmony_ci
157762306a36Sopenharmony_ci		if ((is_cpu && (!strcmp(name, HP_WMI_PATTERN_CPU_TEMP) ||
157862306a36Sopenharmony_ci				!strcmp(name, HP_WMI_PATTERN_CPU_TEMP2))) ||
157962306a36Sopenharmony_ci		    (is_sys && (!strcmp(name, HP_WMI_PATTERN_SYS_TEMP) ||
158062306a36Sopenharmony_ci				!strcmp(name, HP_WMI_PATTERN_SYS_TEMP2)))) {
158162306a36Sopenharmony_ci			temp_info[0] = info;
158262306a36Sopenharmony_ci			return 1;
158362306a36Sopenharmony_ci		}
158462306a36Sopenharmony_ci
158562306a36Sopenharmony_ci		if (is_cpu && (strstr(name, HP_WMI_PATTERN_CPU) &&
158662306a36Sopenharmony_ci			       strstr(name, HP_WMI_PATTERN_TEMP)))
158762306a36Sopenharmony_ci			temp_info[count++] = info;
158862306a36Sopenharmony_ci	}
158962306a36Sopenharmony_ci
159062306a36Sopenharmony_ci	return count;
159162306a36Sopenharmony_ci}
159262306a36Sopenharmony_ci
159362306a36Sopenharmony_ci/* hp_wmi_devm_debugfs_remove - devm callback for WMI event handler removal */
159462306a36Sopenharmony_cistatic void hp_wmi_devm_notify_remove(void *ignored)
159562306a36Sopenharmony_ci{
159662306a36Sopenharmony_ci	wmi_remove_notify_handler(HP_WMI_EVENT_GUID);
159762306a36Sopenharmony_ci}
159862306a36Sopenharmony_ci
159962306a36Sopenharmony_ci/* hp_wmi_notify - WMI event notification handler */
160062306a36Sopenharmony_cistatic void hp_wmi_notify(u32 value, void *context)
160162306a36Sopenharmony_ci{
160262306a36Sopenharmony_ci	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
160362306a36Sopenharmony_ci	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
160462306a36Sopenharmony_ci	struct hp_wmi_sensors *state = context;
160562306a36Sopenharmony_ci	struct device *dev = &state->wdev->dev;
160662306a36Sopenharmony_ci	struct hp_wmi_event event = {};
160762306a36Sopenharmony_ci	struct hp_wmi_info *fan_info;
160862306a36Sopenharmony_ci	union acpi_object *wobj;
160962306a36Sopenharmony_ci	acpi_status err;
161062306a36Sopenharmony_ci	int event_type;
161162306a36Sopenharmony_ci	u8 count;
161262306a36Sopenharmony_ci
161362306a36Sopenharmony_ci	/*
161462306a36Sopenharmony_ci	 * The following warning may occur in the kernel log:
161562306a36Sopenharmony_ci	 *
161662306a36Sopenharmony_ci	 *   ACPI Warning: \_SB.WMID._WED: Return type mismatch -
161762306a36Sopenharmony_ci	 *     found Package, expected Integer/String/Buffer
161862306a36Sopenharmony_ci	 *
161962306a36Sopenharmony_ci	 * After using [4] to decode BMOF blobs found in [3], careless copying
162062306a36Sopenharmony_ci	 * of BIOS code seems the most likely explanation for this warning.
162162306a36Sopenharmony_ci	 * HP_WMI_EVENT_GUID refers to \\.\root\WMI\HPBIOS_BIOSEvent on
162262306a36Sopenharmony_ci	 * business-class systems, but it refers to \\.\root\WMI\hpqBEvnt on
162362306a36Sopenharmony_ci	 * non-business-class systems. Per the existing hp-wmi driver, it
162462306a36Sopenharmony_ci	 * looks like an instance of hpqBEvnt delivered as event data may
162562306a36Sopenharmony_ci	 * indeed take the form of a raw ACPI_BUFFER on non-business-class
162662306a36Sopenharmony_ci	 * systems ("may" because ASL shows some BIOSes do strange things).
162762306a36Sopenharmony_ci	 *
162862306a36Sopenharmony_ci	 * In any case, we can ignore this warning, because we always validate
162962306a36Sopenharmony_ci	 * the event data to ensure it is an ACPI_PACKAGE containing a
163062306a36Sopenharmony_ci	 * HPBIOS_BIOSEvent instance.
163162306a36Sopenharmony_ci	 */
163262306a36Sopenharmony_ci
163362306a36Sopenharmony_ci	mutex_lock(&state->lock);
163462306a36Sopenharmony_ci
163562306a36Sopenharmony_ci	err = wmi_get_event_data(value, &out);
163662306a36Sopenharmony_ci	if (ACPI_FAILURE(err))
163762306a36Sopenharmony_ci		goto out_unlock;
163862306a36Sopenharmony_ci
163962306a36Sopenharmony_ci	wobj = out.pointer;
164062306a36Sopenharmony_ci
164162306a36Sopenharmony_ci	err = populate_event_from_wobj(dev, &event, wobj);
164262306a36Sopenharmony_ci	if (err) {
164362306a36Sopenharmony_ci		dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
164462306a36Sopenharmony_ci		goto out_free_wobj;
164562306a36Sopenharmony_ci	}
164662306a36Sopenharmony_ci
164762306a36Sopenharmony_ci	event_type = classify_event(event.name, event.category);
164862306a36Sopenharmony_ci	switch (event_type) {
164962306a36Sopenharmony_ci	case HP_WMI_TYPE_AIR_FLOW:
165062306a36Sopenharmony_ci		fan_info = match_fan_event(state, event.description);
165162306a36Sopenharmony_ci		if (fan_info)
165262306a36Sopenharmony_ci			fan_info->alarm = true;
165362306a36Sopenharmony_ci		break;
165462306a36Sopenharmony_ci
165562306a36Sopenharmony_ci	case HP_WMI_TYPE_INTRUSION:
165662306a36Sopenharmony_ci		state->intrusion = true;
165762306a36Sopenharmony_ci		break;
165862306a36Sopenharmony_ci
165962306a36Sopenharmony_ci	case HP_WMI_TYPE_TEMPERATURE:
166062306a36Sopenharmony_ci		count = match_temp_events(state, event.description, temp_info);
166162306a36Sopenharmony_ci		while (count)
166262306a36Sopenharmony_ci			temp_info[--count]->alarm = true;
166362306a36Sopenharmony_ci		break;
166462306a36Sopenharmony_ci
166562306a36Sopenharmony_ci	default:
166662306a36Sopenharmony_ci		break;
166762306a36Sopenharmony_ci	}
166862306a36Sopenharmony_ci
166962306a36Sopenharmony_ciout_free_wobj:
167062306a36Sopenharmony_ci	kfree(wobj);
167162306a36Sopenharmony_ci
167262306a36Sopenharmony_ci	devm_kfree(dev, event.name);
167362306a36Sopenharmony_ci	devm_kfree(dev, event.description);
167462306a36Sopenharmony_ci
167562306a36Sopenharmony_ciout_unlock:
167662306a36Sopenharmony_ci	mutex_unlock(&state->lock);
167762306a36Sopenharmony_ci}
167862306a36Sopenharmony_ci
167962306a36Sopenharmony_cistatic int init_platform_events(struct device *dev,
168062306a36Sopenharmony_ci				struct hp_wmi_platform_events **out_pevents,
168162306a36Sopenharmony_ci				u8 *out_pcount)
168262306a36Sopenharmony_ci{
168362306a36Sopenharmony_ci	struct hp_wmi_platform_events *pevents_arr;
168462306a36Sopenharmony_ci	struct hp_wmi_platform_events *pevents;
168562306a36Sopenharmony_ci	union acpi_object *wobj;
168662306a36Sopenharmony_ci	u8 count;
168762306a36Sopenharmony_ci	int err;
168862306a36Sopenharmony_ci	u8 i;
168962306a36Sopenharmony_ci
169062306a36Sopenharmony_ci	count = hp_wmi_wobj_instance_count(HP_WMI_PLATFORM_EVENTS_GUID);
169162306a36Sopenharmony_ci	if (!count) {
169262306a36Sopenharmony_ci		*out_pcount = 0;
169362306a36Sopenharmony_ci
169462306a36Sopenharmony_ci		dev_dbg(dev, "No platform events\n");
169562306a36Sopenharmony_ci
169662306a36Sopenharmony_ci		return 0;
169762306a36Sopenharmony_ci	}
169862306a36Sopenharmony_ci
169962306a36Sopenharmony_ci	pevents_arr = devm_kcalloc(dev, count, sizeof(*pevents), GFP_KERNEL);
170062306a36Sopenharmony_ci	if (!pevents_arr)
170162306a36Sopenharmony_ci		return -ENOMEM;
170262306a36Sopenharmony_ci
170362306a36Sopenharmony_ci	for (i = 0, pevents = pevents_arr; i < count; i++, pevents++) {
170462306a36Sopenharmony_ci		wobj = hp_wmi_get_wobj(HP_WMI_PLATFORM_EVENTS_GUID, i);
170562306a36Sopenharmony_ci		if (!wobj)
170662306a36Sopenharmony_ci			return -EIO;
170762306a36Sopenharmony_ci
170862306a36Sopenharmony_ci		err = populate_platform_events_from_wobj(dev, pevents, wobj);
170962306a36Sopenharmony_ci
171062306a36Sopenharmony_ci		kfree(wobj);
171162306a36Sopenharmony_ci
171262306a36Sopenharmony_ci		if (err)
171362306a36Sopenharmony_ci			return err;
171462306a36Sopenharmony_ci	}
171562306a36Sopenharmony_ci
171662306a36Sopenharmony_ci	*out_pevents = pevents_arr;
171762306a36Sopenharmony_ci	*out_pcount = count;
171862306a36Sopenharmony_ci
171962306a36Sopenharmony_ci	dev_dbg(dev, "Found %u platform events\n", count);
172062306a36Sopenharmony_ci
172162306a36Sopenharmony_ci	return 0;
172262306a36Sopenharmony_ci}
172362306a36Sopenharmony_ci
172462306a36Sopenharmony_cistatic int init_numeric_sensors(struct hp_wmi_sensors *state,
172562306a36Sopenharmony_ci				struct hp_wmi_info *connected[],
172662306a36Sopenharmony_ci				struct hp_wmi_info **out_info,
172762306a36Sopenharmony_ci				u8 *out_icount, u8 *out_count,
172862306a36Sopenharmony_ci				bool *out_is_new)
172962306a36Sopenharmony_ci{
173062306a36Sopenharmony_ci	struct hp_wmi_info ***info_map = state->info_map;
173162306a36Sopenharmony_ci	u8 *channel_count = state->channel_count;
173262306a36Sopenharmony_ci	struct device *dev = &state->wdev->dev;
173362306a36Sopenharmony_ci	struct hp_wmi_numeric_sensor *nsensor;
173462306a36Sopenharmony_ci	u8 channel_index[hwmon_max] = {};
173562306a36Sopenharmony_ci	enum hwmon_sensor_types type;
173662306a36Sopenharmony_ci	struct hp_wmi_info *info_arr;
173762306a36Sopenharmony_ci	struct hp_wmi_info *info;
173862306a36Sopenharmony_ci	union acpi_object *wobj;
173962306a36Sopenharmony_ci	u8 count = 0;
174062306a36Sopenharmony_ci	bool is_new;
174162306a36Sopenharmony_ci	u8 icount;
174262306a36Sopenharmony_ci	int wtype;
174362306a36Sopenharmony_ci	int err;
174462306a36Sopenharmony_ci	u8 c;
174562306a36Sopenharmony_ci	u8 i;
174662306a36Sopenharmony_ci
174762306a36Sopenharmony_ci	icount = hp_wmi_wobj_instance_count(HP_WMI_NUMERIC_SENSOR_GUID);
174862306a36Sopenharmony_ci	if (!icount)
174962306a36Sopenharmony_ci		return -ENODATA;
175062306a36Sopenharmony_ci
175162306a36Sopenharmony_ci	info_arr = devm_kcalloc(dev, icount, sizeof(*info), GFP_KERNEL);
175262306a36Sopenharmony_ci	if (!info_arr)
175362306a36Sopenharmony_ci		return -ENOMEM;
175462306a36Sopenharmony_ci
175562306a36Sopenharmony_ci	for (i = 0, info = info_arr; i < icount; i++, info++) {
175662306a36Sopenharmony_ci		wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
175762306a36Sopenharmony_ci		if (!wobj)
175862306a36Sopenharmony_ci			return -EIO;
175962306a36Sopenharmony_ci
176062306a36Sopenharmony_ci		info->instance = i;
176162306a36Sopenharmony_ci		info->state = state;
176262306a36Sopenharmony_ci		nsensor = &info->nsensor;
176362306a36Sopenharmony_ci
176462306a36Sopenharmony_ci		err = populate_numeric_sensor_from_wobj(dev, nsensor, wobj,
176562306a36Sopenharmony_ci							&is_new);
176662306a36Sopenharmony_ci
176762306a36Sopenharmony_ci		kfree(wobj);
176862306a36Sopenharmony_ci
176962306a36Sopenharmony_ci		if (err)
177062306a36Sopenharmony_ci			return err;
177162306a36Sopenharmony_ci
177262306a36Sopenharmony_ci		if (!numeric_sensor_is_connected(nsensor))
177362306a36Sopenharmony_ci			continue;
177462306a36Sopenharmony_ci
177562306a36Sopenharmony_ci		wtype = classify_numeric_sensor(nsensor);
177662306a36Sopenharmony_ci		if (wtype < 0)
177762306a36Sopenharmony_ci			continue;
177862306a36Sopenharmony_ci
177962306a36Sopenharmony_ci		type = hp_wmi_hwmon_type_map[wtype];
178062306a36Sopenharmony_ci
178162306a36Sopenharmony_ci		channel_count[type]++;
178262306a36Sopenharmony_ci
178362306a36Sopenharmony_ci		info->type = type;
178462306a36Sopenharmony_ci
178562306a36Sopenharmony_ci		interpret_info(info);
178662306a36Sopenharmony_ci
178762306a36Sopenharmony_ci		connected[count++] = info;
178862306a36Sopenharmony_ci	}
178962306a36Sopenharmony_ci
179062306a36Sopenharmony_ci	dev_dbg(dev, "Found %u sensors (%u connected)\n", i, count);
179162306a36Sopenharmony_ci
179262306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
179362306a36Sopenharmony_ci		info = connected[i];
179462306a36Sopenharmony_ci		type = info->type;
179562306a36Sopenharmony_ci		c = channel_index[type]++;
179662306a36Sopenharmony_ci
179762306a36Sopenharmony_ci		if (!info_map[type]) {
179862306a36Sopenharmony_ci			info_map[type] = devm_kcalloc(dev, channel_count[type],
179962306a36Sopenharmony_ci						      sizeof(*info_map),
180062306a36Sopenharmony_ci						      GFP_KERNEL);
180162306a36Sopenharmony_ci			if (!info_map[type])
180262306a36Sopenharmony_ci				return -ENOMEM;
180362306a36Sopenharmony_ci		}
180462306a36Sopenharmony_ci
180562306a36Sopenharmony_ci		info_map[type][c] = info;
180662306a36Sopenharmony_ci	}
180762306a36Sopenharmony_ci
180862306a36Sopenharmony_ci	*out_info = info_arr;
180962306a36Sopenharmony_ci	*out_icount = icount;
181062306a36Sopenharmony_ci	*out_count = count;
181162306a36Sopenharmony_ci	*out_is_new = is_new;
181262306a36Sopenharmony_ci
181362306a36Sopenharmony_ci	return 0;
181462306a36Sopenharmony_ci}
181562306a36Sopenharmony_ci
181662306a36Sopenharmony_cistatic bool find_event_attributes(struct hp_wmi_sensors *state,
181762306a36Sopenharmony_ci				  struct hp_wmi_platform_events *pevents,
181862306a36Sopenharmony_ci				  u8 pevents_count)
181962306a36Sopenharmony_ci{
182062306a36Sopenharmony_ci	/*
182162306a36Sopenharmony_ci	 * The existence of this HPBIOS_PlatformEvents instance:
182262306a36Sopenharmony_ci	 *
182362306a36Sopenharmony_ci	 *   {
182462306a36Sopenharmony_ci	 *     Name = "Rear Chassis Fan0 Stall";
182562306a36Sopenharmony_ci	 *     Description = "Rear Chassis Fan0 Speed";
182662306a36Sopenharmony_ci	 *     Category = 3;           // "Sensor"
182762306a36Sopenharmony_ci	 *     PossibleSeverity = 25;  // "Critical Failure"
182862306a36Sopenharmony_ci	 *     PossibleStatus = 5;     // "Predictive Failure"
182962306a36Sopenharmony_ci	 *     [...]
183062306a36Sopenharmony_ci	 *   }
183162306a36Sopenharmony_ci	 *
183262306a36Sopenharmony_ci	 * means that this HPBIOS_BIOSEvent instance may occur:
183362306a36Sopenharmony_ci	 *
183462306a36Sopenharmony_ci	 *   {
183562306a36Sopenharmony_ci	 *     Name = "Rear Chassis Fan0 Stall";
183662306a36Sopenharmony_ci	 *     Description = "Rear Chassis Fan0 Speed";
183762306a36Sopenharmony_ci	 *     Category = 3;           // "Sensor"
183862306a36Sopenharmony_ci	 *     Severity = 25;          // "Critical Failure"
183962306a36Sopenharmony_ci	 *     Status = 5;             // "Predictive Failure"
184062306a36Sopenharmony_ci	 *   }
184162306a36Sopenharmony_ci	 *
184262306a36Sopenharmony_ci	 * After the event occurs (e.g. because the fan was unplugged),
184362306a36Sopenharmony_ci	 * polling the related HPBIOS_BIOSNumericSensor instance gives:
184462306a36Sopenharmony_ci	 *
184562306a36Sopenharmony_ci	 *   {
184662306a36Sopenharmony_ci	 *      Name = "Rear Chassis Fan0";
184762306a36Sopenharmony_ci	 *      Description = "Reports rear chassis fan0 speed";
184862306a36Sopenharmony_ci	 *      OperationalStatus = 5; // "Predictive Failure", was 3 ("OK")
184962306a36Sopenharmony_ci	 *      CurrentReading = 0;
185062306a36Sopenharmony_ci	 *      [...]
185162306a36Sopenharmony_ci	 *   }
185262306a36Sopenharmony_ci	 *
185362306a36Sopenharmony_ci	 * In this example, the hwmon fan channel for "Rear Chassis Fan0"
185462306a36Sopenharmony_ci	 * should support the alarm flag and have it be set if the related
185562306a36Sopenharmony_ci	 * HPBIOS_BIOSEvent instance occurs.
185662306a36Sopenharmony_ci	 *
185762306a36Sopenharmony_ci	 * In addition to fan events, temperature (CPU/chassis) and intrusion
185862306a36Sopenharmony_ci	 * events are relevant to hwmon [2]. Note that much information in [2]
185962306a36Sopenharmony_ci	 * is unreliable; it is referenced in addition to ACPI dumps [3] merely
186062306a36Sopenharmony_ci	 * to support the conclusion that sensor and event names/descriptions
186162306a36Sopenharmony_ci	 * are systematic enough to allow this driver to match them.
186262306a36Sopenharmony_ci	 *
186362306a36Sopenharmony_ci	 * Complications and limitations:
186462306a36Sopenharmony_ci	 *
186562306a36Sopenharmony_ci	 * - Strings are freeform and may vary, cf. sensor Name "CPU0 Fan"
186662306a36Sopenharmony_ci	 *   on a Z420 vs. "CPU Fan Speed" on an EliteOne 800 G1.
186762306a36Sopenharmony_ci	 * - Leading/trailing whitespace is a rare but real possibility [3].
186862306a36Sopenharmony_ci	 * - The HPBIOS_PlatformEvents object may not exist or its instances
186962306a36Sopenharmony_ci	 *   may show that the system only has e.g. BIOS setting-related
187062306a36Sopenharmony_ci	 *   events (cf. the ProBook 4540s and ProBook 470 G0 [3]).
187162306a36Sopenharmony_ci	 */
187262306a36Sopenharmony_ci
187362306a36Sopenharmony_ci	struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
187462306a36Sopenharmony_ci	const char *event_description;
187562306a36Sopenharmony_ci	struct hp_wmi_info *fan_info;
187662306a36Sopenharmony_ci	bool has_events = false;
187762306a36Sopenharmony_ci	const char *event_name;
187862306a36Sopenharmony_ci	u32 event_category;
187962306a36Sopenharmony_ci	int event_type;
188062306a36Sopenharmony_ci	u8 count;
188162306a36Sopenharmony_ci	u8 i;
188262306a36Sopenharmony_ci
188362306a36Sopenharmony_ci	for (i = 0; i < pevents_count; i++, pevents++) {
188462306a36Sopenharmony_ci		event_name = pevents->name;
188562306a36Sopenharmony_ci		event_description = pevents->description;
188662306a36Sopenharmony_ci		event_category = pevents->category;
188762306a36Sopenharmony_ci
188862306a36Sopenharmony_ci		event_type = classify_event(event_name, event_category);
188962306a36Sopenharmony_ci		switch (event_type) {
189062306a36Sopenharmony_ci		case HP_WMI_TYPE_AIR_FLOW:
189162306a36Sopenharmony_ci			fan_info = match_fan_event(state, event_description);
189262306a36Sopenharmony_ci			if (!fan_info)
189362306a36Sopenharmony_ci				break;
189462306a36Sopenharmony_ci
189562306a36Sopenharmony_ci			fan_info->has_alarm = true;
189662306a36Sopenharmony_ci			has_events = true;
189762306a36Sopenharmony_ci			break;
189862306a36Sopenharmony_ci
189962306a36Sopenharmony_ci		case HP_WMI_TYPE_INTRUSION:
190062306a36Sopenharmony_ci			state->has_intrusion = true;
190162306a36Sopenharmony_ci			has_events = true;
190262306a36Sopenharmony_ci			break;
190362306a36Sopenharmony_ci
190462306a36Sopenharmony_ci		case HP_WMI_TYPE_TEMPERATURE:
190562306a36Sopenharmony_ci			count = match_temp_events(state, event_description,
190662306a36Sopenharmony_ci						  temp_info);
190762306a36Sopenharmony_ci			if (!count)
190862306a36Sopenharmony_ci				break;
190962306a36Sopenharmony_ci
191062306a36Sopenharmony_ci			while (count)
191162306a36Sopenharmony_ci				temp_info[--count]->has_alarm = true;
191262306a36Sopenharmony_ci			has_events = true;
191362306a36Sopenharmony_ci			break;
191462306a36Sopenharmony_ci
191562306a36Sopenharmony_ci		default:
191662306a36Sopenharmony_ci			break;
191762306a36Sopenharmony_ci		}
191862306a36Sopenharmony_ci	}
191962306a36Sopenharmony_ci
192062306a36Sopenharmony_ci	return has_events;
192162306a36Sopenharmony_ci}
192262306a36Sopenharmony_ci
192362306a36Sopenharmony_cistatic int make_chip_info(struct hp_wmi_sensors *state, bool has_events)
192462306a36Sopenharmony_ci{
192562306a36Sopenharmony_ci	const struct hwmon_channel_info **ptr_channel_info;
192662306a36Sopenharmony_ci	struct hp_wmi_info ***info_map = state->info_map;
192762306a36Sopenharmony_ci	u8 *channel_count = state->channel_count;
192862306a36Sopenharmony_ci	struct hwmon_channel_info *channel_info;
192962306a36Sopenharmony_ci	struct device *dev = &state->wdev->dev;
193062306a36Sopenharmony_ci	enum hwmon_sensor_types type;
193162306a36Sopenharmony_ci	u8 type_count = 0;
193262306a36Sopenharmony_ci	u32 *config;
193362306a36Sopenharmony_ci	u32 attr;
193462306a36Sopenharmony_ci	u8 count;
193562306a36Sopenharmony_ci	u8 i;
193662306a36Sopenharmony_ci
193762306a36Sopenharmony_ci	if (channel_count[hwmon_temp])
193862306a36Sopenharmony_ci		channel_count[hwmon_chip] = 1;
193962306a36Sopenharmony_ci
194062306a36Sopenharmony_ci	if (has_events && state->has_intrusion)
194162306a36Sopenharmony_ci		channel_count[hwmon_intrusion] = 1;
194262306a36Sopenharmony_ci
194362306a36Sopenharmony_ci	for (type = hwmon_chip; type < hwmon_max; type++)
194462306a36Sopenharmony_ci		if (channel_count[type])
194562306a36Sopenharmony_ci			type_count++;
194662306a36Sopenharmony_ci
194762306a36Sopenharmony_ci	channel_info = devm_kcalloc(dev, type_count,
194862306a36Sopenharmony_ci				    sizeof(*channel_info), GFP_KERNEL);
194962306a36Sopenharmony_ci	if (!channel_info)
195062306a36Sopenharmony_ci		return -ENOMEM;
195162306a36Sopenharmony_ci
195262306a36Sopenharmony_ci	ptr_channel_info = devm_kcalloc(dev, type_count + 1,
195362306a36Sopenharmony_ci					sizeof(*ptr_channel_info), GFP_KERNEL);
195462306a36Sopenharmony_ci	if (!ptr_channel_info)
195562306a36Sopenharmony_ci		return -ENOMEM;
195662306a36Sopenharmony_ci
195762306a36Sopenharmony_ci	hp_wmi_chip_info.info = ptr_channel_info;
195862306a36Sopenharmony_ci
195962306a36Sopenharmony_ci	for (type = hwmon_chip; type < hwmon_max; type++) {
196062306a36Sopenharmony_ci		count = channel_count[type];
196162306a36Sopenharmony_ci		if (!count)
196262306a36Sopenharmony_ci			continue;
196362306a36Sopenharmony_ci
196462306a36Sopenharmony_ci		config = devm_kcalloc(dev, count + 1,
196562306a36Sopenharmony_ci				      sizeof(*config), GFP_KERNEL);
196662306a36Sopenharmony_ci		if (!config)
196762306a36Sopenharmony_ci			return -ENOMEM;
196862306a36Sopenharmony_ci
196962306a36Sopenharmony_ci		attr = hp_wmi_hwmon_attributes[type];
197062306a36Sopenharmony_ci		channel_info->type = type;
197162306a36Sopenharmony_ci		channel_info->config = config;
197262306a36Sopenharmony_ci		memset32(config, attr, count);
197362306a36Sopenharmony_ci
197462306a36Sopenharmony_ci		*ptr_channel_info++ = channel_info++;
197562306a36Sopenharmony_ci
197662306a36Sopenharmony_ci		if (!has_events || (type != hwmon_temp && type != hwmon_fan))
197762306a36Sopenharmony_ci			continue;
197862306a36Sopenharmony_ci
197962306a36Sopenharmony_ci		attr = type == hwmon_temp ? HWMON_T_ALARM : HWMON_F_ALARM;
198062306a36Sopenharmony_ci
198162306a36Sopenharmony_ci		for (i = 0; i < count; i++)
198262306a36Sopenharmony_ci			if (info_map[type][i]->has_alarm)
198362306a36Sopenharmony_ci				config[i] |= attr;
198462306a36Sopenharmony_ci	}
198562306a36Sopenharmony_ci
198662306a36Sopenharmony_ci	return 0;
198762306a36Sopenharmony_ci}
198862306a36Sopenharmony_ci
198962306a36Sopenharmony_cistatic bool add_event_handler(struct hp_wmi_sensors *state)
199062306a36Sopenharmony_ci{
199162306a36Sopenharmony_ci	struct device *dev = &state->wdev->dev;
199262306a36Sopenharmony_ci	int err;
199362306a36Sopenharmony_ci
199462306a36Sopenharmony_ci	err = wmi_install_notify_handler(HP_WMI_EVENT_GUID,
199562306a36Sopenharmony_ci					 hp_wmi_notify, state);
199662306a36Sopenharmony_ci	if (err) {
199762306a36Sopenharmony_ci		dev_info(dev, "Failed to subscribe to WMI event\n");
199862306a36Sopenharmony_ci		return false;
199962306a36Sopenharmony_ci	}
200062306a36Sopenharmony_ci
200162306a36Sopenharmony_ci	err = devm_add_action_or_reset(dev, hp_wmi_devm_notify_remove, NULL);
200262306a36Sopenharmony_ci	if (err)
200362306a36Sopenharmony_ci		return false;
200462306a36Sopenharmony_ci
200562306a36Sopenharmony_ci	return true;
200662306a36Sopenharmony_ci}
200762306a36Sopenharmony_ci
200862306a36Sopenharmony_cistatic int hp_wmi_sensors_init(struct hp_wmi_sensors *state)
200962306a36Sopenharmony_ci{
201062306a36Sopenharmony_ci	struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES];
201162306a36Sopenharmony_ci	struct hp_wmi_platform_events *pevents = NULL;
201262306a36Sopenharmony_ci	struct device *dev = &state->wdev->dev;
201362306a36Sopenharmony_ci	struct hp_wmi_info *info;
201462306a36Sopenharmony_ci	struct device *hwdev;
201562306a36Sopenharmony_ci	bool has_events;
201662306a36Sopenharmony_ci	bool is_new;
201762306a36Sopenharmony_ci	u8 icount;
201862306a36Sopenharmony_ci	u8 pcount;
201962306a36Sopenharmony_ci	u8 count;
202062306a36Sopenharmony_ci	int err;
202162306a36Sopenharmony_ci
202262306a36Sopenharmony_ci	err = init_platform_events(dev, &pevents, &pcount);
202362306a36Sopenharmony_ci	if (err)
202462306a36Sopenharmony_ci		return err;
202562306a36Sopenharmony_ci
202662306a36Sopenharmony_ci	err = init_numeric_sensors(state, connected, &info,
202762306a36Sopenharmony_ci				   &icount, &count, &is_new);
202862306a36Sopenharmony_ci	if (err)
202962306a36Sopenharmony_ci		return err;
203062306a36Sopenharmony_ci
203162306a36Sopenharmony_ci	if (IS_ENABLED(CONFIG_DEBUG_FS))
203262306a36Sopenharmony_ci		hp_wmi_debugfs_init(dev, info, pevents, icount, pcount, is_new);
203362306a36Sopenharmony_ci
203462306a36Sopenharmony_ci	if (!count)
203562306a36Sopenharmony_ci		return 0;	/* No connected sensors; debugfs only. */
203662306a36Sopenharmony_ci
203762306a36Sopenharmony_ci	has_events = find_event_attributes(state, pevents, pcount);
203862306a36Sopenharmony_ci
203962306a36Sopenharmony_ci	/* Survive failure to install WMI event handler. */
204062306a36Sopenharmony_ci	if (has_events && !add_event_handler(state))
204162306a36Sopenharmony_ci		has_events = false;
204262306a36Sopenharmony_ci
204362306a36Sopenharmony_ci	err = make_chip_info(state, has_events);
204462306a36Sopenharmony_ci	if (err)
204562306a36Sopenharmony_ci		return err;
204662306a36Sopenharmony_ci
204762306a36Sopenharmony_ci	hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors",
204862306a36Sopenharmony_ci						     state, &hp_wmi_chip_info,
204962306a36Sopenharmony_ci						     NULL);
205062306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(hwdev);
205162306a36Sopenharmony_ci}
205262306a36Sopenharmony_ci
205362306a36Sopenharmony_cistatic int hp_wmi_sensors_probe(struct wmi_device *wdev, const void *context)
205462306a36Sopenharmony_ci{
205562306a36Sopenharmony_ci	struct device *dev = &wdev->dev;
205662306a36Sopenharmony_ci	struct hp_wmi_sensors *state;
205762306a36Sopenharmony_ci
205862306a36Sopenharmony_ci	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
205962306a36Sopenharmony_ci	if (!state)
206062306a36Sopenharmony_ci		return -ENOMEM;
206162306a36Sopenharmony_ci
206262306a36Sopenharmony_ci	state->wdev = wdev;
206362306a36Sopenharmony_ci
206462306a36Sopenharmony_ci	mutex_init(&state->lock);
206562306a36Sopenharmony_ci
206662306a36Sopenharmony_ci	dev_set_drvdata(dev, state);
206762306a36Sopenharmony_ci
206862306a36Sopenharmony_ci	return hp_wmi_sensors_init(state);
206962306a36Sopenharmony_ci}
207062306a36Sopenharmony_ci
207162306a36Sopenharmony_cistatic const struct wmi_device_id hp_wmi_sensors_id_table[] = {
207262306a36Sopenharmony_ci	{ HP_WMI_NUMERIC_SENSOR_GUID, NULL },
207362306a36Sopenharmony_ci	{},
207462306a36Sopenharmony_ci};
207562306a36Sopenharmony_ci
207662306a36Sopenharmony_cistatic struct wmi_driver hp_wmi_sensors_driver = {
207762306a36Sopenharmony_ci	.driver   = { .name = "hp-wmi-sensors" },
207862306a36Sopenharmony_ci	.id_table = hp_wmi_sensors_id_table,
207962306a36Sopenharmony_ci	.probe    = hp_wmi_sensors_probe,
208062306a36Sopenharmony_ci};
208162306a36Sopenharmony_cimodule_wmi_driver(hp_wmi_sensors_driver);
208262306a36Sopenharmony_ci
208362306a36Sopenharmony_ciMODULE_AUTHOR("James Seo <james@equiv.tech>");
208462306a36Sopenharmony_ciMODULE_DESCRIPTION("HP WMI Sensors driver");
208562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2086