2019-06-20 19:28:46 +03:00
// SPDX-License-Identifier: GPL-2.0+
2005-04-17 02:20:36 +04:00
/*
* IB700 Single Board Computer WDT driver
*
* ( c ) Copyright 2001 Charles Howes < chowes @ vsol . net >
*
2007-01-28 00:07:03 +03:00
* Based on advantechwdt . c which is based on acquirewdt . c which
* is based on wdt . c .
2005-04-17 02:20:36 +04:00
*
* ( c ) Copyright 2000 - 2001 Marek Michalkiewicz < marekm @ linux . org . pl >
*
* Based on acquirewdt . c which is based on wdt . c .
* Original copyright messages :
*
2008-10-27 18:17:56 +03:00
* ( c ) Copyright 1996 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
2005-04-17 02:20:36 +04:00
*
* Neither Alan Cox nor CymruNet Ltd . admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*
2008-10-27 18:17:56 +03:00
* ( c ) Copyright 1995 Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-17 02:20:36 +04:00
*
2007-01-28 00:07:03 +03:00
* 14 - Dec - 2001 Matt Domsch < Matt_Domsch @ dell . com >
* Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
* Added timeout module option to override default
2005-04-17 02:20:36 +04:00
*
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/ioport.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/moduleparam.h>
2007-01-28 00:39:46 +03:00
# include <linux/platform_device.h>
2008-05-19 17:05:52 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2008-07-15 15:46:11 +04:00
2005-04-17 02:20:36 +04:00
2007-01-28 00:39:46 +03:00
static struct platform_device * ibwdt_platform_device ;
2005-04-17 02:20:36 +04:00
static unsigned long ibwdt_is_open ;
2007-11-02 02:27:08 +03:00
static DEFINE_SPINLOCK ( ibwdt_lock ) ;
2005-04-17 02:20:36 +04:00
static char expect_close ;
2007-01-28 00:39:46 +03:00
/* Module information */
# define DRV_NAME "ib700wdt"
2005-04-17 02:20:36 +04:00
/*
*
* Watchdog Timer Configuration
*
* The function of the watchdog timer is to reset the system
* automatically and is defined at I / O port 0443 H . To enable the
* watchdog timer and allow the system to reset , write I / O port 0443 H .
* To disable the timer , write I / O port 0441 H for the system to stop the
* watchdog function . The timer has a tolerance of 20 % for its
* intervals .
*
* The following describes how the timer should be programmed .
*
* Enabling Watchdog :
* MOV AX , 000F H ( Choose the values from 0 to F )
* MOV DX , 0443 H
* OUT DX , AX
*
* Disabling Watchdog :
* MOV AX , 000F H ( Any value is fine . )
* MOV DX , 0441 H
* OUT DX , AX
*
* Watchdog timer control table :
* Level Value Time / sec | Level Value Time / sec
* 1 F 0 | 9 7 16
* 2 E 2 | 10 6 18
* 3 D 4 | 11 5 20
* 4 C 6 | 12 4 22
* 5 B 8 | 13 3 24
* 6 A 10 | 14 2 26
* 7 9 12 | 15 1 28
* 8 8 14 | 16 0 30
*
*/
# define WDT_STOP 0x441
# define WDT_START 0x443
/* Default timeout */
2008-10-15 15:44:40 +04:00
# define WATCHDOG_TIMEOUT 30 /* 30 seconds +/- 20% */
static int timeout = WATCHDOG_TIMEOUT ; /* in seconds */
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. 0<= timeout <=30, default= "
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " . " ) ;
2005-04-17 02:20:36 +04:00
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:05:52 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-04-17 02:20:36 +04:00
/*
2007-01-27 23:50:53 +03:00
* Watchdog Operations
2005-04-17 02:20:36 +04:00
*/
2008-08-07 00:19:41 +04:00
static void ibwdt_ping ( void )
2005-04-17 02:20:36 +04:00
{
2008-10-15 15:44:40 +04:00
int wd_margin = 15 - ( ( timeout + 1 ) / 2 ) ;
2007-01-28 00:12:54 +03:00
spin_lock ( & ibwdt_lock ) ;
2005-04-17 02:20:36 +04:00
/* Write a watchdog value */
outb_p ( wd_margin , WDT_START ) ;
2007-01-28 00:12:54 +03:00
spin_unlock ( & ibwdt_lock ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-07 00:19:41 +04:00
static void ibwdt_disable ( void )
2007-01-27 23:50:53 +03:00
{
2007-01-28 00:12:54 +03:00
spin_lock ( & ibwdt_lock ) ;
2007-01-27 23:50:53 +03:00
outb_p ( 0 , WDT_STOP ) ;
2007-01-28 00:12:54 +03:00
spin_unlock ( & ibwdt_lock ) ;
2007-01-27 23:50:53 +03:00
}
2008-08-07 00:19:41 +04:00
static int ibwdt_set_heartbeat ( int t )
2007-01-27 23:50:53 +03:00
{
2008-10-15 15:44:40 +04:00
if ( t < 0 | | t > 30 )
2007-01-27 23:50:53 +03:00
return - EINVAL ;
2008-10-15 15:44:40 +04:00
timeout = t ;
2007-01-27 23:50:53 +03:00
return 0 ;
}
/*
* / dev / watchdog handling
*/
2008-05-19 17:05:52 +04:00
static ssize_t ibwdt_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
if ( count ) {
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
}
ibwdt_ping ( ) ;
}
return count ;
}
2008-05-19 17:05:52 +04:00
static long ibwdt_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2007-01-27 23:50:53 +03:00
int new_margin ;
2005-04-17 02:20:36 +04:00
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2008-05-19 17:05:52 +04:00
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
| WDIOF_MAGICCLOSE ,
2005-04-17 02:20:36 +04:00
. firmware_version = 1 ,
. identity = " IB700 WDT " ,
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2008-05-19 17:05:52 +04:00
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
return - EFAULT ;
break ;
2005-04-17 02:20:36 +04:00
case WDIOC_GETSTATUS :
2007-01-28 00:07:03 +03:00
case WDIOC_GETBOOTSTATUS :
2008-05-19 17:05:52 +04:00
return put_user ( 0 , p ) ;
2005-04-17 02:20:36 +04:00
2007-01-28 00:12:54 +03:00
case WDIOC_SETOPTIONS :
{
2008-05-19 17:05:52 +04:00
int options , retval = - EINVAL ;
2007-01-28 00:12:54 +03:00
2008-05-19 17:05:52 +04:00
if ( get_user ( options , p ) )
return - EFAULT ;
2007-01-28 00:12:54 +03:00
2008-05-19 17:05:52 +04:00
if ( options & WDIOS_DISABLECARD ) {
ibwdt_disable ( ) ;
retval = 0 ;
}
if ( options & WDIOS_ENABLECARD ) {
ibwdt_ping ( ) ;
retval = 0 ;
}
return retval ;
2007-01-28 00:12:54 +03:00
}
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
ibwdt_ping ( ) ;
break ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , p ) )
return - EFAULT ;
if ( ibwdt_set_heartbeat ( new_margin ) )
return - EINVAL ;
ibwdt_ping ( ) ;
2020-07-07 20:11:21 +03:00
fallthrough ;
2008-07-18 15:41:17 +04:00
case WDIOC_GETTIMEOUT :
2008-10-15 15:44:40 +04:00
return put_user ( timeout , p ) ;
2008-07-18 15:41:17 +04:00
2005-04-17 02:20:36 +04:00
default :
2008-05-19 17:05:52 +04:00
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
2008-05-19 17:05:52 +04:00
static int ibwdt_open ( struct inode * inode , struct file * file )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:05:52 +04:00
if ( test_and_set_bit ( 0 , & ibwdt_is_open ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
/* Activate */
ibwdt_ping ( ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-07 00:19:41 +04:00
static int ibwdt_close ( struct inode * inode , struct file * file )
2005-04-17 02:20:36 +04:00
{
2007-01-28 00:07:03 +03:00
if ( expect_close = = 42 ) {
2007-01-27 23:50:53 +03:00
ibwdt_disable ( ) ;
2007-01-28 00:07:03 +03:00
} else {
2012-02-16 03:06:19 +04:00
pr_crit ( " WDT device closed unexpectedly. WDT will not stop! \n " ) ;
2007-01-28 00:07:03 +03:00
ibwdt_ping ( ) ;
}
2005-04-17 02:20:36 +04:00
clear_bit ( 0 , & ibwdt_is_open ) ;
expect_close = 0 ;
return 0 ;
}
/*
* Kernel Interfaces
*/
2006-07-03 11:24:21 +04:00
static const struct file_operations ibwdt_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = ibwdt_write ,
2008-05-19 17:05:52 +04:00
. unlocked_ioctl = ibwdt_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2005-04-17 02:20:36 +04:00
. open = ibwdt_open ,
. release = ibwdt_close ,
} ;
static struct miscdevice ibwdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & ibwdt_fops ,
} ;
2007-01-27 23:58:08 +03:00
/*
* Init & exit routines
*/
2014-03-14 16:18:47 +04:00
static int __init ibwdt_probe ( struct platform_device * dev )
2005-04-17 02:20:36 +04:00
{
int res ;
# if WDT_START != WDT_STOP
if ( ! request_region ( WDT_STOP , 1 , " IB700 WDT " ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " STOP method I/O %X is not available \n " , WDT_STOP ) ;
2005-04-17 02:20:36 +04:00
res = - EIO ;
goto out_nostopreg ;
}
# endif
if ( ! request_region ( WDT_START , 1 , " IB700 WDT " ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " START method I/O %X is not available \n " , WDT_START ) ;
2005-04-17 02:20:36 +04:00
res = - EIO ;
goto out_nostartreg ;
}
2007-01-27 23:58:08 +03:00
2008-10-15 15:44:40 +04:00
/* Check that the heartbeat value is within it's range ;
* if not reset to the default */
if ( ibwdt_set_heartbeat ( timeout ) ) {
ibwdt_set_heartbeat ( WATCHDOG_TIMEOUT ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " timeout value must be 0<=x<=30, using %d \n " , timeout ) ;
2008-10-15 15:44:40 +04:00
}
2007-01-27 23:58:08 +03:00
res = misc_register ( & ibwdt_miscdev ) ;
if ( res ) {
2012-02-16 03:06:19 +04:00
pr_err ( " failed to register misc device \n " ) ;
2007-01-27 23:58:08 +03:00
goto out_nomisc ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
2007-01-27 23:58:08 +03:00
out_nomisc :
2005-04-17 02:20:36 +04:00
release_region ( WDT_START , 1 ) ;
out_nostartreg :
# if WDT_START != WDT_STOP
release_region ( WDT_STOP , 1 ) ;
# endif
out_nostopreg :
return res ;
}
2023-03-04 00:36:56 +03:00
static void ibwdt_remove ( struct platform_device * dev )
2005-04-17 02:20:36 +04:00
{
misc_deregister ( & ibwdt_miscdev ) ;
2008-05-19 17:05:52 +04:00
release_region ( WDT_START , 1 ) ;
2005-04-17 02:20:36 +04:00
# if WDT_START != WDT_STOP
2008-05-19 17:05:52 +04:00
release_region ( WDT_STOP , 1 ) ;
2005-04-17 02:20:36 +04:00
# endif
2007-01-28 00:39:46 +03:00
}
2007-01-28 00:54:18 +03:00
static void ibwdt_shutdown ( struct platform_device * dev )
{
/* Turn the WDT off if we have a soft shutdown */
ibwdt_disable ( ) ;
}
2007-01-28 00:39:46 +03:00
static struct platform_driver ibwdt_driver = {
2023-03-04 00:36:56 +03:00
. remove_new = ibwdt_remove ,
2007-01-28 00:54:18 +03:00
. shutdown = ibwdt_shutdown ,
2007-01-28 00:39:46 +03:00
. driver = {
. name = DRV_NAME ,
} ,
} ;
static int __init ibwdt_init ( void )
{
int err ;
2012-02-16 03:06:19 +04:00
pr_info ( " WDT driver for IB700 single board computer initialising \n " ) ;
2007-01-28 00:39:46 +03:00
2008-05-19 17:05:52 +04:00
ibwdt_platform_device = platform_device_register_simple ( DRV_NAME ,
- 1 , NULL , 0 ) ;
2014-03-14 16:18:47 +04:00
if ( IS_ERR ( ibwdt_platform_device ) )
return PTR_ERR ( ibwdt_platform_device ) ;
err = platform_driver_probe ( & ibwdt_driver , ibwdt_probe ) ;
if ( err )
goto unreg_platform_device ;
2007-01-28 00:39:46 +03:00
return 0 ;
2014-03-14 16:18:47 +04:00
unreg_platform_device :
platform_device_unregister ( ibwdt_platform_device ) ;
2007-01-28 00:39:46 +03:00
return err ;
}
static void __exit ibwdt_exit ( void )
{
platform_device_unregister ( ibwdt_platform_device ) ;
platform_driver_unregister ( & ibwdt_driver ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog Module Unloaded \n " ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( ibwdt_init ) ;
module_exit ( ibwdt_exit ) ;
MODULE_AUTHOR ( " Charles Howes <chowes@vsol.net> " ) ;
MODULE_DESCRIPTION ( " IB700 SBC watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
/* end of ib700wdt.c */