diff options
Diffstat (limited to 'drivers/watchdog/sp805_wdt.c')
| -rw-r--r-- | drivers/watchdog/sp805_wdt.c | 336 |
1 files changed, 126 insertions, 210 deletions
diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index eef1524ae52..c1b03f4235b 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -4,7 +4,7 @@ * Watchdog driver for ARM SP805 watchdog module * * Copyright (C) 2010 ST Microelectronics - * Viresh Kumar<viresh.kumar@st.com> + * Viresh Kumar <viresh.linux@gmail.com> * * This file is licensed under the terms of the GNU General Public * License version 2 or later. This program is licensed "as is" without any @@ -16,19 +16,16 @@ #include <linux/amba/bus.h> #include <linux/bitops.h> #include <linux/clk.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/pm.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/types.h> -#include <linux/uaccess.h> #include <linux/watchdog.h> /* default timeout in seconds */ @@ -55,34 +52,32 @@ /** * struct sp805_wdt: sp805 wdt device structure - * - * lock: spin lock protecting dev structure and io access - * base: base address of wdt - * clk: clock structure of wdt - * dev: amba device structure of wdt - * status: current status of wdt - * load_val: load value to be set for current timeout - * timeout: current programmed timeout + * @wdd: instance of struct watchdog_device + * @lock: spin lock protecting dev structure and io access + * @base: base address of wdt + * @clk: clock structure of wdt + * @adev: amba device structure of wdt + * @status: current status of wdt + * @load_val: load value to be set for current timeout */ struct sp805_wdt { + struct watchdog_device wdd; spinlock_t lock; void __iomem *base; struct clk *clk; struct amba_device *adev; - unsigned long status; - #define WDT_BUSY 0 - #define WDT_CAN_BE_CLOSED 1 unsigned int load_val; - unsigned int timeout; }; -/* local variables */ -static struct sp805_wdt *wdt; -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); /* This routine finds load value that will reset system in required timout */ -static void wdt_setload(unsigned int timeout) +static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout) { + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); u64 load, rate; rate = clk_get_rate(wdt->clk); @@ -101,256 +96,191 @@ static void wdt_setload(unsigned int timeout) spin_lock(&wdt->lock); wdt->load_val = load; /* roundup timeout to closest positive integer value */ - wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); + wdd->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); spin_unlock(&wdt->lock); + + return 0; } /* returns number of seconds left for reset to occur */ -static u32 wdt_timeleft(void) +static unsigned int wdt_timeleft(struct watchdog_device *wdd) { + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); u64 load, rate; rate = clk_get_rate(wdt->clk); spin_lock(&wdt->lock); - load = readl(wdt->base + WDTVALUE); + load = readl_relaxed(wdt->base + WDTVALUE); /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */ - if (!(readl(wdt->base + WDTRIS) & INT_MASK)) + if (!(readl_relaxed(wdt->base + WDTRIS) & INT_MASK)) load += wdt->load_val + 1; spin_unlock(&wdt->lock); return div_u64(load, rate); } -/* enables watchdog timers reset */ -static void wdt_enable(void) +static int wdt_config(struct watchdog_device *wdd, bool ping) { - spin_lock(&wdt->lock); + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); + int ret; - writel(UNLOCK, wdt->base + WDTLOCK); - writel(wdt->load_val, wdt->base + WDTLOAD); - writel(INT_MASK, wdt->base + WDTINTCLR); - writel(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); - writel(LOCK, wdt->base + WDTLOCK); + if (!ping) { - /* Flush posted writes. */ - readl(wdt->base + WDTLOCK); - spin_unlock(&wdt->lock); -} + ret = clk_prepare_enable(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock enable fail"); + return ret; + } + } -/* disables watchdog timers reset */ -static void wdt_disable(void) -{ spin_lock(&wdt->lock); - writel(UNLOCK, wdt->base + WDTLOCK); - writel(0, wdt->base + WDTCONTROL); - writel(LOCK, wdt->base + WDTLOCK); + writel_relaxed(UNLOCK, wdt->base + WDTLOCK); + writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); + + if (!ping) { + writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); + writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + + WDTCONTROL); + } + + writel_relaxed(LOCK, wdt->base + WDTLOCK); /* Flush posted writes. */ - readl(wdt->base + WDTLOCK); + readl_relaxed(wdt->base + WDTLOCK); spin_unlock(&wdt->lock); + + return 0; } -static ssize_t sp805_wdt_write(struct file *file, const char *data, - size_t len, loff_t *ppos) +static int wdt_ping(struct watchdog_device *wdd) { - 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; - /* Check for Magic Close character */ - if (c == 'V') { - set_bit(WDT_CAN_BE_CLOSED, - &wdt->status); - break; - } - } - } - wdt_enable(); - } - return len; + return wdt_config(wdd, true); } -static const struct watchdog_info ident = { - .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .identity = MODULE_NAME, -}; - -static long sp805_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +/* enables watchdog timers reset */ +static int wdt_enable(struct watchdog_device *wdd) { - int ret = -ENOTTY; - unsigned 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_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(timeout, (unsigned int *)arg); - if (ret) - break; - - wdt_setload(timeout); - - wdt_enable(); - /* Fall through */ - - case WDIOC_GETTIMEOUT: - ret = put_user(wdt->timeout, (unsigned int *)arg); - break; - case WDIOC_GETTIMELEFT: - ret = put_user(wdt_timeleft(), (unsigned int *)arg); - break; - } - return ret; + return wdt_config(wdd, false); } -static int sp805_wdt_open(struct inode *inode, struct file *file) +/* disables watchdog timers reset */ +static int wdt_disable(struct watchdog_device *wdd) { - int ret = 0; + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); - if (test_and_set_bit(WDT_BUSY, &wdt->status)) - return -EBUSY; - - ret = clk_enable(wdt->clk); - if (ret) { - dev_err(&wdt->adev->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); + spin_lock(&wdt->lock); -err: - clear_bit(WDT_BUSY, &wdt->status); - return ret; -} + writel_relaxed(UNLOCK, wdt->base + WDTLOCK); + writel_relaxed(0, wdt->base + WDTCONTROL); + writel_relaxed(LOCK, wdt->base + WDTLOCK); -static int sp805_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->adev->dev, "Device closed unexpectedly\n"); - return 0; - } + /* Flush posted writes. */ + readl_relaxed(wdt->base + WDTLOCK); + spin_unlock(&wdt->lock); - wdt_disable(); - clk_disable(wdt->clk); - clear_bit(WDT_BUSY, &wdt->status); + clk_disable_unprepare(wdt->clk); return 0; } -static const struct file_operations sp805_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sp805_wdt_write, - .unlocked_ioctl = sp805_wdt_ioctl, - .open = sp805_wdt_open, - .release = sp805_wdt_release, +static const struct watchdog_info wdt_info = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = MODULE_NAME, }; -static struct miscdevice sp805_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sp805_wdt_fops, +static const struct watchdog_ops wdt_ops = { + .owner = THIS_MODULE, + .start = wdt_enable, + .stop = wdt_disable, + .ping = wdt_ping, + .set_timeout = wdt_setload, + .get_timeleft = wdt_timeleft, }; -static int __devinit +static int sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) { + struct sp805_wdt *wdt; int ret = 0; - if (!request_mem_region(adev->res.start, resource_size(&adev->res), - "sp805_wdt")) { - dev_warn(&adev->dev, "Failed to get memory region resource\n"); - ret = -ENOENT; - goto err; - } - - wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); + wdt = devm_kzalloc(&adev->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) { - dev_warn(&adev->dev, "Kzalloc failed\n"); ret = -ENOMEM; - goto err_kzalloc; + goto err; } - wdt->clk = clk_get(&adev->dev, NULL); + wdt->base = devm_ioremap_resource(&adev->dev, &adev->res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + wdt->clk = devm_clk_get(&adev->dev, NULL); if (IS_ERR(wdt->clk)) { dev_warn(&adev->dev, "Clock not found\n"); ret = PTR_ERR(wdt->clk); - goto err_clk_get; - } - - wdt->base = ioremap(adev->res.start, resource_size(&adev->res)); - if (!wdt->base) { - ret = -ENOMEM; - dev_warn(&adev->dev, "ioremap fail\n"); - goto err_ioremap; + goto err; } wdt->adev = adev; + wdt->wdd.info = &wdt_info; + wdt->wdd.ops = &wdt_ops; + spin_lock_init(&wdt->lock); - wdt_setload(DEFAULT_TIMEOUT); + watchdog_set_nowayout(&wdt->wdd, nowayout); + watchdog_set_drvdata(&wdt->wdd, wdt); + wdt_setload(&wdt->wdd, DEFAULT_TIMEOUT); - ret = misc_register(&sp805_wdt_miscdev); - if (ret < 0) { - dev_warn(&adev->dev, "cannot register misc device\n"); - goto err_misc_register; + ret = watchdog_register_device(&wdt->wdd); + if (ret) { + dev_err(&adev->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err; } + amba_set_drvdata(adev, wdt); dev_info(&adev->dev, "registration successful\n"); 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(adev->res.start, resource_size(&adev->res)); err: dev_err(&adev->dev, "Probe Failed!!!\n"); return ret; } -static int __devexit sp805_wdt_remove(struct amba_device *adev) +static int sp805_wdt_remove(struct amba_device *adev) +{ + struct sp805_wdt *wdt = amba_get_drvdata(adev); + + watchdog_unregister_device(&wdt->wdd); + watchdog_set_drvdata(&wdt->wdd, NULL); + + return 0; +} + +static int __maybe_unused sp805_wdt_suspend(struct device *dev) +{ + struct sp805_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + return wdt_disable(&wdt->wdd); + + return 0; +} + +static int __maybe_unused sp805_wdt_resume(struct device *dev) { - misc_deregister(&sp805_wdt_miscdev); - iounmap(wdt->base); - clk_put(wdt->clk); - kfree(wdt); - release_mem_region(adev->res.start, resource_size(&adev->res)); + struct sp805_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + return wdt_enable(&wdt->wdd); return 0; } +static SIMPLE_DEV_PM_OPS(sp805_wdt_dev_pm_ops, sp805_wdt_suspend, + sp805_wdt_resume); + static struct amba_id sp805_wdt_ids[] = { { .id = 0x00141805, @@ -364,29 +294,15 @@ MODULE_DEVICE_TABLE(amba, sp805_wdt_ids); static struct amba_driver sp805_wdt_driver = { .drv = { .name = MODULE_NAME, + .pm = &sp805_wdt_dev_pm_ops, }, .id_table = sp805_wdt_ids, .probe = sp805_wdt_probe, - .remove = __devexit_p(sp805_wdt_remove), + .remove = sp805_wdt_remove, }; -static int __init sp805_wdt_init(void) -{ - return amba_driver_register(&sp805_wdt_driver); -} -module_init(sp805_wdt_init); - -static void __exit sp805_wdt_exit(void) -{ - amba_driver_unregister(&sp805_wdt_driver); -} -module_exit(sp805_wdt_exit); - -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, - "Set to 1 to keep watchdog running after device release"); +module_amba_driver(sp805_wdt_driver); -MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>"); +MODULE_AUTHOR("Viresh Kumar <viresh.linux@gmail.com>"); MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
