2008-08-30 04:07:01 +04:00
/* cpwd.c - driver implementation for hardware watchdog
2005-04-17 02:20:36 +04:00
* timers found on Sun Microsystems CP1400 and CP1500 boards .
*
2009-03-18 11:05:24 +03:00
* This device supports both the generic Linux watchdog
2005-04-17 02:20:36 +04:00
* interface and Solaris - compatible ioctls as best it is
* able .
*
* NOTE : CP1400 systems appear to have a defective intr_mask
* register on the PLD , preventing the disabling of
2009-03-18 11:05:24 +03:00
* timer interrupts . We use a timer to periodically
2005-04-17 02:20:36 +04:00
* reset ' stopped ' watchdogs on affected platforms .
*
* Copyright ( c ) 2000 Eric Brower ( ebrower @ usa . net )
2008-08-30 04:05:51 +04:00
* Copyright ( C ) 2008 David S . Miller < davem @ davemloft . net >
2005-04-17 02:20:36 +04:00
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/errno.h>
# include <linux/major.h>
# include <linux/init.h>
# include <linux/miscdevice.h>
# include <linux/interrupt.h>
# include <linux/ioport.h>
# include <linux/timer.h>
2005-11-08 01:13:14 +03:00
# include <linux/smp_lock.h>
2007-07-16 10:42:03 +04:00
# include <linux/io.h>
2008-08-30 04:05:51 +04:00
# include <linux/of.h>
# include <linux/of_device.h>
2009-03-18 12:09:26 +03:00
# include <linux/uaccess.h>
2008-08-30 04:05:51 +04:00
2005-04-17 02:20:36 +04:00
# include <asm/irq.h>
# include <asm/watchdog.h>
2008-08-30 04:05:51 +04:00
# define DRIVER_NAME "cpwd"
# define PFX DRIVER_NAME ": "
2005-04-17 02:20:36 +04:00
# define WD_OBPNAME "watchdog"
2008-08-30 04:05:51 +04:00
# define WD_BADMODEL "SUNW,501-5336"
2005-04-17 02:20:36 +04:00
# define WD_BTIMEOUT (jiffies + (HZ * 1000))
# define WD_BLIMIT 0xFFFF
# define WD0_MINOR 212
2009-03-18 11:05:24 +03:00
# define WD1_MINOR 213
# define WD2_MINOR 214
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
/* Internal driver definitions. */
# define WD0_ID 0
# define WD1_ID 1
# define WD2_ID 2
# define WD_NUMDEVS 3
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
# define WD_INTR_OFF 0
# define WD_INTR_ON 1
2005-04-17 02:20:36 +04:00
# define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */
# define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */
# define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred */
/* Register value definitions
*/
# define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */
# define WD1_INTR_MASK 0x02
# define WD2_INTR_MASK 0x04
# define WD_S_RUNNING 0x01 /* Watchdog device status running */
# define WD_S_EXPIRED 0x02 /* Watchdog device status expired */
2008-08-30 04:05:51 +04:00
struct cpwd {
void __iomem * regs ;
spinlock_t lock ;
unsigned int irq ;
unsigned long timeout ;
bool enabled ;
bool reboot ;
bool broken ;
bool initialized ;
struct {
struct miscdevice misc ;
void __iomem * regs ;
u8 intr_mask ;
u8 runstatus ;
u16 timeout ;
} devs [ WD_NUMDEVS ] ;
} ;
static struct cpwd * cpwd_device ;
2009-03-18 11:05:24 +03:00
/* Sun uses Altera PLD EPF8820ATC144-4
2005-04-17 02:20:36 +04:00
* providing three hardware watchdogs :
*
2009-03-18 11:05:24 +03:00
* 1 ) RIC - sends an interrupt when triggered
* 2 ) XIR - asserts XIR_B_RESET when triggered , resets CPU
* 3 ) POR - asserts POR_B_RESET when triggered , resets CPU , backplane , board
2005-04-17 02:20:36 +04:00
*
* * * Timer register block definition ( struct wd_timer_regblk )
*
2009-03-18 11:05:24 +03:00
* dcntr and limit registers ( halfword access ) :
2005-04-17 02:20:36 +04:00
* - - - - - - - - - - - - - - - - - - -
* | 15 | . . . | 1 | 0 |
* - - - - - - - - - - - - - - - - - - -
* | - counter val - |
* - - - - - - - - - - - - - - - - - - -
* dcntr - Current 16 - bit downcounter value .
* When downcounter reaches ' 0 ' watchdog expires .
2009-03-18 11:05:24 +03:00
* Reading this register resets downcounter with
* ' limit ' value .
2005-04-17 02:20:36 +04:00
* limit - 16 - bit countdown value in 1 / 10 th second increments .
* Writing this register begins countdown with input value .
* Reading from this register does not affect counter .
* NOTES : After watchdog reset , dcntr and limit contain ' 1 '
*
* status register ( byte access ) :
* - - - - - - - - - - - - - - - - - - - - - - - - - - -
* | 7 | . . . | 2 | 1 | 0 |
* - - - - - - - - - - - - - - + - - - - - - - - - - - -
* | - UNUSED - | EXP | RUN |
* - - - - - - - - - - - - - - - - - - - - - - - - - - -
* status - Bit 0 - Watchdog is running
* Bit 1 - Watchdog has expired
*
* * * PLD register block definition ( struct wd_pld_regblk )
*
* intr_mask register ( byte access ) :
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* | 7 | . . . | 3 | 2 | 1 | 0 |
* + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - -
* | - UNUSED - | WD3 | WD2 | WD1 |
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* WD3 - 1 = = Interrupt disabled for watchdog 3
* WD2 - 1 = = Interrupt disabled for watchdog 2
* WD1 - 1 = = Interrupt disabled for watchdog 1
*
* pld_status register ( byte access ) :
* UNKNOWN , MAGICAL MYSTERY REGISTER
*
*/
# define WD_TIMER_REGSZ 16
# define WD0_OFF 0
# define WD1_OFF (WD_TIMER_REGSZ * 1)
# define WD2_OFF (WD_TIMER_REGSZ * 2)
# define PLD_OFF (WD_TIMER_REGSZ * 3)
# define WD_DCNTR 0x00
# define WD_LIMIT 0x04
# define WD_STATUS 0x08
# define PLD_IMASK (PLD_OFF + 0x00)
# define PLD_STATUS (PLD_OFF + 0x04)
2008-08-30 04:05:51 +04:00
static struct timer_list cpwd_timer ;
2005-04-17 02:20:36 +04:00
static int wd0_timeout = 0 ;
static int wd1_timeout = 0 ;
static int wd2_timeout = 0 ;
2009-03-18 11:05:24 +03:00
module_param ( wd0_timeout , int , 0 ) ;
2005-04-17 02:20:36 +04:00
MODULE_PARM_DESC ( wd0_timeout , " Default watchdog0 timeout in 1/10secs " ) ;
2009-03-18 11:05:24 +03:00
module_param ( wd1_timeout , int , 0 ) ;
2005-04-17 02:20:36 +04:00
MODULE_PARM_DESC ( wd1_timeout , " Default watchdog1 timeout in 1/10secs " ) ;
2009-03-18 11:05:24 +03:00
module_param ( wd2_timeout , int , 0 ) ;
2005-04-17 02:20:36 +04:00
MODULE_PARM_DESC ( wd2_timeout , " Default watchdog2 timeout in 1/10secs " ) ;
2008-08-30 04:05:51 +04:00
MODULE_AUTHOR ( " Eric Brower <ebrower@usa.net> " ) ;
MODULE_DESCRIPTION ( " Hardware watchdog driver for Sun Microsystems CP1400/1500 " ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
2008-08-30 04:05:51 +04:00
MODULE_SUPPORTED_DEVICE ( " watchdog " ) ;
static void cpwd_writew ( u16 val , void __iomem * addr )
{
writew ( cpu_to_le16 ( val ) , addr ) ;
}
static u16 cpwd_readw ( void __iomem * addr )
{
u16 val = readw ( addr ) ;
return le16_to_cpu ( val ) ;
}
static void cpwd_writeb ( u8 val , void __iomem * addr )
{
writeb ( val , addr ) ;
}
static u8 cpwd_readb ( void __iomem * addr )
{
return readb ( addr ) ;
}
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
/* Enable or disable watchdog interrupts
* Because of the CP1400 defect this should only be
* called during initialzation or by wd_ [ start | stop ] timer ( )
*
* index - sub - device index , or - 1 for ' all '
* enable - non - zero to enable interrupts , zero to disable
2005-04-17 02:20:36 +04:00
*/
2008-08-30 04:05:51 +04:00
static void cpwd_toggleintr ( struct cpwd * p , int index , int enable )
{
unsigned char curregs = cpwd_readb ( p - > regs + PLD_IMASK ) ;
2009-03-18 11:05:24 +03:00
unsigned char setregs =
( index = = - 1 ) ?
( WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK ) :
2008-08-30 04:05:51 +04:00
( p - > devs [ index ] . intr_mask ) ;
if ( enable = = WD_INTR_ON )
curregs & = ~ setregs ;
else
curregs | = setregs ;
cpwd_writeb ( curregs , p - > regs + PLD_IMASK ) ;
}
/* Restarts timer with maximum limit value and
* does not unset ' brokenstop ' value .
2005-04-17 02:20:36 +04:00
*/
2008-08-30 04:05:51 +04:00
static void cpwd_resetbrokentimer ( struct cpwd * p , int index )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
cpwd_toggleintr ( p , index , WD_INTR_ON ) ;
cpwd_writew ( WD_BLIMIT , p - > devs [ index ] . regs + WD_LIMIT ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
/* Timer method called to reset stopped watchdogs--
* because of the PLD bug on CP1400 , we cannot mask
* interrupts within the PLD so me must continually
* reset the timers ad infinitum .
*/
static void cpwd_brokentimer ( unsigned long data )
{
struct cpwd * p = ( struct cpwd * ) data ;
int id , tripped = 0 ;
/* kill a running timer instance, in case we
* were called directly instead of by kernel timer
*/
if ( timer_pending ( & cpwd_timer ) )
del_timer ( & cpwd_timer ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
for ( id = 0 ; id < WD_NUMDEVS ; id + + ) {
if ( p - > devs [ id ] . runstatus & WD_STAT_BSTOP ) {
+ + tripped ;
cpwd_resetbrokentimer ( p , id ) ;
}
}
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
if ( tripped ) {
/* there is at least one timer brokenstopped-- reschedule */
cpwd_timer . expires = WD_BTIMEOUT ;
add_timer ( & cpwd_timer ) ;
}
}
/* Reset countdown timer with 'limit' value and continue countdown.
* This will not start a stopped timer .
2005-04-17 02:20:36 +04:00
*/
2008-08-30 04:05:51 +04:00
static void cpwd_pingtimer ( struct cpwd * p , int index )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
if ( cpwd_readb ( p - > devs [ index ] . regs + WD_STATUS ) & WD_S_RUNNING )
cpwd_readw ( p - > devs [ index ] . regs + WD_DCNTR ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
/* Stop a running watchdog timer-- the timer actually keeps
* running , but the interrupt is masked so that no action is
* taken upon expiration .
2005-04-17 02:20:36 +04:00
*/
2008-08-30 04:05:51 +04:00
static void cpwd_stoptimer ( struct cpwd * p , int index )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
if ( cpwd_readb ( p - > devs [ index ] . regs + WD_STATUS ) & WD_S_RUNNING ) {
cpwd_toggleintr ( p , index , WD_INTR_OFF ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
if ( p - > broken ) {
p - > devs [ index ] . runstatus | = WD_STAT_BSTOP ;
cpwd_brokentimer ( ( unsigned long ) p ) ;
}
}
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
/* Start a watchdog timer with the specified limit value
* If the watchdog is running , it will be restarted with
* the provided limit value .
*
* This function will enable interrupts on the specified
* watchdog .
2005-04-17 02:20:36 +04:00
*/
2008-08-30 04:05:51 +04:00
static void cpwd_starttimer ( struct cpwd * p , int index )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
if ( p - > broken )
p - > devs [ index ] . runstatus & = ~ WD_STAT_BSTOP ;
p - > devs [ index ] . runstatus & = ~ WD_STAT_SVCD ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
cpwd_writew ( p - > devs [ index ] . timeout , p - > devs [ index ] . regs + WD_LIMIT ) ;
cpwd_toggleintr ( p , index , WD_INTR_ON ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static int cpwd_getstatus ( struct cpwd * p , int index )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
unsigned char stat = cpwd_readb ( p - > devs [ index ] . regs + WD_STATUS ) ;
unsigned char intr = cpwd_readb ( p - > devs [ index ] . regs + PLD_IMASK ) ;
unsigned char ret = WD_STOPPED ;
/* determine STOPPED */
2009-03-18 11:05:24 +03:00
if ( ! stat )
2008-08-30 04:05:51 +04:00
return ret ;
/* determine EXPIRED vs FREERUN vs RUNNING */
else if ( WD_S_EXPIRED & stat ) {
ret = WD_EXPIRED ;
2009-03-18 11:05:24 +03:00
} else if ( WD_S_RUNNING & stat ) {
2008-08-30 04:05:51 +04:00
if ( intr & p - > devs [ index ] . intr_mask ) {
ret = WD_FREERUN ;
} else {
/* Fudge WD_EXPIRED status for defective CP1400--
2009-03-18 11:05:24 +03:00
* IF timer is running
* AND brokenstop is set
2008-08-30 04:05:51 +04:00
* AND an interrupt has been serviced
* we are WD_EXPIRED .
*
2009-03-18 11:05:24 +03:00
* IF timer is running
* AND brokenstop is set
2008-08-30 04:05:51 +04:00
* AND no interrupt has been serviced
* we are WD_FREERUN .
*/
if ( p - > broken & &
( p - > devs [ index ] . runstatus & WD_STAT_BSTOP ) ) {
if ( p - > devs [ index ] . runstatus & WD_STAT_SVCD ) {
ret = WD_EXPIRED ;
} else {
2009-03-18 11:05:24 +03:00
/* we could as well pretend
* we are expired */
2008-08-30 04:05:51 +04:00
ret = WD_FREERUN ;
}
} else {
ret = WD_RUNNING ;
2005-04-17 02:20:36 +04:00
}
}
}
2008-08-30 04:05:51 +04:00
/* determine SERVICED */
if ( p - > devs [ index ] . runstatus & WD_STAT_SVCD )
ret | = WD_SERVICED ;
2009-03-18 11:05:24 +03:00
return ret ;
2008-08-30 04:05:51 +04:00
}
static irqreturn_t cpwd_interrupt ( int irq , void * dev_id )
{
struct cpwd * p = dev_id ;
/* Only WD0 will interrupt-- others are NMI and we won't
* see them here . . . .
*/
spin_lock_irq ( & p - > lock ) ;
cpwd_stoptimer ( p , WD0_ID ) ;
p - > devs [ WD0_ID ] . runstatus | = WD_STAT_SVCD ;
spin_unlock_irq ( & p - > lock ) ;
return IRQ_HANDLED ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static int cpwd_open ( struct inode * inode , struct file * f )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
struct cpwd * p = cpwd_device ;
2008-05-20 21:15:43 +04:00
lock_kernel ( ) ;
2009-03-18 11:05:24 +03:00
switch ( iminor ( inode ) ) {
case WD0_MINOR :
case WD1_MINOR :
case WD2_MINOR :
break ;
2008-08-30 04:05:51 +04:00
2009-03-18 11:05:24 +03:00
default :
unlock_kernel ( ) ;
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
/* Register IRQ on first open of device */
2008-08-30 04:05:51 +04:00
if ( ! p - > initialized ) {
2009-03-18 11:05:24 +03:00
if ( request_irq ( p - > irq , & cpwd_interrupt ,
2008-08-30 04:05:51 +04:00
IRQF_SHARED , DRIVER_NAME , p ) ) {
2009-03-18 11:05:24 +03:00
printk ( KERN_ERR PFX " Cannot register IRQ %d \n " ,
2008-08-30 04:05:51 +04:00
p - > irq ) ;
2008-05-20 21:15:43 +04:00
unlock_kernel ( ) ;
2008-08-30 04:05:51 +04:00
return - EBUSY ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
p - > initialized = true ;
2005-04-17 02:20:36 +04:00
}
2008-05-20 21:15:43 +04:00
unlock_kernel ( ) ;
2008-08-30 04:05:51 +04:00
return nonseekable_open ( inode , f ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static int cpwd_release ( struct inode * inode , struct file * file )
2005-04-17 02:20:36 +04:00
{
return 0 ;
}
2009-01-21 14:13:11 +03:00
static long cpwd_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
static struct watchdog_info info = {
. options = WDIOF_SETTIMEOUT ,
. firmware_version = 1 ,
. identity = DRIVER_NAME ,
2005-04-17 02:20:36 +04:00
} ;
2008-08-30 04:05:51 +04:00
void __user * argp = ( void __user * ) arg ;
2009-01-21 14:13:11 +03:00
struct inode * inode = file - > f_path . dentry - > d_inode ;
2008-08-30 04:05:51 +04:00
int index = iminor ( inode ) - WD0_MINOR ;
struct cpwd * p = cpwd_device ;
int setopt = 0 ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
switch ( cmd ) {
/* Generic Linux IOCTLs */
case WDIOC_GETSUPPORT :
if ( copy_to_user ( argp , & info , sizeof ( struct watchdog_info ) ) )
return - EFAULT ;
break ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
if ( put_user ( 0 , ( int __user * ) argp ) )
return - EFAULT ;
break ;
case WDIOC_KEEPALIVE :
cpwd_pingtimer ( p , index ) ;
break ;
case WDIOC_SETOPTIONS :
if ( copy_from_user ( & setopt , argp , sizeof ( unsigned int ) ) )
return - EFAULT ;
if ( setopt & WDIOS_DISABLECARD ) {
if ( p - > enabled )
return - EINVAL ;
cpwd_stoptimer ( p , index ) ;
} else if ( setopt & WDIOS_ENABLECARD ) {
cpwd_starttimer ( p , index ) ;
} else {
return - EINVAL ;
2009-03-18 11:05:24 +03:00
}
2008-08-30 04:05:51 +04:00
break ;
/* Solaris-compatible IOCTLs */
case WIOCGSTAT :
setopt = cpwd_getstatus ( p , index ) ;
if ( copy_to_user ( argp , & setopt , sizeof ( unsigned int ) ) )
return - EFAULT ;
break ;
case WIOCSTART :
cpwd_starttimer ( p , index ) ;
break ;
case WIOCSTOP :
if ( p - > enabled )
2009-03-18 11:05:24 +03:00
return - EINVAL ;
2008-08-30 04:05:51 +04:00
cpwd_stoptimer ( p , index ) ;
break ;
default :
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static long cpwd_compat_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-11-08 01:13:14 +03:00
{
int rval = - ENOIOCTLCMD ;
switch ( cmd ) {
/* solaris ioctls are specific to this driver */
case WIOCSTART :
case WIOCSTOP :
case WIOCGSTAT :
lock_kernel ( ) ;
2009-01-21 14:13:11 +03:00
rval = cpwd_ioctl ( file , cmd , arg ) ;
2005-11-09 23:05:37 +03:00
unlock_kernel ( ) ;
2005-11-08 01:13:14 +03:00
break ;
2008-08-30 04:05:51 +04:00
2005-11-08 01:13:14 +03:00
/* everything else is handled by the generic compat layer */
default :
break ;
}
return rval ;
}
2009-03-18 11:05:24 +03:00
static ssize_t cpwd_write ( struct file * file , const char __user * buf ,
2008-08-30 04:05:51 +04:00
size_t count , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
struct inode * inode = file - > f_path . dentry - > d_inode ;
struct cpwd * p = cpwd_device ;
int index = iminor ( inode ) ;
2005-04-17 02:20:36 +04:00
if ( count ) {
2008-08-30 04:05:51 +04:00
cpwd_pingtimer ( p , index ) ;
2005-04-17 02:20:36 +04:00
return 1 ;
}
2008-08-30 04:05:51 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2009-03-18 11:05:24 +03:00
static ssize_t cpwd_read ( struct file * file , char __user * buffer ,
2008-08-30 04:05:51 +04:00
size_t count , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static const struct file_operations cpwd_fops = {
2009-01-21 14:13:11 +03:00
. owner = THIS_MODULE ,
. unlocked_ioctl = cpwd_ioctl ,
. compat_ioctl = cpwd_compat_ioctl ,
. open = cpwd_open ,
. write = cpwd_write ,
. read = cpwd_read ,
. release = cpwd_release ,
2005-04-17 02:20:36 +04:00
} ;
2008-08-30 04:05:51 +04:00
static int __devinit cpwd_probe ( struct of_device * op ,
const struct of_device_id * match )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
struct device_node * options ;
const char * str_prop ;
const void * prop_val ;
int i , err = - EINVAL ;
struct cpwd * p ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
if ( cpwd_device )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
p = kzalloc ( sizeof ( * p ) , GFP_KERNEL ) ;
err = - ENOMEM ;
if ( ! p ) {
printk ( KERN_ERR PFX " Unable to allocate struct cpwd. \n " ) ;
goto out ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
p - > irq = op - > irqs [ 0 ] ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
spin_lock_init ( & p - > lock ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
p - > regs = of_ioremap ( & op - > resource [ 0 ] , 0 ,
4 * WD_TIMER_REGSZ , DRIVER_NAME ) ;
if ( ! p - > regs ) {
printk ( KERN_ERR PFX " Unable to map registers. \n " ) ;
goto out_free ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
options = of_find_node_by_path ( " /options " ) ;
err = - ENODEV ;
if ( ! options ) {
printk ( KERN_ERR PFX " Unable to find /options node. \n " ) ;
goto out_iounmap ;
}
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
prop_val = of_get_property ( options , " watchdog-enable? " , NULL ) ;
p - > enabled = ( prop_val ? true : false ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
prop_val = of_get_property ( options , " watchdog-reboot? " , NULL ) ;
p - > reboot = ( prop_val ? true : false ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
str_prop = of_get_property ( options , " watchdog-timeout " , NULL ) ;
if ( str_prop )
p - > timeout = simple_strtoul ( str_prop , NULL , 10 ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
/* CP1400s seem to have broken PLD implementations-- the
* interrupt_mask register cannot be written , so no timer
* interrupts can be masked within the PLD .
2005-04-17 02:20:36 +04:00
*/
2008-08-30 04:05:51 +04:00
str_prop = of_get_property ( op - > node , " model " , NULL ) ;
p - > broken = ( str_prop & & ! strcmp ( str_prop , WD_BADMODEL ) ) ;
if ( ! p - > enabled )
cpwd_toggleintr ( p , - 1 , WD_INTR_OFF ) ;
for ( i = 0 ; i < WD_NUMDEVS ; i + + ) {
static const char * cpwd_names [ ] = { " RIC " , " XIR " , " POR " } ;
static int * parms [ ] = { & wd0_timeout ,
& wd1_timeout ,
& wd2_timeout } ;
struct miscdevice * mp = & p - > devs [ i ] . misc ;
mp - > minor = WD0_MINOR + i ;
mp - > name = cpwd_names [ i ] ;
mp - > fops = & cpwd_fops ;
p - > devs [ i ] . regs = p - > regs + ( i * WD_TIMER_REGSZ ) ;
p - > devs [ i ] . intr_mask = ( WD0_INTR_MASK < < i ) ;
p - > devs [ i ] . runstatus & = ~ WD_STAT_BSTOP ;
p - > devs [ i ] . runstatus | = WD_STAT_INIT ;
p - > devs [ i ] . timeout = p - > timeout ;
if ( * parms [ i ] )
p - > devs [ i ] . timeout = * parms [ i ] ;
err = misc_register ( & p - > devs [ i ] . misc ) ;
if ( err ) {
printk ( KERN_ERR " Could not register misc device for "
" dev %d \n " , i ) ;
goto out_unregister ;
2005-04-17 02:20:36 +04:00
}
}
2008-08-30 04:05:51 +04:00
if ( p - > broken ) {
init_timer ( & cpwd_timer ) ;
cpwd_timer . function = cpwd_brokentimer ;
cpwd_timer . data = ( unsigned long ) p ;
cpwd_timer . expires = WD_BTIMEOUT ;
printk ( KERN_INFO PFX " PLD defect workaround enabled for "
" model " WD_BADMODEL " . \n " ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
dev_set_drvdata ( & op - > dev , p ) ;
cpwd_device = p ;
err = 0 ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
out :
return err ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
out_unregister :
for ( i - - ; i > = 0 ; i - - )
misc_deregister ( & p - > devs [ i ] . misc ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
out_iounmap :
of_iounmap ( & op - > resource [ 0 ] , p - > regs , 4 * WD_TIMER_REGSZ ) ;
out_free :
kfree ( p ) ;
goto out ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static int __devexit cpwd_remove ( struct of_device * op )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
struct cpwd * p = dev_get_drvdata ( & op - > dev ) ;
int i ;
for ( i = 0 ; i < 4 ; i + + ) {
misc_deregister ( & p - > devs [ i ] . misc ) ;
if ( ! p - > enabled ) {
cpwd_stoptimer ( p , i ) ;
if ( p - > devs [ i ] . runstatus & WD_STAT_BSTOP )
cpwd_resetbrokentimer ( p , i ) ;
2005-04-17 02:20:36 +04:00
}
}
2008-08-30 04:05:51 +04:00
if ( p - > broken )
del_timer_sync ( & cpwd_timer ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
if ( p - > initialized )
free_irq ( p - > irq , p ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
of_iounmap ( & op - > resource [ 0 ] , p - > regs , 4 * WD_TIMER_REGSZ ) ;
kfree ( p ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
cpwd_device = NULL ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
2008-08-31 12:23:17 +04:00
static const struct of_device_id cpwd_match [ ] = {
2008-08-30 04:05:51 +04:00
{
. name = " watchdog " ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cpwd_match ) ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
static struct of_platform_driver cpwd_driver = {
. name = DRIVER_NAME ,
. match_table = cpwd_match ,
. probe = cpwd_probe ,
. remove = __devexit_p ( cpwd_remove ) ,
} ;
2005-04-17 02:20:36 +04:00
2008-08-30 04:05:51 +04:00
static int __init cpwd_init ( void )
{
return of_register_driver ( & cpwd_driver , & of_bus_type ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
static void __exit cpwd_exit ( void )
2005-04-17 02:20:36 +04:00
{
2008-08-30 04:05:51 +04:00
of_unregister_driver ( & cpwd_driver ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-30 04:05:51 +04:00
module_init ( cpwd_init ) ;
module_exit ( cpwd_exit ) ;