2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2011-01-04 23:28:19 +03:00
/*
* Atheros AR71XX / AR724X / AR913X built - in hardware watchdog timer .
*
* Copyright ( C ) 2008 - 2011 Gabor Juhos < juhosg @ openwrt . org >
* Copyright ( C ) 2008 Imre Kaloz < kaloz @ openwrt . org >
*
* This driver was based on : drivers / watchdog / ixp4xx_wdt . c
* Author : Deepak Saxena < dsaxena @ plexity . net >
* Copyright 2004 ( c ) MontaVista , Software , Inc .
*
* which again was based on sa1100 driver ,
* Copyright ( C ) 2000 Oleg Drokin < green @ crimea . edu >
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2011-01-04 23:28:19 +03:00
# include <linux/bitops.h>
2014-04-16 13:34:41 +04:00
# include <linux/delay.h>
2011-01-04 23:28:19 +03:00
# include <linux/errno.h>
# include <linux/fs.h>
2012-12-27 18:38:26 +04:00
# include <linux/io.h>
2011-01-04 23:28:19 +03:00
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/platform_device.h>
# include <linux/types.h>
# include <linux/watchdog.h>
# include <linux/clk.h>
# include <linux/err.h>
2013-02-02 13:34:54 +04:00
# include <linux/of.h>
# include <linux/of_platform.h>
2016-09-05 18:35:50 +03:00
# include <linux/uaccess.h>
2011-01-04 23:28:19 +03:00
# define DRIVER_NAME "ath79-wdt"
# define WDT_TIMEOUT 15 /* seconds */
2012-12-27 18:38:26 +04:00
# define WDOG_REG_CTRL 0x00
# define WDOG_REG_TIMER 0x04
2011-01-04 23:28:19 +03:00
# define WDOG_CTRL_LAST_RESET BIT(31)
# define WDOG_CTRL_ACTION_MASK 3
# define WDOG_CTRL_ACTION_NONE 0 /* no action */
# define WDOG_CTRL_ACTION_GPI 1 /* general purpose interrupt */
# define WDOG_CTRL_ACTION_NMI 2 /* NMI */
# define WDOG_CTRL_ACTION_FCR 3 /* full chip reset */
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2011-01-04 23:28:19 +03:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static int timeout = WDT_TIMEOUT ;
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout in seconds "
" (default= " __MODULE_STRING ( WDT_TIMEOUT ) " s) " ) ;
static unsigned long wdt_flags ;
# define WDT_FLAGS_BUSY 0
# define WDT_FLAGS_EXPECT_CLOSE 1
static struct clk * wdt_clk ;
static unsigned long wdt_freq ;
static int boot_status ;
static int max_timeout ;
2012-12-27 18:38:26 +04:00
static void __iomem * wdt_base ;
static inline void ath79_wdt_wr ( unsigned reg , u32 val )
{
iowrite32 ( val , wdt_base + reg ) ;
}
static inline u32 ath79_wdt_rr ( unsigned reg )
{
return ioread32 ( wdt_base + reg ) ;
}
2011-01-04 23:28:19 +03:00
static inline void ath79_wdt_keepalive ( void )
{
2012-12-27 18:38:26 +04:00
ath79_wdt_wr ( WDOG_REG_TIMER , wdt_freq * timeout ) ;
2011-12-23 22:25:42 +04:00
/* flush write */
2012-12-27 18:38:26 +04:00
ath79_wdt_rr ( WDOG_REG_TIMER ) ;
2011-01-04 23:28:19 +03:00
}
static inline void ath79_wdt_enable ( void )
{
ath79_wdt_keepalive ( ) ;
2014-04-16 13:34:41 +04:00
/*
* Updating the TIMER register requires a few microseconds
* on the AR934x SoCs at least . Use a small delay to ensure
* that the TIMER register is updated within the hardware
* before enabling the watchdog .
*/
udelay ( 2 ) ;
2012-12-27 18:38:26 +04:00
ath79_wdt_wr ( WDOG_REG_CTRL , WDOG_CTRL_ACTION_FCR ) ;
2011-12-23 22:25:42 +04:00
/* flush write */
2012-12-27 18:38:26 +04:00
ath79_wdt_rr ( WDOG_REG_CTRL ) ;
2011-01-04 23:28:19 +03:00
}
static inline void ath79_wdt_disable ( void )
{
2012-12-27 18:38:26 +04:00
ath79_wdt_wr ( WDOG_REG_CTRL , WDOG_CTRL_ACTION_NONE ) ;
2011-12-23 22:25:42 +04:00
/* flush write */
2012-12-27 18:38:26 +04:00
ath79_wdt_rr ( WDOG_REG_CTRL ) ;
2011-01-04 23:28:19 +03:00
}
static int ath79_wdt_set_timeout ( int val )
{
if ( val < 1 | | val > max_timeout )
return - EINVAL ;
timeout = val ;
ath79_wdt_keepalive ( ) ;
return 0 ;
}
static int ath79_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( WDT_FLAGS_BUSY , & wdt_flags ) )
return - EBUSY ;
clear_bit ( WDT_FLAGS_EXPECT_CLOSE , & wdt_flags ) ;
ath79_wdt_enable ( ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2011-01-04 23:28:19 +03:00
}
static int ath79_wdt_release ( struct inode * inode , struct file * file )
{
if ( test_bit ( WDT_FLAGS_EXPECT_CLOSE , & wdt_flags ) )
ath79_wdt_disable ( ) ;
else {
2012-02-16 03:06:19 +04:00
pr_crit ( " device closed unexpectedly, watchdog timer will not stop! \n " ) ;
2011-01-04 23:28:19 +03:00
ath79_wdt_keepalive ( ) ;
}
clear_bit ( WDT_FLAGS_BUSY , & wdt_flags ) ;
clear_bit ( WDT_FLAGS_EXPECT_CLOSE , & wdt_flags ) ;
return 0 ;
}
static ssize_t ath79_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
clear_bit ( WDT_FLAGS_EXPECT_CLOSE , & wdt_flags ) ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
set_bit ( WDT_FLAGS_EXPECT_CLOSE ,
& wdt_flags ) ;
}
}
ath79_wdt_keepalive ( ) ;
}
return len ;
}
static const struct watchdog_info ath79_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE | WDIOF_CARDRESET ,
. firmware_version = 0 ,
. identity = " ATH79 watchdog " ,
} ;
static long ath79_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int err ;
int t ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
err = copy_to_user ( argp , & ath79_wdt_info ,
sizeof ( ath79_wdt_info ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
err = put_user ( 0 , p ) ;
break ;
case WDIOC_GETBOOTSTATUS :
err = put_user ( boot_status , p ) ;
break ;
case WDIOC_KEEPALIVE :
ath79_wdt_keepalive ( ) ;
err = 0 ;
break ;
case WDIOC_SETTIMEOUT :
err = get_user ( t , p ) ;
if ( err )
break ;
err = ath79_wdt_set_timeout ( t ) ;
if ( err )
break ;
/* fallthrough */
case WDIOC_GETTIMEOUT :
err = put_user ( timeout , p ) ;
break ;
default :
err = - ENOTTY ;
break ;
}
return err ;
}
static const struct file_operations ath79_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = ath79_wdt_write ,
. unlocked_ioctl = ath79_wdt_ioctl ,
. open = ath79_wdt_open ,
. release = ath79_wdt_release ,
} ;
static struct miscdevice ath79_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & ath79_wdt_fops ,
} ;
2012-11-19 22:21:41 +04:00
static int ath79_wdt_probe ( struct platform_device * pdev )
2011-01-04 23:28:19 +03:00
{
u32 ctrl ;
int err ;
2012-12-27 18:38:26 +04:00
if ( wdt_base )
return - EBUSY ;
watchdog: Convert to use devm_platform_ioremap_resource
Use devm_platform_ioremap_resource to reduce source code size,
improve readability, and reduce the likelyhood of bugs.
The conversion was done automatically with coccinelle using the
following semantic patch.
@r@
identifier res, pdev;
expression a;
expression index;
expression e;
@@
<+...
- res = platform_get_resource(pdev, IORESOURCE_MEM, index);
- a = devm_ioremap_resource(e, res);
+ a = devm_platform_ioremap_resource(pdev, index);
...+>
@depends on r@
identifier r.res;
@@
- struct resource *res;
... when != res
@@
identifier res, pdev;
expression index;
expression a;
@@
- struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, index);
- a = devm_ioremap_resource(&pdev->dev, res);
+ a = devm_platform_ioremap_resource(pdev, index);
Cc: Joel Stanley <joel@jms.id.au>
Cc: Nicolas Ferre <nicolas.ferre@microchip.com>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Baruch Siach <baruch@tkos.co.il>
Cc: Keguang Zhang <keguang.zhang@gmail.com>
Cc: Vladimir Zapolskiy <vz@mleia.com>
Cc: Kevin Hilman <khilman@baylibre.com>
Cc: Matthias Brugger <matthias.bgg@gmail.com>
Cc: Avi Fishman <avifishman70@gmail.com>
Cc: Nancy Yuen <yuenn@google.com>
Cc: Brendan Higgins <brendanhiggins@google.com>
Cc: Wan ZongShun <mcuos.com@gmail.com>
Cc: Michal Simek <michal.simek@xilinx.com>
Cc: Sylvain Lemieux <slemieux.tyco@gmail.com>
Cc: Kukjin Kim <kgene@kernel.org>
Cc: Barry Song <baohua@kernel.org>
Cc: Orson Zhai <orsonzhai@gmail.com>
Cc: Patrice Chotard <patrice.chotard@st.com>
Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Cc: Maxime Ripard <maxime.ripard@bootlin.com>
Cc: Chen-Yu Tsai <wens@csie.org>
Cc: Marc Gonzalez <marc.w.gonzalez@free.fr>
Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: Shawn Guo <shawnguo@kernel.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Tested-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Acked-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
Acked-by: Michal Simek <michal.simek@xilinx.com> (cadence/xilinx wdts)
Acked-by: Thierry Reding <treding@nvidia.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Acked-by: Patrice Chotard <patrice.chotard@st.com>
Acked-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
2019-04-02 22:01:53 +03:00
wdt_base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2013-03-04 09:06:41 +04:00
if ( IS_ERR ( wdt_base ) )
return PTR_ERR ( wdt_base ) ;
2012-12-27 18:38:26 +04:00
2012-12-27 18:38:24 +04:00
wdt_clk = devm_clk_get ( & pdev - > dev , " wdt " ) ;
2011-01-04 23:28:19 +03:00
if ( IS_ERR ( wdt_clk ) )
return PTR_ERR ( wdt_clk ) ;
2014-04-16 13:35:50 +04:00
err = clk_prepare_enable ( wdt_clk ) ;
2011-01-04 23:28:19 +03:00
if ( err )
2012-12-27 18:38:24 +04:00
return err ;
2011-01-04 23:28:19 +03:00
wdt_freq = clk_get_rate ( wdt_clk ) ;
if ( ! wdt_freq ) {
err = - EINVAL ;
goto err_clk_disable ;
}
max_timeout = ( 0xfffffffful / wdt_freq ) ;
if ( timeout < 1 | | timeout > max_timeout ) {
timeout = max_timeout ;
dev_info ( & pdev - > dev ,
" timeout value must be 0 < timeout < %d, using %d \n " ,
max_timeout , timeout ) ;
}
2012-12-27 18:38:26 +04:00
ctrl = ath79_wdt_rr ( WDOG_REG_CTRL ) ;
2011-01-04 23:28:19 +03:00
boot_status = ( ctrl & WDOG_CTRL_LAST_RESET ) ? WDIOF_CARDRESET : 0 ;
err = misc_register ( & ath79_wdt_miscdev ) ;
if ( err ) {
dev_err ( & pdev - > dev ,
" unable to register misc device, err=%d \n " , err ) ;
goto err_clk_disable ;
}
return 0 ;
err_clk_disable :
2014-04-16 13:35:50 +04:00
clk_disable_unprepare ( wdt_clk ) ;
2011-01-04 23:28:19 +03:00
return err ;
}
2012-11-19 22:26:24 +04:00
static int ath79_wdt_remove ( struct platform_device * pdev )
2011-01-04 23:28:19 +03:00
{
misc_deregister ( & ath79_wdt_miscdev ) ;
2014-04-16 13:35:50 +04:00
clk_disable_unprepare ( wdt_clk ) ;
2011-01-04 23:28:19 +03:00
return 0 ;
}
static void ath97_wdt_shutdown ( struct platform_device * pdev )
{
ath79_wdt_disable ( ) ;
}
2013-02-02 13:34:54 +04:00
# ifdef CONFIG_OF
static const struct of_device_id ath79_wdt_match [ ] = {
{ . compatible = " qca,ar7130-wdt " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ath79_wdt_match ) ;
# endif
2011-01-04 23:28:19 +03:00
static struct platform_driver ath79_wdt_driver = {
2012-08-14 17:35:19 +04:00
. probe = ath79_wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = ath79_wdt_remove ,
2011-01-04 23:28:19 +03:00
. shutdown = ath97_wdt_shutdown ,
. driver = {
. name = DRIVER_NAME ,
2013-02-02 13:34:54 +04:00
. of_match_table = of_match_ptr ( ath79_wdt_match ) ,
2011-01-04 23:28:19 +03:00
} ,
} ;
2012-08-14 17:35:19 +04:00
module_platform_driver ( ath79_wdt_driver ) ;
2011-01-04 23:28:19 +03:00
MODULE_DESCRIPTION ( " Atheros AR71XX/AR724X/AR913X hardware watchdog driver " ) ;
MODULE_AUTHOR ( " Gabor Juhos <juhosg@openwrt.org " ) ;
MODULE_AUTHOR ( " Imre Kaloz <kaloz@openwrt.org " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;