2009-09-18 23:41:09 +04:00
/*
* 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/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 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 ;
}
2010-01-10 19:15:36 +03:00
static const struct acpi_device_id power_meter_ids [ ] = {
2009-09-18 23:41:09 +04:00
{ " 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_string model_number ;
acpi_string serial_number ;
acpi_string oem_info ;
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 ] ;
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 ;
}
2009-10-22 05:01:37 +04:00
/* _PTP returns 0 on success, nonzero otherwise */
if ( data )
return - EINVAL ;
return 0 ;
2009-09-18 23:41:09 +04:00
}
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 ;
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 [ ] = {
{ 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 ) ;
2010-01-12 15:37:07 +03:00
resource - > num_domain_devices = 0 ;
2009-09-18 23:41:09 +04:00
}
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 ) ;