/*
* A hwmon driver for ACPI 4.0 power meters
* Copyright (C) 2009 IBM
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#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"
ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
#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 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;
int sensors_valid;
unsigned long sensors_last_updated;
struct sensor_device_attribute sensors[NUM_SENSORS];
int num_sensors;
s64 trip[2];
int num_domain_devices;
struct acpi_device **domain_devices;
struct kobject *holders_dir;
};
struct sensor_template {
char *label;
ssize_t (*show)(struct device *dev,
struct device_attribute *devattr,
char *buf);
ssize_t (*set)(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count);
int index;
};
/* 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_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
return -ENODEV;
}
resource->avg_interval = data;
return 0;
}
static ssize_t show_avg_interval(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
struct acpi_device *acpi_dev = to_acpi_device(dev);
struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
mutex_lock(&resource->lock);
update_avg_interval(resource);
mutex_unlock(&resource->lock);
return sprintf(buf, "%llu\n", resource->avg_interval);
}
static ssize_t set_avg_interval(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct acpi_device *acpi_dev = to_acpi_device(dev);
struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
union acpi_object arg0 = { ACPI_TYPE_INTEGER };
struct acpi_object_list args = { 1, &arg0 };
int res;
unsigned long temp;
unsigned long long data;
acpi_status status;
res = kstrtoul(buf, 10, &temp);
if (res