2019-05-28 19:57:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2011-12-05 20:00:23 +04:00
/*
* VIA Chipset Watchdog Driver
*
* Copyright ( C ) 2011 Sigfox
* Author : Marc Vertes < marc . vertes @ sigfox . com >
* Based on a preliminary version from Harald Welte < HaraldWelte @ viatech . com >
* Timer code by Wim Van Sebroeck < wim @ iguana . be >
*
* Caveat : PnP must be enabled in BIOS to allow full access to watchdog
* control registers . If not , the watchdog must be configured in BIOS manually .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2011-12-05 20:00:23 +04:00
# include <linux/device.h>
# include <linux/io.h>
# include <linux/jiffies.h>
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/timer.h>
# include <linux/watchdog.h>
/* Configuration registers relative to the pci device */
# define VIA_WDT_MMIO_BASE 0xe8 /* MMIO region base address */
# define VIA_WDT_CONF 0xec /* watchdog enable state */
/* Relevant bits for the VIA_WDT_CONF register */
# define VIA_WDT_CONF_ENABLE 0x01 /* 1: enable watchdog */
# define VIA_WDT_CONF_MMIO 0x02 /* 1: enable watchdog MMIO */
/*
2018-09-08 13:20:05 +03:00
* The MMIO region contains the watchdog control register and the
2011-12-05 20:00:23 +04:00
* hardware timer counter .
*/
# define VIA_WDT_MMIO_LEN 8 /* MMIO region length in bytes */
# define VIA_WDT_CTL 0 /* MMIO addr+0: state/control reg. */
# define VIA_WDT_COUNT 4 /* MMIO addr+4: timer counter reg. */
/* Bits for the VIA_WDT_CTL register */
# define VIA_WDT_RUNNING 0x01 /* 0: stop, 1: running */
# define VIA_WDT_FIRED 0x02 /* 1: restarted by expired watchdog */
# define VIA_WDT_PWROFF 0x04 /* 0: reset, 1: poweroff */
# define VIA_WDT_DISABLED 0x08 /* 1: timer is disabled */
# define VIA_WDT_TRIGGER 0x80 /* 1: start a new countdown */
/* Hardware heartbeat in seconds */
# define WDT_HW_HEARTBEAT 1
/* Timer heartbeat (500ms) */
# define WDT_HEARTBEAT (HZ / 2) /* should be <= ((WDT_HW_HEARTBEAT*HZ)/2) */
/* User space timeout in seconds */
# define WDT_TIMEOUT_MAX 1023 /* approx. 17 min. */
# define WDT_TIMEOUT 60
static int timeout = WDT_TIMEOUT ;
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout in seconds, between 1 and 1023 "
" (default = " __MODULE_STRING ( WDT_TIMEOUT ) " ) " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2011-12-05 20:00:23 +04:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default = " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static struct watchdog_device wdt_dev ;
static struct resource wdt_res ;
static void __iomem * wdt_mem ;
static unsigned int mmio ;
2017-08-28 21:28:21 +03:00
static void wdt_timer_tick ( struct timer_list * unused ) ;
2017-10-05 02:27:04 +03:00
static DEFINE_TIMER ( timer , wdt_timer_tick ) ;
2011-12-05 20:00:23 +04:00
/* The timer that pings the watchdog */
static unsigned long next_heartbeat ; /* the next_heartbeat for the timer */
static inline void wdt_reset ( void )
{
unsigned int ctl = readl ( wdt_mem ) ;
writel ( ctl | VIA_WDT_TRIGGER , wdt_mem ) ;
}
/*
* Timer tick : the timer will make sure that the watchdog timer hardware
* is being reset in time . The conditions to do this are :
2018-09-08 13:20:05 +03:00
* 1 ) the watchdog timer has been started and / dev / watchdog is open
2011-12-05 20:00:23 +04:00
* and there is still time left before userspace should send the
* next heartbeat / ping . ( note : the internal heartbeat is much smaller
* then the external / userspace heartbeat ) .
* 2 ) the watchdog timer has been stopped by userspace .
*/
2017-08-28 21:28:21 +03:00
static void wdt_timer_tick ( struct timer_list * unused )
2011-12-05 20:00:23 +04:00
{
if ( time_before ( jiffies , next_heartbeat ) | |
2012-03-12 08:21:56 +04:00
( ! watchdog_active ( & wdt_dev ) ) ) {
2011-12-05 20:00:23 +04:00
wdt_reset ( ) ;
mod_timer ( & timer , jiffies + WDT_HEARTBEAT ) ;
} else
pr_crit ( " I will reboot your machine ! \n " ) ;
}
static int wdt_ping ( struct watchdog_device * wdd )
{
/* calculate when the next userspace timeout will be */
2012-02-29 23:20:58 +04:00
next_heartbeat = jiffies + wdd - > timeout * HZ ;
2011-12-05 20:00:23 +04:00
return 0 ;
}
static int wdt_start ( struct watchdog_device * wdd )
{
unsigned int ctl = readl ( wdt_mem ) ;
2012-02-29 23:20:58 +04:00
writel ( wdd - > timeout , wdt_mem + VIA_WDT_COUNT ) ;
2011-12-05 20:00:23 +04:00
writel ( ctl | VIA_WDT_RUNNING | VIA_WDT_TRIGGER , wdt_mem ) ;
wdt_ping ( wdd ) ;
mod_timer ( & timer , jiffies + WDT_HEARTBEAT ) ;
return 0 ;
}
static int wdt_stop ( struct watchdog_device * wdd )
{
unsigned int ctl = readl ( wdt_mem ) ;
writel ( ctl & ~ VIA_WDT_RUNNING , wdt_mem ) ;
return 0 ;
}
static int wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int new_timeout )
{
writel ( new_timeout , wdt_mem + VIA_WDT_COUNT ) ;
2012-02-29 23:20:58 +04:00
wdd - > timeout = new_timeout ;
2011-12-05 20:00:23 +04:00
return 0 ;
}
static const struct watchdog_info wdt_info = {
. identity = " VIA watchdog " ,
. options = WDIOF_CARDRESET |
WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING ,
} ;
static const struct watchdog_ops wdt_ops = {
. owner = THIS_MODULE ,
. start = wdt_start ,
. stop = wdt_stop ,
. ping = wdt_ping ,
. set_timeout = wdt_set_timeout ,
} ;
static struct watchdog_device wdt_dev = {
. info = & wdt_info ,
. ops = & wdt_ops ,
2012-01-21 11:08:38 +04:00
. min_timeout = 1 ,
. max_timeout = WDT_TIMEOUT_MAX ,
2011-12-05 20:00:23 +04:00
} ;
2012-11-19 22:21:41 +04:00
static int wdt_probe ( struct pci_dev * pdev ,
2011-12-05 20:00:23 +04:00
const struct pci_device_id * ent )
{
unsigned char conf ;
int ret = - ENODEV ;
if ( pci_enable_device ( pdev ) ) {
dev_err ( & pdev - > dev , " cannot enable PCI device \n " ) ;
return - ENODEV ;
}
/*
* Allocate a MMIO region which contains watchdog control register
* and counter , then configure the watchdog to use this region .
* This is possible only if PnP is properly enabled in BIOS .
* If not , the watchdog must be configured in BIOS manually .
*/
if ( allocate_resource ( & iomem_resource , & wdt_res , VIA_WDT_MMIO_LEN ,
0xf0000000 , 0xffffff00 , 0xff , NULL , NULL ) ) {
dev_err ( & pdev - > dev , " MMIO allocation failed \n " ) ;
goto err_out_disable_device ;
}
pci_write_config_dword ( pdev , VIA_WDT_MMIO_BASE , wdt_res . start ) ;
pci_read_config_byte ( pdev , VIA_WDT_CONF , & conf ) ;
conf | = VIA_WDT_CONF_ENABLE | VIA_WDT_CONF_MMIO ;
pci_write_config_byte ( pdev , VIA_WDT_CONF , conf ) ;
pci_read_config_dword ( pdev , VIA_WDT_MMIO_BASE , & mmio ) ;
if ( mmio ) {
dev_info ( & pdev - > dev , " VIA Chipset watchdog MMIO: %x \n " , mmio ) ;
} else {
dev_err ( & pdev - > dev , " MMIO setting failed. Check BIOS. \n " ) ;
goto err_out_resource ;
}
if ( ! request_mem_region ( mmio , VIA_WDT_MMIO_LEN , " via_wdt " ) ) {
dev_err ( & pdev - > dev , " MMIO region busy \n " ) ;
goto err_out_resource ;
}
wdt_mem = ioremap ( mmio , VIA_WDT_MMIO_LEN ) ;
if ( wdt_mem = = NULL ) {
dev_err ( & pdev - > dev , " cannot remap VIA wdt MMIO registers \n " ) ;
goto err_out_release ;
}
2012-05-04 16:43:25 +04:00
if ( timeout < 1 | | timeout > WDT_TIMEOUT_MAX )
timeout = WDT_TIMEOUT ;
2011-12-05 20:00:23 +04:00
wdt_dev . timeout = timeout ;
2015-08-20 11:35:01 +03:00
wdt_dev . parent = & pdev - > dev ;
2011-12-05 20:00:23 +04:00
watchdog_set_nowayout ( & wdt_dev , nowayout ) ;
if ( readl ( wdt_mem ) & VIA_WDT_FIRED )
wdt_dev . bootstatus | = WDIOF_CARDRESET ;
ret = watchdog_register_device ( & wdt_dev ) ;
if ( ret )
goto err_out_iounmap ;
/* start triggering, in case of watchdog already enabled by BIOS */
mod_timer ( & timer , jiffies + WDT_HEARTBEAT ) ;
return 0 ;
err_out_iounmap :
iounmap ( wdt_mem ) ;
err_out_release :
release_mem_region ( mmio , VIA_WDT_MMIO_LEN ) ;
err_out_resource :
release_resource ( & wdt_res ) ;
err_out_disable_device :
pci_disable_device ( pdev ) ;
return ret ;
}
2012-11-19 22:26:24 +04:00
static void wdt_remove ( struct pci_dev * pdev )
2011-12-05 20:00:23 +04:00
{
watchdog_unregister_device ( & wdt_dev ) ;
2014-04-01 17:49:19 +04:00
del_timer_sync ( & timer ) ;
2011-12-05 20:00:23 +04:00
iounmap ( wdt_mem ) ;
release_mem_region ( mmio , VIA_WDT_MMIO_LEN ) ;
release_resource ( & wdt_res ) ;
pci_disable_device ( pdev ) ;
}
2013-12-03 03:30:22 +04:00
static const struct pci_device_id wdt_pci_table [ ] = {
2011-12-05 20:00:23 +04:00
{ PCI_DEVICE ( PCI_VENDOR_ID_VIA , PCI_DEVICE_ID_VIA_CX700 ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_VIA , PCI_DEVICE_ID_VIA_VX800 ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_VIA , PCI_DEVICE_ID_VIA_VX855 ) } ,
{ 0 }
} ;
static struct pci_driver wdt_driver = {
. name = " via_wdt " ,
. id_table = wdt_pci_table ,
. probe = wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = wdt_remove ,
2011-12-05 20:00:23 +04:00
} ;
2012-05-04 16:43:25 +04:00
module_pci_driver ( wdt_driver ) ;
2011-12-05 20:00:23 +04:00
MODULE_AUTHOR ( " Marc Vertes " ) ;
MODULE_DESCRIPTION ( " Driver for watchdog timer on VIA chipset " ) ;
MODULE_LICENSE ( " GPL " ) ;