2012-10-12 13:30:03 +04:00
/*
* System monitoring driver for DA9055 PMICs .
*
* Copyright ( c ) 2012 Dialog Semiconductor Ltd .
*
* Author : David Dajun Chen < dchen @ diasemi . com >
*
* 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/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# include <linux/delay.h>
# include <linux/mfd/da9055/core.h>
# include <linux/mfd/da9055/reg.h>
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
# define DA9055_DEF_TIMEOUT 4
# define DA9055_TWDMIN 256
struct da9055_wdt_data {
struct watchdog_device wdt ;
struct da9055 * da9055 ;
struct kref kref ;
} ;
static const struct {
u8 reg_val ;
int user_time ; /* In seconds */
} da9055_wdt_maps [ ] = {
{ 0 , 0 } ,
{ 1 , 2 } ,
{ 2 , 4 } ,
{ 3 , 8 } ,
{ 4 , 16 } ,
{ 5 , 32 } ,
{ 5 , 33 } , /* Actual time 32.768s so included both 32s and 33s */
{ 6 , 65 } ,
{ 6 , 66 } , /* Actual time 65.536s so include both, 65s and 66s */
{ 7 , 131 } ,
} ;
static int da9055_wdt_set_timeout ( struct watchdog_device * wdt_dev ,
unsigned int timeout )
{
struct da9055_wdt_data * driver_data = watchdog_get_drvdata ( wdt_dev ) ;
struct da9055 * da9055 = driver_data - > da9055 ;
int ret , i ;
for ( i = 0 ; i < ARRAY_SIZE ( da9055_wdt_maps ) ; i + + )
if ( da9055_wdt_maps [ i ] . user_time = = timeout )
break ;
if ( i = = ARRAY_SIZE ( da9055_wdt_maps ) )
ret = - EINVAL ;
else
ret = da9055_reg_update ( da9055 , DA9055_REG_CONTROL_B ,
DA9055_TWDSCALE_MASK ,
da9055_wdt_maps [ i ] . reg_val < <
DA9055_TWDSCALE_SHIFT ) ;
2012-12-22 07:07:01 +04:00
if ( ret < 0 ) {
2012-10-12 13:30:03 +04:00
dev_err ( da9055 - > dev ,
" Failed to update timescale bit, %d \n " , ret ) ;
2012-12-22 07:07:01 +04:00
return ret ;
}
2012-10-12 13:30:03 +04:00
wdt_dev - > timeout = timeout ;
2012-12-22 07:07:01 +04:00
return 0 ;
2012-10-12 13:30:03 +04:00
}
static int da9055_wdt_ping ( struct watchdog_device * wdt_dev )
{
struct da9055_wdt_data * driver_data = watchdog_get_drvdata ( wdt_dev ) ;
struct da9055 * da9055 = driver_data - > da9055 ;
/*
* We have a minimum time for watchdog window called TWDMIN . A write
* to the watchdog before this elapsed time will cause an error .
*/
mdelay ( DA9055_TWDMIN ) ;
/* Reset the watchdog timer */
2012-12-22 07:07:01 +04:00
return da9055_reg_update ( da9055 , DA9055_REG_CONTROL_E ,
DA9055_WATCHDOG_MASK , 1 ) ;
2012-10-12 13:30:03 +04:00
}
static void da9055_wdt_release_resources ( struct kref * r )
{
}
static void da9055_wdt_ref ( struct watchdog_device * wdt_dev )
{
struct da9055_wdt_data * driver_data = watchdog_get_drvdata ( wdt_dev ) ;
kref_get ( & driver_data - > kref ) ;
}
static void da9055_wdt_unref ( struct watchdog_device * wdt_dev )
{
struct da9055_wdt_data * driver_data = watchdog_get_drvdata ( wdt_dev ) ;
kref_put ( & driver_data - > kref , da9055_wdt_release_resources ) ;
}
static int da9055_wdt_start ( struct watchdog_device * wdt_dev )
{
return da9055_wdt_set_timeout ( wdt_dev , wdt_dev - > timeout ) ;
}
static int da9055_wdt_stop ( struct watchdog_device * wdt_dev )
{
return da9055_wdt_set_timeout ( wdt_dev , 0 ) ;
}
static struct watchdog_info da9055_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = " DA9055 Watchdog " ,
} ;
static const struct watchdog_ops da9055_wdt_ops = {
. owner = THIS_MODULE ,
. start = da9055_wdt_start ,
. stop = da9055_wdt_stop ,
. ping = da9055_wdt_ping ,
. set_timeout = da9055_wdt_set_timeout ,
. ref = da9055_wdt_ref ,
. unref = da9055_wdt_unref ,
} ;
static int da9055_wdt_probe ( struct platform_device * pdev )
{
struct da9055 * da9055 = dev_get_drvdata ( pdev - > dev . parent ) ;
struct da9055_wdt_data * driver_data ;
struct watchdog_device * da9055_wdt ;
int ret ;
driver_data = devm_kzalloc ( & pdev - > dev , sizeof ( * driver_data ) ,
GFP_KERNEL ) ;
if ( ! driver_data ) {
dev_err ( da9055 - > dev , " Failed to allocate watchdog device \n " ) ;
return - ENOMEM ;
}
driver_data - > da9055 = da9055 ;
da9055_wdt = & driver_data - > wdt ;
da9055_wdt - > timeout = DA9055_DEF_TIMEOUT ;
da9055_wdt - > info = & da9055_wdt_info ;
da9055_wdt - > ops = & da9055_wdt_ops ;
watchdog_set_nowayout ( da9055_wdt , nowayout ) ;
watchdog_set_drvdata ( da9055_wdt , driver_data ) ;
kref_init ( & driver_data - > kref ) ;
ret = da9055_wdt_stop ( da9055_wdt ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Failed to stop watchdog, %d \n " , ret ) ;
goto err ;
}
2013-05-23 14:44:23 +04:00
platform_set_drvdata ( pdev , driver_data ) ;
2012-10-12 13:30:03 +04:00
ret = watchdog_register_device ( & driver_data - > wdt ) ;
if ( ret ! = 0 )
dev_err ( da9055 - > dev , " watchdog_register_device() failed: %d \n " ,
ret ) ;
err :
return ret ;
}
static int da9055_wdt_remove ( struct platform_device * pdev )
{
2013-05-23 14:44:23 +04:00
struct da9055_wdt_data * driver_data = platform_get_drvdata ( pdev ) ;
2012-10-12 13:30:03 +04:00
watchdog_unregister_device ( & driver_data - > wdt ) ;
kref_put ( & driver_data - > kref , da9055_wdt_release_resources ) ;
return 0 ;
}
static struct platform_driver da9055_wdt_driver = {
. probe = da9055_wdt_probe ,
. remove = da9055_wdt_remove ,
. driver = {
. name = " da9055-watchdog " ,
} ,
} ;
module_platform_driver ( da9055_wdt_driver ) ;
MODULE_AUTHOR ( " David Dajun Chen <dchen@diasemi.com> " ) ;
MODULE_DESCRIPTION ( " DA9055 watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:da9055-watchdog " ) ;