2007-05-04 00:32:17 +04:00
/*
* Common power driver for PDAs and phones with one or two external
* power supplies ( AC / USB ) connected to main and backup batteries ,
* and optional builtin charger .
*
* Copyright © 2007 Anton Vorontsov < cbou @ mail . ru >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/power_supply.h>
# include <linux/pda_power.h>
# include <linux/timer.h>
# include <linux/jiffies.h>
static inline unsigned int get_irq_flags ( struct resource * res )
{
unsigned int flags = IRQF_DISABLED | IRQF_SHARED ;
flags | = res - > flags & IRQF_TRIGGER_MASK ;
return flags ;
}
static struct device * dev ;
static struct pda_power_pdata * pdata ;
static struct resource * ac_irq , * usb_irq ;
static struct timer_list charger_timer ;
static struct timer_list supply_timer ;
static int pda_power_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
switch ( psp ) {
case POWER_SUPPLY_PROP_ONLINE :
if ( psy - > type = = POWER_SUPPLY_TYPE_MAINS )
val - > intval = pdata - > is_ac_online ?
pdata - > is_ac_online ( ) : 0 ;
else
val - > intval = pdata - > is_usb_online ?
pdata - > is_usb_online ( ) : 0 ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static enum power_supply_property pda_power_props [ ] = {
POWER_SUPPLY_PROP_ONLINE ,
} ;
static char * pda_power_supplied_to [ ] = {
" main-battery " ,
" backup-battery " ,
} ;
static struct power_supply pda_power_supplies [ ] = {
{
. name = " ac " ,
. type = POWER_SUPPLY_TYPE_MAINS ,
. supplied_to = pda_power_supplied_to ,
. num_supplicants = ARRAY_SIZE ( pda_power_supplied_to ) ,
. properties = pda_power_props ,
. num_properties = ARRAY_SIZE ( pda_power_props ) ,
. get_property = pda_power_get_property ,
} ,
{
. name = " usb " ,
. type = POWER_SUPPLY_TYPE_USB ,
. supplied_to = pda_power_supplied_to ,
. num_supplicants = ARRAY_SIZE ( pda_power_supplied_to ) ,
. properties = pda_power_props ,
. num_properties = ARRAY_SIZE ( pda_power_props ) ,
. get_property = pda_power_get_property ,
} ,
} ;
static void update_charger ( void )
{
if ( ! pdata - > set_charge )
return ;
if ( pdata - > is_ac_online & & pdata - > is_ac_online ( ) ) {
dev_dbg ( dev , " charger on (AC) \n " ) ;
pdata - > set_charge ( PDA_POWER_CHARGE_AC ) ;
} else if ( pdata - > is_usb_online & & pdata - > is_usb_online ( ) ) {
dev_dbg ( dev , " charger on (USB) \n " ) ;
pdata - > set_charge ( PDA_POWER_CHARGE_USB ) ;
} else {
dev_dbg ( dev , " charger off \n " ) ;
pdata - > set_charge ( 0 ) ;
}
}
2007-07-15 03:12:04 +04:00
static void supply_timer_func ( unsigned long power_supply_ptr )
2007-05-04 00:32:17 +04:00
{
2007-07-15 03:12:04 +04:00
void * power_supply = ( void * ) power_supply_ptr ;
power_supply_changed ( power_supply ) ;
2007-05-04 00:32:17 +04:00
}
2007-07-15 03:12:04 +04:00
static void charger_timer_func ( unsigned long power_supply_ptr )
2007-05-04 00:32:17 +04:00
{
update_charger ( ) ;
/* Okay, charger set. Now wait a bit before notifying supplicants,
* charge power should stabilize . */
2007-07-15 03:12:04 +04:00
supply_timer . data = power_supply_ptr ;
2007-05-04 00:32:17 +04:00
mod_timer ( & supply_timer ,
jiffies + msecs_to_jiffies ( pdata - > wait_for_charger ) ) ;
}
2007-07-15 03:12:04 +04:00
static irqreturn_t power_changed_isr ( int irq , void * power_supply )
2007-05-04 00:32:17 +04:00
{
/* Wait a bit before reading ac/usb line status and setting charger,
* because ac / usb status readings may lag from irq . */
2007-07-15 03:12:04 +04:00
charger_timer . data = ( unsigned long ) power_supply ;
2007-05-04 00:32:17 +04:00
mod_timer ( & charger_timer ,
jiffies + msecs_to_jiffies ( pdata - > wait_for_status ) ) ;
return IRQ_HANDLED ;
}
static int pda_power_probe ( struct platform_device * pdev )
{
int ret = 0 ;
dev = & pdev - > dev ;
if ( pdev - > id ! = - 1 ) {
dev_err ( dev , " it's meaningless to register several "
" pda_powers; use id = -1 \n " ) ;
ret = - EINVAL ;
goto wrongid ;
}
pdata = pdev - > dev . platform_data ;
update_charger ( ) ;
if ( ! pdata - > wait_for_status )
pdata - > wait_for_status = 500 ;
if ( ! pdata - > wait_for_charger )
pdata - > wait_for_charger = 500 ;
setup_timer ( & charger_timer , charger_timer_func , 0 ) ;
setup_timer ( & supply_timer , supply_timer_func , 0 ) ;
ac_irq = platform_get_resource_byname ( pdev , IORESOURCE_IRQ , " ac " ) ;
usb_irq = platform_get_resource_byname ( pdev , IORESOURCE_IRQ , " usb " ) ;
if ( ! ac_irq & & ! usb_irq ) {
dev_err ( dev , " no ac/usb irq specified \n " ) ;
ret = - ENODEV ;
goto noirqs ;
}
if ( pdata - > supplied_to ) {
pda_power_supplies [ 0 ] . supplied_to = pdata - > supplied_to ;
pda_power_supplies [ 1 ] . supplied_to = pdata - > supplied_to ;
pda_power_supplies [ 0 ] . num_supplicants = pdata - > num_supplicants ;
pda_power_supplies [ 1 ] . num_supplicants = pdata - > num_supplicants ;
}
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online ) {
ret = power_supply_register ( & pdev - > dev , & pda_power_supplies [ 0 ] ) ;
if ( ret ) {
dev_err ( dev , " failed to register %s power supply \n " ,
pda_power_supplies [ 0 ] . name ) ;
goto ac_supply_failed ;
}
2007-05-04 00:32:17 +04:00
2008-01-07 04:12:39 +03:00
if ( ac_irq ) {
ret = request_irq ( ac_irq - > start , power_changed_isr ,
get_irq_flags ( ac_irq ) , ac_irq - > name ,
& pda_power_supplies [ 0 ] ) ;
if ( ret ) {
dev_err ( dev , " request ac irq failed \n " ) ;
goto ac_irq_failed ;
}
}
2007-05-04 00:32:17 +04:00
}
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online ) {
ret = power_supply_register ( & pdev - > dev , & pda_power_supplies [ 1 ] ) ;
2007-05-04 00:32:17 +04:00
if ( ret ) {
2008-01-07 04:12:39 +03:00
dev_err ( dev , " failed to register %s power supply \n " ,
pda_power_supplies [ 1 ] . name ) ;
goto usb_supply_failed ;
2007-05-04 00:32:17 +04:00
}
2008-01-07 04:12:39 +03:00
if ( usb_irq ) {
ret = request_irq ( usb_irq - > start , power_changed_isr ,
get_irq_flags ( usb_irq ) ,
usb_irq - > name ,
& pda_power_supplies [ 1 ] ) ;
if ( ret ) {
dev_err ( dev , " request usb irq failed \n " ) ;
goto usb_irq_failed ;
}
2007-05-04 00:32:17 +04:00
}
}
2008-01-13 02:35:43 +03:00
device_init_wakeup ( & pdev - > dev , 1 ) ;
2008-01-07 04:12:39 +03:00
return 0 ;
2007-05-04 00:32:17 +04:00
usb_irq_failed :
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online )
power_supply_unregister ( & pda_power_supplies [ 1 ] ) ;
usb_supply_failed :
if ( pdata - > is_ac_online & & ac_irq )
2007-05-04 00:32:17 +04:00
free_irq ( ac_irq - > start , & pda_power_supplies [ 0 ] ) ;
ac_irq_failed :
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online )
power_supply_unregister ( & pda_power_supplies [ 0 ] ) ;
ac_supply_failed :
2007-05-04 00:32:17 +04:00
noirqs :
wrongid :
return ret ;
}
static int pda_power_remove ( struct platform_device * pdev )
{
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online & & usb_irq )
2007-05-04 00:32:17 +04:00
free_irq ( usb_irq - > start , & pda_power_supplies [ 1 ] ) ;
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online & & ac_irq )
2007-05-04 00:32:17 +04:00
free_irq ( ac_irq - > start , & pda_power_supplies [ 0 ] ) ;
del_timer_sync ( & charger_timer ) ;
del_timer_sync ( & supply_timer ) ;
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online )
power_supply_unregister ( & pda_power_supplies [ 1 ] ) ;
if ( pdata - > is_ac_online )
power_supply_unregister ( & pda_power_supplies [ 0 ] ) ;
2007-05-04 00:32:17 +04:00
return 0 ;
}
2008-01-13 02:35:43 +03:00
# ifdef CONFIG_PM
static int pda_power_suspend ( struct platform_device * pdev , pm_message_t state )
{
if ( device_may_wakeup ( & pdev - > dev ) ) {
if ( ac_irq )
enable_irq_wake ( ac_irq - > start ) ;
if ( usb_irq )
enable_irq_wake ( usb_irq - > start ) ;
}
return 0 ;
}
static int pda_power_resume ( struct platform_device * pdev )
{
if ( device_may_wakeup ( & pdev - > dev ) ) {
if ( usb_irq )
disable_irq_wake ( usb_irq - > start ) ;
if ( ac_irq )
disable_irq_wake ( ac_irq - > start ) ;
}
return 0 ;
}
# else
# define pda_power_suspend NULL
# define pda_power_resume NULL
# endif /* CONFIG_PM */
2007-05-04 00:32:17 +04:00
static struct platform_driver pda_power_pdrv = {
. driver = {
. name = " pda-power " ,
} ,
. probe = pda_power_probe ,
. remove = pda_power_remove ,
2008-01-13 02:35:43 +03:00
. suspend = pda_power_suspend ,
. resume = pda_power_resume ,
2007-05-04 00:32:17 +04:00
} ;
static int __init pda_power_init ( void )
{
return platform_driver_register ( & pda_power_pdrv ) ;
}
static void __exit pda_power_exit ( void )
{
platform_driver_unregister ( & pda_power_pdrv ) ;
}
module_init ( pda_power_init ) ;
module_exit ( pda_power_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Anton Vorontsov <cbou@mail.ru> " ) ;