]> rtime.felk.cvut.cz Git - zynq/linux.git/commitdiff
Xilinx: PSS WDT Driver
authorSadanand M <[sadanand.mutyala@xilinx.com]>
Thu, 11 Feb 2010 16:07:17 +0000 (09:07 -0700)
committerJohn Linn <john.linn@xilinx.com>
Thu, 11 Feb 2010 16:07:17 +0000 (09:07 -0700)
This is a new driver for Cadence WDT in Pele
It has been tested on EP4.5

Signed-off-by: Sadanand M <sadanan@xilinx.com>
arch/arm/mach-xilinx/devices.c
arch/arm/mach-xilinx/include/mach/hardware.h
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/xilinx_wdtpss.c [new file with mode: 0644]

index c9755c43e60ef0f37b6ef2c41789b861658e0a59..75f83b934cc16afd6dd554c979d0a2455f319f82 100644 (file)
@@ -315,6 +315,24 @@ static struct platform_device xilinx_spipss_0_device = {
        .num_resources = ARRAY_SIZE(xspipss_0_resource),
 };
 
+/*************************PSS WDT*********************/
+static struct resource xwdtpss_0_resource[] = {
+       {
+               .start  = WDT0_BASE,
+               .end    = WDT0_BASE + 0x00FF,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+static struct platform_device xilinx_wdtpss_0_device = {
+       .name = "xilinx_pss_wdt",
+       .id = 0,
+       .dev = {
+               .platform_data = NULL,
+       },
+       .resource = xwdtpss_0_resource,
+       .num_resources = ARRAY_SIZE(xwdtpss_0_resource),
+};
 
 /* add all platform devices to the following table so they
  * will be registered
@@ -332,6 +350,7 @@ struct platform_device *xilinx_pdevices[] __initdata = {
        &eth_device1,
 #endif
        &xilinx_spipss_0_device,
+       &xilinx_wdtpss_0_device,
 };
 
 /**
index 9baef7b5d3b13f8fe8d019b12da85fe350d22e0c..fdb3b954cbd64c940a20b4fc984213d96f80ca93 100644 (file)
@@ -54,6 +54,7 @@
 #define ETH1_BASE               (IO_BASE + 0x0000C000)  /* 0xE000C000 */
 #define SPI0_BASE              (IO_BASE + 0x00006000)  /* 0xE0006000 */
 #define SPI1_BASE              (IO_BASE + 0x00007000)  /* 0xE0007000 */
+#define WDT0_BASE              (IO_BASE + 0x0C002000)  /* 0xEC002000 */
 
 /*
  * GIC Interrupts
index 3711b888d482f13f2284d5c25c16a9d84d1ecbab..d1b15b637e2a260d9549fb1baaa83624f95c1bdf 100644 (file)
@@ -289,6 +289,13 @@ config ADX_WATCHDOG
          Say Y here if you want support for the watchdog timer on Avionic
          Design Xanthos boards.
 
+config XILINX_PSS_WATCHDOG
+       tristate "Xilinx PSS Watchdog Timer"
+       depends on ARCH_XILINX
+       help
+         Say Y here if you want to include support for the watchdog
+         timer in the Xilinx PSS.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
index 699199b1baa6baf24d31f8e0355631ab84b1060d..edb58ee7e7b2aa903c32ce8a7a30e0bf5e4fa8f3 100644 (file)
@@ -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_XILINX_PSS_WATCHDOG) += xilinx_wdtpss.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
diff --git a/drivers/watchdog/xilinx_wdtpss.c b/drivers/watchdog/xilinx_wdtpss.c
new file mode 100644 (file)
index 0000000..8e52aac
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ * Xilinx PSS WDT driver
+ *
+ * Copyright (c) 20010 Xilinx Inc.
+ *
+ * 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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA
+ * 02139, USA.
+ */
+
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+
+
+#define XWDTPSS_DEFAULT_TIMEOUT        10      /* Supports 1 - 600 sec */
+
+static int wdt_timeout = XWDTPSS_DEFAULT_TIMEOUT;
+static int nowayout = WATCHDOG_NOWAYOUT;
+
+module_param(wdt_timeout, int, 0);
+MODULE_PARM_DESC(wdt_timeout,
+                "Watchdog time in seconds. (default="
+                __MODULE_STRING(XWDTPSS_DEFAULT_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout,
+                "Watchdog cannot be stopped once started (default="
+                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+#endif
+
+/**
+ * struct xwdtpss - Watchdog device structure.
+ * @regs: baseaddress of device.
+ * @busy: flag for the device.
+ * @miscdev: miscdev structure.
+ *
+ * Structure containing the standard miscellaneous device 'miscdev'
+ * structure along with the parameters specific to pss watchdog.
+ */
+struct xwdtpss {
+       void __iomem            *regs;          /* Base address */
+       unsigned long           busy;           /* Device Status */
+       struct miscdevice       miscdev;        /* Device structure */
+       spinlock_t              io_lock;
+};
+static struct xwdtpss *wdt;
+
+/*
+ * Info structure used to indicate the features supported by the device
+ * to the upper layers. This is defined in watchdog.h header file.
+ */
+static struct watchdog_info xwdtpss_info = {
+       .identity       = "xwdtpss watchdog",
+       .options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+};
+
+/* Write access to Registers */
+#define xwdtpss_writereg(val, offset) \
+                               __raw_writel(val, (wdt->regs) + offset)
+
+/*************************Register Map**************************************/
+
+/* Register Offsets for the WDT */
+#define XWDTPSS_ZMR_OFFSET     0x0     /* Zero Mode Register */
+#define XWDTPSS_CCR_OFFSET     0x4     /* Counter Control Register */
+#define XWDTPSS_RESTART_OFFSET 0x8     /* Restart Register */
+#define XWDTPSS_SR_OFFSET      0xC     /* Status Register */
+
+/*
+ * Zero Mode Register - This register controls how the time out is indicated
+ * and also contains the access code to allow writes to the register (0xABC).
+ */
+#define XWDTPSS_ZMR_WDEN_MASK  0x00000001 /* Enable the WDT */
+#define XWDTPSS_ZMR_RSTEN_MASK 0x00000002 /* Enable the reset output */
+#define XWDTPSS_ZMR_RSTLEN_2   0x00000000 /* Reset pulse of 2 pclk cycles */
+#define XWDTPSS_ZMR_ZKEY_VAL   0x00ABC000 /* Access key, 0xABC << 12 */
+/*
+ * Counter Control register - This register controls how fast the timer runs
+ * and the reset value and also contains the access code to allow writes to
+ * the register.
+ */
+#define XWDTPSS_CCR_CRV_MASK   0x00003FFC /* Counter reset value */
+
+
+/**
+ * xwdtpss_stop -  Stop the watchdog.
+ *
+ * Read the contents of the ZMR register, clear the WDEN bit
+ * in the register and set the access key for successful write.
+ **/
+static void xwdtpss_stop(void)
+{
+       spin_lock(&wdt->io_lock);
+       xwdtpss_writereg((XWDTPSS_ZMR_ZKEY_VAL & (~XWDTPSS_ZMR_WDEN_MASK)),
+                        XWDTPSS_ZMR_OFFSET);
+       spin_unlock(&wdt->io_lock);
+}
+
+/**
+ * xwdtpss_reload -  Reload the watchdog timer (i.e. pat the watchdog).
+ *
+ * Write the restart key value (0x00001999) to the restart register.
+ **/
+static void xwdtpss_reload(void)
+{
+       spin_lock(&wdt->io_lock);
+       xwdtpss_writereg(0x00001999, XWDTPSS_RESTART_OFFSET);
+       spin_unlock(&wdt->io_lock);
+}
+
+/**
+ * xwdtpss_start -  Enable and start the watchdog.
+ *
+ * The clock to the WDT is 100 MHz, the prescalar is set to divide
+ * the clock by 4096 and the counter value is calculated according to
+ * the formula:
+ *             calculated count = (timeout * clock) / prescalar + 1.
+ * The calculated count is divided by 0x1000 to obtain the field value
+ * to write to counter control register.
+ * Clears the contents of prescalar and counter reset value. Sets the
+ * prescalar to 4096 and the calculated count and access key
+ * to write to CCR Register.
+ * Sets the WDT (WDEN bit) and Reset signal(RSTEN bit) with length as 2 pclk
+ * cycles and the access key to write to ZMR Register.
+ **/
+static void xwdtpss_start(void)
+{
+       unsigned int data = 0;
+       int count;
+
+       /*
+        * 64           - Prescalar divide value.
+        * 0x1000       - Counter Value Divide, to obtain the value of counter
+        *                reset to write to control register.
+        * 781250       - Input clock value.
+        * This code needs to be modified when the clock value increases
+        * in H/W.
+        */
+       count = (wdt_timeout * 781250) / (64 * 0x1000) + 1;
+
+       /* Check for boundary conditions of counter value */
+       if (count > 0xFFF)
+               count = 0xFFF;
+
+       spin_lock(&wdt->io_lock);
+       xwdtpss_writereg(XWDTPSS_ZMR_ZKEY_VAL, XWDTPSS_ZMR_OFFSET);
+
+       /* Shift the count value to correct bit positions */
+       count = (count << 2) & XWDTPSS_CCR_CRV_MASK;
+
+       /*
+        * 0x00000001 - Bit value to set 64 prescalar divide.
+        * 0x00920000 - Counter register key value.
+        */
+       data = (count | 0x00920000 | 0x00000001);
+       xwdtpss_writereg(data, XWDTPSS_CCR_OFFSET);
+
+       data = (XWDTPSS_ZMR_WDEN_MASK | XWDTPSS_ZMR_RSTEN_MASK | \
+               XWDTPSS_ZMR_RSTLEN_2 | XWDTPSS_ZMR_ZKEY_VAL);
+       xwdtpss_writereg(data, XWDTPSS_ZMR_OFFSET);
+       spin_unlock(&wdt->io_lock);
+       xwdtpss_writereg(0x00001999, XWDTPSS_RESTART_OFFSET);
+}
+
+/**
+ * xwdtpss_settimeout -  Set a new timeout value for the watchdog device.
+ *
+ * @new_time: new timeout value that needs to be set.
+ *
+ * Check whether the timeout is in the valid range. If not, don't update the
+ * timeout value, otherwise update the global variable wdt_timeout with new
+ * value which is used when xwdtpss_start is called.
+ * Returns -ENOTSUPP, if timeout value is out-of-range.
+ * Returns 0 on success.
+ **/
+static int xwdtpss_settimeout(int new_time)
+{
+       if ((new_time <= 0) || (new_time > 600))
+               return -ENOTSUPP;
+       wdt_timeout = new_time;
+       return 0;
+}
+
+/*************************WDT Device Operations****************************/
+
+/**
+ * xwdtpss_open -  Open the watchdog device.
+ *
+ * @inode: inode of device.
+ * @file: file handle to device.
+ *
+ * Check whether the device is already in use and then only start the watchdog
+ * timer. Returns 0 on success, otherwise -EBUSY.
+ **/
+static int xwdtpss_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(0, &(wdt->busy)))
+               return -EBUSY;
+       xwdtpss_start();
+       return nonseekable_open(inode, file);
+}
+
+/**
+ * xwdtpss_close -  Close the watchdog device only when nowayout is disabled.
+ *
+ * @inode: inode of device.
+ * @file: file handle to device.
+ *
+ * Stops the watchdog and clears the busy flag.
+ * Returns 0 on success, -ENOTSUPP when the nowayout is enabled.
+ **/
+static int xwdtpss_close(struct inode *inode, struct file *file)
+{
+       if (!nowayout) {
+               /* Disable the watchdog */
+               xwdtpss_stop();
+               clear_bit(0, &(wdt->busy));
+               return 0;
+       }
+       return -ENOTSUPP;
+}
+
+/**
+ * xwdtpss_ioctl -  Handle IOCTL operations on the device.
+ *
+ * @inode: inode of the device.
+ * @file: file handle to the device.
+ * @cmd: watchdog command.
+ * @arg: argument pointer.
+ *
+ * The watchdog API defines a common set of functions for all
+ * watchdogs according to available features. The IOCTL's are defined in
+ * watchdog.h header file, based on the features of device, we support
+ * the following IOCTL's - WDIOC_KEEPALIVE, WDIOC_GETSUPPORT,
+ * WDIOC_SETTIMEOUT, WDIOC_GETTIMEOUT, WDIOC_SETOPTIONS.
+ * Returns 0 on success, negative error otherwise.
+ **/
+static int xwdtpss_ioctl(struct inode *inode, struct file *file,
+                        unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       int __user *p = argp;
+       int new_value;
+
+       switch (cmd) {
+       case WDIOC_KEEPALIVE:
+               /* pat the watchdog */
+               xwdtpss_reload();
+               return 0;
+
+       case WDIOC_GETSUPPORT:
+               /*
+                * Indicate the features supported to the user through the
+                * instance of watchdog_info structure.
+                */
+               return copy_to_user(argp, &xwdtpss_info,
+                                   sizeof(xwdtpss_info)) ? -EFAULT : 0;
+
+       case WDIOC_SETTIMEOUT:
+               if (get_user(new_value, p))
+                       return -EFAULT;
+
+               /* Check for the validity */
+               if (xwdtpss_settimeout(new_value))
+                       return -EINVAL;
+               xwdtpss_start();
+               /* Return current value */
+               return put_user(wdt_timeout, p);
+
+       case WDIOC_GETTIMEOUT:
+               /* Return the current timeout */
+               return put_user(wdt_timeout, p);
+
+       case WDIOC_GETSTATUS:
+       case WDIOC_GETBOOTSTATUS:
+               return put_user(0, p);
+
+       case WDIOC_SETOPTIONS:
+               if (get_user(new_value, p))
+                       return -EFAULT;
+               /* Based on the flag, enable or disable the watchdog */
+               if (new_value & WDIOS_DISABLECARD)
+                       xwdtpss_stop();
+               if (new_value & WDIOS_ENABLECARD)
+                       xwdtpss_start();
+               return 0;
+
+       default:
+               return -ENOIOCTLCMD;
+       }
+}
+
+/**
+ * xwdtpss_write -  Pats the watchdog, i.e. reload the counter.
+ *
+ * @file: file handle to the device.
+ * @data: value is ignored.
+ * @len:  count of bytes to be processed.
+ * @ppos: value is ignored.
+ *
+ * A write to watchdog device is similar to keepalive signal.
+ * Returns the len value.
+ **/
+static ssize_t xwdtpss_write(struct file *file, const char __user *data,
+                            size_t len, loff_t *ppos)
+{
+       xwdtpss_reload();               /* pat the watchdog */
+       return len;
+}
+
+/**
+ * xwdtpss_notify_sys -  Notifier for reboot or shutdown.
+ *
+ * @this: handle to notifier block.
+ * @code: turn off indicator.
+ * @unused: unused.
+ *
+ * This notifier is invoked whenever the system reboot or shutdown occur
+ * because we need to disable the WDT before system goes down as WDT might
+ * reset on the next boot.
+ * Returns NOTIFY_DONE.
+ **/
+static int xwdtpss_notify_sys(struct notifier_block *this, unsigned long code,
+                             void *unused)
+{
+       if (code == SYS_DOWN || code == SYS_HALT) {
+               /* Stop the watchdog */
+               xwdtpss_stop();
+       }
+       return NOTIFY_DONE;
+}
+
+/* File operations structure */
+static const struct file_operations xwdtpss_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .ioctl          = xwdtpss_ioctl,
+       .open           = xwdtpss_open,
+       .release        = xwdtpss_close,
+       .write          = xwdtpss_write,
+};
+
+/* Notifier Structure */
+static struct notifier_block xwdtpss_notifier = {
+       .notifier_call = xwdtpss_notify_sys,
+};
+
+/************************Platform Operations*****************************/
+/**
+ * xwdtpss_probe -  Probe call for the device.
+ *
+ * @pdev: handle to the platform device structure.
+ *
+ * It does all the memory allocation and registration for the device.
+ * Returns 0 on success, negative error otherwise.
+ **/
+static int __init xwdtpss_probe(struct platform_device *pdev)
+{
+       struct resource *regs;
+       int res;
+
+       /* Check whether WDT is in use, just for safety */
+       if (wdt) {
+               dev_err(&pdev->dev, "Device Busy, only 1 xwdtpss instance \
+                       supported.\n");
+               return -EBUSY;
+       }
+
+       /* Get the device base address */
+       regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!regs) {
+               dev_err(&pdev->dev, "Unable to locate mmio resource\n");
+               return -ENODEV;
+       }
+
+       /* Allocate an instance of the xwdtpss structure */
+       wdt = kzalloc(sizeof(struct xwdtpss), GFP_KERNEL);
+       if (!wdt) {
+               dev_err(&pdev->dev, "No memory for wdt structure\n");
+               return -ENOMEM;
+       }
+
+       wdt->regs = ioremap(regs->start, regs->end - regs->start + 1);
+       if (!wdt->regs) {
+               res = -ENOMEM;
+               dev_err(&pdev->dev, "Could not map I/O memory\n");
+               goto err_free;
+       }
+
+       /* Register the reboot notifier */
+       res = register_reboot_notifier(&xwdtpss_notifier);
+       if (res != 0) {
+               dev_err(&pdev->dev, "cannot register reboot notifier err=%d)\n",
+                       res);
+               goto err_iounmap;
+       }
+
+       /* Initialize the members of xwdtpss structure */
+       wdt->miscdev.minor      = WATCHDOG_MINOR,
+       wdt->miscdev.name       = "watchdog",
+       wdt->miscdev.fops       = &xwdtpss_fops,
+
+       /* Initialize the busy flag to zero */
+       clear_bit(0, &wdt->busy);
+       spin_lock_init(&wdt->io_lock);
+
+       /* Register the WDT */
+       res = misc_register(&wdt->miscdev);
+       if (res) {
+               dev_err(&pdev->dev, "Failed to register wdt miscdev\n");
+               goto err_notifier;
+       }
+       platform_set_drvdata(pdev, wdt);
+       wdt->miscdev.parent = &pdev->dev;
+
+       dev_info(&pdev->dev, "Xilinx Watchdog Timer at 0x%p with timeout "
+                "%d seconds%s\n", wdt->regs, wdt_timeout,
+                nowayout ? ", nowayout" : "");
+
+       return 0;
+
+err_notifier:
+       unregister_reboot_notifier(&xwdtpss_notifier);
+err_iounmap:
+       iounmap(wdt->regs);
+err_free:
+       kfree(wdt);
+       wdt = NULL;
+       return res;
+}
+
+/**
+ * xwdtpss_remove -  Probe call for the device.
+ *
+ * @pdev: handle to the platform device structure.
+ *
+ * Unregister the device after releasing the resources.
+ * Stop is allowed only when nowayout is disabled.
+ * Returns 0 on success, otherwise negative error.
+ **/
+static int __exit xwdtpss_remove(struct platform_device *pdev)
+{
+       int res = 0;
+
+       if (wdt && !nowayout) {
+               xwdtpss_stop();
+               res = misc_deregister(&wdt->miscdev);
+               if (!res)
+                       wdt->miscdev.parent = NULL;
+               unregister_reboot_notifier(&xwdtpss_notifier);
+               iounmap(wdt->regs);
+               kfree(wdt);
+               wdt = NULL;
+               platform_set_drvdata(pdev, NULL);
+       } else {
+               dev_err(&pdev->dev, "Cannot stop watchdog, still ticking\n");
+               return -ENOTSUPP;
+       }
+       return res;
+}
+
+/**
+ * xwdtpss_shutdown -  Stop the device.
+ *
+ * @pdev: handle to the platform structure.
+ *
+ **/
+static void xwdtpss_shutdown(struct platform_device *pdev)
+{
+       /* Stop the device */
+       xwdtpss_stop();
+}
+
+#ifdef CONFIG_PM
+/**
+ * xwdtpss_suspend -  Stop the device.
+ *
+ * @pdev: handle to the platform structure.
+ * @message: message to the device.
+ *
+ * Returns 0 always.
+ **/
+static int xwdtpss_suspend(struct platform_device *pdev, pm_message_t message)
+{
+       /* Stop the device */
+       xwdtpss_stop();
+       return 0;
+}
+
+/**
+ * xwdtpss_resume -  Resume the device.
+ *
+ * @pdev: handle to the platform structure.
+ *
+ * Returns 0 always.
+ **/
+static int xwdtpss_resume(struct platform_device *pdev)
+{
+       /* Start the device */
+       xwdtpss_start();
+       return 0;
+}
+#else
+#define xwdtpss_suspend NULL
+#define xwdtpss_resume NULL
+#endif
+
+/* Driver Structure */
+static struct platform_driver xwdtpss_driver = {
+       .probe          = xwdtpss_probe,
+       .remove         = __exit_p(xwdtpss_remove),
+       .shutdown       = xwdtpss_shutdown,
+       .suspend        = xwdtpss_suspend,
+       .resume         = xwdtpss_resume,
+       .driver         = {
+               .name   = "xilinx_pss_wdt",
+               .owner  = THIS_MODULE,
+       },
+};
+
+/**
+ * xwdtpss_init -  Register the WDT.
+ *
+ * Returns 0 on success, otherwise negative error.
+ */
+static int __init xwdtpss_init(void)
+{
+       /*
+        * Check that the timeout value is within range. If not, reset to the
+        * default.
+        */
+       if (xwdtpss_settimeout(wdt_timeout)) {
+               xwdtpss_settimeout(XWDTPSS_DEFAULT_TIMEOUT);
+               pr_info("xwdtpss: wdt_timeout value limited to 1 - 600 sec, "
+                       "using default timeout of %dsec\n",
+                       XWDTPSS_DEFAULT_TIMEOUT);
+       }
+       return platform_driver_register(&xwdtpss_driver);
+}
+
+/**
+ * xwdtpss_exit -  Unregister the WDT.
+ */
+static void __exit xwdtpss_exit(void)
+{
+       platform_driver_unregister(&xwdtpss_driver);
+}
+
+module_init(xwdtpss_init);
+module_exit(xwdtpss_exit);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("Watchdog driver for PSS WDT");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS("platform: pss wdt");