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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxx>
> ---
>  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@xxxxxxxxxx>
> + *
> + * 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@xxxxxxxxxx>");
> +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@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux