2018-03-16 18:14:11 +03:00
// SPDX-License-Identifier: GPL-2.0+
2014-09-28 21:05:45 +04:00
/*
* Watchdog driver for DA9063 PMICs .
*
* Copyright ( c ) 2012 Dialog Semiconductor Ltd .
*
* Author : Mariusz Wojtasik < mariusz . wojtasik @ diasemi . com >
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/watchdog.h>
# include <linux/platform_device.h>
# include <linux/uaccess.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/mfd/da9063/registers.h>
# include <linux/mfd/da9063/core.h>
# include <linux/regmap.h>
/*
* Watchdog selector to timeout in seconds .
* 0 : WDT disabled ;
* others : timeout = 2048 ms * 2 ^ ( TWDSCALE - 1 ) .
*/
static const unsigned int wdt_timeout [ ] = { 0 , 2 , 4 , 8 , 16 , 32 , 65 , 131 } ;
# define DA9063_TWDSCALE_DISABLE 0
# define DA9063_TWDSCALE_MIN 1
# define DA9063_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1)
# define DA9063_WDT_MIN_TIMEOUT wdt_timeout[DA9063_TWDSCALE_MIN]
# define DA9063_WDT_MAX_TIMEOUT wdt_timeout[DA9063_TWDSCALE_MAX]
# define DA9063_WDG_TIMEOUT wdt_timeout[3]
2016-07-06 11:40:11 +03:00
# define DA9063_RESET_PROTECTION_MS 256
2014-09-28 21:05:45 +04:00
static unsigned int da9063_wdt_timeout_to_sel ( unsigned int secs )
{
unsigned int i ;
for ( i = DA9063_TWDSCALE_MIN ; i < = DA9063_TWDSCALE_MAX ; i + + ) {
if ( wdt_timeout [ i ] > = secs )
return i ;
}
return DA9063_TWDSCALE_MAX ;
}
2018-05-28 09:45:46 +03:00
/*
* Return 0 if watchdog is disabled , else non zero .
*/
static unsigned int da9063_wdt_is_running ( struct da9063 * da9063 )
{
unsigned int val ;
regmap_read ( da9063 - > regmap , DA9063_REG_CONTROL_D , & val ) ;
return val & DA9063_TWDSCALE_MASK ;
}
2018-05-28 09:45:44 +03:00
static int da9063_wdt_disable_timer ( struct da9063 * da9063 )
{
return regmap_update_bits ( da9063 - > regmap , DA9063_REG_CONTROL_D ,
DA9063_TWDSCALE_MASK ,
DA9063_TWDSCALE_DISABLE ) ;
}
2018-06-04 18:00:59 +03:00
static int
da9063_wdt_update_timeout ( struct da9063 * da9063 , unsigned int timeout )
2014-09-28 21:05:45 +04:00
{
2018-06-04 18:00:59 +03:00
unsigned int regval ;
2018-05-28 09:45:44 +03:00
int ret ;
/*
* The watchdog triggers a reboot if a timeout value is already
* programmed because the timeout value combines two functions
* in one : indicating the counter limit and starting the watchdog .
* The watchdog must be disabled to be able to change the timeout
* value if the watchdog is already running . Then we can set the
* new timeout value which enables the watchdog again .
*/
ret = da9063_wdt_disable_timer ( da9063 ) ;
if ( ret )
return ret ;
usleep_range ( 150 , 300 ) ;
2018-06-04 18:00:59 +03:00
regval = da9063_wdt_timeout_to_sel ( timeout ) ;
2018-05-28 09:45:44 +03:00
2014-09-28 21:05:45 +04:00
return regmap_update_bits ( da9063 - > regmap , DA9063_REG_CONTROL_D ,
DA9063_TWDSCALE_MASK , regval ) ;
}
static int da9063_wdt_start ( struct watchdog_device * wdd )
{
2017-07-25 14:25:58 +03:00
struct da9063 * da9063 = watchdog_get_drvdata ( wdd ) ;
2014-09-28 21:05:45 +04:00
int ret ;
2018-06-04 18:00:59 +03:00
ret = da9063_wdt_update_timeout ( da9063 , wdd - > timeout ) ;
2014-09-28 21:05:45 +04:00
if ( ret )
2017-07-25 14:25:58 +03:00
dev_err ( da9063 - > dev , " Watchdog failed to start (err = %d) \n " ,
2014-09-28 21:05:45 +04:00
ret ) ;
return ret ;
}
static int da9063_wdt_stop ( struct watchdog_device * wdd )
{
2017-07-25 14:25:58 +03:00
struct da9063 * da9063 = watchdog_get_drvdata ( wdd ) ;
2014-09-28 21:05:45 +04:00
int ret ;
2018-05-28 09:45:44 +03:00
ret = da9063_wdt_disable_timer ( da9063 ) ;
2014-09-28 21:05:45 +04:00
if ( ret )
2017-07-25 14:25:58 +03:00
dev_alert ( da9063 - > dev , " Watchdog failed to stop (err = %d) \n " ,
2014-09-28 21:05:45 +04:00
ret ) ;
return ret ;
}
static int da9063_wdt_ping ( struct watchdog_device * wdd )
{
2017-07-25 14:25:58 +03:00
struct da9063 * da9063 = watchdog_get_drvdata ( wdd ) ;
2014-09-28 21:05:45 +04:00
int ret ;
2017-07-25 14:25:58 +03:00
ret = regmap_write ( da9063 - > regmap , DA9063_REG_CONTROL_F ,
2014-09-28 21:05:45 +04:00
DA9063_WATCHDOG ) ;
if ( ret )
2017-07-25 14:25:58 +03:00
dev_alert ( da9063 - > dev , " Failed to ping the watchdog (err = %d) \n " ,
2014-09-28 21:05:45 +04:00
ret ) ;
return ret ;
}
static int da9063_wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
2017-07-25 14:25:58 +03:00
struct da9063 * da9063 = watchdog_get_drvdata ( wdd ) ;
2018-05-28 09:45:45 +03:00
int ret = 0 ;
2014-09-28 21:05:45 +04:00
2018-05-28 09:45:45 +03:00
/*
* There are two cases when a set_timeout ( ) will be called :
* 1. The watchdog is off and someone wants to set the timeout for the
* further use .
* 2. The watchdog is already running and a new timeout value should be
* set .
*
* The watchdog can ' t store a timeout value not equal zero without
* enabling the watchdog , so the timeout must be buffered by the driver .
*/
if ( watchdog_active ( wdd ) )
2018-06-04 18:00:59 +03:00
ret = da9063_wdt_update_timeout ( da9063 , timeout ) ;
2018-05-28 09:45:45 +03:00
2014-09-28 21:05:45 +04:00
if ( ret )
2017-07-25 14:25:58 +03:00
dev_err ( da9063 - > dev , " Failed to set watchdog timeout (err = %d) \n " ,
2014-09-28 21:05:45 +04:00
ret ) ;
else
2018-06-04 18:00:59 +03:00
wdd - > timeout = wdt_timeout [ da9063_wdt_timeout_to_sel ( timeout ) ] ;
2014-09-28 21:05:45 +04:00
return ret ;
}
2016-02-27 04:32:49 +03:00
static int da9063_wdt_restart ( struct watchdog_device * wdd , unsigned long action ,
void * data )
2015-01-29 17:26:05 +03:00
{
2017-07-25 14:25:58 +03:00
struct da9063 * da9063 = watchdog_get_drvdata ( wdd ) ;
2015-01-29 17:26:05 +03:00
int ret ;
2017-07-25 14:25:58 +03:00
ret = regmap_write ( da9063 - > regmap , DA9063_REG_CONTROL_F ,
2015-01-29 17:26:05 +03:00
DA9063_SHUTDOWN ) ;
if ( ret )
2017-07-25 14:25:58 +03:00
dev_alert ( da9063 - > dev , " Failed to shutdown (err = %d) \n " ,
2015-01-29 17:26:05 +03:00
ret ) ;
2015-11-16 20:28:01 +03:00
return ret ;
2015-01-29 17:26:05 +03:00
}
2014-09-28 21:05:45 +04:00
static const struct watchdog_info da9063_watchdog_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = " DA9063 Watchdog " ,
} ;
static const struct watchdog_ops da9063_watchdog_ops = {
. owner = THIS_MODULE ,
. start = da9063_wdt_start ,
. stop = da9063_wdt_stop ,
. ping = da9063_wdt_ping ,
. set_timeout = da9063_wdt_set_timeout ,
2015-11-16 20:28:01 +03:00
. restart = da9063_wdt_restart ,
2014-09-28 21:05:45 +04:00
} ;
static int da9063_wdt_probe ( struct platform_device * pdev )
{
2019-04-08 22:38:36 +03:00
struct device * dev = & pdev - > dev ;
2014-09-28 21:05:45 +04:00
struct da9063 * da9063 ;
2017-07-25 14:25:58 +03:00
struct watchdog_device * wdd ;
2014-09-28 21:05:45 +04:00
2019-04-08 22:38:36 +03:00
if ( ! dev - > parent )
2014-09-28 21:05:45 +04:00
return - EINVAL ;
2019-04-08 22:38:36 +03:00
da9063 = dev_get_drvdata ( dev - > parent ) ;
2014-09-28 21:05:45 +04:00
if ( ! da9063 )
return - EINVAL ;
2019-04-08 22:38:36 +03:00
wdd = devm_kzalloc ( dev , sizeof ( * wdd ) , GFP_KERNEL ) ;
2017-07-25 14:25:58 +03:00
if ( ! wdd )
2014-09-28 21:05:45 +04:00
return - ENOMEM ;
2017-07-25 14:25:58 +03:00
wdd - > info = & da9063_watchdog_info ;
wdd - > ops = & da9063_watchdog_ops ;
wdd - > min_timeout = DA9063_WDT_MIN_TIMEOUT ;
wdd - > max_timeout = DA9063_WDT_MAX_TIMEOUT ;
wdd - > min_hw_heartbeat_ms = DA9063_RESET_PROTECTION_MS ;
2019-04-08 22:38:36 +03:00
wdd - > parent = dev ;
2017-07-25 14:25:58 +03:00
wdd - > status = WATCHDOG_NOWAYOUT_INIT_STATUS ;
2014-09-28 21:05:45 +04:00
2017-07-25 14:25:58 +03:00
watchdog_set_restart_priority ( wdd , 128 ) ;
watchdog_set_drvdata ( wdd , da9063 ) ;
2014-09-28 21:05:45 +04:00
2019-04-14 14:09:33 +03:00
/* Set default timeout, maybe override it with DT value, scale it */
wdd - > timeout = DA9063_WDG_TIMEOUT ;
watchdog_init_timeout ( wdd , 0 , dev ) ;
da9063_wdt_set_timeout ( wdd , wdd - > timeout ) ;
2018-05-28 09:45:46 +03:00
/* Change the timeout to the default value if the watchdog is running */
if ( da9063_wdt_is_running ( da9063 ) ) {
2019-04-14 14:09:33 +03:00
da9063_wdt_update_timeout ( da9063 , wdd - > timeout ) ;
2018-05-28 09:45:46 +03:00
set_bit ( WDOG_HW_RUNNING , & wdd - > status ) ;
}
2019-04-08 22:38:36 +03:00
return devm_watchdog_register_device ( dev , wdd ) ;
2014-09-28 21:05:45 +04:00
}
static struct platform_driver da9063_wdt_driver = {
. probe = da9063_wdt_probe ,
. driver = {
. name = DA9063_DRVNAME_WATCHDOG ,
} ,
} ;
module_platform_driver ( da9063_wdt_driver ) ;
MODULE_AUTHOR ( " Mariusz Wojtasik <mariusz.wojtasik@diasemi.com> " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for Dialog DA9063 " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform: " DA9063_DRVNAME_WATCHDOG ) ;