lkml.org 
[lkml]   [2009]   [Jul]   [26]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    SubjectRe: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
    From
    Date
    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



    \
     
     \ /
      Last update: 2009-07-27 03:47    [W:0.093 / U:1.436 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site