diff options
author | Wim Van Sebroeck <wim@iguana.be> | 2007-08-17 08:38:02 (GMT) |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2007-10-18 10:39:03 (GMT) |
commit | b7e04f8c61a46d742de23af5d7ca2b41b33e40ac (patch) | |
tree | c52a7ea568648e2d8ed0d423098b42298de2058b /drivers/watchdog/rm9k_wdt.c | |
parent | d85714d81cc0408daddb68c10f7fd69eafe7c213 (diff) | |
download | linux-b7e04f8c61a46d742de23af5d7ca2b41b33e40ac.tar.xz |
mv watchdog tree under drivers
move watchdog tree from drivers/char/watchdog to drivers/watchdog.
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog/rm9k_wdt.c')
-rw-r--r-- | drivers/watchdog/rm9k_wdt.c | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/drivers/watchdog/rm9k_wdt.c b/drivers/watchdog/rm9k_wdt.c new file mode 100644 index 0000000..5c921e4 --- /dev/null +++ b/drivers/watchdog/rm9k_wdt.c @@ -0,0 +1,420 @@ +/* + * Watchdog implementation for GPI h/w found on PMC-Sierra RM9xxx + * chips. + * + * Copyright (C) 2004 by Basler Vision Technologies AG + * Author: Thomas Koeller <thomas.koeller@baslerweb.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/notifier.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <asm/processor.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/rm9k-ocd.h> + +#include <rm9k_wdt.h> + + +#define CLOCK 125000000 +#define MAX_TIMEOUT_SECONDS 32 +#define CPCCR 0x0080 +#define CPGIG1SR 0x0044 +#define CPGIG1ER 0x0054 + + +/* Function prototypes */ +static irqreturn_t wdt_gpi_irqhdl(int, void *); +static void wdt_gpi_start(void); +static void wdt_gpi_stop(void); +static void wdt_gpi_set_timeout(unsigned int); +static int wdt_gpi_open(struct inode *, struct file *); +static int wdt_gpi_release(struct inode *, struct file *); +static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *); +static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long); +static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); +static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); +static int __init wdt_gpi_probe(struct device *); +static int __exit wdt_gpi_remove(struct device *); + + +static const char wdt_gpi_name[] = "wdt_gpi"; +static atomic_t opencnt; +static int expect_close; +static int locked; + + +/* These are set from device resources */ +static void __iomem * wd_regs; +static unsigned int wd_irq, wd_ctr; + + +/* Module arguments */ +static int timeout = MAX_TIMEOUT_SECONDS; +module_param(timeout, int, 0444); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); + +static unsigned long resetaddr = 0xbffdc200; +module_param(resetaddr, ulong, 0444); +MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); + +static unsigned long flagaddr = 0xbffdc104; +module_param(flagaddr, ulong, 0444); +MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); + +static int powercycle; +module_param(powercycle, bool, 0444); +MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0444); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); + + +/* Kernel interfaces */ +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = wdt_gpi_open, + .release = wdt_gpi_release, + .write = wdt_gpi_write, + .unlocked_ioctl = wdt_gpi_ioctl, +}; + +static struct miscdevice miscdev = { + .minor = WATCHDOG_MINOR, + .name = wdt_gpi_name, + .fops = &fops, +}; + +static struct notifier_block wdt_gpi_shutdown = { + .notifier_call = wdt_gpi_notify, +}; + + +/* Interrupt handler */ +static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt) +{ + if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) + return IRQ_NONE; + __raw_writel(0x1, wd_regs + 0x0008); + + + printk(KERN_CRIT "%s: watchdog expired - resetting system\n", + wdt_gpi_name); + + *(volatile char *) flagaddr |= 0x01; + *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; + iob(); + while (1) + cpu_relax(); +} + + +/* Watchdog functions */ +static void wdt_gpi_start(void) +{ + u32 reg; + + lock_titan_regs(); + reg = titan_readl(CPGIG1ER); + titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER); + iob(); + unlock_titan_regs(); +} + +static void wdt_gpi_stop(void) +{ + u32 reg; + + lock_titan_regs(); + reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); + titan_writel(reg, CPCCR); + reg = titan_readl(CPGIG1ER); + titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); + iob(); + unlock_titan_regs(); +} + +static void wdt_gpi_set_timeout(unsigned int to) +{ + u32 reg; + const u32 wdval = (to * CLOCK) & ~0x0000000f; + + lock_titan_regs(); + reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); + titan_writel(reg, CPCCR); + wmb(); + __raw_writel(wdval, wd_regs + 0x0000); + wmb(); + titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR); + wmb(); + titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR); + iob(); + unlock_titan_regs(); +} + + +/* /dev/watchdog operations */ +static int wdt_gpi_open(struct inode *inode, struct file *file) +{ + int res; + + if (unlikely(atomic_dec_if_positive(&opencnt) < 0)) + return -EBUSY; + + expect_close = 0; + if (locked) { + module_put(THIS_MODULE); + free_irq(wd_irq, &miscdev); + locked = 0; + } + + res = request_irq(wd_irq, wdt_gpi_irqhdl, IRQF_SHARED | IRQF_DISABLED, + wdt_gpi_name, &miscdev); + if (unlikely(res)) + return res; + + wdt_gpi_set_timeout(timeout); + wdt_gpi_start(); + + printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", + wdt_gpi_name, timeout); + return nonseekable_open(inode, file); +} + +static int wdt_gpi_release(struct inode *inode, struct file *file) +{ + if (nowayout) { + printk(KERN_INFO "%s: no way out - watchdog left running\n", + wdt_gpi_name); + __module_get(THIS_MODULE); + locked = 1; + } else { + if (expect_close) { + wdt_gpi_stop(); + free_irq(wd_irq, &miscdev); + printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); + } else { + printk(KERN_CRIT "%s: unexpected close() -" + " watchdog left running\n", + wdt_gpi_name); + wdt_gpi_set_timeout(timeout); + __module_get(THIS_MODULE); + locked = 1; + } + } + + atomic_inc(&opencnt); + return 0; +} + +static ssize_t +wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o) +{ + char val; + + wdt_gpi_set_timeout(timeout); + expect_close = (s > 0) && !get_user(val, d) && (val == 'V'); + return s ? 1 : 0; +} + +static long +wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + long res = -ENOTTY; + const long size = _IOC_SIZE(cmd); + int stat; + void __user *argp = (void __user *)arg; + static struct watchdog_info wdinfo = { + .identity = "RM9xxx/GPI watchdog", + .firmware_version = 0, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING + }; + + if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE)) + return -ENOTTY; + + if ((_IOC_DIR(cmd) & _IOC_READ) + && !access_ok(VERIFY_WRITE, arg, size)) + return -EFAULT; + + if ((_IOC_DIR(cmd) & _IOC_WRITE) + && !access_ok(VERIFY_READ, arg, size)) + return -EFAULT; + + expect_close = 0; + + switch (cmd) { + case WDIOC_GETSUPPORT: + wdinfo.options = nowayout ? + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING : + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE; + res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size; + break; + + case WDIOC_GETSTATUS: + break; + + case WDIOC_GETBOOTSTATUS: + stat = (*(volatile char *) flagaddr & 0x01) + ? WDIOF_CARDRESET : 0; + res = __copy_to_user(argp, &stat, size) ? + -EFAULT : size; + break; + + case WDIOC_SETOPTIONS: + break; + + case WDIOC_KEEPALIVE: + wdt_gpi_set_timeout(timeout); + res = size; + break; + + case WDIOC_SETTIMEOUT: + { + int val; + if (unlikely(__copy_from_user(&val, argp, size))) { + res = -EFAULT; + break; + } + + if (val > MAX_TIMEOUT_SECONDS) + val = MAX_TIMEOUT_SECONDS; + timeout = val; + wdt_gpi_set_timeout(val); + res = size; + printk(KERN_INFO "%s: timeout set to %u seconds\n", + wdt_gpi_name, timeout); + } + break; + + case WDIOC_GETTIMEOUT: + res = __copy_to_user(argp, &timeout, size) ? + -EFAULT : size; + break; + } + + return res; +} + + +/* Shutdown notifier */ +static int +wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_gpi_stop(); + + return NOTIFY_DONE; +} + + +/* Init & exit procedures */ +static const struct resource * +wdt_gpi_get_resource(struct platform_device *pdv, const char *name, + unsigned int type) +{ + char buf[80]; + if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf) + return NULL; + return platform_get_resource_byname(pdv, type, buf); +} + +/* No hotplugging on the platform bus - use __init */ +static int __init wdt_gpi_probe(struct device *dev) +{ + int res; + struct platform_device * const pdv = to_platform_device(dev); + const struct resource + * const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS, + IORESOURCE_MEM), + * const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ, + IORESOURCE_IRQ), + * const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER, + 0); + + if (unlikely(!rr || !ri || !rc)) + return -ENXIO; + + wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start); + if (unlikely(!wd_regs)) + return -ENOMEM; + wd_irq = ri->start; + wd_ctr = rc->start; + res = misc_register(&miscdev); + if (res) + iounmap(wd_regs); + else + register_reboot_notifier(&wdt_gpi_shutdown); + return res; +} + +static int __exit wdt_gpi_remove(struct device *dev) +{ + int res; + + unregister_reboot_notifier(&wdt_gpi_shutdown); + res = misc_deregister(&miscdev); + iounmap(wd_regs); + wd_regs = NULL; + return res; +} + + +/* Device driver init & exit */ +static struct device_driver wdt_gpi_driver = { + .name = (char *) wdt_gpi_name, + .bus = &platform_bus_type, + .owner = THIS_MODULE, + .probe = wdt_gpi_probe, + .remove = __exit_p(wdt_gpi_remove), + .shutdown = NULL, + .suspend = NULL, + .resume = NULL, +}; + +static int __init wdt_gpi_init_module(void) +{ + atomic_set(&opencnt, 1); + if (timeout > MAX_TIMEOUT_SECONDS) + timeout = MAX_TIMEOUT_SECONDS; + return driver_register(&wdt_gpi_driver); +} + +static void __exit wdt_gpi_cleanup_module(void) +{ + driver_unregister(&wdt_gpi_driver); +} + +module_init(wdt_gpi_init_module); +module_exit(wdt_gpi_cleanup_module); + +MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>"); +MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices"); +MODULE_VERSION("0.1"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + |