2013-06-18 19:19:45 +04:00
/*
* Watchdog driver for the A21 VME CPU Boards
*
* Copyright ( C ) 2013 MEN Mikro Elektronik Nuernberg GmbH
*
* 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/slab.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# include <linux/uaccess.h>
# include <linux/gpio.h>
# include <linux/of_gpio.h>
# include <linux/delay.h>
# include <linux/bitops.h>
# define NUM_GPIOS 6
enum a21_wdt_gpios {
GPIO_WD_ENAB ,
GPIO_WD_FAST ,
GPIO_WD_TRIG ,
GPIO_WD_RST0 ,
GPIO_WD_RST1 ,
GPIO_WD_RST2 ,
} ;
struct a21_wdt_drv {
struct watchdog_device wdt ;
struct mutex lock ;
unsigned gpios [ NUM_GPIOS ] ;
} ;
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static unsigned int a21_wdt_get_bootstatus ( struct a21_wdt_drv * drv )
{
int reset = 0 ;
reset | = gpio_get_value ( drv - > gpios [ GPIO_WD_RST0 ] ) ? ( 1 < < 0 ) : 0 ;
reset | = gpio_get_value ( drv - > gpios [ GPIO_WD_RST1 ] ) ? ( 1 < < 1 ) : 0 ;
reset | = gpio_get_value ( drv - > gpios [ GPIO_WD_RST2 ] ) ? ( 1 < < 2 ) : 0 ;
return reset ;
}
static int a21_wdt_start ( struct watchdog_device * wdt )
{
struct a21_wdt_drv * drv = watchdog_get_drvdata ( wdt ) ;
mutex_lock ( & drv - > lock ) ;
gpio_set_value ( drv - > gpios [ GPIO_WD_ENAB ] , 1 ) ;
mutex_unlock ( & drv - > lock ) ;
return 0 ;
}
static int a21_wdt_stop ( struct watchdog_device * wdt )
{
struct a21_wdt_drv * drv = watchdog_get_drvdata ( wdt ) ;
mutex_lock ( & drv - > lock ) ;
gpio_set_value ( drv - > gpios [ GPIO_WD_ENAB ] , 0 ) ;
mutex_unlock ( & drv - > lock ) ;
return 0 ;
}
static int a21_wdt_ping ( struct watchdog_device * wdt )
{
struct a21_wdt_drv * drv = watchdog_get_drvdata ( wdt ) ;
mutex_lock ( & drv - > lock ) ;
gpio_set_value ( drv - > gpios [ GPIO_WD_TRIG ] , 0 ) ;
ndelay ( 10 ) ;
gpio_set_value ( drv - > gpios [ GPIO_WD_TRIG ] , 1 ) ;
mutex_unlock ( & drv - > lock ) ;
return 0 ;
}
static int a21_wdt_set_timeout ( struct watchdog_device * wdt ,
unsigned int timeout )
{
struct a21_wdt_drv * drv = watchdog_get_drvdata ( wdt ) ;
if ( timeout ! = 1 & & timeout ! = 30 ) {
2015-12-25 01:22:03 +03:00
dev_err ( wdt - > parent , " Only 1 and 30 allowed as timeout \n " ) ;
2013-06-18 19:19:45 +04:00
return - EINVAL ;
}
if ( timeout = = 30 & & wdt - > timeout = = 1 ) {
2015-12-25 01:22:03 +03:00
dev_err ( wdt - > parent ,
2013-06-18 19:19:45 +04:00
" Transition from fast to slow mode not allowed \n " ) ;
return - EINVAL ;
}
mutex_lock ( & drv - > lock ) ;
if ( timeout = = 1 )
gpio_set_value ( drv - > gpios [ GPIO_WD_FAST ] , 1 ) ;
else
gpio_set_value ( drv - > gpios [ GPIO_WD_FAST ] , 0 ) ;
wdt - > timeout = timeout ;
mutex_unlock ( & drv - > lock ) ;
return 0 ;
}
static const struct watchdog_info a21_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
. identity = " MEN A21 Watchdog " ,
} ;
static const struct watchdog_ops a21_wdt_ops = {
. owner = THIS_MODULE ,
. start = a21_wdt_start ,
. stop = a21_wdt_stop ,
. ping = a21_wdt_ping ,
. set_timeout = a21_wdt_set_timeout ,
} ;
static struct watchdog_device a21_wdt = {
. info = & a21_wdt_info ,
. ops = & a21_wdt_ops ,
. min_timeout = 1 ,
. max_timeout = 30 ,
} ;
static int a21_wdt_probe ( struct platform_device * pdev )
{
struct device_node * node ;
struct a21_wdt_drv * drv ;
unsigned int reset = 0 ;
int num_gpios ;
int ret ;
int i ;
drv = devm_kzalloc ( & pdev - > dev , sizeof ( struct a21_wdt_drv ) , GFP_KERNEL ) ;
if ( ! drv )
return - ENOMEM ;
/* Fill GPIO pin array */
node = pdev - > dev . of_node ;
num_gpios = of_gpio_count ( node ) ;
if ( num_gpios ! = NUM_GPIOS ) {
dev_err ( & pdev - > dev , " gpios DT property wrong, got %d want %d " ,
num_gpios , NUM_GPIOS ) ;
return - ENODEV ;
}
for ( i = 0 ; i < num_gpios ; i + + ) {
int val ;
val = of_get_gpio ( node , i ) ;
if ( val < 0 )
return val ;
drv - > gpios [ i ] = val ;
}
/* Request the used GPIOs */
for ( i = 0 ; i < num_gpios ; i + + ) {
ret = devm_gpio_request ( & pdev - > dev , drv - > gpios [ i ] ,
" MEN A21 Watchdog " ) ;
if ( ret )
return ret ;
if ( i < GPIO_WD_RST0 )
ret = gpio_direction_output ( drv - > gpios [ i ] ,
gpio_get_value ( drv - > gpios [ i ] ) ) ;
else /* GPIO_WD_RST[0..2] are inputs */
ret = gpio_direction_input ( drv - > gpios [ i ] ) ;
if ( ret )
return ret ;
}
mutex_init ( & drv - > lock ) ;
watchdog_init_timeout ( & a21_wdt , 30 , & pdev - > dev ) ;
watchdog_set_nowayout ( & a21_wdt , nowayout ) ;
watchdog_set_drvdata ( & a21_wdt , drv ) ;
2015-08-20 11:35:01 +03:00
a21_wdt . parent = & pdev - > dev ;
2013-06-18 19:19:45 +04:00
reset = a21_wdt_get_bootstatus ( drv ) ;
if ( reset = = 2 )
a21_wdt . bootstatus | = WDIOF_EXTERN1 ;
else if ( reset = = 4 )
a21_wdt . bootstatus | = WDIOF_CARDRESET ;
else if ( reset = = 5 )
a21_wdt . bootstatus | = WDIOF_POWERUNDER ;
else if ( reset = = 7 )
a21_wdt . bootstatus | = WDIOF_EXTERN2 ;
2015-06-02 13:25:26 +03:00
drv - > wdt = a21_wdt ;
dev_set_drvdata ( & pdev - > dev , drv ) ;
2013-06-18 19:19:45 +04:00
ret = watchdog_register_device ( & a21_wdt ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Cannot register watchdog device \n " ) ;
goto err_register_wd ;
}
dev_info ( & pdev - > dev , " MEN A21 watchdog timer driver enabled \n " ) ;
return 0 ;
err_register_wd :
mutex_destroy ( & drv - > lock ) ;
return ret ;
}
static int a21_wdt_remove ( struct platform_device * pdev )
{
struct a21_wdt_drv * drv = dev_get_drvdata ( & pdev - > dev ) ;
dev_warn ( & pdev - > dev ,
" Unregistering A21 watchdog driver, board may reboot \n " ) ;
watchdog_unregister_device ( & drv - > wdt ) ;
mutex_destroy ( & drv - > lock ) ;
return 0 ;
}
static void a21_wdt_shutdown ( struct platform_device * pdev )
{
struct a21_wdt_drv * drv = dev_get_drvdata ( & pdev - > dev ) ;
gpio_set_value ( drv - > gpios [ GPIO_WD_ENAB ] , 0 ) ;
}
static const struct of_device_id a21_wdt_ids [ ] = {
{ . compatible = " men,a021-wdt " } ,
{ } ,
} ;
2015-09-03 14:06:09 +03:00
MODULE_DEVICE_TABLE ( of , a21_wdt_ids ) ;
2013-06-18 19:19:45 +04:00
static struct platform_driver a21_wdt_driver = {
. probe = a21_wdt_probe ,
. remove = a21_wdt_remove ,
. shutdown = a21_wdt_shutdown ,
. driver = {
. name = " a21-watchdog " ,
. of_match_table = a21_wdt_ids ,
} ,
} ;
module_platform_driver ( a21_wdt_driver ) ;
MODULE_AUTHOR ( " MEN Mikro Elektronik " ) ;
MODULE_DESCRIPTION ( " MEN A21 Watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:a21-watchdog " ) ;