[brcm63xx] fix the watchdog driver and enable it by default
authorFlorian Fainelli <florian@openwrt.org>
Tue, 9 Jun 2009 21:28:15 +0000 (21:28 +0000)
committerFlorian Fainelli <florian@openwrt.org>
Tue, 9 Jun 2009 21:28:15 +0000 (21:28 +0000)
SVN-Revision: 16394

target/linux/brcm63xx/config-2.6.27
target/linux/brcm63xx/files/drivers/watchdog/bcm63xx_wdt.c

index 2550802..83af14f 100644 (file)
@@ -19,6 +19,7 @@ CONFIG_BCM63XX_CPU_6348=y
 CONFIG_BCM63XX_CPU_6358=y
 CONFIG_BCM63XX_ENET=y
 CONFIG_BCM63XX_PHY=y
+CONFIG_BCM63XX_WDT=y
 CONFIG_BITREVERSE=y
 CONFIG_BLK_DEV_IO_TRACE=y
 CONFIG_BOARD_BCM963XX=y
@@ -59,7 +60,6 @@ CONFIG_CPU_SUPPORTS_HIGHMEM=y
 # CONFIG_CPU_VR41XX is not set
 CONFIG_CRAMFS=y
 CONFIG_CSRC_R4K=y
-CONFIG_DEFAULT_BIC=y
 CONFIG_DEFAULT_CFQ=y
 # CONFIG_DEFAULT_DEADLINE is not set
 CONFIG_DEFAULT_IOSCHED="cfq"
@@ -106,7 +106,6 @@ CONFIG_INOTIFY=y
 CONFIG_INOTIFY_USER=y
 CONFIG_IOSCHED_CFQ=y
 # CONFIG_IOSCHED_DEADLINE is not set
-CONFIG_IP_MROUTE=y
 CONFIG_IP_PIMSM_V1=y
 CONFIG_IP_PIMSM_V2=y
 CONFIG_IRQ_CPU=y
@@ -198,12 +197,11 @@ CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
 CONFIG_TICK_ONESHOT=y
 CONFIG_TRAD_SIGNALS=y
 CONFIG_USB_EHCI_BIG_ENDIAN_MMIO=y
-# CONFIG_USB_EHCI_ROOT_HUB_TT is not set
 CONFIG_USB_OHCI_BIG_ENDIAN_DESC=y
 CONFIG_USB_OHCI_BIG_ENDIAN_MMIO=y
 CONFIG_USB_SUPPORT=y
 # CONFIG_VGASTATE is not set
 # CONFIG_VIA_RHINE is not set
 CONFIG_VM_EVENT_COUNTERS=y
-# CONFIG_WATCHDOG is not set
+CONFIG_WATCHDOG_NOWAYOUT=y
 CONFIG_ZONE_DMA_FLAG=0
index b40c52a..cc41ec5 100644 (file)
  *  2 of the License, or (at your option) any later version.
  */
 
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
 #include <linux/fs.h>
-#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
 #include <linux/miscdevice.h>
-#include <linux/watchdog.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/reboot.h>
-#include <linux/smp_lock.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
+#include <linux/types.h>
 #include <linux/uaccess.h>
+#include <linux/watchdog.h>
 #include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/resource.h>
+#include <linux/platform_device.h>
 
 #include <bcm63xx_cpu.h>
 #include <bcm63xx_io.h>
 #define PFX KBUILD_MODNAME
 
 #define WDT_HZ         50000000 /* Fclk */
-#define WDT_INTERVAL   (40)    /* in seconds */
+#define WDT_DEFAULT_TIME       30      /* seconds */
+#define WDT_MAX_TIME           256     /* seconds */
 
 static struct {
        void __iomem *regs;
-       struct completion stop;
-       int running;
        struct timer_list timer;
-       int queue;
        int default_ticks;
        unsigned long inuse;
+       atomic_t ticks;
 } bcm63xx_wdt_device;
 
-static int ticks = 100 * WDT_HZ;
-
 static int expect_close;
 static int timeout;
 
+static int wdt_time = WDT_DEFAULT_TIME;
 static int nowayout = WATCHDOG_NOWAYOUT;
 module_param(nowayout, int, 0);
 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 
-
-static void bcm63xx_wdt_toggle(void)
+/* HW functions */
+static void bcm63xx_wdt_hw_start(void)
 {
+       bcm_writel(0xffffffff, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
        bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
        bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
 }
 
-static void bcm63xx_wdt_start(void)
+static void bcm63xx_wdt_hw_stop(void)
 {
-       if (!bcm63xx_wdt_device.inuse) {
-               bcm63xx_wdt_toggle();
-               mod_timer(&bcm63xx_wdt_device.timer, jiffies + WDT_INTERVAL);
-       }
-
-       bcm63xx_wdt_device.running++;
+       bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
+       bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
 }
 
-static void bcm63xx_wdt_stop(void)
+static void bcm63xx_timer_tick(unsigned long unused)
 {
-       if (bcm63xx_wdt_device.running) {
-               bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
-               bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
+       if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) {
+               bcm63xx_wdt_hw_start();
+               mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ);
+       } else
+               printk(KERN_CRIT PFX "watchdog will restart system\n");
+}
 
-               bcm63xx_wdt_device.running = 0;
-       }
+static void bcm63xx_wdt_pet(void)
+{
+       atomic_set(&bcm63xx_wdt_device.ticks, wdt_time);
 }
 
-static void bcm63xx_wdt_set(int new_timeout)
+static void bcm63xx_wdt_start(void)
 {
-       new_timeout *= WDT_HZ;
-       bcm_writel(new_timeout, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
+       bcm63xx_wdt_pet();
+       bcm63xx_timer_tick(0);
 }
 
-static void bcm63xx_wdt_reset(void)
+static void bcm63xx_wdt_pause(void)
 {
-       ticks = bcm63xx_wdt_device.default_ticks;
+       del_timer_sync(&bcm63xx_wdt_device.timer);
+       bcm63xx_wdt_hw_stop();
 }
 
-static void bcm63xx_wdt_update(unsigned long unused)
+static int bcm63xx_wdt_settimeout(int new_time)
 {
-       if (bcm63xx_wdt_device.running)
-               ticks--;
+       if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
+               return -EINVAL;
 
-       bcm63xx_wdt_toggle();
+       wdt_time = new_time;
 
-       if (bcm63xx_wdt_device.queue && ticks)
-               mod_timer(&bcm63xx_wdt_device.timer,
-                       jiffies + WDT_INTERVAL);
-       else
-               complete(&bcm63xx_wdt_device.stop);
+       return 0;
 }
 
 static int bcm63xx_wdt_open(struct inode *inode, struct file *file)
 {
        if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse))
                return -EBUSY;
-
-       if (nowayout)
-               __module_get(THIS_MODULE);
-
+       
+       bcm63xx_wdt_start();
        return nonseekable_open(inode, file);
 }
 
 static int bcm63xx_wdt_release(struct inode *inode, struct file *file)
 {
-       if (expect_close && nowayout == 0) {
-               bcm63xx_wdt_stop();
-               printk(KERN_INFO PFX ": disabling watchdog timer\n");
-               module_put(THIS_MODULE);
-       } else
+       if (expect_close == 42)
+               bcm63xx_wdt_pause();
+       else {
                printk(KERN_CRIT PFX
-                       ": device closed unexpectedly. WDT will not stop !\n");
-
+                       ": Unexpected close, not stopping watchdog!\n");
+               bcm63xx_wdt_start();
+       }
        clear_bit(0, &bcm63xx_wdt_device.inuse);
+       expect_close = 0;
        return 0;
 }
 
@@ -145,70 +142,81 @@ static ssize_t bcm63xx_wdt_write(struct file *file, const char *data,
                                if (get_user(c, data + i))
                                        return -EFAULT;
                                if (c == 'V')
-                                       expect_close = 1;
+                                       expect_close = 42;
                        }
                }
-               bcm63xx_wdt_update(0);
-               return len;
+               bcm63xx_wdt_pet();
        }
-       return 0;
+       return len;
 }
 
+static struct watchdog_info bcm63xx_wdt_info = {
+       .identity       = PFX,
+       .options        = WDIOF_SETTIMEOUT |
+                               WDIOF_KEEPALIVEPING |
+                               WDIOF_MAGICCLOSE,
+};
+
+
 static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd,
                                unsigned long arg)
 {
        void __user *argp = (void __user *)arg;
-       int new_timeout;
-       unsigned int value;
-       static struct watchdog_info ident = {
-               .options =              WDIOF_SETTIMEOUT |
-                                       WDIOF_KEEPALIVEPING |
-                                       WDIOF_MAGICCLOSE,
-               .identity =             "BCM63xx Watchdog",
-       };
+       int __user *p = argp;
+       int new_value, retval = -EINVAL;
+
        switch (cmd) {
-       case WDIOC_KEEPALIVE:
-               bcm63xx_wdt_reset();
-               break;
+       case WDIOC_GETSUPPORT:
+               return copy_to_user(argp, &bcm63xx_wdt_info,
+                       sizeof(bcm63xx_wdt_info)) ? -EFAULT : 0;
+
        case WDIOC_GETSTATUS:
        case WDIOC_GETBOOTSTATUS:
-               value = bcm_readl(bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
-               if (copy_to_user(argp, &value, sizeof(int)))
-                       return -EFAULT;
-               break;
-       case WDIOC_GETSUPPORT:
-               if (copy_to_user(argp, &ident, sizeof(ident)))
-                       return -EFAULT;
-               break;
+               return put_user(0, p);
+
        case WDIOC_SETOPTIONS:
-               if (copy_from_user(&value, argp, sizeof(int)))
+               if (get_user(new_value, p))
                        return -EFAULT;
-               switch (value) {
-               case WDIOS_ENABLECARD:
+
+               if (new_value & WDIOS_DISABLECARD) {
+                       bcm63xx_wdt_pause();
+                       retval = 0;
+               }
+               if (new_value & WDIOS_ENABLECARD) {
                        bcm63xx_wdt_start();
-                       break;
-               case WDIOS_DISABLECARD:
-                       bcm63xx_wdt_stop();
-               default:
-                       return -EINVAL;
+                       retval = 0;
                }
-               break;
+
+               return retval;
+       
+       case WDIOC_KEEPALIVE:
+                bcm63xx_wdt_pet();
+               return 0;
+               
        case WDIOC_SETTIMEOUT:
-               if (copy_from_user(&new_timeout, argp, sizeof(int)))
+               if (get_user(new_value, p))
                        return -EFAULT;
-               if (new_timeout < 5)
-                       return -EINVAL;
-               if (new_timeout > 40)
+
+               if (bcm63xx_wdt_settimeout(new_value))
                        return -EINVAL;
-               bcm63xx_wdt_set(new_timeout);
-               bcm63xx_wdt_toggle();
+
+               bcm63xx_wdt_pet();
+
        case WDIOC_GETTIMEOUT:
-               return copy_to_user(argp, &timeout, sizeof(int));
+               return put_user(wdt_time, p);
+
        default:
                return -ENOTTY;
+
        }
+}
 
-       return 0;
+static int bcm63xx_wdt_notify_sys(struct notifier_block *this,
+           unsigned long code, void *unused)
+{
+       if (code == SYS_DOWN || code == SYS_HALT)
+               bcm63xx_wdt_pause();
+       return NOTIFY_DONE;
 }
 
 static struct file_operations bcm63xx_wdt_fops = {
@@ -226,10 +234,17 @@ static struct miscdevice bcm63xx_wdt_miscdev = {
        .fops   = &bcm63xx_wdt_fops,
 };
 
+static struct notifier_block bcm63xx_wdt_notifier = {
+       .notifier_call = bcm63xx_wdt_notify_sys,
+};
+
+
 static int bcm63xx_wdt_probe(struct platform_device *pdev)
 {
        int ret;
        struct resource *r;
+       
+       setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L);
 
        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!r) {
@@ -245,6 +260,20 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
                return -ENXIO;
        }
 
+       if (bcm63xx_wdt_settimeout(wdt_time)) {
+               bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME);
+               printk(KERN_INFO PFX
+                       ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n",
+                       wdt_time);
+       }
+
+       ret = register_reboot_notifier(&bcm63xx_wdt_notifier);
+       if (ret) {
+               printk(KERN_ERR PFX
+                       "failed to register reboot_notifier\n");
+                return ret;
+       }
+
        ret = misc_register(&bcm63xx_wdt_miscdev);
        if (ret < 0) {
                printk(KERN_ERR PFX
@@ -252,37 +281,27 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
                goto unmap;
        }
 
-       init_completion(&bcm63xx_wdt_device.stop);
-       bcm63xx_wdt_device.queue = 0;
-
-       clear_bit(0, &bcm63xx_wdt_device.inuse);
-
-       setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_wdt_update, 0L);
-
-       bcm63xx_wdt_device.default_ticks = ticks;
-       bcm63xx_wdt_set(ticks);
-       bcm63xx_wdt_start();
-       
-       printk(KERN_INFO PFX " started, timer margin: %d sec\n", WDT_INTERVAL);
+       printk(KERN_INFO PFX " started, timer margin: %d sec\n", WDT_DEFAULT_TIME);
 
        return 0;
 
 unmap:
+       unregister_reboot_notifier(&bcm63xx_wdt_notifier);
        iounmap(bcm63xx_wdt_device.regs);
        return ret;
 }
 
 static int bcm63xx_wdt_remove(struct platform_device *pdev)
 {
-       if (bcm63xx_wdt_device.queue) {
-               bcm63xx_wdt_device.queue = 0;
-               wait_for_completion(&bcm63xx_wdt_device.stop);
-       }
+       if (!nowayout)
+               bcm63xx_wdt_pause();
 
        misc_deregister(&bcm63xx_wdt_miscdev);
 
        iounmap(bcm63xx_wdt_device.regs);
 
+       unregister_reboot_notifier(&bcm63xx_wdt_notifier);
+
        return 0;
 }