blob: 18d17c6952a2bcd947114c36c767b7640f3599c5 [file] [log] [blame]
/*
* drivers/watchdog/zx29_wdt.c
*
* Copyright (C) 2015 ZTE-TSP
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <mach/board.h>
#include "zx29_wdt.h"
/*****************************************************************************
** | --> | --> |
** 15s(value load) 5s(feed in irq) 0s(reset if no feed)
*****************************************************************************
**/
#define CONFIG_ZX29_WATCHDOG_ATBOOT (0)
#define CONFIG_ZX29_WATCHDOG_DEFAULT_TIME (15)
#define CONFIG_ZX29_WATCHDOG_DEADLINE_TIME (5)
static bool nowayout = WATCHDOG_NOWAYOUT; /* 1-disagree to close wdt 0-agree to close wdt */
static int tmr_margin = CONFIG_ZX29_WATCHDOG_DEFAULT_TIME; /* max during to feed dog, unit is second */
static int tmr_atboot = CONFIG_ZX29_WATCHDOG_ATBOOT; /* 0 - no enable wdt when boot */
static int debug;
module_param(tmr_margin, int, 0);
module_param(tmr_atboot, int, 0);
module_param(nowayout, bool, 0);
module_param(debug, int, 0);
MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
__MODULE_STRING(CONFIG_ZX29_WATCHDOG_DEFAULT_TIME) ")");
MODULE_PARM_DESC(tmr_atboot,
"Watchdog is started at boot time if set to 1, default="
__MODULE_STRING(CONFIG_ZX29_WATCHDOG_ATBOOT));
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)");
static struct device *wdt_dev; /* platform device attached to */
static struct clk *wdt_clock;
static struct clk *wdt_apb_clock;
static void __iomem *wdt_base;
static unsigned int wdt_count;
static DEFINE_SPINLOCK(wdt_lock);
static struct resource *wdt_irq;
/* watchdog control routines */
#define DBG(fmt, ...) \
do { \
if (debug) \
pr_info(fmt, ##__VA_ARGS__); \
} while (0)
/* functions */
static unsigned int feed_count = 0;
static int zx29_wdt_keepalive(struct watchdog_device *wdd)
{
spin_lock(&wdt_lock);
__wdt_set_load(wdt_base, wdt_count);
feed_count ++;
spin_unlock(&wdt_lock);
return 0;
}
static int zx29_wdt_stop(struct watchdog_device *wdd)
{
spin_lock(&wdt_lock);
__wdt_stop(wdt_base);
spin_unlock(&wdt_lock);
return 0;
}
static int zx29_wdt_start(struct watchdog_device *wdd)
{
spin_lock(&wdt_lock);
__wdt_start(wdt_base);
spin_unlock(&wdt_lock);
return 0;
}
/*
* timeout -- unit(s)
*
*/
static int zx29_wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout)
{
wdt_count = timeout*WDT_TIME_1S;
zx29_wdt_keepalive(wdd);
spin_lock(&wdt_lock);
__wdt_set_int_value(wdt_base, CONFIG_ZX29_WATCHDOG_DEADLINE_TIME*WDT_TIME_1S);
spin_unlock(&wdt_lock);
return 0;
}
#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
static const struct watchdog_info zx29_wdt_ident = {
.options = OPTIONS,
.firmware_version = 0,
.identity = "ZX29 Watchdog",
};
static struct watchdog_ops zx29_wdt_ops = {
.owner = THIS_MODULE,
.start = zx29_wdt_start,
.stop = zx29_wdt_stop,
.ping = zx29_wdt_keepalive,
.set_timeout = zx29_wdt_set_heartbeat,
};
static struct watchdog_device zx29_wdd = {
.info = &zx29_wdt_ident,
.ops = &zx29_wdt_ops,
};
/* interrupt handler code */
static irqreturn_t zx29_wdt_irq(int irqno, void *param)
{
dev_info(wdt_dev, "watchdog timer expired (irq)\n");
zx29_wdt_keepalive(&zx29_wdd);
return IRQ_HANDLED;
}
static int __devinit wdt_init_clk(struct platform_device *pdev)
{
int ret;
wdt_apb_clock = clk_get(&pdev->dev, "apb_clk");
if (IS_ERR(wdt_apb_clock)) {
dev_err(&pdev->dev, "failed to find watchdog apb clock source\n");
ret = PTR_ERR(wdt_apb_clock);
return ret;
}
clk_enable(wdt_apb_clock);
wdt_clock = clk_get(&pdev->dev, "work_clk");
if (IS_ERR(wdt_clock)) {
dev_err(&pdev->dev, "failed to find watchdog clock source\n");
ret = PTR_ERR(wdt_clock);
goto err;
}
clk_enable(wdt_clock);
clk_set_rate(wdt_clock, WDT_SOURCE_CLOCK_RATE);
/* 32768/32 = 1kHz */
__wdt_set_prescale(wdt_base, 31);
return 0;
err:
clk_disable(wdt_apb_clock);
clk_put(wdt_apb_clock);
wdt_apb_clock = NULL;
return ret;
}
static int __devinit zx29_wdt_probe(struct platform_device *pdev)
{
struct device *dev;
int started = 0;
int ret;
struct resource *wdt_mem;
DBG("[WDT]%s: probe=%p\n", __func__, pdev);
dev = &pdev->dev;
wdt_dev = &pdev->dev;
wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (wdt_mem == NULL) {
dev_err(dev, "no memory resource specified\n");
return -ENOENT;
}
wdt_base = (void __iomem *)wdt_mem->start;
DBG("[WDT]probe: wdt_base=%p\n", wdt_base);
wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (wdt_irq == NULL) {
dev_err(dev, "no irq resource specified\n");
return -ENOENT;
}
ret = wdt_init_clk(pdev);
if(ret)
return ret;
started = zx29_wdt_set_heartbeat(&zx29_wdd, tmr_margin);
if (started) {
dev_info(dev,
"tmr_margin value out of range, ( %d ) used\n",
tmr_margin);
}
ret = request_irq(wdt_irq->start, zx29_wdt_irq, 0, pdev->name, pdev);
if (ret != 0) {
dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_clk;
}
watchdog_set_nowayout(&zx29_wdd, nowayout);
ret = watchdog_register_device(&zx29_wdd);
if (ret) {
dev_err(dev, "cannot register watchdog (%d)\n", ret);
goto err_irq;
}
if (tmr_atboot && started == 0) {
dev_info(dev, "starting watchdog timer\n");
zx29_wdt_start(&zx29_wdd);
__wdt_enable_reset();
} else if (!tmr_atboot) {
zx29_wdt_stop(&zx29_wdd);
}
pr_info("[WDT]Watchdog Timer init OK.\n");
return 0;
err_irq:
free_irq(wdt_irq->start, pdev);
err_clk:
clk_disable(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL;
return ret;
}
static int __devexit zx29_wdt_remove(struct platform_device *dev)
{
watchdog_unregister_device(&zx29_wdd);
free_irq(wdt_irq->start, dev);
clk_disable(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL;
clk_disable(wdt_apb_clock);
clk_put(wdt_apb_clock);
wdt_apb_clock = NULL;
return 0;
}
static void zx29_wdt_shutdown(struct platform_device *dev)
{
zx29_wdt_stop(&zx29_wdd);
__wdt_disable_reset();
}
#ifdef CONFIG_PM
static zx29_wdt_context wdt_context;
static int zx29_wdt_suspend(struct platform_device *dev, pm_message_t state)
{
spin_lock(&wdt_lock);
__wdt_save_context(wdt_base, (u32 *)&wdt_context);
spin_unlock(&wdt_lock);
return 0;
}
static int zx29_wdt_resume(struct platform_device *dev)
{
spin_lock(&wdt_lock);
__wdt_restore_context(wdt_base, (u32 *)&wdt_context);
spin_unlock(&wdt_lock);
return 0;
}
#else
#define zx29_wdt_suspend NULL
#define zx29_wdt_resume NULL
#endif /* CONFIG_PM */
#ifdef CONFIG_OF
static const struct of_device_id zx29_wdt_match[] = {
{ .compatible = "zte, zx29_ap_wdt" },
{},
};
MODULE_DEVICE_TABLE(of, zx29_wdt_match);
#else
#define zx29_wdt_match NULL
#endif
static struct platform_driver zx29_wdt_driver = {
.probe = zx29_wdt_probe,
.remove = __devexit_p(zx29_wdt_remove),
.shutdown = zx29_wdt_shutdown,
.suspend = zx29_wdt_suspend,
.resume = zx29_wdt_resume,
.driver = {
.owner = THIS_MODULE,
.name = "zx29_ap_wdt",
.of_match_table = zx29_wdt_match,
},
};
static int __init watchdog_init(void)
{
return platform_driver_register(&zx29_wdt_driver);
}
static void __exit watchdog_exit(void)
{
platform_driver_unregister(&zx29_wdt_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);