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>
2009-01-18 19:40:27 +03:00
# include <linux/err.h>
2007-05-04 00:32:17 +04:00
# include <linux/interrupt.h>
2011-07-11 03:01:15 +04:00
# include <linux/notifier.h>
2007-05-04 00:32:17 +04:00
# include <linux/power_supply.h>
# include <linux/pda_power.h>
2009-01-18 19:40:27 +03:00
# include <linux/regulator/consumer.h>
2007-05-04 00:32:17 +04:00
# include <linux/timer.h>
# include <linux/jiffies.h>
2009-01-18 19:40:27 +03:00
# include <linux/usb/otg.h>
2007-05-04 00:32:17 +04:00
static inline unsigned int get_irq_flags ( struct resource * res )
{
2012-07-17 21:40:18 +04:00
return IRQF_SHARED | ( res - > flags & IRQF_TRIGGER_MASK ) ;
2007-05-04 00:32:17 +04:00
}
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 ;
2008-01-13 02:44:20 +03:00
static struct timer_list polling_timer ;
static int polling ;
2007-05-04 00:32:17 +04:00
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2012-02-13 15:24:02 +04:00
static struct usb_phy * transceiver ;
2011-07-11 03:01:15 +04:00
static struct notifier_block otg_nb ;
2011-08-23 16:34:33 +04:00
# endif
2009-01-18 19:40:27 +03:00
static struct regulator * ac_draw ;
2008-01-13 02:39:17 +03:00
enum {
PDA_PSY_OFFLINE = 0 ,
PDA_PSY_ONLINE = 1 ,
PDA_PSY_TO_CHANGE ,
} ;
static int new_ac_status = - 1 ;
static int new_usb_status = - 1 ;
static int ac_status = - 1 ;
static int usb_status = - 1 ;
2007-05-04 00:32:17 +04:00
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 " ,
} ;
2008-01-13 02:39:17 +03:00
static struct power_supply pda_psy_ac = {
. 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 ,
2007-05-04 00:32:17 +04:00
} ;
2008-01-13 02:39:17 +03:00
static struct power_supply pda_psy_usb = {
. 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_status ( void )
{
if ( pdata - > is_ac_online )
new_ac_status = ! ! pdata - > is_ac_online ( ) ;
if ( pdata - > is_usb_online )
new_usb_status = ! ! pdata - > is_usb_online ( ) ;
}
2007-05-04 00:32:17 +04:00
static void update_charger ( void )
{
2009-01-18 19:40:27 +03:00
static int regulator_enabled ;
int max_uA = pdata - > ac_max_uA ;
if ( pdata - > set_charge ) {
if ( new_ac_status > 0 ) {
dev_dbg ( dev , " charger on (AC) \n " ) ;
pdata - > set_charge ( PDA_POWER_CHARGE_AC ) ;
} else if ( new_usb_status > 0 ) {
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 ) ;
}
} else if ( ac_draw ) {
if ( new_ac_status > 0 ) {
regulator_set_current_limit ( ac_draw , max_uA , max_uA ) ;
if ( ! regulator_enabled ) {
dev_dbg ( dev , " charger on (AC) \n " ) ;
2012-06-12 05:46:26 +04:00
WARN_ON ( regulator_enable ( ac_draw ) ) ;
2009-01-18 19:40:27 +03:00
regulator_enabled = 1 ;
}
} else {
if ( regulator_enabled ) {
dev_dbg ( dev , " charger off \n " ) ;
2012-06-12 05:46:26 +04:00
WARN_ON ( regulator_disable ( ac_draw ) ) ;
2009-01-18 19:40:27 +03:00
regulator_enabled = 0 ;
}
}
2007-05-04 00:32:17 +04:00
}
}
2008-01-13 02:39:17 +03:00
static void supply_timer_func ( unsigned long unused )
2007-05-04 00:32:17 +04:00
{
2008-01-13 02:39:17 +03:00
if ( ac_status = = PDA_PSY_TO_CHANGE ) {
ac_status = new_ac_status ;
power_supply_changed ( & pda_psy_ac ) ;
}
2007-07-15 03:12:04 +04:00
2008-01-13 02:39:17 +03:00
if ( usb_status = = PDA_PSY_TO_CHANGE ) {
usb_status = new_usb_status ;
power_supply_changed ( & pda_psy_usb ) ;
}
2007-05-04 00:32:17 +04:00
}
2008-01-13 02:39:17 +03:00
static void psy_changed ( void )
2007-05-04 00:32:17 +04:00
{
update_charger ( ) ;
2008-01-13 02:39:17 +03:00
/*
* Okay , charger set . Now wait a bit before notifying supplicants ,
* charge power should stabilize .
*/
2007-05-04 00:32:17 +04:00
mod_timer ( & supply_timer ,
jiffies + msecs_to_jiffies ( pdata - > wait_for_charger ) ) ;
}
2008-01-13 02:39:17 +03:00
static void charger_timer_func ( unsigned long unused )
{
update_status ( ) ;
psy_changed ( ) ;
}
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
{
2008-01-13 02:39:17 +03:00
if ( power_supply = = & pda_psy_ac )
ac_status = PDA_PSY_TO_CHANGE ;
else if ( power_supply = = & pda_psy_usb )
usb_status = PDA_PSY_TO_CHANGE ;
else
return IRQ_NONE ;
/*
* Wait a bit before reading ac / usb line status and setting charger ,
* because ac / usb status readings may lag from irq .
*/
2007-05-04 00:32:17 +04:00
mod_timer ( & charger_timer ,
jiffies + msecs_to_jiffies ( pdata - > wait_for_status ) ) ;
2008-01-13 02:39:17 +03:00
2007-05-04 00:32:17 +04:00
return IRQ_HANDLED ;
}
2008-01-13 02:44:20 +03:00
static void polling_timer_func ( unsigned long unused )
{
int changed = 0 ;
dev_dbg ( dev , " polling... \n " ) ;
update_status ( ) ;
if ( ! ac_irq & & new_ac_status ! = ac_status ) {
ac_status = PDA_PSY_TO_CHANGE ;
changed = 1 ;
}
if ( ! usb_irq & & new_usb_status ! = usb_status ) {
usb_status = PDA_PSY_TO_CHANGE ;
changed = 1 ;
}
if ( changed )
psy_changed ( ) ;
mod_timer ( & polling_timer ,
jiffies + msecs_to_jiffies ( pdata - > polling_interval ) ) ;
}
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2009-01-18 19:40:27 +03:00
static int otg_is_usb_online ( void )
{
2011-07-11 03:01:15 +04:00
return ( transceiver - > last_event = = USB_EVENT_VBUS | |
transceiver - > last_event = = USB_EVENT_ENUMERATED ) ;
}
static int otg_is_ac_online ( void )
{
return ( transceiver - > last_event = = USB_EVENT_CHARGER ) ;
}
static int otg_handle_notification ( struct notifier_block * nb ,
unsigned long event , void * unused )
{
switch ( event ) {
case USB_EVENT_CHARGER :
ac_status = PDA_PSY_TO_CHANGE ;
break ;
case USB_EVENT_VBUS :
case USB_EVENT_ENUMERATED :
usb_status = PDA_PSY_TO_CHANGE ;
break ;
case USB_EVENT_NONE :
ac_status = PDA_PSY_TO_CHANGE ;
usb_status = PDA_PSY_TO_CHANGE ;
break ;
default :
return NOTIFY_OK ;
}
/*
* Wait a bit before reading ac / usb line status and setting charger ,
* because ac / usb status readings may lag from irq .
*/
mod_timer ( & charger_timer ,
jiffies + msecs_to_jiffies ( pdata - > wait_for_status ) ) ;
return NOTIFY_OK ;
2009-01-18 19:40:27 +03:00
}
# endif
2007-05-04 00:32:17 +04:00
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 ;
2008-04-12 15:47:45 +04:00
if ( pdata - > init ) {
ret = pdata - > init ( dev ) ;
if ( ret < 0 )
goto init_failed ;
}
2012-09-21 01:26:05 +04:00
ac_draw = regulator_get ( dev , " ac_draw " ) ;
if ( IS_ERR ( ac_draw ) ) {
dev_dbg ( dev , " couldn't get ac_draw regulator \n " ) ;
ac_draw = NULL ;
}
2008-01-13 02:39:17 +03:00
update_status ( ) ;
2007-05-04 00:32:17 +04:00
update_charger ( ) ;
if ( ! pdata - > wait_for_status )
pdata - > wait_for_status = 500 ;
if ( ! pdata - > wait_for_charger )
pdata - > wait_for_charger = 500 ;
2008-01-13 02:44:20 +03:00
if ( ! pdata - > polling_interval )
pdata - > polling_interval = 2000 ;
2009-01-18 19:40:27 +03:00
if ( ! pdata - > ac_max_uA )
pdata - > ac_max_uA = 500000 ;
2007-05-04 00:32:17 +04:00
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 ( pdata - > supplied_to ) {
2008-01-13 02:39:17 +03:00
pda_psy_ac . supplied_to = pdata - > supplied_to ;
pda_psy_ac . num_supplicants = pdata - > num_supplicants ;
pda_psy_usb . supplied_to = pdata - > supplied_to ;
pda_psy_usb . num_supplicants = pdata - > num_supplicants ;
2007-05-04 00:32:17 +04:00
}
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2012-06-22 15:32:46 +04:00
transceiver = usb_get_phy ( USB_PHY_TYPE_USB2 ) ;
2012-06-26 16:10:32 +04:00
if ( ! IS_ERR_OR_NULL ( transceiver ) ) {
if ( ! pdata - > is_usb_online )
pdata - > is_usb_online = otg_is_usb_online ;
if ( ! pdata - > is_ac_online )
pdata - > is_ac_online = otg_is_ac_online ;
2011-07-11 03:01:15 +04:00
}
2011-08-23 16:34:33 +04:00
# endif
2011-07-11 03:01:15 +04:00
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online ) {
2008-01-13 02:39:17 +03:00
ret = power_supply_register ( & pdev - > dev , & pda_psy_ac ) ;
2008-01-07 04:12:39 +03:00
if ( ret ) {
dev_err ( dev , " failed to register %s power supply \n " ,
2008-01-13 02:39:17 +03:00
pda_psy_ac . name ) ;
2008-01-07 04:12:39 +03:00
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 ,
2008-01-13 02:39:17 +03:00
& pda_psy_ac ) ;
2008-01-07 04:12:39 +03:00
if ( ret ) {
dev_err ( dev , " request ac irq failed \n " ) ;
goto ac_irq_failed ;
}
2008-01-13 02:44:20 +03:00
} else {
polling = 1 ;
2008-01-07 04:12:39 +03:00
}
2007-05-04 00:32:17 +04:00
}
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online ) {
2008-01-13 02:39:17 +03:00
ret = power_supply_register ( & pdev - > dev , & pda_psy_usb ) ;
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 " ,
2008-01-13 02:39:17 +03:00
pda_psy_usb . name ) ;
2008-01-07 04:12:39 +03:00
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 ) ,
2008-01-13 02:39:17 +03:00
usb_irq - > name , & pda_psy_usb ) ;
2008-01-07 04:12:39 +03:00
if ( ret ) {
dev_err ( dev , " request usb irq failed \n " ) ;
goto usb_irq_failed ;
}
2008-01-13 02:44:20 +03:00
} else {
polling = 1 ;
2007-05-04 00:32:17 +04:00
}
}
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2012-06-26 16:10:32 +04:00
if ( ! IS_ERR_OR_NULL ( transceiver ) & & pdata - > use_otg_notifier ) {
2011-07-11 03:01:15 +04:00
otg_nb . notifier_call = otg_handle_notification ;
2012-02-13 15:24:16 +04:00
ret = usb_register_notifier ( transceiver , & otg_nb ) ;
2011-07-11 03:01:15 +04:00
if ( ret ) {
dev_err ( dev , " failure to register otg notifier \n " ) ;
goto otg_reg_notifier_failed ;
}
polling = 0 ;
}
2011-08-23 16:34:33 +04:00
# endif
2011-07-11 03:01:15 +04:00
2008-01-13 02:44:20 +03:00
if ( polling ) {
dev_dbg ( dev , " will poll for status \n " ) ;
setup_timer ( & polling_timer , polling_timer_func , 0 ) ;
mod_timer ( & polling_timer ,
jiffies + msecs_to_jiffies ( pdata - > polling_interval ) ) ;
}
if ( ac_irq | | usb_irq )
device_init_wakeup ( & pdev - > dev , 1 ) ;
2008-01-13 02:35:43 +03:00
2008-01-07 04:12:39 +03:00
return 0 ;
2007-05-04 00:32:17 +04:00
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2011-07-11 03:01:15 +04:00
otg_reg_notifier_failed :
if ( pdata - > is_usb_online & & usb_irq )
free_irq ( usb_irq - > start , & pda_psy_usb ) ;
2011-08-23 16:34:33 +04:00
# endif
2007-05-04 00:32:17 +04:00
usb_irq_failed :
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online )
2008-01-13 02:39:17 +03:00
power_supply_unregister ( & pda_psy_usb ) ;
2008-01-07 04:12:39 +03:00
usb_supply_failed :
if ( pdata - > is_ac_online & & ac_irq )
2008-01-13 02:39:17 +03:00
free_irq ( ac_irq - > start , & pda_psy_ac ) ;
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2012-06-26 16:10:32 +04:00
if ( ! IS_ERR_OR_NULL ( transceiver ) )
2012-06-22 15:32:45 +04:00
usb_put_phy ( transceiver ) ;
2011-08-23 16:34:33 +04:00
# endif
2007-05-04 00:32:17 +04:00
ac_irq_failed :
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online )
2008-01-13 02:39:17 +03:00
power_supply_unregister ( & pda_psy_ac ) ;
2008-01-07 04:12:39 +03:00
ac_supply_failed :
2009-01-18 19:40:27 +03:00
if ( ac_draw ) {
regulator_put ( ac_draw ) ;
ac_draw = NULL ;
}
2008-04-12 15:47:45 +04:00
if ( pdata - > exit )
pdata - > exit ( dev ) ;
init_failed :
2007-05-04 00:32:17 +04:00
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 )
2008-01-13 02:39:17 +03:00
free_irq ( usb_irq - > start , & pda_psy_usb ) ;
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online & & ac_irq )
2008-01-13 02:39:17 +03:00
free_irq ( ac_irq - > start , & pda_psy_ac ) ;
2008-01-13 02:44:20 +03:00
if ( polling )
del_timer_sync ( & polling_timer ) ;
2007-05-04 00:32:17 +04:00
del_timer_sync ( & charger_timer ) ;
del_timer_sync ( & supply_timer ) ;
2008-01-13 02:39:17 +03:00
2008-01-07 04:12:39 +03:00
if ( pdata - > is_usb_online )
2008-01-13 02:39:17 +03:00
power_supply_unregister ( & pda_psy_usb ) ;
2008-01-07 04:12:39 +03:00
if ( pdata - > is_ac_online )
2008-01-13 02:39:17 +03:00
power_supply_unregister ( & pda_psy_ac ) ;
2013-03-07 13:23:50 +04:00
# if IS_ENABLED(CONFIG_USB_PHY)
2012-06-26 16:10:32 +04:00
if ( ! IS_ERR_OR_NULL ( transceiver ) )
2012-06-22 15:32:45 +04:00
usb_put_phy ( transceiver ) ;
2009-01-18 19:40:27 +03:00
# endif
if ( ac_draw ) {
regulator_put ( ac_draw ) ;
ac_draw = NULL ;
}
2008-04-12 15:47:45 +04:00
if ( pdata - > exit )
pdata - > exit ( dev ) ;
2008-01-13 02:39:17 +03:00
2007-05-04 00:32:17 +04:00
return 0 ;
}
2008-01-13 02:35:43 +03:00
# ifdef CONFIG_PM
2008-08-12 00:22:27 +04:00
static int ac_wakeup_enabled ;
static int usb_wakeup_enabled ;
2008-01-13 02:35:43 +03:00
static int pda_power_suspend ( struct platform_device * pdev , pm_message_t state )
{
2010-04-13 01:33:22 +04:00
if ( pdata - > suspend ) {
int ret = pdata - > suspend ( state ) ;
if ( ret )
return ret ;
}
2008-01-13 02:35:43 +03:00
if ( device_may_wakeup ( & pdev - > dev ) ) {
if ( ac_irq )
2008-08-12 00:22:27 +04:00
ac_wakeup_enabled = ! enable_irq_wake ( ac_irq - > start ) ;
2008-01-13 02:35:43 +03:00
if ( usb_irq )
2008-08-12 00:22:27 +04:00
usb_wakeup_enabled = ! enable_irq_wake ( usb_irq - > start ) ;
2008-01-13 02:35:43 +03:00
}
return 0 ;
}
static int pda_power_resume ( struct platform_device * pdev )
{
if ( device_may_wakeup ( & pdev - > dev ) ) {
2008-08-12 00:22:27 +04:00
if ( usb_irq & & usb_wakeup_enabled )
2008-01-13 02:35:43 +03:00
disable_irq_wake ( usb_irq - > start ) ;
2008-08-12 00:22:27 +04:00
if ( ac_irq & & ac_wakeup_enabled )
2008-01-13 02:35:43 +03:00
disable_irq_wake ( ac_irq - > start ) ;
}
2010-04-13 01:33:22 +04:00
if ( pdata - > resume )
return pdata - > resume ( ) ;
2008-01-13 02:35:43 +03:00
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
} ;
2011-11-26 08:01:10 +04:00
module_platform_driver ( pda_power_pdrv ) ;
2007-05-04 00:32:17 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Anton Vorontsov <cbou@mail.ru> " ) ;
2011-11-26 08:01:10 +04:00
MODULE_ALIAS ( " platform:pda-power " ) ;