/* * drivers/char/watchdog/sp805-wdt.c * * Watchdog driver for ARM SP805 watchdog module * * Copyright (C) 2010 ST Microelectronics * Viresh Kumar<viresh.kumar@st.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 * warranty of any kind, whether express or implied. */ #include <linux/device.h> #include <linux/resource.h> #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/slab.h> #include <linux/spinlock.h> #include <linux/types.h> #include <linux/uaccess.h> #include <linux/watchdog.h> /* default timeout in seconds */ #define DEFAULT_TIMEOUT 60 #define MODULE_NAME "sp805-wdt" /* watchdog register offsets and masks */ #define WDTLOAD 0x000 #define LOAD_MIN 0x00000001 #define LOAD_MAX 0xFFFFFFFF #define WDTVALUE 0x004 #define WDTCONTROL 0x008 /* control register masks */ #define INT_ENABLE (1 << 0) #define RESET_ENABLE (1 << 1) #define WDTINTCLR 0x00C #define WDTRIS 0x010 #define WDTMIS 0x014 #define INT_MASK (1 << 0) #define WDTLOCK 0xC00 #define UNLOCK 0x1ACCE551 #define LOCK 0x00000001 /** * 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 */ struct sp805_wdt { 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; /* This routine finds load value that will reset system in required timout */ static void wdt_setload(unsigned int timeout) { u64 load, rate; rate = clk_get_rate(wdt->clk); /* * sp805 runs counter with given value twice, after the end of first * counter it gives an interrupt and then starts counter again. If * interrupt already occurred then it resets the system. This is why * load is half of what should be required. */ load = div_u64(rate, 2) * timeout - 1; load = (load > LOAD_MAX) ? LOAD_MAX : load; load = (load < LOAD_MIN) ? LOAD_MIN : load; 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); spin_unlock(&wdt->lock); } /* returns number of seconds left for reset to occur */ static u32 wdt_timeleft(void) { u64 load, rate; rate = clk_get_rate(wdt->clk); spin_lock(&wdt->lock); load = readl(wdt->base + WDTVALUE); /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */ if (!(readl(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) { spin_lock(&wdt->lock); 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); spin_unlock(&wdt->lock); } /* disables watchdog timers reset */ static void wdt_disable(void) { spin_lock(&wdt->lock); writel(UNLOCK, wdt->base + WDTLOCK); writel(0, wdt->base + WDTCONTROL); writel(0, wdt->base + WDTLOAD); writel(LOCK, wdt->base + WDTLOCK); spin_unlock(&wdt->lock); } static ssize_t sp805_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) { 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; } 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) { 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; } static int sp805_wdt_open(struct inode *inode, struct file *file) { int ret = 0; 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); err: clear_bit(WDT_BUSY, &wdt->status); return ret; } 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; } wdt_disable(); clk_disable(wdt->clk); clear_bit(WDT_BUSY, &wdt->status); 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 struct miscdevice sp805_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &sp805_wdt_fops, }; static int __devinit sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) { 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); if (!wdt) { dev_warn(&adev->dev, "Kzalloc failed\n"); ret = -ENOMEM; goto err_kzalloc; } wdt->clk = 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; } wdt->adev = adev; spin_lock_init(&wdt->lock); wdt_setload(DEFAULT_TIMEOUT); ret = misc_register(&sp805_wdt_miscdev); if (ret < 0) { dev_warn(&adev->dev, "cannot register misc device\n"); goto err_misc_register; } 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) { misc_deregister(&sp805_wdt_miscdev); iounmap(wdt->base); clk_put(wdt->clk); kfree(wdt); release_mem_region(adev->res.start, resource_size(&adev->res)); return 0; } static struct amba_id sp805_wdt_ids[] __initdata = { { .id = 0x00141805, .mask = 0x00ffffff, }, { 0, 0 }, }; static struct amba_driver sp805_wdt_driver = { .drv = { .name = MODULE_NAME, }, .id_table = sp805_wdt_ids, .probe = sp805_wdt_probe, .remove = __devexit_p(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_AUTHOR("Viresh Kumar <viresh.kumar@st.com>"); MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);