http://linux-watchdog.bkbits.net/linux-2.6-watchdog-mm ben@fluff.org|ChangeSet|20040907191042|53234 ben # This is a BitKeeper generated diff -Nru style patch. # # ChangeSet # 2004/09/07 21:10:42+02:00 ben@fluff.org # Add S3C2410 (Samsung ARM9 Mobile SoC) watchdog driver # # drivers/char/watchdog/s3c2410_wdt.c # 2004/09/07 21:10:34+02:00 ben@fluff.org +507 -0 # # include/asm-arm/arch-s3c2410/regs-watchdog.h # 2004/09/07 21:10:34+02:00 ben@fluff.org +1 -0 # Add S3C2410 (Samsung ARM9 Mobile SoC) watchdog driver # # drivers/char/watchdog/s3c2410_wdt.c # 2004/09/07 21:10:34+02:00 ben@fluff.org +0 -0 # BitKeeper file /home/wim/BitKeeper/projects/linux-2.6-watchdog-mm/drivers/char/watchdog/s3c2410_wdt.c # # drivers/char/watchdog/Makefile # 2004/09/07 21:10:34+02:00 ben@fluff.org +1 -0 # Add S3C2410 (Samsung ARM9 Mobile SoC) watchdog driver # # drivers/char/watchdog/Kconfig # 2004/09/07 21:10:34+02:00 ben@fluff.org +16 -0 # Add S3C2410 (Samsung ARM9 Mobile SoC) watchdog driver # # arch/arm/configs/s3c2410_defconfig # 2004/09/07 21:10:34+02:00 ben@fluff.org +8 -1 # Add S3C2410 (Samsung ARM9 Mobile SoC) watchdog driver # # arch/arm/configs/bast_defconfig # 2004/09/07 21:10:34+02:00 ben@fluff.org +9 -2 # Add S3C2410 (Samsung ARM9 Mobile SoC) watchdog driver # diff -Nru a/arch/arm/configs/bast_defconfig b/arch/arm/configs/bast_defconfig --- a/arch/arm/configs/bast_defconfig 2004-09-26 11:18:28 -07:00 +++ b/arch/arm/configs/bast_defconfig 2004-09-26 11:18:28 -07:00 @@ -479,12 +479,19 @@ # # Watchdog Cards # -# CONFIG_WATCHDOG is not set +CONFIG_WATCHDOG=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_S3C2410_WATCHDOG=y # CONFIG_NVRAM is not set CONFIG_RTC=y # CONFIG_DTLK is not set # CONFIG_R3964 is not set -# CONFIG_APPLICOM is not set + # # Ftape, the floppy tape device driver diff -Nru a/arch/arm/configs/s3c2410_defconfig b/arch/arm/configs/s3c2410_defconfig --- a/arch/arm/configs/s3c2410_defconfig 2004-09-26 11:18:28 -07:00 +++ b/arch/arm/configs/s3c2410_defconfig 2004-09-26 11:18:28 -07:00 @@ -493,7 +493,14 @@ # # Watchdog Cards # -# CONFIG_WATCHDOG is not set +CONFIG_WATCHDOG=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_S3C2410_WATCHDOG=y # CONFIG_NVRAM is not set CONFIG_RTC=y # CONFIG_DTLK is not set diff -Nru a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig --- a/drivers/char/watchdog/Kconfig 2004-09-26 11:18:28 -07:00 +++ b/drivers/char/watchdog/Kconfig 2004-09-26 11:18:28 -07:00 @@ -106,6 +106,22 @@ Say N if you are unsure. +config S3C2410_WATCHDOG + tristate "S3C2410 Watchdog" + depends on WATCHDOG && ARCH_S3C2410 + help + Watchdog timer block in the Samsung S3C2410 chips. This will + reboot the system when the timer expires with the watchdog + enabled. + + The driver is limited by the speed of the system's PCLK + signal, so with reasonbaly fast systems (PCLK around 50-66MHz) + then watchdog intervals of over approximately 20seconds are + unavailable. + + The driver can be built as a module by choosing M, and will + be called s3c2410_wdt + config SA1100_WATCHDOG tristate "SA1100/PXA2xx watchdog" depends on WATCHDOG && ( ARCH_SA1100 || ARCH_PXA ) diff -Nru a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile --- a/drivers/char/watchdog/Makefile 2004-09-26 11:18:28 -07:00 +++ b/drivers/char/watchdog/Makefile 2004-09-26 11:18:28 -07:00 @@ -21,6 +21,7 @@ obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SH_WDT) += shwdt.o +obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o diff -Nru a/drivers/char/watchdog/s3c2410_wdt.c b/drivers/char/watchdog/s3c2410_wdt.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/char/watchdog/s3c2410_wdt.c 2004-09-26 11:18:28 -07:00 @@ -0,0 +1,507 @@ +/* linux/drivers/char/watchdog/s3c2410_wdt.c + * + * Copyright (c) 2004 Simtec Electronics + * Ben Dooks + * + * S3C2410 Watchdog Timer Support + * + * Based on, softdog.c by Alan Cox, + * (c) Copyright 1996 Alan Cox + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#undef S3C2410_VA_WATCHDOG +#define S3C2410_VA_WATCHDOG (0) + +#include + +#define PFX "s3c2410-wdt: " + +#ifdef CONFIG_S3C2410_WATCHDOG_DEBUG +#undef pr_debug +#define pr_debug(msg, x...) do { printk(KERN_INFO msg, x); } while(0) +#endif + +/* configurations from makefile */ + +#ifndef CONFIG_WATCHDOG_NOWAYOUT +#define CONFIG_WATCHDOG_NOWAYOUT (0) +#endif + +#ifndef CONFIG_S3C2410_WATCHDOG_ATBOOT +#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) +#endif + +#ifndef CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME +#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) +#endif + +static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; +static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; +static int nowayout = CONFIG_WATCHDOG_NOWAYOUT; +static int soft_noboot = 0; + +module_param(tmr_margin, int, 0); +module_param(tmr_atboot, int, 0); +module_param(nowayout, int, 0); +module_param(soft_noboot, int, 0); + +MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); + +MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); + +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); + + +typedef enum close_state { + CLOSE_STATE_NOT, + CLOSE_STATE_ALLOW=0x4021 +} close_state_t; + +static struct semaphore open_lock; +static struct resource *wdt_mem; +static struct resource *wdt_irq; +static struct clk *wdt_clock; +static unsigned long wdt_base; +static unsigned int wdt_count; +static close_state_t allow_close; + +static unsigned int tmr_count; + +/* watchdog control routines */ + +static int s3c2410wdt_keepalive(void) +{ + writel(wdt_count, wdt_base + S3C2410_WTCNT); + return 0; +} + +static int s3c2410wdt_stop(void) +{ + unsigned long wtcon; + + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); + writel(wtcon, wdt_base + S3C2410_WTCON); + + return 0; +} + +static int s3c2410wdt_start(void) +{ + unsigned long wtcon; + + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; + + if (soft_noboot) { + wtcon |= S3C2410_WTCON_INTEN; + wtcon &= ~S3C2410_WTCON_RSTEN; + } else { + wtcon &= ~S3C2410_WTCON_INTEN; + wtcon |= S3C2410_WTCON_RSTEN; + } + + clk_enable(wdt_clock); + + pr_debug("%s: tmr_count=0x%08x, wtcon=%08lx\n", + __FUNCTION__, tmr_count, wtcon); + + writel(tmr_count, wdt_base + S3C2410_WTDAT); + writel(wtcon, wdt_base + S3C2410_WTCON); + + return 0; +} + +static int s3c2410wdt_set_heartbeat(int timeout) +{ + unsigned int freq = clk_get_rate(wdt_clock); + unsigned int count; + unsigned int divisor = 1; + unsigned long wtcon; + + if (timeout < 1) + return -EINVAL; + + tmr_margin = timeout; + + /* I think someone must have missed a divide-by-2 in the 2410, + * as a divisor of 128 gives half the calculated delay... + */ + + freq /= 128/2; + count = timeout * freq; + + pr_debug("%s: count=%d, timeout=%d, freq=%d\n", + __FUNCTION__, count, timeout, freq); + + /* if the count is bigger than the watchdog register, + then work out what we need to do (and if) we can + actually make this value + */ + + if (count >= 0x10000) { + for (divisor = 1; divisor <= 0x100; divisor++) { + if ((count / divisor) < 0x10000) + break; + } + + if ((count / divisor) >= 0x10000) { + printk(KERN_ERR PFX "timeout %d too big\n", timeout); + return -EINVAL; + } + } + + pr_debug("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", + __FUNCTION__, timeout, divisor, count, count/divisor); + + count /= divisor; + tmr_count = count; + + /* update the pre-scaler */ + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; + wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); + + writel(count, wdt_base + S3C2410_WTDAT); + writel(wtcon, wdt_base + S3C2410_WTCON); + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int s3c2410wdt_open(struct inode *inode, struct file *file) +{ + if(down_trylock(&open_lock)) + return -EBUSY; + + if (nowayout) { + __module_get(THIS_MODULE); + } else { + allow_close = CLOSE_STATE_ALLOW; + } + + /* start the timer */ + s3c2410wdt_start(); + return nonseekable_open(inode, file); +} + +static int s3c2410wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (allow_close == CLOSE_STATE_ALLOW) { + s3c2410wdt_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + s3c2410wdt_keepalive(); + } + + allow_close = CLOSE_STATE_NOT; + up(&open_lock); + return 0; +} + +static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* + * Refresh the timer. + */ + if(len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + allow_close = CLOSE_STATE_NOT; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + allow_close = CLOSE_STATE_ALLOW; + } + } + + s3c2410wdt_keepalive(); + } + return len; +} + +#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE + +static struct watchdog_info s3c2410_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "S3C2410 Watchdog", +}; + + +static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &s3c2410_wdt_ident, + sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + s3c2410wdt_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + + if (s3c2410wdt_set_heartbeat(new_margin)) + return -EINVAL; + + s3c2410wdt_keepalive(); + return put_user(tmr_margin, p); + + case WDIOC_GETTIMEOUT: + return put_user(tmr_margin, p); + } +} + +/* + * Notifier for system down + */ + +static int s3c2410wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + s3c2410wdt_stop(); + } + return NOTIFY_DONE; +} + +/* kernel interface */ + +static struct file_operations s3c2410wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = s3c2410wdt_write, + .ioctl = s3c2410wdt_ioctl, + .open = s3c2410wdt_open, + .release = s3c2410wdt_release, +}; + +static struct miscdevice s3c2410wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &s3c2410wdt_fops, +}; + +static struct notifier_block s3c2410wdt_notifier = { + .notifier_call = s3c2410wdt_notify_sys, +}; + +/* interrupt handler code */ + +static irqreturn_t s3c2410wdt_irq(int irqno, void *param, + struct pt_regs *regs) +{ + printk(KERN_INFO PFX "Watchdog timer expired!\n"); + + s3c2410wdt_keepalive(); + return IRQ_HANDLED; +} +/* device interface */ + +static int s3c2410wdt_probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + int started = 0; + int ret; + + pr_debug("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev); + + /* get the memory region for the watchdog timer */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + printk(KERN_INFO PFX "failed to get memory region resouce\n"); + return -ENOENT; + } + + if (!request_mem_region(res->start, res->end-res->start, pdev->name)) { + printk(KERN_INFO PFX "failed to get memory region\n"); + return -ENOENT; + } + + wdt_base = (unsigned long)ioremap(res->start, res->end - res->start); + if (wdt_base == 0) { + printk(KERN_INFO PFX "failed to ioremap() region\n"); + return -EINVAL; + } + + pr_debug("wdt_base=%08lx\n", wdt_base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + printk(KERN_INFO PFX "failed to get irq resource\n"); + return -ENOENT; + } + + ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, dev); + if (ret != 0) { + printk(KERN_INFO PFX "failed to install irq (%d)\n", ret); + return ret; + } + + wdt_clock = clk_get(dev, "watchdog"); + if (wdt_clock == NULL) { + printk(KERN_INFO PFX "failed to find watchdog clock source\n"); + return -ENOENT; + } + + clk_use(wdt_clock); + + /* see if we can actually set the requested timer margin, and if + * not, try the default value */ + + if (s3c2410wdt_set_heartbeat(tmr_margin)) { + started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); + + if (started == 0) { + printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n", + CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); + } else { + printk(KERN_INFO PFX "default timer value is out of range, cannot start\n"); + } + } + + ret = register_reboot_notifier(&s3c2410wdt_notifier); + if (ret) { + printk (KERN_ERR PFX "cannot register reboot notifier (%d)\n", + ret); + return ret; + } + + ret = misc_register(&s3c2410wdt_miscdev); + if (ret) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&s3c2410wdt_notifier); + return ret; + } + + if (tmr_atboot && started == 0) { + printk(KERN_INFO PFX "Starting Watchdog Timer\n"); + s3c2410wdt_start(); + } + + return 0; +} + +static int s3c2410wdt_remove(struct device *dev) +{ + if (wdt_mem != NULL) { + release_mem_region(wdt_mem->start, + wdt_mem->end - wdt_mem->start); + wdt_mem = NULL; + } + + if (wdt_irq != NULL) { + free_irq(wdt_irq->start, dev); + wdt_irq = NULL; + } + + if (wdt_clock != NULL) { + clk_disable(wdt_clock); + clk_unuse(wdt_clock); + clk_put(wdt_clock); + wdt_clock = NULL; + } + + misc_deregister(&s3c2410wdt_miscdev); + return 0; +} + +static struct device_driver s3c2410wdt_driver = { + .name = "s3c2410-wdt", + .bus = &platform_bus_type, + .probe = s3c2410wdt_probe, + .remove = s3c2410wdt_remove, +}; + + + +static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics"; + +static int __init watchdog_init(void) +{ + printk(banner); + return driver_register(&s3c2410wdt_driver); +} + +static void __exit watchdog_exit(void) +{ + driver_unregister(&s3c2410wdt_driver); + unregister_reboot_notifier(&s3c2410wdt_notifier); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff -Nru a/include/asm-arm/arch-s3c2410/regs-watchdog.h b/include/asm-arm/arch-s3c2410/regs-watchdog.h --- a/include/asm-arm/arch-s3c2410/regs-watchdog.h 2004-09-26 11:18:28 -07:00 +++ b/include/asm-arm/arch-s3c2410/regs-watchdog.h 2004-09-26 11:18:28 -07:00 @@ -38,6 +38,7 @@ #define S3C2410_WTCON_DIV128 (3<<3) #define S3C2410_WTCON_PRESCALE(x) ((x) << 8) +#define S3C2410_WTCON_PRESCALE_MASK (0xff00) #endif /* __ASM_ARCH_REGS_WATCHDOG_H */