2019-03-21 05:26:47 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018 - 2019 NXP .
*/
# include <linux/arm-smccc.h>
2019-05-27 10:03:17 +03:00
# include <linux/firmware/imx/sci.h>
2019-03-21 05:26:47 +03:00
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# define DEFAULT_TIMEOUT 60
/*
* Software timer tick implemented in scfw side , support 10 ms to 0xffffffff ms
* in theory , but for normal case , 1 s ~ 128 s is enough , you can change this max
* value in case it ' s not enough .
*/
# define MAX_TIMEOUT 128
# define IMX_SIP_TIMER 0xC2000002
# define IMX_SIP_TIMER_START_WDOG 0x01
# define IMX_SIP_TIMER_STOP_WDOG 0x02
# define IMX_SIP_TIMER_SET_WDOG_ACT 0x03
# define IMX_SIP_TIMER_PING_WDOG 0x04
# define IMX_SIP_TIMER_SET_TIMEOUT_WDOG 0x05
# define IMX_SIP_TIMER_GET_WDOG_STAT 0x06
# define IMX_SIP_TIMER_SET_PRETIME_WDOG 0x07
# define SC_TIMER_WDOG_ACTION_PARTITION 0
2019-05-27 10:03:17 +03:00
# define SC_IRQ_WDOG 1
# define SC_IRQ_GROUP_WDOG 1
2019-03-21 05:26:47 +03:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0000 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2019-05-27 10:03:17 +03:00
struct imx_sc_wdt_device {
struct watchdog_device wdd ;
struct notifier_block wdt_notifier ;
} ;
2019-03-21 05:26:47 +03:00
static int imx_sc_wdt_ping ( struct watchdog_device * wdog )
{
struct arm_smccc_res res ;
arm_smccc_smc ( IMX_SIP_TIMER , IMX_SIP_TIMER_PING_WDOG ,
0 , 0 , 0 , 0 , 0 , 0 , & res ) ;
return 0 ;
}
static int imx_sc_wdt_start ( struct watchdog_device * wdog )
{
struct arm_smccc_res res ;
arm_smccc_smc ( IMX_SIP_TIMER , IMX_SIP_TIMER_START_WDOG ,
0 , 0 , 0 , 0 , 0 , 0 , & res ) ;
if ( res . a0 )
return - EACCES ;
arm_smccc_smc ( IMX_SIP_TIMER , IMX_SIP_TIMER_SET_WDOG_ACT ,
SC_TIMER_WDOG_ACTION_PARTITION ,
0 , 0 , 0 , 0 , 0 , & res ) ;
return res . a0 ? - EACCES : 0 ;
}
static int imx_sc_wdt_stop ( struct watchdog_device * wdog )
{
struct arm_smccc_res res ;
arm_smccc_smc ( IMX_SIP_TIMER , IMX_SIP_TIMER_STOP_WDOG ,
0 , 0 , 0 , 0 , 0 , 0 , & res ) ;
return res . a0 ? - EACCES : 0 ;
}
static int imx_sc_wdt_set_timeout ( struct watchdog_device * wdog ,
unsigned int timeout )
{
struct arm_smccc_res res ;
wdog - > timeout = timeout ;
arm_smccc_smc ( IMX_SIP_TIMER , IMX_SIP_TIMER_SET_TIMEOUT_WDOG ,
timeout * 1000 , 0 , 0 , 0 , 0 , 0 , & res ) ;
return res . a0 ? - EACCES : 0 ;
}
2019-05-27 10:03:17 +03:00
static int imx_sc_wdt_set_pretimeout ( struct watchdog_device * wdog ,
unsigned int pretimeout )
{
struct arm_smccc_res res ;
2019-10-09 10:37:47 +03:00
/*
* SCU firmware calculates pretimeout based on current time
* stamp instead of watchdog timeout stamp , need to convert
* the pretimeout to SCU firmware ' s timeout value .
*/
2019-05-27 10:03:17 +03:00
arm_smccc_smc ( IMX_SIP_TIMER , IMX_SIP_TIMER_SET_PRETIME_WDOG ,
2019-10-09 10:37:47 +03:00
( wdog - > timeout - pretimeout ) * 1000 , 0 , 0 , 0 ,
0 , 0 , & res ) ;
2019-05-27 10:03:17 +03:00
if ( res . a0 )
return - EACCES ;
wdog - > pretimeout = pretimeout ;
return 0 ;
}
static int imx_sc_wdt_notify ( struct notifier_block * nb ,
unsigned long event , void * group )
{
struct imx_sc_wdt_device * imx_sc_wdd =
container_of ( nb ,
struct imx_sc_wdt_device ,
wdt_notifier ) ;
if ( event & SC_IRQ_WDOG & &
* ( u8 * ) group = = SC_IRQ_GROUP_WDOG )
watchdog_notify_pretimeout ( & imx_sc_wdd - > wdd ) ;
return 0 ;
}
static void imx_sc_wdt_action ( void * data )
{
struct notifier_block * wdt_notifier = data ;
imx_scu_irq_unregister_notifier ( wdt_notifier ) ;
imx_scu_irq_group_enable ( SC_IRQ_GROUP_WDOG ,
SC_IRQ_WDOG ,
false ) ;
}
2019-03-21 05:26:47 +03:00
static const struct watchdog_ops imx_sc_wdt_ops = {
. owner = THIS_MODULE ,
. start = imx_sc_wdt_start ,
. stop = imx_sc_wdt_stop ,
. ping = imx_sc_wdt_ping ,
. set_timeout = imx_sc_wdt_set_timeout ,
2019-05-27 10:03:17 +03:00
. set_pretimeout = imx_sc_wdt_set_pretimeout ,
2019-03-21 05:26:47 +03:00
} ;
2019-05-27 10:03:17 +03:00
static struct watchdog_info imx_sc_wdt_info = {
2019-03-21 05:26:47 +03:00
. identity = " i.MX SC watchdog timer " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
2019-05-27 10:03:17 +03:00
WDIOF_MAGICCLOSE ,
2019-03-21 05:26:47 +03:00
} ;
static int imx_sc_wdt_probe ( struct platform_device * pdev )
{
2019-05-27 10:03:17 +03:00
struct imx_sc_wdt_device * imx_sc_wdd ;
struct watchdog_device * wdog ;
2019-04-10 19:27:51 +03:00
struct device * dev = & pdev - > dev ;
2019-03-21 05:26:47 +03:00
int ret ;
2019-04-10 19:27:51 +03:00
imx_sc_wdd = devm_kzalloc ( dev , sizeof ( * imx_sc_wdd ) , GFP_KERNEL ) ;
2019-03-21 05:26:47 +03:00
if ( ! imx_sc_wdd )
return - ENOMEM ;
platform_set_drvdata ( pdev , imx_sc_wdd ) ;
2019-05-27 10:03:17 +03:00
wdog = & imx_sc_wdd - > wdd ;
wdog - > info = & imx_sc_wdt_info ;
wdog - > ops = & imx_sc_wdt_ops ;
wdog - > min_timeout = 1 ;
wdog - > max_timeout = MAX_TIMEOUT ;
wdog - > parent = dev ;
wdog - > timeout = DEFAULT_TIMEOUT ;
watchdog_init_timeout ( wdog , 0 , dev ) ;
2020-04-13 02:01:22 +03:00
ret = imx_sc_wdt_set_timeout ( wdog , wdog - > timeout ) ;
if ( ret )
return ret ;
2019-05-27 10:03:17 +03:00
watchdog_stop_on_reboot ( wdog ) ;
watchdog_stop_on_unregister ( wdog ) ;
ret = imx_scu_irq_group_enable ( SC_IRQ_GROUP_WDOG ,
SC_IRQ_WDOG ,
true ) ;
if ( ret ) {
dev_warn ( dev , " Enable irq failed, pretimeout NOT supported \n " ) ;
2021-04-06 15:12:47 +03:00
goto register_device ;
2019-05-27 10:03:17 +03:00
}
imx_sc_wdd - > wdt_notifier . notifier_call = imx_sc_wdt_notify ;
ret = imx_scu_irq_register_notifier ( & imx_sc_wdd - > wdt_notifier ) ;
if ( ret ) {
imx_scu_irq_group_enable ( SC_IRQ_GROUP_WDOG ,
SC_IRQ_WDOG ,
false ) ;
dev_warn ( dev ,
" Register irq notifier failed, pretimeout NOT supported \n " ) ;
2021-04-06 15:12:47 +03:00
goto register_device ;
2019-05-27 10:03:17 +03:00
}
ret = devm_add_action_or_reset ( dev , imx_sc_wdt_action ,
& imx_sc_wdd - > wdt_notifier ) ;
if ( ! ret )
imx_sc_wdt_info . options | = WDIOF_PRETIMEOUT ;
else
dev_warn ( dev , " Add action failed, pretimeout NOT supported \n " ) ;
2019-03-21 05:26:47 +03:00
2021-04-06 15:12:47 +03:00
register_device :
return devm_watchdog_register_device ( dev , wdog ) ;
2019-03-21 05:26:47 +03:00
}
static int __maybe_unused imx_sc_wdt_suspend ( struct device * dev )
{
2019-05-27 10:03:17 +03:00
struct imx_sc_wdt_device * imx_sc_wdd = dev_get_drvdata ( dev ) ;
2019-03-21 05:26:47 +03:00
2019-05-27 10:03:17 +03:00
if ( watchdog_active ( & imx_sc_wdd - > wdd ) )
imx_sc_wdt_stop ( & imx_sc_wdd - > wdd ) ;
2019-03-21 05:26:47 +03:00
return 0 ;
}
static int __maybe_unused imx_sc_wdt_resume ( struct device * dev )
{
2019-05-27 10:03:17 +03:00
struct imx_sc_wdt_device * imx_sc_wdd = dev_get_drvdata ( dev ) ;
2019-03-21 05:26:47 +03:00
2019-05-27 10:03:17 +03:00
if ( watchdog_active ( & imx_sc_wdd - > wdd ) )
imx_sc_wdt_start ( & imx_sc_wdd - > wdd ) ;
2019-03-21 05:26:47 +03:00
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( imx_sc_wdt_pm_ops ,
imx_sc_wdt_suspend , imx_sc_wdt_resume ) ;
static const struct of_device_id imx_sc_wdt_dt_ids [ ] = {
{ . compatible = " fsl,imx-sc-wdt " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , imx_sc_wdt_dt_ids ) ;
static struct platform_driver imx_sc_wdt_driver = {
. probe = imx_sc_wdt_probe ,
. driver = {
. name = " imx-sc-wdt " ,
. of_match_table = imx_sc_wdt_dt_ids ,
. pm = & imx_sc_wdt_pm_ops ,
} ,
} ;
module_platform_driver ( imx_sc_wdt_driver ) ;
MODULE_AUTHOR ( " Robin Gong <yibin.gong@nxp.com> " ) ;
MODULE_DESCRIPTION ( " NXP i.MX system controller watchdog driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;