2019-05-29 17:17:58 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-09-26 02:51:02 +04:00
/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
*/
2019-09-06 23:54:10 +03:00
# include <linux/bits.h>
2014-09-26 02:51:02 +04:00
# include <linux/clk.h>
2014-09-26 02:51:04 +04:00
# include <linux/delay.h>
2019-09-06 23:54:10 +03:00
# include <linux/interrupt.h>
2014-09-26 02:51:02 +04:00
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
2016-06-29 20:50:01 +03:00
# include <linux/of_device.h>
2014-09-26 02:51:02 +04:00
2016-06-29 20:50:01 +03:00
enum wdt_reg {
WDT_RST ,
WDT_EN ,
WDT_STS ,
2016-06-28 21:35:21 +03:00
WDT_BARK_TIME ,
2016-06-29 20:50:01 +03:00
WDT_BITE_TIME ,
} ;
2019-09-06 23:54:10 +03:00
# define QCOM_WDT_ENABLE BIT(0)
# define QCOM_WDT_ENABLE_IRQ BIT(1)
2016-06-29 20:50:01 +03:00
static const u32 reg_offset_data_apcs_tmr [ ] = {
[ WDT_RST ] = 0x38 ,
[ WDT_EN ] = 0x40 ,
[ WDT_STS ] = 0x44 ,
2016-06-28 21:35:21 +03:00
[ WDT_BARK_TIME ] = 0x4C ,
2016-06-29 20:50:01 +03:00
[ WDT_BITE_TIME ] = 0x5C ,
} ;
static const u32 reg_offset_data_kpss [ ] = {
[ WDT_RST ] = 0x4 ,
[ WDT_EN ] = 0x8 ,
[ WDT_STS ] = 0xC ,
2016-06-28 21:35:21 +03:00
[ WDT_BARK_TIME ] = 0x10 ,
2016-06-29 20:50:01 +03:00
[ WDT_BITE_TIME ] = 0x14 ,
} ;
2014-09-26 02:51:02 +04:00
struct qcom_wdt {
struct watchdog_device wdd ;
unsigned long rate ;
void __iomem * base ;
2016-06-29 20:50:01 +03:00
const u32 * layout ;
2014-09-26 02:51:02 +04:00
} ;
2016-06-29 20:50:01 +03:00
static void __iomem * wdt_addr ( struct qcom_wdt * wdt , enum wdt_reg reg )
{
return wdt - > base + wdt - > layout [ reg ] ;
}
2014-09-26 02:51:02 +04:00
static inline
struct qcom_wdt * to_qcom_wdt ( struct watchdog_device * wdd )
{
return container_of ( wdd , struct qcom_wdt , wdd ) ;
}
2019-09-06 23:54:10 +03:00
static inline int qcom_get_enable ( struct watchdog_device * wdd )
{
int enable = QCOM_WDT_ENABLE ;
if ( wdd - > pretimeout )
enable | = QCOM_WDT_ENABLE_IRQ ;
return enable ;
}
static irqreturn_t qcom_wdt_isr ( int irq , void * arg )
{
struct watchdog_device * wdd = arg ;
watchdog_notify_pretimeout ( wdd ) ;
return IRQ_HANDLED ;
}
2014-09-26 02:51:02 +04:00
static int qcom_wdt_start ( struct watchdog_device * wdd )
{
struct qcom_wdt * wdt = to_qcom_wdt ( wdd ) ;
2019-09-06 23:54:10 +03:00
unsigned int bark = wdd - > timeout - wdd - > pretimeout ;
2014-09-26 02:51:02 +04:00
2016-06-29 20:50:01 +03:00
writel ( 0 , wdt_addr ( wdt , WDT_EN ) ) ;
writel ( 1 , wdt_addr ( wdt , WDT_RST ) ) ;
2019-09-06 23:54:10 +03:00
writel ( bark * wdt - > rate , wdt_addr ( wdt , WDT_BARK_TIME ) ) ;
2016-06-29 20:50:01 +03:00
writel ( wdd - > timeout * wdt - > rate , wdt_addr ( wdt , WDT_BITE_TIME ) ) ;
2019-09-06 23:54:10 +03:00
writel ( qcom_get_enable ( wdd ) , wdt_addr ( wdt , WDT_EN ) ) ;
2014-09-26 02:51:02 +04:00
return 0 ;
}
static int qcom_wdt_stop ( struct watchdog_device * wdd )
{
struct qcom_wdt * wdt = to_qcom_wdt ( wdd ) ;
2016-06-29 20:50:01 +03:00
writel ( 0 , wdt_addr ( wdt , WDT_EN ) ) ;
2014-09-26 02:51:02 +04:00
return 0 ;
}
static int qcom_wdt_ping ( struct watchdog_device * wdd )
{
struct qcom_wdt * wdt = to_qcom_wdt ( wdd ) ;
2016-06-29 20:50:01 +03:00
writel ( 1 , wdt_addr ( wdt , WDT_RST ) ) ;
2014-09-26 02:51:02 +04:00
return 0 ;
}
static int qcom_wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
wdd - > timeout = timeout ;
return qcom_wdt_start ( wdd ) ;
}
2019-09-06 23:54:10 +03:00
static int qcom_wdt_set_pretimeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
wdd - > pretimeout = timeout ;
return qcom_wdt_start ( wdd ) ;
}
2016-02-27 04:32:49 +03:00
static int qcom_wdt_restart ( struct watchdog_device * wdd , unsigned long action ,
void * data )
2014-09-26 02:51:04 +04:00
{
2015-11-16 20:28:09 +03:00
struct qcom_wdt * wdt = to_qcom_wdt ( wdd ) ;
2014-09-26 02:51:04 +04:00
u32 timeout ;
/*
* Trigger watchdog bite :
* Setup BITE_TIME to be 128 ms , and enable WDT .
*/
timeout = 128 * wdt - > rate / 1000 ;
2016-06-29 20:50:01 +03:00
writel ( 0 , wdt_addr ( wdt , WDT_EN ) ) ;
writel ( 1 , wdt_addr ( wdt , WDT_RST ) ) ;
2016-06-28 21:35:21 +03:00
writel ( timeout , wdt_addr ( wdt , WDT_BARK_TIME ) ) ;
2016-06-29 20:50:01 +03:00
writel ( timeout , wdt_addr ( wdt , WDT_BITE_TIME ) ) ;
2019-09-06 23:54:10 +03:00
writel ( QCOM_WDT_ENABLE , wdt_addr ( wdt , WDT_EN ) ) ;
2014-09-26 02:51:04 +04:00
/*
* Actually make sure the above sequence hits hardware before sleeping .
*/
wmb ( ) ;
msleep ( 150 ) ;
2015-11-16 20:28:09 +03:00
return 0 ;
2014-09-26 02:51:04 +04:00
}
2015-11-16 20:28:09 +03:00
static const struct watchdog_ops qcom_wdt_ops = {
. start = qcom_wdt_start ,
. stop = qcom_wdt_stop ,
. ping = qcom_wdt_ping ,
. set_timeout = qcom_wdt_set_timeout ,
2019-09-06 23:54:10 +03:00
. set_pretimeout = qcom_wdt_set_pretimeout ,
2015-11-16 20:28:09 +03:00
. restart = qcom_wdt_restart ,
. owner = THIS_MODULE ,
} ;
static const struct watchdog_info qcom_wdt_info = {
. options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
2016-04-05 03:37:46 +03:00
| WDIOF_SETTIMEOUT
| WDIOF_CARDRESET ,
2015-11-16 20:28:09 +03:00
. identity = KBUILD_MODNAME ,
} ;
2019-09-06 23:54:10 +03:00
static const struct watchdog_info qcom_wdt_pt_info = {
. options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT
| WDIOF_PRETIMEOUT
| WDIOF_CARDRESET ,
. identity = KBUILD_MODNAME ,
} ;
2019-04-09 20:23:50 +03:00
static void qcom_clk_disable_unprepare ( void * data )
{
clk_disable_unprepare ( data ) ;
}
2014-09-26 02:51:02 +04:00
static int qcom_wdt_probe ( struct platform_device * pdev )
{
2019-04-09 20:23:50 +03:00
struct device * dev = & pdev - > dev ;
2014-09-26 02:51:02 +04:00
struct qcom_wdt * wdt ;
struct resource * res ;
2019-04-09 20:23:50 +03:00
struct device_node * np = dev - > of_node ;
2016-06-29 20:50:01 +03:00
const u32 * regs ;
2015-02-21 05:19:34 +03:00
u32 percpu_offset ;
2019-09-06 23:54:10 +03:00
int irq , ret ;
2019-09-06 23:54:11 +03:00
struct clk * clk ;
2014-09-26 02:51:02 +04:00
2019-04-09 20:23:50 +03:00
regs = of_device_get_match_data ( dev ) ;
2016-06-29 20:50:01 +03:00
if ( ! regs ) {
2019-04-09 20:23:50 +03:00
dev_err ( dev , " Unsupported QCOM WDT module \n " ) ;
2016-06-29 20:50:01 +03:00
return - ENODEV ;
}
2019-04-09 20:23:50 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2014-09-26 02:51:02 +04:00
if ( ! wdt )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2017-07-22 19:04:33 +03:00
if ( ! res )
return - ENOMEM ;
2015-02-21 05:19:34 +03:00
/* We use CPU0's DGT for the watchdog */
if ( of_property_read_u32 ( np , " cpu-offset " , & percpu_offset ) )
percpu_offset = 0 ;
res - > start + = percpu_offset ;
res - > end + = percpu_offset ;
2019-04-09 20:23:50 +03:00
wdt - > base = devm_ioremap_resource ( dev , res ) ;
2014-09-26 02:51:02 +04:00
if ( IS_ERR ( wdt - > base ) )
return PTR_ERR ( wdt - > base ) ;
2019-09-06 23:54:11 +03:00
clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( clk ) ) {
2019-04-09 20:23:50 +03:00
dev_err ( dev , " failed to get input clock \n " ) ;
2019-09-06 23:54:11 +03:00
return PTR_ERR ( clk ) ;
2014-09-26 02:51:02 +04:00
}
2019-09-06 23:54:11 +03:00
ret = clk_prepare_enable ( clk ) ;
2014-09-26 02:51:02 +04:00
if ( ret ) {
2019-04-09 20:23:50 +03:00
dev_err ( dev , " failed to setup clock \n " ) ;
2014-09-26 02:51:02 +04:00
return ret ;
}
2019-09-06 23:54:11 +03:00
ret = devm_add_action_or_reset ( dev , qcom_clk_disable_unprepare , clk ) ;
2019-04-09 20:23:50 +03:00
if ( ret )
return ret ;
2014-09-26 02:51:02 +04:00
/*
* We use the clock rate to calculate the max timeout , so ensure it ' s
* not zero to avoid a divide - by - zero exception .
*
* WATCHDOG_CORE assumes units of seconds , if the WDT is clocked such
* that it would bite before a second elapses it ' s usefulness is
* limited . Bail if this is the case .
*/
2019-09-06 23:54:11 +03:00
wdt - > rate = clk_get_rate ( clk ) ;
2014-09-26 02:51:02 +04:00
if ( wdt - > rate = = 0 | |
wdt - > rate > 0x10000000U ) {
2019-04-09 20:23:50 +03:00
dev_err ( dev , " invalid clock rate \n " ) ;
return - EINVAL ;
2014-09-26 02:51:02 +04:00
}
2019-09-06 23:54:10 +03:00
/* check if there is pretimeout support */
2019-12-13 09:49:34 +03:00
irq = platform_get_irq_optional ( pdev , 0 ) ;
2019-09-06 23:54:10 +03:00
if ( irq > 0 ) {
ret = devm_request_irq ( dev , irq , qcom_wdt_isr ,
IRQF_TRIGGER_RISING ,
" wdt_bark " , & wdt - > wdd ) ;
if ( ret )
return ret ;
wdt - > wdd . info = & qcom_wdt_pt_info ;
wdt - > wdd . pretimeout = 1 ;
} else {
if ( irq = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
wdt - > wdd . info = & qcom_wdt_info ;
}
2014-09-26 02:51:02 +04:00
wdt - > wdd . ops = & qcom_wdt_ops ;
wdt - > wdd . min_timeout = 1 ;
wdt - > wdd . max_timeout = 0x10000000U / wdt - > rate ;
2019-04-09 20:23:50 +03:00
wdt - > wdd . parent = dev ;
2016-06-29 20:50:01 +03:00
wdt - > layout = regs ;
2014-09-26 02:51:02 +04:00
2016-11-14 04:11:16 +03:00
if ( readl ( wdt_addr ( wdt , WDT_STS ) ) & 1 )
2016-04-05 03:37:46 +03:00
wdt - > wdd . bootstatus = WDIOF_CARDRESET ;
2014-09-26 02:51:02 +04:00
/*
* If ' timeout - sec ' unspecified in devicetree , assume a 30 second
* default , unless the max timeout is less than 30 seconds , then use
* the max instead .
*/
wdt - > wdd . timeout = min ( wdt - > wdd . max_timeout , 30U ) ;
2019-04-09 20:23:50 +03:00
watchdog_init_timeout ( & wdt - > wdd , 0 , dev ) ;
2014-09-26 02:51:02 +04:00
2019-04-09 20:23:50 +03:00
ret = devm_watchdog_register_device ( dev , & wdt - > wdd ) ;
2019-05-19 00:27:48 +03:00
if ( ret )
2019-04-09 20:23:50 +03:00
return ret ;
2014-09-26 02:51:02 +04:00
platform_set_drvdata ( pdev , wdt ) ;
return 0 ;
}
2019-01-17 18:19:42 +03:00
static int __maybe_unused qcom_wdt_suspend ( struct device * dev )
{
struct qcom_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdd ) )
qcom_wdt_stop ( & wdt - > wdd ) ;
return 0 ;
}
static int __maybe_unused qcom_wdt_resume ( struct device * dev )
{
struct qcom_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdd ) )
qcom_wdt_start ( & wdt - > wdd ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( qcom_wdt_pm_ops , qcom_wdt_suspend , qcom_wdt_resume ) ;
2014-09-26 02:51:02 +04:00
static const struct of_device_id qcom_wdt_of_table [ ] = {
2016-06-29 20:50:01 +03:00
{ . compatible = " qcom,kpss-timer " , . data = reg_offset_data_apcs_tmr } ,
{ . compatible = " qcom,scss-timer " , . data = reg_offset_data_apcs_tmr } ,
{ . compatible = " qcom,kpss-wdt " , . data = reg_offset_data_kpss } ,
2014-09-26 02:51:02 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , qcom_wdt_of_table ) ;
static struct platform_driver qcom_watchdog_driver = {
. probe = qcom_wdt_probe ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = qcom_wdt_of_table ,
2019-01-17 18:19:42 +03:00
. pm = & qcom_wdt_pm_ops ,
2014-09-26 02:51:02 +04:00
} ,
} ;
module_platform_driver ( qcom_watchdog_driver ) ;
MODULE_DESCRIPTION ( " QCOM KPSS Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;