2011-06-02 22:13:11 +01:00
/*
2013-05-31 07:56:33 +02:00
* Watchdog Device Driver for Xilinx axi / xps_timebase_wdt
*
2014-02-12 14:34:32 +01:00
* ( C ) Copyright 2013 - 2014 Xilinx , Inc .
2013-05-31 07:56:33 +02:00
* ( C ) Copyright 2011 ( Alejandro Cabrera < aldaya @ gmail . com > )
*
* 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 ; either version
* 2 of the License , or ( at your option ) any later version .
*/
2011-06-02 22:13:11 +01:00
2016-08-12 12:17:01 +05:30
# include <linux/clk.h>
2014-02-12 14:34:34 +01:00
# include <linux/err.h>
2011-06-02 22:13:11 +01:00
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/ioport.h>
# include <linux/watchdog.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_address.h>
/* Register offsets for the Wdt device */
# define XWT_TWCSR0_OFFSET 0x0 /* Control/Status Register0 */
# define XWT_TWCSR1_OFFSET 0x4 /* Control/Status Register1 */
# define XWT_TBR_OFFSET 0x8 /* Timebase Register Offset */
/* Control/Status Register Masks */
# define XWT_CSR0_WRS_MASK 0x00000008 /* Reset status */
# define XWT_CSR0_WDS_MASK 0x00000004 /* Timer state */
# define XWT_CSR0_EWDT1_MASK 0x00000002 /* Enable bit 1 */
/* Control/Status Register 0/1 bits */
# define XWT_CSRX_EWDT2_MASK 0x00000001 /* Enable bit 2 */
/* SelfTest constants */
# define XWT_MAX_SELFTEST_LOOP_COUNT 0x00010000
# define XWT_TIMER_FAILED 0xFFFFFFFF
# define WATCHDOG_NAME "Xilinx Watchdog"
struct xwdt_device {
void __iomem * base ;
u32 wdt_interval ;
2014-02-12 14:41:19 +01:00
spinlock_t spinlock ;
struct watchdog_device xilinx_wdt_wdd ;
2016-08-12 12:17:01 +05:30
struct clk * clk ;
2011-06-02 22:13:11 +01:00
} ;
2014-02-12 14:34:32 +01:00
static int xilinx_wdt_start ( struct watchdog_device * wdd )
2011-06-02 22:13:11 +01:00
{
2017-08-07 13:24:22 +02:00
int ret ;
2014-02-12 14:34:33 +01:00
u32 control_status_reg ;
2014-02-12 14:41:19 +01:00
struct xwdt_device * xdev = watchdog_get_drvdata ( wdd ) ;
2014-02-12 14:34:33 +01:00
2017-08-07 13:24:22 +02:00
ret = clk_enable ( xdev - > clk ) ;
if ( ret ) {
dev_err ( wdd - > parent , " Failed to enable clock \n " ) ;
return ret ;
}
2014-02-12 14:41:19 +01:00
spin_lock ( & xdev - > spinlock ) ;
2011-06-02 22:13:11 +01:00
/* Clean previous status and enable the watchdog timer */
2014-02-12 14:41:19 +01:00
control_status_reg = ioread32 ( xdev - > base + XWT_TWCSR0_OFFSET ) ;
2011-06-02 22:13:11 +01:00
control_status_reg | = ( XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK ) ;
iowrite32 ( ( control_status_reg | XWT_CSR0_EWDT1_MASK ) ,
2014-02-12 14:41:19 +01:00
xdev - > base + XWT_TWCSR0_OFFSET ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
iowrite32 ( XWT_CSRX_EWDT2_MASK , xdev - > base + XWT_TWCSR1_OFFSET ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
spin_unlock ( & xdev - > spinlock ) ;
2014-02-12 14:34:32 +01:00
return 0 ;
2011-06-02 22:13:11 +01:00
}
2014-02-12 14:34:32 +01:00
static int xilinx_wdt_stop ( struct watchdog_device * wdd )
2011-06-02 22:13:11 +01:00
{
2014-02-12 14:34:33 +01:00
u32 control_status_reg ;
2014-02-12 14:41:19 +01:00
struct xwdt_device * xdev = watchdog_get_drvdata ( wdd ) ;
2014-02-12 14:34:33 +01:00
2014-02-12 14:41:19 +01:00
spin_lock ( & xdev - > spinlock ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
control_status_reg = ioread32 ( xdev - > base + XWT_TWCSR0_OFFSET ) ;
2011-06-02 22:13:11 +01:00
iowrite32 ( ( control_status_reg & ~ XWT_CSR0_EWDT1_MASK ) ,
2014-02-12 14:41:19 +01:00
xdev - > base + XWT_TWCSR0_OFFSET ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
iowrite32 ( 0 , xdev - > base + XWT_TWCSR1_OFFSET ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
spin_unlock ( & xdev - > spinlock ) ;
2017-08-07 13:24:22 +02:00
clk_disable ( xdev - > clk ) ;
2012-02-15 15:06:19 -08:00
pr_info ( " Stopped! \n " ) ;
2014-02-12 14:34:32 +01:00
return 0 ;
2011-06-02 22:13:11 +01:00
}
2014-02-12 14:34:32 +01:00
static int xilinx_wdt_keepalive ( struct watchdog_device * wdd )
2011-06-02 22:13:11 +01:00
{
2014-02-12 14:34:33 +01:00
u32 control_status_reg ;
2014-02-12 14:41:19 +01:00
struct xwdt_device * xdev = watchdog_get_drvdata ( wdd ) ;
2014-02-12 14:34:33 +01:00
2014-02-12 14:41:19 +01:00
spin_lock ( & xdev - > spinlock ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
control_status_reg = ioread32 ( xdev - > base + XWT_TWCSR0_OFFSET ) ;
2011-06-02 22:13:11 +01:00
control_status_reg | = ( XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK ) ;
2014-02-12 14:41:19 +01:00
iowrite32 ( control_status_reg , xdev - > base + XWT_TWCSR0_OFFSET ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
spin_unlock ( & xdev - > spinlock ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:34:32 +01:00
return 0 ;
}
2011-06-02 22:13:11 +01:00
2014-02-12 14:34:32 +01:00
static const struct watchdog_info xilinx_wdt_ident = {
. options = WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING ,
. firmware_version = 1 ,
. identity = WATCHDOG_NAME ,
} ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:34:32 +01:00
static const struct watchdog_ops xilinx_wdt_ops = {
. owner = THIS_MODULE ,
. start = xilinx_wdt_start ,
. stop = xilinx_wdt_stop ,
. ping = xilinx_wdt_keepalive ,
} ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
static u32 xwdt_selftest ( struct xwdt_device * xdev )
2011-06-02 22:13:11 +01:00
{
int i ;
u32 timer_value1 ;
u32 timer_value2 ;
2014-02-12 14:41:19 +01:00
spin_lock ( & xdev - > spinlock ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:19 +01:00
timer_value1 = ioread32 ( xdev - > base + XWT_TBR_OFFSET ) ;
timer_value2 = ioread32 ( xdev - > base + XWT_TBR_OFFSET ) ;
2011-06-02 22:13:11 +01:00
for ( i = 0 ;
( ( i < = XWT_MAX_SELFTEST_LOOP_COUNT ) & &
( timer_value2 = = timer_value1 ) ) ; i + + ) {
2014-02-12 14:41:19 +01:00
timer_value2 = ioread32 ( xdev - > base + XWT_TBR_OFFSET ) ;
2011-06-02 22:13:11 +01:00
}
2014-02-12 14:41:19 +01:00
spin_unlock ( & xdev - > spinlock ) ;
2011-06-02 22:13:11 +01:00
if ( timer_value2 ! = timer_value1 )
return ~ XWT_TIMER_FAILED ;
else
return XWT_TIMER_FAILED ;
}
2012-11-19 13:21:41 -05:00
static int xwdt_probe ( struct platform_device * pdev )
2011-06-02 22:13:11 +01:00
{
int rc ;
2014-02-12 14:41:25 +01:00
u32 pfreq = 0 , enable_once = 0 ;
2014-02-12 14:34:34 +01:00
struct resource * res ;
2014-02-12 14:41:19 +01:00
struct xwdt_device * xdev ;
struct watchdog_device * xilinx_wdt_wdd ;
xdev = devm_kzalloc ( & pdev - > dev , sizeof ( * xdev ) , GFP_KERNEL ) ;
if ( ! xdev )
return - ENOMEM ;
xilinx_wdt_wdd = & xdev - > xilinx_wdt_wdd ;
xilinx_wdt_wdd - > info = & xilinx_wdt_ident ;
xilinx_wdt_wdd - > ops = & xilinx_wdt_ops ;
xilinx_wdt_wdd - > parent = & pdev - > dev ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:34:34 +01:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2014-02-12 14:41:19 +01:00
xdev - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( xdev - > base ) )
return PTR_ERR ( xdev - > base ) ;
2014-02-12 14:34:34 +01:00
2014-02-12 14:41:21 +01:00
rc = of_property_read_u32 ( pdev - > dev . of_node , " xlnx,wdt-interval " ,
& xdev - > wdt_interval ) ;
2014-02-12 14:41:25 +01:00
if ( rc )
2014-02-12 14:41:20 +01:00
dev_warn ( & pdev - > dev ,
" Parameter \" xlnx,wdt-interval \" not found \n " ) ;
2011-06-02 22:13:11 +01:00
2014-02-12 14:41:21 +01:00
rc = of_property_read_u32 ( pdev - > dev . of_node , " xlnx,wdt-enable-once " ,
& enable_once ) ;
if ( rc )
2014-02-12 14:41:20 +01:00
dev_warn ( & pdev - > dev ,
" Parameter \" xlnx,wdt-enable-once \" not found \n " ) ;
2014-02-12 14:41:21 +01:00
watchdog_set_nowayout ( xilinx_wdt_wdd , enable_once ) ;
2011-06-02 22:13:11 +01:00
2017-08-07 13:24:22 +02:00
xdev - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( xdev - > clk ) ) {
if ( PTR_ERR ( xdev - > clk ) ! = - ENOENT )
return PTR_ERR ( xdev - > clk ) ;
/*
* Clock framework support is optional , continue on
* anyways if we don ' t find a matching clock .
*/
xdev - > clk = NULL ;
rc = of_property_read_u32 ( pdev - > dev . of_node , " clock-frequency " ,
& pfreq ) ;
if ( rc )
dev_warn ( & pdev - > dev ,
" The watchdog clock freq cannot be obtained \n " ) ;
} else {
pfreq = clk_get_rate ( xdev - > clk ) ;
}
2014-02-12 14:41:22 +01:00
/*
* Twice of the 2 ^ wdt_interval / freq because the first wdt overflow is
* ignored ( interrupt ) , reset is only generated at second wdt overflow
*/
2014-02-12 14:41:25 +01:00
if ( pfreq & & xdev - > wdt_interval )
2014-02-12 14:41:19 +01:00
xilinx_wdt_wdd - > timeout = 2 * ( ( 1 < < xdev - > wdt_interval ) /
2014-02-12 14:41:21 +01:00
pfreq ) ;
2014-02-12 14:41:19 +01:00
spin_lock_init ( & xdev - > spinlock ) ;
watchdog_set_drvdata ( xilinx_wdt_wdd , xdev ) ;
2011-06-02 22:13:11 +01:00
2016-08-12 12:17:01 +05:30
rc = clk_prepare_enable ( xdev - > clk ) ;
if ( rc ) {
dev_err ( & pdev - > dev , " unable to enable clock \n " ) ;
return rc ;
}
2014-02-12 14:41:19 +01:00
rc = xwdt_selftest ( xdev ) ;
2011-06-02 22:13:11 +01:00
if ( rc = = XWT_TIMER_FAILED ) {
2014-02-12 14:41:20 +01:00
dev_err ( & pdev - > dev , " SelfTest routine error \n " ) ;
2016-08-12 12:17:01 +05:30
goto err_clk_disable ;
2011-06-02 22:13:11 +01:00
}
2014-02-12 14:41:19 +01:00
rc = watchdog_register_device ( xilinx_wdt_wdd ) ;
2011-06-02 22:13:11 +01:00
if ( rc ) {
2014-02-12 14:41:20 +01:00
dev_err ( & pdev - > dev , " Cannot register watchdog (err=%d) \n " , rc ) ;
2016-08-12 12:17:01 +05:30
goto err_clk_disable ;
2011-06-02 22:13:11 +01:00
}
2017-08-07 13:24:22 +02:00
clk_disable ( xdev - > clk ) ;
2014-02-12 14:34:32 +01:00
dev_info ( & pdev - > dev , " Xilinx Watchdog Timer at %p with timeout %ds \n " ,
2014-02-12 14:41:19 +01:00
xdev - > base , xilinx_wdt_wdd - > timeout ) ;
platform_set_drvdata ( pdev , xdev ) ;
2011-06-02 22:13:11 +01:00
return 0 ;
2016-08-12 12:17:01 +05:30
err_clk_disable :
clk_disable_unprepare ( xdev - > clk ) ;
return rc ;
2011-06-02 22:13:11 +01:00
}
2014-02-12 14:41:19 +01:00
static int xwdt_remove ( struct platform_device * pdev )
2011-06-02 22:13:11 +01:00
{
2014-02-12 14:41:19 +01:00
struct xwdt_device * xdev = platform_get_drvdata ( pdev ) ;
watchdog_unregister_device ( & xdev - > xilinx_wdt_wdd ) ;
2016-08-12 12:17:01 +05:30
clk_disable_unprepare ( xdev - > clk ) ;
2011-06-02 22:13:11 +01:00
return 0 ;
}
2017-08-07 13:24:23 +02:00
/**
* xwdt_suspend - Suspend the device .
*
* @ dev : handle to the device structure .
* Return : 0 always .
*/
static int __maybe_unused xwdt_suspend ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct xwdt_device * xdev = platform_get_drvdata ( pdev ) ;
if ( watchdog_active ( & xdev - > xilinx_wdt_wdd ) )
xilinx_wdt_stop ( & xdev - > xilinx_wdt_wdd ) ;
return 0 ;
}
/**
* xwdt_resume - Resume the device .
*
* @ dev : handle to the device structure .
* Return : 0 on success , errno otherwise .
*/
static int __maybe_unused xwdt_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct xwdt_device * xdev = platform_get_drvdata ( pdev ) ;
int ret = 0 ;
if ( watchdog_active ( & xdev - > xilinx_wdt_wdd ) )
ret = xilinx_wdt_start ( & xdev - > xilinx_wdt_wdd ) ;
return ret ;
}
static SIMPLE_DEV_PM_OPS ( xwdt_pm_ops , xwdt_suspend , xwdt_resume ) ;
2011-06-02 22:13:11 +01:00
/* Match table for of_platform binding */
2014-05-07 17:42:22 +09:00
static const struct of_device_id xwdt_of_match [ ] = {
2013-05-31 07:56:34 +02:00
{ . compatible = " xlnx,xps-timebase-wdt-1.00.a " , } ,
2011-06-02 22:13:11 +01:00
{ . compatible = " xlnx,xps-timebase-wdt-1.01.a " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , xwdt_of_match ) ;
static struct platform_driver xwdt_driver = {
. probe = xwdt_probe ,
2012-11-19 13:21:12 -05:00
. remove = xwdt_remove ,
2011-06-02 22:13:11 +01:00
. driver = {
. name = WATCHDOG_NAME ,
. of_match_table = xwdt_of_match ,
2017-08-07 13:24:23 +02:00
. pm = & xwdt_pm_ops ,
2011-06-02 22:13:11 +01:00
} ,
} ;
2011-11-29 13:56:27 +08:00
module_platform_driver ( xwdt_driver ) ;
2011-06-02 22:13:11 +01:00
MODULE_AUTHOR ( " Alejandro Cabrera <aldaya@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Xilinx Watchdog driver " ) ;
2013-05-31 07:56:33 +02:00
MODULE_LICENSE ( " GPL v2 " ) ;