2009-07-27 17:46:12 +04:00
/*
* Watchdog driver for the wm831x PMICs
*
* Copyright ( C ) 2009 Wolfson Microelectronics
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# include <linux/uaccess.h>
# include <linux/gpio.h>
# include <linux/mfd/wm831x/core.h>
# include <linux/mfd/wm831x/pdata.h>
# include <linux/mfd/wm831x/watchdog.h>
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static unsigned long wm831x_wdt_users ;
static struct miscdevice wm831x_wdt_miscdev ;
static int wm831x_wdt_expect_close ;
static DEFINE_MUTEX ( wdt_mutex ) ;
static struct wm831x * wm831x ;
static unsigned int update_gpio ;
static unsigned int update_state ;
/* We can't use the sub-second values here but they're included
* for completeness . */
static struct {
int time ; /* Seconds */
u16 val ; /* WDOG_TO value */
} wm831x_wdt_cfgs [ ] = {
{ 1 , 2 } ,
{ 2 , 3 } ,
{ 4 , 4 } ,
{ 8 , 5 } ,
{ 16 , 6 } ,
{ 32 , 7 } ,
{ 33 , 7 } , /* Actually 32.768s so include both, others round down */
} ;
static int wm831x_wdt_set_timeout ( struct wm831x * wm831x , u16 value )
{
int ret ;
mutex_lock ( & wdt_mutex ) ;
ret = wm831x_reg_unlock ( wm831x ) ;
if ( ret = = 0 ) {
ret = wm831x_set_bits ( wm831x , WM831X_WATCHDOG ,
WM831X_WDOG_TO_MASK , value ) ;
wm831x_reg_lock ( wm831x ) ;
} else {
dev_err ( wm831x - > dev , " Failed to unlock security key: %d \n " ,
ret ) ;
}
mutex_unlock ( & wdt_mutex ) ;
return ret ;
}
static int wm831x_wdt_start ( struct wm831x * wm831x )
{
int ret ;
mutex_lock ( & wdt_mutex ) ;
ret = wm831x_reg_unlock ( wm831x ) ;
if ( ret = = 0 ) {
ret = wm831x_set_bits ( wm831x , WM831X_WATCHDOG ,
WM831X_WDOG_ENA , WM831X_WDOG_ENA ) ;
wm831x_reg_lock ( wm831x ) ;
} else {
dev_err ( wm831x - > dev , " Failed to unlock security key: %d \n " ,
ret ) ;
}
mutex_unlock ( & wdt_mutex ) ;
return ret ;
}
static int wm831x_wdt_stop ( struct wm831x * wm831x )
{
int ret ;
mutex_lock ( & wdt_mutex ) ;
ret = wm831x_reg_unlock ( wm831x ) ;
if ( ret = = 0 ) {
ret = wm831x_set_bits ( wm831x , WM831X_WATCHDOG ,
WM831X_WDOG_ENA , 0 ) ;
wm831x_reg_lock ( wm831x ) ;
} else {
dev_err ( wm831x - > dev , " Failed to unlock security key: %d \n " ,
ret ) ;
}
mutex_unlock ( & wdt_mutex ) ;
return ret ;
}
static int wm831x_wdt_kick ( struct wm831x * wm831x )
{
int ret ;
u16 reg ;
mutex_lock ( & wdt_mutex ) ;
if ( update_gpio ) {
gpio_set_value_cansleep ( update_gpio , update_state ) ;
update_state = ! update_state ;
ret = 0 ;
goto out ;
}
reg = wm831x_reg_read ( wm831x , WM831X_WATCHDOG ) ;
if ( ! ( reg & WM831X_WDOG_RST_SRC ) ) {
dev_err ( wm831x - > dev , " Hardware watchdog update unsupported \n " ) ;
ret = - EINVAL ;
goto out ;
}
reg | = WM831X_WDOG_RESET ;
ret = wm831x_reg_unlock ( wm831x ) ;
if ( ret = = 0 ) {
ret = wm831x_reg_write ( wm831x , WM831X_WATCHDOG , reg ) ;
wm831x_reg_lock ( wm831x ) ;
} else {
dev_err ( wm831x - > dev , " Failed to unlock security key: %d \n " ,
ret ) ;
}
out :
mutex_unlock ( & wdt_mutex ) ;
return ret ;
}
static int wm831x_wdt_open ( struct inode * inode , struct file * file )
{
int ret ;
if ( ! wm831x )
return - ENODEV ;
if ( test_and_set_bit ( 0 , & wm831x_wdt_users ) )
return - EBUSY ;
ret = wm831x_wdt_start ( wm831x ) ;
if ( ret ! = 0 )
return ret ;
return nonseekable_open ( inode , file ) ;
}
static int wm831x_wdt_release ( struct inode * inode , struct file * file )
{
if ( wm831x_wdt_expect_close )
wm831x_wdt_stop ( wm831x ) ;
else {
dev_warn ( wm831x - > dev , " Watchdog device closed uncleanly \n " ) ;
wm831x_wdt_kick ( wm831x ) ;
}
clear_bit ( 0 , & wm831x_wdt_users ) ;
return 0 ;
}
static ssize_t wm831x_wdt_write ( struct file * file ,
const char __user * data , size_t count ,
loff_t * ppos )
{
size_t i ;
if ( count ) {
wm831x_wdt_kick ( wm831x ) ;
if ( ! nowayout ) {
/* In case it was set long ago */
wm831x_wdt_expect_close = 0 ;
/* scan to see whether or not we got the magic
character */
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
wm831x_wdt_expect_close = 42 ;
}
}
}
return count ;
}
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2009-07-27 17:46:12 +04:00
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
. identity = " WM831x Watchdog " ,
} ;
static long wm831x_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
int ret = - ENOTTY , time , i ;
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
u16 reg ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
ret = copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
ret = put_user ( 0 , p ) ;
break ;
case WDIOC_SETOPTIONS :
{
int options ;
if ( get_user ( options , p ) )
return - EFAULT ;
ret = - EINVAL ;
/* Setting both simultaneously means at least one must fail */
if ( options = = WDIOS_DISABLECARD )
ret = wm831x_wdt_start ( wm831x ) ;
if ( options = = WDIOS_ENABLECARD )
ret = wm831x_wdt_stop ( wm831x ) ;
break ;
}
case WDIOC_KEEPALIVE :
ret = wm831x_wdt_kick ( wm831x ) ;
break ;
case WDIOC_SETTIMEOUT :
ret = get_user ( time , p ) ;
if ( ret )
break ;
if ( time = = 0 ) {
if ( nowayout )
ret = - EINVAL ;
else
wm831x_wdt_stop ( wm831x ) ;
break ;
}
for ( i = 0 ; i < ARRAY_SIZE ( wm831x_wdt_cfgs ) ; i + + )
if ( wm831x_wdt_cfgs [ i ] . time = = time )
break ;
if ( i = = ARRAY_SIZE ( wm831x_wdt_cfgs ) )
ret = - EINVAL ;
else
ret = wm831x_wdt_set_timeout ( wm831x ,
wm831x_wdt_cfgs [ i ] . val ) ;
break ;
case WDIOC_GETTIMEOUT :
reg = wm831x_reg_read ( wm831x , WM831X_WATCHDOG ) ;
reg & = WM831X_WDOG_TO_MASK ;
for ( i = 0 ; i < ARRAY_SIZE ( wm831x_wdt_cfgs ) ; i + + )
if ( wm831x_wdt_cfgs [ i ] . val = = reg )
break ;
if ( i = = ARRAY_SIZE ( wm831x_wdt_cfgs ) ) {
dev_warn ( wm831x - > dev ,
" Unknown watchdog configuration: %x \n " , reg ) ;
ret = - EINVAL ;
} else
ret = put_user ( wm831x_wdt_cfgs [ i ] . time , p ) ;
}
return ret ;
}
static const struct file_operations wm831x_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = wm831x_wdt_write ,
. unlocked_ioctl = wm831x_wdt_ioctl ,
. open = wm831x_wdt_open ,
. release = wm831x_wdt_release ,
} ;
static struct miscdevice wm831x_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & wm831x_wdt_fops ,
} ;
static int __devinit wm831x_wdt_probe ( struct platform_device * pdev )
{
struct wm831x_pdata * chip_pdata ;
struct wm831x_watchdog_pdata * pdata ;
int reg , ret ;
wm831x = dev_get_drvdata ( pdev - > dev . parent ) ;
ret = wm831x_reg_read ( wm831x , WM831X_WATCHDOG ) ;
if ( ret < 0 ) {
dev_err ( wm831x - > dev , " Failed to read watchdog status: %d \n " ,
ret ) ;
goto err ;
}
reg = ret ;
if ( reg & WM831X_WDOG_DEBUG )
dev_warn ( wm831x - > dev , " Watchdog is paused \n " ) ;
/* Apply any configuration */
if ( pdev - > dev . parent - > platform_data ) {
chip_pdata = pdev - > dev . parent - > platform_data ;
pdata = chip_pdata - > watchdog ;
} else {
pdata = NULL ;
}
if ( pdata ) {
reg & = ~ ( WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK |
WM831X_WDOG_RST_SRC ) ;
reg | = pdata - > primary < < WM831X_WDOG_PRIMACT_SHIFT ;
reg | = pdata - > secondary < < WM831X_WDOG_SECACT_SHIFT ;
reg | = pdata - > software < < WM831X_WDOG_RST_SRC_SHIFT ;
if ( pdata - > update_gpio ) {
ret = gpio_request ( pdata - > update_gpio ,
" Watchdog update " ) ;
if ( ret < 0 ) {
dev_err ( wm831x - > dev ,
" Failed to request update GPIO: %d \n " ,
ret ) ;
goto err ;
}
ret = gpio_direction_output ( pdata - > update_gpio , 0 ) ;
if ( ret ! = 0 ) {
dev_err ( wm831x - > dev ,
" gpio_direction_output returned: %d \n " ,
ret ) ;
goto err_gpio ;
}
update_gpio = pdata - > update_gpio ;
/* Make sure the watchdog takes hardware updates */
reg | = WM831X_WDOG_RST_SRC ;
}
ret = wm831x_reg_unlock ( wm831x ) ;
if ( ret = = 0 ) {
ret = wm831x_reg_write ( wm831x , WM831X_WATCHDOG , reg ) ;
wm831x_reg_lock ( wm831x ) ;
} else {
dev_err ( wm831x - > dev ,
" Failed to unlock security key: %d \n " , ret ) ;
goto err_gpio ;
}
}
wm831x_wdt_miscdev . parent = & pdev - > dev ;
ret = misc_register ( & wm831x_wdt_miscdev ) ;
if ( ret ! = 0 ) {
dev_err ( wm831x - > dev , " Failed to register miscdev: %d \n " , ret ) ;
goto err_gpio ;
}
return 0 ;
err_gpio :
if ( update_gpio ) {
gpio_free ( update_gpio ) ;
update_gpio = 0 ;
}
err :
return ret ;
}
static int __devexit wm831x_wdt_remove ( struct platform_device * pdev )
{
if ( update_gpio ) {
gpio_free ( update_gpio ) ;
update_gpio = 0 ;
}
misc_deregister ( & wm831x_wdt_miscdev ) ;
return 0 ;
}
static struct platform_driver wm831x_wdt_driver = {
. probe = wm831x_wdt_probe ,
. remove = __devexit_p ( wm831x_wdt_remove ) ,
. driver = {
. name = " wm831x-watchdog " ,
} ,
} ;
static int __init wm831x_wdt_init ( void )
{
return platform_driver_register ( & wm831x_wdt_driver ) ;
}
module_init ( wm831x_wdt_init ) ;
static void __exit wm831x_wdt_exit ( void )
{
platform_driver_unregister ( & wm831x_wdt_driver ) ;
}
module_exit ( wm831x_wdt_exit ) ;
MODULE_AUTHOR ( " Mark Brown " ) ;
MODULE_DESCRIPTION ( " WM831x Watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:wm831x-watchdog " ) ;