2020-12-16 21:32:48 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Watchdog driver for Intel Keem Bay non - secure watchdog .
*
* Copyright ( C ) 2020 Intel Corporation
*/
# include <linux/arm-smccc.h>
# include <linux/bits.h>
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/limits.h>
# include <linux/module.h>
# include <linux/mod_devicetable.h>
# include <linux/platform_device.h>
# include <linux/reboot.h>
# include <linux/watchdog.h>
/* Non-secure watchdog register offsets */
# define TIM_WATCHDOG 0x0
# define TIM_WATCHDOG_INT_THRES 0x4
# define TIM_WDOG_EN 0x8
# define TIM_SAFE 0xc
2021-05-17 20:49:48 +03:00
# define WDT_TH_INT_MASK BIT(8)
# define WDT_TO_INT_MASK BIT(9)
2021-05-17 20:49:52 +03:00
# define WDT_INT_CLEAR_SMC 0x8200ff18
2021-05-17 20:49:53 +03:00
2020-12-16 21:32:48 +03:00
# define WDT_UNLOCK 0xf1d0dead
2021-05-17 20:49:51 +03:00
# define WDT_DISABLE 0x0
# define WDT_ENABLE 0x1
2021-05-17 20:49:53 +03:00
2020-12-16 21:32:48 +03:00
# define WDT_LOAD_MAX U32_MAX
# define WDT_LOAD_MIN 1
2021-05-17 20:49:53 +03:00
2020-12-16 21:32:48 +03:00
# define WDT_TIMEOUT 5
2021-05-17 20:49:45 +03:00
# define WDT_PRETIMEOUT 4
2020-12-16 21:32:48 +03:00
static unsigned int timeout = WDT_TIMEOUT ;
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout period in seconds (default = "
__MODULE_STRING ( WDT_TIMEOUT ) " ) " ) ;
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 keembay_wdt {
struct watchdog_device wdd ;
struct clk * clk ;
unsigned int rate ;
int to_irq ;
int th_irq ;
void __iomem * base ;
} ;
static inline u32 keembay_wdt_readl ( struct keembay_wdt * wdt , u32 offset )
{
return readl ( wdt - > base + offset ) ;
}
static inline void keembay_wdt_writel ( struct keembay_wdt * wdt , u32 offset , u32 val )
{
writel ( WDT_UNLOCK , wdt - > base + TIM_SAFE ) ;
writel ( val , wdt - > base + offset ) ;
}
static void keembay_wdt_set_timeout_reg ( struct watchdog_device * wdog )
{
struct keembay_wdt * wdt = watchdog_get_drvdata ( wdog ) ;
keembay_wdt_writel ( wdt , TIM_WATCHDOG , wdog - > timeout * wdt - > rate ) ;
}
static void keembay_wdt_set_pretimeout_reg ( struct watchdog_device * wdog )
{
struct keembay_wdt * wdt = watchdog_get_drvdata ( wdog ) ;
u32 th_val = 0 ;
if ( wdog - > pretimeout )
th_val = wdog - > timeout - wdog - > pretimeout ;
keembay_wdt_writel ( wdt , TIM_WATCHDOG_INT_THRES , th_val * wdt - > rate ) ;
}
static int keembay_wdt_start ( struct watchdog_device * wdog )
{
struct keembay_wdt * wdt = watchdog_get_drvdata ( wdog ) ;
2021-05-17 20:49:51 +03:00
keembay_wdt_writel ( wdt , TIM_WDOG_EN , WDT_ENABLE ) ;
2020-12-16 21:32:48 +03:00
return 0 ;
}
static int keembay_wdt_stop ( struct watchdog_device * wdog )
{
struct keembay_wdt * wdt = watchdog_get_drvdata ( wdog ) ;
2021-05-17 20:49:51 +03:00
keembay_wdt_writel ( wdt , TIM_WDOG_EN , WDT_DISABLE ) ;
2020-12-16 21:32:48 +03:00
return 0 ;
}
static int keembay_wdt_ping ( struct watchdog_device * wdog )
{
keembay_wdt_set_timeout_reg ( wdog ) ;
return 0 ;
}
static int keembay_wdt_set_timeout ( struct watchdog_device * wdog , u32 t )
{
wdog - > timeout = t ;
keembay_wdt_set_timeout_reg ( wdog ) ;
2021-05-17 20:49:46 +03:00
keembay_wdt_set_pretimeout_reg ( wdog ) ;
2020-12-16 21:32:48 +03:00
return 0 ;
}
static int keembay_wdt_set_pretimeout ( struct watchdog_device * wdog , u32 t )
{
if ( t > wdog - > timeout )
return - EINVAL ;
wdog - > pretimeout = t ;
keembay_wdt_set_pretimeout_reg ( wdog ) ;
return 0 ;
}
static unsigned int keembay_wdt_get_timeleft ( struct watchdog_device * wdog )
{
struct keembay_wdt * wdt = watchdog_get_drvdata ( wdog ) ;
return keembay_wdt_readl ( wdt , TIM_WATCHDOG ) / wdt - > rate ;
}
/*
* SMC call is used to clear the interrupt bits , because the TIM_GEN_CONFIG
* register is in the secure bank .
*/
static irqreturn_t keembay_wdt_to_isr ( int irq , void * dev_id )
{
struct keembay_wdt * wdt = dev_id ;
struct arm_smccc_res res ;
2021-05-17 20:49:52 +03:00
arm_smccc_smc ( WDT_INT_CLEAR_SMC , WDT_TO_INT_MASK , 0 , 0 , 0 , 0 , 0 , 0 , & res ) ;
2021-05-17 20:49:53 +03:00
dev_crit ( wdt - > wdd . parent , " Intel Keem Bay non-secure wdt timeout. \n " ) ;
2020-12-16 21:32:48 +03:00
emergency_restart ( ) ;
return IRQ_HANDLED ;
}
static irqreturn_t keembay_wdt_th_isr ( int irq , void * dev_id )
{
struct keembay_wdt * wdt = dev_id ;
struct arm_smccc_res res ;
2021-05-17 20:49:47 +03:00
keembay_wdt_set_pretimeout ( & wdt - > wdd , 0x0 ) ;
2021-05-17 20:49:52 +03:00
arm_smccc_smc ( WDT_INT_CLEAR_SMC , WDT_TH_INT_MASK , 0 , 0 , 0 , 0 , 0 , 0 , & res ) ;
2021-05-17 20:49:53 +03:00
dev_crit ( wdt - > wdd . parent , " Intel Keem Bay non-secure wdt pre-timeout. \n " ) ;
2020-12-16 21:32:48 +03:00
watchdog_notify_pretimeout ( & wdt - > wdd ) ;
return IRQ_HANDLED ;
}
static const struct watchdog_info keembay_wdt_info = {
. identity = " Intel Keem Bay Watchdog Timer " ,
. options = WDIOF_SETTIMEOUT |
WDIOF_PRETIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING ,
} ;
static const struct watchdog_ops keembay_wdt_ops = {
. owner = THIS_MODULE ,
. start = keembay_wdt_start ,
. stop = keembay_wdt_stop ,
. ping = keembay_wdt_ping ,
. set_timeout = keembay_wdt_set_timeout ,
. set_pretimeout = keembay_wdt_set_pretimeout ,
. get_timeleft = keembay_wdt_get_timeleft ,
} ;
static int keembay_wdt_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct keembay_wdt * wdt ;
int ret ;
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 ) ;
/* we do not need to enable the clock as it is enabled by default */
wdt - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( wdt - > clk ) )
return dev_err_probe ( dev , PTR_ERR ( wdt - > clk ) , " Failed to get clock \n " ) ;
wdt - > rate = clk_get_rate ( wdt - > clk ) ;
if ( ! wdt - > rate )
return dev_err_probe ( dev , - EINVAL , " Failed to get clock rate \n " ) ;
wdt - > th_irq = platform_get_irq_byname ( pdev , " threshold " ) ;
if ( wdt - > th_irq < 0 )
return dev_err_probe ( dev , wdt - > th_irq , " Failed to get IRQ for threshold \n " ) ;
ret = devm_request_irq ( dev , wdt - > th_irq , keembay_wdt_th_isr , 0 ,
" keembay-wdt " , wdt ) ;
if ( ret )
return dev_err_probe ( dev , ret , " Failed to request IRQ for threshold \n " ) ;
wdt - > to_irq = platform_get_irq_byname ( pdev , " timeout " ) ;
if ( wdt - > to_irq < 0 )
return dev_err_probe ( dev , wdt - > to_irq , " Failed to get IRQ for timeout \n " ) ;
ret = devm_request_irq ( dev , wdt - > to_irq , keembay_wdt_to_isr , 0 ,
" keembay-wdt " , wdt ) ;
if ( ret )
return dev_err_probe ( dev , ret , " Failed to request IRQ for timeout \n " ) ;
wdt - > wdd . parent = dev ;
wdt - > wdd . info = & keembay_wdt_info ;
wdt - > wdd . ops = & keembay_wdt_ops ;
wdt - > wdd . min_timeout = WDT_LOAD_MIN ;
wdt - > wdd . max_timeout = WDT_LOAD_MAX / wdt - > rate ;
wdt - > wdd . timeout = WDT_TIMEOUT ;
2021-05-17 20:49:45 +03:00
wdt - > wdd . pretimeout = WDT_PRETIMEOUT ;
2020-12-16 21:32:48 +03:00
watchdog_set_drvdata ( & wdt - > wdd , wdt ) ;
watchdog_set_nowayout ( & wdt - > wdd , nowayout ) ;
watchdog_init_timeout ( & wdt - > wdd , timeout , dev ) ;
keembay_wdt_set_timeout ( & wdt - > wdd , wdt - > wdd . timeout ) ;
2021-05-17 20:49:45 +03:00
keembay_wdt_set_pretimeout ( & wdt - > wdd , wdt - > wdd . pretimeout ) ;
2020-12-16 21:32:48 +03:00
ret = devm_watchdog_register_device ( dev , & wdt - > wdd ) ;
if ( ret )
return dev_err_probe ( dev , ret , " Failed to register watchdog device. \n " ) ;
platform_set_drvdata ( pdev , wdt ) ;
dev_info ( dev , " Initial timeout %d sec%s. \n " ,
wdt - > wdd . timeout , nowayout ? " , nowayout " : " " ) ;
return 0 ;
}
static int __maybe_unused keembay_wdt_suspend ( struct device * dev )
{
struct keembay_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdd ) )
return keembay_wdt_stop ( & wdt - > wdd ) ;
return 0 ;
}
static int __maybe_unused keembay_wdt_resume ( struct device * dev )
{
struct keembay_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdd ) )
return keembay_wdt_start ( & wdt - > wdd ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( keembay_wdt_pm_ops , keembay_wdt_suspend ,
keembay_wdt_resume ) ;
static const struct of_device_id keembay_wdt_match [ ] = {
{ . compatible = " intel,keembay-wdt " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , keembay_wdt_match ) ;
static struct platform_driver keembay_wdt_driver = {
2021-05-17 20:49:53 +03:00
. probe = keembay_wdt_probe ,
. driver = {
2020-12-16 21:32:48 +03:00
. name = " keembay_wdt " ,
. of_match_table = keembay_wdt_match ,
. pm = & keembay_wdt_pm_ops ,
} ,
} ;
module_platform_driver ( keembay_wdt_driver ) ;
MODULE_DESCRIPTION ( " Intel Keem Bay SoC watchdog driver " ) ;
MODULE_AUTHOR ( " Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;