2011-02-02 19:20:16 +03:00
/*
* Intel_SCU 0.2 : An Intel SCU IOH Based Watchdog Device
* for Intel part # ( s ) :
* - AF82MP20 PCH
*
* Copyright ( C ) 2009 - 2010 Intel Corporation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of version 2 of the GNU General
* Public License as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be
* useful , but WITHOUT ANY WARRANTY ; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE . See the GNU General Public License for more details .
* You should have received a copy of the GNU General Public
* License along with this program ; if not , write to the Free
* Software Foundation , Inc . , 59 Temple Place - Suite 330 ,
* Boston , MA 02111 - 1307 , USA .
* The full GNU General Public License is included in this
* distribution in the file called COPYING .
*
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2011-02-02 19:20:16 +03:00
# include <linux/compiler.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
# include <linux/jiffies.h>
# include <linux/uaccess.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/sched.h>
# include <linux/signal.h>
# include <linux/sfi.h>
# include <asm/irq.h>
2011-07-27 03:09:06 +04:00
# include <linux/atomic.h>
2011-02-02 19:20:16 +03:00
# include <asm/intel_scu_ipc.h>
# include <asm/apb_timer.h>
2013-10-18 02:35:27 +04:00
# include <asm/intel-mid.h>
2011-02-02 19:20:16 +03:00
# include "intel_scu_watchdog.h"
/* Bounds number of times we will retry loading time count */
/* This retry is a work around for a silicon bug. */
# define MAX_RETRY 16
# define IPC_SET_WATCHDOG_TIMER 0xF8
static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN ;
module_param ( timer_margin , int , 0 ) ;
MODULE_PARM_DESC ( timer_margin ,
" Watchdog timer margin "
" Time between interrupt and resetting the system "
" The range is from 1 to 160 "
" This is the time for all keep alives to arrive " ) ;
static int timer_set = DEFAULT_TIME ;
module_param ( timer_set , int , 0 ) ;
MODULE_PARM_DESC ( timer_set ,
" Default Watchdog timer setting "
" Complete cycle time "
" The range is from 1 to 170 "
" This is the time for all keep alives to arrive " ) ;
/* After watchdog device is closed, check force_boot. If:
* force_boot = = 0 , then force boot on next watchdog interrupt after close ,
* force_boot = = 1 , then force boot immediately when device is closed .
*/
static int force_boot ;
module_param ( force_boot , int , 0 ) ;
MODULE_PARM_DESC ( force_boot ,
" A value of 1 means that the driver will reboot "
" the system immediately if the /dev/watchdog device is closed "
" A value of 0 means that when /dev/watchdog device is closed "
" the watchdog timer will be refreshed for one more interval "
" of length: timer_set. At the end of this interval, the "
" watchdog timer will reset the system. "
) ;
/* there is only one device in the system now; this can be made into
* an array in the future if we have more than one device */
static struct intel_scu_watchdog_dev watchdog_device ;
/* Forces restart, if force_reboot is set */
static void watchdog_fire ( void )
{
if ( force_boot ) {
2012-02-16 03:06:19 +04:00
pr_crit ( " Initiating system reboot \n " ) ;
2011-02-02 19:20:16 +03:00
emergency_restart ( ) ;
2012-02-16 03:06:19 +04:00
pr_crit ( " Reboot didn't ????? \n " ) ;
2011-02-02 19:20:16 +03:00
}
else {
2012-02-16 03:06:19 +04:00
pr_crit ( " Immediate Reboot Disabled \n " ) ;
pr_crit ( " System will reset when watchdog timer times out! \n " ) ;
2011-02-02 19:20:16 +03:00
}
}
static int check_timer_margin ( int new_margin )
{
if ( ( new_margin < MIN_TIME_CYCLE ) | |
( new_margin > MAX_TIME - timer_set ) ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " value of new_margin %d is out of the range %d to %d \n " ,
new_margin , MIN_TIME_CYCLE , MAX_TIME - timer_set ) ;
2011-02-02 19:20:16 +03:00
return - EINVAL ;
}
return 0 ;
}
/*
* IPC operations
*/
static int watchdog_set_ipc ( int soft_threshold , int threshold )
{
u32 * ipc_wbuf ;
u8 cbuf [ 16 ] = { ' \0 ' } ;
int ipc_ret = 0 ;
ipc_wbuf = ( u32 * ) & cbuf ;
ipc_wbuf [ 0 ] = soft_threshold ;
ipc_wbuf [ 1 ] = threshold ;
ipc_ret = intel_scu_ipc_command (
IPC_SET_WATCHDOG_TIMER ,
0 ,
ipc_wbuf ,
2 ,
NULL ,
0 ) ;
if ( ipc_ret ! = 0 )
pr_err ( " Error setting SCU watchdog timer: %x \n " , ipc_ret ) ;
return ipc_ret ;
} ;
/*
* Intel_SCU operations
*/
/* timer interrupt handler */
static irqreturn_t watchdog_timer_interrupt ( int irq , void * dev_id )
{
int int_status ;
int_status = ioread32 ( watchdog_device . timer_interrupt_status_addr ) ;
2012-02-16 03:06:19 +04:00
pr_debug ( " irq, int_status: %x \n " , int_status ) ;
2011-02-02 19:20:16 +03:00
if ( int_status ! = 0 )
return IRQ_NONE ;
/* has the timer been started? If not, then this is spurious */
if ( watchdog_device . timer_started = = 0 ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " spurious interrupt received \n " ) ;
2011-02-02 19:20:16 +03:00
return IRQ_HANDLED ;
}
/* temporarily disable the timer */
iowrite32 ( 0x00000002 , watchdog_device . timer_control_addr ) ;
/* set the timer to the threshold */
iowrite32 ( watchdog_device . threshold ,
watchdog_device . timer_load_count_addr ) ;
/* allow the timer to run */
iowrite32 ( 0x00000003 , watchdog_device . timer_control_addr ) ;
return IRQ_HANDLED ;
}
static int intel_scu_keepalive ( void )
{
/* read eoi register - clears interrupt */
ioread32 ( watchdog_device . timer_clear_interrupt_addr ) ;
/* temporarily disable the timer */
iowrite32 ( 0x00000002 , watchdog_device . timer_control_addr ) ;
/* set the timer to the soft_threshold */
iowrite32 ( watchdog_device . soft_threshold ,
watchdog_device . timer_load_count_addr ) ;
/* allow the timer to run */
iowrite32 ( 0x00000003 , watchdog_device . timer_control_addr ) ;
return 0 ;
}
static int intel_scu_stop ( void )
{
iowrite32 ( 0 , watchdog_device . timer_control_addr ) ;
return 0 ;
}
static int intel_scu_set_heartbeat ( u32 t )
{
int ipc_ret ;
int retry_count ;
u32 soft_value ;
u32 hw_pre_value ;
u32 hw_value ;
watchdog_device . timer_set = t ;
watchdog_device . threshold =
timer_margin * watchdog_device . timer_tbl_ptr - > freq_hz ;
watchdog_device . soft_threshold =
( watchdog_device . timer_set - timer_margin )
* watchdog_device . timer_tbl_ptr - > freq_hz ;
2012-02-16 03:06:19 +04:00
pr_debug ( " set_heartbeat: timer freq is %d \n " ,
watchdog_device . timer_tbl_ptr - > freq_hz ) ;
pr_debug ( " set_heartbeat: timer_set is %x (hex) \n " ,
watchdog_device . timer_set ) ;
pr_debug ( " set_hearbeat: timer_margin is %x (hex) \n " , timer_margin ) ;
pr_debug ( " set_heartbeat: threshold is %x (hex) \n " ,
watchdog_device . threshold ) ;
pr_debug ( " set_heartbeat: soft_threshold is %x (hex) \n " ,
watchdog_device . soft_threshold ) ;
2011-02-02 19:20:16 +03:00
/* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */
/* watchdog timing come out right. */
watchdog_device . threshold =
watchdog_device . threshold / FREQ_ADJUSTMENT ;
watchdog_device . soft_threshold =
watchdog_device . soft_threshold / FREQ_ADJUSTMENT ;
/* temporarily disable the timer */
iowrite32 ( 0x00000002 , watchdog_device . timer_control_addr ) ;
/* send the threshold and soft_threshold via IPC to the processor */
ipc_ret = watchdog_set_ipc ( watchdog_device . soft_threshold ,
watchdog_device . threshold ) ;
if ( ipc_ret ! = 0 ) {
/* Make sure the watchdog timer is stopped */
intel_scu_stop ( ) ;
return ipc_ret ;
}
/* Soft Threshold set loop. Early versions of silicon did */
/* not always set this count correctly. This loop checks */
/* the value and retries if it was not set correctly. */
retry_count = 0 ;
soft_value = watchdog_device . soft_threshold & 0xFFFF0000 ;
do {
/* Make sure timer is stopped */
intel_scu_stop ( ) ;
if ( MAX_RETRY < retry_count + + ) {
/* Unable to set timer value */
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to set timer \n " ) ;
2011-02-02 19:20:16 +03:00
return - ENODEV ;
}
/* set the timer to the soft threshold */
iowrite32 ( watchdog_device . soft_threshold ,
watchdog_device . timer_load_count_addr ) ;
/* read count value before starting timer */
hw_pre_value = ioread32 ( watchdog_device . timer_load_count_addr ) ;
hw_pre_value = hw_pre_value & 0xFFFF0000 ;
/* Start the timer */
iowrite32 ( 0x00000003 , watchdog_device . timer_control_addr ) ;
/* read the value the time loaded into its count reg */
hw_value = ioread32 ( watchdog_device . timer_load_count_addr ) ;
hw_value = hw_value & 0xFFFF0000 ;
} while ( soft_value ! = hw_value ) ;
watchdog_device . timer_started = 1 ;
return 0 ;
}
/*
* / dev / watchdog handling
*/
static int intel_scu_open ( struct inode * inode , struct file * file )
{
/* Set flag to indicate that watchdog device is open */
if ( test_and_set_bit ( 0 , & watchdog_device . driver_open ) )
return - EBUSY ;
/* Check for reopen of driver. Reopens are not allowed */
if ( watchdog_device . driver_closed )
return - EPERM ;
return nonseekable_open ( inode , file ) ;
}
static int intel_scu_release ( struct inode * inode , struct file * file )
{
/*
* This watchdog should not be closed , after the timer
* is started with the WDIPC_SETTIMEOUT ioctl
* If force_boot is set watchdog_fire ( ) will cause an
* immediate reset . If force_boot is not set , the watchdog
* timer is refreshed for one more interval . At the end
* of that interval , the watchdog timer will reset the system .
*/
if ( ! test_and_clear_bit ( 0 , & watchdog_device . driver_open ) ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " intel_scu_release, without open \n " ) ;
2011-02-02 19:20:16 +03:00
return - ENOTTY ;
}
if ( ! watchdog_device . timer_started ) {
/* Just close, since timer has not been started */
2012-02-16 03:06:19 +04:00
pr_debug ( " closed, without starting timer \n " ) ;
2011-02-02 19:20:16 +03:00
return 0 ;
}
2012-02-16 03:06:19 +04:00
pr_crit ( " Unexpected close of /dev/watchdog! \n " ) ;
2011-02-02 19:20:16 +03:00
/* Since the timer was started, prevent future reopens */
watchdog_device . driver_closed = 1 ;
/* Refresh the timer for one more interval */
intel_scu_keepalive ( ) ;
/* Reboot system (if force_boot is set) */
watchdog_fire ( ) ;
/* We should only reach this point if force_boot is not set */
return 0 ;
}
static ssize_t intel_scu_write ( struct file * file ,
char const * data ,
size_t len ,
loff_t * ppos )
{
if ( watchdog_device . timer_started )
/* Watchdog already started, keep it alive */
intel_scu_keepalive ( ) ;
else
/* Start watchdog with timer value set by init */
intel_scu_set_heartbeat ( watchdog_device . timer_set ) ;
return len ;
}
static long intel_scu_ioctl ( struct file * file ,
unsigned int cmd ,
unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
u32 __user * p = argp ;
u32 new_margin ;
static const struct watchdog_info ident = {
. options = WDIOF_SETTIMEOUT
| WDIOF_KEEPALIVEPING ,
. firmware_version = 0 , /* @todo Get from SCU via
ipc_get_scu_fw_version ( ) ? */
. identity = " Intel_SCU IOH Watchdog " /* len < 32 */
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp ,
& ident ,
sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
intel_scu_keepalive ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , p ) )
return - EFAULT ;
if ( check_timer_margin ( new_margin ) )
return - EINVAL ;
if ( intel_scu_set_heartbeat ( new_margin ) )
return - EINVAL ;
return 0 ;
case WDIOC_GETTIMEOUT :
return put_user ( watchdog_device . soft_threshold , p ) ;
default :
return - ENOTTY ;
}
}
/*
* Notifier for system down
*/
static int intel_scu_notify_sys ( struct notifier_block * this ,
unsigned long code ,
void * another_unused )
{
if ( code = = SYS_DOWN | | code = = SYS_HALT )
/* Turn off the watchdog timer. */
intel_scu_stop ( ) ;
return NOTIFY_DONE ;
}
/*
* Kernel Interfaces
*/
static const struct file_operations intel_scu_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = intel_scu_write ,
. unlocked_ioctl = intel_scu_ioctl ,
. open = intel_scu_open ,
. release = intel_scu_release ,
} ;
static int __init intel_scu_watchdog_init ( void )
{
int ret ;
u32 __iomem * tmp_addr ;
/*
* We don ' t really need to check this as the SFI timer get will fail
* but if we do so we can exit with a clearer reason and no noise .
*
* If it isn ' t an intel MID device then it doesn ' t have this watchdog
*/
2013-10-18 02:35:29 +04:00
if ( ! intel_mid_identify_cpu ( ) )
2011-02-02 19:20:16 +03:00
return - ENODEV ;
/* Check boot parameters to verify that their initial values */
/* are in range. */
/* Check value of timer_set boot parameter */
if ( ( timer_set < MIN_TIME_CYCLE ) | |
( timer_set > MAX_TIME - MIN_TIME_CYCLE ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " value of timer_set %x (hex) is out of range from %x to %x (hex) \n " ,
timer_set , MIN_TIME_CYCLE , MAX_TIME - MIN_TIME_CYCLE ) ;
2011-02-02 19:20:16 +03:00
return - EINVAL ;
}
/* Check value of timer_margin boot parameter */
if ( check_timer_margin ( timer_margin ) )
return - EINVAL ;
watchdog_device . timer_tbl_ptr = sfi_get_mtmr ( sfi_mtimer_num - 1 ) ;
if ( watchdog_device . timer_tbl_ptr = = NULL ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " timer is not available \n " ) ;
2011-02-02 19:20:16 +03:00
return - ENODEV ;
}
/* make sure the timer exists */
if ( watchdog_device . timer_tbl_ptr - > phys_addr = = 0 ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " timer %d does not have valid physical memory \n " ,
sfi_mtimer_num ) ;
2011-02-02 19:20:16 +03:00
return - ENODEV ;
}
if ( watchdog_device . timer_tbl_ptr - > irq = = 0 ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " timer %d invalid irq \n " , sfi_mtimer_num ) ;
2011-02-02 19:20:16 +03:00
return - ENODEV ;
}
tmp_addr = ioremap_nocache ( watchdog_device . timer_tbl_ptr - > phys_addr ,
20 ) ;
if ( tmp_addr = = NULL ) {
2012-02-16 03:06:19 +04:00
pr_debug ( " timer unable to ioremap \n " ) ;
2011-02-02 19:20:16 +03:00
return - ENOMEM ;
}
watchdog_device . timer_load_count_addr = tmp_addr + + ;
watchdog_device . timer_current_value_addr = tmp_addr + + ;
watchdog_device . timer_control_addr = tmp_addr + + ;
watchdog_device . timer_clear_interrupt_addr = tmp_addr + + ;
watchdog_device . timer_interrupt_status_addr = tmp_addr + + ;
/* Set the default time values in device structure */
watchdog_device . timer_set = timer_set ;
watchdog_device . threshold =
timer_margin * watchdog_device . timer_tbl_ptr - > freq_hz ;
watchdog_device . soft_threshold =
( watchdog_device . timer_set - timer_margin )
* watchdog_device . timer_tbl_ptr - > freq_hz ;
watchdog_device . intel_scu_notifier . notifier_call =
intel_scu_notify_sys ;
ret = register_reboot_notifier ( & watchdog_device . intel_scu_notifier ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register notifier %d) \n " , ret ) ;
2011-02-02 19:20:16 +03:00
goto register_reboot_error ;
}
watchdog_device . miscdev . minor = WATCHDOG_MINOR ;
watchdog_device . miscdev . name = " watchdog " ;
watchdog_device . miscdev . fops = & intel_scu_fops ;
ret = misc_register ( & watchdog_device . miscdev ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register miscdev %d err =%d \n " ,
WATCHDOG_MINOR , ret ) ;
2011-02-02 19:20:16 +03:00
goto misc_register_error ;
}
ret = request_irq ( ( unsigned int ) watchdog_device . timer_tbl_ptr - > irq ,
watchdog_timer_interrupt ,
IRQF_SHARED , " watchdog " ,
& watchdog_device . timer_load_count_addr ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " error requesting irq %d \n " , ret ) ;
2011-02-02 19:20:16 +03:00
goto request_irq_error ;
}
/* Make sure timer is disabled before returning */
intel_scu_stop ( ) ;
return 0 ;
/* error cleanup */
request_irq_error :
misc_deregister ( & watchdog_device . miscdev ) ;
misc_register_error :
unregister_reboot_notifier ( & watchdog_device . intel_scu_notifier ) ;
register_reboot_error :
intel_scu_stop ( ) ;
iounmap ( watchdog_device . timer_load_count_addr ) ;
return ret ;
}
static void __exit intel_scu_watchdog_exit ( void )
{
misc_deregister ( & watchdog_device . miscdev ) ;
unregister_reboot_notifier ( & watchdog_device . intel_scu_notifier ) ;
/* disable the timer */
iowrite32 ( 0x00000002 , watchdog_device . timer_control_addr ) ;
iounmap ( watchdog_device . timer_load_count_addr ) ;
}
late_initcall ( intel_scu_watchdog_init ) ;
module_exit ( intel_scu_watchdog_exit ) ;
MODULE_AUTHOR ( " Intel Corporation " ) ;
MODULE_DESCRIPTION ( " Intel SCU Watchdog Device Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( WDT_VER ) ;