162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * A hwmon driver for ACPI 4.0 power meters 462306a36Sopenharmony_ci * Copyright (C) 2009 IBM 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Author: Darrick J. Wong <darrick.wong@oracle.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/hwmon.h> 1162306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h> 1262306a36Sopenharmony_ci#include <linux/jiffies.h> 1362306a36Sopenharmony_ci#include <linux/mutex.h> 1462306a36Sopenharmony_ci#include <linux/dmi.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/kdev_t.h> 1762306a36Sopenharmony_ci#include <linux/sched.h> 1862306a36Sopenharmony_ci#include <linux/time.h> 1962306a36Sopenharmony_ci#include <linux/err.h> 2062306a36Sopenharmony_ci#include <linux/acpi.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define ACPI_POWER_METER_NAME "power_meter" 2362306a36Sopenharmony_ci#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" 2462306a36Sopenharmony_ci#define ACPI_POWER_METER_CLASS "pwr_meter_resource" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define NUM_SENSORS 17 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define POWER_METER_CAN_MEASURE (1 << 0) 2962306a36Sopenharmony_ci#define POWER_METER_CAN_TRIP (1 << 1) 3062306a36Sopenharmony_ci#define POWER_METER_CAN_CAP (1 << 2) 3162306a36Sopenharmony_ci#define POWER_METER_CAN_NOTIFY (1 << 3) 3262306a36Sopenharmony_ci#define POWER_METER_IS_BATTERY (1 << 8) 3362306a36Sopenharmony_ci#define UNKNOWN_HYSTERESIS 0xFFFFFFFF 3462306a36Sopenharmony_ci#define UNKNOWN_POWER 0xFFFFFFFF 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define METER_NOTIFY_CONFIG 0x80 3762306a36Sopenharmony_ci#define METER_NOTIFY_TRIP 0x81 3862306a36Sopenharmony_ci#define METER_NOTIFY_CAP 0x82 3962306a36Sopenharmony_ci#define METER_NOTIFY_CAPPING 0x83 4062306a36Sopenharmony_ci#define METER_NOTIFY_INTERVAL 0x84 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define POWER_AVERAGE_NAME "power1_average" 4362306a36Sopenharmony_ci#define POWER_CAP_NAME "power1_cap" 4462306a36Sopenharmony_ci#define POWER_AVG_INTERVAL_NAME "power1_average_interval" 4562306a36Sopenharmony_ci#define POWER_ALARM_NAME "power1_alarm" 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int cap_in_hardware; 4862306a36Sopenharmony_cistatic bool force_cap_on; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic int can_cap_in_hardware(void) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci return force_cap_on || cap_in_hardware; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic const struct acpi_device_id power_meter_ids[] = { 5662306a36Sopenharmony_ci {"ACPI000D", 0}, 5762306a36Sopenharmony_ci {"", 0}, 5862306a36Sopenharmony_ci}; 5962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, power_meter_ids); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistruct acpi_power_meter_capabilities { 6262306a36Sopenharmony_ci u64 flags; 6362306a36Sopenharmony_ci u64 units; 6462306a36Sopenharmony_ci u64 type; 6562306a36Sopenharmony_ci u64 accuracy; 6662306a36Sopenharmony_ci u64 sampling_time; 6762306a36Sopenharmony_ci u64 min_avg_interval; 6862306a36Sopenharmony_ci u64 max_avg_interval; 6962306a36Sopenharmony_ci u64 hysteresis; 7062306a36Sopenharmony_ci u64 configurable_cap; 7162306a36Sopenharmony_ci u64 min_cap; 7262306a36Sopenharmony_ci u64 max_cap; 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistruct acpi_power_meter_resource { 7662306a36Sopenharmony_ci struct acpi_device *acpi_dev; 7762306a36Sopenharmony_ci acpi_bus_id name; 7862306a36Sopenharmony_ci struct mutex lock; 7962306a36Sopenharmony_ci struct device *hwmon_dev; 8062306a36Sopenharmony_ci struct acpi_power_meter_capabilities caps; 8162306a36Sopenharmony_ci acpi_string model_number; 8262306a36Sopenharmony_ci acpi_string serial_number; 8362306a36Sopenharmony_ci acpi_string oem_info; 8462306a36Sopenharmony_ci u64 power; 8562306a36Sopenharmony_ci u64 cap; 8662306a36Sopenharmony_ci u64 avg_interval; 8762306a36Sopenharmony_ci int sensors_valid; 8862306a36Sopenharmony_ci unsigned long sensors_last_updated; 8962306a36Sopenharmony_ci struct sensor_device_attribute sensors[NUM_SENSORS]; 9062306a36Sopenharmony_ci int num_sensors; 9162306a36Sopenharmony_ci s64 trip[2]; 9262306a36Sopenharmony_ci int num_domain_devices; 9362306a36Sopenharmony_ci struct acpi_device **domain_devices; 9462306a36Sopenharmony_ci struct kobject *holders_dir; 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistruct sensor_template { 9862306a36Sopenharmony_ci char *label; 9962306a36Sopenharmony_ci ssize_t (*show)(struct device *dev, 10062306a36Sopenharmony_ci struct device_attribute *devattr, 10162306a36Sopenharmony_ci char *buf); 10262306a36Sopenharmony_ci ssize_t (*set)(struct device *dev, 10362306a36Sopenharmony_ci struct device_attribute *devattr, 10462306a36Sopenharmony_ci const char *buf, size_t count); 10562306a36Sopenharmony_ci int index; 10662306a36Sopenharmony_ci}; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/* Averaging interval */ 10962306a36Sopenharmony_cistatic int update_avg_interval(struct acpi_power_meter_resource *resource) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci unsigned long long data; 11262306a36Sopenharmony_ci acpi_status status; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI", 11562306a36Sopenharmony_ci NULL, &data); 11662306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 11762306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_GAI", 11862306a36Sopenharmony_ci status); 11962306a36Sopenharmony_ci return -ENODEV; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci resource->avg_interval = data; 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic ssize_t show_avg_interval(struct device *dev, 12762306a36Sopenharmony_ci struct device_attribute *devattr, 12862306a36Sopenharmony_ci char *buf) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 13162306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci mutex_lock(&resource->lock); 13462306a36Sopenharmony_ci update_avg_interval(resource); 13562306a36Sopenharmony_ci mutex_unlock(&resource->lock); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return sprintf(buf, "%llu\n", resource->avg_interval); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic ssize_t set_avg_interval(struct device *dev, 14162306a36Sopenharmony_ci struct device_attribute *devattr, 14262306a36Sopenharmony_ci const char *buf, size_t count) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 14562306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 14662306a36Sopenharmony_ci union acpi_object arg0 = { ACPI_TYPE_INTEGER }; 14762306a36Sopenharmony_ci struct acpi_object_list args = { 1, &arg0 }; 14862306a36Sopenharmony_ci int res; 14962306a36Sopenharmony_ci unsigned long temp; 15062306a36Sopenharmony_ci unsigned long long data; 15162306a36Sopenharmony_ci acpi_status status; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci res = kstrtoul(buf, 10, &temp); 15462306a36Sopenharmony_ci if (res) 15562306a36Sopenharmony_ci return res; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (temp > resource->caps.max_avg_interval || 15862306a36Sopenharmony_ci temp < resource->caps.min_avg_interval) 15962306a36Sopenharmony_ci return -EINVAL; 16062306a36Sopenharmony_ci arg0.integer.value = temp; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci mutex_lock(&resource->lock); 16362306a36Sopenharmony_ci status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI", 16462306a36Sopenharmony_ci &args, &data); 16562306a36Sopenharmony_ci if (ACPI_SUCCESS(status)) 16662306a36Sopenharmony_ci resource->avg_interval = temp; 16762306a36Sopenharmony_ci mutex_unlock(&resource->lock); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 17062306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PAI", 17162306a36Sopenharmony_ci status); 17262306a36Sopenharmony_ci return -EINVAL; 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* _PAI returns 0 on success, nonzero otherwise */ 17662306a36Sopenharmony_ci if (data) 17762306a36Sopenharmony_ci return -EINVAL; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci return count; 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci/* Cap functions */ 18362306a36Sopenharmony_cistatic int update_cap(struct acpi_power_meter_resource *resource) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci unsigned long long data; 18662306a36Sopenharmony_ci acpi_status status; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL", 18962306a36Sopenharmony_ci NULL, &data); 19062306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 19162306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_GHL", 19262306a36Sopenharmony_ci status); 19362306a36Sopenharmony_ci return -ENODEV; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci resource->cap = data; 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic ssize_t show_cap(struct device *dev, 20162306a36Sopenharmony_ci struct device_attribute *devattr, 20262306a36Sopenharmony_ci char *buf) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 20562306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci mutex_lock(&resource->lock); 20862306a36Sopenharmony_ci update_cap(resource); 20962306a36Sopenharmony_ci mutex_unlock(&resource->lock); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci return sprintf(buf, "%llu\n", resource->cap * 1000); 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic ssize_t set_cap(struct device *dev, struct device_attribute *devattr, 21562306a36Sopenharmony_ci const char *buf, size_t count) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 21862306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 21962306a36Sopenharmony_ci union acpi_object arg0 = { ACPI_TYPE_INTEGER }; 22062306a36Sopenharmony_ci struct acpi_object_list args = { 1, &arg0 }; 22162306a36Sopenharmony_ci int res; 22262306a36Sopenharmony_ci unsigned long temp; 22362306a36Sopenharmony_ci unsigned long long data; 22462306a36Sopenharmony_ci acpi_status status; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci res = kstrtoul(buf, 10, &temp); 22762306a36Sopenharmony_ci if (res) 22862306a36Sopenharmony_ci return res; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci temp = DIV_ROUND_CLOSEST(temp, 1000); 23162306a36Sopenharmony_ci if (temp > resource->caps.max_cap || temp < resource->caps.min_cap) 23262306a36Sopenharmony_ci return -EINVAL; 23362306a36Sopenharmony_ci arg0.integer.value = temp; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci mutex_lock(&resource->lock); 23662306a36Sopenharmony_ci status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL", 23762306a36Sopenharmony_ci &args, &data); 23862306a36Sopenharmony_ci if (ACPI_SUCCESS(status)) 23962306a36Sopenharmony_ci resource->cap = temp; 24062306a36Sopenharmony_ci mutex_unlock(&resource->lock); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 24362306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_SHL", 24462306a36Sopenharmony_ci status); 24562306a36Sopenharmony_ci return -EINVAL; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* _SHL returns 0 on success, nonzero otherwise */ 24962306a36Sopenharmony_ci if (data) 25062306a36Sopenharmony_ci return -EINVAL; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci return count; 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci/* Power meter trip points */ 25662306a36Sopenharmony_cistatic int set_acpi_trip(struct acpi_power_meter_resource *resource) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci union acpi_object arg_objs[] = { 25962306a36Sopenharmony_ci {ACPI_TYPE_INTEGER}, 26062306a36Sopenharmony_ci {ACPI_TYPE_INTEGER} 26162306a36Sopenharmony_ci }; 26262306a36Sopenharmony_ci struct acpi_object_list args = { 2, arg_objs }; 26362306a36Sopenharmony_ci unsigned long long data; 26462306a36Sopenharmony_ci acpi_status status; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* Both trip levels must be set */ 26762306a36Sopenharmony_ci if (resource->trip[0] < 0 || resource->trip[1] < 0) 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* This driver stores min, max; ACPI wants max, min. */ 27162306a36Sopenharmony_ci arg_objs[0].integer.value = resource->trip[1]; 27262306a36Sopenharmony_ci arg_objs[1].integer.value = resource->trip[0]; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP", 27562306a36Sopenharmony_ci &args, &data); 27662306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 27762306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PTP", 27862306a36Sopenharmony_ci status); 27962306a36Sopenharmony_ci return -EINVAL; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* _PTP returns 0 on success, nonzero otherwise */ 28362306a36Sopenharmony_ci if (data) 28462306a36Sopenharmony_ci return -EINVAL; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return 0; 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic ssize_t set_trip(struct device *dev, struct device_attribute *devattr, 29062306a36Sopenharmony_ci const char *buf, size_t count) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 29362306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 29462306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 29562306a36Sopenharmony_ci int res; 29662306a36Sopenharmony_ci unsigned long temp; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci res = kstrtoul(buf, 10, &temp); 29962306a36Sopenharmony_ci if (res) 30062306a36Sopenharmony_ci return res; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci temp = DIV_ROUND_CLOSEST(temp, 1000); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci mutex_lock(&resource->lock); 30562306a36Sopenharmony_ci resource->trip[attr->index - 7] = temp; 30662306a36Sopenharmony_ci res = set_acpi_trip(resource); 30762306a36Sopenharmony_ci mutex_unlock(&resource->lock); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (res) 31062306a36Sopenharmony_ci return res; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci return count; 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci/* Power meter */ 31662306a36Sopenharmony_cistatic int update_meter(struct acpi_power_meter_resource *resource) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci unsigned long long data; 31962306a36Sopenharmony_ci acpi_status status; 32062306a36Sopenharmony_ci unsigned long local_jiffies = jiffies; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (time_before(local_jiffies, resource->sensors_last_updated + 32362306a36Sopenharmony_ci msecs_to_jiffies(resource->caps.sampling_time)) && 32462306a36Sopenharmony_ci resource->sensors_valid) 32562306a36Sopenharmony_ci return 0; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM", 32862306a36Sopenharmony_ci NULL, &data); 32962306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 33062306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PMM", 33162306a36Sopenharmony_ci status); 33262306a36Sopenharmony_ci return -ENODEV; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci resource->power = data; 33662306a36Sopenharmony_ci resource->sensors_valid = 1; 33762306a36Sopenharmony_ci resource->sensors_last_updated = jiffies; 33862306a36Sopenharmony_ci return 0; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cistatic ssize_t show_power(struct device *dev, 34262306a36Sopenharmony_ci struct device_attribute *devattr, 34362306a36Sopenharmony_ci char *buf) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 34662306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci mutex_lock(&resource->lock); 34962306a36Sopenharmony_ci update_meter(resource); 35062306a36Sopenharmony_ci mutex_unlock(&resource->lock); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci if (resource->power == UNKNOWN_POWER) 35362306a36Sopenharmony_ci return -ENODATA; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci return sprintf(buf, "%llu\n", resource->power * 1000); 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci/* Miscellaneous */ 35962306a36Sopenharmony_cistatic ssize_t show_str(struct device *dev, 36062306a36Sopenharmony_ci struct device_attribute *devattr, 36162306a36Sopenharmony_ci char *buf) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 36462306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 36562306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 36662306a36Sopenharmony_ci acpi_string val; 36762306a36Sopenharmony_ci int ret; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci mutex_lock(&resource->lock); 37062306a36Sopenharmony_ci switch (attr->index) { 37162306a36Sopenharmony_ci case 0: 37262306a36Sopenharmony_ci val = resource->model_number; 37362306a36Sopenharmony_ci break; 37462306a36Sopenharmony_ci case 1: 37562306a36Sopenharmony_ci val = resource->serial_number; 37662306a36Sopenharmony_ci break; 37762306a36Sopenharmony_ci case 2: 37862306a36Sopenharmony_ci val = resource->oem_info; 37962306a36Sopenharmony_ci break; 38062306a36Sopenharmony_ci default: 38162306a36Sopenharmony_ci WARN(1, "Implementation error: unexpected attribute index %d\n", 38262306a36Sopenharmony_ci attr->index); 38362306a36Sopenharmony_ci val = ""; 38462306a36Sopenharmony_ci break; 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci ret = sprintf(buf, "%s\n", val); 38762306a36Sopenharmony_ci mutex_unlock(&resource->lock); 38862306a36Sopenharmony_ci return ret; 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_cistatic ssize_t show_val(struct device *dev, 39262306a36Sopenharmony_ci struct device_attribute *devattr, 39362306a36Sopenharmony_ci char *buf) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 39662306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 39762306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 39862306a36Sopenharmony_ci u64 val = 0; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci switch (attr->index) { 40162306a36Sopenharmony_ci case 0: 40262306a36Sopenharmony_ci val = resource->caps.min_avg_interval; 40362306a36Sopenharmony_ci break; 40462306a36Sopenharmony_ci case 1: 40562306a36Sopenharmony_ci val = resource->caps.max_avg_interval; 40662306a36Sopenharmony_ci break; 40762306a36Sopenharmony_ci case 2: 40862306a36Sopenharmony_ci val = resource->caps.min_cap * 1000; 40962306a36Sopenharmony_ci break; 41062306a36Sopenharmony_ci case 3: 41162306a36Sopenharmony_ci val = resource->caps.max_cap * 1000; 41262306a36Sopenharmony_ci break; 41362306a36Sopenharmony_ci case 4: 41462306a36Sopenharmony_ci if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS) 41562306a36Sopenharmony_ci return sprintf(buf, "unknown\n"); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci val = resource->caps.hysteresis * 1000; 41862306a36Sopenharmony_ci break; 41962306a36Sopenharmony_ci case 5: 42062306a36Sopenharmony_ci if (resource->caps.flags & POWER_METER_IS_BATTERY) 42162306a36Sopenharmony_ci val = 1; 42262306a36Sopenharmony_ci else 42362306a36Sopenharmony_ci val = 0; 42462306a36Sopenharmony_ci break; 42562306a36Sopenharmony_ci case 6: 42662306a36Sopenharmony_ci if (resource->power > resource->cap) 42762306a36Sopenharmony_ci val = 1; 42862306a36Sopenharmony_ci else 42962306a36Sopenharmony_ci val = 0; 43062306a36Sopenharmony_ci break; 43162306a36Sopenharmony_ci case 7: 43262306a36Sopenharmony_ci case 8: 43362306a36Sopenharmony_ci if (resource->trip[attr->index - 7] < 0) 43462306a36Sopenharmony_ci return sprintf(buf, "unknown\n"); 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci val = resource->trip[attr->index - 7] * 1000; 43762306a36Sopenharmony_ci break; 43862306a36Sopenharmony_ci default: 43962306a36Sopenharmony_ci WARN(1, "Implementation error: unexpected attribute index %d\n", 44062306a36Sopenharmony_ci attr->index); 44162306a36Sopenharmony_ci break; 44262306a36Sopenharmony_ci } 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci return sprintf(buf, "%llu\n", val); 44562306a36Sopenharmony_ci} 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_cistatic ssize_t show_accuracy(struct device *dev, 44862306a36Sopenharmony_ci struct device_attribute *devattr, 44962306a36Sopenharmony_ci char *buf) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci struct acpi_device *acpi_dev = to_acpi_device(dev); 45262306a36Sopenharmony_ci struct acpi_power_meter_resource *resource = acpi_dev->driver_data; 45362306a36Sopenharmony_ci unsigned int acc = resource->caps.accuracy; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000); 45662306a36Sopenharmony_ci} 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cistatic ssize_t show_name(struct device *dev, 45962306a36Sopenharmony_ci struct device_attribute *devattr, 46062306a36Sopenharmony_ci char *buf) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME); 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci#define RO_SENSOR_TEMPLATE(_label, _show, _index) \ 46662306a36Sopenharmony_ci { \ 46762306a36Sopenharmony_ci .label = _label, \ 46862306a36Sopenharmony_ci .show = _show, \ 46962306a36Sopenharmony_ci .index = _index, \ 47062306a36Sopenharmony_ci } 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci#define RW_SENSOR_TEMPLATE(_label, _show, _set, _index) \ 47362306a36Sopenharmony_ci { \ 47462306a36Sopenharmony_ci .label = _label, \ 47562306a36Sopenharmony_ci .show = _show, \ 47662306a36Sopenharmony_ci .set = _set, \ 47762306a36Sopenharmony_ci .index = _index, \ 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci/* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */ 48162306a36Sopenharmony_cistatic struct sensor_template meter_attrs[] = { 48262306a36Sopenharmony_ci RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0), 48362306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0), 48462306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0), 48562306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), 48662306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), 48762306a36Sopenharmony_ci RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, 48862306a36Sopenharmony_ci set_avg_interval, 0), 48962306a36Sopenharmony_ci {}, 49062306a36Sopenharmony_ci}; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cistatic struct sensor_template misc_cap_attrs[] = { 49362306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2), 49462306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3), 49562306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4), 49662306a36Sopenharmony_ci RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6), 49762306a36Sopenharmony_ci {}, 49862306a36Sopenharmony_ci}; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic struct sensor_template ro_cap_attrs[] = { 50162306a36Sopenharmony_ci RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0), 50262306a36Sopenharmony_ci {}, 50362306a36Sopenharmony_ci}; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_cistatic struct sensor_template rw_cap_attrs[] = { 50662306a36Sopenharmony_ci RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0), 50762306a36Sopenharmony_ci {}, 50862306a36Sopenharmony_ci}; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_cistatic struct sensor_template trip_attrs[] = { 51162306a36Sopenharmony_ci RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7), 51262306a36Sopenharmony_ci RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8), 51362306a36Sopenharmony_ci {}, 51462306a36Sopenharmony_ci}; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_cistatic struct sensor_template misc_attrs[] = { 51762306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("name", show_name, 0), 51862306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0), 51962306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2), 52062306a36Sopenharmony_ci RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1), 52162306a36Sopenharmony_ci {}, 52262306a36Sopenharmony_ci}; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci#undef RO_SENSOR_TEMPLATE 52562306a36Sopenharmony_ci#undef RW_SENSOR_TEMPLATE 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci/* Read power domain data */ 52862306a36Sopenharmony_cistatic void remove_domain_devices(struct acpi_power_meter_resource *resource) 52962306a36Sopenharmony_ci{ 53062306a36Sopenharmony_ci int i; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci if (!resource->num_domain_devices) 53362306a36Sopenharmony_ci return; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci for (i = 0; i < resource->num_domain_devices; i++) { 53662306a36Sopenharmony_ci struct acpi_device *obj = resource->domain_devices[i]; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci if (!obj) 53962306a36Sopenharmony_ci continue; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci sysfs_remove_link(resource->holders_dir, 54262306a36Sopenharmony_ci kobject_name(&obj->dev.kobj)); 54362306a36Sopenharmony_ci acpi_dev_put(obj); 54462306a36Sopenharmony_ci } 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci kfree(resource->domain_devices); 54762306a36Sopenharmony_ci kobject_put(resource->holders_dir); 54862306a36Sopenharmony_ci resource->num_domain_devices = 0; 54962306a36Sopenharmony_ci} 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_cistatic int read_domain_devices(struct acpi_power_meter_resource *resource) 55262306a36Sopenharmony_ci{ 55362306a36Sopenharmony_ci int res = 0; 55462306a36Sopenharmony_ci int i; 55562306a36Sopenharmony_ci struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 55662306a36Sopenharmony_ci union acpi_object *pss; 55762306a36Sopenharmony_ci acpi_status status; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL, 56062306a36Sopenharmony_ci &buffer); 56162306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 56262306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PMD", 56362306a36Sopenharmony_ci status); 56462306a36Sopenharmony_ci return -ENODEV; 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci pss = buffer.pointer; 56862306a36Sopenharmony_ci if (!pss || 56962306a36Sopenharmony_ci pss->type != ACPI_TYPE_PACKAGE) { 57062306a36Sopenharmony_ci dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME 57162306a36Sopenharmony_ci "Invalid _PMD data\n"); 57262306a36Sopenharmony_ci res = -EFAULT; 57362306a36Sopenharmony_ci goto end; 57462306a36Sopenharmony_ci } 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci if (!pss->package.count) 57762306a36Sopenharmony_ci goto end; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci resource->domain_devices = kcalloc(pss->package.count, 58062306a36Sopenharmony_ci sizeof(struct acpi_device *), 58162306a36Sopenharmony_ci GFP_KERNEL); 58262306a36Sopenharmony_ci if (!resource->domain_devices) { 58362306a36Sopenharmony_ci res = -ENOMEM; 58462306a36Sopenharmony_ci goto end; 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci resource->holders_dir = kobject_create_and_add("measures", 58862306a36Sopenharmony_ci &resource->acpi_dev->dev.kobj); 58962306a36Sopenharmony_ci if (!resource->holders_dir) { 59062306a36Sopenharmony_ci res = -ENOMEM; 59162306a36Sopenharmony_ci goto exit_free; 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci resource->num_domain_devices = pss->package.count; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci for (i = 0; i < pss->package.count; i++) { 59762306a36Sopenharmony_ci struct acpi_device *obj; 59862306a36Sopenharmony_ci union acpi_object *element = &pss->package.elements[i]; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci /* Refuse non-references */ 60162306a36Sopenharmony_ci if (element->type != ACPI_TYPE_LOCAL_REFERENCE) 60262306a36Sopenharmony_ci continue; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci /* Create a symlink to domain objects */ 60562306a36Sopenharmony_ci obj = acpi_get_acpi_dev(element->reference.handle); 60662306a36Sopenharmony_ci resource->domain_devices[i] = obj; 60762306a36Sopenharmony_ci if (!obj) 60862306a36Sopenharmony_ci continue; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, 61162306a36Sopenharmony_ci kobject_name(&obj->dev.kobj)); 61262306a36Sopenharmony_ci if (res) { 61362306a36Sopenharmony_ci acpi_dev_put(obj); 61462306a36Sopenharmony_ci resource->domain_devices[i] = NULL; 61562306a36Sopenharmony_ci } 61662306a36Sopenharmony_ci } 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci res = 0; 61962306a36Sopenharmony_ci goto end; 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ciexit_free: 62262306a36Sopenharmony_ci kfree(resource->domain_devices); 62362306a36Sopenharmony_ciend: 62462306a36Sopenharmony_ci kfree(buffer.pointer); 62562306a36Sopenharmony_ci return res; 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci/* Registration and deregistration */ 62962306a36Sopenharmony_cistatic int register_attrs(struct acpi_power_meter_resource *resource, 63062306a36Sopenharmony_ci struct sensor_template *attrs) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci struct device *dev = &resource->acpi_dev->dev; 63362306a36Sopenharmony_ci struct sensor_device_attribute *sensors = 63462306a36Sopenharmony_ci &resource->sensors[resource->num_sensors]; 63562306a36Sopenharmony_ci int res = 0; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci while (attrs->label) { 63862306a36Sopenharmony_ci sensors->dev_attr.attr.name = attrs->label; 63962306a36Sopenharmony_ci sensors->dev_attr.attr.mode = 0444; 64062306a36Sopenharmony_ci sensors->dev_attr.show = attrs->show; 64162306a36Sopenharmony_ci sensors->index = attrs->index; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci if (attrs->set) { 64462306a36Sopenharmony_ci sensors->dev_attr.attr.mode |= 0200; 64562306a36Sopenharmony_ci sensors->dev_attr.store = attrs->set; 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci sysfs_attr_init(&sensors->dev_attr.attr); 64962306a36Sopenharmony_ci res = device_create_file(dev, &sensors->dev_attr); 65062306a36Sopenharmony_ci if (res) { 65162306a36Sopenharmony_ci sensors->dev_attr.attr.name = NULL; 65262306a36Sopenharmony_ci goto error; 65362306a36Sopenharmony_ci } 65462306a36Sopenharmony_ci sensors++; 65562306a36Sopenharmony_ci resource->num_sensors++; 65662306a36Sopenharmony_ci attrs++; 65762306a36Sopenharmony_ci } 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_cierror: 66062306a36Sopenharmony_ci return res; 66162306a36Sopenharmony_ci} 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_cistatic void remove_attrs(struct acpi_power_meter_resource *resource) 66462306a36Sopenharmony_ci{ 66562306a36Sopenharmony_ci int i; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci for (i = 0; i < resource->num_sensors; i++) { 66862306a36Sopenharmony_ci if (!resource->sensors[i].dev_attr.attr.name) 66962306a36Sopenharmony_ci continue; 67062306a36Sopenharmony_ci device_remove_file(&resource->acpi_dev->dev, 67162306a36Sopenharmony_ci &resource->sensors[i].dev_attr); 67262306a36Sopenharmony_ci } 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci remove_domain_devices(resource); 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci resource->num_sensors = 0; 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_cistatic int setup_attrs(struct acpi_power_meter_resource *resource) 68062306a36Sopenharmony_ci{ 68162306a36Sopenharmony_ci int res = 0; 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci res = read_domain_devices(resource); 68462306a36Sopenharmony_ci if (res) 68562306a36Sopenharmony_ci return res; 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci if (resource->caps.flags & POWER_METER_CAN_MEASURE) { 68862306a36Sopenharmony_ci res = register_attrs(resource, meter_attrs); 68962306a36Sopenharmony_ci if (res) 69062306a36Sopenharmony_ci goto error; 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci if (resource->caps.flags & POWER_METER_CAN_CAP) { 69462306a36Sopenharmony_ci if (!can_cap_in_hardware()) { 69562306a36Sopenharmony_ci dev_warn(&resource->acpi_dev->dev, 69662306a36Sopenharmony_ci "Ignoring unsafe software power cap!\n"); 69762306a36Sopenharmony_ci goto skip_unsafe_cap; 69862306a36Sopenharmony_ci } 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci if (resource->caps.configurable_cap) 70162306a36Sopenharmony_ci res = register_attrs(resource, rw_cap_attrs); 70262306a36Sopenharmony_ci else 70362306a36Sopenharmony_ci res = register_attrs(resource, ro_cap_attrs); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci if (res) 70662306a36Sopenharmony_ci goto error; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci res = register_attrs(resource, misc_cap_attrs); 70962306a36Sopenharmony_ci if (res) 71062306a36Sopenharmony_ci goto error; 71162306a36Sopenharmony_ci } 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ciskip_unsafe_cap: 71462306a36Sopenharmony_ci if (resource->caps.flags & POWER_METER_CAN_TRIP) { 71562306a36Sopenharmony_ci res = register_attrs(resource, trip_attrs); 71662306a36Sopenharmony_ci if (res) 71762306a36Sopenharmony_ci goto error; 71862306a36Sopenharmony_ci } 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci res = register_attrs(resource, misc_attrs); 72162306a36Sopenharmony_ci if (res) 72262306a36Sopenharmony_ci goto error; 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci return res; 72562306a36Sopenharmony_cierror: 72662306a36Sopenharmony_ci remove_attrs(resource); 72762306a36Sopenharmony_ci return res; 72862306a36Sopenharmony_ci} 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_cistatic void free_capabilities(struct acpi_power_meter_resource *resource) 73162306a36Sopenharmony_ci{ 73262306a36Sopenharmony_ci acpi_string *str; 73362306a36Sopenharmony_ci int i; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci str = &resource->model_number; 73662306a36Sopenharmony_ci for (i = 0; i < 3; i++, str++) { 73762306a36Sopenharmony_ci kfree(*str); 73862306a36Sopenharmony_ci *str = NULL; 73962306a36Sopenharmony_ci } 74062306a36Sopenharmony_ci} 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_cistatic int read_capabilities(struct acpi_power_meter_resource *resource) 74362306a36Sopenharmony_ci{ 74462306a36Sopenharmony_ci int res = 0; 74562306a36Sopenharmony_ci int i; 74662306a36Sopenharmony_ci struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 74762306a36Sopenharmony_ci struct acpi_buffer state = { 0, NULL }; 74862306a36Sopenharmony_ci struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" }; 74962306a36Sopenharmony_ci union acpi_object *pss; 75062306a36Sopenharmony_ci acpi_string *str; 75162306a36Sopenharmony_ci acpi_status status; 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL, 75462306a36Sopenharmony_ci &buffer); 75562306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 75662306a36Sopenharmony_ci acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PMC", 75762306a36Sopenharmony_ci status); 75862306a36Sopenharmony_ci return -ENODEV; 75962306a36Sopenharmony_ci } 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci pss = buffer.pointer; 76262306a36Sopenharmony_ci if (!pss || 76362306a36Sopenharmony_ci pss->type != ACPI_TYPE_PACKAGE || 76462306a36Sopenharmony_ci pss->package.count != 14) { 76562306a36Sopenharmony_ci dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME 76662306a36Sopenharmony_ci "Invalid _PMC data\n"); 76762306a36Sopenharmony_ci res = -EFAULT; 76862306a36Sopenharmony_ci goto end; 76962306a36Sopenharmony_ci } 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci /* Grab all the integer data at once */ 77262306a36Sopenharmony_ci state.length = sizeof(struct acpi_power_meter_capabilities); 77362306a36Sopenharmony_ci state.pointer = &resource->caps; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci status = acpi_extract_package(pss, &format, &state); 77662306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 77762306a36Sopenharmony_ci dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME 77862306a36Sopenharmony_ci "_PMC package parsing failed: %s\n", 77962306a36Sopenharmony_ci acpi_format_exception(status)); 78062306a36Sopenharmony_ci res = -EFAULT; 78162306a36Sopenharmony_ci goto end; 78262306a36Sopenharmony_ci } 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci if (resource->caps.units) { 78562306a36Sopenharmony_ci dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME 78662306a36Sopenharmony_ci "Unknown units %llu.\n", 78762306a36Sopenharmony_ci resource->caps.units); 78862306a36Sopenharmony_ci res = -EINVAL; 78962306a36Sopenharmony_ci goto end; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci /* Grab the string data */ 79362306a36Sopenharmony_ci str = &resource->model_number; 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci for (i = 11; i < 14; i++) { 79662306a36Sopenharmony_ci union acpi_object *element = &pss->package.elements[i]; 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci if (element->type != ACPI_TYPE_STRING) { 79962306a36Sopenharmony_ci res = -EINVAL; 80062306a36Sopenharmony_ci goto error; 80162306a36Sopenharmony_ci } 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci *str = kcalloc(element->string.length + 1, sizeof(u8), 80462306a36Sopenharmony_ci GFP_KERNEL); 80562306a36Sopenharmony_ci if (!*str) { 80662306a36Sopenharmony_ci res = -ENOMEM; 80762306a36Sopenharmony_ci goto error; 80862306a36Sopenharmony_ci } 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci strncpy(*str, element->string.pointer, element->string.length); 81162306a36Sopenharmony_ci str++; 81262306a36Sopenharmony_ci } 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n"); 81562306a36Sopenharmony_ci goto end; 81662306a36Sopenharmony_cierror: 81762306a36Sopenharmony_ci free_capabilities(resource); 81862306a36Sopenharmony_ciend: 81962306a36Sopenharmony_ci kfree(buffer.pointer); 82062306a36Sopenharmony_ci return res; 82162306a36Sopenharmony_ci} 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci/* Handle ACPI event notifications */ 82462306a36Sopenharmony_cistatic void acpi_power_meter_notify(struct acpi_device *device, u32 event) 82562306a36Sopenharmony_ci{ 82662306a36Sopenharmony_ci struct acpi_power_meter_resource *resource; 82762306a36Sopenharmony_ci int res; 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci if (!device || !acpi_driver_data(device)) 83062306a36Sopenharmony_ci return; 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci resource = acpi_driver_data(device); 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci switch (event) { 83562306a36Sopenharmony_ci case METER_NOTIFY_CONFIG: 83662306a36Sopenharmony_ci mutex_lock(&resource->lock); 83762306a36Sopenharmony_ci free_capabilities(resource); 83862306a36Sopenharmony_ci res = read_capabilities(resource); 83962306a36Sopenharmony_ci mutex_unlock(&resource->lock); 84062306a36Sopenharmony_ci if (res) 84162306a36Sopenharmony_ci break; 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci remove_attrs(resource); 84462306a36Sopenharmony_ci setup_attrs(resource); 84562306a36Sopenharmony_ci break; 84662306a36Sopenharmony_ci case METER_NOTIFY_TRIP: 84762306a36Sopenharmony_ci sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); 84862306a36Sopenharmony_ci break; 84962306a36Sopenharmony_ci case METER_NOTIFY_CAP: 85062306a36Sopenharmony_ci sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); 85162306a36Sopenharmony_ci break; 85262306a36Sopenharmony_ci case METER_NOTIFY_INTERVAL: 85362306a36Sopenharmony_ci sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); 85462306a36Sopenharmony_ci break; 85562306a36Sopenharmony_ci case METER_NOTIFY_CAPPING: 85662306a36Sopenharmony_ci sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); 85762306a36Sopenharmony_ci dev_info(&device->dev, "Capping in progress.\n"); 85862306a36Sopenharmony_ci break; 85962306a36Sopenharmony_ci default: 86062306a36Sopenharmony_ci WARN(1, "Unexpected event %d\n", event); 86162306a36Sopenharmony_ci break; 86262306a36Sopenharmony_ci } 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, 86562306a36Sopenharmony_ci dev_name(&device->dev), event, 0); 86662306a36Sopenharmony_ci} 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_cistatic int acpi_power_meter_add(struct acpi_device *device) 86962306a36Sopenharmony_ci{ 87062306a36Sopenharmony_ci int res; 87162306a36Sopenharmony_ci struct acpi_power_meter_resource *resource; 87262306a36Sopenharmony_ci 87362306a36Sopenharmony_ci if (!device) 87462306a36Sopenharmony_ci return -EINVAL; 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci resource = kzalloc(sizeof(*resource), GFP_KERNEL); 87762306a36Sopenharmony_ci if (!resource) 87862306a36Sopenharmony_ci return -ENOMEM; 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci resource->sensors_valid = 0; 88162306a36Sopenharmony_ci resource->acpi_dev = device; 88262306a36Sopenharmony_ci mutex_init(&resource->lock); 88362306a36Sopenharmony_ci strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); 88462306a36Sopenharmony_ci strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); 88562306a36Sopenharmony_ci device->driver_data = resource; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci res = read_capabilities(resource); 88862306a36Sopenharmony_ci if (res) 88962306a36Sopenharmony_ci goto exit_free; 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci resource->trip[0] = -1; 89262306a36Sopenharmony_ci resource->trip[1] = -1; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci res = setup_attrs(resource); 89562306a36Sopenharmony_ci if (res) 89662306a36Sopenharmony_ci goto exit_free_capability; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci resource->hwmon_dev = hwmon_device_register(&device->dev); 89962306a36Sopenharmony_ci if (IS_ERR(resource->hwmon_dev)) { 90062306a36Sopenharmony_ci res = PTR_ERR(resource->hwmon_dev); 90162306a36Sopenharmony_ci goto exit_remove; 90262306a36Sopenharmony_ci } 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci res = 0; 90562306a36Sopenharmony_ci goto exit; 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ciexit_remove: 90862306a36Sopenharmony_ci remove_attrs(resource); 90962306a36Sopenharmony_ciexit_free_capability: 91062306a36Sopenharmony_ci free_capabilities(resource); 91162306a36Sopenharmony_ciexit_free: 91262306a36Sopenharmony_ci kfree(resource); 91362306a36Sopenharmony_ciexit: 91462306a36Sopenharmony_ci return res; 91562306a36Sopenharmony_ci} 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_cistatic void acpi_power_meter_remove(struct acpi_device *device) 91862306a36Sopenharmony_ci{ 91962306a36Sopenharmony_ci struct acpi_power_meter_resource *resource; 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci if (!device || !acpi_driver_data(device)) 92262306a36Sopenharmony_ci return; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci resource = acpi_driver_data(device); 92562306a36Sopenharmony_ci hwmon_device_unregister(resource->hwmon_dev); 92662306a36Sopenharmony_ci 92762306a36Sopenharmony_ci remove_attrs(resource); 92862306a36Sopenharmony_ci free_capabilities(resource); 92962306a36Sopenharmony_ci 93062306a36Sopenharmony_ci kfree(resource); 93162306a36Sopenharmony_ci} 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_cistatic int acpi_power_meter_resume(struct device *dev) 93462306a36Sopenharmony_ci{ 93562306a36Sopenharmony_ci struct acpi_power_meter_resource *resource; 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci if (!dev) 93862306a36Sopenharmony_ci return -EINVAL; 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci resource = acpi_driver_data(to_acpi_device(dev)); 94162306a36Sopenharmony_ci if (!resource) 94262306a36Sopenharmony_ci return -EINVAL; 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci free_capabilities(resource); 94562306a36Sopenharmony_ci read_capabilities(resource); 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_ci return 0; 94862306a36Sopenharmony_ci} 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_cistatic DEFINE_SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, 95162306a36Sopenharmony_ci acpi_power_meter_resume); 95262306a36Sopenharmony_ci 95362306a36Sopenharmony_cistatic struct acpi_driver acpi_power_meter_driver = { 95462306a36Sopenharmony_ci .name = "power_meter", 95562306a36Sopenharmony_ci .class = ACPI_POWER_METER_CLASS, 95662306a36Sopenharmony_ci .ids = power_meter_ids, 95762306a36Sopenharmony_ci .ops = { 95862306a36Sopenharmony_ci .add = acpi_power_meter_add, 95962306a36Sopenharmony_ci .remove = acpi_power_meter_remove, 96062306a36Sopenharmony_ci .notify = acpi_power_meter_notify, 96162306a36Sopenharmony_ci }, 96262306a36Sopenharmony_ci .drv.pm = pm_sleep_ptr(&acpi_power_meter_pm), 96362306a36Sopenharmony_ci}; 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci/* Module init/exit routines */ 96662306a36Sopenharmony_cistatic int __init enable_cap_knobs(const struct dmi_system_id *d) 96762306a36Sopenharmony_ci{ 96862306a36Sopenharmony_ci cap_in_hardware = 1; 96962306a36Sopenharmony_ci return 0; 97062306a36Sopenharmony_ci} 97162306a36Sopenharmony_ci 97262306a36Sopenharmony_cistatic const struct dmi_system_id pm_dmi_table[] __initconst = { 97362306a36Sopenharmony_ci { 97462306a36Sopenharmony_ci enable_cap_knobs, "IBM Active Energy Manager", 97562306a36Sopenharmony_ci { 97662306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "IBM") 97762306a36Sopenharmony_ci }, 97862306a36Sopenharmony_ci }, 97962306a36Sopenharmony_ci {} 98062306a36Sopenharmony_ci}; 98162306a36Sopenharmony_ci 98262306a36Sopenharmony_cistatic int __init acpi_power_meter_init(void) 98362306a36Sopenharmony_ci{ 98462306a36Sopenharmony_ci int result; 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci if (acpi_disabled) 98762306a36Sopenharmony_ci return -ENODEV; 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_ci dmi_check_system(pm_dmi_table); 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ci result = acpi_bus_register_driver(&acpi_power_meter_driver); 99262306a36Sopenharmony_ci if (result < 0) 99362306a36Sopenharmony_ci return result; 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci return 0; 99662306a36Sopenharmony_ci} 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_cistatic void __exit acpi_power_meter_exit(void) 99962306a36Sopenharmony_ci{ 100062306a36Sopenharmony_ci acpi_bus_unregister_driver(&acpi_power_meter_driver); 100162306a36Sopenharmony_ci} 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_ciMODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>"); 100462306a36Sopenharmony_ciMODULE_DESCRIPTION("ACPI 4.0 power meter driver"); 100562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 100662306a36Sopenharmony_ci 100762306a36Sopenharmony_cimodule_param(force_cap_on, bool, 0644); 100862306a36Sopenharmony_ciMODULE_PARM_DESC(force_cap_on, "Enable power cap even it is unsafe to do so."); 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_cimodule_init(acpi_power_meter_init); 101162306a36Sopenharmony_cimodule_exit(acpi_power_meter_exit); 1012