Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters

ykzhao
2009-07-27T01:43:56Z

On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> devices.
It seems OK to me. But it seems that some attributes can't be obtained
by using the hwmon sys I/F.
a. Can we add a sys I/F that can exports the Power meter capability?
display all the content of _PMC as what we have done in battery
b. Add a sys I/F that displays the name of device list measured by
the power meter if there exists the _PMD object.

Thanks.

>
> Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
> ---
> Documentation/hwmon/acpi_power_meter | 25 +
> drivers/acpi/Kconfig | 12 +
> drivers/acpi/Makefile | 1
> drivers/acpi/power_meter.c | 776 ++++++++++++++++++++++++++++++++++
> 4 files changed, 814 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/hwmon/acpi_power_meter
> create mode 100644 drivers/acpi/power_meter.c
>
>
> diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
> new file mode 100644
> index 0000000..0af2e9a
> --- /dev/null
> +++ b/Documentation/hwmon/acpi_power_meter
> @@ -0,0 +1,25 @@
> +Kernel driver power_meter
> +=========================
> +
> +This driver talks to ACPI 4.0 power meters.
> +
> +Supported systems:
> + * Any recent system with ACPI 4.0.
> + Prefix: 'power_meter'
> + Datasheet: http://acpi.info/, section 10.4.
> +
> +Author: Darrick J. Wong
> +
> +Description
> +-----------
> +
> +This driver implements sensor reading support for the power meters exposed in
> +the ACPI 4.0 spec (Chapter 10.4). These devices have a simple set of
> +features--a power meter that returns average power use over a configurable
> +interval, an optional capping mechanism, and a couple of trip points.
> +
> +Special Features
> +----------------
> +
> +The power[1-*]_is_battery knob indicates if the power supply is a battery.
> +Both power[1-*]_average_{min,max} must be set before the trip points will work.
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index 7ec7d88..8ac801d 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
>
> Say N to delete power /proc/acpi/ directories that have moved to /sys/
>
> +config ACPI_POWER_METER
> + tristate "ACPI 4.0 power meter"
> + depends on HWMON
> + default m
> + help
> + This driver exposes ACPI 4.0 power meters as hardware monitoring
> + devices. Say Y (or M) if you have an Intel or AMD computer with
> + a power meter.
> +
> + To compile this driver as a module, choose M here:
> + the module will be called power-meter.
> +
> config ACPI_SYSFS_POWER
> bool "Future power /sys interface"
> select POWER_SUPPLY
> diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
> index 03a985b..82cd49d 100644
> --- a/drivers/acpi/Makefile
> +++ b/drivers/acpi/Makefile
> @@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
> obj-$(CONFIG_ACPI_BATTERY) += battery.o
> obj-$(CONFIG_ACPI_SBS) += sbshc.o
> obj-$(CONFIG_ACPI_SBS) += sbs.o
> +obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o
>
> # processor has its own "processor." module_param namespace
> processor-y := processor_core.o processor_throttling.o
> diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
> new file mode 100644
> index 0000000..9418bbd
> --- /dev/null
> +++ b/drivers/acpi/power_meter.c
> @@ -0,0 +1,776 @@
> +/*
> + * A hwmon driver for ACPI 4.0 power meters
> + * Copyright (C) 2009 IBM
> + *
> + * Author: Darrick J. Wong <djwong@us.ibm.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/kdev_t.h>
> +#include <linux/sched.h>
> +#include <linux/time.h>
> +#include <acpi/acpi_drivers.h>
> +#include <acpi/acpi_bus.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 "power_meter_resource"
> +
> +#define NUM_SENSORS 14
> +
> +#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
> +
> +static struct acpi_device_id power_meter_ids[] = {
> + {"ACPI000D", 0},
> + {"", 0},
> +};
> +MODULE_DEVICE_TABLE(acpi, power_meter_ids);
> +
> +struct acpi_power_meter_capabilities {
> + acpi_integer flags;
> + acpi_integer units;
> + acpi_integer type;
> + acpi_integer accuracy;
> + acpi_integer sampling_time;
> + acpi_integer min_avg_interval;
> + acpi_integer max_avg_interval;
> + acpi_integer hysteresis;
> + acpi_integer configurable_cap;
> + acpi_integer min_cap;
> + acpi_integer 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_integer power;
> + acpi_integer cap;
> + acpi_integer avg_interval;
> + int sensors_valid;
> + unsigned long sensors_last_updated;
> + struct sensor_device_attribute sensors[NUM_SENSORS];
> + int num_sensors;
> + int trip[2];
> +};
> +
> +struct ro_sensor_template {
> + char *label;
> + ssize_t (*show)(struct device *dev,
> + struct device_attribute *devattr,
> + char *buf);
> + int index;
> +};
> +
> +struct rw_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 = strict_strtoul(buf, 10, &temp);
> + if (res)
> + return res;
> +
> + if (temp > resource->caps.max_avg_interval ||
> + temp < resource->caps.min_avg_interval)
> + return -EINVAL;
> + arg0.integer.value = temp;
> +
> + mutex_lock(&resource->lock);
> + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
> + &args, &data);
> + if (!ACPI_FAILURE(status))
> + resource->avg_interval = temp;
> + mutex_unlock(&resource->lock);
> +
> + if (ACPI_FAILURE(status)) {
> + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> + return -EINVAL;
> + }
> +
> + if (data)
> + return -EINVAL;
> +
> + return count;
> +}
> +
> +/* 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_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
> + return -ENODEV;
> + }
> +
> + resource->cap = data;
> + return 0;
> +}
> +
> +static ssize_t show_cap(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_cap(resource);
> + mutex_unlock(&resource->lock);
> +
> + return sprintf(buf, "%llu\n", resource->cap * 1000);
> +}
> +
> +static ssize_t set_cap(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 = strict_strtoul(buf, 10, &temp);
> + if (res)
> + return res;
> +
> + temp /= 1000;
> + if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
> + return -EINVAL;
> + arg0.integer.value = temp;
> +
> + mutex_lock(&resource->lock);
> + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
> + &args, &data);
> + if (!ACPI_FAILURE(status))
> + resource->cap = temp;
> + mutex_unlock(&resource->lock);
> +
> + if (ACPI_FAILURE(status)) {
> + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> + return -EINVAL;
> + }
> +
> + if (data)
> + return -EINVAL;
> +
> + return count;
> +}
> +
> +/* 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_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
> + return -EINVAL;
> + }
> +
> + return data;
> +}
> +
> +static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
> + const char *buf, size_t count)
> +{
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> + struct acpi_device *acpi_dev = to_acpi_device(dev);
> + struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> + int res;
> + unsigned long temp;
> +
> + res = strict_strtoul(buf, 10, &temp);
> + if (res)
> + return res;
> +
> + temp /= 1000;
> + if (temp < 0)
> + return -EINVAL;
> +
> + mutex_lock(&resource->lock);
> + resource->trip[attr->index - 7] = temp;
> + res = set_acpi_trip(resource);
> + mutex_unlock(&resource->lock);
> +
> + if (res)
> + return res;
> +
> + return count;
> +}
> +
> +/* 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_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
> + return -ENODEV;
> + }
> +
> + resource->power = data;
> + resource->sensors_valid = 1;
> + resource->sensors_last_updated = jiffies;
> + return 0;
> +}
> +
> +static ssize_t show_power(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_meter(resource);
> + mutex_unlock(&resource->lock);
> +
> + return sprintf(buf, "%llu\n", resource->power * 1000);
> +}
> +
> +/* Miscellaneous */
> +static ssize_t show_val(struct device *dev,
> + struct device_attribute *devattr,
> + char *buf)
> +{
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> + struct acpi_device *acpi_dev = to_acpi_device(dev);
> + struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> + acpi_integer val = 0;
> +
> + switch (attr->index) {
> + case 0:
> + val = resource->caps.min_avg_interval;
> + break;
> + case 1:
> + val = resource->caps.max_avg_interval;
> + break;
> + case 2:
> + val = resource->caps.min_cap * 1000;
> + break;
> + case 3:
> + val = resource->caps.max_cap * 1000;
> + break;
> + case 4:
> + if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS)
> + return sprintf(buf, "unknown\n");
> +
> + val = resource->caps.hysteresis * 1000;
> + break;
> + case 5:
> + if (resource->caps.flags & POWER_METER_IS_BATTERY)
> + val = 1;
> + else
> + val = 0;
> + break;
> + case 6:
> + if (resource->power > resource->cap)
> + val = 1;
> + else
> + val = 0;
> + break;
> + case 7:
> + case 8:
> + if (resource->trip[attr->index - 7] < 0)
> + return sprintf(buf, "unknown\n");
> +
> + val = resource->trip[attr->index - 7] * 1000;
> + break;
> + default:
> + BUG();
> + }
> +
> + return sprintf(buf, "%llu\n", val);
> +}
> +
> +static ssize_t show_accuracy(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;
> + unsigned int acc = resource->caps.accuracy;
> +
> + return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
> +}
> +
> +static ssize_t show_name(struct device *dev,
> + struct device_attribute *devattr,
> + char *buf)
> +{
> + return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
> +}
> +
> +/* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */
> +static struct ro_sensor_template meter_ro_attrs[] = {
> +{"power1_average", show_power, 0},
> +{"power1_accuracy", show_accuracy, 0},
> +{"power1_average_interval_min", show_val, 0},
> +{"power1_average_interval_max", show_val, 1},
> +{"power1_is_battery", show_val, 5},
> +{NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template meter_rw_attrs[] = {
> +{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template misc_cap_attrs[] = {
> +{"power1_cap_min", show_val, 2},
> +{"power1_cap_max", show_val, 3},
> +{"power1_cap_hyst", show_val, 4},
> +{"power1_alarm", show_val, 6},
> +{NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template ro_cap_attrs[] = {
> +{"power1_cap", show_cap, 0},
> +{NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template rw_cap_attrs[] = {
> +{"power1_cap", show_cap, set_cap, 0},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template trip_attrs[] = {
> +{"power1_average_min", show_val, set_trip, 7},
> +{"power1_average_max", show_val, set_trip, 8},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template misc_attrs[] = {
> +{"name", show_name, 0},
> +{NULL, NULL, 0},
> +};
> +
> +/* Registration and deregistration */
> +static int register_ro_attrs(struct acpi_power_meter_resource *resource,
> + struct ro_sensor_template *ro)
> +{
> + struct device *dev = &resource->acpi_dev->dev;
> + struct sensor_device_attribute *sensors =
> + &resource->sensors[resource->num_sensors];
> + int res;
> +
> + while (ro->label) {
> + sensors->dev_attr.attr.name = ro->label;
> + sensors->dev_attr.attr.mode = S_IRUGO;
> + sensors->dev_attr.show = ro->show;
> + sensors->index = ro->index;
> +
> + res = device_create_file(dev, &sensors->dev_attr);
> + if (res) {
> + sensors->dev_attr.attr.name = NULL;
> + goto error;
> + }
> + sensors++;
> + resource->num_sensors++;
> + ro++;
> + }
> +
> +error:
> + return res;
> +}
> +
> +static int register_rw_attrs(struct acpi_power_meter_resource *resource,
> + struct rw_sensor_template *rw)
> +{
> + struct device *dev = &resource->acpi_dev->dev;
> + struct sensor_device_attribute *sensors =
> + &resource->sensors[resource->num_sensors];
> + int res;
> +
> + while (rw->label) {
> + sensors->dev_attr.attr.name = rw->label;
> + sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
> + sensors->dev_attr.show = rw->show;
> + sensors->dev_attr.store = rw->set;
> + sensors->index = rw->index;
> +
> + res = device_create_file(dev, &sensors->dev_attr);
> + if (res) {
> + sensors->dev_attr.attr.name = NULL;
> + goto error;
> + }
> + sensors++;
> + resource->num_sensors++;
> + rw++;
> + }
> +
> +error:
> + return res;
> +}
> +
> +static void remove_attrs(struct acpi_power_meter_resource *resource)
> +{
> + int i;
> +
> + for (i = 0; i < resource->num_sensors; i++) {
> + if (!resource->sensors[i].dev_attr.attr.name)
> + continue;
> + device_remove_file(&resource->acpi_dev->dev,
> + &resource->sensors[i].dev_attr);
> + }
> +
> + resource->num_sensors = 0;
> +}
> +
> +static int setup_attrs(struct acpi_power_meter_resource *resource)
> +{
> + int res = 0;
> +
> + if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
> + res = register_ro_attrs(resource, meter_ro_attrs);
> + if (res)
> + goto error;
> + res = register_rw_attrs(resource, meter_rw_attrs);
> + if (res)
> + goto error;
> + }
> +
> + if (resource->caps.flags & POWER_METER_CAN_CAP) {
> + if (resource->caps.configurable_cap) {
> + res = register_rw_attrs(resource, rw_cap_attrs);
> + if (res)
> + goto error;
> + } else {
> + res = register_ro_attrs(resource, ro_cap_attrs);
> + if (res)
> + goto error;
> + }
> + res = register_ro_attrs(resource, misc_cap_attrs);
> + if (res)
> + goto error;
> + }
> +
> + if (resource->caps.flags & POWER_METER_CAN_TRIP) {
> + res = register_rw_attrs(resource, trip_attrs);
> + if (res)
> + goto error;
> + }
> +
> + res = register_ro_attrs(resource, misc_attrs);
> + if (res)
> + goto error;
> +
> + return res;
> +error:
> + remove_attrs(resource);
> + return res;
> +}
> +
> +static int read_capabilities(struct acpi_power_meter_resource *resource)
> +{
> + int res = 0;
> + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> + struct acpi_buffer state = { 0, NULL };
> + struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
> + union acpi_object *pss;
> + acpi_status status;
> +
> + status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
> + &buffer);
> + if (ACPI_FAILURE(status)) {
> + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
> + return -ENODEV;
> + }
> +
> + pss = buffer.pointer;
> + if (!pss ||
> + pss->type != ACPI_TYPE_PACKAGE ||
> + pss->package.count != 14) {
> + dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
> + res = -EFAULT;
> + goto end;
> + }
> +
> + state.length = sizeof(struct acpi_power_meter_capabilities);
> + state.pointer = &resource->caps;
> +
> + status = acpi_extract_package(pss, &format, &state);
> + if (ACPI_FAILURE(status)) {
> + ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
> + res = -EFAULT;
> + goto end;
> + }
> +
> + if (resource->caps.units) {
> + dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
> + resource->caps.units);
> + res = -EINVAL;
> + goto end;
> + }
> +
> + dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
> +end:
> + kfree(buffer.pointer);
> + return res;
> +}
> +
> +/* Handle ACPI event notifications */
> +static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
> +{
> + struct acpi_power_meter_resource *resource;
> + int res;
> +
> + if (!device || !acpi_driver_data(device))
> + return;
> +
> + resource = acpi_driver_data(device);
> +
> + mutex_lock(&resource->lock);
> + switch (event) {
> + case METER_NOTIFY_CONFIG:
> + res = read_capabilities(resource);
> + if (res)
> + break;
> +
> + remove_attrs(resource);
> + setup_attrs(resource);
> + break;
> + case METER_NOTIFY_TRIP:
> + update_meter(resource);
> + break;
> + case METER_NOTIFY_CAP:
> + update_cap(resource);
> + break;
> + case METER_NOTIFY_INTERVAL:
> + update_avg_interval(resource);
> + break;
> + case METER_NOTIFY_CAPPING:
> + dev_info(&device->dev, "Capping in progress.\n");
> + break;
> + default:
> + BUG();
> + }
> + mutex_unlock(&resource->lock);
> +}
> +
> +static int acpi_power_meter_add(struct acpi_device *device)
> +{
> + int res;
> + struct acpi_power_meter_resource *resource;
> +
> + if (!device)
> + return -EINVAL;
> +
> + resource = kzalloc(sizeof(struct acpi_power_meter_resource),
> + GFP_KERNEL);
> + if (!resource)
> + return -ENOMEM;
> +
> + resource->sensors_valid = 0;
> + resource->acpi_dev = device;
> + mutex_init(&resource->lock);
> + strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
> + strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
> + device->driver_data = resource;
> +
> + res = read_capabilities(resource);
> + if (res)
> + goto exit_free;
> +
> + resource->trip[0] = resource->trip[1] = -1;
> +
> + res = setup_attrs(resource);
> + if (res)
> + goto exit_free;
> +
> + resource->hwmon_dev = hwmon_device_register(&device->dev);
> + if (IS_ERR(resource->hwmon_dev)) {
> + res = PTR_ERR(resource->hwmon_dev);
> + goto exit_remove;
> + }
> +
> + res = 0;
> + goto exit;
> +
> +exit_remove:
> + remove_attrs(resource);
> +exit_free:
> + kfree(resource);
> +exit:
> + return res;
> +}
> +
> +static int acpi_power_meter_remove(struct acpi_device *device, int type)
> +{
> + struct acpi_power_meter_resource *resource;
> +
> + if (!device || !acpi_driver_data(device))
> + return -EINVAL;
> +
> + resource = acpi_driver_data(device);
> + hwmon_device_unregister(resource->hwmon_dev);
> +
> + remove_attrs(resource);
> +
> + kfree(resource);
> + return 0;
> +}
> +
> +static int acpi_power_meter_resume(struct acpi_device *device)
> +{
> + struct acpi_power_meter_resource *resource;
> +
> + if (!device || !acpi_driver_data(device))
> + return -EINVAL;
> +
> + resource = acpi_driver_data(device);
> + read_capabilities(resource);
> +
> + return 0;
> +}
> +
> +static struct acpi_driver acpi_power_meter_driver = {
> + .name = "power_meter",
> + .class = ACPI_POWER_METER_CLASS,
> + .ids = power_meter_ids,
> + .ops = {
> + .add = acpi_power_meter_add,
> + .remove = acpi_power_meter_remove,
> + .resume = acpi_power_meter_resume,
> + .notify = acpi_power_meter_notify,
> + },
> +};
> +
> +/* Module init/exit routines */
> +
> +static int __init acpi_power_meter_init(void)
> +{
> + int result;
> +
> + result = acpi_bus_register_driver(&acpi_power_meter_driver);
> + if (result < 0)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static void __exit acpi_power_meter_exit(void)
> +{
> + acpi_bus_unregister_driver(&acpi_power_meter_driver);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
> +MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(acpi_power_meter_init);
> +module_exit(acpi_power_meter_exit);
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html