forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
6.8 KiB
261 lines
6.8 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* drivers/char/watchdog/pnx4008_wdt.c |
|
* |
|
* Watchdog driver for PNX4008 board |
|
* |
|
* Authors: Dmitry Chigirev <[email protected]>, |
|
* Vitaly Wool <[email protected]> |
|
* Based on sa1100 driver, |
|
* Copyright (C) 2000 Oleg Drokin <[email protected]> |
|
* |
|
* 2005-2006 (c) MontaVista Software, Inc. |
|
* |
|
* (C) 2012 Wolfram Sang, Pengutronix |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/module.h> |
|
#include <linux/moduleparam.h> |
|
#include <linux/types.h> |
|
#include <linux/kernel.h> |
|
#include <linux/watchdog.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/clk.h> |
|
#include <linux/spinlock.h> |
|
#include <linux/io.h> |
|
#include <linux/slab.h> |
|
#include <linux/err.h> |
|
#include <linux/of.h> |
|
#include <linux/delay.h> |
|
#include <linux/reboot.h> |
|
|
|
/* WatchDog Timer - Chapter 23 Page 207 */ |
|
|
|
#define DEFAULT_HEARTBEAT 19 |
|
#define MAX_HEARTBEAT 60 |
|
|
|
/* Watchdog timer register set definition */ |
|
#define WDTIM_INT(p) ((p) + 0x0) |
|
#define WDTIM_CTRL(p) ((p) + 0x4) |
|
#define WDTIM_COUNTER(p) ((p) + 0x8) |
|
#define WDTIM_MCTRL(p) ((p) + 0xC) |
|
#define WDTIM_MATCH0(p) ((p) + 0x10) |
|
#define WDTIM_EMR(p) ((p) + 0x14) |
|
#define WDTIM_PULSE(p) ((p) + 0x18) |
|
#define WDTIM_RES(p) ((p) + 0x1C) |
|
|
|
/* WDTIM_INT bit definitions */ |
|
#define MATCH_INT 1 |
|
|
|
/* WDTIM_CTRL bit definitions */ |
|
#define COUNT_ENAB 1 |
|
#define RESET_COUNT (1 << 1) |
|
#define DEBUG_EN (1 << 2) |
|
|
|
/* WDTIM_MCTRL bit definitions */ |
|
#define MR0_INT 1 |
|
#undef RESET_COUNT0 |
|
#define RESET_COUNT0 (1 << 2) |
|
#define STOP_COUNT0 (1 << 2) |
|
#define M_RES1 (1 << 3) |
|
#define M_RES2 (1 << 4) |
|
#define RESFRC1 (1 << 5) |
|
#define RESFRC2 (1 << 6) |
|
|
|
/* WDTIM_EMR bit definitions */ |
|
#define EXT_MATCH0 1 |
|
#define MATCH_OUTPUT_HIGH (2 << 4) /*a MATCH_CTRL setting */ |
|
|
|
/* WDTIM_RES bit definitions */ |
|
#define WDOG_RESET 1 /* read only */ |
|
|
|
#define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ |
|
|
|
static bool nowayout = WATCHDOG_NOWAYOUT; |
|
static unsigned int heartbeat; |
|
|
|
static DEFINE_SPINLOCK(io_lock); |
|
static void __iomem *wdt_base; |
|
static struct clk *wdt_clk; |
|
|
|
static int pnx4008_wdt_start(struct watchdog_device *wdd) |
|
{ |
|
spin_lock(&io_lock); |
|
|
|
/* stop counter, initiate counter reset */ |
|
writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); |
|
/*wait for reset to complete. 100% guarantee event */ |
|
while (readl(WDTIM_COUNTER(wdt_base))) |
|
cpu_relax(); |
|
/* internal and external reset, stop after that */ |
|
writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, WDTIM_MCTRL(wdt_base)); |
|
/* configure match output */ |
|
writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); |
|
/* clear interrupt, just in case */ |
|
writel(MATCH_INT, WDTIM_INT(wdt_base)); |
|
/* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ |
|
writel(0xFFFF, WDTIM_PULSE(wdt_base)); |
|
writel(wdd->timeout * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); |
|
/*enable counter, stop when debugger active */ |
|
writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); |
|
|
|
spin_unlock(&io_lock); |
|
return 0; |
|
} |
|
|
|
static int pnx4008_wdt_stop(struct watchdog_device *wdd) |
|
{ |
|
spin_lock(&io_lock); |
|
|
|
writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ |
|
|
|
spin_unlock(&io_lock); |
|
return 0; |
|
} |
|
|
|
static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, |
|
unsigned int new_timeout) |
|
{ |
|
wdd->timeout = new_timeout; |
|
return 0; |
|
} |
|
|
|
static int pnx4008_restart_handler(struct watchdog_device *wdd, |
|
unsigned long mode, void *cmd) |
|
{ |
|
const char *boot_cmd = cmd; |
|
|
|
/* |
|
* Verify if a "cmd" passed from the userspace program rebooting |
|
* the system; if available, handle it. |
|
* - For details, see the 'reboot' syscall in kernel/reboot.c |
|
* - If the received "cmd" is not supported, use the default mode. |
|
*/ |
|
if (boot_cmd) { |
|
if (boot_cmd[0] == 'h') |
|
mode = REBOOT_HARD; |
|
else if (boot_cmd[0] == 's') |
|
mode = REBOOT_SOFT; |
|
} |
|
|
|
if (mode == REBOOT_SOFT) { |
|
/* Force match output active */ |
|
writel(EXT_MATCH0, WDTIM_EMR(wdt_base)); |
|
/* Internal reset on match output (RESOUT_N not asserted) */ |
|
writel(M_RES1, WDTIM_MCTRL(wdt_base)); |
|
} else { |
|
/* Instant assert of RESETOUT_N with pulse length 1mS */ |
|
writel(13000, WDTIM_PULSE(wdt_base)); |
|
writel(M_RES2 | RESFRC1 | RESFRC2, WDTIM_MCTRL(wdt_base)); |
|
} |
|
|
|
/* Wait for watchdog to reset system */ |
|
mdelay(1000); |
|
|
|
return NOTIFY_DONE; |
|
} |
|
|
|
static const struct watchdog_info pnx4008_wdt_ident = { |
|
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | |
|
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, |
|
.identity = "PNX4008 Watchdog", |
|
}; |
|
|
|
static const struct watchdog_ops pnx4008_wdt_ops = { |
|
.owner = THIS_MODULE, |
|
.start = pnx4008_wdt_start, |
|
.stop = pnx4008_wdt_stop, |
|
.set_timeout = pnx4008_wdt_set_timeout, |
|
.restart = pnx4008_restart_handler, |
|
}; |
|
|
|
static struct watchdog_device pnx4008_wdd = { |
|
.info = &pnx4008_wdt_ident, |
|
.ops = &pnx4008_wdt_ops, |
|
.timeout = DEFAULT_HEARTBEAT, |
|
.min_timeout = 1, |
|
.max_timeout = MAX_HEARTBEAT, |
|
}; |
|
|
|
static void pnx4008_clk_disable_unprepare(void *data) |
|
{ |
|
clk_disable_unprepare(data); |
|
} |
|
|
|
static int pnx4008_wdt_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
int ret = 0; |
|
|
|
watchdog_init_timeout(&pnx4008_wdd, heartbeat, dev); |
|
|
|
wdt_base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(wdt_base)) |
|
return PTR_ERR(wdt_base); |
|
|
|
wdt_clk = devm_clk_get(dev, NULL); |
|
if (IS_ERR(wdt_clk)) |
|
return PTR_ERR(wdt_clk); |
|
|
|
ret = clk_prepare_enable(wdt_clk); |
|
if (ret) |
|
return ret; |
|
ret = devm_add_action_or_reset(dev, pnx4008_clk_disable_unprepare, |
|
wdt_clk); |
|
if (ret) |
|
return ret; |
|
|
|
pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? |
|
WDIOF_CARDRESET : 0; |
|
pnx4008_wdd.parent = dev; |
|
watchdog_set_nowayout(&pnx4008_wdd, nowayout); |
|
watchdog_set_restart_priority(&pnx4008_wdd, 128); |
|
|
|
if (readl(WDTIM_CTRL(wdt_base)) & COUNT_ENAB) |
|
set_bit(WDOG_HW_RUNNING, &pnx4008_wdd.status); |
|
|
|
ret = devm_watchdog_register_device(dev, &pnx4008_wdd); |
|
if (ret < 0) |
|
return ret; |
|
|
|
dev_info(dev, "heartbeat %d sec\n", pnx4008_wdd.timeout); |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_OF |
|
static const struct of_device_id pnx4008_wdt_match[] = { |
|
{ .compatible = "nxp,pnx4008-wdt" }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(of, pnx4008_wdt_match); |
|
#endif |
|
|
|
static struct platform_driver platform_wdt_driver = { |
|
.driver = { |
|
.name = "pnx4008-watchdog", |
|
.of_match_table = of_match_ptr(pnx4008_wdt_match), |
|
}, |
|
.probe = pnx4008_wdt_probe, |
|
}; |
|
|
|
module_platform_driver(platform_wdt_driver); |
|
|
|
MODULE_AUTHOR("MontaVista Software, Inc. <[email protected]>"); |
|
MODULE_AUTHOR("Wolfram Sang <[email protected]>"); |
|
MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); |
|
|
|
module_param(heartbeat, uint, 0); |
|
MODULE_PARM_DESC(heartbeat, |
|
"Watchdog heartbeat period in seconds from 1 to " |
|
__MODULE_STRING(MAX_HEARTBEAT) ", default " |
|
__MODULE_STRING(DEFAULT_HEARTBEAT)); |
|
|
|
module_param(nowayout, bool, 0); |
|
MODULE_PARM_DESC(nowayout, |
|
"Set to 1 to keep watchdog running after device release"); |
|
|
|
MODULE_LICENSE("GPL"); |
|
MODULE_ALIAS("platform:pnx4008-watchdog");
|
|
|