[PATCH] Watchdog: Adding support for Watchdog found with ARM CortexA9
Viresh KUMAR
viresh.kumar at st.com
Mon May 17 05:54:04 EDT 2010
Watchdog details can be found in ARM Cortex A9 reference manual at:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0407e/indix.html
Signed-off-by: Viresh Kumar <viresh.kumar at st.com>
---
drivers/watchdog/Kconfig | 7 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/cortexa9_wdt.c | 414 +++++++++++++++++++++++++++++++++++++++
3 files changed, 422 insertions(+), 0 deletions(-)
create mode 100755 drivers/watchdog/cortexa9_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 0bf5020..b5c0f6f 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -73,6 +73,13 @@ config WM8350_WATCHDOG
# ARM Architecture
+config ARM_CORTEXA9_WATCHDOG
+ tristate "ARM CortexA9 Watchdog"
+ depends on ARM_AMBA
+ help
+ Watchdog timer present with ARM Cortex A9 processor. This will reboot
+ your system when the timeout is reached.
+
config AT91RM9200_WATCHDOG
tristate "AT91RM9200 watchdog"
depends on ARCH_AT91RM9200
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 5e3cb95..7704efc 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
# ALPHA Architecture
# ARM Architecture
+obj-$(CONFIG_ARM_CORTEXA9_WATCHDOG) += cortexa9_wdt.o
obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o
obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
diff --git a/drivers/watchdog/cortexa9_wdt.c b/drivers/watchdog/cortexa9_wdt.c
new file mode 100755
index 0000000..f50ec64
--- /dev/null
+++ b/drivers/watchdog/cortexa9_wdt.c
@@ -0,0 +1,414 @@
+/*
+ * drivers/char/watchdog/cortexa9-wdt.c
+ *
+ * Watchdog driver for watchdog module present with ARM CORTEX A9
+ *
+ * Copyright (C) 2010 ST Microelectronics
+ * Viresh Kumar<viresh.kumar at st.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+
+/* default timeout in seconds */
+#define DEFAULT_TIMEOUT 60
+
+#define MODULE_NAME "cortexa9-wdt"
+
+/* watchdog register offsets and masks */
+#define WDTLOAD 0x000
+ #define LOAD_MIN 0x00000000
+ #define LOAD_MAX 0xFFFFFFFF
+#define WDTCOUNTER 0x004
+#define WDTCONTROL 0x008
+ /* control register masks */
+ #define WDT_ENB (1 << 0)
+ #define WDT_ARLD_ENB (1 << 1) /* Autoreload enable */
+ #define WDT_IRQ_ENB (1 << 2)
+ #define WDT_MODE (1 << 3)
+ #define WDT_PRESCALE(x) ((x) << 8)
+ #define PRESCALE_MIN 0x00
+ #define PRESCALE_MAX 0xFF
+#define WDTINTSTS 0x00C
+ #define INT_MASK 0x1
+#define WDTRSTSTS 0x010
+ #define RST_MASK 0x1
+#define WDTDISABLE 0x014
+ #define DISABLE_VAL1 0x12345678
+ #define DISABLE_VAL2 0x87654321
+
+/**
+ * struct cortexa9_wdt: cortex a9 wdt device structure
+ *
+ * lock: spin lock protecting dev structure and io access
+ * base: base address of wdt
+ * clk: clock structure of wdt
+ * pdev: platform device structure of wdt
+ * boot_status: boot time status of wdt
+ * status: current status of wdt
+ * prescale: prescale value to be set for current timeout
+ * load_val: load value to be set for current timeout
+ * timeout: current programmed timeout
+ */
+struct cortexa9_wdt {
+ spinlock_t lock;
+ void __iomem *base;
+ struct clk *clk;
+ struct platform_device *pdev;
+ unsigned long boot_status;
+ unsigned long status;
+ #define WDT_BUSY 0
+ #define WDT_CAN_BE_CLOSED 1
+ unsigned int prescale;
+ unsigned int load_val;
+ unsigned int timeout;
+};
+
+/* local variables */
+static struct cortexa9_wdt *wdt;
+static int nowayout = WATCHDOG_NOWAYOUT;
+
+/* binary search */
+static inline void bsearch(u32 *var, u32 var_start, u32 var_end,
+ const u64 param, const u32 timeout, u32 rate)
+{
+ u64 tmp = 0;
+
+ /* get the lowest var value that can satisfy our requirement */
+ while (var_start < var_end) {
+ tmp = var_start;
+ tmp += var_end;
+ tmp = div_u64(tmp, 2);
+ if (timeout > div_u64((param + 1) * (tmp + 1), rate)) {
+ if (var_start == tmp)
+ break;
+ else
+ var_start = tmp;
+ } else {
+ if (var_end == tmp)
+ break;
+ else
+ var_end = tmp;
+ }
+ }
+ *var = tmp;
+}
+
+/*
+ * This routine finds the most appropriate prescale and load value for input
+ * timout value
+ */
+static void wdt_config(unsigned int timeout)
+{
+ unsigned int psc, rate;
+ u64 load = 0;
+
+ rate = clk_get_rate(wdt->clk);
+
+ /* get appropriate value of psc and load */
+ bsearch(&psc, PRESCALE_MIN, PRESCALE_MAX, LOAD_MAX, timeout, rate);
+ bsearch((u32 *)&load, LOAD_MIN, LOAD_MAX, psc, timeout, rate);
+
+ spin_lock(&wdt->lock);
+ wdt->prescale = psc;
+ wdt->load_val = load;
+
+ /* roundup timeout to closest positive integer value */
+ wdt->timeout = div_u64((psc + 1) * (load + 1) + (rate / 2), rate);
+ spin_unlock(&wdt->lock);
+}
+
+static void wdt_enable(void)
+{
+ u32 val;
+
+ spin_lock(&wdt->lock);
+
+ writel(wdt->load_val, wdt->base + WDTLOAD);
+ val = WDT_MODE | WDT_PRESCALE(wdt->prescale) | WDT_ENB;
+ writel(val, wdt->base + WDTCONTROL);
+
+ spin_unlock(&wdt->lock);
+}
+
+static void wdt_disable(void)
+{
+ spin_lock(&wdt->lock);
+
+ /* firstly we need to put watchdog in timer mode */
+ writel(DISABLE_VAL1, wdt->base + WDTDISABLE);
+ writel(DISABLE_VAL2, wdt->base + WDTDISABLE);
+
+ writel(0, wdt->base + WDTCONTROL);
+ writel(INT_MASK, wdt->base + WDTINTSTS);
+ writel(RST_MASK, wdt->base + WDTRSTSTS);
+
+ spin_unlock(&wdt->lock);
+}
+
+static ssize_t cortexa9_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ clear_bit(WDT_CAN_BE_CLOSED, &wdt->status);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V') {
+ set_bit(WDT_CAN_BE_CLOSED,
+ &wdt->status);
+ break;
+ }
+ }
+ }
+ wdt_enable();
+ }
+ return len;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
+ WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = MODULE_NAME,
+};
+
+static long cortexa9_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY;
+ int timeout;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(wdt->boot_status, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ wdt_enable();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(timeout, (int *)arg);
+ if (ret)
+ break;
+
+ wdt_config(timeout);
+
+ wdt_enable();
+ /* Fall through */
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(wdt->timeout, (int *)arg);
+ break;
+ }
+ return ret;
+}
+
+static int cortexa9_wdt_open(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+
+ if (test_and_set_bit(WDT_BUSY, &wdt->status))
+ return -EBUSY;
+
+ ret = clk_enable(wdt->clk);
+ if (ret) {
+ dev_err(&wdt->pdev->dev, "clock enable fail");
+ goto err;
+ }
+
+ wdt_enable();
+
+ /* can not be closed, once enabled */
+ clear_bit(WDT_CAN_BE_CLOSED, &wdt->status);
+ return nonseekable_open(inode, file);
+
+err:
+ clear_bit(WDT_BUSY, &wdt->status);
+ return ret;
+}
+
+static int cortexa9_wdt_release(struct inode *inode, struct file *file)
+{
+ if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) {
+ clear_bit(WDT_BUSY, &wdt->status);
+ dev_warn(&wdt->pdev->dev, "Device closed unexpectedly\n");
+ return 0;
+ }
+
+ wdt_disable();
+ clk_disable(wdt->clk);
+ clear_bit(WDT_BUSY, &wdt->status);
+
+ return 0;
+}
+
+static const struct file_operations cortexa9_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = cortexa9_wdt_write,
+ .unlocked_ioctl = cortexa9_wdt_ioctl,
+ .open = cortexa9_wdt_open,
+ .release = cortexa9_wdt_release,
+};
+
+static struct miscdevice cortexa9_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &cortexa9_wdt_fops,
+};
+
+static int __devinit cortexa9_wdt_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENOENT;
+ dev_warn(&pdev->dev, "Memory resource not defined\n");
+ goto err;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+ dev_warn(&pdev->dev, "Failed to get memory region resource\n");
+ ret = -ENOENT;
+ goto err;
+ }
+
+ wdt = kzalloc(sizeof(*wdt), GFP_KERNEL);
+ if (!wdt) {
+ dev_warn(&pdev->dev, "Kzalloc failed\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ wdt->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(wdt->clk)) {
+ dev_warn(&pdev->dev, "Clock not found\n");
+ ret = PTR_ERR(wdt->clk);
+ goto err_clk_get;
+ }
+
+ wdt->base = ioremap(res->start, resource_size(res));
+ if (!wdt->base) {
+ ret = -ENOMEM;
+ dev_warn(&pdev->dev, "ioremap fail\n");
+ goto err_ioremap;
+ }
+
+ spin_lock_init(&wdt->lock);
+ /* This checks if system booted after watchdog reset or not */
+ ret = clk_enable(wdt->clk);
+ if (!ret) {
+ wdt->boot_status = (readl(wdt->base + WDTRSTSTS) & RST_MASK) ?
+ WDIOF_CARDRESET : 0;
+ wdt_disable();
+ clk_disable(wdt->clk);
+ }
+
+ wdt->pdev = pdev;
+ wdt_config(DEFAULT_TIMEOUT);
+
+ ret = misc_register(&cortexa9_wdt_miscdev);
+ if (ret < 0) {
+ dev_warn(&pdev->dev, "cannot register misc device\n");
+ goto err_misc_register;
+ }
+
+ return 0;
+
+err_misc_register:
+ iounmap(wdt->base);
+err_ioremap:
+ clk_put(wdt->clk);
+err_clk_get:
+ kfree(wdt);
+ wdt = NULL;
+err_kzalloc:
+ release_mem_region(res->start, resource_size(res));
+err:
+ dev_err(&pdev->dev, "Probe Failed!!!\n");
+ return ret;
+}
+
+static int __devexit cortexa9_wdt_remove(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ misc_deregister(&cortexa9_wdt_miscdev);
+ iounmap(wdt->base);
+ clk_put(wdt->clk);
+ kfree(wdt);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+
+ return 0;
+}
+
+static struct platform_driver cortexa9_wdt_driver = {
+ .driver = {
+ .name = MODULE_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = cortexa9_wdt_probe,
+ .remove = __devexit_p(cortexa9_wdt_remove),
+};
+
+static int __init cortexa9_wdt_init(void)
+{
+ return platform_driver_register(&cortexa9_wdt_driver);
+}
+module_init(cortexa9_wdt_init);
+
+static void __exit cortexa9_wdt_exit(void)
+{
+ platform_driver_unregister(&cortexa9_wdt_driver);
+}
+module_exit(cortexa9_wdt_exit);
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout,
+ "Set to 1 to keep watchdog running after device release");
+
+MODULE_AUTHOR("Viresh Kumar <viresh.kumar at st.com>");
+MODULE_DESCRIPTION("ARM CORTEX A9 Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
--
1.6.0.2
More information about the linux-arm-kernel
mailing list