2006-09-29 12:59:18 +04:00
/*
2008-09-20 05:14:01 +04:00
* omap_wdt . c
2006-09-29 12:59:18 +04:00
*
2008-09-20 05:14:01 +04:00
* Watchdog driver for the TI OMAP 16 xx & 24 xx / 34 xx 32 KHz ( non - secure ) watchdog
2006-09-29 12:59:18 +04:00
*
* Author : MontaVista Software , Inc .
* < gdavis @ mvista . com > or < source @ mvista . com >
*
* 2003 ( c ) MontaVista Software , Inc . This file is licensed under the
* terms of the GNU General Public License version 2. This program is
* licensed " as is " without any warranty of any kind , whether express
* or implied .
*
* History :
*
* 20030527 : George G . Davis < gdavis @ mvista . com >
* Initially based on linux - 2.4 .19 - rmk7 - pxa1 / drivers / char / sa1100_wdt . c
* ( c ) Copyright 2000 Oleg Drokin < green @ crimea . edu >
2008-10-27 18:17:56 +03:00
* Based on SoftDog driver by Alan Cox < alan @ lxorguk . ukuu . org . uk >
2006-09-29 12:59:18 +04:00
*
* Copyright ( c ) 2004 Texas Instruments .
* 1. Modified to support OMAP1610 32 - KHz watchdog timer
* 2. Ported to 2.6 kernel
*
* Copyright ( c ) 2005 David Brownell
* Use the driver model and standard identifiers ; handle bigger timeouts .
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/reboot.h>
# include <linux/init.h>
# include <linux/err.h>
# include <linux/platform_device.h>
# include <linux/moduleparam.h>
# include <linux/clk.h>
2007-10-19 10:40:25 +04:00
# include <linux/bitops.h>
2008-07-15 15:46:11 +04:00
# include <linux/io.h>
2008-05-19 17:07:32 +04:00
# include <linux/uaccess.h>
2008-08-05 19:14:15 +04:00
# include <mach/hardware.h>
2009-10-20 20:40:47 +04:00
# include <plat/prcm.h>
2006-09-29 12:59:18 +04:00
# include "omap_wdt.h"
2008-09-20 05:14:01 +04:00
static struct platform_device * omap_wdt_dev ;
2006-09-29 12:59:18 +04:00
static unsigned timer_margin ;
module_param ( timer_margin , uint , 0 ) ;
MODULE_PARM_DESC ( timer_margin , " initial watchdog timeout (in seconds) " ) ;
static unsigned int wdt_trgr_pattern = 0x1234 ;
2008-05-19 17:07:32 +04:00
static spinlock_t wdt_lock ;
2006-09-29 12:59:18 +04:00
2008-09-20 05:14:01 +04:00
struct omap_wdt_dev {
void __iomem * base ; /* physical */
struct device * dev ;
int omap_wdt_users ;
2009-01-19 23:44:33 +03:00
struct clk * ick ;
struct clk * fck ;
2008-09-20 05:14:01 +04:00
struct resource * mem ;
struct miscdevice omap_wdt_miscdev ;
} ;
static void omap_wdt_ping ( struct omap_wdt_dev * wdev )
2006-09-29 12:59:18 +04:00
{
2008-09-20 05:14:01 +04:00
void __iomem * base = wdev - > base ;
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
/* wait for posted write to complete */
2008-09-20 05:14:02 +04:00
while ( ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) ) & 0x08 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
wdt_trgr_pattern = ~ wdt_trgr_pattern ;
2008-09-20 05:14:02 +04:00
__raw_writel ( wdt_trgr_pattern , ( base + OMAP_WATCHDOG_TGR ) ) ;
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
/* wait for posted write to complete */
2008-09-20 05:14:02 +04:00
while ( ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) ) & 0x08 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
/* reloaded WCRR from WLDR */
}
2008-09-20 05:14:01 +04:00
static void omap_wdt_enable ( struct omap_wdt_dev * wdev )
2006-09-29 12:59:18 +04:00
{
2008-09-20 05:14:03 +04:00
void __iomem * base = wdev - > base ;
2006-09-29 12:59:18 +04:00
/* Sequence to enable the watchdog */
2008-09-20 05:14:02 +04:00
__raw_writel ( 0xBBBB , base + OMAP_WATCHDOG_SPR ) ;
while ( ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) ) & 0x10 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
2008-09-20 05:14:03 +04:00
2008-09-20 05:14:02 +04:00
__raw_writel ( 0x4444 , base + OMAP_WATCHDOG_SPR ) ;
while ( ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) ) & 0x10 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
}
2008-09-20 05:14:01 +04:00
static void omap_wdt_disable ( struct omap_wdt_dev * wdev )
2006-09-29 12:59:18 +04:00
{
2008-09-20 05:14:03 +04:00
void __iomem * base = wdev - > base ;
2006-09-29 12:59:18 +04:00
/* sequence required to disable watchdog */
2008-09-20 05:14:02 +04:00
__raw_writel ( 0xAAAA , base + OMAP_WATCHDOG_SPR ) ; /* TIMER_MODE */
while ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) & 0x10 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
2008-09-20 05:14:03 +04:00
2008-09-20 05:14:02 +04:00
__raw_writel ( 0x5555 , base + OMAP_WATCHDOG_SPR ) ; /* TIMER_MODE */
while ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) & 0x10 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
}
static void omap_wdt_adjust_timeout ( unsigned new_timeout )
{
if ( new_timeout < TIMER_MARGIN_MIN )
new_timeout = TIMER_MARGIN_DEFAULT ;
if ( new_timeout > TIMER_MARGIN_MAX )
new_timeout = TIMER_MARGIN_MAX ;
timer_margin = new_timeout ;
}
2008-09-20 05:14:01 +04:00
static void omap_wdt_set_timeout ( struct omap_wdt_dev * wdev )
2006-09-29 12:59:18 +04:00
{
u32 pre_margin = GET_WLDR_VAL ( timer_margin ) ;
2008-09-20 05:14:03 +04:00
void __iomem * base = wdev - > base ;
2006-09-29 12:59:18 +04:00
/* just count up at 32 KHz */
2008-09-20 05:14:02 +04:00
while ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) & 0x04 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
2008-09-20 05:14:03 +04:00
2008-09-20 05:14:02 +04:00
__raw_writel ( pre_margin , base + OMAP_WATCHDOG_LDR ) ;
while ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) & 0x04 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
}
/*
* Allow only one task to hold it open
*/
static int omap_wdt_open ( struct inode * inode , struct file * file )
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = platform_get_drvdata ( omap_wdt_dev ) ;
void __iomem * base = wdev - > base ;
2008-09-20 05:14:01 +04:00
if ( test_and_set_bit ( 1 , ( unsigned long * ) & ( wdev - > omap_wdt_users ) ) )
2006-09-29 12:59:18 +04:00
return - EBUSY ;
2009-01-23 15:48:37 +03:00
clk_enable ( wdev - > ick ) ; /* Enable the interface clock */
2009-01-19 23:44:33 +03:00
clk_enable ( wdev - > fck ) ; /* Enable the functional clock */
2006-09-29 12:59:18 +04:00
/* initialize prescaler */
2008-09-20 05:14:02 +04:00
while ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) & 0x01 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
2008-09-20 05:14:03 +04:00
2008-09-20 05:14:02 +04:00
__raw_writel ( ( 1 < < 5 ) | ( PTV < < 2 ) , base + OMAP_WATCHDOG_CNTRL ) ;
while ( __raw_readl ( base + OMAP_WATCHDOG_WPS ) & 0x01 )
2006-09-29 12:59:18 +04:00
cpu_relax ( ) ;
2008-09-20 05:14:01 +04:00
file - > private_data = ( void * ) wdev ;
omap_wdt_set_timeout ( wdev ) ;
2009-06-13 01:18:32 +04:00
omap_wdt_ping ( wdev ) ; /* trigger loading of new timeout value */
2008-09-20 05:14:01 +04:00
omap_wdt_enable ( wdev ) ;
2008-09-20 05:14:03 +04:00
2007-07-21 00:41:37 +04:00
return nonseekable_open ( inode , file ) ;
2006-09-29 12:59:18 +04:00
}
static int omap_wdt_release ( struct inode * inode , struct file * file )
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = file - > private_data ;
2006-09-29 12:59:18 +04:00
/*
* Shut off the timer unless NOWAYOUT is defined .
*/
# ifndef CONFIG_WATCHDOG_NOWAYOUT
2008-09-20 05:14:01 +04:00
omap_wdt_disable ( wdev ) ;
2006-09-29 12:59:18 +04:00
2009-01-23 15:48:37 +03:00
clk_disable ( wdev - > ick ) ;
2009-01-19 23:44:33 +03:00
clk_disable ( wdev - > fck ) ;
2006-09-29 12:59:18 +04:00
# else
printk ( KERN_CRIT " omap_wdt: Unexpected close, not stopping! \n " ) ;
# endif
2008-09-20 05:14:01 +04:00
wdev - > omap_wdt_users = 0 ;
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
return 0 ;
}
2008-05-19 17:07:32 +04:00
static ssize_t omap_wdt_write ( struct file * file , const char __user * data ,
2006-09-29 12:59:18 +04:00
size_t len , loff_t * ppos )
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = file - > private_data ;
2006-09-29 12:59:18 +04:00
/* Refresh LOAD_TIME. */
2008-05-19 17:07:32 +04:00
if ( len ) {
spin_lock ( & wdt_lock ) ;
2008-09-20 05:14:01 +04:00
omap_wdt_ping ( wdev ) ;
2008-05-19 17:07:32 +04:00
spin_unlock ( & wdt_lock ) ;
}
2006-09-29 12:59:18 +04:00
return len ;
}
2008-05-19 17:07:32 +04:00
static long omap_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2006-09-29 12:59:18 +04:00
{
2008-09-20 05:14:01 +04:00
struct omap_wdt_dev * wdev ;
2006-09-29 12:59:18 +04:00
int new_margin ;
2008-05-19 17:07:32 +04:00
static const struct watchdog_info ident = {
2006-09-29 12:59:18 +04:00
. identity = " OMAP Watchdog " ,
. options = WDIOF_SETTIMEOUT ,
. firmware_version = 0 ,
} ;
2008-09-20 05:14:03 +04:00
2008-09-20 05:14:01 +04:00
wdev = file - > private_data ;
2006-09-29 12:59:18 +04:00
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( ( struct watchdog_info __user * ) arg , & ident ,
sizeof ( ident ) ) ;
case WDIOC_GETSTATUS :
return put_user ( 0 , ( int __user * ) arg ) ;
case WDIOC_GETBOOTSTATUS :
if ( cpu_is_omap16xx ( ) )
2008-09-20 05:14:02 +04:00
return put_user ( __raw_readw ( ARM_SYSST ) ,
2006-09-29 12:59:18 +04:00
( int __user * ) arg ) ;
if ( cpu_is_omap24xx ( ) )
return put_user ( omap_prcm_get_reset_sources ( ) ,
( int __user * ) arg ) ;
case WDIOC_KEEPALIVE :
2008-05-19 17:07:32 +04:00
spin_lock ( & wdt_lock ) ;
2008-09-20 05:14:01 +04:00
omap_wdt_ping ( wdev ) ;
2008-05-19 17:07:32 +04:00
spin_unlock ( & wdt_lock ) ;
2006-09-29 12:59:18 +04:00
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , ( int __user * ) arg ) )
return - EFAULT ;
omap_wdt_adjust_timeout ( new_margin ) ;
2008-05-19 17:07:32 +04:00
spin_lock ( & wdt_lock ) ;
2008-09-20 05:14:01 +04:00
omap_wdt_disable ( wdev ) ;
omap_wdt_set_timeout ( wdev ) ;
omap_wdt_enable ( wdev ) ;
2006-09-29 12:59:18 +04:00
2008-09-20 05:14:01 +04:00
omap_wdt_ping ( wdev ) ;
2008-05-19 17:07:32 +04:00
spin_unlock ( & wdt_lock ) ;
2006-09-29 12:59:18 +04:00
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( timer_margin , ( int __user * ) arg ) ;
2008-07-18 15:41:17 +04:00
default :
return - ENOTTY ;
2006-09-29 12:59:18 +04:00
}
}
2007-02-12 11:55:32 +03:00
static const struct file_operations omap_wdt_fops = {
2006-09-29 12:59:18 +04:00
. owner = THIS_MODULE ,
. write = omap_wdt_write ,
2008-05-19 17:07:32 +04:00
. unlocked_ioctl = omap_wdt_ioctl ,
2006-09-29 12:59:18 +04:00
. open = omap_wdt_open ,
. release = omap_wdt_release ,
} ;
2009-03-28 02:26:56 +03:00
static int __devinit omap_wdt_probe ( struct platform_device * pdev )
2006-09-29 12:59:18 +04:00
{
struct resource * res , * mem ;
2008-09-20 05:14:01 +04:00
struct omap_wdt_dev * wdev ;
2008-09-20 05:14:03 +04:00
int ret ;
2006-09-29 12:59:18 +04:00
/* reserve static register mappings */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2008-09-20 05:14:03 +04:00
if ( ! res ) {
ret = - ENOENT ;
goto err_get_resource ;
}
2006-09-29 12:59:18 +04:00
2008-09-20 05:14:03 +04:00
if ( omap_wdt_dev ) {
ret = - EBUSY ;
goto err_busy ;
}
2008-09-20 05:14:01 +04:00
2006-09-29 12:59:18 +04:00
mem = request_mem_region ( res - > start , res - > end - res - > start + 1 ,
pdev - > name ) ;
2008-09-20 05:14:03 +04:00
if ( ! mem ) {
ret = - EBUSY ;
goto err_busy ;
}
2006-09-29 12:59:18 +04:00
2008-09-20 05:14:01 +04:00
wdev = kzalloc ( sizeof ( struct omap_wdt_dev ) , GFP_KERNEL ) ;
if ( ! wdev ) {
ret = - ENOMEM ;
2008-09-20 05:14:03 +04:00
goto err_kzalloc ;
2008-09-20 05:14:01 +04:00
}
2008-09-20 05:14:03 +04:00
2008-09-20 05:14:01 +04:00
wdev - > omap_wdt_users = 0 ;
wdev - > mem = mem ;
2006-09-29 12:59:18 +04:00
2009-01-23 15:48:37 +03:00
wdev - > ick = clk_get ( & pdev - > dev , " ick " ) ;
if ( IS_ERR ( wdev - > ick ) ) {
ret = PTR_ERR ( wdev - > ick ) ;
wdev - > ick = NULL ;
goto err_clk ;
2006-09-29 12:59:18 +04:00
}
2009-01-19 23:44:33 +03:00
wdev - > fck = clk_get ( & pdev - > dev , " fck " ) ;
if ( IS_ERR ( wdev - > fck ) ) {
ret = PTR_ERR ( wdev - > fck ) ;
wdev - > fck = NULL ;
goto err_clk ;
2008-09-20 05:14:01 +04:00
}
2008-09-20 05:14:02 +04:00
wdev - > base = ioremap ( res - > start , res - > end - res - > start + 1 ) ;
if ( ! wdev - > base ) {
ret = - ENOMEM ;
2008-09-20 05:14:03 +04:00
goto err_ioremap ;
2008-09-20 05:14:02 +04:00
}
2008-09-20 05:14:01 +04:00
platform_set_drvdata ( pdev , wdev ) ;
2006-09-29 12:59:18 +04:00
2009-06-13 01:18:32 +04:00
clk_enable ( wdev - > ick ) ;
clk_enable ( wdev - > fck ) ;
2008-09-20 05:14:01 +04:00
omap_wdt_disable ( wdev ) ;
2006-09-29 12:59:18 +04:00
omap_wdt_adjust_timeout ( timer_margin ) ;
2008-09-20 05:14:01 +04:00
wdev - > omap_wdt_miscdev . parent = & pdev - > dev ;
wdev - > omap_wdt_miscdev . minor = WATCHDOG_MINOR ;
wdev - > omap_wdt_miscdev . name = " watchdog " ;
wdev - > omap_wdt_miscdev . fops = & omap_wdt_fops ;
ret = misc_register ( & ( wdev - > omap_wdt_miscdev ) ) ;
2006-09-29 12:59:18 +04:00
if ( ret )
2008-09-20 05:14:03 +04:00
goto err_misc ;
2006-09-29 12:59:18 +04:00
2008-09-20 05:14:01 +04:00
pr_info ( " OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec \n " ,
2008-09-20 05:14:02 +04:00
__raw_readl ( wdev - > base + OMAP_WATCHDOG_REV ) & 0xFF ,
2008-09-20 05:14:01 +04:00
timer_margin ) ;
2006-09-29 12:59:18 +04:00
/* autogate OCP interface clock */
2008-09-20 05:14:02 +04:00
__raw_writel ( 0x01 , wdev - > base + OMAP_WATCHDOG_SYS_CONFIG ) ;
2008-09-20 05:14:01 +04:00
2009-06-13 01:18:32 +04:00
clk_disable ( wdev - > ick ) ;
clk_disable ( wdev - > fck ) ;
2008-09-20 05:14:01 +04:00
omap_wdt_dev = pdev ;
2006-09-29 12:59:18 +04:00
return 0 ;
2008-09-20 05:14:03 +04:00
err_misc :
platform_set_drvdata ( pdev , NULL ) ;
iounmap ( wdev - > base ) ;
err_ioremap :
wdev - > base = NULL ;
err_clk :
2009-01-19 23:44:33 +03:00
if ( wdev - > ick )
clk_put ( wdev - > ick ) ;
if ( wdev - > fck )
clk_put ( wdev - > fck ) ;
2008-09-20 05:14:03 +04:00
kfree ( wdev ) ;
err_kzalloc :
release_mem_region ( res - > start , res - > end - res - > start + 1 ) ;
err_busy :
err_get_resource :
2006-09-29 12:59:18 +04:00
return ret ;
}
static void omap_wdt_shutdown ( struct platform_device * pdev )
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = platform_get_drvdata ( pdev ) ;
2008-09-20 05:14:01 +04:00
if ( wdev - > omap_wdt_users )
omap_wdt_disable ( wdev ) ;
2006-09-29 12:59:18 +04:00
}
2009-03-28 02:26:56 +03:00
static int __devexit omap_wdt_remove ( struct platform_device * pdev )
2006-09-29 12:59:18 +04:00
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = platform_get_drvdata ( pdev ) ;
2008-09-20 05:14:01 +04:00
struct resource * res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res )
return - ENOENT ;
misc_deregister ( & ( wdev - > omap_wdt_miscdev ) ) ;
release_mem_region ( res - > start , res - > end - res - > start + 1 ) ;
platform_set_drvdata ( pdev , NULL ) ;
2008-09-20 05:14:03 +04:00
2009-01-23 15:48:37 +03:00
clk_put ( wdev - > ick ) ;
2009-01-19 23:44:33 +03:00
clk_put ( wdev - > fck ) ;
2008-09-20 05:14:02 +04:00
iounmap ( wdev - > base ) ;
2008-09-20 05:14:01 +04:00
kfree ( wdev ) ;
omap_wdt_dev = NULL ;
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
return 0 ;
}
# ifdef CONFIG_PM
/* REVISIT ... not clear this is the best way to handle system suspend; and
* it ' s very inappropriate for selective device suspend ( e . g . suspending this
* through sysfs rather than by stopping the watchdog daemon ) . Also , this
* may not play well enough with NOWAYOUT . . .
*/
static int omap_wdt_suspend ( struct platform_device * pdev , pm_message_t state )
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = platform_get_drvdata ( pdev ) ;
2008-09-20 05:14:01 +04:00
if ( wdev - > omap_wdt_users )
omap_wdt_disable ( wdev ) ;
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
return 0 ;
}
static int omap_wdt_resume ( struct platform_device * pdev )
{
2008-09-20 05:14:03 +04:00
struct omap_wdt_dev * wdev = platform_get_drvdata ( pdev ) ;
2008-09-20 05:14:01 +04:00
if ( wdev - > omap_wdt_users ) {
omap_wdt_enable ( wdev ) ;
omap_wdt_ping ( wdev ) ;
2006-09-29 12:59:18 +04:00
}
2008-09-20 05:14:03 +04:00
2006-09-29 12:59:18 +04:00
return 0 ;
}
# else
# define omap_wdt_suspend NULL
# define omap_wdt_resume NULL
# endif
static struct platform_driver omap_wdt_driver = {
. probe = omap_wdt_probe ,
2009-03-28 02:26:56 +03:00
. remove = __devexit_p ( omap_wdt_remove ) ,
2006-09-29 12:59:18 +04:00
. shutdown = omap_wdt_shutdown ,
. suspend = omap_wdt_suspend ,
. resume = omap_wdt_resume ,
. driver = {
. owner = THIS_MODULE ,
. name = " omap_wdt " ,
} ,
} ;
static int __init omap_wdt_init ( void )
{
2008-05-19 17:07:32 +04:00
spin_lock_init ( & wdt_lock ) ;
2006-09-29 12:59:18 +04:00
return platform_driver_register ( & omap_wdt_driver ) ;
}
static void __exit omap_wdt_exit ( void )
{
platform_driver_unregister ( & omap_wdt_driver ) ;
}
module_init ( omap_wdt_init ) ;
module_exit ( omap_wdt_exit ) ;
MODULE_AUTHOR ( " George G. Davis " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
2008-04-11 08:29:23 +04:00
MODULE_ALIAS ( " platform:omap_wdt " ) ;