2011-02-14 13:17:12 +03:00
/*
* Copyright ( C ) ST - Ericsson SA 2010
* Author : Mattias Nilsson < mattias . i . nilsson @ stericsson . com > for ST Ericsson .
* License terms : GNU General Public License ( GPL ) version 2
*/
# include <linux/err.h>
2011-07-03 23:13:27 +04:00
# include <linux/module.h>
2011-02-14 13:17:12 +03:00
# include <linux/platform_device.h>
2013-01-14 17:26:15 +04:00
# include <linux/pm.h>
2011-08-17 15:20:21 +04:00
# include <linux/reboot.h>
2013-01-14 17:26:15 +04:00
# include <linux/signal.h>
2011-08-17 15:20:21 +04:00
# include <linux/power_supply.h>
2011-02-14 13:17:12 +03:00
# include <linux/mfd/abx500.h>
2011-12-02 17:16:33 +04:00
# include <linux/mfd/abx500/ab8500.h>
# include <linux/mfd/abx500/ab8500-sysctrl.h>
2011-02-14 13:17:12 +03:00
2013-02-12 19:11:19 +04:00
/* RtcCtrl bits */
# define AB8500_ALARM_MIN_LOW 0x08
# define AB8500_ALARM_MIN_MID 0x09
# define RTC_CTRL 0x0B
# define RTC_ALARM_ENABLE 0x4
2011-02-14 13:17:12 +03:00
static struct device * sysctrl_dev ;
2013-01-14 17:26:15 +04:00
void ab8500_power_off ( void )
{
sigset_t old ;
sigset_t all ;
2012-05-30 15:02:37 +04:00
static char * pss [ ] = { " ab8500_ac " , " pm2301 " , " ab8500_usb " } ;
2011-08-17 15:20:21 +04:00
int i ;
2011-08-17 17:58:52 +04:00
bool charger_present = false ;
union power_supply_propval val ;
struct power_supply * psy ;
int ret ;
2011-08-17 15:20:21 +04:00
2012-06-18 17:00:40 +04:00
if ( sysctrl_dev = = NULL ) {
pr_err ( " %s: sysctrl not initialized \n " , __func__ ) ;
return ;
}
2011-08-17 15:20:21 +04:00
/*
* If we have a charger connected and we ' re powering off ,
* reboot into charge - only mode .
*/
for ( i = 0 ; i < ARRAY_SIZE ( pss ) ; i + + ) {
psy = power_supply_get_by_name ( pss [ i ] ) ;
if ( ! psy )
continue ;
2011-08-17 17:58:52 +04:00
2011-08-17 15:20:21 +04:00
ret = psy - > get_property ( psy , POWER_SUPPLY_PROP_ONLINE , & val ) ;
if ( ! ret & & val . intval ) {
2011-08-17 17:58:52 +04:00
charger_present = true ;
break ;
}
}
if ( ! charger_present )
goto shutdown ;
/* Check if battery is known */
psy = power_supply_get_by_name ( " ab8500_btemp " ) ;
if ( psy ) {
ret = psy - > get_property ( psy , POWER_SUPPLY_PROP_TECHNOLOGY ,
& val ) ;
if ( ! ret & & val . intval ! = POWER_SUPPLY_TECHNOLOGY_UNKNOWN ) {
2011-08-17 15:20:21 +04:00
printk ( KERN_INFO
2011-08-17 17:58:52 +04:00
" Charger \" %s \" is connected with known battery. "
" Rebooting. \n " ,
2011-08-17 15:20:21 +04:00
pss [ i ] ) ;
2011-08-18 12:14:38 +04:00
machine_restart ( " charging " ) ;
2011-08-17 15:20:21 +04:00
}
}
2013-01-14 17:26:15 +04:00
2011-08-17 17:58:52 +04:00
shutdown :
2013-01-14 17:26:15 +04:00
sigfillset ( & all ) ;
if ( ! sigprocmask ( SIG_BLOCK , & all , & old ) ) {
( void ) ab8500_sysctrl_set ( AB8500_STW4500CTRL1 ,
AB8500_STW4500CTRL1_SWOFF |
AB8500_STW4500CTRL1_SWRESET4500N ) ;
( void ) sigprocmask ( SIG_SETMASK , & old , NULL ) ;
}
}
2013-02-12 19:11:19 +04:00
/*
* Use the AB WD to reset the platform . It will perform a hard
* reset instead of a soft reset . Write the reset reason to
* the AB before reset , which can be read upon restart .
*/
void ab8500_restart ( char mode , const char * cmd )
{
struct ab8500_platform_data * plat ;
struct ab8500_sysctrl_platform_data * pdata ;
u16 reason = 0 ;
u8 val ;
if ( sysctrl_dev = = NULL ) {
pr_err ( " %s: sysctrl not initialized \n " , __func__ ) ;
return ;
}
plat = dev_get_platdata ( sysctrl_dev - > parent ) ;
pdata = plat - > sysctrl ;
if ( pdata - > reboot_reason_code )
reason = pdata - > reboot_reason_code ( cmd ) ;
else
pr_warn ( " [%s] No reboot reason set. Default reason %d \n " ,
__func__ , reason ) ;
/*
* Disable RTC alarm , just a precaution so that no alarm
* is running when WD reset is executed .
*/
abx500_get_register_interruptible ( sysctrl_dev , AB8500_RTC ,
RTC_CTRL , & val ) ;
abx500_set_register_interruptible ( sysctrl_dev , AB8500_RTC ,
RTC_CTRL , ( val & ~ RTC_ALARM_ENABLE ) ) ;
/*
* Android is not using the RTC alarm registers during reboot
* so we borrow them for writing the reason of reset
*/
/* reason[8 LSB] */
val = reason & 0xFF ;
abx500_set_register_interruptible ( sysctrl_dev , AB8500_RTC ,
AB8500_ALARM_MIN_LOW , val ) ;
/* reason[8 MSB] */
val = ( reason > > 8 ) & 0xFF ;
abx500_set_register_interruptible ( sysctrl_dev , AB8500_RTC ,
AB8500_ALARM_MIN_MID , val ) ;
/* Setting WD timeout to 0 */
ab8500_sysctrl_write ( AB8500_MAINWDOGTIMER , 0xFF , 0x0 ) ;
/* Setting the parameters to AB8500 WD*/
ab8500_sysctrl_write ( AB8500_MAINWDOGCTRL , 0xFF , ( AB8500_ENABLE_WD |
AB8500_WD_RESTART_ON_EXPIRE | AB8500_KICK_WD ) ) ;
}
2011-02-14 13:17:12 +03:00
static inline bool valid_bank ( u8 bank )
{
return ( ( bank = = AB8500_SYS_CTRL1_BLOCK ) | |
( bank = = AB8500_SYS_CTRL2_BLOCK ) ) ;
}
int ab8500_sysctrl_read ( u16 reg , u8 * value )
{
u8 bank ;
if ( sysctrl_dev = = NULL )
2012-06-18 17:00:40 +04:00
return - EINVAL ;
2011-02-14 13:17:12 +03:00
bank = ( reg > > 8 ) ;
if ( ! valid_bank ( bank ) )
return - EINVAL ;
return abx500_get_register_interruptible ( sysctrl_dev , bank ,
( u8 ) ( reg & 0xFF ) , value ) ;
}
2011-11-11 10:52:10 +04:00
EXPORT_SYMBOL ( ab8500_sysctrl_read ) ;
2011-02-14 13:17:12 +03:00
int ab8500_sysctrl_write ( u16 reg , u8 mask , u8 value )
{
u8 bank ;
if ( sysctrl_dev = = NULL )
2012-06-18 17:00:40 +04:00
return - EINVAL ;
2011-02-14 13:17:12 +03:00
bank = ( reg > > 8 ) ;
if ( ! valid_bank ( bank ) )
return - EINVAL ;
return abx500_mask_and_set_register_interruptible ( sysctrl_dev , bank ,
( u8 ) ( reg & 0xFF ) , mask , value ) ;
}
2011-11-11 10:52:10 +04:00
EXPORT_SYMBOL ( ab8500_sysctrl_write ) ;
2011-02-14 13:17:12 +03:00
2012-11-19 22:23:04 +04:00
static int ab8500_sysctrl_probe ( struct platform_device * pdev )
2011-02-14 13:17:12 +03:00
{
2012-08-22 15:08:11 +04:00
struct ab8500 * ab8500 = dev_get_drvdata ( pdev - > dev . parent ) ;
2013-01-14 17:26:15 +04:00
struct ab8500_platform_data * plat ;
2011-09-27 11:23:56 +04:00
struct ab8500_sysctrl_platform_data * pdata ;
2013-01-14 17:26:15 +04:00
plat = dev_get_platdata ( pdev - > dev . parent ) ;
2012-06-18 17:00:40 +04:00
if ( ! ( plat & & plat - > sysctrl ) )
return - EINVAL ;
2013-01-14 17:26:15 +04:00
if ( plat - > pm_power_off )
pm_power_off = ab8500_power_off ;
2011-09-27 11:23:56 +04:00
pdata = plat - > sysctrl ;
2012-08-22 15:08:11 +04:00
if ( pdata ) {
int last , ret , i , j ;
if ( is_ab8505 ( ab8500 ) )
last = AB8500_SYSCLKREQ4RFCLKBUF ;
else
last = AB8500_SYSCLKREQ8RFCLKBUF ;
for ( i = AB8500_SYSCLKREQ1RFCLKBUF ; i < = last ; i + + ) {
j = i - AB8500_SYSCLKREQ1RFCLKBUF ;
ret = ab8500_sysctrl_write ( i , 0xff ,
pdata - > initial_req_buf_config [ j ] ) ;
dev_dbg ( & pdev - > dev ,
" Setting SysClkReq%dRfClkBuf 0x%X \n " ,
j + 1 ,
pdata - > initial_req_buf_config [ j ] ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev ,
" unable to set sysClkReq%dRfClkBuf: "
" %d \n " , j + 1 , ret ) ;
}
2011-09-27 11:23:56 +04:00
}
}
2011-02-14 13:17:12 +03:00
return 0 ;
}
2012-11-19 22:26:01 +04:00
static int ab8500_sysctrl_remove ( struct platform_device * pdev )
2011-02-14 13:17:12 +03:00
{
sysctrl_dev = NULL ;
return 0 ;
}
static struct platform_driver ab8500_sysctrl_driver = {
. driver = {
. name = " ab8500-sysctrl " ,
. owner = THIS_MODULE ,
} ,
. probe = ab8500_sysctrl_probe ,
2012-11-19 22:20:24 +04:00
. remove = ab8500_sysctrl_remove ,
2011-02-14 13:17:12 +03:00
} ;
static int __init ab8500_sysctrl_init ( void )
{
return platform_driver_register ( & ab8500_sysctrl_driver ) ;
}
subsys_initcall ( ab8500_sysctrl_init ) ;
MODULE_AUTHOR ( " Mattias Nilsson <mattias.i.nilsson@stericsson.com " ) ;
MODULE_DESCRIPTION ( " AB8500 system control driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;