2016-04-01 14:56:23 +03:00
/*
* Watchdog driver for Renesas WDT watchdog
*
2017-07-27 00:54:39 +03:00
* Copyright ( C ) 2015 - 17 Wolfram Sang , Sang Engineering < wsa @ sang - engineering . com >
* Copyright ( C ) 2015 - 17 Renesas Electronics Corporation
2016-04-01 14:56:23 +03:00
*
* 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/bitops.h>
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/watchdog.h>
# define RWTCNT 0
# define RWTCSRA 4
# define RWTCSRA_WOVF BIT(4)
# define RWTCSRA_WRFLG BIT(5)
# define RWTCSRA_TME BIT(7)
2017-07-19 11:27:55 +03:00
# define RWTCSRB 8
2016-04-01 14:56:23 +03:00
# define RWDT_DEFAULT_TIMEOUT 60U
2017-07-19 11:27:54 +03:00
/*
* In probe , clk_rate is checked to be not more than 16 bit * biggest clock
2017-07-19 11:27:55 +03:00
* divider ( 12 bits ) . d is only a factor to fully utilize the WDT counter and
2017-07-19 11:27:54 +03:00
* will not exceed its 16 bits . Thus , no overflow , we stay below 32 bits .
*/
# define MUL_BY_CLKS_PER_SEC(p, d) \
DIV_ROUND_UP ( ( d ) * ( p ) - > clk_rate , clk_divs [ ( p ) - > cks ] )
2017-07-19 11:27:55 +03:00
/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */
2017-07-19 11:27:54 +03:00
# define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)
2017-07-19 11:27:55 +03:00
static const unsigned int clk_divs [ ] = { 1 , 4 , 16 , 32 , 64 , 128 , 1024 , 4096 } ;
2016-04-01 14:56:23 +03:00
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 ) " ) " ) ;
struct rwdt_priv {
void __iomem * base ;
struct watchdog_device wdev ;
2017-07-19 11:27:54 +03:00
unsigned long clk_rate ;
2016-04-01 14:56:23 +03:00
u8 cks ;
} ;
static void rwdt_write ( struct rwdt_priv * priv , u32 val , unsigned int reg )
{
if ( reg = = RWTCNT )
val | = 0x5a5a0000 ;
else
val | = 0xa5a5a500 ;
writel_relaxed ( val , priv - > base + reg ) ;
}
static int rwdt_init_timeout ( struct watchdog_device * wdev )
{
struct rwdt_priv * priv = watchdog_get_drvdata ( wdev ) ;
2017-07-19 11:27:54 +03:00
rwdt_write ( priv , 65536 - MUL_BY_CLKS_PER_SEC ( priv , wdev - > timeout ) , RWTCNT ) ;
2016-04-01 14:56:23 +03:00
return 0 ;
}
static int rwdt_start ( struct watchdog_device * wdev )
{
struct rwdt_priv * priv = watchdog_get_drvdata ( wdev ) ;
2017-07-27 00:54:37 +03:00
pm_runtime_get_sync ( wdev - > parent ) ;
2016-04-01 14:56:23 +03:00
2017-07-19 11:27:55 +03:00
rwdt_write ( priv , 0 , RWTCSRB ) ;
2016-04-01 14:56:23 +03:00
rwdt_write ( priv , priv - > cks , RWTCSRA ) ;
rwdt_init_timeout ( wdev ) ;
while ( readb_relaxed ( priv - > base + RWTCSRA ) & RWTCSRA_WRFLG )
cpu_relax ( ) ;
rwdt_write ( priv , priv - > cks | RWTCSRA_TME , RWTCSRA ) ;
return 0 ;
}
static int rwdt_stop ( struct watchdog_device * wdev )
{
struct rwdt_priv * priv = watchdog_get_drvdata ( wdev ) ;
rwdt_write ( priv , priv - > cks , RWTCSRA ) ;
2017-07-27 00:54:37 +03:00
pm_runtime_put ( wdev - > parent ) ;
2016-04-01 14:56:23 +03:00
return 0 ;
}
static unsigned int rwdt_get_timeleft ( struct watchdog_device * wdev )
{
struct rwdt_priv * priv = watchdog_get_drvdata ( wdev ) ;
u16 val = readw_relaxed ( priv - > base + RWTCNT ) ;
2017-07-19 11:27:54 +03:00
return DIV_BY_CLKS_PER_SEC ( priv , 65536 - val ) ;
2016-04-01 14:56:23 +03:00
}
static const struct watchdog_info rwdt_ident = {
. options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT ,
. identity = " Renesas WDT Watchdog " ,
} ;
static const struct watchdog_ops rwdt_ops = {
. owner = THIS_MODULE ,
. start = rwdt_start ,
. stop = rwdt_stop ,
. ping = rwdt_init_timeout ,
. get_timeleft = rwdt_get_timeleft ,
} ;
static int rwdt_probe ( struct platform_device * pdev )
{
struct rwdt_priv * priv ;
struct resource * res ;
2017-07-27 00:54:38 +03:00
struct clk * clk ;
2017-07-19 11:27:54 +03:00
unsigned long clks_per_sec ;
2016-04-01 14:56:23 +03:00
int ret , i ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( priv - > base ) )
return PTR_ERR ( priv - > base ) ;
2017-07-27 00:54:38 +03:00
clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( clk ) )
return PTR_ERR ( clk ) ;
2016-04-01 14:56:23 +03:00
2017-07-27 00:54:37 +03:00
pm_runtime_enable ( & pdev - > dev ) ;
pm_runtime_get_sync ( & pdev - > dev ) ;
2017-07-27 00:54:38 +03:00
priv - > clk_rate = clk_get_rate ( clk ) ;
2017-07-27 00:54:37 +03:00
pm_runtime_put ( & pdev - > dev ) ;
if ( ! priv - > clk_rate ) {
ret = - ENOENT ;
goto out_pm_disable ;
}
2016-04-01 14:56:23 +03:00
for ( i = ARRAY_SIZE ( clk_divs ) - 1 ; i > = 0 ; i - - ) {
2017-07-19 11:27:54 +03:00
clks_per_sec = priv - > clk_rate / clk_divs [ i ] ;
2017-07-19 11:27:52 +03:00
if ( clks_per_sec & & clks_per_sec < 65536 ) {
2016-04-01 14:56:23 +03:00
priv - > cks = i ;
break ;
}
}
2017-07-19 11:27:52 +03:00
if ( i < 0 ) {
2016-04-01 14:56:23 +03:00
dev_err ( & pdev - > dev , " Can't find suitable clock divider \n " ) ;
2017-07-27 00:54:37 +03:00
ret = - ERANGE ;
goto out_pm_disable ;
2016-04-01 14:56:23 +03:00
}
priv - > wdev . info = & rwdt_ident ,
priv - > wdev . ops = & rwdt_ops ,
priv - > wdev . parent = & pdev - > dev ;
priv - > wdev . min_timeout = 1 ;
2017-07-19 11:27:54 +03:00
priv - > wdev . max_timeout = DIV_BY_CLKS_PER_SEC ( priv , 65536 ) ;
2016-04-01 14:56:23 +03:00
priv - > wdev . timeout = min ( priv - > wdev . max_timeout , RWDT_DEFAULT_TIMEOUT ) ;
platform_set_drvdata ( pdev , priv ) ;
watchdog_set_drvdata ( & priv - > wdev , priv ) ;
watchdog_set_nowayout ( & priv - > wdev , nowayout ) ;
/* This overrides the default timeout only if DT configuration was found */
ret = watchdog_init_timeout ( & priv - > wdev , 0 , & pdev - > dev ) ;
if ( ret )
dev_warn ( & pdev - > dev , " Specified timeout value invalid, using default \n " ) ;
ret = watchdog_register_device ( & priv - > wdev ) ;
2017-07-27 00:54:37 +03:00
if ( ret < 0 )
goto out_pm_disable ;
2016-04-01 14:56:23 +03:00
return 0 ;
2017-07-27 00:54:37 +03:00
out_pm_disable :
pm_runtime_disable ( & pdev - > dev ) ;
return ret ;
2016-04-01 14:56:23 +03:00
}
static int rwdt_remove ( struct platform_device * pdev )
{
struct rwdt_priv * priv = platform_get_drvdata ( pdev ) ;
watchdog_unregister_device ( & priv - > wdev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
return 0 ;
}
/*
* This driver does also fit for R - Car Gen2 ( r8a779 [ 0 - 4 ] ) WDT . However , for SMP
* to work there , one also needs a RESET ( RST ) driver which does not exist yet
* due to HW issues . This needs to be solved before adding compatibles here .
*/
static const struct of_device_id rwdt_ids [ ] = {
{ . compatible = " renesas,rcar-gen3-wdt " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , rwdt_ids ) ;
static struct platform_driver rwdt_driver = {
. driver = {
. name = " renesas_wdt " ,
. of_match_table = rwdt_ids ,
} ,
. probe = rwdt_probe ,
. remove = rwdt_remove ,
} ;
module_platform_driver ( rwdt_driver ) ;
MODULE_DESCRIPTION ( " Renesas WDT Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Wolfram Sang <wsa@sang-engineering.com> " ) ;