// SPDX-License-Identifier: GPL-2.0-or-later
/*
* A hwmon driver for ACPI 4.0 power meters
* Copyright (C) 2009 IBM
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include <linux/module.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/dmi.h>
#include <linux/slab.h>
#include <linux/kdev_t.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/err.h>
#include <linux/acpi.h>
#define ACPI_POWER_METER_NAME "power_meter"
#define ACPI_POWER_METER_DEVICE_NAME "Power Meter"
#define ACPI_POWER_METER_CLASS "pwr_meter_resource"
#define NUM_SENSORS 17
#define POWER_METER_CAN_MEASURE (1 << 0)
#define POWER_METER_CAN_TRIP (1 << 1)
#define POWER_METER_CAN_CAP (1 << 2)
#define POWER_METER_CAN_NOTIFY (1 << 3)
#define POWER_METER_IS_BATTERY (1 << 8)
#define UNKNOWN_HYSTERESIS 0xFFFFFFFF
#define UNKNOWN_POWER 0xFFFFFFFF
#define METER_NOTIFY_CONFIG 0x80
#define METER_NOTIFY_TRIP 0x81
#define METER_NOTIFY_CAP 0x82
#define METER_NOTIFY_CAPPING 0x83
#define METER_NOTIFY_INTERVAL 0x84
#define POWER_AVERAGE_NAME "power1_average"
#define POWER_CAP_NAME "power1_cap"
#define POWER_AVG_INTERVAL_NAME "power1_average_interval"
#define POWER_ALARM_NAME "power1_alarm"
static int cap_in_hardware;
static bool force_cap_on;
static int can_cap_in_hardware(void)
{
return force_cap_on || cap_in_hardware;
}
static const struct acpi_device_id power_meter_ids[] = {
{"ACPI000D", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, power_meter_ids);
struct acpi_power_meter_capabilities {
u64 flags;
u64 units;
u64 type;
u64 accuracy;
u64 sampling_time;
u64 min_avg_interval;
u64 max_avg_interval;
u64 hysteresis;
u64 configurable_cap;
u64 min_cap;
u64 max_cap;
};
struct acpi_power_meter_resource {
struct acpi_device *acpi_dev;
acpi_bus_id name;
struct mutex lock;
struct device *hwmon_dev;
struct acpi_power_meter_capabilities caps;
acpi_string model_number;
acpi_string serial_number;
acpi_string oem_info;
u64 power;
u64 cap;
u64 avg_interval;
bool power_alarm;
int sensors_valid;
unsigned long sensors_last_updated;
#define POWER_METER_TRIP_AVERAGE_MIN_IDX 0
#define POWER_METER_TRIP_AVERAGE_MAX_IDX 1
s64 trip[2];
int num_domain_devices;
struct acpi_device **domain_devices;
struct kobject *holders_dir;
};
/* Averaging interval */
static int update_avg_interval(struct acpi_power_meter_resource *resource)
{
unsigned long long data;
acpi_status status;
status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
NULL, &data);
if (ACPI_FAILURE(status)) {
acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_GAI",
status);
return -ENODEV;
}
resource->avg_interval = data;
return 0;
}
/* Cap functions */
static int update_cap(struct acpi_power_meter_resource *resource)
{
unsigned long long data;
acpi_status status;
status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
NULL, &data);
if (ACPI_FAILURE(status)) {
acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_GHL",
status);
return -ENODEV;
}
resource->cap = data;
return 0;
}
/* Power meter trip points */
static int set_acpi_trip(struct acpi_power_meter_resource *resource)
{
union acpi_object arg_objs[] = {
{ACPI_TYPE_INTEGER},
{ACPI_TYPE_INTEGER}
};
struct acpi_object_list args = { 2, arg_objs };
unsigned long long data;
acpi_status status;
/* Both trip levels must be set */
if (resource->trip[0] < 0 || resource->trip[1] < 0)
return 0;
/* This driver stores min, max; ACPI wants max, min. */
arg_objs[0].integer.value = resource->trip[1];
arg_objs[1].integer.value = resource->trip[0];
status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
&args, &data);
if (ACPI_FAILURE(status)) {
acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PTP",
status);
return -EINVAL;
}
/* _PTP returns 0 on success, nonzero otherwise */
if (data)
return -EINVAL;
return 0;
}
/* Power meter */
static int update_meter(struct acpi_power_meter_resource *resource)
{
unsigned long long data;
acpi_status status;
unsigned long local_jiffies = jiffies;
if (time_before(local_jiffies, resource->sensors_last_updated +
msecs_to_jiffies(resource->caps.sampling_time)) &&
resource->sensors_valid)
return 0;
status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
NULL, &data);
if (ACPI_FAILURE(status)) {
acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PMM",
status);
return -ENODEV;
}
resource->power = data;
resource->sensors_valid = 1;
resource->sensors_last_updated = jiffies;
return 0;
}
/* Read power domain data */
static void remove_domain_devices(struct acpi_power_meter_resource *resource)
{
int i;
if (!resource->num_domain_devices)
return;
for (i = 0; i < resource->num_domain_devices; i++) {
struct acpi_device *obj = resource->domain_devices[i];
if (!obj)
continue;
sysfs_remove_link(resource->holders_dir,