2009-01-09 01:50:55 +01:00
/* NXP PCF50633 Main Battery Charger Driver
*
* ( C ) 2006 - 2008 by Openmoko , Inc .
* Author : Balaji Rao < balajirrao @ openmoko . org >
* All rights reserved .
*
* Broken down from monstrous PCF50633 driver mainly by
* Harald Welte , Andy Green and Werner Almesberger
*
* 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 .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/device.h>
# include <linux/sysfs.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/mfd/pcf50633/core.h>
# include <linux/mfd/pcf50633/mbc.h>
struct pcf50633_mbc {
struct pcf50633 * pcf ;
int adapter_online ;
int usb_online ;
struct power_supply usb ;
struct power_supply adapter ;
2009-11-05 00:24:54 +03:00
struct power_supply ac ;
2009-01-09 01:50:55 +01:00
} ;
int pcf50633_mbc_usb_curlim_set ( struct pcf50633 * pcf , int ma )
{
struct pcf50633_mbc * mbc = platform_get_drvdata ( pcf - > mbc_pdev ) ;
int ret = 0 ;
u8 bits ;
2009-01-27 19:23:12 +05:30
int charging_start = 1 ;
u8 mbcs2 , chgmod ;
2009-11-05 00:24:55 +03:00
unsigned int mbcc5 ;
2009-01-09 01:50:55 +01:00
2009-11-05 00:24:55 +03:00
if ( ma > = 1000 ) {
2009-01-09 01:50:55 +01:00
bits = PCF50633_MBCC7_USB_1000mA ;
2009-11-05 00:24:55 +03:00
ma = 1000 ;
} else if ( ma > = 500 ) {
2009-01-09 01:50:55 +01:00
bits = PCF50633_MBCC7_USB_500mA ;
2009-11-05 00:24:55 +03:00
ma = 500 ;
} else if ( ma > = 100 ) {
2009-01-09 01:50:55 +01:00
bits = PCF50633_MBCC7_USB_100mA ;
2009-11-05 00:24:55 +03:00
ma = 100 ;
} else {
2009-01-09 01:50:55 +01:00
bits = PCF50633_MBCC7_USB_SUSPEND ;
2009-01-27 19:23:12 +05:30
charging_start = 0 ;
2009-11-05 00:24:55 +03:00
ma = 0 ;
2009-01-27 19:23:12 +05:30
}
2009-01-09 01:50:55 +01:00
ret = pcf50633_reg_set_bit_mask ( pcf , PCF50633_REG_MBCC7 ,
PCF50633_MBCC7_USB_MASK , bits ) ;
if ( ret )
dev_err ( pcf - > dev , " error setting usb curlim to %d mA \n " , ma ) ;
else
dev_info ( pcf - > dev , " usb curlim to %d mA \n " , ma ) ;
2009-11-05 00:24:55 +03:00
/*
* We limit the charging current to be the USB current limit .
* The reason is that on pcf50633 , when it enters PMU Standby mode ,
* which it does when the device goes " off " , the USB current limit
* reverts to the variant default . In at least one common case , that
* default is 500 mA . By setting the charging current to be the same
* as the USB limit we set here before PMU standby , we enforce it only
* using the correct amount of current even when the USB current limit
* gets reset to the wrong thing
*/
if ( mbc - > pcf - > pdata - > charger_reference_current_ma ) {
mbcc5 = ( ma < < 8 ) / mbc - > pcf - > pdata - > charger_reference_current_ma ;
if ( mbcc5 > 255 )
mbcc5 = 255 ;
pcf50633_reg_write ( mbc - > pcf , PCF50633_REG_MBCC5 , mbcc5 ) ;
}
2009-11-05 00:24:59 +03:00
mbcs2 = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCS2 ) ;
2009-01-27 19:23:12 +05:30
chgmod = ( mbcs2 & PCF50633_MBCS2_MBC_MASK ) ;
/* If chgmod == BATFULL, setting chgena has no effect.
2009-11-05 00:24:58 +03:00
* Datasheet says we need to set resume instead but when autoresume is
* used resume doesn ' t work . Clear and set chgena instead .
2009-01-27 19:23:12 +05:30
*/
if ( chgmod ! = PCF50633_MBCS2_MBC_BAT_FULL )
pcf50633_reg_set_bit_mask ( pcf , PCF50633_REG_MBCC1 ,
PCF50633_MBCC1_CHGENA , PCF50633_MBCC1_CHGENA ) ;
2009-11-05 00:24:58 +03:00
else {
pcf50633_reg_clear_bits ( pcf , PCF50633_REG_MBCC1 ,
PCF50633_MBCC1_CHGENA ) ;
2009-01-27 19:23:12 +05:30
pcf50633_reg_set_bit_mask ( pcf , PCF50633_REG_MBCC1 ,
2009-11-05 00:24:58 +03:00
PCF50633_MBCC1_CHGENA , PCF50633_MBCC1_CHGENA ) ;
}
2009-01-27 19:23:12 +05:30
2009-01-09 01:50:55 +01:00
power_supply_changed ( & mbc - > usb ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( pcf50633_mbc_usb_curlim_set ) ;
int pcf50633_mbc_get_status ( struct pcf50633 * pcf )
{
struct pcf50633_mbc * mbc = platform_get_drvdata ( pcf - > mbc_pdev ) ;
int status = 0 ;
2009-11-05 00:24:59 +03:00
u8 chgmod ;
if ( ! mbc )
return 0 ;
chgmod = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCS2 )
& PCF50633_MBCS2_MBC_MASK ;
2009-01-09 01:50:55 +01:00
if ( mbc - > usb_online )
status | = PCF50633_MBC_USB_ONLINE ;
2009-11-05 00:24:59 +03:00
if ( chgmod = = PCF50633_MBCS2_MBC_USB_PRE | |
chgmod = = PCF50633_MBCS2_MBC_USB_PRE_WAIT | |
chgmod = = PCF50633_MBCS2_MBC_USB_FAST | |
chgmod = = PCF50633_MBCS2_MBC_USB_FAST_WAIT )
2009-01-09 01:50:55 +01:00
status | = PCF50633_MBC_USB_ACTIVE ;
if ( mbc - > adapter_online )
status | = PCF50633_MBC_ADAPTER_ONLINE ;
2009-11-05 00:24:59 +03:00
if ( chgmod = = PCF50633_MBCS2_MBC_ADP_PRE | |
chgmod = = PCF50633_MBCS2_MBC_ADP_PRE_WAIT | |
chgmod = = PCF50633_MBCS2_MBC_ADP_FAST | |
chgmod = = PCF50633_MBCS2_MBC_ADP_FAST_WAIT )
2009-01-09 01:50:55 +01:00
status | = PCF50633_MBC_ADAPTER_ACTIVE ;
return status ;
}
EXPORT_SYMBOL_GPL ( pcf50633_mbc_get_status ) ;
2009-11-05 00:24:59 +03:00
int pcf50633_mbc_get_usb_online_status ( struct pcf50633 * pcf )
{
struct pcf50633_mbc * mbc = platform_get_drvdata ( pcf - > mbc_pdev ) ;
if ( ! mbc )
return 0 ;
return mbc - > usb_online ;
}
EXPORT_SYMBOL_GPL ( pcf50633_mbc_get_usb_online_status ) ;
2009-01-09 01:50:55 +01:00
static ssize_t
show_chgmode ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct pcf50633_mbc * mbc = dev_get_drvdata ( dev ) ;
u8 mbcs2 = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCS2 ) ;
u8 chgmod = ( mbcs2 & PCF50633_MBCS2_MBC_MASK ) ;
return sprintf ( buf , " %d \n " , chgmod ) ;
}
static DEVICE_ATTR ( chgmode , S_IRUGO , show_chgmode , NULL ) ;
static ssize_t
show_usblim ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct pcf50633_mbc * mbc = dev_get_drvdata ( dev ) ;
u8 usblim = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCC7 ) &
PCF50633_MBCC7_USB_MASK ;
unsigned int ma ;
if ( usblim = = PCF50633_MBCC7_USB_1000mA )
ma = 1000 ;
else if ( usblim = = PCF50633_MBCC7_USB_500mA )
ma = 500 ;
else if ( usblim = = PCF50633_MBCC7_USB_100mA )
ma = 100 ;
else
ma = 0 ;
return sprintf ( buf , " %u \n " , ma ) ;
}
static ssize_t set_usblim ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct pcf50633_mbc * mbc = dev_get_drvdata ( dev ) ;
unsigned long ma ;
int ret ;
ret = strict_strtoul ( buf , 10 , & ma ) ;
if ( ret )
return - EINVAL ;
pcf50633_mbc_usb_curlim_set ( mbc - > pcf , ma ) ;
return count ;
}
static DEVICE_ATTR ( usb_curlim , S_IRUGO | S_IWUSR , show_usblim , set_usblim ) ;
2009-11-05 00:24:55 +03:00
static ssize_t
show_chglim ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct pcf50633_mbc * mbc = dev_get_drvdata ( dev ) ;
u8 mbcc5 = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCC5 ) ;
unsigned int ma ;
if ( ! mbc - > pcf - > pdata - > charger_reference_current_ma )
return - ENODEV ;
ma = ( mbc - > pcf - > pdata - > charger_reference_current_ma * mbcc5 ) > > 8 ;
return sprintf ( buf , " %u \n " , ma ) ;
}
static ssize_t set_chglim ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
struct pcf50633_mbc * mbc = dev_get_drvdata ( dev ) ;
unsigned long ma ;
unsigned int mbcc5 ;
int ret ;
if ( ! mbc - > pcf - > pdata - > charger_reference_current_ma )
return - ENODEV ;
ret = strict_strtoul ( buf , 10 , & ma ) ;
if ( ret )
return - EINVAL ;
mbcc5 = ( ma < < 8 ) / mbc - > pcf - > pdata - > charger_reference_current_ma ;
if ( mbcc5 > 255 )
mbcc5 = 255 ;
pcf50633_reg_write ( mbc - > pcf , PCF50633_REG_MBCC5 , mbcc5 ) ;
return count ;
}
/*
* This attribute allows to change MBC charging limit on the fly
* independently of usb current limit . It also gets set automatically every
* time usb current limit is changed .
*/
static DEVICE_ATTR ( chg_curlim , S_IRUGO | S_IWUSR , show_chglim , set_chglim ) ;
2009-01-09 01:50:55 +01:00
static struct attribute * pcf50633_mbc_sysfs_entries [ ] = {
& dev_attr_chgmode . attr ,
& dev_attr_usb_curlim . attr ,
2009-11-05 00:24:55 +03:00
& dev_attr_chg_curlim . attr ,
2009-01-09 01:50:55 +01:00
NULL ,
} ;
static struct attribute_group mbc_attr_group = {
. name = NULL , /* put in device directory */
. attrs = pcf50633_mbc_sysfs_entries ,
} ;
static void
pcf50633_mbc_irq_handler ( int irq , void * data )
{
struct pcf50633_mbc * mbc = data ;
/* USB */
if ( irq = = PCF50633_IRQ_USBINS ) {
mbc - > usb_online = 1 ;
} else if ( irq = = PCF50633_IRQ_USBREM ) {
mbc - > usb_online = 0 ;
pcf50633_mbc_usb_curlim_set ( mbc - > pcf , 0 ) ;
}
/* Adapter */
2009-11-05 00:24:59 +03:00
if ( irq = = PCF50633_IRQ_ADPINS )
2009-01-09 01:50:55 +01:00
mbc - > adapter_online = 1 ;
2009-11-05 00:24:59 +03:00
else if ( irq = = PCF50633_IRQ_ADPREM )
2009-01-09 01:50:55 +01:00
mbc - > adapter_online = 0 ;
2009-11-05 00:24:54 +03:00
power_supply_changed ( & mbc - > ac ) ;
2009-01-09 01:50:55 +01:00
power_supply_changed ( & mbc - > usb ) ;
power_supply_changed ( & mbc - > adapter ) ;
if ( mbc - > pcf - > pdata - > mbc_event_callback )
mbc - > pcf - > pdata - > mbc_event_callback ( mbc - > pcf , irq ) ;
}
static int adapter_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
2009-01-27 19:22:38 +05:30
struct pcf50633_mbc * mbc = container_of ( psy ,
struct pcf50633_mbc , adapter ) ;
2009-01-09 01:50:55 +01:00
int ret = 0 ;
switch ( psp ) {
case POWER_SUPPLY_PROP_ONLINE :
val - > intval = mbc - > adapter_online ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
static int usb_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct pcf50633_mbc * mbc = container_of ( psy , struct pcf50633_mbc , usb ) ;
int ret = 0 ;
2009-11-05 00:24:54 +03:00
u8 usblim = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCC7 ) &
PCF50633_MBCC7_USB_MASK ;
2009-01-09 01:50:55 +01:00
switch ( psp ) {
case POWER_SUPPLY_PROP_ONLINE :
2009-11-05 00:24:54 +03:00
val - > intval = mbc - > usb_online & &
( usblim < = PCF50633_MBCC7_USB_500mA ) ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
static int ac_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct pcf50633_mbc * mbc = container_of ( psy , struct pcf50633_mbc , ac ) ;
int ret = 0 ;
u8 usblim = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCC7 ) &
PCF50633_MBCC7_USB_MASK ;
2009-01-09 01:50:55 +01:00
switch ( psp ) {
case POWER_SUPPLY_PROP_ONLINE :
2009-11-05 00:24:54 +03:00
val - > intval = mbc - > usb_online & &
( usblim = = PCF50633_MBCC7_USB_1000mA ) ;
2009-01-09 01:50:55 +01:00
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
static enum power_supply_property power_props [ ] = {
POWER_SUPPLY_PROP_ONLINE ,
} ;
static const u8 mbc_irq_handlers [ ] = {
PCF50633_IRQ_ADPINS ,
PCF50633_IRQ_ADPREM ,
PCF50633_IRQ_USBINS ,
PCF50633_IRQ_USBREM ,
PCF50633_IRQ_BATFULL ,
PCF50633_IRQ_CHGHALT ,
PCF50633_IRQ_THLIMON ,
PCF50633_IRQ_THLIMOFF ,
PCF50633_IRQ_USBLIMON ,
PCF50633_IRQ_USBLIMOFF ,
PCF50633_IRQ_LOWSYS ,
PCF50633_IRQ_LOWBAT ,
} ;
static int __devinit pcf50633_mbc_probe ( struct platform_device * pdev )
{
struct pcf50633_mbc * mbc ;
int ret ;
int i ;
u8 mbcs1 ;
mbc = kzalloc ( sizeof ( * mbc ) , GFP_KERNEL ) ;
if ( ! mbc )
return - ENOMEM ;
platform_set_drvdata ( pdev , mbc ) ;
2009-10-14 02:12:33 +04:00
mbc - > pcf = dev_to_pcf50633 ( pdev - > dev . parent ) ;
2009-01-09 01:50:55 +01:00
/* Set up IRQ handlers */
for ( i = 0 ; i < ARRAY_SIZE ( mbc_irq_handlers ) ; i + + )
pcf50633_register_irq ( mbc - > pcf , mbc_irq_handlers [ i ] ,
pcf50633_mbc_irq_handler , mbc ) ;
/* Create power supplies */
mbc - > adapter . name = " adapter " ;
mbc - > adapter . type = POWER_SUPPLY_TYPE_MAINS ;
mbc - > adapter . properties = power_props ;
mbc - > adapter . num_properties = ARRAY_SIZE ( power_props ) ;
mbc - > adapter . get_property = & adapter_get_property ;
mbc - > adapter . supplied_to = mbc - > pcf - > pdata - > batteries ;
mbc - > adapter . num_supplicants = mbc - > pcf - > pdata - > num_batteries ;
mbc - > usb . name = " usb " ;
mbc - > usb . type = POWER_SUPPLY_TYPE_USB ;
mbc - > usb . properties = power_props ;
mbc - > usb . num_properties = ARRAY_SIZE ( power_props ) ;
mbc - > usb . get_property = usb_get_property ;
mbc - > usb . supplied_to = mbc - > pcf - > pdata - > batteries ;
mbc - > usb . num_supplicants = mbc - > pcf - > pdata - > num_batteries ;
2009-11-05 00:24:54 +03:00
mbc - > ac . name = " ac " ;
mbc - > ac . type = POWER_SUPPLY_TYPE_MAINS ;
mbc - > ac . properties = power_props ;
mbc - > ac . num_properties = ARRAY_SIZE ( power_props ) ;
mbc - > ac . get_property = ac_get_property ;
mbc - > ac . supplied_to = mbc - > pcf - > pdata - > batteries ;
mbc - > ac . num_supplicants = mbc - > pcf - > pdata - > num_batteries ;
2009-01-09 01:50:55 +01:00
ret = power_supply_register ( & pdev - > dev , & mbc - > adapter ) ;
if ( ret ) {
dev_err ( mbc - > pcf - > dev , " failed to register adapter \n " ) ;
kfree ( mbc ) ;
return ret ;
}
ret = power_supply_register ( & pdev - > dev , & mbc - > usb ) ;
if ( ret ) {
dev_err ( mbc - > pcf - > dev , " failed to register usb \n " ) ;
power_supply_unregister ( & mbc - > adapter ) ;
kfree ( mbc ) ;
return ret ;
}
2009-11-05 00:24:54 +03:00
ret = power_supply_register ( & pdev - > dev , & mbc - > ac ) ;
if ( ret ) {
dev_err ( mbc - > pcf - > dev , " failed to register ac \n " ) ;
power_supply_unregister ( & mbc - > adapter ) ;
power_supply_unregister ( & mbc - > usb ) ;
kfree ( mbc ) ;
return ret ;
}
2009-01-27 19:23:12 +05:30
2009-01-09 01:50:55 +01:00
ret = sysfs_create_group ( & pdev - > dev . kobj , & mbc_attr_group ) ;
if ( ret )
dev_err ( mbc - > pcf - > dev , " failed to create sysfs entries \n " ) ;
mbcs1 = pcf50633_reg_read ( mbc - > pcf , PCF50633_REG_MBCS1 ) ;
if ( mbcs1 & PCF50633_MBCS1_USBPRES )
pcf50633_mbc_irq_handler ( PCF50633_IRQ_USBINS , mbc ) ;
if ( mbcs1 & PCF50633_MBCS1_ADAPTPRES )
pcf50633_mbc_irq_handler ( PCF50633_IRQ_ADPINS , mbc ) ;
return 0 ;
}
static int __devexit pcf50633_mbc_remove ( struct platform_device * pdev )
{
struct pcf50633_mbc * mbc = platform_get_drvdata ( pdev ) ;
int i ;
/* Remove IRQ handlers */
for ( i = 0 ; i < ARRAY_SIZE ( mbc_irq_handlers ) ; i + + )
pcf50633_free_irq ( mbc - > pcf , mbc_irq_handlers [ i ] ) ;
power_supply_unregister ( & mbc - > usb ) ;
power_supply_unregister ( & mbc - > adapter ) ;
2009-11-05 00:24:54 +03:00
power_supply_unregister ( & mbc - > ac ) ;
2009-01-27 19:23:12 +05:30
2009-01-09 01:50:55 +01:00
kfree ( mbc ) ;
return 0 ;
}
static struct platform_driver pcf50633_mbc_driver = {
. driver = {
. name = " pcf50633-mbc " ,
} ,
. probe = pcf50633_mbc_probe ,
. remove = __devexit_p ( pcf50633_mbc_remove ) ,
} ;
static int __init pcf50633_mbc_init ( void )
{
return platform_driver_register ( & pcf50633_mbc_driver ) ;
}
module_init ( pcf50633_mbc_init ) ;
static void __exit pcf50633_mbc_exit ( void )
{
platform_driver_unregister ( & pcf50633_mbc_driver ) ;
}
module_exit ( pcf50633_mbc_exit ) ;
MODULE_AUTHOR ( " Balaji Rao <balajirrao@openmoko.org> " ) ;
MODULE_DESCRIPTION ( " PCF50633 mbc driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:pcf50633-mbc " ) ;