2006-09-29 12:59:18 +04:00
/*
* linux / drivers / char / watchdog / omap_wdt . c
*
* Watchdog driver for the TI OMAP 16 xx & 24 xx 32 KHz ( non - secure ) watchdog
*
* 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 >
* Based on SoftDog driver by Alan Cox < alan @ redhat . com >
*
* 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>
2006-09-29 12:59:18 +04:00
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/hardware.h>
# include <asm/arch/prcm.h>
# include "omap_wdt.h"
static unsigned timer_margin ;
module_param ( timer_margin , uint , 0 ) ;
MODULE_PARM_DESC ( timer_margin , " initial watchdog timeout (in seconds) " ) ;
static int omap_wdt_users ;
static struct clk * armwdt_ck = NULL ;
static struct clk * mpu_wdt_ick = NULL ;
static struct clk * mpu_wdt_fck = NULL ;
static unsigned int wdt_trgr_pattern = 0x1234 ;
static void omap_wdt_ping ( void )
{
/* wait for posted write to complete */
while ( ( omap_readl ( OMAP_WATCHDOG_WPS ) ) & 0x08 )
cpu_relax ( ) ;
wdt_trgr_pattern = ~ wdt_trgr_pattern ;
omap_writel ( wdt_trgr_pattern , ( OMAP_WATCHDOG_TGR ) ) ;
/* wait for posted write to complete */
while ( ( omap_readl ( OMAP_WATCHDOG_WPS ) ) & 0x08 )
cpu_relax ( ) ;
/* reloaded WCRR from WLDR */
}
static void omap_wdt_enable ( void )
{
/* Sequence to enable the watchdog */
omap_writel ( 0xBBBB , OMAP_WATCHDOG_SPR ) ;
while ( ( omap_readl ( OMAP_WATCHDOG_WPS ) ) & 0x10 )
cpu_relax ( ) ;
omap_writel ( 0x4444 , OMAP_WATCHDOG_SPR ) ;
while ( ( omap_readl ( OMAP_WATCHDOG_WPS ) ) & 0x10 )
cpu_relax ( ) ;
}
static void omap_wdt_disable ( void )
{
/* sequence required to disable watchdog */
omap_writel ( 0xAAAA , OMAP_WATCHDOG_SPR ) ; /* TIMER_MODE */
while ( omap_readl ( OMAP_WATCHDOG_WPS ) & 0x10 )
cpu_relax ( ) ;
omap_writel ( 0x5555 , OMAP_WATCHDOG_SPR ) ; /* TIMER_MODE */
while ( omap_readl ( OMAP_WATCHDOG_WPS ) & 0x10 )
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 ;
}
static void omap_wdt_set_timeout ( void )
{
u32 pre_margin = GET_WLDR_VAL ( timer_margin ) ;
/* just count up at 32 KHz */
while ( omap_readl ( OMAP_WATCHDOG_WPS ) & 0x04 )
cpu_relax ( ) ;
omap_writel ( pre_margin , OMAP_WATCHDOG_LDR ) ;
while ( omap_readl ( OMAP_WATCHDOG_WPS ) & 0x04 )
cpu_relax ( ) ;
}
/*
* Allow only one task to hold it open
*/
static int omap_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 1 , ( unsigned long * ) & omap_wdt_users ) )
return - EBUSY ;
if ( cpu_is_omap16xx ( ) )
clk_enable ( armwdt_ck ) ; /* Enable the clock */
if ( cpu_is_omap24xx ( ) ) {
clk_enable ( mpu_wdt_ick ) ; /* Enable the interface clock */
clk_enable ( mpu_wdt_fck ) ; /* Enable the functional clock */
}
/* initialize prescaler */
while ( omap_readl ( OMAP_WATCHDOG_WPS ) & 0x01 )
cpu_relax ( ) ;
omap_writel ( ( 1 < < 5 ) | ( PTV < < 2 ) , OMAP_WATCHDOG_CNTRL ) ;
while ( omap_readl ( OMAP_WATCHDOG_WPS ) & 0x01 )
cpu_relax ( ) ;
omap_wdt_set_timeout ( ) ;
omap_wdt_enable ( ) ;
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 )
{
/*
* Shut off the timer unless NOWAYOUT is defined .
*/
# ifndef CONFIG_WATCHDOG_NOWAYOUT
omap_wdt_disable ( ) ;
if ( cpu_is_omap16xx ( ) ) {
clk_disable ( armwdt_ck ) ; /* Disable the clock */
clk_put ( armwdt_ck ) ;
armwdt_ck = NULL ;
}
if ( cpu_is_omap24xx ( ) ) {
clk_disable ( mpu_wdt_ick ) ; /* Disable the clock */
clk_disable ( mpu_wdt_fck ) ; /* Disable the clock */
clk_put ( mpu_wdt_ick ) ;
clk_put ( mpu_wdt_fck ) ;
mpu_wdt_ick = NULL ;
mpu_wdt_fck = NULL ;
}
# else
printk ( KERN_CRIT " omap_wdt: Unexpected close, not stopping! \n " ) ;
# endif
omap_wdt_users = 0 ;
return 0 ;
}
static ssize_t
omap_wdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
/* Refresh LOAD_TIME. */
if ( len )
omap_wdt_ping ( ) ;
return len ;
}
static int
omap_wdt_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
int new_margin ;
static struct watchdog_info ident = {
. identity = " OMAP Watchdog " ,
. options = WDIOF_SETTIMEOUT ,
. firmware_version = 0 ,
} ;
switch ( cmd ) {
default :
2007-07-25 01:55:06 +04:00
return - ENOTTY ;
2006-09-29 12:59:18 +04:00
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 ( ) )
return put_user ( omap_readw ( ARM_SYSST ) ,
( int __user * ) arg ) ;
if ( cpu_is_omap24xx ( ) )
return put_user ( omap_prcm_get_reset_sources ( ) ,
( int __user * ) arg ) ;
case WDIOC_KEEPALIVE :
omap_wdt_ping ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , ( int __user * ) arg ) )
return - EFAULT ;
omap_wdt_adjust_timeout ( new_margin ) ;
omap_wdt_disable ( ) ;
omap_wdt_set_timeout ( ) ;
omap_wdt_enable ( ) ;
omap_wdt_ping ( ) ;
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( timer_margin , ( int __user * ) arg ) ;
}
}
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 ,
. ioctl = omap_wdt_ioctl ,
. open = omap_wdt_open ,
. release = omap_wdt_release ,
} ;
static struct miscdevice omap_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & omap_wdt_fops
} ;
static int __init omap_wdt_probe ( struct platform_device * pdev )
{
struct resource * res , * mem ;
int ret ;
/* reserve static register mappings */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res )
return - ENOENT ;
mem = request_mem_region ( res - > start , res - > end - res - > start + 1 ,
pdev - > name ) ;
if ( mem = = NULL )
return - EBUSY ;
platform_set_drvdata ( pdev , mem ) ;
omap_wdt_users = 0 ;
if ( cpu_is_omap16xx ( ) ) {
armwdt_ck = clk_get ( & pdev - > dev , " armwdt_ck " ) ;
if ( IS_ERR ( armwdt_ck ) ) {
ret = PTR_ERR ( armwdt_ck ) ;
armwdt_ck = NULL ;
goto fail ;
}
}
if ( cpu_is_omap24xx ( ) ) {
mpu_wdt_ick = clk_get ( & pdev - > dev , " mpu_wdt_ick " ) ;
if ( IS_ERR ( mpu_wdt_ick ) ) {
ret = PTR_ERR ( mpu_wdt_ick ) ;
mpu_wdt_ick = NULL ;
goto fail ;
}
mpu_wdt_fck = clk_get ( & pdev - > dev , " mpu_wdt_fck " ) ;
if ( IS_ERR ( mpu_wdt_fck ) ) {
ret = PTR_ERR ( mpu_wdt_fck ) ;
mpu_wdt_fck = NULL ;
goto fail ;
}
}
omap_wdt_disable ( ) ;
omap_wdt_adjust_timeout ( timer_margin ) ;
2006-12-04 16:56:18 +03:00
omap_wdt_miscdev . parent = & pdev - > dev ;
2006-09-29 12:59:18 +04:00
ret = misc_register ( & omap_wdt_miscdev ) ;
if ( ret )
goto fail ;
pr_info ( " OMAP Watchdog Timer: initial timeout %d sec \n " , timer_margin ) ;
/* autogate OCP interface clock */
omap_writel ( 0x01 , OMAP_WATCHDOG_SYS_CONFIG ) ;
return 0 ;
fail :
if ( armwdt_ck )
clk_put ( armwdt_ck ) ;
if ( mpu_wdt_ick )
clk_put ( mpu_wdt_ick ) ;
if ( mpu_wdt_fck )
clk_put ( mpu_wdt_fck ) ;
release_resource ( mem ) ;
return ret ;
}
static void omap_wdt_shutdown ( struct platform_device * pdev )
{
omap_wdt_disable ( ) ;
}
static int omap_wdt_remove ( struct platform_device * pdev )
{
struct resource * mem = platform_get_drvdata ( pdev ) ;
misc_deregister ( & omap_wdt_miscdev ) ;
release_resource ( mem ) ;
if ( armwdt_ck )
clk_put ( armwdt_ck ) ;
if ( mpu_wdt_ick )
clk_put ( mpu_wdt_ick ) ;
if ( mpu_wdt_fck )
clk_put ( mpu_wdt_fck ) ;
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 )
{
if ( omap_wdt_users )
omap_wdt_disable ( ) ;
return 0 ;
}
static int omap_wdt_resume ( struct platform_device * pdev )
{
if ( omap_wdt_users ) {
omap_wdt_enable ( ) ;
omap_wdt_ping ( ) ;
}
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 ,
. remove = omap_wdt_remove ,
. 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 )
{
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 ) ;