| /* | 
 |  * 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/dmi.h> | 
 | #include <linux/slab.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		"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 int 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; | 
 | 	int			trip[2]; | 
 | 	int			num_domain_devices; | 
 | 	struct acpi_device	**domain_devices; | 
 | 	struct kobject		*holders_dir; | 
 | }; | 
 |  | 
 | 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; | 
 | 	} | 
 |  | 
 | 	/* _PAI returns 0 on success, nonzero otherwise */ | 
 | 	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; | 
 | 	} | 
 |  | 
 | 	/* _SHL returns 0 on success, nonzero otherwise */ | 
 | 	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; | 
 | 	} | 
 |  | 
 | 	/* _PTP returns 0 on success, nonzero otherwise */ | 
 | 	if (data) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | 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_str(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_string val; | 
 |  | 
 | 	switch (attr->index) { | 
 | 	case 0: | 
 | 		val = resource->model_number; | 
 | 		break; | 
 | 	case 1: | 
 | 		val = resource->serial_number; | 
 | 		break; | 
 | 	case 2: | 
 | 		val = resource->oem_info; | 
 | 		break; | 
 | 	default: | 
 | 		BUG(); | 
 | 	} | 
 |  | 
 | 	return sprintf(buf, "%s\n", val); | 
 | } | 
 |  | 
 | 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; | 
 | 	u64 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[] = { | 
 | {POWER_AVERAGE_NAME, 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[] = { | 
 | {POWER_AVG_INTERVAL_NAME, 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}, | 
 | {POWER_ALARM_NAME, show_val, 6}, | 
 | {NULL, NULL, 0}, | 
 | }; | 
 |  | 
 | static struct ro_sensor_template ro_cap_attrs[] = { | 
 | {POWER_CAP_NAME, show_cap, 0}, | 
 | {NULL, NULL, 0}, | 
 | }; | 
 |  | 
 | static struct rw_sensor_template rw_cap_attrs[] = { | 
 | {POWER_CAP_NAME, 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}, | 
 | {"power1_model_number", show_str, 0}, | 
 | {"power1_oem_info", show_str, 2}, | 
 | {"power1_serial_number", show_str, 1}, | 
 | {NULL, NULL, 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, | 
 | 				  kobject_name(&obj->dev.kobj)); | 
 | 		put_device(&obj->dev); | 
 | 	} | 
 |  | 
 | 	kfree(resource->domain_devices); | 
 | 	kobject_put(resource->holders_dir); | 
 | 	resource->num_domain_devices = 0; | 
 | } | 
 |  | 
 | static int read_domain_devices(struct acpi_power_meter_resource *resource) | 
 | { | 
 | 	int res = 0; | 
 | 	int i; | 
 | 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | 
 | 	union acpi_object *pss; | 
 | 	acpi_status status; | 
 |  | 
 | 	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL, | 
 | 				      &buffer); | 
 | 	if (ACPI_FAILURE(status)) { | 
 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD")); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	pss = buffer.pointer; | 
 | 	if (!pss || | 
 | 	    pss->type != ACPI_TYPE_PACKAGE) { | 
 | 		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME | 
 | 			"Invalid _PMD data\n"); | 
 | 		res = -EFAULT; | 
 | 		goto end; | 
 | 	} | 
 |  | 
 | 	if (!pss->package.count) | 
 | 		goto end; | 
 |  | 
 | 	resource->domain_devices = kzalloc(sizeof(struct acpi_device *) * | 
 | 					   pss->package.count, GFP_KERNEL); | 
 | 	if (!resource->domain_devices) { | 
 | 		res = -ENOMEM; | 
 | 		goto end; | 
 | 	} | 
 |  | 
 | 	resource->holders_dir = kobject_create_and_add("measures", | 
 | 					&resource->acpi_dev->dev.kobj); | 
 | 	if (!resource->holders_dir) { | 
 | 		res = -ENOMEM; | 
 | 		goto exit_free; | 
 | 	} | 
 |  | 
 | 	resource->num_domain_devices = pss->package.count; | 
 |  | 
 | 	for (i = 0; i < pss->package.count; i++) { | 
 | 		struct acpi_device *obj; | 
 | 		union acpi_object *element = &(pss->package.elements[i]); | 
 |  | 
 | 		/* Refuse non-references */ | 
 | 		if (element->type != ACPI_TYPE_LOCAL_REFERENCE) | 
 | 			continue; | 
 |  | 
 | 		/* Create a symlink to domain objects */ | 
 | 		resource->domain_devices[i] = NULL; | 
 | 		status = acpi_bus_get_device(element->reference.handle, | 
 | 					     &resource->domain_devices[i]); | 
 | 		if (ACPI_FAILURE(status)) | 
 | 			continue; | 
 |  | 
 | 		obj = resource->domain_devices[i]; | 
 | 		get_device(&obj->dev); | 
 |  | 
 | 		res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, | 
 | 				      kobject_name(&obj->dev.kobj)); | 
 | 		if (res) { | 
 | 			put_device(&obj->dev); | 
 | 			resource->domain_devices[i] = NULL; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	res = 0; | 
 | 	goto end; | 
 |  | 
 | exit_free: | 
 | 	kfree(resource->domain_devices); | 
 | end: | 
 | 	kfree(buffer.pointer); | 
 | 	return res; | 
 | } | 
 |  | 
 | /* 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 = 0; | 
 |  | 
 | 	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 = 0; | 
 |  | 
 | 	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); | 
 | 	} | 
 |  | 
 | 	remove_domain_devices(resource); | 
 |  | 
 | 	resource->num_sensors = 0; | 
 | } | 
 |  | 
 | static int setup_attrs(struct acpi_power_meter_resource *resource) | 
 | { | 
 | 	int res = 0; | 
 |  | 
 | 	res = read_domain_devices(resource); | 
 | 	if (res) | 
 | 		return res; | 
 |  | 
 | 	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 (!can_cap_in_hardware()) { | 
 | 			dev_err(&resource->acpi_dev->dev, | 
 | 				"Ignoring unsafe software power cap!\n"); | 
 | 			goto skip_unsafe_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; | 
 | 	} | 
 | skip_unsafe_cap: | 
 |  | 
 | 	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 void free_capabilities(struct acpi_power_meter_resource *resource) | 
 | { | 
 | 	acpi_string *str; | 
 | 	int i; | 
 |  | 
 | 	str = &resource->model_number; | 
 | 	for (i = 0; i < 3; i++, str++) | 
 | 		kfree(*str); | 
 | } | 
 |  | 
 | static int read_capabilities(struct acpi_power_meter_resource *resource) | 
 | { | 
 | 	int res = 0; | 
 | 	int i; | 
 | 	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_string *str; | 
 | 	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, ACPI_POWER_METER_NAME | 
 | 			"Invalid _PMC data\n"); | 
 | 		res = -EFAULT; | 
 | 		goto end; | 
 | 	} | 
 |  | 
 | 	/* Grab all the integer data at once */ | 
 | 	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, ACPI_POWER_METER_NAME | 
 | 			"Unknown units %llu.\n", | 
 | 			resource->caps.units); | 
 | 		res = -EINVAL; | 
 | 		goto end; | 
 | 	} | 
 |  | 
 | 	/* Grab the string data */ | 
 | 	str = &resource->model_number; | 
 |  | 
 | 	for (i = 11; i < 14; i++) { | 
 | 		union acpi_object *element = &(pss->package.elements[i]); | 
 |  | 
 | 		if (element->type != ACPI_TYPE_STRING) { | 
 | 			res = -EINVAL; | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		*str = kzalloc(sizeof(u8) * (element->string.length + 1), | 
 | 			       GFP_KERNEL); | 
 | 		if (!*str) { | 
 | 			res = -ENOMEM; | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		strncpy(*str, element->string.pointer, element->string.length); | 
 | 		str++; | 
 | 	} | 
 |  | 
 | 	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n"); | 
 | 	goto end; | 
 | error: | 
 | 	str = &resource->model_number; | 
 | 	for (i = 0; i < 3; i++, str++) | 
 | 		kfree(*str); | 
 | 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: | 
 | 		free_capabilities(resource); | 
 | 		res = read_capabilities(resource); | 
 | 		if (res) | 
 | 			break; | 
 |  | 
 | 		remove_attrs(resource); | 
 | 		setup_attrs(resource); | 
 | 		break; | 
 | 	case METER_NOTIFY_TRIP: | 
 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); | 
 | 		update_meter(resource); | 
 | 		break; | 
 | 	case METER_NOTIFY_CAP: | 
 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); | 
 | 		update_cap(resource); | 
 | 		break; | 
 | 	case METER_NOTIFY_INTERVAL: | 
 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); | 
 | 		update_avg_interval(resource); | 
 | 		break; | 
 | 	case METER_NOTIFY_CAPPING: | 
 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); | 
 | 		dev_info(&device->dev, "Capping in progress.\n"); | 
 | 		break; | 
 | 	default: | 
 | 		BUG(); | 
 | 	} | 
 | 	mutex_unlock(&resource->lock); | 
 |  | 
 | 	acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, | 
 | 					dev_name(&device->dev), event, 0); | 
 | } | 
 |  | 
 | 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; | 
 |  | 
 | 	free_capabilities(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); | 
 |  | 
 | 	free_capabilities(resource); | 
 | 	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); | 
 | 	free_capabilities(resource); | 
 | 	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 enable_cap_knobs(const struct dmi_system_id *d) | 
 | { | 
 | 	cap_in_hardware = 1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct dmi_system_id __initdata pm_dmi_table[] = { | 
 | 	{ | 
 | 		enable_cap_knobs, "IBM Active Energy Manager", | 
 | 		{ | 
 | 			DMI_MATCH(DMI_SYS_VENDOR, "IBM") | 
 | 		}, | 
 | 	}, | 
 | 	{} | 
 | }; | 
 |  | 
 | static int __init acpi_power_meter_init(void) | 
 | { | 
 | 	int result; | 
 |  | 
 | 	if (acpi_disabled) | 
 | 		return -ENODEV; | 
 |  | 
 | 	dmi_check_system(pm_dmi_table); | 
 |  | 
 | 	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_param(force_cap_on, bool, 0644); | 
 | MODULE_PARM_DESC(force_cap_on, "Enable power cap even it is unsafe to do so."); | 
 |  | 
 | module_init(acpi_power_meter_init); | 
 | module_exit(acpi_power_meter_exit); |