2014-09-26 02:51:02 +04:00
/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/clk.h>
2014-09-26 02:51:04 +04:00
# include <linux/delay.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 ,
} ;
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 ;
struct clk * clk ;
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 ) ;
}
static int qcom_wdt_start ( 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 ) ) ;
writel ( 1 , wdt_addr ( wdt , WDT_RST ) ) ;
2016-06-28 21:35:21 +03:00
writel ( wdd - > timeout * 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 ) ) ;
writel ( 1 , 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 ) ;
}
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 ) ) ;
writel ( 1 , 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 ,
. 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 ,
} ;
2014-09-26 02:51:02 +04:00
static int qcom_wdt_probe ( struct platform_device * pdev )
{
struct qcom_wdt * wdt ;
struct resource * res ;
2015-02-21 05:19:34 +03:00
struct device_node * np = pdev - > 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 ;
2014-09-26 02:51:02 +04:00
int ret ;
2016-06-29 20:50:01 +03:00
regs = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! regs ) {
dev_err ( & pdev - > dev , " Unsupported QCOM WDT module \n " ) ;
return - ENODEV ;
}
2014-09-26 02:51:02 +04:00
wdt = devm_kzalloc ( & pdev - > dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
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 ;
2014-09-26 02:51:02 +04:00
wdt - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( wdt - > base ) )
return PTR_ERR ( wdt - > base ) ;
wdt - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( wdt - > clk ) ) {
dev_err ( & pdev - > dev , " failed to get input clock \n " ) ;
return PTR_ERR ( wdt - > clk ) ;
}
ret = clk_prepare_enable ( wdt - > clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to setup clock \n " ) ;
return ret ;
}
/*
* 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 .
*/
wdt - > rate = clk_get_rate ( wdt - > clk ) ;
if ( wdt - > rate = = 0 | |
wdt - > rate > 0x10000000U ) {
dev_err ( & pdev - > dev , " invalid clock rate \n " ) ;
ret = - EINVAL ;
goto err_clk_unprepare ;
}
wdt - > wdd . info = & qcom_wdt_info ;
wdt - > wdd . ops = & qcom_wdt_ops ;
wdt - > wdd . min_timeout = 1 ;
wdt - > wdd . max_timeout = 0x10000000U / wdt - > rate ;
2015-08-20 11:35:01 +03:00
wdt - > wdd . parent = & pdev - > 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 ) ;
watchdog_init_timeout ( & wdt - > wdd , 0 , & pdev - > dev ) ;
ret = watchdog_register_device ( & wdt - > wdd ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register watchdog \n " ) ;
goto err_clk_unprepare ;
}
platform_set_drvdata ( pdev , wdt ) ;
return 0 ;
err_clk_unprepare :
clk_disable_unprepare ( wdt - > clk ) ;
return ret ;
}
static int qcom_wdt_remove ( struct platform_device * pdev )
{
struct qcom_wdt * wdt = platform_get_drvdata ( pdev ) ;
watchdog_unregister_device ( & wdt - > wdd ) ;
clk_disable_unprepare ( wdt - > clk ) ;
return 0 ;
}
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 ,
. remove = qcom_wdt_remove ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = qcom_wdt_of_table ,
} ,
} ;
module_platform_driver ( qcom_watchdog_driver ) ;
MODULE_DESCRIPTION ( " QCOM KPSS Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;