2018-03-16 18:14:11 +03:00
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2016-07-10 12:11:04 +03:00
/*
* Copyright ( c ) 2016 BayLibre , SAS .
* Author : Neil Armstrong < narmstrong @ baylibre . com >
*
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/watchdog.h>
# define DEFAULT_TIMEOUT 30 /* seconds */
# define GXBB_WDT_CTRL_REG 0x0
# define GXBB_WDT_TCNT_REG 0x8
# define GXBB_WDT_RSET_REG 0xc
# define GXBB_WDT_CTRL_CLKDIV_EN BIT(25)
# define GXBB_WDT_CTRL_CLK_EN BIT(24)
# define GXBB_WDT_CTRL_EE_RESET BIT(21)
# define GXBB_WDT_CTRL_EN BIT(18)
# define GXBB_WDT_CTRL_DIV_MASK (BIT(18) - 1)
# define GXBB_WDT_TCNT_SETUP_MASK (BIT(16) - 1)
# define GXBB_WDT_TCNT_CNT_SHIFT 16
struct meson_gxbb_wdt {
void __iomem * reg_base ;
struct watchdog_device wdt_dev ;
struct clk * clk ;
} ;
static int meson_gxbb_wdt_start ( struct watchdog_device * wdt_dev )
{
struct meson_gxbb_wdt * data = watchdog_get_drvdata ( wdt_dev ) ;
writel ( readl ( data - > reg_base + GXBB_WDT_CTRL_REG ) | GXBB_WDT_CTRL_EN ,
data - > reg_base + GXBB_WDT_CTRL_REG ) ;
return 0 ;
}
static int meson_gxbb_wdt_stop ( struct watchdog_device * wdt_dev )
{
struct meson_gxbb_wdt * data = watchdog_get_drvdata ( wdt_dev ) ;
writel ( readl ( data - > reg_base + GXBB_WDT_CTRL_REG ) & ~ GXBB_WDT_CTRL_EN ,
data - > reg_base + GXBB_WDT_CTRL_REG ) ;
return 0 ;
}
static int meson_gxbb_wdt_ping ( struct watchdog_device * wdt_dev )
{
struct meson_gxbb_wdt * data = watchdog_get_drvdata ( wdt_dev ) ;
writel ( 0 , data - > reg_base + GXBB_WDT_RSET_REG ) ;
return 0 ;
}
static int meson_gxbb_wdt_set_timeout ( struct watchdog_device * wdt_dev ,
unsigned int timeout )
{
struct meson_gxbb_wdt * data = watchdog_get_drvdata ( wdt_dev ) ;
unsigned long tcnt = timeout * 1000 ;
if ( tcnt > GXBB_WDT_TCNT_SETUP_MASK )
tcnt = GXBB_WDT_TCNT_SETUP_MASK ;
wdt_dev - > timeout = timeout ;
meson_gxbb_wdt_ping ( wdt_dev ) ;
writel ( tcnt , data - > reg_base + GXBB_WDT_TCNT_REG ) ;
return 0 ;
}
static unsigned int meson_gxbb_wdt_get_timeleft ( struct watchdog_device * wdt_dev )
{
struct meson_gxbb_wdt * data = watchdog_get_drvdata ( wdt_dev ) ;
unsigned long reg ;
reg = readl ( data - > reg_base + GXBB_WDT_TCNT_REG ) ;
return ( ( reg > > GXBB_WDT_TCNT_CNT_SHIFT ) -
( reg & GXBB_WDT_TCNT_SETUP_MASK ) ) / 1000 ;
}
static const struct watchdog_ops meson_gxbb_wdt_ops = {
. start = meson_gxbb_wdt_start ,
. stop = meson_gxbb_wdt_stop ,
. ping = meson_gxbb_wdt_ping ,
. set_timeout = meson_gxbb_wdt_set_timeout ,
. get_timeleft = meson_gxbb_wdt_get_timeleft ,
} ;
static const struct watchdog_info meson_gxbb_wdt_info = {
. identity = " Meson GXBB Watchdog " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
} ;
static int __maybe_unused meson_gxbb_wdt_resume ( struct device * dev )
{
struct meson_gxbb_wdt * data = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & data - > wdt_dev ) )
meson_gxbb_wdt_start ( & data - > wdt_dev ) ;
return 0 ;
}
static int __maybe_unused meson_gxbb_wdt_suspend ( struct device * dev )
{
struct meson_gxbb_wdt * data = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & data - > wdt_dev ) )
meson_gxbb_wdt_stop ( & data - > wdt_dev ) ;
return 0 ;
}
static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( meson_gxbb_wdt_suspend , meson_gxbb_wdt_resume )
} ;
static const struct of_device_id meson_gxbb_wdt_dt_ids [ ] = {
{ . compatible = " amlogic,meson-gxbb-wdt " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , meson_gxbb_wdt_dt_ids ) ;
static int meson_gxbb_wdt_probe ( struct platform_device * pdev )
{
struct meson_gxbb_wdt * data ;
struct resource * res ;
int ret ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
data - > reg_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( data - > reg_base ) )
return PTR_ERR ( data - > reg_base ) ;
data - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( data - > clk ) )
return PTR_ERR ( data - > clk ) ;
2017-06-07 12:34:15 +03:00
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret )
return ret ;
2016-07-10 12:11:04 +03:00
platform_set_drvdata ( pdev , data ) ;
data - > wdt_dev . parent = & pdev - > dev ;
data - > wdt_dev . info = & meson_gxbb_wdt_info ;
data - > wdt_dev . ops = & meson_gxbb_wdt_ops ;
data - > wdt_dev . max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK ;
data - > wdt_dev . min_timeout = 1 ;
data - > wdt_dev . timeout = DEFAULT_TIMEOUT ;
watchdog_set_drvdata ( & data - > wdt_dev , data ) ;
/* Setup with 1ms timebase */
writel ( ( ( clk_get_rate ( data - > clk ) / 1000 ) & GXBB_WDT_CTRL_DIV_MASK ) |
GXBB_WDT_CTRL_EE_RESET |
GXBB_WDT_CTRL_CLK_EN |
GXBB_WDT_CTRL_CLKDIV_EN ,
data - > reg_base + GXBB_WDT_CTRL_REG ) ;
meson_gxbb_wdt_set_timeout ( & data - > wdt_dev , data - > wdt_dev . timeout ) ;
ret = watchdog_register_device ( & data - > wdt_dev ) ;
if ( ret ) {
clk_disable_unprepare ( data - > clk ) ;
return ret ;
}
return 0 ;
}
static int meson_gxbb_wdt_remove ( struct platform_device * pdev )
{
struct meson_gxbb_wdt * data = platform_get_drvdata ( pdev ) ;
watchdog_unregister_device ( & data - > wdt_dev ) ;
clk_disable_unprepare ( data - > clk ) ;
return 0 ;
}
static void meson_gxbb_wdt_shutdown ( struct platform_device * pdev )
{
struct meson_gxbb_wdt * data = platform_get_drvdata ( pdev ) ;
meson_gxbb_wdt_stop ( & data - > wdt_dev ) ;
}
static struct platform_driver meson_gxbb_wdt_driver = {
. probe = meson_gxbb_wdt_probe ,
. remove = meson_gxbb_wdt_remove ,
. shutdown = meson_gxbb_wdt_shutdown ,
. driver = {
. name = " meson-gxbb-wdt " ,
. pm = & meson_gxbb_wdt_pm_ops ,
. of_match_table = meson_gxbb_wdt_dt_ids ,
} ,
} ;
module_platform_driver ( meson_gxbb_wdt_driver ) ;
MODULE_AUTHOR ( " Neil Armstrong <narmstrong@baylibre.com> " ) ;
MODULE_DESCRIPTION ( " Amlogic Meson GXBB Watchdog timer driver " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;