2019-05-27 09:55:05 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2010-08-01 17:30:55 +04:00
/***************************************************************************
* Copyright ( C ) 2006 by Hans Edgington < hans @ edgington . nl > *
* Copyright ( C ) 2007 - 2009 Hans de Goede < hdegoede @ redhat . com > *
* Copyright ( C ) 2010 Giel van Schijndel < me @ mortis . eu > *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-08-01 17:30:55 +04:00
# include <linux/err.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/uaccess.h>
# include <linux/watchdog.h>
# define DRVNAME "f71808e_wdt"
# define SIO_F71808FG_LD_WDT 0x07 /* Watchdog timer logical device */
# define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
2016-04-25 18:34:47 +03:00
# define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
2010-08-01 17:30:55 +04:00
# define SIO_REG_LDSEL 0x07 /* Logical device select */
# define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
# define SIO_REG_DEVREV 0x22 /* Device revision */
# define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
2019-09-12 20:55:50 +03:00
# define SIO_REG_CLOCK_SEL 0x26 /* Clock select */
2010-09-26 18:25:35 +04:00
# define SIO_REG_ROM_ADDR_SEL 0x27 /* ROM address select */
2016-06-08 09:57:50 +03:00
# define SIO_F81866_REG_PORT_SEL 0x27 /* F81866 Multi-Function Register */
2019-09-12 20:55:50 +03:00
# define SIO_REG_TSI_LEVEL_SEL 0x28 /* TSI Level select */
2010-09-26 18:38:20 +04:00
# define SIO_REG_MFUNCT1 0x29 /* Multi function select 1 */
# define SIO_REG_MFUNCT2 0x2a /* Multi function select 2 */
# define SIO_REG_MFUNCT3 0x2b /* Multi function select 3 */
2016-06-08 09:57:50 +03:00
# define SIO_F81866_REG_GPIO1 0x2c /* F81866 GPIO1 Enable Register */
2010-08-01 17:30:55 +04:00
# define SIO_REG_ENABLE 0x30 /* Logical device enable */
# define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
# define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */
2010-09-26 18:38:20 +04:00
# define SIO_F71808_ID 0x0901 /* Chipset ID */
# define SIO_F71858_ID 0x0507 /* Chipset ID */
2010-08-01 17:30:55 +04:00
# define SIO_F71862_ID 0x0601 /* Chipset ID */
2017-04-17 23:37:05 +03:00
# define SIO_F71868_ID 0x1106 /* Chipset ID */
2010-12-06 22:53:45 +03:00
# define SIO_F71869_ID 0x0814 /* Chipset ID */
2012-06-11 09:07:58 +04:00
# define SIO_F71869A_ID 0x1007 /* Chipset ID */
2010-08-01 17:30:55 +04:00
# define SIO_F71882_ID 0x0541 /* Chipset ID */
# define SIO_F71889_ID 0x0723 /* Chipset ID */
2019-09-12 20:55:50 +03:00
# define SIO_F81803_ID 0x1210 /* Chipset ID */
2016-04-25 13:28:51 +03:00
# define SIO_F81865_ID 0x0704 /* Chipset ID */
2016-06-08 09:57:50 +03:00
# define SIO_F81866_ID 0x1010 /* Chipset ID */
2010-08-01 17:30:55 +04:00
# define F71808FG_REG_WDO_CONF 0xf0
# define F71808FG_REG_WDT_CONF 0xf5
# define F71808FG_REG_WD_TIME 0xf6
# define F71808FG_FLAG_WDOUT_EN 7
2016-04-26 09:44:16 +03:00
# define F71808FG_FLAG_WDTMOUT_STS 6
2010-08-01 17:30:55 +04:00
# define F71808FG_FLAG_WD_EN 5
# define F71808FG_FLAG_WD_PULSE 4
# define F71808FG_FLAG_WD_UNIT 3
2016-04-25 13:28:51 +03:00
# define F81865_REG_WDO_CONF 0xfa
# define F81865_FLAG_WDOUT_EN 0
2010-08-01 17:30:55 +04:00
/* Default values */
# define WATCHDOG_TIMEOUT 60 /* 1 minute default timeout */
# define WATCHDOG_MAX_TIMEOUT (60 * 255)
# define WATCHDOG_PULSE_WIDTH 125 / * 125 ms, default pulse width for
watchdog signal */
2010-09-26 18:25:35 +04:00
# define WATCHDOG_F71862FG_PIN 63 / * default watchdog reset output
pin number 63 */
2010-08-01 17:30:55 +04:00
static unsigned short force_id ;
module_param ( force_id , ushort , 0 ) ;
MODULE_PARM_DESC ( force_id , " Override the detected device ID " ) ;
static const int max_timeout = WATCHDOG_MAX_TIMEOUT ;
2010-09-26 18:38:20 +04:00
static int timeout = WATCHDOG_TIMEOUT ; /* default timeout in seconds */
2010-08-01 17:30:55 +04:00
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. 1<= timeout <= "
__MODULE_STRING ( WATCHDOG_MAX_TIMEOUT ) " (default= "
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " ) " ) ;
static unsigned int pulse_width = WATCHDOG_PULSE_WIDTH ;
module_param ( pulse_width , uint , 0 ) ;
MODULE_PARM_DESC ( pulse_width ,
2017-04-17 23:37:05 +03:00
" Watchdog signal pulse width. 0(=level), 1, 25, 30, 125, 150, 5000 or 6000 ms "
2010-08-01 17:30:55 +04:00
" (default= " __MODULE_STRING ( WATCHDOG_PULSE_WIDTH ) " ) " ) ;
2010-09-26 18:25:35 +04:00
static unsigned int f71862fg_pin = WATCHDOG_F71862FG_PIN ;
module_param ( f71862fg_pin , uint , 0 ) ;
MODULE_PARM_DESC ( f71862fg_pin ,
" Watchdog f71862fg reset output pin configuration. Choose pin 56 or 63 "
" (default= " __MODULE_STRING ( WATCHDOG_F71862FG_PIN ) " ) " ) ;
2012-01-13 03:02:20 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2010-08-01 17:30:55 +04:00
module_param ( nowayout , bool , 0444 ) ;
MODULE_PARM_DESC ( nowayout , " Disable watchdog shutdown on close " ) ;
static unsigned int start_withtimeout ;
module_param ( start_withtimeout , uint , 0 ) ;
MODULE_PARM_DESC ( start_withtimeout , " Start watchdog timer on module load with "
" given initial timeout. Zero (default) disables this feature. " ) ;
2017-04-17 23:37:05 +03:00
enum chips { f71808fg , f71858fg , f71862fg , f71868 , f71869 , f71882fg , f71889fg ,
2019-09-12 20:55:50 +03:00
f81803 , f81865 , f81866 } ;
2010-08-01 17:30:55 +04:00
static const char * f71808e_names [ ] = {
" f71808fg " ,
" f71858fg " ,
" f71862fg " ,
2017-04-17 23:37:05 +03:00
" f71868 " ,
2010-12-06 22:53:45 +03:00
" f71869 " ,
2010-08-01 17:30:55 +04:00
" f71882fg " ,
" f71889fg " ,
2019-09-12 20:55:50 +03:00
" f81803 " ,
2016-04-25 13:28:51 +03:00
" f81865 " ,
2016-06-08 09:57:50 +03:00
" f81866 " ,
2010-08-01 17:30:55 +04:00
} ;
/* Super-I/O Function prototypes */
static inline int superio_inb ( int base , int reg ) ;
static inline int superio_inw ( int base , int reg ) ;
static inline void superio_outb ( int base , int reg , u8 val ) ;
static inline void superio_set_bit ( int base , int reg , int bit ) ;
static inline void superio_clear_bit ( int base , int reg , int bit ) ;
static inline int superio_enter ( int base ) ;
static inline void superio_select ( int base , int ld ) ;
static inline void superio_exit ( int base ) ;
struct watchdog_data {
unsigned short sioaddr ;
enum chips type ;
unsigned long opened ;
struct mutex lock ;
char expect_close ;
struct watchdog_info ident ;
unsigned short timeout ;
u8 timer_val ; /* content for the wd_time register */
char minutes_mode ;
u8 pulse_val ; /* pulse width flag */
char pulse_mode ; /* enable pulse output mode? */
char caused_reboot ; /* last reboot was by the watchdog */
} ;
static struct watchdog_data watchdog = {
. lock = __MUTEX_INITIALIZER ( watchdog . lock ) ,
} ;
/* Super I/O functions */
static inline int superio_inb ( int base , int reg )
{
outb ( reg , base ) ;
return inb ( base + 1 ) ;
}
static int superio_inw ( int base , int reg )
{
int val ;
val = superio_inb ( base , reg ) < < 8 ;
val | = superio_inb ( base , reg + 1 ) ;
return val ;
}
static inline void superio_outb ( int base , int reg , u8 val )
{
outb ( reg , base ) ;
outb ( val , base + 1 ) ;
}
static inline void superio_set_bit ( int base , int reg , int bit )
{
unsigned long val = superio_inb ( base , reg ) ;
__set_bit ( bit , & val ) ;
superio_outb ( base , reg , val ) ;
}
static inline void superio_clear_bit ( int base , int reg , int bit )
{
unsigned long val = superio_inb ( base , reg ) ;
__clear_bit ( bit , & val ) ;
superio_outb ( base , reg , val ) ;
}
static inline int superio_enter ( int base )
{
/* Don't step on other drivers' I/O space by accident */
if ( ! request_muxed_region ( base , 2 , DRVNAME ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " I/O address 0x%04x already in use \n " , ( int ) base ) ;
2010-08-01 17:30:55 +04:00
return - EBUSY ;
}
2012-06-11 09:07:58 +04:00
/* according to the datasheet the key must be sent twice! */
2010-08-01 17:30:55 +04:00
outb ( SIO_UNLOCK_KEY , base ) ;
outb ( SIO_UNLOCK_KEY , base ) ;
return 0 ;
}
static inline void superio_select ( int base , int ld )
{
outb ( SIO_REG_LDSEL , base ) ;
outb ( ld , base + 1 ) ;
}
static inline void superio_exit ( int base )
{
outb ( SIO_LOCK_KEY , base ) ;
release_region ( base , 2 ) ;
}
static int watchdog_set_timeout ( int timeout )
{
if ( timeout < = 0
| | timeout > max_timeout ) {
2012-02-16 03:06:19 +04:00
pr_err ( " watchdog timeout out of range \n " ) ;
2010-08-01 17:30:55 +04:00
return - EINVAL ;
}
mutex_lock ( & watchdog . lock ) ;
watchdog . timeout = timeout ;
if ( timeout > 0xff ) {
watchdog . timer_val = DIV_ROUND_UP ( timeout , 60 ) ;
watchdog . minutes_mode = true ;
} else {
watchdog . timer_val = timeout ;
watchdog . minutes_mode = false ;
}
mutex_unlock ( & watchdog . lock ) ;
return 0 ;
}
static int watchdog_set_pulse_width ( unsigned int pw )
{
int err = 0 ;
2017-04-17 23:37:05 +03:00
unsigned int t1 = 25 , t2 = 125 , t3 = 5000 ;
if ( watchdog . type = = f71868 ) {
t1 = 30 ;
t2 = 150 ;
t3 = 6000 ;
}
2010-08-01 17:30:55 +04:00
mutex_lock ( & watchdog . lock ) ;
2017-04-17 23:37:05 +03:00
if ( pw < = 1 ) {
2010-08-01 17:30:55 +04:00
watchdog . pulse_val = 0 ;
2017-04-17 23:37:05 +03:00
} else if ( pw < = t1 ) {
2010-08-01 17:30:55 +04:00
watchdog . pulse_val = 1 ;
2017-04-17 23:37:05 +03:00
} else if ( pw < = t2 ) {
2010-08-01 17:30:55 +04:00
watchdog . pulse_val = 2 ;
2017-04-17 23:37:05 +03:00
} else if ( pw < = t3 ) {
2010-08-01 17:30:55 +04:00
watchdog . pulse_val = 3 ;
} else {
2012-02-16 03:06:19 +04:00
pr_err ( " pulse width out of range \n " ) ;
2010-08-01 17:30:55 +04:00
err = - EINVAL ;
goto exit_unlock ;
}
watchdog . pulse_mode = pw ;
exit_unlock :
mutex_unlock ( & watchdog . lock ) ;
return err ;
}
static int watchdog_keepalive ( void )
{
int err = 0 ;
mutex_lock ( & watchdog . lock ) ;
err = superio_enter ( watchdog . sioaddr ) ;
if ( err )
goto exit_unlock ;
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
if ( watchdog . minutes_mode )
/* select minutes for timer units */
superio_set_bit ( watchdog . sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_UNIT ) ;
else
/* select seconds for timer units */
superio_clear_bit ( watchdog . sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_UNIT ) ;
/* Set timer value */
superio_outb ( watchdog . sioaddr , F71808FG_REG_WD_TIME ,
watchdog . timer_val ) ;
superio_exit ( watchdog . sioaddr ) ;
exit_unlock :
mutex_unlock ( & watchdog . lock ) ;
return err ;
}
static int watchdog_start ( void )
{
2019-03-27 09:42:50 +03:00
int err ;
2019-03-27 09:42:51 +03:00
u8 tmp ;
2019-03-27 09:42:50 +03:00
2010-08-01 17:30:55 +04:00
/* Make sure we don't die as soon as the watchdog is enabled below */
2019-03-27 09:42:50 +03:00
err = watchdog_keepalive ( ) ;
2010-08-01 17:30:55 +04:00
if ( err )
return err ;
mutex_lock ( & watchdog . lock ) ;
err = superio_enter ( watchdog . sioaddr ) ;
if ( err )
goto exit_unlock ;
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
/* Watchdog pin configuration */
switch ( watchdog . type ) {
case f71808fg :
/* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */
2010-09-26 18:38:20 +04:00
superio_clear_bit ( watchdog . sioaddr , SIO_REG_MFUNCT2 , 3 ) ;
superio_clear_bit ( watchdog . sioaddr , SIO_REG_MFUNCT3 , 3 ) ;
2010-08-01 17:30:55 +04:00
break ;
2010-09-26 18:25:35 +04:00
case f71862fg :
2020-06-11 22:17:46 +03:00
if ( f71862fg_pin = = 63 ) {
/* SPI must be disabled first to use this pin! */
superio_clear_bit ( watchdog . sioaddr , SIO_REG_ROM_ADDR_SEL , 6 ) ;
superio_set_bit ( watchdog . sioaddr , SIO_REG_MFUNCT3 , 4 ) ;
} else if ( f71862fg_pin = = 56 ) {
superio_set_bit ( watchdog . sioaddr , SIO_REG_MFUNCT1 , 1 ) ;
}
2010-09-26 18:25:35 +04:00
break ;
2017-04-17 23:37:05 +03:00
case f71868 :
2010-12-06 22:53:45 +03:00
case f71869 :
/* GPIO14 --> WDTRST# */
superio_clear_bit ( watchdog . sioaddr , SIO_REG_MFUNCT1 , 4 ) ;
break ;
2010-08-01 17:30:55 +04:00
case f71882fg :
/* Set pin 56 to WDTRST# */
2010-09-26 18:38:20 +04:00
superio_set_bit ( watchdog . sioaddr , SIO_REG_MFUNCT1 , 1 ) ;
2010-08-01 17:30:55 +04:00
break ;
2010-10-04 12:45:28 +04:00
case f71889fg :
/* set pin 40 to WDTRST# */
2010-09-26 18:38:20 +04:00
superio_outb ( watchdog . sioaddr , SIO_REG_MFUNCT3 ,
superio_inb ( watchdog . sioaddr , SIO_REG_MFUNCT3 ) & 0xcf ) ;
2010-10-04 12:45:28 +04:00
break ;
2019-09-12 20:55:50 +03:00
case f81803 :
/* Enable TSI Level register bank */
superio_clear_bit ( watchdog . sioaddr , SIO_REG_CLOCK_SEL , 3 ) ;
/* Set pin 27 to WDTRST# */
superio_outb ( watchdog . sioaddr , SIO_REG_TSI_LEVEL_SEL , 0x5f &
superio_inb ( watchdog . sioaddr , SIO_REG_TSI_LEVEL_SEL ) ) ;
break ;
2016-04-25 13:28:51 +03:00
case f81865 :
/* Set pin 70 to WDTRST# */
superio_clear_bit ( watchdog . sioaddr , SIO_REG_MFUNCT3 , 5 ) ;
break ;
2016-06-08 09:57:50 +03:00
case f81866 :
/*
* GPIO1 Control Register when 27 h BIT3 : 2 = 01 & BIT0 = 0.
* The PIN 70 ( GPIO15 / WDTRST ) is controlled by 2 Ch :
* BIT5 : 0 - > WDTRST #
* 1 - > GPIO15
*/
2019-03-27 09:42:51 +03:00
tmp = superio_inb ( watchdog . sioaddr , SIO_F81866_REG_PORT_SEL ) ;
tmp & = ~ ( BIT ( 3 ) | BIT ( 0 ) ) ;
tmp | = BIT ( 2 ) ;
superio_outb ( watchdog . sioaddr , SIO_F81866_REG_PORT_SEL , tmp ) ;
superio_clear_bit ( watchdog . sioaddr , SIO_F81866_REG_GPIO1 , 5 ) ;
2016-06-08 09:57:50 +03:00
break ;
2010-08-01 17:30:55 +04:00
default :
/*
* ' default ' label to shut up the compiler and catch
* programmer errors
*/
err = - ENODEV ;
goto exit_superio ;
}
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
superio_set_bit ( watchdog . sioaddr , SIO_REG_ENABLE , 0 ) ;
2016-04-25 13:28:51 +03:00
2016-06-08 09:57:50 +03:00
if ( watchdog . type = = f81865 | | watchdog . type = = f81866 )
2016-04-25 13:28:51 +03:00
superio_set_bit ( watchdog . sioaddr , F81865_REG_WDO_CONF ,
F81865_FLAG_WDOUT_EN ) ;
else
superio_set_bit ( watchdog . sioaddr , F71808FG_REG_WDO_CONF ,
F71808FG_FLAG_WDOUT_EN ) ;
2010-08-01 17:30:55 +04:00
superio_set_bit ( watchdog . sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_EN ) ;
if ( watchdog . pulse_mode ) {
/* Select "pulse" output mode with given duration */
u8 wdt_conf = superio_inb ( watchdog . sioaddr ,
F71808FG_REG_WDT_CONF ) ;
/* Set WD_PSWIDTH bits (1:0) */
wdt_conf = ( wdt_conf & 0xfc ) | ( watchdog . pulse_val & 0x03 ) ;
/* Set WD_PULSE to "pulse" mode */
wdt_conf | = BIT ( F71808FG_FLAG_WD_PULSE ) ;
superio_outb ( watchdog . sioaddr , F71808FG_REG_WDT_CONF ,
wdt_conf ) ;
} else {
/* Select "level" output mode */
superio_clear_bit ( watchdog . sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_PULSE ) ;
}
exit_superio :
superio_exit ( watchdog . sioaddr ) ;
exit_unlock :
mutex_unlock ( & watchdog . lock ) ;
return err ;
}
static int watchdog_stop ( void )
{
int err = 0 ;
mutex_lock ( & watchdog . lock ) ;
err = superio_enter ( watchdog . sioaddr ) ;
if ( err )
goto exit_unlock ;
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
superio_clear_bit ( watchdog . sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_EN ) ;
superio_exit ( watchdog . sioaddr ) ;
exit_unlock :
mutex_unlock ( & watchdog . lock ) ;
return err ;
}
static int watchdog_get_status ( void )
{
int status = 0 ;
mutex_lock ( & watchdog . lock ) ;
status = ( watchdog . caused_reboot ) ? WDIOF_CARDRESET : 0 ;
mutex_unlock ( & watchdog . lock ) ;
return status ;
}
static bool watchdog_is_running ( void )
{
/*
* if we fail to determine the watchdog ' s status assume it to be
* running to be on the safe side
*/
bool is_running = true ;
mutex_lock ( & watchdog . lock ) ;
if ( superio_enter ( watchdog . sioaddr ) )
goto exit_unlock ;
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
is_running = ( superio_inb ( watchdog . sioaddr , SIO_REG_ENABLE ) & BIT ( 0 ) )
& & ( superio_inb ( watchdog . sioaddr , F71808FG_REG_WDT_CONF )
2018-03-07 10:47:25 +03:00
& BIT ( F71808FG_FLAG_WD_EN ) ) ;
2010-08-01 17:30:55 +04:00
superio_exit ( watchdog . sioaddr ) ;
exit_unlock :
mutex_unlock ( & watchdog . lock ) ;
return is_running ;
}
/* /dev/watchdog api */
static int watchdog_open ( struct inode * inode , struct file * file )
{
int err ;
/* If the watchdog is alive we don't need to start it again */
if ( test_and_set_bit ( 0 , & watchdog . opened ) )
return - EBUSY ;
err = watchdog_start ( ) ;
if ( err ) {
clear_bit ( 0 , & watchdog . opened ) ;
return err ;
}
if ( nowayout )
__module_get ( THIS_MODULE ) ;
watchdog . expect_close = 0 ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2010-08-01 17:30:55 +04:00
}
static int watchdog_release ( struct inode * inode , struct file * file )
{
clear_bit ( 0 , & watchdog . opened ) ;
if ( ! watchdog . expect_close ) {
watchdog_keepalive ( ) ;
2012-02-16 03:06:19 +04:00
pr_crit ( " Unexpected close, not stopping watchdog! \n " ) ;
2010-08-01 17:30:55 +04:00
} else if ( ! nowayout ) {
watchdog_stop ( ) ;
}
return 0 ;
}
/*
* watchdog_write :
* @ file : file handle to the watchdog
* @ buf : buffer to write
* @ count : count of bytes
* @ ppos : pointer to the position to write . No seeks allowed
*
* A write to a watchdog device is defined as a keepalive signal . Any
* write of data will do , as we we don ' t define content meaning .
*/
static ssize_t watchdog_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
if ( count ) {
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
bool expect_close = false ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
2018-02-28 11:59:12 +03:00
if ( c = = ' V ' )
expect_close = true ;
2010-08-01 17:30:55 +04:00
}
/* Properly order writes across fork()ed processes */
mutex_lock ( & watchdog . lock ) ;
watchdog . expect_close = expect_close ;
mutex_unlock ( & watchdog . lock ) ;
}
/* someone wrote to us, we should restart timer */
watchdog_keepalive ( ) ;
}
return count ;
}
/*
* watchdog_ioctl :
* @ inode : inode of the device
* @ file : file handle to the device
* @ cmd : watchdog command
* @ arg : argument pointer
*
* The watchdog API defines a common set of functions for all watchdogs
* according to their available features .
*/
static long watchdog_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
int status ;
int new_options ;
int new_timeout ;
union {
struct watchdog_info __user * ident ;
int __user * i ;
} uarg ;
uarg . i = ( int __user * ) arg ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( uarg . ident , & watchdog . ident ,
sizeof ( watchdog . ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
status = watchdog_get_status ( ) ;
if ( status < 0 )
return status ;
return put_user ( status , uarg . i ) ;
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , uarg . i ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( new_options , uarg . i ) )
return - EFAULT ;
if ( new_options & WDIOS_DISABLECARD )
watchdog_stop ( ) ;
if ( new_options & WDIOS_ENABLECARD )
return watchdog_start ( ) ;
2020-07-07 20:11:21 +03:00
fallthrough ;
2010-08-01 17:30:55 +04:00
case WDIOC_KEEPALIVE :
watchdog_keepalive ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_timeout , uarg . i ) )
return - EFAULT ;
if ( watchdog_set_timeout ( new_timeout ) )
return - EINVAL ;
watchdog_keepalive ( ) ;
2020-07-07 20:11:21 +03:00
fallthrough ;
2010-08-01 17:30:55 +04:00
case WDIOC_GETTIMEOUT :
return put_user ( watchdog . timeout , uarg . i ) ;
default :
return - ENOTTY ;
}
}
static int watchdog_notify_sys ( struct notifier_block * this , unsigned long code ,
void * unused )
{
if ( code = = SYS_DOWN | | code = = SYS_HALT )
watchdog_stop ( ) ;
return NOTIFY_DONE ;
}
static const struct file_operations watchdog_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. open = watchdog_open ,
. release = watchdog_release ,
. write = watchdog_write ,
. unlocked_ioctl = watchdog_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2010-08-01 17:30:55 +04:00
} ;
static struct miscdevice watchdog_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & watchdog_fops ,
} ;
static struct notifier_block watchdog_notifier = {
. notifier_call = watchdog_notify_sys ,
} ;
static int __init watchdog_init ( int sioaddr )
{
int wdt_conf , err = 0 ;
/* No need to lock watchdog.lock here because no entry points
* into the module have been registered yet .
*/
watchdog . sioaddr = sioaddr ;
2020-06-11 22:17:44 +03:00
watchdog . ident . options = WDIOF_MAGICCLOSE
2020-06-11 22:17:43 +03:00
| WDIOF_KEEPALIVEPING
| WDIOF_CARDRESET ;
2010-08-01 17:30:55 +04:00
snprintf ( watchdog . ident . identity ,
sizeof ( watchdog . ident . identity ) , " %s watchdog " ,
f71808e_names [ watchdog . type ] ) ;
err = superio_enter ( sioaddr ) ;
if ( err )
return err ;
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
wdt_conf = superio_inb ( sioaddr , F71808FG_REG_WDT_CONF ) ;
2016-04-26 09:44:16 +03:00
watchdog . caused_reboot = wdt_conf & BIT ( F71808FG_FLAG_WDTMOUT_STS ) ;
2010-08-01 17:30:55 +04:00
2020-06-11 22:17:45 +03:00
/*
* We don ' t want WDTMOUT_STS to stick around till regular reboot .
* Write 1 to the bit to clear it to zero .
*/
superio_outb ( sioaddr , F71808FG_REG_WDT_CONF ,
wdt_conf | BIT ( F71808FG_FLAG_WDTMOUT_STS ) ) ;
2010-08-01 17:30:55 +04:00
superio_exit ( sioaddr ) ;
err = watchdog_set_timeout ( timeout ) ;
if ( err )
return err ;
err = watchdog_set_pulse_width ( pulse_width ) ;
if ( err )
return err ;
err = register_reboot_notifier ( & watchdog_notifier ) ;
if ( err )
return err ;
err = misc_register ( & watchdog_miscdev ) ;
if ( err ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register miscdev on minor=%d \n " ,
watchdog_miscdev . minor ) ;
2010-08-01 17:30:55 +04:00
goto exit_reboot ;
}
if ( start_withtimeout ) {
if ( start_withtimeout < = 0
| | start_withtimeout > max_timeout ) {
2012-02-16 03:06:19 +04:00
pr_err ( " starting timeout out of range \n " ) ;
2010-08-01 17:30:55 +04:00
err = - EINVAL ;
goto exit_miscdev ;
}
err = watchdog_start ( ) ;
if ( err ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot start watchdog timer \n " ) ;
2010-08-01 17:30:55 +04:00
goto exit_miscdev ;
}
mutex_lock ( & watchdog . lock ) ;
err = superio_enter ( sioaddr ) ;
if ( err )
goto exit_unlock ;
superio_select ( watchdog . sioaddr , SIO_F71808FG_LD_WDT ) ;
if ( start_withtimeout > 0xff ) {
/* select minutes for timer units */
superio_set_bit ( sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_UNIT ) ;
superio_outb ( sioaddr , F71808FG_REG_WD_TIME ,
DIV_ROUND_UP ( start_withtimeout , 60 ) ) ;
} else {
/* select seconds for timer units */
superio_clear_bit ( sioaddr , F71808FG_REG_WDT_CONF ,
F71808FG_FLAG_WD_UNIT ) ;
superio_outb ( sioaddr , F71808FG_REG_WD_TIME ,
start_withtimeout ) ;
}
superio_exit ( sioaddr ) ;
mutex_unlock ( & watchdog . lock ) ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " watchdog started with initial timeout of %u sec \n " ,
2010-08-01 17:30:55 +04:00
start_withtimeout ) ;
}
return 0 ;
exit_unlock :
mutex_unlock ( & watchdog . lock ) ;
exit_miscdev :
misc_deregister ( & watchdog_miscdev ) ;
exit_reboot :
unregister_reboot_notifier ( & watchdog_notifier ) ;
return err ;
}
static int __init f71808e_find ( int sioaddr )
{
u16 devid ;
int err = superio_enter ( sioaddr ) ;
if ( err )
return err ;
devid = superio_inw ( sioaddr , SIO_REG_MANID ) ;
if ( devid ! = SIO_FINTEK_ID ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " Not a Fintek device \n " ) ;
2010-08-01 17:30:55 +04:00
err = - ENODEV ;
goto exit ;
}
devid = force_id ? force_id : superio_inw ( sioaddr , SIO_REG_DEVID ) ;
switch ( devid ) {
case SIO_F71808_ID :
watchdog . type = f71808fg ;
break ;
2010-09-26 18:25:35 +04:00
case SIO_F71862_ID :
watchdog . type = f71862fg ;
break ;
2017-04-17 23:37:05 +03:00
case SIO_F71868_ID :
watchdog . type = f71868 ;
break ;
2010-12-06 22:53:45 +03:00
case SIO_F71869_ID :
2012-06-11 09:07:58 +04:00
case SIO_F71869A_ID :
2010-12-06 22:53:45 +03:00
watchdog . type = f71869 ;
break ;
2010-08-01 17:30:55 +04:00
case SIO_F71882_ID :
watchdog . type = f71882fg ;
break ;
case SIO_F71889_ID :
2010-10-04 12:45:28 +04:00
watchdog . type = f71889fg ;
break ;
2010-08-01 17:30:55 +04:00
case SIO_F71858_ID :
/* Confirmed (by datasheet) not to have a watchdog. */
err = - ENODEV ;
goto exit ;
2019-09-12 20:55:50 +03:00
case SIO_F81803_ID :
watchdog . type = f81803 ;
break ;
2016-04-25 13:28:51 +03:00
case SIO_F81865_ID :
watchdog . type = f81865 ;
2016-06-08 09:57:50 +03:00
break ;
case SIO_F81866_ID :
watchdog . type = f81866 ;
2016-04-25 13:28:51 +03:00
break ;
2010-08-01 17:30:55 +04:00
default :
2012-02-16 03:06:19 +04:00
pr_info ( " Unrecognized Fintek device: %04x \n " ,
( unsigned int ) devid ) ;
2010-08-01 17:30:55 +04:00
err = - ENODEV ;
goto exit ;
}
2012-02-16 03:06:19 +04:00
pr_info ( " Found %s watchdog chip, revision %d \n " ,
2010-08-01 17:30:55 +04:00
f71808e_names [ watchdog . type ] ,
( int ) superio_inb ( sioaddr , SIO_REG_DEVREV ) ) ;
exit :
superio_exit ( sioaddr ) ;
return err ;
}
static int __init f71808e_init ( void )
{
static const unsigned short addrs [ ] = { 0x2e , 0x4e } ;
int err = - ENODEV ;
int i ;
2020-06-11 22:17:46 +03:00
if ( f71862fg_pin ! = 63 & & f71862fg_pin ! = 56 ) {
pr_err ( " Invalid argument f71862fg_pin=%d \n " , f71862fg_pin ) ;
return - EINVAL ;
}
2010-08-01 17:30:55 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( addrs ) ; i + + ) {
err = f71808e_find ( addrs [ i ] ) ;
if ( err = = 0 )
break ;
}
if ( i = = ARRAY_SIZE ( addrs ) )
return err ;
return watchdog_init ( addrs [ i ] ) ;
}
static void __exit f71808e_exit ( void )
{
if ( watchdog_is_running ( ) ) {
2012-02-16 03:06:19 +04:00
pr_warn ( " Watchdog timer still running, stopping it \n " ) ;
2010-08-01 17:30:55 +04:00
watchdog_stop ( ) ;
}
misc_deregister ( & watchdog_miscdev ) ;
unregister_reboot_notifier ( & watchdog_notifier ) ;
}
MODULE_DESCRIPTION ( " F71808E Watchdog Driver " ) ;
MODULE_AUTHOR ( " Giel van Schijndel <me@mortis.eu> " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( f71808e_init ) ;
module_exit ( f71808e_exit ) ;