2007-05-04 00:27:45 +04:00
/*
* Universal power supply monitor class
*
* Copyright © 2007 Anton Vorontsov < cbou @ mail . ru >
* Copyright © 2004 Szabolcs Gyurko
* Copyright © 2003 Ian Molton < spyro @ f2s . com >
*
* Modified : 2004 , Oct Szabolcs Gyurko
*
* You may use this code as per GPL version 2
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/init.h>
2010-05-18 23:49:51 +04:00
# include <linux/slab.h>
2007-05-04 00:27:45 +04:00
# include <linux/device.h>
# include <linux/err.h>
# include <linux/power_supply.h>
2012-05-09 19:06:47 +04:00
# include <linux/thermal.h>
2007-05-04 00:27:45 +04:00
# include "power_supply.h"
2009-07-30 17:42:31 +04:00
/* exported for the APM Power driver, APM emulation */
2007-05-04 00:27:45 +04:00
struct class * power_supply_class ;
2009-07-30 17:42:31 +04:00
EXPORT_SYMBOL_GPL ( power_supply_class ) ;
2007-05-04 00:27:45 +04:00
2010-05-18 23:49:51 +04:00
static struct device_type power_supply_dev_type ;
2008-01-22 08:58:22 +03:00
static int __power_supply_changed_work ( struct device * dev , void * data )
{
struct power_supply * psy = ( struct power_supply * ) data ;
struct power_supply * pst = dev_get_drvdata ( dev ) ;
int i ;
for ( i = 0 ; i < psy - > num_supplicants ; i + + )
if ( ! strcmp ( psy - > supplied_to [ i ] , pst - > name ) ) {
if ( pst - > external_power_changed )
pst - > external_power_changed ( pst ) ;
}
return 0 ;
}
2007-05-04 00:27:45 +04:00
static void power_supply_changed_work ( struct work_struct * work )
{
struct power_supply * psy = container_of ( work , struct power_supply ,
changed_work ) ;
2008-04-29 11:58:29 +04:00
dev_dbg ( psy - > dev , " %s \n " , __func__ ) ;
2007-05-04 00:27:45 +04:00
2008-05-23 01:21:08 +04:00
class_for_each_device ( power_supply_class , NULL , psy ,
2008-01-22 08:58:22 +03:00
__power_supply_changed_work ) ;
2007-05-04 00:27:45 +04:00
power_supply_update_leds ( psy ) ;
kobject_uevent ( & psy - > dev - > kobj , KOBJ_CHANGE ) ;
}
void power_supply_changed ( struct power_supply * psy )
{
2008-04-29 11:58:29 +04:00
dev_dbg ( psy - > dev , " %s \n " , __func__ ) ;
2007-05-04 00:27:45 +04:00
schedule_work ( & psy - > changed_work ) ;
}
2009-07-30 17:42:31 +04:00
EXPORT_SYMBOL_GPL ( power_supply_changed ) ;
2007-05-04 00:27:45 +04:00
2008-01-22 08:58:22 +03:00
static int __power_supply_am_i_supplied ( struct device * dev , void * data )
2007-05-04 00:27:45 +04:00
{
union power_supply_propval ret = { 0 , } ;
2008-01-22 08:58:22 +03:00
struct power_supply * psy = ( struct power_supply * ) data ;
struct power_supply * epsy = dev_get_drvdata ( dev ) ;
int i ;
for ( i = 0 ; i < epsy - > num_supplicants ; i + + ) {
if ( ! strcmp ( epsy - > supplied_to [ i ] , psy - > name ) ) {
if ( epsy - > get_property ( epsy ,
POWER_SUPPLY_PROP_ONLINE , & ret ) )
continue ;
if ( ret . intval )
return ret . intval ;
2007-05-04 00:27:45 +04:00
}
}
2008-01-22 08:58:22 +03:00
return 0 ;
}
int power_supply_am_i_supplied ( struct power_supply * psy )
{
int error ;
2008-05-23 01:21:08 +04:00
error = class_for_each_device ( power_supply_class , NULL , psy ,
2008-01-22 08:58:22 +03:00
__power_supply_am_i_supplied ) ;
2007-05-04 00:27:45 +04:00
2008-04-29 11:58:29 +04:00
dev_dbg ( psy - > dev , " %s %d \n " , __func__ , error ) ;
2007-05-04 00:27:45 +04:00
2008-01-22 08:58:22 +03:00
return error ;
2007-05-04 00:27:45 +04:00
}
2009-07-30 17:42:31 +04:00
EXPORT_SYMBOL_GPL ( power_supply_am_i_supplied ) ;
2007-05-04 00:27:45 +04:00
2008-08-27 00:09:59 +04:00
static int __power_supply_is_system_supplied ( struct device * dev , void * data )
{
union power_supply_propval ret = { 0 , } ;
struct power_supply * psy = dev_get_drvdata ( dev ) ;
2011-12-11 01:53:36 +04:00
unsigned int * count = data ;
2008-08-27 00:09:59 +04:00
2011-12-11 01:53:36 +04:00
( * count ) + + ;
2008-08-27 00:09:59 +04:00
if ( psy - > type ! = POWER_SUPPLY_TYPE_BATTERY ) {
if ( psy - > get_property ( psy , POWER_SUPPLY_PROP_ONLINE , & ret ) )
return 0 ;
if ( ret . intval )
return ret . intval ;
}
return 0 ;
}
int power_supply_is_system_supplied ( void )
{
int error ;
2011-12-11 01:53:36 +04:00
unsigned int count = 0 ;
2008-08-27 00:09:59 +04:00
2011-12-11 01:53:36 +04:00
error = class_for_each_device ( power_supply_class , NULL , & count ,
2008-08-27 00:09:59 +04:00
__power_supply_is_system_supplied ) ;
2011-12-11 01:53:36 +04:00
/*
* If no power class device was found at all , most probably we are
* running on a desktop system , so assume we are on mains power .
*/
if ( count = = 0 )
return 1 ;
2008-08-27 00:09:59 +04:00
return error ;
}
2009-07-30 17:42:31 +04:00
EXPORT_SYMBOL_GPL ( power_supply_is_system_supplied ) ;
2008-08-27 00:09:59 +04:00
2009-07-23 22:35:53 +04:00
int power_supply_set_battery_charged ( struct power_supply * psy )
{
if ( psy - > type = = POWER_SUPPLY_TYPE_BATTERY & & psy - > set_charged ) {
psy - > set_charged ( psy ) ;
return 0 ;
}
return - EINVAL ;
}
EXPORT_SYMBOL_GPL ( power_supply_set_battery_charged ) ;
static int power_supply_match_device_by_name ( struct device * dev , void * data )
{
const char * name = data ;
struct power_supply * psy = dev_get_drvdata ( dev ) ;
return strcmp ( psy - > name , name ) = = 0 ;
}
struct power_supply * power_supply_get_by_name ( char * name )
{
struct device * dev = class_find_device ( power_supply_class , NULL , name ,
power_supply_match_device_by_name ) ;
return dev ? dev_get_drvdata ( dev ) : NULL ;
}
EXPORT_SYMBOL_GPL ( power_supply_get_by_name ) ;
2011-12-07 21:15:45 +04:00
int power_supply_powers ( struct power_supply * psy , struct device * dev )
{
2012-01-05 19:17:25 +04:00
return sysfs_create_link ( & psy - > dev - > kobj , & dev - > kobj , " powers " ) ;
2011-12-07 21:15:45 +04:00
}
EXPORT_SYMBOL_GPL ( power_supply_powers ) ;
2010-05-18 23:49:51 +04:00
static void power_supply_dev_release ( struct device * dev )
{
pr_debug ( " device: '%s': %s \n " , dev_name ( dev ) , __func__ ) ;
kfree ( dev ) ;
}
2012-05-09 19:06:47 +04:00
# ifdef CONFIG_THERMAL
static int power_supply_read_temp ( struct thermal_zone_device * tzd ,
unsigned long * temp )
{
struct power_supply * psy ;
union power_supply_propval val ;
int ret ;
WARN_ON ( tzd = = NULL ) ;
psy = tzd - > devdata ;
ret = psy - > get_property ( psy , POWER_SUPPLY_PROP_TEMP , & val ) ;
/* Convert tenths of degree Celsius to milli degree Celsius. */
if ( ! ret )
* temp = val . intval * 100 ;
return ret ;
}
static struct thermal_zone_device_ops psy_tzd_ops = {
. get_temp = power_supply_read_temp ,
} ;
static int psy_register_thermal ( struct power_supply * psy )
{
int i ;
/* Register battery zone device psy reports temperature */
for ( i = 0 ; i < psy - > num_properties ; i + + ) {
if ( psy - > properties [ i ] = = POWER_SUPPLY_PROP_TEMP ) {
2012-07-31 15:59:42 +04:00
psy - > tzd = thermal_zone_device_register ( psy - > name , 0 , 0 ,
2012-09-18 09:34:56 +04:00
psy , & psy_tzd_ops , NULL , 0 , 0 ) ;
2012-05-09 19:06:47 +04:00
if ( IS_ERR ( psy - > tzd ) )
return PTR_ERR ( psy - > tzd ) ;
break ;
}
}
return 0 ;
}
static void psy_unregister_thermal ( struct power_supply * psy )
{
if ( IS_ERR_OR_NULL ( psy - > tzd ) )
return ;
thermal_zone_device_unregister ( psy - > tzd ) ;
}
2012-10-09 20:55:59 +04:00
/* thermal cooling device callbacks */
static int ps_get_max_charge_cntl_limit ( struct thermal_cooling_device * tcd ,
unsigned long * state )
{
struct power_supply * psy ;
union power_supply_propval val ;
int ret ;
psy = tcd - > devdata ;
ret = psy - > get_property ( psy ,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX , & val ) ;
if ( ! ret )
* state = val . intval ;
return ret ;
}
static int ps_get_cur_chrage_cntl_limit ( struct thermal_cooling_device * tcd ,
unsigned long * state )
{
struct power_supply * psy ;
union power_supply_propval val ;
int ret ;
psy = tcd - > devdata ;
ret = psy - > get_property ( psy ,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT , & val ) ;
if ( ! ret )
* state = val . intval ;
return ret ;
}
static int ps_set_cur_charge_cntl_limit ( struct thermal_cooling_device * tcd ,
unsigned long state )
{
struct power_supply * psy ;
union power_supply_propval val ;
int ret ;
psy = tcd - > devdata ;
val . intval = state ;
ret = psy - > set_property ( psy ,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT , & val ) ;
return ret ;
}
static struct thermal_cooling_device_ops psy_tcd_ops = {
. get_max_state = ps_get_max_charge_cntl_limit ,
. get_cur_state = ps_get_cur_chrage_cntl_limit ,
. set_cur_state = ps_set_cur_charge_cntl_limit ,
} ;
static int psy_register_cooler ( struct power_supply * psy )
{
int i ;
/* Register for cooling device if psy can control charging */
for ( i = 0 ; i < psy - > num_properties ; i + + ) {
if ( psy - > properties [ i ] = =
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT ) {
psy - > tcd = thermal_cooling_device_register (
( char * ) psy - > name ,
psy , & psy_tcd_ops ) ;
if ( IS_ERR ( psy - > tcd ) )
return PTR_ERR ( psy - > tcd ) ;
break ;
}
}
return 0 ;
}
static void psy_unregister_cooler ( struct power_supply * psy )
{
if ( IS_ERR_OR_NULL ( psy - > tcd ) )
return ;
thermal_cooling_device_unregister ( psy - > tcd ) ;
}
2012-05-09 19:06:47 +04:00
# else
static int psy_register_thermal ( struct power_supply * psy )
{
return 0 ;
}
static void psy_unregister_thermal ( struct power_supply * psy )
{
}
2012-10-09 20:55:59 +04:00
static int psy_register_cooler ( struct power_supply * psy )
{
return 0 ;
}
static void psy_unregister_cooler ( struct power_supply * psy )
{
}
2012-05-09 19:06:47 +04:00
# endif
2007-05-04 00:27:45 +04:00
int power_supply_register ( struct device * parent , struct power_supply * psy )
{
2010-05-18 23:49:51 +04:00
struct device * dev ;
int rc ;
2007-05-04 00:27:45 +04:00
2010-05-18 23:49:51 +04:00
dev = kzalloc ( sizeof ( * dev ) , GFP_KERNEL ) ;
if ( ! dev )
return - ENOMEM ;
2007-05-04 00:27:45 +04:00
2010-05-18 23:49:51 +04:00
device_initialize ( dev ) ;
2007-05-04 00:27:45 +04:00
2010-05-18 23:49:51 +04:00
dev - > class = power_supply_class ;
dev - > type = & power_supply_dev_type ;
dev - > parent = parent ;
dev - > release = power_supply_dev_release ;
dev_set_drvdata ( dev , psy ) ;
psy - > dev = dev ;
2011-02-21 17:34:19 +03:00
INIT_WORK ( & psy - > changed_work , power_supply_changed_work ) ;
2010-05-18 23:49:51 +04:00
rc = kobject_set_name ( & dev - > kobj , " %s " , psy - > name ) ;
if ( rc )
goto kobject_set_name_failed ;
rc = device_add ( dev ) ;
2007-05-04 00:27:45 +04:00
if ( rc )
2010-05-18 23:49:51 +04:00
goto device_add_failed ;
2012-05-09 19:06:47 +04:00
rc = psy_register_thermal ( psy ) ;
if ( rc )
goto register_thermal_failed ;
2012-10-09 20:55:59 +04:00
rc = psy_register_cooler ( psy ) ;
if ( rc )
goto register_cooler_failed ;
2007-05-04 00:27:45 +04:00
rc = power_supply_create_triggers ( psy ) ;
if ( rc )
goto create_triggers_failed ;
power_supply_changed ( psy ) ;
goto success ;
create_triggers_failed :
2012-10-09 20:55:59 +04:00
psy_unregister_cooler ( psy ) ;
register_cooler_failed :
2012-05-09 19:06:47 +04:00
psy_unregister_thermal ( psy ) ;
register_thermal_failed :
2010-11-19 21:41:58 +03:00
device_del ( dev ) ;
2010-05-18 23:49:51 +04:00
kobject_set_name_failed :
device_add_failed :
2010-11-19 21:41:58 +03:00
put_device ( dev ) ;
2007-05-04 00:27:45 +04:00
success :
return rc ;
}
2009-07-30 17:42:31 +04:00
EXPORT_SYMBOL_GPL ( power_supply_register ) ;
2007-05-04 00:27:45 +04:00
void power_supply_unregister ( struct power_supply * psy )
{
2010-12-11 19:51:45 +03:00
cancel_work_sync ( & psy - > changed_work ) ;
2011-12-07 21:15:45 +04:00
sysfs_remove_link ( & psy - > dev - > kobj , " powers " ) ;
2007-05-04 00:27:45 +04:00
power_supply_remove_triggers ( psy ) ;
2012-10-09 20:55:59 +04:00
psy_unregister_cooler ( psy ) ;
2012-05-09 19:06:47 +04:00
psy_unregister_thermal ( psy ) ;
2007-05-04 00:27:45 +04:00
device_unregister ( psy - > dev ) ;
}
2009-07-30 17:42:31 +04:00
EXPORT_SYMBOL_GPL ( power_supply_unregister ) ;
2007-05-04 00:27:45 +04:00
static int __init power_supply_class_init ( void )
{
power_supply_class = class_create ( THIS_MODULE , " power_supply " ) ;
if ( IS_ERR ( power_supply_class ) )
return PTR_ERR ( power_supply_class ) ;
power_supply_class - > dev_uevent = power_supply_uevent ;
2010-05-18 23:49:51 +04:00
power_supply_init_attrs ( & power_supply_dev_type ) ;
2007-05-04 00:27:45 +04:00
return 0 ;
}
static void __exit power_supply_class_exit ( void )
{
class_destroy ( power_supply_class ) ;
}
subsys_initcall ( power_supply_class_init ) ;
module_exit ( power_supply_class_exit ) ;
MODULE_DESCRIPTION ( " Universal power supply monitor class " ) ;
MODULE_AUTHOR ( " Ian Molton <spyro@f2s.com>, "
" Szabolcs Gyurko, "
" Anton Vorontsov <cbou@mail.ru> " ) ;
MODULE_LICENSE ( " GPL " ) ;