| /* |
| * 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 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, SA_SHIRQ | SA_INTERRUPT, |
| 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); |
| |