2022-04-27 16:55:31 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Renesas RZ / N1 Watchdog timer .
* This is a 12 - bit timer driver from a ( 62.5 / 16384 ) MHz clock . It can ' t even
* cope with 2 seconds .
*
* Copyright 2018 Renesas Electronics Europe Ltd .
*
* Derived from Ralink RT288x watchdog timer .
*/
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/platform_device.h>
# include <linux/reboot.h>
# include <linux/watchdog.h>
# define DEFAULT_TIMEOUT 60
# define RZN1_WDT_RETRIGGER 0x0
# define RZN1_WDT_RETRIGGER_RELOAD_VAL 0
# define RZN1_WDT_RETRIGGER_RELOAD_VAL_MASK 0xfff
# define RZN1_WDT_RETRIGGER_PRESCALE BIT(12)
# define RZN1_WDT_RETRIGGER_ENABLE BIT(13)
# define RZN1_WDT_RETRIGGER_WDSI (0x2 << 14)
# define RZN1_WDT_PRESCALER 16384
# define RZN1_WDT_MAX 4095
struct rzn1_watchdog {
struct watchdog_device wdtdev ;
void __iomem * base ;
unsigned long clk_rate_khz ;
} ;
static inline uint32_t max_heart_beat_ms ( unsigned long clk_rate_khz )
{
return ( RZN1_WDT_MAX * RZN1_WDT_PRESCALER ) / clk_rate_khz ;
}
static inline uint32_t compute_reload_value ( uint32_t tick_ms ,
unsigned long clk_rate_khz )
{
return ( tick_ms * clk_rate_khz ) / RZN1_WDT_PRESCALER ;
}
static int rzn1_wdt_ping ( struct watchdog_device * w )
{
struct rzn1_watchdog * wdt = watchdog_get_drvdata ( w ) ;
/* Any value retrigggers the watchdog */
writel ( 0 , wdt - > base + RZN1_WDT_RETRIGGER ) ;
return 0 ;
}
static int rzn1_wdt_start ( struct watchdog_device * w )
{
struct rzn1_watchdog * wdt = watchdog_get_drvdata ( w ) ;
u32 val ;
/*
* The hardware allows you to write to this reg only once .
* Since this includes the reload value , there is no way to change the
* timeout once started . Also note that the WDT clock is half the bus
* fabric clock rate , so if the bus fabric clock rate is changed after
* the WDT is started , the WDT interval will be wrong .
*/
val = RZN1_WDT_RETRIGGER_WDSI ;
val | = RZN1_WDT_RETRIGGER_ENABLE ;
val | = RZN1_WDT_RETRIGGER_PRESCALE ;
val | = compute_reload_value ( w - > max_hw_heartbeat_ms , wdt - > clk_rate_khz ) ;
writel ( val , wdt - > base + RZN1_WDT_RETRIGGER ) ;
return 0 ;
}
static irqreturn_t rzn1_wdt_irq ( int irq , void * _wdt )
{
pr_crit ( " RZN1 Watchdog. Initiating system reboot \n " ) ;
emergency_restart ( ) ;
return IRQ_HANDLED ;
}
static struct watchdog_info rzn1_wdt_info = {
. identity = " RZ/N1 Watchdog " ,
. options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
} ;
static const struct watchdog_ops rzn1_wdt_ops = {
. owner = THIS_MODULE ,
. start = rzn1_wdt_start ,
. ping = rzn1_wdt_ping ,
} ;
static int rzn1_wdt_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct rzn1_watchdog * wdt ;
struct device_node * np = dev - > of_node ;
struct clk * clk ;
unsigned long clk_rate ;
int ret ;
int irq ;
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
if ( ! wdt )
return - ENOMEM ;
wdt - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( wdt - > base ) )
return PTR_ERR ( wdt - > base ) ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 )
return irq ;
ret = devm_request_irq ( dev , irq , rzn1_wdt_irq , 0 ,
np - > name , wdt ) ;
if ( ret ) {
dev_err ( dev , " failed to request irq %d \n " , irq ) ;
return ret ;
}
2022-12-30 19:36:20 +03:00
clk = devm_clk_get_enabled ( dev , NULL ) ;
2022-04-27 16:55:31 +03:00
if ( IS_ERR ( clk ) ) {
dev_err ( dev , " failed to get the clock \n " ) ;
return PTR_ERR ( clk ) ;
}
clk_rate = clk_get_rate ( clk ) ;
if ( ! clk_rate ) {
dev_err ( dev , " failed to get the clock rate \n " ) ;
return - EINVAL ;
}
wdt - > clk_rate_khz = clk_rate / 1000 ;
wdt - > wdtdev . info = & rzn1_wdt_info ,
wdt - > wdtdev . ops = & rzn1_wdt_ops ,
wdt - > wdtdev . status = WATCHDOG_NOWAYOUT_INIT_STATUS ,
wdt - > wdtdev . parent = dev ;
/*
* The period of the watchdog cannot be changed once set
* and is limited to a very short period .
* Configure it for a 1 s period once and for all , and
* rely on the heart - beat provided by the watchdog core
* to make this usable by the user - space .
*/
wdt - > wdtdev . max_hw_heartbeat_ms = max_heart_beat_ms ( wdt - > clk_rate_khz ) ;
if ( wdt - > wdtdev . max_hw_heartbeat_ms > 1000 )
wdt - > wdtdev . max_hw_heartbeat_ms = 1000 ;
wdt - > wdtdev . timeout = DEFAULT_TIMEOUT ;
ret = watchdog_init_timeout ( & wdt - > wdtdev , 0 , dev ) ;
if ( ret )
return ret ;
watchdog_set_drvdata ( & wdt - > wdtdev , wdt ) ;
return devm_watchdog_register_device ( dev , & wdt - > wdtdev ) ;
}
static const struct of_device_id rzn1_wdt_match [ ] = {
{ . compatible = " renesas,rzn1-wdt " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , rzn1_wdt_match ) ;
static struct platform_driver rzn1_wdt_driver = {
. probe = rzn1_wdt_probe ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = rzn1_wdt_match ,
} ,
} ;
module_platform_driver ( rzn1_wdt_driver ) ;
MODULE_DESCRIPTION ( " Renesas RZ/N1 hardware watchdog " ) ;
MODULE_AUTHOR ( " Phil Edworthy <phil.edworthy@renesas.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;