| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * A hwmon driver for ACPI 4.0 power meters | 
 | 3 |  * Copyright (C) 2009 IBM | 
 | 4 |  * | 
 | 5 |  * Author: Darrick J. Wong <djwong@us.ibm.com> | 
 | 6 |  * | 
 | 7 |  * This program is free software; you can redistribute it and/or modify | 
 | 8 |  * it under the terms of the GNU General Public License as published by | 
 | 9 |  * the Free Software Foundation; either version 2 of the License, or | 
 | 10 |  * (at your option) any later version. | 
 | 11 |  * | 
 | 12 |  * This program is distributed in the hope that it will be useful, | 
 | 13 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 14 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 15 |  * GNU General Public License for more details. | 
 | 16 |  * | 
 | 17 |  * You should have received a copy of the GNU General Public License | 
 | 18 |  * along with this program; if not, write to the Free Software | 
 | 19 |  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
 | 20 |  */ | 
 | 21 |  | 
 | 22 | #include <linux/module.h> | 
 | 23 | #include <linux/hwmon.h> | 
 | 24 | #include <linux/hwmon-sysfs.h> | 
 | 25 | #include <linux/jiffies.h> | 
 | 26 | #include <linux/mutex.h> | 
 | 27 | #include <linux/dmi.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 28 | #include <linux/slab.h> | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 29 | #include <linux/kdev_t.h> | 
 | 30 | #include <linux/sched.h> | 
 | 31 | #include <linux/time.h> | 
| Jean Delvare | fa84574 | 2012-10-10 15:25:56 +0200 | [diff] [blame] | 32 | #include <linux/err.h> | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 33 | #include <acpi/acpi_drivers.h> | 
 | 34 | #include <acpi/acpi_bus.h> | 
 | 35 |  | 
 | 36 | #define ACPI_POWER_METER_NAME		"power_meter" | 
 | 37 | ACPI_MODULE_NAME(ACPI_POWER_METER_NAME); | 
 | 38 | #define ACPI_POWER_METER_DEVICE_NAME	"Power Meter" | 
| Dan Carpenter | 1826271 | 2010-04-27 14:01:07 -0700 | [diff] [blame] | 39 | #define ACPI_POWER_METER_CLASS		"pwr_meter_resource" | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 40 |  | 
 | 41 | #define NUM_SENSORS			17 | 
 | 42 |  | 
 | 43 | #define POWER_METER_CAN_MEASURE	(1 << 0) | 
 | 44 | #define POWER_METER_CAN_TRIP	(1 << 1) | 
 | 45 | #define POWER_METER_CAN_CAP	(1 << 2) | 
 | 46 | #define POWER_METER_CAN_NOTIFY	(1 << 3) | 
 | 47 | #define POWER_METER_IS_BATTERY	(1 << 8) | 
 | 48 | #define UNKNOWN_HYSTERESIS	0xFFFFFFFF | 
 | 49 |  | 
 | 50 | #define METER_NOTIFY_CONFIG	0x80 | 
 | 51 | #define METER_NOTIFY_TRIP	0x81 | 
 | 52 | #define METER_NOTIFY_CAP	0x82 | 
 | 53 | #define METER_NOTIFY_CAPPING	0x83 | 
 | 54 | #define METER_NOTIFY_INTERVAL	0x84 | 
 | 55 |  | 
 | 56 | #define POWER_AVERAGE_NAME	"power1_average" | 
 | 57 | #define POWER_CAP_NAME		"power1_cap" | 
 | 58 | #define POWER_AVG_INTERVAL_NAME	"power1_average_interval" | 
 | 59 | #define POWER_ALARM_NAME	"power1_alarm" | 
 | 60 |  | 
 | 61 | static int cap_in_hardware; | 
| Rusty Russell | 90ab5ee | 2012-01-13 09:32:20 +1030 | [diff] [blame] | 62 | static bool force_cap_on; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 63 |  | 
 | 64 | static int can_cap_in_hardware(void) | 
 | 65 | { | 
 | 66 | 	return force_cap_on || cap_in_hardware; | 
 | 67 | } | 
 | 68 |  | 
| Márton Németh | c97adf9 | 2010-01-10 17:15:36 +0100 | [diff] [blame] | 69 | static const struct acpi_device_id power_meter_ids[] = { | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 70 | 	{"ACPI000D", 0}, | 
 | 71 | 	{"", 0}, | 
 | 72 | }; | 
 | 73 | MODULE_DEVICE_TABLE(acpi, power_meter_ids); | 
 | 74 |  | 
 | 75 | struct acpi_power_meter_capabilities { | 
| Lin Ming | 439913f | 2010-01-28 10:53:19 +0800 | [diff] [blame] | 76 | 	u64		flags; | 
 | 77 | 	u64		units; | 
 | 78 | 	u64		type; | 
 | 79 | 	u64		accuracy; | 
 | 80 | 	u64		sampling_time; | 
 | 81 | 	u64		min_avg_interval; | 
 | 82 | 	u64		max_avg_interval; | 
 | 83 | 	u64		hysteresis; | 
 | 84 | 	u64		configurable_cap; | 
 | 85 | 	u64		min_cap; | 
 | 86 | 	u64		max_cap; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 87 | }; | 
 | 88 |  | 
 | 89 | struct acpi_power_meter_resource { | 
 | 90 | 	struct acpi_device	*acpi_dev; | 
 | 91 | 	acpi_bus_id		name; | 
 | 92 | 	struct mutex		lock; | 
 | 93 | 	struct device		*hwmon_dev; | 
 | 94 | 	struct acpi_power_meter_capabilities	caps; | 
 | 95 | 	acpi_string		model_number; | 
 | 96 | 	acpi_string		serial_number; | 
 | 97 | 	acpi_string		oem_info; | 
| Lin Ming | 439913f | 2010-01-28 10:53:19 +0800 | [diff] [blame] | 98 | 	u64		power; | 
 | 99 | 	u64		cap; | 
 | 100 | 	u64		avg_interval; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 101 | 	int			sensors_valid; | 
 | 102 | 	unsigned long		sensors_last_updated; | 
 | 103 | 	struct sensor_device_attribute	sensors[NUM_SENSORS]; | 
 | 104 | 	int			num_sensors; | 
| Guenter Roeck | 75311be | 2012-06-21 06:21:05 -0700 | [diff] [blame] | 105 | 	s64			trip[2]; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 106 | 	int			num_domain_devices; | 
 | 107 | 	struct acpi_device	**domain_devices; | 
 | 108 | 	struct kobject		*holders_dir; | 
 | 109 | }; | 
 | 110 |  | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 111 | struct sensor_template { | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 112 | 	char *label; | 
 | 113 | 	ssize_t (*show)(struct device *dev, | 
 | 114 | 			struct device_attribute *devattr, | 
 | 115 | 			char *buf); | 
 | 116 | 	ssize_t (*set)(struct device *dev, | 
 | 117 | 		       struct device_attribute *devattr, | 
 | 118 | 		       const char *buf, size_t count); | 
 | 119 | 	int index; | 
 | 120 | }; | 
 | 121 |  | 
 | 122 | /* Averaging interval */ | 
 | 123 | static int update_avg_interval(struct acpi_power_meter_resource *resource) | 
 | 124 | { | 
 | 125 | 	unsigned long long data; | 
 | 126 | 	acpi_status status; | 
 | 127 |  | 
 | 128 | 	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI", | 
 | 129 | 				       NULL, &data); | 
 | 130 | 	if (ACPI_FAILURE(status)) { | 
 | 131 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI")); | 
 | 132 | 		return -ENODEV; | 
 | 133 | 	} | 
 | 134 |  | 
 | 135 | 	resource->avg_interval = data; | 
 | 136 | 	return 0; | 
 | 137 | } | 
 | 138 |  | 
 | 139 | static ssize_t show_avg_interval(struct device *dev, | 
 | 140 | 				 struct device_attribute *devattr, | 
 | 141 | 				 char *buf) | 
 | 142 | { | 
 | 143 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 144 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 145 |  | 
 | 146 | 	mutex_lock(&resource->lock); | 
 | 147 | 	update_avg_interval(resource); | 
 | 148 | 	mutex_unlock(&resource->lock); | 
 | 149 |  | 
 | 150 | 	return sprintf(buf, "%llu\n", resource->avg_interval); | 
 | 151 | } | 
 | 152 |  | 
 | 153 | static ssize_t set_avg_interval(struct device *dev, | 
 | 154 | 				struct device_attribute *devattr, | 
 | 155 | 				const char *buf, size_t count) | 
 | 156 | { | 
 | 157 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 158 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 159 | 	union acpi_object arg0 = { ACPI_TYPE_INTEGER }; | 
 | 160 | 	struct acpi_object_list args = { 1, &arg0 }; | 
 | 161 | 	int res; | 
 | 162 | 	unsigned long temp; | 
 | 163 | 	unsigned long long data; | 
 | 164 | 	acpi_status status; | 
 | 165 |  | 
| Frans Meulenbroeks | 179c4fd | 2012-01-04 20:58:52 +0100 | [diff] [blame] | 166 | 	res = kstrtoul(buf, 10, &temp); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 167 | 	if (res) | 
 | 168 | 		return res; | 
 | 169 |  | 
 | 170 | 	if (temp > resource->caps.max_avg_interval || | 
 | 171 | 	    temp < resource->caps.min_avg_interval) | 
 | 172 | 		return -EINVAL; | 
 | 173 | 	arg0.integer.value = temp; | 
 | 174 |  | 
 | 175 | 	mutex_lock(&resource->lock); | 
 | 176 | 	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI", | 
 | 177 | 				       &args, &data); | 
 | 178 | 	if (!ACPI_FAILURE(status)) | 
 | 179 | 		resource->avg_interval = temp; | 
 | 180 | 	mutex_unlock(&resource->lock); | 
 | 181 |  | 
 | 182 | 	if (ACPI_FAILURE(status)) { | 
 | 183 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI")); | 
 | 184 | 		return -EINVAL; | 
 | 185 | 	} | 
 | 186 |  | 
 | 187 | 	/* _PAI returns 0 on success, nonzero otherwise */ | 
 | 188 | 	if (data) | 
 | 189 | 		return -EINVAL; | 
 | 190 |  | 
 | 191 | 	return count; | 
 | 192 | } | 
 | 193 |  | 
 | 194 | /* Cap functions */ | 
 | 195 | static int update_cap(struct acpi_power_meter_resource *resource) | 
 | 196 | { | 
 | 197 | 	unsigned long long data; | 
 | 198 | 	acpi_status status; | 
 | 199 |  | 
 | 200 | 	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL", | 
 | 201 | 				       NULL, &data); | 
 | 202 | 	if (ACPI_FAILURE(status)) { | 
 | 203 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL")); | 
 | 204 | 		return -ENODEV; | 
 | 205 | 	} | 
 | 206 |  | 
 | 207 | 	resource->cap = data; | 
 | 208 | 	return 0; | 
 | 209 | } | 
 | 210 |  | 
 | 211 | static ssize_t show_cap(struct device *dev, | 
 | 212 | 			struct device_attribute *devattr, | 
 | 213 | 			char *buf) | 
 | 214 | { | 
 | 215 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 216 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 217 |  | 
 | 218 | 	mutex_lock(&resource->lock); | 
 | 219 | 	update_cap(resource); | 
 | 220 | 	mutex_unlock(&resource->lock); | 
 | 221 |  | 
 | 222 | 	return sprintf(buf, "%llu\n", resource->cap * 1000); | 
 | 223 | } | 
 | 224 |  | 
 | 225 | static ssize_t set_cap(struct device *dev, struct device_attribute *devattr, | 
 | 226 | 		       const char *buf, size_t count) | 
 | 227 | { | 
 | 228 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 229 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 230 | 	union acpi_object arg0 = { ACPI_TYPE_INTEGER }; | 
 | 231 | 	struct acpi_object_list args = { 1, &arg0 }; | 
 | 232 | 	int res; | 
 | 233 | 	unsigned long temp; | 
 | 234 | 	unsigned long long data; | 
 | 235 | 	acpi_status status; | 
 | 236 |  | 
| Frans Meulenbroeks | 179c4fd | 2012-01-04 20:58:52 +0100 | [diff] [blame] | 237 | 	res = kstrtoul(buf, 10, &temp); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 238 | 	if (res) | 
 | 239 | 		return res; | 
 | 240 |  | 
| Guenter Roeck | 27c4db3 | 2012-06-18 22:37:13 -0700 | [diff] [blame] | 241 | 	temp = DIV_ROUND_CLOSEST(temp, 1000); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 242 | 	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap) | 
 | 243 | 		return -EINVAL; | 
 | 244 | 	arg0.integer.value = temp; | 
 | 245 |  | 
 | 246 | 	mutex_lock(&resource->lock); | 
 | 247 | 	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL", | 
 | 248 | 				       &args, &data); | 
 | 249 | 	if (!ACPI_FAILURE(status)) | 
 | 250 | 		resource->cap = temp; | 
 | 251 | 	mutex_unlock(&resource->lock); | 
 | 252 |  | 
 | 253 | 	if (ACPI_FAILURE(status)) { | 
 | 254 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL")); | 
 | 255 | 		return -EINVAL; | 
 | 256 | 	} | 
 | 257 |  | 
 | 258 | 	/* _SHL returns 0 on success, nonzero otherwise */ | 
 | 259 | 	if (data) | 
 | 260 | 		return -EINVAL; | 
 | 261 |  | 
 | 262 | 	return count; | 
 | 263 | } | 
 | 264 |  | 
 | 265 | /* Power meter trip points */ | 
 | 266 | static int set_acpi_trip(struct acpi_power_meter_resource *resource) | 
 | 267 | { | 
 | 268 | 	union acpi_object arg_objs[] = { | 
 | 269 | 		{ACPI_TYPE_INTEGER}, | 
 | 270 | 		{ACPI_TYPE_INTEGER} | 
 | 271 | 	}; | 
 | 272 | 	struct acpi_object_list args = { 2, arg_objs }; | 
 | 273 | 	unsigned long long data; | 
 | 274 | 	acpi_status status; | 
 | 275 |  | 
 | 276 | 	/* Both trip levels must be set */ | 
 | 277 | 	if (resource->trip[0] < 0 || resource->trip[1] < 0) | 
 | 278 | 		return 0; | 
 | 279 |  | 
 | 280 | 	/* This driver stores min, max; ACPI wants max, min. */ | 
 | 281 | 	arg_objs[0].integer.value = resource->trip[1]; | 
 | 282 | 	arg_objs[1].integer.value = resource->trip[0]; | 
 | 283 |  | 
 | 284 | 	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP", | 
 | 285 | 				       &args, &data); | 
 | 286 | 	if (ACPI_FAILURE(status)) { | 
 | 287 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP")); | 
 | 288 | 		return -EINVAL; | 
 | 289 | 	} | 
 | 290 |  | 
| Darrick J. Wong | 22aeceb | 2009-10-21 18:01:37 -0700 | [diff] [blame] | 291 | 	/* _PTP returns 0 on success, nonzero otherwise */ | 
 | 292 | 	if (data) | 
 | 293 | 		return -EINVAL; | 
 | 294 |  | 
 | 295 | 	return 0; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 296 | } | 
 | 297 |  | 
 | 298 | static ssize_t set_trip(struct device *dev, struct device_attribute *devattr, | 
 | 299 | 			const char *buf, size_t count) | 
 | 300 | { | 
 | 301 | 	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | 
 | 302 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 303 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 304 | 	int res; | 
 | 305 | 	unsigned long temp; | 
 | 306 |  | 
| Frans Meulenbroeks | 179c4fd | 2012-01-04 20:58:52 +0100 | [diff] [blame] | 307 | 	res = kstrtoul(buf, 10, &temp); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 308 | 	if (res) | 
 | 309 | 		return res; | 
 | 310 |  | 
| Guenter Roeck | 27c4db3 | 2012-06-18 22:37:13 -0700 | [diff] [blame] | 311 | 	temp = DIV_ROUND_CLOSEST(temp, 1000); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 312 |  | 
 | 313 | 	mutex_lock(&resource->lock); | 
 | 314 | 	resource->trip[attr->index - 7] = temp; | 
 | 315 | 	res = set_acpi_trip(resource); | 
 | 316 | 	mutex_unlock(&resource->lock); | 
 | 317 |  | 
 | 318 | 	if (res) | 
 | 319 | 		return res; | 
 | 320 |  | 
 | 321 | 	return count; | 
 | 322 | } | 
 | 323 |  | 
 | 324 | /* Power meter */ | 
 | 325 | static int update_meter(struct acpi_power_meter_resource *resource) | 
 | 326 | { | 
 | 327 | 	unsigned long long data; | 
 | 328 | 	acpi_status status; | 
 | 329 | 	unsigned long local_jiffies = jiffies; | 
 | 330 |  | 
 | 331 | 	if (time_before(local_jiffies, resource->sensors_last_updated + | 
 | 332 | 			msecs_to_jiffies(resource->caps.sampling_time)) && | 
 | 333 | 			resource->sensors_valid) | 
 | 334 | 		return 0; | 
 | 335 |  | 
 | 336 | 	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM", | 
 | 337 | 				       NULL, &data); | 
 | 338 | 	if (ACPI_FAILURE(status)) { | 
 | 339 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM")); | 
 | 340 | 		return -ENODEV; | 
 | 341 | 	} | 
 | 342 |  | 
 | 343 | 	resource->power = data; | 
 | 344 | 	resource->sensors_valid = 1; | 
 | 345 | 	resource->sensors_last_updated = jiffies; | 
 | 346 | 	return 0; | 
 | 347 | } | 
 | 348 |  | 
 | 349 | static ssize_t show_power(struct device *dev, | 
 | 350 | 			  struct device_attribute *devattr, | 
 | 351 | 			  char *buf) | 
 | 352 | { | 
 | 353 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 354 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 355 |  | 
 | 356 | 	mutex_lock(&resource->lock); | 
 | 357 | 	update_meter(resource); | 
 | 358 | 	mutex_unlock(&resource->lock); | 
 | 359 |  | 
 | 360 | 	return sprintf(buf, "%llu\n", resource->power * 1000); | 
 | 361 | } | 
 | 362 |  | 
 | 363 | /* Miscellaneous */ | 
 | 364 | static ssize_t show_str(struct device *dev, | 
 | 365 | 			struct device_attribute *devattr, | 
 | 366 | 			char *buf) | 
 | 367 | { | 
 | 368 | 	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | 
 | 369 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 370 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 371 | 	acpi_string val; | 
 | 372 |  | 
 | 373 | 	switch (attr->index) { | 
 | 374 | 	case 0: | 
 | 375 | 		val = resource->model_number; | 
 | 376 | 		break; | 
 | 377 | 	case 1: | 
 | 378 | 		val = resource->serial_number; | 
 | 379 | 		break; | 
 | 380 | 	case 2: | 
 | 381 | 		val = resource->oem_info; | 
 | 382 | 		break; | 
 | 383 | 	default: | 
 | 384 | 		BUG(); | 
| Guenter Roeck | 776cdc1 | 2012-03-28 09:03:26 -0700 | [diff] [blame] | 385 | 		val = ""; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 386 | 	} | 
 | 387 |  | 
 | 388 | 	return sprintf(buf, "%s\n", val); | 
 | 389 | } | 
 | 390 |  | 
 | 391 | static ssize_t show_val(struct device *dev, | 
 | 392 | 			struct device_attribute *devattr, | 
 | 393 | 			char *buf) | 
 | 394 | { | 
 | 395 | 	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | 
 | 396 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 397 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
| Lin Ming | 439913f | 2010-01-28 10:53:19 +0800 | [diff] [blame] | 398 | 	u64 val = 0; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 399 |  | 
 | 400 | 	switch (attr->index) { | 
 | 401 | 	case 0: | 
 | 402 | 		val = resource->caps.min_avg_interval; | 
 | 403 | 		break; | 
 | 404 | 	case 1: | 
 | 405 | 		val = resource->caps.max_avg_interval; | 
 | 406 | 		break; | 
 | 407 | 	case 2: | 
 | 408 | 		val = resource->caps.min_cap * 1000; | 
 | 409 | 		break; | 
 | 410 | 	case 3: | 
 | 411 | 		val = resource->caps.max_cap * 1000; | 
 | 412 | 		break; | 
 | 413 | 	case 4: | 
 | 414 | 		if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS) | 
 | 415 | 			return sprintf(buf, "unknown\n"); | 
 | 416 |  | 
 | 417 | 		val = resource->caps.hysteresis * 1000; | 
 | 418 | 		break; | 
 | 419 | 	case 5: | 
 | 420 | 		if (resource->caps.flags & POWER_METER_IS_BATTERY) | 
 | 421 | 			val = 1; | 
 | 422 | 		else | 
 | 423 | 			val = 0; | 
 | 424 | 		break; | 
 | 425 | 	case 6: | 
 | 426 | 		if (resource->power > resource->cap) | 
 | 427 | 			val = 1; | 
 | 428 | 		else | 
 | 429 | 			val = 0; | 
 | 430 | 		break; | 
 | 431 | 	case 7: | 
 | 432 | 	case 8: | 
 | 433 | 		if (resource->trip[attr->index - 7] < 0) | 
 | 434 | 			return sprintf(buf, "unknown\n"); | 
 | 435 |  | 
 | 436 | 		val = resource->trip[attr->index - 7] * 1000; | 
 | 437 | 		break; | 
 | 438 | 	default: | 
 | 439 | 		BUG(); | 
 | 440 | 	} | 
 | 441 |  | 
 | 442 | 	return sprintf(buf, "%llu\n", val); | 
 | 443 | } | 
 | 444 |  | 
 | 445 | static ssize_t show_accuracy(struct device *dev, | 
 | 446 | 			     struct device_attribute *devattr, | 
 | 447 | 			     char *buf) | 
 | 448 | { | 
 | 449 | 	struct acpi_device *acpi_dev = to_acpi_device(dev); | 
 | 450 | 	struct acpi_power_meter_resource *resource = acpi_dev->driver_data; | 
 | 451 | 	unsigned int acc = resource->caps.accuracy; | 
 | 452 |  | 
 | 453 | 	return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000); | 
 | 454 | } | 
 | 455 |  | 
 | 456 | static ssize_t show_name(struct device *dev, | 
 | 457 | 			 struct device_attribute *devattr, | 
 | 458 | 			 char *buf) | 
 | 459 | { | 
 | 460 | 	return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME); | 
 | 461 | } | 
 | 462 |  | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 463 | #define RO_SENSOR_TEMPLATE(_label, _show, _index)	\ | 
 | 464 | 	{						\ | 
 | 465 | 		.label = _label,			\ | 
 | 466 | 		.show  = _show,				\ | 
 | 467 | 		.index = _index,			\ | 
 | 468 | 	} | 
 | 469 |  | 
 | 470 | #define RW_SENSOR_TEMPLATE(_label, _show, _set, _index)	\ | 
 | 471 | 	{						\ | 
 | 472 | 		.label = _label,			\ | 
 | 473 | 		.show  = _show,				\ | 
 | 474 | 		.set   = _set,				\ | 
 | 475 | 		.index = _index,			\ | 
 | 476 | 	} | 
 | 477 |  | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 478 | /* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */ | 
| Kyle McMartin | 9fe789f | 2012-04-02 14:19:03 -0400 | [diff] [blame] | 479 | static struct sensor_template meter_attrs[] = { | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 480 | 	RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0), | 
 | 481 | 	RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0), | 
 | 482 | 	RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0), | 
 | 483 | 	RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), | 
 | 484 | 	RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 485 | 	RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, | 
 | 486 | 		set_avg_interval, 0), | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 487 | 	{}, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 488 | }; | 
 | 489 |  | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 490 | static struct sensor_template misc_cap_attrs[] = { | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 491 | 	RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2), | 
 | 492 | 	RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3), | 
 | 493 | 	RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4), | 
 | 494 | 	RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6), | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 495 | 	{}, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 496 | }; | 
 | 497 |  | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 498 | static struct sensor_template ro_cap_attrs[] = { | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 499 | 	RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0), | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 500 | 	{}, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 501 | }; | 
 | 502 |  | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 503 | static struct sensor_template rw_cap_attrs[] = { | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 504 | 	RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0), | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 505 | 	{}, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 506 | }; | 
 | 507 |  | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 508 | static struct sensor_template trip_attrs[] = { | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 509 | 	RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7), | 
 | 510 | 	RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8), | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 511 | 	{}, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 512 | }; | 
 | 513 |  | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 514 | static struct sensor_template misc_attrs[] = { | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 515 | 	RO_SENSOR_TEMPLATE("name", show_name, 0), | 
 | 516 | 	RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0), | 
 | 517 | 	RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2), | 
 | 518 | 	RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1), | 
| Kyle McMartin | 81194cd | 2012-04-02 14:19:00 -0400 | [diff] [blame] | 519 | 	{}, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 520 | }; | 
 | 521 |  | 
| Kyle McMartin | 3c45f2c | 2012-04-02 14:19:01 -0400 | [diff] [blame] | 522 | #undef RO_SENSOR_TEMPLATE | 
 | 523 | #undef RW_SENSOR_TEMPLATE | 
 | 524 |  | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 525 | /* Read power domain data */ | 
 | 526 | static void remove_domain_devices(struct acpi_power_meter_resource *resource) | 
 | 527 | { | 
 | 528 | 	int i; | 
 | 529 |  | 
 | 530 | 	if (!resource->num_domain_devices) | 
 | 531 | 		return; | 
 | 532 |  | 
 | 533 | 	for (i = 0; i < resource->num_domain_devices; i++) { | 
 | 534 | 		struct acpi_device *obj = resource->domain_devices[i]; | 
 | 535 | 		if (!obj) | 
 | 536 | 			continue; | 
 | 537 |  | 
 | 538 | 		sysfs_remove_link(resource->holders_dir, | 
 | 539 | 				  kobject_name(&obj->dev.kobj)); | 
 | 540 | 		put_device(&obj->dev); | 
 | 541 | 	} | 
 | 542 |  | 
 | 543 | 	kfree(resource->domain_devices); | 
 | 544 | 	kobject_put(resource->holders_dir); | 
| Darren Jenkins | 7f07a60 | 2010-01-12 23:37:07 +1100 | [diff] [blame] | 545 | 	resource->num_domain_devices = 0; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 546 | } | 
 | 547 |  | 
 | 548 | static int read_domain_devices(struct acpi_power_meter_resource *resource) | 
 | 549 | { | 
 | 550 | 	int res = 0; | 
 | 551 | 	int i; | 
 | 552 | 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | 
 | 553 | 	union acpi_object *pss; | 
 | 554 | 	acpi_status status; | 
 | 555 |  | 
 | 556 | 	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL, | 
 | 557 | 				      &buffer); | 
 | 558 | 	if (ACPI_FAILURE(status)) { | 
 | 559 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD")); | 
 | 560 | 		return -ENODEV; | 
 | 561 | 	} | 
 | 562 |  | 
 | 563 | 	pss = buffer.pointer; | 
 | 564 | 	if (!pss || | 
 | 565 | 	    pss->type != ACPI_TYPE_PACKAGE) { | 
 | 566 | 		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME | 
 | 567 | 			"Invalid _PMD data\n"); | 
 | 568 | 		res = -EFAULT; | 
 | 569 | 		goto end; | 
 | 570 | 	} | 
 | 571 |  | 
 | 572 | 	if (!pss->package.count) | 
 | 573 | 		goto end; | 
 | 574 |  | 
 | 575 | 	resource->domain_devices = kzalloc(sizeof(struct acpi_device *) * | 
 | 576 | 					   pss->package.count, GFP_KERNEL); | 
 | 577 | 	if (!resource->domain_devices) { | 
 | 578 | 		res = -ENOMEM; | 
 | 579 | 		goto end; | 
 | 580 | 	} | 
 | 581 |  | 
 | 582 | 	resource->holders_dir = kobject_create_and_add("measures", | 
 | 583 | 					&resource->acpi_dev->dev.kobj); | 
 | 584 | 	if (!resource->holders_dir) { | 
 | 585 | 		res = -ENOMEM; | 
 | 586 | 		goto exit_free; | 
 | 587 | 	} | 
 | 588 |  | 
 | 589 | 	resource->num_domain_devices = pss->package.count; | 
 | 590 |  | 
 | 591 | 	for (i = 0; i < pss->package.count; i++) { | 
 | 592 | 		struct acpi_device *obj; | 
 | 593 | 		union acpi_object *element = &(pss->package.elements[i]); | 
 | 594 |  | 
 | 595 | 		/* Refuse non-references */ | 
 | 596 | 		if (element->type != ACPI_TYPE_LOCAL_REFERENCE) | 
 | 597 | 			continue; | 
 | 598 |  | 
 | 599 | 		/* Create a symlink to domain objects */ | 
 | 600 | 		resource->domain_devices[i] = NULL; | 
 | 601 | 		status = acpi_bus_get_device(element->reference.handle, | 
 | 602 | 					     &resource->domain_devices[i]); | 
 | 603 | 		if (ACPI_FAILURE(status)) | 
 | 604 | 			continue; | 
 | 605 |  | 
 | 606 | 		obj = resource->domain_devices[i]; | 
 | 607 | 		get_device(&obj->dev); | 
 | 608 |  | 
 | 609 | 		res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, | 
 | 610 | 				      kobject_name(&obj->dev.kobj)); | 
 | 611 | 		if (res) { | 
 | 612 | 			put_device(&obj->dev); | 
 | 613 | 			resource->domain_devices[i] = NULL; | 
 | 614 | 		} | 
 | 615 | 	} | 
 | 616 |  | 
 | 617 | 	res = 0; | 
 | 618 | 	goto end; | 
 | 619 |  | 
 | 620 | exit_free: | 
 | 621 | 	kfree(resource->domain_devices); | 
 | 622 | end: | 
 | 623 | 	kfree(buffer.pointer); | 
 | 624 | 	return res; | 
 | 625 | } | 
 | 626 |  | 
 | 627 | /* Registration and deregistration */ | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 628 | static int register_attrs(struct acpi_power_meter_resource *resource, | 
 | 629 | 			  struct sensor_template *attrs) | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 630 | { | 
 | 631 | 	struct device *dev = &resource->acpi_dev->dev; | 
 | 632 | 	struct sensor_device_attribute *sensors = | 
 | 633 | 		&resource->sensors[resource->num_sensors]; | 
 | 634 | 	int res = 0; | 
 | 635 |  | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 636 | 	while (attrs->label) { | 
 | 637 | 		sensors->dev_attr.attr.name = attrs->label; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 638 | 		sensors->dev_attr.attr.mode = S_IRUGO; | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 639 | 		sensors->dev_attr.show = attrs->show; | 
 | 640 | 		sensors->index = attrs->index; | 
 | 641 |  | 
 | 642 | 		if (attrs->set) { | 
 | 643 | 			sensors->dev_attr.attr.mode |= S_IWUSR; | 
 | 644 | 			sensors->dev_attr.store = attrs->set; | 
 | 645 | 		} | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 646 |  | 
| Kyle McMartin | 31e354e | 2012-03-28 15:11:47 -0400 | [diff] [blame] | 647 | 		sysfs_attr_init(&sensors->dev_attr.attr); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 648 | 		res = device_create_file(dev, &sensors->dev_attr); | 
 | 649 | 		if (res) { | 
 | 650 | 			sensors->dev_attr.attr.name = NULL; | 
 | 651 | 			goto error; | 
 | 652 | 		} | 
 | 653 | 		sensors++; | 
 | 654 | 		resource->num_sensors++; | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 655 | 		attrs++; | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 656 | 	} | 
 | 657 |  | 
 | 658 | error: | 
 | 659 | 	return res; | 
 | 660 | } | 
 | 661 |  | 
 | 662 | static void remove_attrs(struct acpi_power_meter_resource *resource) | 
 | 663 | { | 
 | 664 | 	int i; | 
 | 665 |  | 
 | 666 | 	for (i = 0; i < resource->num_sensors; i++) { | 
 | 667 | 		if (!resource->sensors[i].dev_attr.attr.name) | 
 | 668 | 			continue; | 
 | 669 | 		device_remove_file(&resource->acpi_dev->dev, | 
 | 670 | 				   &resource->sensors[i].dev_attr); | 
 | 671 | 	} | 
 | 672 |  | 
 | 673 | 	remove_domain_devices(resource); | 
 | 674 |  | 
 | 675 | 	resource->num_sensors = 0; | 
 | 676 | } | 
 | 677 |  | 
 | 678 | static int setup_attrs(struct acpi_power_meter_resource *resource) | 
 | 679 | { | 
 | 680 | 	int res = 0; | 
 | 681 |  | 
 | 682 | 	res = read_domain_devices(resource); | 
 | 683 | 	if (res) | 
 | 684 | 		return res; | 
 | 685 |  | 
 | 686 | 	if (resource->caps.flags & POWER_METER_CAN_MEASURE) { | 
| Kyle McMartin | 9fe789f | 2012-04-02 14:19:03 -0400 | [diff] [blame] | 687 | 		res = register_attrs(resource, meter_attrs); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 688 | 		if (res) | 
 | 689 | 			goto error; | 
 | 690 | 	} | 
 | 691 |  | 
 | 692 | 	if (resource->caps.flags & POWER_METER_CAN_CAP) { | 
 | 693 | 		if (!can_cap_in_hardware()) { | 
 | 694 | 			dev_err(&resource->acpi_dev->dev, | 
 | 695 | 				"Ignoring unsafe software power cap!\n"); | 
 | 696 | 			goto skip_unsafe_cap; | 
 | 697 | 		} | 
 | 698 |  | 
| Kyle McMartin | 7bb5ee0 | 2012-04-02 14:19:04 -0400 | [diff] [blame] | 699 | 		if (resource->caps.configurable_cap) | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 700 | 			res = register_attrs(resource, rw_cap_attrs); | 
| Kyle McMartin | 7bb5ee0 | 2012-04-02 14:19:04 -0400 | [diff] [blame] | 701 | 		else | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 702 | 			res = register_attrs(resource, ro_cap_attrs); | 
| Kyle McMartin | 7bb5ee0 | 2012-04-02 14:19:04 -0400 | [diff] [blame] | 703 |  | 
 | 704 | 		if (res) | 
 | 705 | 			goto error; | 
 | 706 |  | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 707 | 		res = register_attrs(resource, misc_cap_attrs); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 708 | 		if (res) | 
 | 709 | 			goto error; | 
 | 710 | 	} | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 711 |  | 
| Kyle McMartin | 7bb5ee0 | 2012-04-02 14:19:04 -0400 | [diff] [blame] | 712 | skip_unsafe_cap: | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 713 | 	if (resource->caps.flags & POWER_METER_CAN_TRIP) { | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 714 | 		res = register_attrs(resource, trip_attrs); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 715 | 		if (res) | 
 | 716 | 			goto error; | 
 | 717 | 	} | 
 | 718 |  | 
| Kyle McMartin | f49d6a7 | 2012-04-02 14:19:02 -0400 | [diff] [blame] | 719 | 	res = register_attrs(resource, misc_attrs); | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 720 | 	if (res) | 
 | 721 | 		goto error; | 
 | 722 |  | 
 | 723 | 	return res; | 
 | 724 | error: | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 725 | 	remove_attrs(resource); | 
 | 726 | 	return res; | 
 | 727 | } | 
 | 728 |  | 
 | 729 | static void free_capabilities(struct acpi_power_meter_resource *resource) | 
 | 730 | { | 
 | 731 | 	acpi_string *str; | 
 | 732 | 	int i; | 
 | 733 |  | 
 | 734 | 	str = &resource->model_number; | 
 | 735 | 	for (i = 0; i < 3; i++, str++) | 
 | 736 | 		kfree(*str); | 
 | 737 | } | 
 | 738 |  | 
 | 739 | static int read_capabilities(struct acpi_power_meter_resource *resource) | 
 | 740 | { | 
 | 741 | 	int res = 0; | 
 | 742 | 	int i; | 
 | 743 | 	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | 
 | 744 | 	struct acpi_buffer state = { 0, NULL }; | 
 | 745 | 	struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" }; | 
 | 746 | 	union acpi_object *pss; | 
 | 747 | 	acpi_string *str; | 
 | 748 | 	acpi_status status; | 
 | 749 |  | 
 | 750 | 	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL, | 
 | 751 | 				      &buffer); | 
 | 752 | 	if (ACPI_FAILURE(status)) { | 
 | 753 | 		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC")); | 
 | 754 | 		return -ENODEV; | 
 | 755 | 	} | 
 | 756 |  | 
 | 757 | 	pss = buffer.pointer; | 
 | 758 | 	if (!pss || | 
 | 759 | 	    pss->type != ACPI_TYPE_PACKAGE || | 
 | 760 | 	    pss->package.count != 14) { | 
 | 761 | 		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME | 
 | 762 | 			"Invalid _PMC data\n"); | 
 | 763 | 		res = -EFAULT; | 
 | 764 | 		goto end; | 
 | 765 | 	} | 
 | 766 |  | 
 | 767 | 	/* Grab all the integer data at once */ | 
 | 768 | 	state.length = sizeof(struct acpi_power_meter_capabilities); | 
 | 769 | 	state.pointer = &resource->caps; | 
 | 770 |  | 
 | 771 | 	status = acpi_extract_package(pss, &format, &state); | 
 | 772 | 	if (ACPI_FAILURE(status)) { | 
 | 773 | 		ACPI_EXCEPTION((AE_INFO, status, "Invalid data")); | 
 | 774 | 		res = -EFAULT; | 
 | 775 | 		goto end; | 
 | 776 | 	} | 
 | 777 |  | 
 | 778 | 	if (resource->caps.units) { | 
 | 779 | 		dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME | 
 | 780 | 			"Unknown units %llu.\n", | 
 | 781 | 			resource->caps.units); | 
 | 782 | 		res = -EINVAL; | 
 | 783 | 		goto end; | 
 | 784 | 	} | 
 | 785 |  | 
 | 786 | 	/* Grab the string data */ | 
 | 787 | 	str = &resource->model_number; | 
 | 788 |  | 
 | 789 | 	for (i = 11; i < 14; i++) { | 
 | 790 | 		union acpi_object *element = &(pss->package.elements[i]); | 
 | 791 |  | 
 | 792 | 		if (element->type != ACPI_TYPE_STRING) { | 
 | 793 | 			res = -EINVAL; | 
 | 794 | 			goto error; | 
 | 795 | 		} | 
 | 796 |  | 
 | 797 | 		*str = kzalloc(sizeof(u8) * (element->string.length + 1), | 
 | 798 | 			       GFP_KERNEL); | 
 | 799 | 		if (!*str) { | 
 | 800 | 			res = -ENOMEM; | 
 | 801 | 			goto error; | 
 | 802 | 		} | 
 | 803 |  | 
 | 804 | 		strncpy(*str, element->string.pointer, element->string.length); | 
 | 805 | 		str++; | 
 | 806 | 	} | 
 | 807 |  | 
 | 808 | 	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n"); | 
 | 809 | 	goto end; | 
 | 810 | error: | 
 | 811 | 	str = &resource->model_number; | 
 | 812 | 	for (i = 0; i < 3; i++, str++) | 
 | 813 | 		kfree(*str); | 
 | 814 | end: | 
 | 815 | 	kfree(buffer.pointer); | 
 | 816 | 	return res; | 
 | 817 | } | 
 | 818 |  | 
 | 819 | /* Handle ACPI event notifications */ | 
 | 820 | static void acpi_power_meter_notify(struct acpi_device *device, u32 event) | 
 | 821 | { | 
 | 822 | 	struct acpi_power_meter_resource *resource; | 
 | 823 | 	int res; | 
 | 824 |  | 
 | 825 | 	if (!device || !acpi_driver_data(device)) | 
 | 826 | 		return; | 
 | 827 |  | 
 | 828 | 	resource = acpi_driver_data(device); | 
 | 829 |  | 
 | 830 | 	mutex_lock(&resource->lock); | 
 | 831 | 	switch (event) { | 
 | 832 | 	case METER_NOTIFY_CONFIG: | 
 | 833 | 		free_capabilities(resource); | 
 | 834 | 		res = read_capabilities(resource); | 
 | 835 | 		if (res) | 
 | 836 | 			break; | 
 | 837 |  | 
 | 838 | 		remove_attrs(resource); | 
 | 839 | 		setup_attrs(resource); | 
 | 840 | 		break; | 
 | 841 | 	case METER_NOTIFY_TRIP: | 
 | 842 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); | 
 | 843 | 		update_meter(resource); | 
 | 844 | 		break; | 
 | 845 | 	case METER_NOTIFY_CAP: | 
 | 846 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); | 
 | 847 | 		update_cap(resource); | 
 | 848 | 		break; | 
 | 849 | 	case METER_NOTIFY_INTERVAL: | 
 | 850 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); | 
 | 851 | 		update_avg_interval(resource); | 
 | 852 | 		break; | 
 | 853 | 	case METER_NOTIFY_CAPPING: | 
 | 854 | 		sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); | 
 | 855 | 		dev_info(&device->dev, "Capping in progress.\n"); | 
 | 856 | 		break; | 
 | 857 | 	default: | 
 | 858 | 		BUG(); | 
 | 859 | 	} | 
 | 860 | 	mutex_unlock(&resource->lock); | 
 | 861 |  | 
 | 862 | 	acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, | 
 | 863 | 					dev_name(&device->dev), event, 0); | 
 | 864 | } | 
 | 865 |  | 
 | 866 | static int acpi_power_meter_add(struct acpi_device *device) | 
 | 867 | { | 
 | 868 | 	int res; | 
 | 869 | 	struct acpi_power_meter_resource *resource; | 
 | 870 |  | 
 | 871 | 	if (!device) | 
 | 872 | 		return -EINVAL; | 
 | 873 |  | 
 | 874 | 	resource = kzalloc(sizeof(struct acpi_power_meter_resource), | 
 | 875 | 			   GFP_KERNEL); | 
 | 876 | 	if (!resource) | 
 | 877 | 		return -ENOMEM; | 
 | 878 |  | 
 | 879 | 	resource->sensors_valid = 0; | 
 | 880 | 	resource->acpi_dev = device; | 
 | 881 | 	mutex_init(&resource->lock); | 
 | 882 | 	strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); | 
 | 883 | 	strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); | 
 | 884 | 	device->driver_data = resource; | 
 | 885 |  | 
 | 886 | 	free_capabilities(resource); | 
 | 887 | 	res = read_capabilities(resource); | 
 | 888 | 	if (res) | 
 | 889 | 		goto exit_free; | 
 | 890 |  | 
 | 891 | 	resource->trip[0] = resource->trip[1] = -1; | 
 | 892 |  | 
 | 893 | 	res = setup_attrs(resource); | 
 | 894 | 	if (res) | 
 | 895 | 		goto exit_free; | 
 | 896 |  | 
 | 897 | 	resource->hwmon_dev = hwmon_device_register(&device->dev); | 
 | 898 | 	if (IS_ERR(resource->hwmon_dev)) { | 
 | 899 | 		res = PTR_ERR(resource->hwmon_dev); | 
 | 900 | 		goto exit_remove; | 
 | 901 | 	} | 
 | 902 |  | 
 | 903 | 	res = 0; | 
 | 904 | 	goto exit; | 
 | 905 |  | 
 | 906 | exit_remove: | 
 | 907 | 	remove_attrs(resource); | 
 | 908 | exit_free: | 
 | 909 | 	kfree(resource); | 
 | 910 | exit: | 
 | 911 | 	return res; | 
 | 912 | } | 
 | 913 |  | 
| Rafael J. Wysocki | 51fac83 | 2013-01-24 00:24:48 +0100 | [diff] [blame] | 914 | static int acpi_power_meter_remove(struct acpi_device *device) | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 915 | { | 
 | 916 | 	struct acpi_power_meter_resource *resource; | 
 | 917 |  | 
 | 918 | 	if (!device || !acpi_driver_data(device)) | 
 | 919 | 		return -EINVAL; | 
 | 920 |  | 
 | 921 | 	resource = acpi_driver_data(device); | 
 | 922 | 	hwmon_device_unregister(resource->hwmon_dev); | 
 | 923 |  | 
 | 924 | 	free_capabilities(resource); | 
 | 925 | 	remove_attrs(resource); | 
 | 926 |  | 
 | 927 | 	kfree(resource); | 
 | 928 | 	return 0; | 
 | 929 | } | 
 | 930 |  | 
| Guenter Roeck | 9baeb8f | 2012-07-27 07:11:32 -0700 | [diff] [blame] | 931 | #ifdef CONFIG_PM_SLEEP | 
 | 932 |  | 
| Rafael J. Wysocki | c5dec01 | 2012-06-29 23:40:05 +0200 | [diff] [blame] | 933 | static int acpi_power_meter_resume(struct device *dev) | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 934 | { | 
 | 935 | 	struct acpi_power_meter_resource *resource; | 
 | 936 |  | 
| Rafael J. Wysocki | c5dec01 | 2012-06-29 23:40:05 +0200 | [diff] [blame] | 937 | 	if (!dev) | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 938 | 		return -EINVAL; | 
 | 939 |  | 
| Rafael J. Wysocki | c5dec01 | 2012-06-29 23:40:05 +0200 | [diff] [blame] | 940 | 	resource = acpi_driver_data(to_acpi_device(dev)); | 
 | 941 | 	if (!resource) | 
 | 942 | 		return -EINVAL; | 
 | 943 |  | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 944 | 	free_capabilities(resource); | 
 | 945 | 	read_capabilities(resource); | 
 | 946 |  | 
 | 947 | 	return 0; | 
 | 948 | } | 
 | 949 |  | 
| Guenter Roeck | 9baeb8f | 2012-07-27 07:11:32 -0700 | [diff] [blame] | 950 | #endif /* CONFIG_PM_SLEEP */ | 
 | 951 |  | 
| Rafael J. Wysocki | c5dec01 | 2012-06-29 23:40:05 +0200 | [diff] [blame] | 952 | static SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, acpi_power_meter_resume); | 
 | 953 |  | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 954 | static struct acpi_driver acpi_power_meter_driver = { | 
 | 955 | 	.name = "power_meter", | 
 | 956 | 	.class = ACPI_POWER_METER_CLASS, | 
 | 957 | 	.ids = power_meter_ids, | 
 | 958 | 	.ops = { | 
 | 959 | 		.add = acpi_power_meter_add, | 
 | 960 | 		.remove = acpi_power_meter_remove, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 961 | 		.notify = acpi_power_meter_notify, | 
 | 962 | 		}, | 
| Rafael J. Wysocki | c5dec01 | 2012-06-29 23:40:05 +0200 | [diff] [blame] | 963 | 	.drv.pm = &acpi_power_meter_pm, | 
| Darrick J. Wong | de584af | 2009-09-18 12:41:09 -0700 | [diff] [blame] | 964 | }; | 
 | 965 |  | 
 | 966 | /* Module init/exit routines */ | 
 | 967 | static int __init enable_cap_knobs(const struct dmi_system_id *d) | 
 | 968 | { | 
 | 969 | 	cap_in_hardware = 1; | 
 | 970 | 	return 0; | 
 | 971 | } | 
 | 972 |  | 
 | 973 | static struct dmi_system_id __initdata pm_dmi_table[] = { | 
 | 974 | 	{ | 
 | 975 | 		enable_cap_knobs, "IBM Active Energy Manager", | 
 | 976 | 		{ | 
 | 977 | 			DMI_MATCH(DMI_SYS_VENDOR, "IBM") | 
 | 978 | 		}, | 
 | 979 | 	}, | 
 | 980 | 	{} | 
 | 981 | }; | 
 | 982 |  | 
 | 983 | static int __init acpi_power_meter_init(void) | 
 | 984 | { | 
 | 985 | 	int result; | 
 | 986 |  | 
 | 987 | 	if (acpi_disabled) | 
 | 988 | 		return -ENODEV; | 
 | 989 |  | 
 | 990 | 	dmi_check_system(pm_dmi_table); | 
 | 991 |  | 
 | 992 | 	result = acpi_bus_register_driver(&acpi_power_meter_driver); | 
 | 993 | 	if (result < 0) | 
 | 994 | 		return -ENODEV; | 
 | 995 |  | 
 | 996 | 	return 0; | 
 | 997 | } | 
 | 998 |  | 
 | 999 | static void __exit acpi_power_meter_exit(void) | 
 | 1000 | { | 
 | 1001 | 	acpi_bus_unregister_driver(&acpi_power_meter_driver); | 
 | 1002 | } | 
 | 1003 |  | 
 | 1004 | MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>"); | 
 | 1005 | MODULE_DESCRIPTION("ACPI 4.0 power meter driver"); | 
 | 1006 | MODULE_LICENSE("GPL"); | 
 | 1007 |  | 
 | 1008 | module_param(force_cap_on, bool, 0644); | 
 | 1009 | MODULE_PARM_DESC(force_cap_on, "Enable power cap even it is unsafe to do so."); | 
 | 1010 |  | 
 | 1011 | module_init(acpi_power_meter_init); | 
 | 1012 | module_exit(acpi_power_meter_exit); |