diff options
Diffstat (limited to 'drivers/watchdog/shwdt.c')
| -rw-r--r-- | drivers/watchdog/shwdt.c | 483 |
1 files changed, 163 insertions, 320 deletions
diff --git a/drivers/watchdog/shwdt.c b/drivers/watchdog/shwdt.c index 6fc74065abe..061756e36cf 100644 --- a/drivers/watchdog/shwdt.c +++ b/drivers/watchdog/shwdt.c @@ -1,9 +1,9 @@ /* - * drivers/char/watchdog/shwdt.c + * drivers/watchdog/shwdt.c * * Watchdog driver for integrated watchdog in the SuperH processors. * - * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org> + * Copyright (C) 2001 - 2012 Paul Mundt <lethal@linux-sh.org> * * 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 @@ -17,22 +17,26 @@ * Added expect close support, made emulated timeout runtime changeable * general cleanups, add some ioctls */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/platform_device.h> #include <linux/init.h> #include <linux/types.h> -#include <linux/miscdevice.h> +#include <linux/spinlock.h> #include <linux/watchdog.h> -#include <linux/reboot.h> -#include <linux/notifier.h> -#include <linux/ioport.h> +#include <linux/pm_runtime.h> #include <linux/fs.h> #include <linux/mm.h> +#include <linux/slab.h> #include <linux/io.h> -#include <linux/uaccess.h> +#include <linux/clk.h> +#include <linux/err.h> #include <asm/watchdog.h> -#define PFX "shwdt: " +#define DRV_NAME "sh-wdt" /* * Default clock division ratio is 5.25 msecs. For an additional table of @@ -48,7 +52,7 @@ * necssary. * * As a result of this timing problem, the only modes that are particularly - * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms + * feasible are the 4096 and the 2048 divisors, which yield 5.25 and 2.62ms * overflow periods respectively. * * Also, since we can't really expect userspace to be responsive enough @@ -62,37 +66,35 @@ * misses its deadline, the kernel timer will allow the WDT to overflow. */ static int clock_division_ratio = WTCSR_CKS_4096; - -#define next_ping_period(cks) msecs_to_jiffies(cks - 4) - -static void sh_wdt_ping(unsigned long data); - -static unsigned long shwdt_is_open; -static const struct watchdog_info sh_wdt_info; -static char shwdt_expect_close; -static DEFINE_TIMER(timer, sh_wdt_ping, 0, 0); -static unsigned long next_heartbeat; -static DEFINE_SPINLOCK(shwdt_lock); +#define next_ping_period(cks) (jiffies + msecs_to_jiffies(cks - 4)) #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned long next_heartbeat; -static int nowayout = WATCHDOG_NOWAYOUT; +struct sh_wdt { + void __iomem *base; + struct device *dev; + struct clk *clk; + spinlock_t lock; -/** - * sh_wdt_start - Start the Watchdog - * - * Starts the watchdog. - */ -static void sh_wdt_start(void) + struct timer_list timer; +}; + +static int sh_wdt_start(struct watchdog_device *wdt_dev) { - __u8 csr; + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; + u8 csr; + + pm_runtime_get_sync(wdt->dev); + clk_enable(wdt->clk); - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); next_heartbeat = jiffies + (heartbeat * HZ); - mod_timer(&timer, next_ping_period(clock_division_ratio)); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); csr = sh_wdt_read_csr(); csr |= WTCSR_WT | clock_division_ratio; @@ -114,84 +116,73 @@ static void sh_wdt_start(void) sh_wdt_write_csr(csr); #ifdef CONFIG_CPU_SH2 - /* - * Whoever came up with the RSTCSR semantics must've been smoking - * some of the good stuff, since in addition to the WTCSR/WTCNT write - * brain-damage, it's managed to fuck things up one step further.. - * - * If we need to clear the WOVF bit, the upper byte has to be 0xa5.. - * but if we want to touch RSTE or RSTS, the upper byte has to be - * 0x5a.. - */ csr = sh_wdt_read_rstcsr(); csr &= ~RSTCSR_RSTS; sh_wdt_write_rstcsr(csr); #endif - spin_unlock_irqrestore(&shwdt_lock, flags); + spin_unlock_irqrestore(&wdt->lock, flags); + + return 0; } -/** - * sh_wdt_stop - Stop the Watchdog - * Stops the watchdog. - */ -static void sh_wdt_stop(void) +static int sh_wdt_stop(struct watchdog_device *wdt_dev) { - __u8 csr; + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; + u8 csr; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); - del_timer(&timer); + del_timer(&wdt->timer); csr = sh_wdt_read_csr(); csr &= ~WTCSR_TME; sh_wdt_write_csr(csr); - spin_unlock_irqrestore(&shwdt_lock, flags); + + spin_unlock_irqrestore(&wdt->lock, flags); + + clk_disable(wdt->clk); + pm_runtime_put_sync(wdt->dev); + + return 0; } -/** - * sh_wdt_keepalive - Keep the Userspace Watchdog Alive - * The Userspace watchdog got a KeepAlive: schedule the next heartbeat. - */ -static inline void sh_wdt_keepalive(void) +static int sh_wdt_keepalive(struct watchdog_device *wdt_dev) { + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); next_heartbeat = jiffies + (heartbeat * HZ); - spin_unlock_irqrestore(&shwdt_lock, flags); + spin_unlock_irqrestore(&wdt->lock, flags); + + return 0; } -/** - * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat - * Set the Userspace Watchdog heartbeat - */ -static int sh_wdt_set_heartbeat(int t) +static int sh_wdt_set_heartbeat(struct watchdog_device *wdt_dev, unsigned t) { + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; if (unlikely(t < 1 || t > 3600)) /* arbitrary upper limit */ return -EINVAL; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); heartbeat = t; - spin_unlock_irqrestore(&shwdt_lock, flags); + wdt_dev->timeout = t; + spin_unlock_irqrestore(&wdt->lock, flags); + return 0; } -/** - * sh_wdt_ping - Ping the Watchdog - * @data: Unused - * - * Clears overflow bit, resets timer counter. - */ static void sh_wdt_ping(unsigned long data) { + struct sh_wdt *wdt = (struct sh_wdt *)data; unsigned long flags; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); if (time_before(jiffies, next_heartbeat)) { - __u8 csr; + u8 csr; csr = sh_wdt_read_csr(); csr &= ~WTCSR_IOVF; @@ -199,299 +190,154 @@ static void sh_wdt_ping(unsigned long data) sh_wdt_write_cnt(0); - mod_timer(&timer, next_ping_period(clock_division_ratio)); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); } else - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping " - "the watchdog\n"); - spin_unlock_irqrestore(&shwdt_lock, flags); + dev_warn(wdt->dev, "Heartbeat lost! Will not ping " + "the watchdog\n"); + spin_unlock_irqrestore(&wdt->lock, flags); } -/** - * sh_wdt_open - Open the Device - * @inode: inode of device - * @file: file handle of device - * - * Watchdog device is opened and started. - */ -static int sh_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &shwdt_is_open)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); +static const struct watchdog_info sh_wdt_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SH WDT", +}; - sh_wdt_start(); +static const struct watchdog_ops sh_wdt_ops = { + .owner = THIS_MODULE, + .start = sh_wdt_start, + .stop = sh_wdt_stop, + .ping = sh_wdt_keepalive, + .set_timeout = sh_wdt_set_heartbeat, +}; - return nonseekable_open(inode, file); -} +static struct watchdog_device sh_wdt_dev = { + .info = &sh_wdt_info, + .ops = &sh_wdt_ops, +}; -/** - * sh_wdt_close - Close the Device - * @inode: inode of device - * @file: file handle of device - * - * Watchdog device is closed and stopped. - */ -static int sh_wdt_close(struct inode *inode, struct file *file) +static int sh_wdt_probe(struct platform_device *pdev) { - if (shwdt_expect_close == 42) { - sh_wdt_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not " - "stopping watchdog!\n"); - sh_wdt_keepalive(); - } + struct sh_wdt *wdt; + struct resource *res; + int rc; - clear_bit(0, &shwdt_is_open); - shwdt_expect_close = 0; + /* + * As this driver only covers the global watchdog case, reject + * any attempts to register per-CPU watchdogs. + */ + if (pdev->id != -1) + return -EINVAL; - return 0; -} + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) + return -EINVAL; -/** - * sh_wdt_write - Write to Device - * @file: file handle of device - * @buf: buffer to write - * @count: length of buffer - * @ppos: offset - * - * Pings the watchdog on write. - */ -static ssize_t sh_wdt_write(struct file *file, const char *buf, - size_t count, loff_t *ppos) -{ - if (count) { - if (!nowayout) { - size_t i; - - shwdt_expect_close = 0; - - for (i = 0; i != count; i++) { - char c; - if (get_user(c, buf + i)) - return -EFAULT; - if (c == 'V') - shwdt_expect_close = 42; - } - } - sh_wdt_keepalive(); + wdt = devm_kzalloc(&pdev->dev, sizeof(struct sh_wdt), GFP_KERNEL); + if (unlikely(!wdt)) + return -ENOMEM; + + wdt->dev = &pdev->dev; + + wdt->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt->clk)) { + /* + * Clock framework support is optional, continue on + * anyways if we don't find a matching clock. + */ + wdt->clk = NULL; } - return count; -} + wdt->base = devm_ioremap_resource(wdt->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); -/** - * sh_wdt_mmap - map WDT/CPG registers into userspace - * @file: file structure for the device - * @vma: VMA to map the registers into - * - * A simple mmap() implementation for the corner cases where the counter - * needs to be mapped in userspace directly. Due to the relatively small - * size of the area, neighbouring registers not necessarily tied to the - * CPG will also be accessible through the register page, so this remains - * configurable for users that really know what they're doing. - * - * Additionaly, the register page maps in the CPG register base relative - * to the nearest page-aligned boundary, which requires that userspace do - * the appropriate CPU subtype math for calculating the page offset for - * the counter value. - */ -static int sh_wdt_mmap(struct file *file, struct vm_area_struct *vma) -{ - int ret = -ENOSYS; + watchdog_set_nowayout(&sh_wdt_dev, nowayout); + watchdog_set_drvdata(&sh_wdt_dev, wdt); -#ifdef CONFIG_SH_WDT_MMAP - unsigned long addr; + spin_lock_init(&wdt->lock); - /* Only support the simple cases where we map in a register page. */ - if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff) - return -EINVAL; + rc = sh_wdt_set_heartbeat(&sh_wdt_dev, heartbeat); + if (unlikely(rc)) { + /* Default timeout if invalid */ + sh_wdt_set_heartbeat(&sh_wdt_dev, WATCHDOG_HEARTBEAT); - /* - * Pick WTCNT as the start, it's usually the first register after the - * FRQCR, and neither one are generally page-aligned out of the box. - */ - addr = WTCNT & ~(PAGE_SIZE - 1); + dev_warn(&pdev->dev, + "heartbeat value must be 1<=x<=3600, using %d\n", + sh_wdt_dev.timeout); + } - vma->vm_flags |= VM_IO; - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + dev_info(&pdev->dev, "configured with heartbeat=%d sec (nowayout=%d)\n", + sh_wdt_dev.timeout, nowayout); - if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT, - PAGE_SIZE, vma->vm_page_prot)) { - printk(KERN_ERR PFX "%s: io_remap_pfn_range failed\n", - __func__); - return -EAGAIN; + rc = watchdog_register_device(&sh_wdt_dev); + if (unlikely(rc)) { + dev_err(&pdev->dev, "Can't register watchdog (err=%d)\n", rc); + return rc; } - ret = 0; -#endif + init_timer(&wdt->timer); + wdt->timer.function = sh_wdt_ping; + wdt->timer.data = (unsigned long)wdt; + wdt->timer.expires = next_ping_period(clock_division_ratio); - return ret; -} + dev_info(&pdev->dev, "initialized.\n"); + + pm_runtime_enable(&pdev->dev); -/** - * sh_wdt_ioctl - Query Device - * @file: file handle of device - * @cmd: watchdog command - * @arg: argument - * - * Query basic information from the device or ping it, as outlined by the - * watchdog API. - */ -static long sh_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - int new_heartbeat; - int options, retval = -EINVAL; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user((struct watchdog_info *)arg, - &sh_wdt_info, sizeof(sh_wdt_info)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, (int *)arg); - case WDIOC_SETOPTIONS: - if (get_user(options, (int *)arg)) - return -EFAULT; - - if (options & WDIOS_DISABLECARD) { - sh_wdt_stop(); - retval = 0; - } - - if (options & WDIOS_ENABLECARD) { - sh_wdt_start(); - retval = 0; - } - - return retval; - case WDIOC_KEEPALIVE: - sh_wdt_keepalive(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_heartbeat, (int *)arg)) - return -EFAULT; - - if (sh_wdt_set_heartbeat(new_heartbeat)) - return -EINVAL; - - sh_wdt_keepalive(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, (int *)arg); - default: - return -ENOTTY; - } return 0; } -/** - * sh_wdt_notify_sys - Notifier Handler - * @this: notifier block - * @code: notifier event - * @unused: unused - * - * Handles specific events, such as turning off the watchdog during a - * shutdown event. - */ -static int sh_wdt_notify_sys(struct notifier_block *this, - unsigned long code, void *unused) +static int sh_wdt_remove(struct platform_device *pdev) { - if (code == SYS_DOWN || code == SYS_HALT) - sh_wdt_stop(); + watchdog_unregister_device(&sh_wdt_dev); - return NOTIFY_DONE; -} + pm_runtime_disable(&pdev->dev); -static const struct file_operations sh_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sh_wdt_write, - .unlocked_ioctl = sh_wdt_ioctl, - .open = sh_wdt_open, - .release = sh_wdt_close, - .mmap = sh_wdt_mmap, -}; + return 0; +} -static const struct watchdog_info sh_wdt_info = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | - WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "SH WDT", -}; +static void sh_wdt_shutdown(struct platform_device *pdev) +{ + sh_wdt_stop(&sh_wdt_dev); +} -static struct notifier_block sh_wdt_notifier = { - .notifier_call = sh_wdt_notify_sys, -}; +static struct platform_driver sh_wdt_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, -static struct miscdevice sh_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sh_wdt_fops, + .probe = sh_wdt_probe, + .remove = sh_wdt_remove, + .shutdown = sh_wdt_shutdown, }; -/** - * sh_wdt_init - Initialize module - * Registers the device and notifier handler. Actual device - * initialization is handled by sh_wdt_open(). - */ static int __init sh_wdt_init(void) { - int rc; - - if (clock_division_ratio < 0x5 || clock_division_ratio > 0x7) { + if (unlikely(clock_division_ratio < 0x5 || + clock_division_ratio > 0x7)) { clock_division_ratio = WTCSR_CKS_4096; - printk(KERN_INFO PFX - "clock_division_ratio value must be 0x5<=x<=0x7, using %d\n", - clock_division_ratio); - } - - rc = sh_wdt_set_heartbeat(heartbeat); - if (unlikely(rc)) { - heartbeat = WATCHDOG_HEARTBEAT; - printk(KERN_INFO PFX - "heartbeat value must be 1<=x<=3600, using %d\n", - heartbeat); - } - rc = register_reboot_notifier(&sh_wdt_notifier); - if (unlikely(rc)) { - printk(KERN_ERR PFX - "Can't register reboot notifier (err=%d)\n", rc); - return rc; + pr_info("divisor must be 0x5<=x<=0x7, using %d\n", + clock_division_ratio); } - rc = misc_register(&sh_wdt_miscdev); - if (unlikely(rc)) { - printk(KERN_ERR PFX - "Can't register miscdev on minor=%d (err=%d)\n", - sh_wdt_miscdev.minor, rc); - unregister_reboot_notifier(&sh_wdt_notifier); - return rc; - } - - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", - heartbeat, nowayout); - - return 0; + return platform_driver_register(&sh_wdt_driver); } -/** - * sh_wdt_exit - Deinitialize module - * Unregisters the device and notifier handler. Actual device - * deinitialization is handled by sh_wdt_close(). - */ static void __exit sh_wdt_exit(void) { - misc_deregister(&sh_wdt_miscdev); - unregister_reboot_notifier(&sh_wdt_notifier); + platform_driver_unregister(&sh_wdt_driver); } +module_init(sh_wdt_init); +module_exit(sh_wdt_exit); MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); MODULE_DESCRIPTION("SuperH watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:" DRV_NAME); module_param(clock_division_ratio, int, 0); MODULE_PARM_DESC(clock_division_ratio, @@ -503,10 +349,7 @@ MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1 <= heartbeat <= 3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); -module_param(nowayout, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -module_init(sh_wdt_init); -module_exit(sh_wdt_exit); |
