2019-05-28 20:10:09 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-04-16 00:06:05 +04:00
/*
* intel - mid_wdt : generic Intel MID SCU watchdog driver
*
* Platforms supported so far :
* - Merrifield only
*
* Copyright ( C ) 2014 Intel Corporation . All rights reserved .
* Contact : David Cohen < david . a . cohen @ linux . intel . com >
*/
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/nmi.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# include <linux/platform_data/intel-mid_wdt.h>
# include <asm/intel_scu_ipc.h>
# include <asm/intel-mid.h>
# define IPC_WATCHDOG 0xf8
# define MID_WDT_PRETIMEOUT 15
# define MID_WDT_TIMEOUT_MIN (1 + MID_WDT_PRETIMEOUT)
# define MID_WDT_TIMEOUT_MAX 170
# define MID_WDT_DEFAULT_TIMEOUT 90
/* SCU watchdog messages */
enum {
SCU_WATCHDOG_START = 0 ,
SCU_WATCHDOG_STOP ,
SCU_WATCHDOG_KEEPALIVE ,
} ;
2020-04-16 11:15:38 +03:00
struct mid_wdt {
struct watchdog_device wd ;
struct device * dev ;
struct intel_scu_ipc_dev * scu ;
} ;
static inline int
wdt_command ( struct mid_wdt * mid , int sub , const void * in , size_t inlen , size_t size )
2014-04-16 00:06:05 +04:00
{
2020-04-16 11:15:38 +03:00
struct intel_scu_ipc_dev * scu = mid - > scu ;
return intel_scu_ipc_dev_command_with_size ( scu , IPC_WATCHDOG , sub , in ,
inlen , size , NULL , 0 ) ;
2014-04-16 00:06:05 +04:00
}
static int wdt_start ( struct watchdog_device * wd )
{
2020-04-16 11:15:38 +03:00
struct mid_wdt * mid = watchdog_get_drvdata ( wd ) ;
2014-04-16 00:06:05 +04:00
int ret , in_size ;
int timeout = wd - > timeout ;
struct ipc_wd_start {
u32 pretimeout ;
u32 timeout ;
} ipc_wd_start = { timeout - MID_WDT_PRETIMEOUT , timeout } ;
/*
2020-04-16 11:15:38 +03:00
* SCU expects the input size for watchdog IPC to be 2 which is the
* size of the structure in dwords . SCU IPC normally takes bytes
* but this is a special case where we specify size to be different
* than inlen .
2014-04-16 00:06:05 +04:00
*/
in_size = DIV_ROUND_UP ( sizeof ( ipc_wd_start ) , 4 ) ;
2020-04-16 11:15:38 +03:00
ret = wdt_command ( mid , SCU_WATCHDOG_START , & ipc_wd_start ,
sizeof ( ipc_wd_start ) , in_size ) ;
2016-11-18 18:24:41 +03:00
if ( ret )
2020-04-16 11:15:38 +03:00
dev_crit ( mid - > dev , " error starting watchdog: %d \n " , ret ) ;
2014-04-16 00:06:05 +04:00
return ret ;
}
static int wdt_ping ( struct watchdog_device * wd )
{
2020-04-16 11:15:38 +03:00
struct mid_wdt * mid = watchdog_get_drvdata ( wd ) ;
2014-04-16 00:06:05 +04:00
int ret ;
2020-04-16 11:15:38 +03:00
ret = wdt_command ( mid , SCU_WATCHDOG_KEEPALIVE , NULL , 0 , 0 ) ;
2016-11-18 18:24:41 +03:00
if ( ret )
2020-04-16 11:15:38 +03:00
dev_crit ( mid - > dev , " Error executing keepalive: %d \n " , ret ) ;
2014-04-16 00:06:05 +04:00
return ret ;
}
static int wdt_stop ( struct watchdog_device * wd )
{
2020-04-16 11:15:38 +03:00
struct mid_wdt * mid = watchdog_get_drvdata ( wd ) ;
2014-04-16 00:06:05 +04:00
int ret ;
2020-04-16 11:15:38 +03:00
ret = wdt_command ( mid , SCU_WATCHDOG_STOP , NULL , 0 , 0 ) ;
2016-11-18 18:24:41 +03:00
if ( ret )
2020-04-16 11:15:38 +03:00
dev_crit ( mid - > dev , " Error stopping watchdog: %d \n " , ret ) ;
2014-04-16 00:06:05 +04:00
return ret ;
}
static irqreturn_t mid_wdt_irq ( int irq , void * dev_id )
{
panic ( " Kernel Watchdog " ) ;
/* This code should not be reached */
return IRQ_HANDLED ;
}
static const struct watchdog_info mid_wdt_info = {
. identity = " Intel MID SCU watchdog " ,
2015-10-06 02:49:58 +03:00
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE ,
2014-04-16 00:06:05 +04:00
} ;
static const struct watchdog_ops mid_wdt_ops = {
. owner = THIS_MODULE ,
. start = wdt_start ,
. stop = wdt_stop ,
. ping = wdt_ping ,
} ;
static int mid_wdt_probe ( struct platform_device * pdev )
{
2019-04-08 22:38:43 +03:00
struct device * dev = & pdev - > dev ;
2014-04-16 00:06:05 +04:00
struct watchdog_device * wdt_dev ;
2019-04-08 22:38:43 +03:00
struct intel_mid_wdt_pdata * pdata = dev - > platform_data ;
2020-04-16 11:15:38 +03:00
struct mid_wdt * mid ;
2014-04-16 00:06:05 +04:00
int ret ;
if ( ! pdata ) {
2019-04-08 22:38:43 +03:00
dev_err ( dev , " missing platform data \n " ) ;
2014-04-16 00:06:05 +04:00
return - EINVAL ;
}
if ( pdata - > probe ) {
ret = pdata - > probe ( pdev ) ;
if ( ret )
return ret ;
}
2020-04-16 11:15:38 +03:00
mid = devm_kzalloc ( dev , sizeof ( * mid ) , GFP_KERNEL ) ;
if ( ! mid )
2014-04-16 00:06:05 +04:00
return - ENOMEM ;
2020-04-16 11:15:38 +03:00
mid - > dev = dev ;
wdt_dev = & mid - > wd ;
2014-04-16 00:06:05 +04:00
wdt_dev - > info = & mid_wdt_info ;
wdt_dev - > ops = & mid_wdt_ops ;
wdt_dev - > min_timeout = MID_WDT_TIMEOUT_MIN ;
wdt_dev - > max_timeout = MID_WDT_TIMEOUT_MAX ;
wdt_dev - > timeout = MID_WDT_DEFAULT_TIMEOUT ;
2019-04-08 22:38:43 +03:00
wdt_dev - > parent = dev ;
2014-04-16 00:06:05 +04:00
2019-09-24 17:31:16 +03:00
watchdog_set_nowayout ( wdt_dev , WATCHDOG_NOWAYOUT ) ;
2020-04-16 11:15:38 +03:00
watchdog_set_drvdata ( wdt_dev , mid ) ;
2014-04-16 00:06:05 +04:00
2020-10-23 19:33:02 +03:00
mid - > scu = devm_intel_scu_ipc_dev_get ( dev ) ;
if ( ! mid - > scu )
return - EPROBE_DEFER ;
2019-04-08 22:38:43 +03:00
ret = devm_request_irq ( dev , pdata - > irq , mid_wdt_irq ,
2014-04-16 00:06:05 +04:00
IRQF_SHARED | IRQF_NO_SUSPEND , " watchdog " ,
wdt_dev ) ;
if ( ret ) {
2019-04-08 22:38:43 +03:00
dev_err ( dev , " error requesting warning irq %d \n " , pdata - > irq ) ;
2014-04-16 00:06:05 +04:00
return ret ;
}
2017-03-11 01:22:17 +03:00
/*
* The firmware followed by U - Boot leaves the watchdog running
* with the default threshold which may vary . When we get here
* we should make a decision to prevent any side effects before
* user space daemon will take care of it . The best option ,
* taking into consideration that there is no way to read values
* back from hardware , is to enforce watchdog being run with
* deterministic values .
*/
ret = wdt_start ( wdt_dev ) ;
if ( ret )
return ret ;
/* Make sure the watchdog is serviced */
set_bit ( WDOG_HW_RUNNING , & wdt_dev - > status ) ;
2016-11-18 17:50:02 +03:00
2019-04-08 22:38:43 +03:00
ret = devm_watchdog_register_device ( dev , wdt_dev ) ;
2019-05-19 00:27:33 +03:00
if ( ret )
2014-04-16 00:06:05 +04:00
return ret ;
2019-04-08 22:38:43 +03:00
dev_info ( dev , " Intel MID watchdog device probed \n " ) ;
2014-04-16 00:06:05 +04:00
return 0 ;
}
static struct platform_driver mid_wdt_driver = {
. probe = mid_wdt_probe ,
. driver = {
. name = " intel_mid_wdt " ,
} ,
} ;
module_platform_driver ( mid_wdt_driver ) ;
MODULE_AUTHOR ( " David Cohen <david.a.cohen@linux.intel.com> " ) ;
MODULE_DESCRIPTION ( " Watchdog Driver for Intel MID platform " ) ;
MODULE_LICENSE ( " GPL " ) ;