2007-06-18 10:17:57 +04:00
# include <linux/init.h>
# include <linux/module.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/io.h>
2007-07-21 01:22:58 +04:00
# include <linux/spinlock.h>
2008-05-23 10:42:56 +04:00
# include <linux/of_platform.h>
2007-06-18 10:17:57 +04:00
# include <asm/uaccess.h>
# include <asm/mpc52xx.h>
# define GPT_MODE_WDT (1<<15)
# define GPT_MODE_CE (1<<12)
# define GPT_MODE_MS_TIMER (0x4)
struct mpc5200_wdt {
unsigned count ; /* timer ticks before watchdog kicks in */
long ipb_freq ;
struct miscdevice miscdev ;
struct resource mem ;
struct mpc52xx_gpt __iomem * regs ;
2007-07-21 01:22:58 +04:00
spinlock_t io_lock ;
2007-06-18 10:17:57 +04:00
} ;
2007-07-21 01:22:58 +04:00
/* is_active stores wether or not the /dev/watchdog device is opened */
static unsigned long is_active ;
2007-06-18 10:17:57 +04:00
/* misc devices don't provide a way, to get back to 'dev' or 'miscdev' from
* file operations , which sucks . But there can be max 1 watchdog anyway , so . . .
*/
static struct mpc5200_wdt * wdt_global ;
/* helper to calculate timeout in timer counts */
static void mpc5200_wdt_set_timeout ( struct mpc5200_wdt * wdt , int timeout )
{
/* use biggest prescaler of 64k */
wdt - > count = ( wdt - > ipb_freq + 0xffff ) / 0x10000 * timeout ;
if ( wdt - > count > 0xffff )
wdt - > count = 0xffff ;
}
/* return timeout in seconds (calculated from timer count) */
static int mpc5200_wdt_get_timeout ( struct mpc5200_wdt * wdt )
{
return wdt - > count * 0x10000 / wdt - > ipb_freq ;
}
/* watchdog operations */
static int mpc5200_wdt_start ( struct mpc5200_wdt * wdt )
{
2007-07-21 01:22:58 +04:00
spin_lock ( & wdt - > io_lock ) ;
2007-06-18 10:17:57 +04:00
/* disable */
out_be32 ( & wdt - > regs - > mode , 0 ) ;
/* set timeout, with maximum prescaler */
out_be32 ( & wdt - > regs - > count , 0x0 | wdt - > count ) ;
/* enable watchdog */
out_be32 ( & wdt - > regs - > mode , GPT_MODE_CE | GPT_MODE_WDT | GPT_MODE_MS_TIMER ) ;
2007-07-21 01:22:58 +04:00
spin_unlock ( & wdt - > io_lock ) ;
2007-06-18 10:17:57 +04:00
return 0 ;
}
static int mpc5200_wdt_ping ( struct mpc5200_wdt * wdt )
{
2007-07-21 01:22:58 +04:00
spin_lock ( & wdt - > io_lock ) ;
2007-06-18 10:17:57 +04:00
/* writing A5 to OCPW resets the watchdog */
out_be32 ( & wdt - > regs - > mode , 0xA5000000 | ( 0xffffff & in_be32 ( & wdt - > regs - > mode ) ) ) ;
2007-07-21 01:22:58 +04:00
spin_unlock ( & wdt - > io_lock ) ;
2007-06-18 10:17:57 +04:00
return 0 ;
}
static int mpc5200_wdt_stop ( struct mpc5200_wdt * wdt )
{
2007-07-21 01:22:58 +04:00
spin_lock ( & wdt - > io_lock ) ;
/* disable */
2007-06-18 10:17:57 +04:00
out_be32 ( & wdt - > regs - > mode , 0 ) ;
2007-07-21 01:22:58 +04:00
spin_unlock ( & wdt - > io_lock ) ;
2007-06-18 10:17:57 +04:00
return 0 ;
}
/* file operations */
2007-10-14 22:34:20 +04:00
static ssize_t mpc5200_wdt_write ( struct file * file , const char __user * data ,
2007-06-18 10:17:57 +04:00
size_t len , loff_t * ppos )
{
struct mpc5200_wdt * wdt = file - > private_data ;
mpc5200_wdt_ping ( wdt ) ;
return 0 ;
}
static struct watchdog_info mpc5200_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = " mpc5200 watchdog on GPT0 " ,
} ;
static int mpc5200_wdt_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
struct mpc5200_wdt * wdt = file - > private_data ;
int __user * data = ( int __user * ) arg ;
int timeout ;
int ret = 0 ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2007-07-21 01:22:58 +04:00
ret = copy_to_user ( data , & mpc5200_wdt_info ,
sizeof ( mpc5200_wdt_info ) ) ;
2007-06-18 10:17:57 +04:00
if ( ret )
ret = - EFAULT ;
break ;
2007-07-21 01:22:58 +04:00
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
ret = put_user ( 0 , data ) ;
break ;
2007-06-18 10:17:57 +04:00
case WDIOC_KEEPALIVE :
mpc5200_wdt_ping ( wdt ) ;
break ;
case WDIOC_SETTIMEOUT :
ret = get_user ( timeout , data ) ;
if ( ret )
break ;
mpc5200_wdt_set_timeout ( wdt , timeout ) ;
mpc5200_wdt_start ( wdt ) ;
/* fall through and return the timeout */
case WDIOC_GETTIMEOUT :
timeout = mpc5200_wdt_get_timeout ( wdt ) ;
ret = put_user ( timeout , data ) ;
break ;
2007-07-21 01:22:58 +04:00
default :
ret = - ENOTTY ;
2007-06-18 10:17:57 +04:00
}
return ret ;
}
static int mpc5200_wdt_open ( struct inode * inode , struct file * file )
{
2007-07-21 01:22:58 +04:00
/* /dev/watchdog can only be opened once */
if ( test_and_set_bit ( 0 , & is_active ) )
return - EBUSY ;
/* Set and activate the watchdog */
2007-06-18 10:17:57 +04:00
mpc5200_wdt_set_timeout ( wdt_global , 30 ) ;
mpc5200_wdt_start ( wdt_global ) ;
file - > private_data = wdt_global ;
2007-07-21 01:22:58 +04:00
return nonseekable_open ( inode , file ) ;
2007-06-18 10:17:57 +04:00
}
static int mpc5200_wdt_release ( struct inode * inode , struct file * file )
{
# if WATCHDOG_NOWAYOUT == 0
struct mpc5200_wdt * wdt = file - > private_data ;
mpc5200_wdt_stop ( wdt ) ;
wdt - > count = 0 ; /* == disabled */
# endif
2007-07-21 01:22:58 +04:00
clear_bit ( 0 , & is_active ) ;
2007-06-18 10:17:57 +04:00
return 0 ;
}
2008-01-22 22:48:10 +03:00
static const struct file_operations mpc5200_wdt_fops = {
2007-06-18 10:17:57 +04:00
. owner = THIS_MODULE ,
. write = mpc5200_wdt_write ,
. ioctl = mpc5200_wdt_ioctl ,
. open = mpc5200_wdt_open ,
. release = mpc5200_wdt_release ,
} ;
/* module operations */
static int mpc5200_wdt_probe ( struct of_device * op , const struct of_device_id * match )
{
struct mpc5200_wdt * wdt ;
int err ;
const void * has_wdt ;
int size ;
has_wdt = of_get_property ( op - > node , " has-wdt " , NULL ) ;
2007-10-18 22:44:24 +04:00
if ( ! has_wdt )
has_wdt = of_get_property ( op - > node , " fsl,has-wdt " , NULL ) ;
2007-06-18 10:17:57 +04:00
if ( ! has_wdt )
return - ENODEV ;
wdt = kzalloc ( sizeof ( * wdt ) , GFP_KERNEL ) ;
if ( ! wdt )
return - ENOMEM ;
wdt - > ipb_freq = mpc52xx_find_ipb_freq ( op - > node ) ;
err = of_address_to_resource ( op - > node , 0 , & wdt - > mem ) ;
if ( err )
goto out_free ;
size = wdt - > mem . end - wdt - > mem . start + 1 ;
if ( ! request_mem_region ( wdt - > mem . start , size , " mpc5200_wdt " ) ) {
err = - ENODEV ;
goto out_free ;
}
wdt - > regs = ioremap ( wdt - > mem . start , size ) ;
if ( ! wdt - > regs ) {
err = - ENODEV ;
goto out_release ;
}
dev_set_drvdata ( & op - > dev , wdt ) ;
2007-07-21 01:22:58 +04:00
spin_lock_init ( & wdt - > io_lock ) ;
2007-06-18 10:17:57 +04:00
wdt - > miscdev = ( struct miscdevice ) {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & mpc5200_wdt_fops ,
. parent = & op - > dev ,
} ;
wdt_global = wdt ;
err = misc_register ( & wdt - > miscdev ) ;
if ( ! err )
return 0 ;
iounmap ( wdt - > regs ) ;
out_release :
release_mem_region ( wdt - > mem . start , size ) ;
out_free :
kfree ( wdt ) ;
return err ;
}
static int mpc5200_wdt_remove ( struct of_device * op )
{
struct mpc5200_wdt * wdt = dev_get_drvdata ( & op - > dev ) ;
mpc5200_wdt_stop ( wdt ) ;
misc_deregister ( & wdt - > miscdev ) ;
iounmap ( wdt - > regs ) ;
release_mem_region ( wdt - > mem . start , wdt - > mem . end - wdt - > mem . start + 1 ) ;
kfree ( wdt ) ;
return 0 ;
}
static int mpc5200_wdt_suspend ( struct of_device * op , pm_message_t state )
{
struct mpc5200_wdt * wdt = dev_get_drvdata ( & op - > dev ) ;
mpc5200_wdt_stop ( wdt ) ;
return 0 ;
}
static int mpc5200_wdt_resume ( struct of_device * op )
{
struct mpc5200_wdt * wdt = dev_get_drvdata ( & op - > dev ) ;
if ( wdt - > count )
mpc5200_wdt_start ( wdt ) ;
return 0 ;
}
static int mpc5200_wdt_shutdown ( struct of_device * op )
{
struct mpc5200_wdt * wdt = dev_get_drvdata ( & op - > dev ) ;
mpc5200_wdt_stop ( wdt ) ;
return 0 ;
}
static struct of_device_id mpc5200_wdt_match [ ] = {
{ . compatible = " mpc5200-gpt " , } ,
2007-10-18 22:44:24 +04:00
{ . compatible = " fsl,mpc5200-gpt " , } ,
2007-06-18 10:17:57 +04:00
{ } ,
} ;
static struct of_platform_driver mpc5200_wdt_driver = {
. owner = THIS_MODULE ,
. name = " mpc5200-gpt-wdt " ,
. match_table = mpc5200_wdt_match ,
. probe = mpc5200_wdt_probe ,
. remove = mpc5200_wdt_remove ,
. suspend = mpc5200_wdt_suspend ,
. resume = mpc5200_wdt_resume ,
. shutdown = mpc5200_wdt_shutdown ,
} ;
static int __init mpc5200_wdt_init ( void )
{
return of_register_platform_driver ( & mpc5200_wdt_driver ) ;
}
static void __exit mpc5200_wdt_exit ( void )
{
of_unregister_platform_driver ( & mpc5200_wdt_driver ) ;
}
module_init ( mpc5200_wdt_init ) ;
module_exit ( mpc5200_wdt_exit ) ;
MODULE_AUTHOR ( " Domen Puncer <domen.puncer@telargo.com> " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;
2007-07-21 01:22:58 +04:00
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;