2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2008-02-25 15:11:31 +03:00
/*
* IDT Interprise 79 RC32434 watchdog driver
*
* Copyright ( C ) 2006 , Ondrej Zajicek < santiago @ crfreenet . org >
* Copyright ( C ) 2008 , Florian Fainelli < florian @ openwrt . org >
*
* based on
* SoftDog 0.05 : A Software Watchdog Device
*
2008-10-27 18:17:56 +03:00
* ( c ) Copyright 1996 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
2008-02-25 15:11:31 +03:00
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2009-02-08 18:44:42 +03:00
# include <linux/module.h> /* For module specific items */
# include <linux/moduleparam.h> /* For new moduleparam's */
# include <linux/types.h> /* For standard types (like size_t) */
# include <linux/errno.h> /* For the -ENODEV/... values */
# include <linux/kernel.h> /* For printk/panic/... */
# include <linux/fs.h> /* For file operations */
2013-10-21 19:38:49 +04:00
# include <linux/miscdevice.h> /* For struct miscdevice */
2009-02-08 18:44:42 +03:00
# include <linux/watchdog.h> /* For the watchdog specific items */
# include <linux/init.h> /* For __init/__exit/... */
# include <linux/platform_device.h> /* For platform_driver framework */
2009-02-23 16:08:36 +03:00
# include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
2009-02-08 18:44:42 +03:00
# include <linux/uaccess.h> /* For copy_to_user/put_user/... */
2013-04-30 09:01:41 +04:00
# include <linux/io.h> /* For devm_ioremap_nocache */
2009-02-08 18:44:42 +03:00
# include <asm/mach-rc32434/integ.h> /* For the Watchdog registers */
2009-02-23 16:08:37 +03:00
# define VERSION "1.0"
2008-02-25 15:11:31 +03:00
static struct {
unsigned long inuse ;
2009-02-23 16:08:36 +03:00
spinlock_t io_lock ;
2008-02-25 15:11:31 +03:00
} rc32434_wdt_device ;
static struct integ __iomem * wdt_reg ;
static int expect_close ;
2009-02-08 18:44:42 +03:00
/* Board internal clock speed in Hz,
* the watchdog timer ticks at . */
extern unsigned int idt_cpu_freq ;
/* translate wtcompare value to seconds and vice versa */
# define WTCOMP2SEC(x) (x / idt_cpu_freq)
# define SEC2WTCOMP(x) (x * idt_cpu_freq)
/* Use a default timeout of 20s. This should be
* safe for CPU clock speeds up to 400 MHz , as
* ( ( 2 ^ 32 ) - 1 ) / ( 400 MHz / 2 ) = 21 s . */
# define WATCHDOG_TIMEOUT 20
static int timeout = WATCHDOG_TIMEOUT ;
2009-02-08 18:44:42 +03:00
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout value, in seconds (default= "
2009-12-02 15:21:23 +03:00
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " ) " ) ;
2008-02-25 15:11:31 +03:00
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-02-25 15:11:31 +03:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2009-02-08 18:44:42 +03:00
/* apply or and nand masks to data read from addr and write back */
# define SET_BITS(addr, or, nand) \
writel ( ( readl ( & addr ) | or ) & ~ nand , & addr )
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
static int rc32434_wdt_set ( int new_timeout )
{
int max_to = WTCOMP2SEC ( ( u32 ) - 1 ) ;
if ( new_timeout < 0 | | new_timeout > max_to ) {
2012-02-16 03:06:19 +04:00
pr_err ( " timeout value must be between 0 and %d \n " , max_to ) ;
2009-02-08 18:44:42 +03:00
return - EINVAL ;
}
timeout = new_timeout ;
2009-02-23 16:08:36 +03:00
spin_lock ( & rc32434_wdt_device . io_lock ) ;
2009-02-08 18:44:42 +03:00
writel ( SEC2WTCOMP ( timeout ) , & wdt_reg - > wtcompare ) ;
2009-02-23 16:08:36 +03:00
spin_unlock ( & rc32434_wdt_device . io_lock ) ;
2009-02-08 18:44:42 +03:00
return 0 ;
}
2008-02-25 15:11:31 +03:00
static void rc32434_wdt_start ( void )
{
2009-02-08 18:44:42 +03:00
u32 or , nand ;
2008-02-25 15:11:31 +03:00
2009-02-23 16:08:36 +03:00
spin_lock ( & rc32434_wdt_device . io_lock ) ;
2009-02-08 18:44:42 +03:00
/* zero the counter before enabling */
writel ( 0 , & wdt_reg - > wtcount ) ;
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
/* don't generate a non-maskable interrupt,
* do a warm reset instead */
nand = 1 < < RC32434_ERR_WNE ;
or = 1 < < RC32434_ERR_WRE ;
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
/* reset the ERRCS timeout bit in case it's set */
nand | = 1 < < RC32434_ERR_WTO ;
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
SET_BITS ( wdt_reg - > errcs , or , nand ) ;
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
/* set the timeout (either default or based on module param) */
rc32434_wdt_set ( timeout ) ;
2009-02-08 18:44:42 +03:00
/* reset WTC timeout bit and enable WDT */
nand = 1 < < RC32434_WTC_TO ;
or = 1 < < RC32434_WTC_EN ;
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
SET_BITS ( wdt_reg - > wtc , or , nand ) ;
2009-02-08 18:44:42 +03:00
2009-02-23 16:08:36 +03:00
spin_unlock ( & rc32434_wdt_device . io_lock ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Started watchdog timer \n " ) ;
2009-02-08 18:44:42 +03:00
}
2008-02-25 15:11:31 +03:00
2009-02-08 18:44:42 +03:00
static void rc32434_wdt_stop ( void )
{
2009-02-23 16:08:36 +03:00
spin_lock ( & rc32434_wdt_device . io_lock ) ;
2009-02-08 18:44:42 +03:00
/* Disable WDT */
SET_BITS ( wdt_reg - > wtc , 0 , 1 < < RC32434_WTC_EN ) ;
2009-02-08 18:44:42 +03:00
2009-02-23 16:08:36 +03:00
spin_unlock ( & rc32434_wdt_device . io_lock ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Stopped watchdog timer \n " ) ;
2008-02-25 15:11:31 +03:00
}
2009-02-08 18:44:42 +03:00
static void rc32434_wdt_ping ( void )
2008-02-25 15:11:31 +03:00
{
2009-02-23 16:08:36 +03:00
spin_lock ( & rc32434_wdt_device . io_lock ) ;
2008-02-25 15:11:31 +03:00
writel ( 0 , & wdt_reg - > wtcount ) ;
2009-02-23 16:08:36 +03:00
spin_unlock ( & rc32434_wdt_device . io_lock ) ;
2008-02-25 15:11:31 +03:00
}
static int rc32434_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & rc32434_wdt_device . inuse ) )
return - EBUSY ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
2009-02-08 18:44:42 +03:00
rc32434_wdt_start ( ) ;
rc32434_wdt_ping ( ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2008-02-25 15:11:31 +03:00
}
static int rc32434_wdt_release ( struct inode * inode , struct file * file )
{
2009-02-08 18:44:42 +03:00
if ( expect_close = = 42 ) {
2008-02-25 15:11:31 +03:00
rc32434_wdt_stop ( ) ;
module_put ( THIS_MODULE ) ;
2009-02-08 18:44:42 +03:00
} else {
2012-02-16 03:06:19 +04:00
pr_crit ( " device closed unexpectedly. WDT will not stop! \n " ) ;
2009-02-08 18:44:42 +03:00
rc32434_wdt_ping ( ) ;
}
2008-02-25 15:11:31 +03:00
clear_bit ( 0 , & rc32434_wdt_device . inuse ) ;
return 0 ;
}
static ssize_t rc32434_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
expect_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
2009-02-08 18:44:42 +03:00
expect_close = 42 ;
2008-02-25 15:11:31 +03:00
}
}
2009-02-08 18:44:42 +03:00
rc32434_wdt_ping ( ) ;
2008-02-25 15:11:31 +03:00
return len ;
}
return 0 ;
}
2008-09-18 16:26:15 +04:00
static long rc32434_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2008-02-25 15:11:31 +03:00
{
void __user * argp = ( void __user * ) arg ;
int new_timeout ;
unsigned int value ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2008-02-25 15:11:31 +03:00
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
. identity = " RC32434_WDT Watchdog " ,
} ;
switch ( cmd ) {
2009-02-08 18:44:42 +03:00
case WDIOC_GETSUPPORT :
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
return - EFAULT ;
2008-02-25 15:11:31 +03:00
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
2009-02-08 18:44:42 +03:00
value = 0 ;
2008-02-25 15:11:31 +03:00
if ( copy_to_user ( argp , & value , sizeof ( int ) ) )
return - EFAULT ;
break ;
case WDIOC_SETOPTIONS :
if ( copy_from_user ( & value , argp , sizeof ( int ) ) )
return - EFAULT ;
switch ( value ) {
case WDIOS_ENABLECARD :
rc32434_wdt_start ( ) ;
break ;
case WDIOS_DISABLECARD :
rc32434_wdt_stop ( ) ;
2009-02-08 18:44:42 +03:00
break ;
2008-02-25 15:11:31 +03:00
default :
return - EINVAL ;
}
break ;
2009-02-08 18:44:42 +03:00
case WDIOC_KEEPALIVE :
rc32434_wdt_ping ( ) ;
break ;
2008-02-25 15:11:31 +03:00
case WDIOC_SETTIMEOUT :
if ( copy_from_user ( & new_timeout , argp , sizeof ( int ) ) )
return - EFAULT ;
2009-02-08 18:44:42 +03:00
if ( rc32434_wdt_set ( new_timeout ) )
2008-02-25 15:11:31 +03:00
return - EINVAL ;
2009-02-08 18:44:42 +03:00
/* Fall through */
2008-02-25 15:11:31 +03:00
case WDIOC_GETTIMEOUT :
2016-02-28 18:44:09 +03:00
return copy_to_user ( argp , & timeout , sizeof ( int ) ) ? - EFAULT : 0 ;
2008-02-25 15:11:31 +03:00
default :
return - ENOTTY ;
}
return 0 ;
}
2009-03-18 11:18:43 +03:00
static const struct file_operations rc32434_wdt_fops = {
2008-02-25 15:11:31 +03:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = rc32434_wdt_write ,
2008-09-18 16:26:15 +04:00
. unlocked_ioctl = rc32434_wdt_ioctl ,
2008-02-25 15:11:31 +03:00
. open = rc32434_wdt_open ,
. release = rc32434_wdt_release ,
} ;
static struct miscdevice rc32434_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & rc32434_wdt_fops ,
} ;
2012-11-19 22:21:41 +04:00
static int rc32434_wdt_probe ( struct platform_device * pdev )
2008-02-25 15:11:31 +03:00
{
int ret ;
struct resource * r ;
2009-02-08 18:44:42 +03:00
r = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " rb532_wdt_res " ) ;
2008-02-25 15:11:31 +03:00
if ( ! r ) {
2012-02-16 03:06:19 +04:00
pr_err ( " failed to retrieve resources \n " ) ;
2008-02-25 15:11:31 +03:00
return - ENODEV ;
}
2013-04-30 09:01:41 +04:00
wdt_reg = devm_ioremap_nocache ( & pdev - > dev , r - > start , resource_size ( r ) ) ;
2008-02-25 15:11:31 +03:00
if ( ! wdt_reg ) {
2012-02-16 03:06:19 +04:00
pr_err ( " failed to remap I/O resources \n " ) ;
2008-02-25 15:11:31 +03:00
return - ENXIO ;
}
2009-02-23 16:08:36 +03:00
spin_lock_init ( & rc32434_wdt_device . io_lock ) ;
2009-02-23 16:08:37 +03:00
/* Make sure the watchdog is not running */
rc32434_wdt_stop ( ) ;
2009-02-08 18:44:42 +03:00
/* Check that the heartbeat value is within it's range;
* if not reset to the default */
if ( rc32434_wdt_set ( timeout ) ) {
rc32434_wdt_set ( WATCHDOG_TIMEOUT ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " timeout value must be between 0 and %d \n " ,
2009-02-08 18:44:42 +03:00
WTCOMP2SEC ( ( u32 ) - 1 ) ) ;
}
2008-02-25 15:11:31 +03:00
ret = misc_register ( & rc32434_wdt_miscdev ) ;
if ( ret < 0 ) {
2012-02-16 03:06:19 +04:00
pr_err ( " failed to register watchdog device \n " ) ;
2013-04-30 09:01:41 +04:00
return ret ;
2008-02-25 15:11:31 +03:00
}
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog Timer version " VERSION " , timer margin: %d sec \n " ,
timeout ) ;
2008-02-25 15:11:31 +03:00
return 0 ;
}
2012-11-19 22:26:24 +04:00
static int rc32434_wdt_remove ( struct platform_device * pdev )
2008-02-25 15:11:31 +03:00
{
misc_deregister ( & rc32434_wdt_miscdev ) ;
return 0 ;
}
2009-02-23 16:08:35 +03:00
static void rc32434_wdt_shutdown ( struct platform_device * pdev )
{
rc32434_wdt_stop ( ) ;
}
2009-02-08 18:44:42 +03:00
static struct platform_driver rc32434_wdt_driver = {
2009-02-23 16:08:35 +03:00
. probe = rc32434_wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = rc32434_wdt_remove ,
2009-02-23 16:08:35 +03:00
. shutdown = rc32434_wdt_shutdown ,
. driver = {
. name = " rc32434_wdt " ,
2008-02-25 15:11:31 +03:00
}
} ;
2011-11-29 09:56:27 +04:00
module_platform_driver ( rc32434_wdt_driver ) ;
2008-02-25 15:11:31 +03:00
MODULE_AUTHOR ( " Ondrej Zajicek <santiago@crfreenet.org>, "
" Florian Fainelli <florian@openwrt.org> " ) ;
MODULE_DESCRIPTION ( " Driver for the IDT RC32434 SoC watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;