[PATCH 1/3] watchdog: Add support for the Freescale MXC watchdog

Vladimir Zapolskiy vzapolskiy at gmail.com
Fri Mar 19 11:13:27 EDT 2010


Add driver for the Freescale MXC SoCs built-in watchdog.

Signed-off-by: Vladimir Zapolskiy <vzapolskiy at gmail.com>
Cc: Wim Van Sebroeck <wim at iguana.be>
Cc: Sascha Hauer <s.hauer at pengutronix.de>
Cc: Uwe Kleine-König <u.kleine-koenig at pengutronix.de>
---
 drivers/watchdog/Kconfig   |   10 ++
 drivers/watchdog/Makefile  |    1 +
 drivers/watchdog/mxc_wdt.c |  385 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 396 insertions(+), 0 deletions(-)
 create mode 100644 drivers/watchdog/mxc_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 088f32f..985d067 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -289,6 +289,16 @@ config ADX_WATCHDOG
 	  Say Y here if you want support for the watchdog timer on Avionic
 	  Design Xanthos boards.
 
+config MXC_WATCHDOG
+	tristate "MXC watchdog"
+	depends on ARCH_MXC
+        select WATCHDOG_NOWAYOUT
+	help
+	  Say Y here if to include support for the watchdog timer
+	  in Freescale MXC SoCs.
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxc_wdt.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 475c611..347c227 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o
 obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o
 obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o
 obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o
+obj-$(CONFIG_MXC_WATCHDOG) += mxc_wdt.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
diff --git a/drivers/watchdog/mxc_wdt.c b/drivers/watchdog/mxc_wdt.c
new file mode 100644
index 0000000..0daa37b
--- /dev/null
+++ b/drivers/watchdog/mxc_wdt.c
@@ -0,0 +1,385 @@
+/*
+ * linux/drivers/char/watchdog/mxc_wdt.c
+ *
+ * Watchdog driver for Freescale MXC SoCs. It is based on omap_wdt.c
+ *
+ * Copyright 2010 Vladimir Zapolskiy <vzapolskiy at gmail.com>
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ * 2005 (c) MontaVista Software, Inc.  All Rights Reserved.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/moduleparam.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#define MXC_WDT_WCR             0x00
+#define MXC_WDT_WSR             0x02
+#define MXC_WDT_WRSR            0x04
+#define WCR_WOE_BIT             (1 << 6)
+#define WCR_WDA_BIT             (1 << 5)
+#define WCR_SRS_BIT             (1 << 4)
+#define WCR_WRE_BIT             (1 << 3)
+#define WCR_WDE_BIT             (1 << 2)
+#define WCR_WDBG_BIT            (1 << 1)
+#define WCR_WDZST_BIT           (1 << 0)
+#define WDT_MAGIC_1             0x5555
+#define WDT_MAGIC_2             0xAAAA
+
+#define TIMER_MARGIN_MAX        127
+#define TIMER_MARGIN_DEFAULT    60        /* 60 seconds */
+#define TIMER_MARGIN_MIN        1
+
+#define WDOG_SEC_TO_COUNT(s)  ((s * 2) << 8)
+#define WDOG_COUNT_TO_SEC(c)  ((c >> 8) / 2)
+
+static struct platform_device *mxc_wdt_dev;
+
+struct mxc_wdt {
+        void __iomem    *base;
+        struct device   *dev;
+        unsigned long   status;
+        struct clk      *clk;
+        struct resource *mem;
+};
+
+static spinlock_t wdt_lock;
+
+static unsigned int timer_margin = TIMER_MARGIN_DEFAULT;
+module_param(timer_margin, uint, 0);
+MODULE_PARM_DESC(timer_margin, "initial watchdog timeout in seconds (default="
+                 __MODULE_STRING(TIMER_MARGIN_DEFAULT) ")");
+
+static u16 mxc_wdt_get_bootstatus(struct mxc_wdt *wdt)
+{
+        void __iomem *base = wdt->base;
+        u16 val;
+
+        val = __raw_readw(base + MXC_WDT_WRSR);
+        return val;
+}
+
+static void mxc_wdt_set_timeout(struct mxc_wdt *wdt)
+{
+        void __iomem *base = wdt->base;
+        u16 val;
+
+        val = __raw_readw(base + MXC_WDT_WCR);
+        val = (val & 0x00FF) | WDOG_SEC_TO_COUNT(timer_margin);
+        __raw_writew(val, base + MXC_WDT_WCR);
+}
+
+static void mxc_wdt_ping(struct mxc_wdt *wdt)
+{
+        void __iomem *base = wdt->base;
+
+        /* issue the service sequence instructions */
+        __raw_writew(WDT_MAGIC_1, base + MXC_WDT_WSR);
+        __raw_writew(WDT_MAGIC_2, base + MXC_WDT_WSR);
+}
+
+static void mxc_wdt_enable(struct mxc_wdt *wdt)
+{
+        void __iomem *base = wdt->base;
+        u16 val;
+
+        val = __raw_readw(base + MXC_WDT_WCR);
+        val &= 0xFF00; /* preserve timeout value */
+        val |= WCR_WOE_BIT | WCR_WDA_BIT | WCR_SRS_BIT |
+                WCR_WDZST_BIT | WCR_WDBG_BIT | WCR_WDE_BIT;
+
+        __raw_writew(val, base + MXC_WDT_WCR);
+
+        mxc_wdt_ping(wdt);
+}
+
+static void mxc_wdt_disable(struct mxc_wdt *wdt)
+{
+        /* This stub is remained to include disable support on IMX1 SoCs */
+}
+
+/* Allow only one task to hold watchdog node open */
+static int mxc_wdt_open(struct inode *inode, struct file *file)
+{
+        struct mxc_wdt *wdt = platform_get_drvdata(mxc_wdt_dev);
+
+        if (test_and_set_bit(0, &wdt->status))
+                return -EBUSY;
+
+        file->private_data = (void *)wdt;
+
+        clk_enable(wdt->clk);
+
+        spin_lock(&wdt_lock);
+        mxc_wdt_set_timeout(wdt);
+        mxc_wdt_enable(wdt);
+        spin_unlock(&wdt_lock);
+
+        return 0;
+}
+
+/* The watchdog found on MXC chips cannot be disabled. */
+static int mxc_wdt_release(struct inode *inode, struct file *file)
+{
+        struct mxc_wdt *wdt = file->private_data;
+
+        /* disable the watchdog timer unless NOWAYOUT is defined. */
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+        mxc_wdt_disable(wdt);
+#else
+        printk(KERN_CRIT "MXC watchdog: unexpected close, timer will not stop\n");
+#endif
+        clear_bit(0, &wdt->status);
+
+        clk_disable(wdt->clk);
+
+        return 0;
+}
+
+static ssize_t mxc_wdt_write(struct file *file, const char __user * data,
+                             size_t len, loff_t * ppos)
+{
+        struct mxc_wdt *wdt = file->private_data;
+
+        /* Reload counter */
+        if (len) {
+                spin_lock(&wdt_lock);
+                mxc_wdt_ping(wdt);
+                spin_unlock(&wdt_lock);
+        }
+
+        return len;
+}
+
+static long mxc_wdt_ioctl(struct file *file, unsigned int cmd,
+                          unsigned long arg)
+{
+        void __user *argp = (void __user *)arg;
+        int __user *p = argp;
+        int new_margin;
+        struct mxc_wdt *wdt = file->private_data;
+
+        static const struct watchdog_info mxc_wdt_ident = {
+                .identity = "MXC watchdog",
+                .options = WDIOF_SETTIMEOUT,
+                .firmware_version = 0,
+        };
+
+        switch (cmd) {
+        case WDIOC_GETSUPPORT:
+                return copy_to_user(argp, &mxc_wdt_ident,
+                                    sizeof(mxc_wdt_ident)) ? -EFAULT : 0;
+        case WDIOC_GETSTATUS:
+                return put_user(0, p);
+        case WDIOC_GETBOOTSTATUS:
+                return put_user(mxc_wdt_get_bootstatus(wdt), p);
+        case WDIOC_KEEPALIVE:
+                spin_lock(&wdt_lock);
+                mxc_wdt_ping(wdt);
+                spin_unlock(&wdt_lock);
+                return 0;
+        case WDIOC_SETTIMEOUT:
+                if (get_user(new_margin, p))
+                        return -EFAULT;
+
+                if (new_margin < TIMER_MARGIN_MIN ||
+                    new_margin > TIMER_MARGIN_MAX)
+                        return -EINVAL;
+
+                spin_lock(&wdt_lock);
+                timer_margin = new_margin;
+                mxc_wdt_set_timeout(wdt);
+                mxc_wdt_ping(wdt);
+                spin_unlock(&wdt_lock);
+                /* Fall */
+        case WDIOC_GETTIMEOUT:
+                return put_user(timer_margin, p);
+        default:
+                return -ENOTTY;
+        }
+}
+
+static const struct file_operations mxc_wdt_fops = {
+        .owner          = THIS_MODULE,
+        .llseek         = no_llseek,
+        .write          = mxc_wdt_write,
+        .unlocked_ioctl = mxc_wdt_ioctl,
+        .open           = mxc_wdt_open,
+        .release        = mxc_wdt_release,
+};
+
+static struct miscdevice mxc_wdt_miscdev = {
+        .minor          = WATCHDOG_MINOR,
+        .name           = "watchdog",
+        .fops           = &mxc_wdt_fops,
+};
+
+static int __init mxc_wdt_probe(struct platform_device *pdev)
+{
+        struct resource *res;
+        struct mxc_wdt *wdt;
+        int ret;
+
+        /* reserve static register mappings */
+        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+        if (!res) {
+                ret = -ENOENT;
+                goto err_get_resource;
+        }
+
+        wdt = kzalloc(sizeof(struct mxc_wdt), GFP_KERNEL);
+        if (!wdt) {
+                ret = -ENOMEM;
+                goto err_kzalloc;
+        }
+
+        wdt->status = 0;
+        wdt->mem = request_mem_region(res->start, resource_size(res),
+                                      pdev->name);
+        if (!wdt->mem) {
+                ret = -EBUSY;
+                goto err_busy;
+        }
+
+        /* To determine watchdog clock state it is assumed
+           that the clock is disabled on probe */
+        wdt->clk = clk_get(&pdev->dev, NULL);
+        if (IS_ERR(wdt->clk)) {
+                ret = PTR_ERR(wdt->clk);
+                printk(KERN_ERR "MXC watchdog: no clock source found\n");
+                goto err_clk;
+        }
+
+        wdt->base = ioremap(res->start, resource_size(res));
+        if (!wdt->base) {
+                ret = -ENOMEM;
+                goto err_ioremap;
+        }
+
+        platform_set_drvdata(pdev, wdt);
+
+        mxc_wdt_miscdev.parent = &pdev->dev;
+
+        ret = misc_register(&mxc_wdt_miscdev);
+        if (ret)
+                goto err_misc;
+
+        pr_info("MXC watchdog: initial timeout %d sec\n", timer_margin);
+
+        mxc_wdt_dev = pdev;
+
+        return 0;
+
+ err_misc:
+        platform_set_drvdata(pdev, NULL);
+        iounmap(wdt->base);
+
+ err_ioremap:
+        clk_put(wdt->clk);
+
+ err_clk:
+        release_mem_region(res->start, resource_size(res));
+
+ err_busy:
+        kfree(wdt);
+
+ err_kzalloc:
+ err_get_resource:
+        return ret;
+}
+
+static void mxc_wdt_shutdown(struct platform_device *pdev)
+{
+        struct mxc_wdt *wdt = platform_get_drvdata(pdev);
+
+        if (wdt->status)
+                mxc_wdt_disable(wdt);
+}
+
+static int __devexit mxc_wdt_remove(struct platform_device *pdev)
+{
+        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+        struct mxc_wdt *wdt = platform_get_drvdata(pdev);
+
+        if (!res)
+                return -ENOENT;
+
+        misc_deregister(&mxc_wdt_miscdev);
+
+        platform_set_drvdata(pdev, NULL);
+        iounmap(wdt->base);
+
+        clk_put(wdt->clk);
+
+        release_mem_region(res->start, resource_size(res));
+
+        kfree(wdt);
+        mxc_wdt_dev = NULL;
+
+        return 0;
+}
+
+static struct platform_driver mxc_wdt_driver = {
+        .probe         = mxc_wdt_probe,
+        .remove        = __devexit_p(mxc_wdt_remove),
+        .shutdown      = mxc_wdt_shutdown,
+        .driver        = {
+                .owner = THIS_MODULE,
+                .name  = "imx-wdt",
+        },
+};
+
+static int __init mxc_wdt_init(void)
+{
+        if ((timer_margin < TIMER_MARGIN_MIN) ||
+            (timer_margin > TIMER_MARGIN_MAX)) {
+                pr_info("MXC watchdog error: wrong timer_margin %d\n",
+                        timer_margin);
+                pr_info("    Valid range: %d to %d seconds\n", TIMER_MARGIN_MIN,
+                        TIMER_MARGIN_MAX);
+                return -EINVAL;
+        }
+
+        spin_lock_init(&wdt_lock);
+        return platform_driver_register(&mxc_wdt_driver);
+}
+
+static void __exit mxc_wdt_exit(void)
+{
+        platform_driver_unregister(&mxc_wdt_driver);
+}
+
+module_init(mxc_wdt_init);
+module_exit(mxc_wdt_exit);
+
+MODULE_AUTHOR("Vladimir Zapolskiy");
+MODULE_DESCRIPTION("MXC Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-wdt");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
-- 
1.6.6.1




More information about the linux-arm-kernel mailing list