18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * A hwmon driver for ACPI 4.0 power meters
48c2ecf20Sopenharmony_ci * Copyright (C) 2009 IBM
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Author: Darrick J. Wong <darrick.wong@oracle.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/hwmon.h>
118c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h>
128c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
138c2ecf20Sopenharmony_ci#include <linux/mutex.h>
148c2ecf20Sopenharmony_ci#include <linux/dmi.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci#include <linux/kdev_t.h>
178c2ecf20Sopenharmony_ci#include <linux/sched.h>
188c2ecf20Sopenharmony_ci#include <linux/time.h>
198c2ecf20Sopenharmony_ci#include <linux/err.h>
208c2ecf20Sopenharmony_ci#include <linux/acpi.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define ACPI_POWER_METER_NAME		"power_meter"
238c2ecf20Sopenharmony_ciACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
248c2ecf20Sopenharmony_ci#define ACPI_POWER_METER_DEVICE_NAME	"Power Meter"
258c2ecf20Sopenharmony_ci#define ACPI_POWER_METER_CLASS		"pwr_meter_resource"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define NUM_SENSORS			17
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define POWER_METER_CAN_MEASURE	(1 << 0)
308c2ecf20Sopenharmony_ci#define POWER_METER_CAN_TRIP	(1 << 1)
318c2ecf20Sopenharmony_ci#define POWER_METER_CAN_CAP	(1 << 2)
328c2ecf20Sopenharmony_ci#define POWER_METER_CAN_NOTIFY	(1 << 3)
338c2ecf20Sopenharmony_ci#define POWER_METER_IS_BATTERY	(1 << 8)
348c2ecf20Sopenharmony_ci#define UNKNOWN_HYSTERESIS	0xFFFFFFFF
358c2ecf20Sopenharmony_ci#define UNKNOWN_POWER		0xFFFFFFFF
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#define METER_NOTIFY_CONFIG	0x80
388c2ecf20Sopenharmony_ci#define METER_NOTIFY_TRIP	0x81
398c2ecf20Sopenharmony_ci#define METER_NOTIFY_CAP	0x82
408c2ecf20Sopenharmony_ci#define METER_NOTIFY_CAPPING	0x83
418c2ecf20Sopenharmony_ci#define METER_NOTIFY_INTERVAL	0x84
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define POWER_AVERAGE_NAME	"power1_average"
448c2ecf20Sopenharmony_ci#define POWER_CAP_NAME		"power1_cap"
458c2ecf20Sopenharmony_ci#define POWER_AVG_INTERVAL_NAME	"power1_average_interval"
468c2ecf20Sopenharmony_ci#define POWER_ALARM_NAME	"power1_alarm"
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic int cap_in_hardware;
498c2ecf20Sopenharmony_cistatic bool force_cap_on;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int can_cap_in_hardware(void)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	return force_cap_on || cap_in_hardware;
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic const struct acpi_device_id power_meter_ids[] = {
578c2ecf20Sopenharmony_ci	{"ACPI000D", 0},
588c2ecf20Sopenharmony_ci	{"", 0},
598c2ecf20Sopenharmony_ci};
608c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, power_meter_ids);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistruct acpi_power_meter_capabilities {
638c2ecf20Sopenharmony_ci	u64		flags;
648c2ecf20Sopenharmony_ci	u64		units;
658c2ecf20Sopenharmony_ci	u64		type;
668c2ecf20Sopenharmony_ci	u64		accuracy;
678c2ecf20Sopenharmony_ci	u64		sampling_time;
688c2ecf20Sopenharmony_ci	u64		min_avg_interval;
698c2ecf20Sopenharmony_ci	u64		max_avg_interval;
708c2ecf20Sopenharmony_ci	u64		hysteresis;
718c2ecf20Sopenharmony_ci	u64		configurable_cap;
728c2ecf20Sopenharmony_ci	u64		min_cap;
738c2ecf20Sopenharmony_ci	u64		max_cap;
748c2ecf20Sopenharmony_ci};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistruct acpi_power_meter_resource {
778c2ecf20Sopenharmony_ci	struct acpi_device	*acpi_dev;
788c2ecf20Sopenharmony_ci	acpi_bus_id		name;
798c2ecf20Sopenharmony_ci	struct mutex		lock;
808c2ecf20Sopenharmony_ci	struct device		*hwmon_dev;
818c2ecf20Sopenharmony_ci	struct acpi_power_meter_capabilities	caps;
828c2ecf20Sopenharmony_ci	acpi_string		model_number;
838c2ecf20Sopenharmony_ci	acpi_string		serial_number;
848c2ecf20Sopenharmony_ci	acpi_string		oem_info;
858c2ecf20Sopenharmony_ci	u64		power;
868c2ecf20Sopenharmony_ci	u64		cap;
878c2ecf20Sopenharmony_ci	u64		avg_interval;
888c2ecf20Sopenharmony_ci	int			sensors_valid;
898c2ecf20Sopenharmony_ci	unsigned long		sensors_last_updated;
908c2ecf20Sopenharmony_ci	struct sensor_device_attribute	sensors[NUM_SENSORS];
918c2ecf20Sopenharmony_ci	int			num_sensors;
928c2ecf20Sopenharmony_ci	s64			trip[2];
938c2ecf20Sopenharmony_ci	int			num_domain_devices;
948c2ecf20Sopenharmony_ci	struct acpi_device	**domain_devices;
958c2ecf20Sopenharmony_ci	struct kobject		*holders_dir;
968c2ecf20Sopenharmony_ci};
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistruct sensor_template {
998c2ecf20Sopenharmony_ci	char *label;
1008c2ecf20Sopenharmony_ci	ssize_t (*show)(struct device *dev,
1018c2ecf20Sopenharmony_ci			struct device_attribute *devattr,
1028c2ecf20Sopenharmony_ci			char *buf);
1038c2ecf20Sopenharmony_ci	ssize_t (*set)(struct device *dev,
1048c2ecf20Sopenharmony_ci		       struct device_attribute *devattr,
1058c2ecf20Sopenharmony_ci		       const char *buf, size_t count);
1068c2ecf20Sopenharmony_ci	int index;
1078c2ecf20Sopenharmony_ci};
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci/* Averaging interval */
1108c2ecf20Sopenharmony_cistatic int update_avg_interval(struct acpi_power_meter_resource *resource)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	unsigned long long data;
1138c2ecf20Sopenharmony_ci	acpi_status status;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
1168c2ecf20Sopenharmony_ci				       NULL, &data);
1178c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
1188c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
1198c2ecf20Sopenharmony_ci		return -ENODEV;
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	resource->avg_interval = data;
1238c2ecf20Sopenharmony_ci	return 0;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic ssize_t show_avg_interval(struct device *dev,
1278c2ecf20Sopenharmony_ci				 struct device_attribute *devattr,
1288c2ecf20Sopenharmony_ci				 char *buf)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
1318c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
1348c2ecf20Sopenharmony_ci	update_avg_interval(resource);
1358c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	return sprintf(buf, "%llu\n", resource->avg_interval);
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic ssize_t set_avg_interval(struct device *dev,
1418c2ecf20Sopenharmony_ci				struct device_attribute *devattr,
1428c2ecf20Sopenharmony_ci				const char *buf, size_t count)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
1458c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
1468c2ecf20Sopenharmony_ci	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
1478c2ecf20Sopenharmony_ci	struct acpi_object_list args = { 1, &arg0 };
1488c2ecf20Sopenharmony_ci	int res;
1498c2ecf20Sopenharmony_ci	unsigned long temp;
1508c2ecf20Sopenharmony_ci	unsigned long long data;
1518c2ecf20Sopenharmony_ci	acpi_status status;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	res = kstrtoul(buf, 10, &temp);
1548c2ecf20Sopenharmony_ci	if (res)
1558c2ecf20Sopenharmony_ci		return res;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (temp > resource->caps.max_avg_interval ||
1588c2ecf20Sopenharmony_ci	    temp < resource->caps.min_avg_interval)
1598c2ecf20Sopenharmony_ci		return -EINVAL;
1608c2ecf20Sopenharmony_ci	arg0.integer.value = temp;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
1638c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
1648c2ecf20Sopenharmony_ci				       &args, &data);
1658c2ecf20Sopenharmony_ci	if (!ACPI_FAILURE(status))
1668c2ecf20Sopenharmony_ci		resource->avg_interval = temp;
1678c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
1708c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
1718c2ecf20Sopenharmony_ci		return -EINVAL;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/* _PAI returns 0 on success, nonzero otherwise */
1758c2ecf20Sopenharmony_ci	if (data)
1768c2ecf20Sopenharmony_ci		return -EINVAL;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	return count;
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci/* Cap functions */
1828c2ecf20Sopenharmony_cistatic int update_cap(struct acpi_power_meter_resource *resource)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	unsigned long long data;
1858c2ecf20Sopenharmony_ci	acpi_status status;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
1888c2ecf20Sopenharmony_ci				       NULL, &data);
1898c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
1908c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
1918c2ecf20Sopenharmony_ci		return -ENODEV;
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	resource->cap = data;
1958c2ecf20Sopenharmony_ci	return 0;
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic ssize_t show_cap(struct device *dev,
1998c2ecf20Sopenharmony_ci			struct device_attribute *devattr,
2008c2ecf20Sopenharmony_ci			char *buf)
2018c2ecf20Sopenharmony_ci{
2028c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
2038c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
2068c2ecf20Sopenharmony_ci	update_cap(resource);
2078c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	return sprintf(buf, "%llu\n", resource->cap * 1000);
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
2138c2ecf20Sopenharmony_ci		       const char *buf, size_t count)
2148c2ecf20Sopenharmony_ci{
2158c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
2168c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
2178c2ecf20Sopenharmony_ci	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
2188c2ecf20Sopenharmony_ci	struct acpi_object_list args = { 1, &arg0 };
2198c2ecf20Sopenharmony_ci	int res;
2208c2ecf20Sopenharmony_ci	unsigned long temp;
2218c2ecf20Sopenharmony_ci	unsigned long long data;
2228c2ecf20Sopenharmony_ci	acpi_status status;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	res = kstrtoul(buf, 10, &temp);
2258c2ecf20Sopenharmony_ci	if (res)
2268c2ecf20Sopenharmony_ci		return res;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	temp = DIV_ROUND_CLOSEST(temp, 1000);
2298c2ecf20Sopenharmony_ci	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
2308c2ecf20Sopenharmony_ci		return -EINVAL;
2318c2ecf20Sopenharmony_ci	arg0.integer.value = temp;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
2348c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
2358c2ecf20Sopenharmony_ci				       &args, &data);
2368c2ecf20Sopenharmony_ci	if (!ACPI_FAILURE(status))
2378c2ecf20Sopenharmony_ci		resource->cap = temp;
2388c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
2418c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
2428c2ecf20Sopenharmony_ci		return -EINVAL;
2438c2ecf20Sopenharmony_ci	}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	/* _SHL returns 0 on success, nonzero otherwise */
2468c2ecf20Sopenharmony_ci	if (data)
2478c2ecf20Sopenharmony_ci		return -EINVAL;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	return count;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci/* Power meter trip points */
2538c2ecf20Sopenharmony_cistatic int set_acpi_trip(struct acpi_power_meter_resource *resource)
2548c2ecf20Sopenharmony_ci{
2558c2ecf20Sopenharmony_ci	union acpi_object arg_objs[] = {
2568c2ecf20Sopenharmony_ci		{ACPI_TYPE_INTEGER},
2578c2ecf20Sopenharmony_ci		{ACPI_TYPE_INTEGER}
2588c2ecf20Sopenharmony_ci	};
2598c2ecf20Sopenharmony_ci	struct acpi_object_list args = { 2, arg_objs };
2608c2ecf20Sopenharmony_ci	unsigned long long data;
2618c2ecf20Sopenharmony_ci	acpi_status status;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	/* Both trip levels must be set */
2648c2ecf20Sopenharmony_ci	if (resource->trip[0] < 0 || resource->trip[1] < 0)
2658c2ecf20Sopenharmony_ci		return 0;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	/* This driver stores min, max; ACPI wants max, min. */
2688c2ecf20Sopenharmony_ci	arg_objs[0].integer.value = resource->trip[1];
2698c2ecf20Sopenharmony_ci	arg_objs[1].integer.value = resource->trip[0];
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
2728c2ecf20Sopenharmony_ci				       &args, &data);
2738c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
2748c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
2758c2ecf20Sopenharmony_ci		return -EINVAL;
2768c2ecf20Sopenharmony_ci	}
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	/* _PTP returns 0 on success, nonzero otherwise */
2798c2ecf20Sopenharmony_ci	if (data)
2808c2ecf20Sopenharmony_ci		return -EINVAL;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	return 0;
2838c2ecf20Sopenharmony_ci}
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cistatic ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
2868c2ecf20Sopenharmony_ci			const char *buf, size_t count)
2878c2ecf20Sopenharmony_ci{
2888c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
2898c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
2908c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
2918c2ecf20Sopenharmony_ci	int res;
2928c2ecf20Sopenharmony_ci	unsigned long temp;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	res = kstrtoul(buf, 10, &temp);
2958c2ecf20Sopenharmony_ci	if (res)
2968c2ecf20Sopenharmony_ci		return res;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	temp = DIV_ROUND_CLOSEST(temp, 1000);
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
3018c2ecf20Sopenharmony_ci	resource->trip[attr->index - 7] = temp;
3028c2ecf20Sopenharmony_ci	res = set_acpi_trip(resource);
3038c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	if (res)
3068c2ecf20Sopenharmony_ci		return res;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	return count;
3098c2ecf20Sopenharmony_ci}
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci/* Power meter */
3128c2ecf20Sopenharmony_cistatic int update_meter(struct acpi_power_meter_resource *resource)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	unsigned long long data;
3158c2ecf20Sopenharmony_ci	acpi_status status;
3168c2ecf20Sopenharmony_ci	unsigned long local_jiffies = jiffies;
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	if (time_before(local_jiffies, resource->sensors_last_updated +
3198c2ecf20Sopenharmony_ci			msecs_to_jiffies(resource->caps.sampling_time)) &&
3208c2ecf20Sopenharmony_ci			resource->sensors_valid)
3218c2ecf20Sopenharmony_ci		return 0;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
3248c2ecf20Sopenharmony_ci				       NULL, &data);
3258c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
3268c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
3278c2ecf20Sopenharmony_ci		return -ENODEV;
3288c2ecf20Sopenharmony_ci	}
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	resource->power = data;
3318c2ecf20Sopenharmony_ci	resource->sensors_valid = 1;
3328c2ecf20Sopenharmony_ci	resource->sensors_last_updated = jiffies;
3338c2ecf20Sopenharmony_ci	return 0;
3348c2ecf20Sopenharmony_ci}
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_cistatic ssize_t show_power(struct device *dev,
3378c2ecf20Sopenharmony_ci			  struct device_attribute *devattr,
3388c2ecf20Sopenharmony_ci			  char *buf)
3398c2ecf20Sopenharmony_ci{
3408c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
3418c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
3448c2ecf20Sopenharmony_ci	update_meter(resource);
3458c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	if (resource->power == UNKNOWN_POWER)
3488c2ecf20Sopenharmony_ci		return -ENODATA;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	return sprintf(buf, "%llu\n", resource->power * 1000);
3518c2ecf20Sopenharmony_ci}
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci/* Miscellaneous */
3548c2ecf20Sopenharmony_cistatic ssize_t show_str(struct device *dev,
3558c2ecf20Sopenharmony_ci			struct device_attribute *devattr,
3568c2ecf20Sopenharmony_ci			char *buf)
3578c2ecf20Sopenharmony_ci{
3588c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
3598c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
3608c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
3618c2ecf20Sopenharmony_ci	acpi_string val;
3628c2ecf20Sopenharmony_ci	int ret;
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	mutex_lock(&resource->lock);
3658c2ecf20Sopenharmony_ci	switch (attr->index) {
3668c2ecf20Sopenharmony_ci	case 0:
3678c2ecf20Sopenharmony_ci		val = resource->model_number;
3688c2ecf20Sopenharmony_ci		break;
3698c2ecf20Sopenharmony_ci	case 1:
3708c2ecf20Sopenharmony_ci		val = resource->serial_number;
3718c2ecf20Sopenharmony_ci		break;
3728c2ecf20Sopenharmony_ci	case 2:
3738c2ecf20Sopenharmony_ci		val = resource->oem_info;
3748c2ecf20Sopenharmony_ci		break;
3758c2ecf20Sopenharmony_ci	default:
3768c2ecf20Sopenharmony_ci		WARN(1, "Implementation error: unexpected attribute index %d\n",
3778c2ecf20Sopenharmony_ci		     attr->index);
3788c2ecf20Sopenharmony_ci		val = "";
3798c2ecf20Sopenharmony_ci		break;
3808c2ecf20Sopenharmony_ci	}
3818c2ecf20Sopenharmony_ci	ret = sprintf(buf, "%s\n", val);
3828c2ecf20Sopenharmony_ci	mutex_unlock(&resource->lock);
3838c2ecf20Sopenharmony_ci	return ret;
3848c2ecf20Sopenharmony_ci}
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_cistatic ssize_t show_val(struct device *dev,
3878c2ecf20Sopenharmony_ci			struct device_attribute *devattr,
3888c2ecf20Sopenharmony_ci			char *buf)
3898c2ecf20Sopenharmony_ci{
3908c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
3918c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
3928c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
3938c2ecf20Sopenharmony_ci	u64 val = 0;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	switch (attr->index) {
3968c2ecf20Sopenharmony_ci	case 0:
3978c2ecf20Sopenharmony_ci		val = resource->caps.min_avg_interval;
3988c2ecf20Sopenharmony_ci		break;
3998c2ecf20Sopenharmony_ci	case 1:
4008c2ecf20Sopenharmony_ci		val = resource->caps.max_avg_interval;
4018c2ecf20Sopenharmony_ci		break;
4028c2ecf20Sopenharmony_ci	case 2:
4038c2ecf20Sopenharmony_ci		val = resource->caps.min_cap * 1000;
4048c2ecf20Sopenharmony_ci		break;
4058c2ecf20Sopenharmony_ci	case 3:
4068c2ecf20Sopenharmony_ci		val = resource->caps.max_cap * 1000;
4078c2ecf20Sopenharmony_ci		break;
4088c2ecf20Sopenharmony_ci	case 4:
4098c2ecf20Sopenharmony_ci		if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS)
4108c2ecf20Sopenharmony_ci			return sprintf(buf, "unknown\n");
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci		val = resource->caps.hysteresis * 1000;
4138c2ecf20Sopenharmony_ci		break;
4148c2ecf20Sopenharmony_ci	case 5:
4158c2ecf20Sopenharmony_ci		if (resource->caps.flags & POWER_METER_IS_BATTERY)
4168c2ecf20Sopenharmony_ci			val = 1;
4178c2ecf20Sopenharmony_ci		else
4188c2ecf20Sopenharmony_ci			val = 0;
4198c2ecf20Sopenharmony_ci		break;
4208c2ecf20Sopenharmony_ci	case 6:
4218c2ecf20Sopenharmony_ci		if (resource->power > resource->cap)
4228c2ecf20Sopenharmony_ci			val = 1;
4238c2ecf20Sopenharmony_ci		else
4248c2ecf20Sopenharmony_ci			val = 0;
4258c2ecf20Sopenharmony_ci		break;
4268c2ecf20Sopenharmony_ci	case 7:
4278c2ecf20Sopenharmony_ci	case 8:
4288c2ecf20Sopenharmony_ci		if (resource->trip[attr->index - 7] < 0)
4298c2ecf20Sopenharmony_ci			return sprintf(buf, "unknown\n");
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci		val = resource->trip[attr->index - 7] * 1000;
4328c2ecf20Sopenharmony_ci		break;
4338c2ecf20Sopenharmony_ci	default:
4348c2ecf20Sopenharmony_ci		WARN(1, "Implementation error: unexpected attribute index %d\n",
4358c2ecf20Sopenharmony_ci		     attr->index);
4368c2ecf20Sopenharmony_ci		break;
4378c2ecf20Sopenharmony_ci	}
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	return sprintf(buf, "%llu\n", val);
4408c2ecf20Sopenharmony_ci}
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_cistatic ssize_t show_accuracy(struct device *dev,
4438c2ecf20Sopenharmony_ci			     struct device_attribute *devattr,
4448c2ecf20Sopenharmony_ci			     char *buf)
4458c2ecf20Sopenharmony_ci{
4468c2ecf20Sopenharmony_ci	struct acpi_device *acpi_dev = to_acpi_device(dev);
4478c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
4488c2ecf20Sopenharmony_ci	unsigned int acc = resource->caps.accuracy;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
4518c2ecf20Sopenharmony_ci}
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_cistatic ssize_t show_name(struct device *dev,
4548c2ecf20Sopenharmony_ci			 struct device_attribute *devattr,
4558c2ecf20Sopenharmony_ci			 char *buf)
4568c2ecf20Sopenharmony_ci{
4578c2ecf20Sopenharmony_ci	return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
4588c2ecf20Sopenharmony_ci}
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ci#define RO_SENSOR_TEMPLATE(_label, _show, _index)	\
4618c2ecf20Sopenharmony_ci	{						\
4628c2ecf20Sopenharmony_ci		.label = _label,			\
4638c2ecf20Sopenharmony_ci		.show  = _show,				\
4648c2ecf20Sopenharmony_ci		.index = _index,			\
4658c2ecf20Sopenharmony_ci	}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci#define RW_SENSOR_TEMPLATE(_label, _show, _set, _index)	\
4688c2ecf20Sopenharmony_ci	{						\
4698c2ecf20Sopenharmony_ci		.label = _label,			\
4708c2ecf20Sopenharmony_ci		.show  = _show,				\
4718c2ecf20Sopenharmony_ci		.set   = _set,				\
4728c2ecf20Sopenharmony_ci		.index = _index,			\
4738c2ecf20Sopenharmony_ci	}
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
4768c2ecf20Sopenharmony_cistatic struct sensor_template meter_attrs[] = {
4778c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0),
4788c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0),
4798c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0),
4808c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1),
4818c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5),
4828c2ecf20Sopenharmony_ci	RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval,
4838c2ecf20Sopenharmony_ci		set_avg_interval, 0),
4848c2ecf20Sopenharmony_ci	{},
4858c2ecf20Sopenharmony_ci};
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_cistatic struct sensor_template misc_cap_attrs[] = {
4888c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2),
4898c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3),
4908c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4),
4918c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6),
4928c2ecf20Sopenharmony_ci	{},
4938c2ecf20Sopenharmony_ci};
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_cistatic struct sensor_template ro_cap_attrs[] = {
4968c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0),
4978c2ecf20Sopenharmony_ci	{},
4988c2ecf20Sopenharmony_ci};
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cistatic struct sensor_template rw_cap_attrs[] = {
5018c2ecf20Sopenharmony_ci	RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0),
5028c2ecf20Sopenharmony_ci	{},
5038c2ecf20Sopenharmony_ci};
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_cistatic struct sensor_template trip_attrs[] = {
5068c2ecf20Sopenharmony_ci	RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7),
5078c2ecf20Sopenharmony_ci	RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8),
5088c2ecf20Sopenharmony_ci	{},
5098c2ecf20Sopenharmony_ci};
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_cistatic struct sensor_template misc_attrs[] = {
5128c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("name", show_name, 0),
5138c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0),
5148c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2),
5158c2ecf20Sopenharmony_ci	RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1),
5168c2ecf20Sopenharmony_ci	{},
5178c2ecf20Sopenharmony_ci};
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci#undef RO_SENSOR_TEMPLATE
5208c2ecf20Sopenharmony_ci#undef RW_SENSOR_TEMPLATE
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci/* Read power domain data */
5238c2ecf20Sopenharmony_cistatic void remove_domain_devices(struct acpi_power_meter_resource *resource)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	int i;
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	if (!resource->num_domain_devices)
5288c2ecf20Sopenharmony_ci		return;
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci	for (i = 0; i < resource->num_domain_devices; i++) {
5318c2ecf20Sopenharmony_ci		struct acpi_device *obj = resource->domain_devices[i];
5328c2ecf20Sopenharmony_ci		if (!obj)
5338c2ecf20Sopenharmony_ci			continue;
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci		sysfs_remove_link(resource->holders_dir,
5368c2ecf20Sopenharmony_ci				  kobject_name(&obj->dev.kobj));
5378c2ecf20Sopenharmony_ci		put_device(&obj->dev);
5388c2ecf20Sopenharmony_ci	}
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci	kfree(resource->domain_devices);
5418c2ecf20Sopenharmony_ci	kobject_put(resource->holders_dir);
5428c2ecf20Sopenharmony_ci	resource->num_domain_devices = 0;
5438c2ecf20Sopenharmony_ci}
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_cistatic int read_domain_devices(struct acpi_power_meter_resource *resource)
5468c2ecf20Sopenharmony_ci{
5478c2ecf20Sopenharmony_ci	int res = 0;
5488c2ecf20Sopenharmony_ci	int i;
5498c2ecf20Sopenharmony_ci	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
5508c2ecf20Sopenharmony_ci	union acpi_object *pss;
5518c2ecf20Sopenharmony_ci	acpi_status status;
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL,
5548c2ecf20Sopenharmony_ci				      &buffer);
5558c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
5568c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD"));
5578c2ecf20Sopenharmony_ci		return -ENODEV;
5588c2ecf20Sopenharmony_ci	}
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	pss = buffer.pointer;
5618c2ecf20Sopenharmony_ci	if (!pss ||
5628c2ecf20Sopenharmony_ci	    pss->type != ACPI_TYPE_PACKAGE) {
5638c2ecf20Sopenharmony_ci		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME
5648c2ecf20Sopenharmony_ci			"Invalid _PMD data\n");
5658c2ecf20Sopenharmony_ci		res = -EFAULT;
5668c2ecf20Sopenharmony_ci		goto end;
5678c2ecf20Sopenharmony_ci	}
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	if (!pss->package.count)
5708c2ecf20Sopenharmony_ci		goto end;
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ci	resource->domain_devices = kcalloc(pss->package.count,
5738c2ecf20Sopenharmony_ci					   sizeof(struct acpi_device *),
5748c2ecf20Sopenharmony_ci					   GFP_KERNEL);
5758c2ecf20Sopenharmony_ci	if (!resource->domain_devices) {
5768c2ecf20Sopenharmony_ci		res = -ENOMEM;
5778c2ecf20Sopenharmony_ci		goto end;
5788c2ecf20Sopenharmony_ci	}
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci	resource->holders_dir = kobject_create_and_add("measures",
5818c2ecf20Sopenharmony_ci					&resource->acpi_dev->dev.kobj);
5828c2ecf20Sopenharmony_ci	if (!resource->holders_dir) {
5838c2ecf20Sopenharmony_ci		res = -ENOMEM;
5848c2ecf20Sopenharmony_ci		goto exit_free;
5858c2ecf20Sopenharmony_ci	}
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci	resource->num_domain_devices = pss->package.count;
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	for (i = 0; i < pss->package.count; i++) {
5908c2ecf20Sopenharmony_ci		struct acpi_device *obj;
5918c2ecf20Sopenharmony_ci		union acpi_object *element = &(pss->package.elements[i]);
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci		/* Refuse non-references */
5948c2ecf20Sopenharmony_ci		if (element->type != ACPI_TYPE_LOCAL_REFERENCE)
5958c2ecf20Sopenharmony_ci			continue;
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci		/* Create a symlink to domain objects */
5988c2ecf20Sopenharmony_ci		resource->domain_devices[i] = NULL;
5998c2ecf20Sopenharmony_ci		if (acpi_bus_get_device(element->reference.handle,
6008c2ecf20Sopenharmony_ci					&resource->domain_devices[i]))
6018c2ecf20Sopenharmony_ci			continue;
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci		obj = resource->domain_devices[i];
6048c2ecf20Sopenharmony_ci		get_device(&obj->dev);
6058c2ecf20Sopenharmony_ci
6068c2ecf20Sopenharmony_ci		res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj,
6078c2ecf20Sopenharmony_ci				      kobject_name(&obj->dev.kobj));
6088c2ecf20Sopenharmony_ci		if (res) {
6098c2ecf20Sopenharmony_ci			put_device(&obj->dev);
6108c2ecf20Sopenharmony_ci			resource->domain_devices[i] = NULL;
6118c2ecf20Sopenharmony_ci		}
6128c2ecf20Sopenharmony_ci	}
6138c2ecf20Sopenharmony_ci
6148c2ecf20Sopenharmony_ci	res = 0;
6158c2ecf20Sopenharmony_ci	goto end;
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_ciexit_free:
6188c2ecf20Sopenharmony_ci	kfree(resource->domain_devices);
6198c2ecf20Sopenharmony_ciend:
6208c2ecf20Sopenharmony_ci	kfree(buffer.pointer);
6218c2ecf20Sopenharmony_ci	return res;
6228c2ecf20Sopenharmony_ci}
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci/* Registration and deregistration */
6258c2ecf20Sopenharmony_cistatic int register_attrs(struct acpi_power_meter_resource *resource,
6268c2ecf20Sopenharmony_ci			  struct sensor_template *attrs)
6278c2ecf20Sopenharmony_ci{
6288c2ecf20Sopenharmony_ci	struct device *dev = &resource->acpi_dev->dev;
6298c2ecf20Sopenharmony_ci	struct sensor_device_attribute *sensors =
6308c2ecf20Sopenharmony_ci		&resource->sensors[resource->num_sensors];
6318c2ecf20Sopenharmony_ci	int res = 0;
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	while (attrs->label) {
6348c2ecf20Sopenharmony_ci		sensors->dev_attr.attr.name = attrs->label;
6358c2ecf20Sopenharmony_ci		sensors->dev_attr.attr.mode = 0444;
6368c2ecf20Sopenharmony_ci		sensors->dev_attr.show = attrs->show;
6378c2ecf20Sopenharmony_ci		sensors->index = attrs->index;
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci		if (attrs->set) {
6408c2ecf20Sopenharmony_ci			sensors->dev_attr.attr.mode |= 0200;
6418c2ecf20Sopenharmony_ci			sensors->dev_attr.store = attrs->set;
6428c2ecf20Sopenharmony_ci		}
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci		sysfs_attr_init(&sensors->dev_attr.attr);
6458c2ecf20Sopenharmony_ci		res = device_create_file(dev, &sensors->dev_attr);
6468c2ecf20Sopenharmony_ci		if (res) {
6478c2ecf20Sopenharmony_ci			sensors->dev_attr.attr.name = NULL;
6488c2ecf20Sopenharmony_ci			goto error;
6498c2ecf20Sopenharmony_ci		}
6508c2ecf20Sopenharmony_ci		sensors++;
6518c2ecf20Sopenharmony_ci		resource->num_sensors++;
6528c2ecf20Sopenharmony_ci		attrs++;
6538c2ecf20Sopenharmony_ci	}
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_cierror:
6568c2ecf20Sopenharmony_ci	return res;
6578c2ecf20Sopenharmony_ci}
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_cistatic void remove_attrs(struct acpi_power_meter_resource *resource)
6608c2ecf20Sopenharmony_ci{
6618c2ecf20Sopenharmony_ci	int i;
6628c2ecf20Sopenharmony_ci
6638c2ecf20Sopenharmony_ci	for (i = 0; i < resource->num_sensors; i++) {
6648c2ecf20Sopenharmony_ci		if (!resource->sensors[i].dev_attr.attr.name)
6658c2ecf20Sopenharmony_ci			continue;
6668c2ecf20Sopenharmony_ci		device_remove_file(&resource->acpi_dev->dev,
6678c2ecf20Sopenharmony_ci				   &resource->sensors[i].dev_attr);
6688c2ecf20Sopenharmony_ci	}
6698c2ecf20Sopenharmony_ci
6708c2ecf20Sopenharmony_ci	remove_domain_devices(resource);
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_ci	resource->num_sensors = 0;
6738c2ecf20Sopenharmony_ci}
6748c2ecf20Sopenharmony_ci
6758c2ecf20Sopenharmony_cistatic int setup_attrs(struct acpi_power_meter_resource *resource)
6768c2ecf20Sopenharmony_ci{
6778c2ecf20Sopenharmony_ci	int res = 0;
6788c2ecf20Sopenharmony_ci
6798c2ecf20Sopenharmony_ci	res = read_domain_devices(resource);
6808c2ecf20Sopenharmony_ci	if (res)
6818c2ecf20Sopenharmony_ci		return res;
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci	if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
6848c2ecf20Sopenharmony_ci		res = register_attrs(resource, meter_attrs);
6858c2ecf20Sopenharmony_ci		if (res)
6868c2ecf20Sopenharmony_ci			goto error;
6878c2ecf20Sopenharmony_ci	}
6888c2ecf20Sopenharmony_ci
6898c2ecf20Sopenharmony_ci	if (resource->caps.flags & POWER_METER_CAN_CAP) {
6908c2ecf20Sopenharmony_ci		if (!can_cap_in_hardware()) {
6918c2ecf20Sopenharmony_ci			dev_warn(&resource->acpi_dev->dev,
6928c2ecf20Sopenharmony_ci				 "Ignoring unsafe software power cap!\n");
6938c2ecf20Sopenharmony_ci			goto skip_unsafe_cap;
6948c2ecf20Sopenharmony_ci		}
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci		if (resource->caps.configurable_cap)
6978c2ecf20Sopenharmony_ci			res = register_attrs(resource, rw_cap_attrs);
6988c2ecf20Sopenharmony_ci		else
6998c2ecf20Sopenharmony_ci			res = register_attrs(resource, ro_cap_attrs);
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci		if (res)
7028c2ecf20Sopenharmony_ci			goto error;
7038c2ecf20Sopenharmony_ci
7048c2ecf20Sopenharmony_ci		res = register_attrs(resource, misc_cap_attrs);
7058c2ecf20Sopenharmony_ci		if (res)
7068c2ecf20Sopenharmony_ci			goto error;
7078c2ecf20Sopenharmony_ci	}
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ciskip_unsafe_cap:
7108c2ecf20Sopenharmony_ci	if (resource->caps.flags & POWER_METER_CAN_TRIP) {
7118c2ecf20Sopenharmony_ci		res = register_attrs(resource, trip_attrs);
7128c2ecf20Sopenharmony_ci		if (res)
7138c2ecf20Sopenharmony_ci			goto error;
7148c2ecf20Sopenharmony_ci	}
7158c2ecf20Sopenharmony_ci
7168c2ecf20Sopenharmony_ci	res = register_attrs(resource, misc_attrs);
7178c2ecf20Sopenharmony_ci	if (res)
7188c2ecf20Sopenharmony_ci		goto error;
7198c2ecf20Sopenharmony_ci
7208c2ecf20Sopenharmony_ci	return res;
7218c2ecf20Sopenharmony_cierror:
7228c2ecf20Sopenharmony_ci	remove_attrs(resource);
7238c2ecf20Sopenharmony_ci	return res;
7248c2ecf20Sopenharmony_ci}
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_cistatic void free_capabilities(struct acpi_power_meter_resource *resource)
7278c2ecf20Sopenharmony_ci{
7288c2ecf20Sopenharmony_ci	acpi_string *str;
7298c2ecf20Sopenharmony_ci	int i;
7308c2ecf20Sopenharmony_ci
7318c2ecf20Sopenharmony_ci	str = &resource->model_number;
7328c2ecf20Sopenharmony_ci	for (i = 0; i < 3; i++, str++)
7338c2ecf20Sopenharmony_ci		kfree(*str);
7348c2ecf20Sopenharmony_ci}
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_cistatic int read_capabilities(struct acpi_power_meter_resource *resource)
7378c2ecf20Sopenharmony_ci{
7388c2ecf20Sopenharmony_ci	int res = 0;
7398c2ecf20Sopenharmony_ci	int i;
7408c2ecf20Sopenharmony_ci	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
7418c2ecf20Sopenharmony_ci	struct acpi_buffer state = { 0, NULL };
7428c2ecf20Sopenharmony_ci	struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
7438c2ecf20Sopenharmony_ci	union acpi_object *pss;
7448c2ecf20Sopenharmony_ci	acpi_string *str;
7458c2ecf20Sopenharmony_ci	acpi_status status;
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
7488c2ecf20Sopenharmony_ci				      &buffer);
7498c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
7508c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
7518c2ecf20Sopenharmony_ci		return -ENODEV;
7528c2ecf20Sopenharmony_ci	}
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci	pss = buffer.pointer;
7558c2ecf20Sopenharmony_ci	if (!pss ||
7568c2ecf20Sopenharmony_ci	    pss->type != ACPI_TYPE_PACKAGE ||
7578c2ecf20Sopenharmony_ci	    pss->package.count != 14) {
7588c2ecf20Sopenharmony_ci		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME
7598c2ecf20Sopenharmony_ci			"Invalid _PMC data\n");
7608c2ecf20Sopenharmony_ci		res = -EFAULT;
7618c2ecf20Sopenharmony_ci		goto end;
7628c2ecf20Sopenharmony_ci	}
7638c2ecf20Sopenharmony_ci
7648c2ecf20Sopenharmony_ci	/* Grab all the integer data at once */
7658c2ecf20Sopenharmony_ci	state.length = sizeof(struct acpi_power_meter_capabilities);
7668c2ecf20Sopenharmony_ci	state.pointer = &resource->caps;
7678c2ecf20Sopenharmony_ci
7688c2ecf20Sopenharmony_ci	status = acpi_extract_package(pss, &format, &state);
7698c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status)) {
7708c2ecf20Sopenharmony_ci		ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
7718c2ecf20Sopenharmony_ci		res = -EFAULT;
7728c2ecf20Sopenharmony_ci		goto end;
7738c2ecf20Sopenharmony_ci	}
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci	if (resource->caps.units) {
7768c2ecf20Sopenharmony_ci		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME
7778c2ecf20Sopenharmony_ci			"Unknown units %llu.\n",
7788c2ecf20Sopenharmony_ci			resource->caps.units);
7798c2ecf20Sopenharmony_ci		res = -EINVAL;
7808c2ecf20Sopenharmony_ci		goto end;
7818c2ecf20Sopenharmony_ci	}
7828c2ecf20Sopenharmony_ci
7838c2ecf20Sopenharmony_ci	/* Grab the string data */
7848c2ecf20Sopenharmony_ci	str = &resource->model_number;
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_ci	for (i = 11; i < 14; i++) {
7878c2ecf20Sopenharmony_ci		union acpi_object *element = &(pss->package.elements[i]);
7888c2ecf20Sopenharmony_ci
7898c2ecf20Sopenharmony_ci		if (element->type != ACPI_TYPE_STRING) {
7908c2ecf20Sopenharmony_ci			res = -EINVAL;
7918c2ecf20Sopenharmony_ci			goto error;
7928c2ecf20Sopenharmony_ci		}
7938c2ecf20Sopenharmony_ci
7948c2ecf20Sopenharmony_ci		*str = kcalloc(element->string.length + 1, sizeof(u8),
7958c2ecf20Sopenharmony_ci			       GFP_KERNEL);
7968c2ecf20Sopenharmony_ci		if (!*str) {
7978c2ecf20Sopenharmony_ci			res = -ENOMEM;
7988c2ecf20Sopenharmony_ci			goto error;
7998c2ecf20Sopenharmony_ci		}
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci		strncpy(*str, element->string.pointer, element->string.length);
8028c2ecf20Sopenharmony_ci		str++;
8038c2ecf20Sopenharmony_ci	}
8048c2ecf20Sopenharmony_ci
8058c2ecf20Sopenharmony_ci	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
8068c2ecf20Sopenharmony_ci	goto end;
8078c2ecf20Sopenharmony_cierror:
8088c2ecf20Sopenharmony_ci	str = &resource->model_number;
8098c2ecf20Sopenharmony_ci	for (i = 0; i < 3; i++, str++)
8108c2ecf20Sopenharmony_ci		kfree(*str);
8118c2ecf20Sopenharmony_ciend:
8128c2ecf20Sopenharmony_ci	kfree(buffer.pointer);
8138c2ecf20Sopenharmony_ci	return res;
8148c2ecf20Sopenharmony_ci}
8158c2ecf20Sopenharmony_ci
8168c2ecf20Sopenharmony_ci/* Handle ACPI event notifications */
8178c2ecf20Sopenharmony_cistatic void acpi_power_meter_notify(struct acpi_device *device, u32 event)
8188c2ecf20Sopenharmony_ci{
8198c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource;
8208c2ecf20Sopenharmony_ci	int res;
8218c2ecf20Sopenharmony_ci
8228c2ecf20Sopenharmony_ci	if (!device || !acpi_driver_data(device))
8238c2ecf20Sopenharmony_ci		return;
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	resource = acpi_driver_data(device);
8268c2ecf20Sopenharmony_ci
8278c2ecf20Sopenharmony_ci	switch (event) {
8288c2ecf20Sopenharmony_ci	case METER_NOTIFY_CONFIG:
8298c2ecf20Sopenharmony_ci		mutex_lock(&resource->lock);
8308c2ecf20Sopenharmony_ci		free_capabilities(resource);
8318c2ecf20Sopenharmony_ci		res = read_capabilities(resource);
8328c2ecf20Sopenharmony_ci		mutex_unlock(&resource->lock);
8338c2ecf20Sopenharmony_ci		if (res)
8348c2ecf20Sopenharmony_ci			break;
8358c2ecf20Sopenharmony_ci
8368c2ecf20Sopenharmony_ci		remove_attrs(resource);
8378c2ecf20Sopenharmony_ci		setup_attrs(resource);
8388c2ecf20Sopenharmony_ci		break;
8398c2ecf20Sopenharmony_ci	case METER_NOTIFY_TRIP:
8408c2ecf20Sopenharmony_ci		sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME);
8418c2ecf20Sopenharmony_ci		break;
8428c2ecf20Sopenharmony_ci	case METER_NOTIFY_CAP:
8438c2ecf20Sopenharmony_ci		sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME);
8448c2ecf20Sopenharmony_ci		break;
8458c2ecf20Sopenharmony_ci	case METER_NOTIFY_INTERVAL:
8468c2ecf20Sopenharmony_ci		sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME);
8478c2ecf20Sopenharmony_ci		break;
8488c2ecf20Sopenharmony_ci	case METER_NOTIFY_CAPPING:
8498c2ecf20Sopenharmony_ci		sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME);
8508c2ecf20Sopenharmony_ci		dev_info(&device->dev, "Capping in progress.\n");
8518c2ecf20Sopenharmony_ci		break;
8528c2ecf20Sopenharmony_ci	default:
8538c2ecf20Sopenharmony_ci		WARN(1, "Unexpected event %d\n", event);
8548c2ecf20Sopenharmony_ci		break;
8558c2ecf20Sopenharmony_ci	}
8568c2ecf20Sopenharmony_ci
8578c2ecf20Sopenharmony_ci	acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS,
8588c2ecf20Sopenharmony_ci					dev_name(&device->dev), event, 0);
8598c2ecf20Sopenharmony_ci}
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_cistatic int acpi_power_meter_add(struct acpi_device *device)
8628c2ecf20Sopenharmony_ci{
8638c2ecf20Sopenharmony_ci	int res;
8648c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource;
8658c2ecf20Sopenharmony_ci
8668c2ecf20Sopenharmony_ci	if (!device)
8678c2ecf20Sopenharmony_ci		return -EINVAL;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	resource = kzalloc(sizeof(struct acpi_power_meter_resource),
8708c2ecf20Sopenharmony_ci			   GFP_KERNEL);
8718c2ecf20Sopenharmony_ci	if (!resource)
8728c2ecf20Sopenharmony_ci		return -ENOMEM;
8738c2ecf20Sopenharmony_ci
8748c2ecf20Sopenharmony_ci	resource->sensors_valid = 0;
8758c2ecf20Sopenharmony_ci	resource->acpi_dev = device;
8768c2ecf20Sopenharmony_ci	mutex_init(&resource->lock);
8778c2ecf20Sopenharmony_ci	strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
8788c2ecf20Sopenharmony_ci	strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
8798c2ecf20Sopenharmony_ci	device->driver_data = resource;
8808c2ecf20Sopenharmony_ci
8818c2ecf20Sopenharmony_ci	free_capabilities(resource);
8828c2ecf20Sopenharmony_ci	res = read_capabilities(resource);
8838c2ecf20Sopenharmony_ci	if (res)
8848c2ecf20Sopenharmony_ci		goto exit_free;
8858c2ecf20Sopenharmony_ci
8868c2ecf20Sopenharmony_ci	resource->trip[0] = resource->trip[1] = -1;
8878c2ecf20Sopenharmony_ci
8888c2ecf20Sopenharmony_ci	res = setup_attrs(resource);
8898c2ecf20Sopenharmony_ci	if (res)
8908c2ecf20Sopenharmony_ci		goto exit_free_capability;
8918c2ecf20Sopenharmony_ci
8928c2ecf20Sopenharmony_ci	resource->hwmon_dev = hwmon_device_register(&device->dev);
8938c2ecf20Sopenharmony_ci	if (IS_ERR(resource->hwmon_dev)) {
8948c2ecf20Sopenharmony_ci		res = PTR_ERR(resource->hwmon_dev);
8958c2ecf20Sopenharmony_ci		goto exit_remove;
8968c2ecf20Sopenharmony_ci	}
8978c2ecf20Sopenharmony_ci
8988c2ecf20Sopenharmony_ci	res = 0;
8998c2ecf20Sopenharmony_ci	goto exit;
9008c2ecf20Sopenharmony_ci
9018c2ecf20Sopenharmony_ciexit_remove:
9028c2ecf20Sopenharmony_ci	remove_attrs(resource);
9038c2ecf20Sopenharmony_ciexit_free_capability:
9048c2ecf20Sopenharmony_ci	free_capabilities(resource);
9058c2ecf20Sopenharmony_ciexit_free:
9068c2ecf20Sopenharmony_ci	kfree(resource);
9078c2ecf20Sopenharmony_ciexit:
9088c2ecf20Sopenharmony_ci	return res;
9098c2ecf20Sopenharmony_ci}
9108c2ecf20Sopenharmony_ci
9118c2ecf20Sopenharmony_cistatic int acpi_power_meter_remove(struct acpi_device *device)
9128c2ecf20Sopenharmony_ci{
9138c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource;
9148c2ecf20Sopenharmony_ci
9158c2ecf20Sopenharmony_ci	if (!device || !acpi_driver_data(device))
9168c2ecf20Sopenharmony_ci		return -EINVAL;
9178c2ecf20Sopenharmony_ci
9188c2ecf20Sopenharmony_ci	resource = acpi_driver_data(device);
9198c2ecf20Sopenharmony_ci	hwmon_device_unregister(resource->hwmon_dev);
9208c2ecf20Sopenharmony_ci
9218c2ecf20Sopenharmony_ci	remove_attrs(resource);
9228c2ecf20Sopenharmony_ci	free_capabilities(resource);
9238c2ecf20Sopenharmony_ci
9248c2ecf20Sopenharmony_ci	kfree(resource);
9258c2ecf20Sopenharmony_ci	return 0;
9268c2ecf20Sopenharmony_ci}
9278c2ecf20Sopenharmony_ci
9288c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
9298c2ecf20Sopenharmony_ci
9308c2ecf20Sopenharmony_cistatic int acpi_power_meter_resume(struct device *dev)
9318c2ecf20Sopenharmony_ci{
9328c2ecf20Sopenharmony_ci	struct acpi_power_meter_resource *resource;
9338c2ecf20Sopenharmony_ci
9348c2ecf20Sopenharmony_ci	if (!dev)
9358c2ecf20Sopenharmony_ci		return -EINVAL;
9368c2ecf20Sopenharmony_ci
9378c2ecf20Sopenharmony_ci	resource = acpi_driver_data(to_acpi_device(dev));
9388c2ecf20Sopenharmony_ci	if (!resource)
9398c2ecf20Sopenharmony_ci		return -EINVAL;
9408c2ecf20Sopenharmony_ci
9418c2ecf20Sopenharmony_ci	free_capabilities(resource);
9428c2ecf20Sopenharmony_ci	read_capabilities(resource);
9438c2ecf20Sopenharmony_ci
9448c2ecf20Sopenharmony_ci	return 0;
9458c2ecf20Sopenharmony_ci}
9468c2ecf20Sopenharmony_ci
9478c2ecf20Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */
9488c2ecf20Sopenharmony_ci
9498c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, acpi_power_meter_resume);
9508c2ecf20Sopenharmony_ci
9518c2ecf20Sopenharmony_cistatic struct acpi_driver acpi_power_meter_driver = {
9528c2ecf20Sopenharmony_ci	.name = "power_meter",
9538c2ecf20Sopenharmony_ci	.class = ACPI_POWER_METER_CLASS,
9548c2ecf20Sopenharmony_ci	.ids = power_meter_ids,
9558c2ecf20Sopenharmony_ci	.ops = {
9568c2ecf20Sopenharmony_ci		.add = acpi_power_meter_add,
9578c2ecf20Sopenharmony_ci		.remove = acpi_power_meter_remove,
9588c2ecf20Sopenharmony_ci		.notify = acpi_power_meter_notify,
9598c2ecf20Sopenharmony_ci		},
9608c2ecf20Sopenharmony_ci	.drv.pm = &acpi_power_meter_pm,
9618c2ecf20Sopenharmony_ci};
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_ci/* Module init/exit routines */
9648c2ecf20Sopenharmony_cistatic int __init enable_cap_knobs(const struct dmi_system_id *d)
9658c2ecf20Sopenharmony_ci{
9668c2ecf20Sopenharmony_ci	cap_in_hardware = 1;
9678c2ecf20Sopenharmony_ci	return 0;
9688c2ecf20Sopenharmony_ci}
9698c2ecf20Sopenharmony_ci
9708c2ecf20Sopenharmony_cistatic const struct dmi_system_id pm_dmi_table[] __initconst = {
9718c2ecf20Sopenharmony_ci	{
9728c2ecf20Sopenharmony_ci		enable_cap_knobs, "IBM Active Energy Manager",
9738c2ecf20Sopenharmony_ci		{
9748c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "IBM")
9758c2ecf20Sopenharmony_ci		},
9768c2ecf20Sopenharmony_ci	},
9778c2ecf20Sopenharmony_ci	{}
9788c2ecf20Sopenharmony_ci};
9798c2ecf20Sopenharmony_ci
9808c2ecf20Sopenharmony_cistatic int __init acpi_power_meter_init(void)
9818c2ecf20Sopenharmony_ci{
9828c2ecf20Sopenharmony_ci	int result;
9838c2ecf20Sopenharmony_ci
9848c2ecf20Sopenharmony_ci	if (acpi_disabled)
9858c2ecf20Sopenharmony_ci		return -ENODEV;
9868c2ecf20Sopenharmony_ci
9878c2ecf20Sopenharmony_ci	dmi_check_system(pm_dmi_table);
9888c2ecf20Sopenharmony_ci
9898c2ecf20Sopenharmony_ci	result = acpi_bus_register_driver(&acpi_power_meter_driver);
9908c2ecf20Sopenharmony_ci	if (result < 0)
9918c2ecf20Sopenharmony_ci		return result;
9928c2ecf20Sopenharmony_ci
9938c2ecf20Sopenharmony_ci	return 0;
9948c2ecf20Sopenharmony_ci}
9958c2ecf20Sopenharmony_ci
9968c2ecf20Sopenharmony_cistatic void __exit acpi_power_meter_exit(void)
9978c2ecf20Sopenharmony_ci{
9988c2ecf20Sopenharmony_ci	acpi_bus_unregister_driver(&acpi_power_meter_driver);
9998c2ecf20Sopenharmony_ci}
10008c2ecf20Sopenharmony_ci
10018c2ecf20Sopenharmony_ciMODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>");
10028c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ACPI 4.0 power meter driver");
10038c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
10048c2ecf20Sopenharmony_ci
10058c2ecf20Sopenharmony_cimodule_param(force_cap_on, bool, 0644);
10068c2ecf20Sopenharmony_ciMODULE_PARM_DESC(force_cap_on, "Enable power cap even it is unsafe to do so.");
10078c2ecf20Sopenharmony_ci
10088c2ecf20Sopenharmony_cimodule_init(acpi_power_meter_init);
10098c2ecf20Sopenharmony_cimodule_exit(acpi_power_meter_exit);
1010