/* * Watchdog driver for Freescale STMP37XX/STMP378X * * Author: Vitaly Wool <vital@embeddedalley.com> * * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. */ #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/platform_device.h> #include <linux/spinlock.h> #include <linux/uaccess.h> #include <mach/platform.h> #include <mach/regs-rtc.h> #define DEFAULT_HEARTBEAT 19 #define MAX_HEARTBEAT (0x10000000 >> 6) /* missing bitmask in headers */ #define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER 0x80000000 #define WDT_IN_USE 0 #define WDT_OK_TO_CLOSE 1 #define WDOG_COUNTER_RATE 1000 /* 1 kHz clock */ static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock); static unsigned long wdt_status; static const int nowayout = WATCHDOG_NOWAYOUT; static int heartbeat = DEFAULT_HEARTBEAT; static unsigned long boot_status; static void wdt_enable(u32 value) { spin_lock(&stmp3xxx_wdt_io_lock); __raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG); stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL); stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, REGS_RTC_BASE + HW_RTC_PERSISTENT1); spin_unlock(&stmp3xxx_wdt_io_lock); } static void wdt_disable(void) { spin_lock(&stmp3xxx_wdt_io_lock); stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, REGS_RTC_BASE + HW_RTC_PERSISTENT1); stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL); spin_unlock(&stmp3xxx_wdt_io_lock); } static void wdt_ping(void) { wdt_enable(heartbeat * WDOG_COUNTER_RATE); } static int stmp3xxx_wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(WDT_IN_USE, &wdt_status)) return -EBUSY; clear_bit(WDT_OK_TO_CLOSE, &wdt_status); wdt_ping(); return nonseekable_open(inode, file); } static ssize_t stmp3xxx_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { if (len) { if (!nowayout) { size_t i; clear_bit(WDT_OK_TO_CLOSE, &wdt_status); for (i = 0; i != len; i++) { char c; if (get_user(c, data + i)) return -EFAULT; if (c == 'V') set_bit(WDT_OK_TO_CLOSE, &wdt_status); } } wdt_ping(); } return len; } static const struct watchdog_info ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, .identity = "STMP3XXX Watchdog", }; static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; int new_heartbeat, opts; int ret = -ENOTTY; switch (cmd) { case WDIOC_GETSUPPORT: ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; break; case WDIOC_GETSTATUS: ret = put_user(0, p); break; case WDIOC_GETBOOTSTATUS: ret = put_user(boot_status, p); break; case WDIOC_SETOPTIONS: if (get_user(opts, p)) { ret = -EFAULT; break; } if (opts & WDIOS_DISABLECARD) wdt_disable(); else if (opts & WDIOS_ENABLECARD) wdt_ping(); else { pr_debug("%s: unknown option 0x%x\n", __func__, opts); ret = -EINVAL; break; } ret = 0; break; case WDIOC_KEEPALIVE: wdt_ping(); ret = 0; break; case WDIOC_SETTIMEOUT: if (get_user(new_heartbeat, p)) { ret = -EFAULT; break; } if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) { ret = -EINVAL; break; } heartbeat = new_heartbeat; wdt_ping(); /* Fall through */ case WDIOC_GETTIMEOUT: ret = put_user(heartbeat, p); break; } return ret; } static int stmp3xxx_wdt_release(struct inode *inode, struct file *file) { int ret = 0; if (!nowayout) { if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { wdt_ping(); pr_debug("%s: Device closed unexpectdly\n", __func__); ret = -EINVAL; } else { wdt_disable(); clear_bit(WDT_OK_TO_CLOSE, &wdt_status); } } clear_bit(WDT_IN_USE, &wdt_status); return ret; } static const struct file_operations stmp3xxx_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = stmp3xxx_wdt_write, .unlocked_ioctl = stmp3xxx_wdt_ioctl, .open = stmp3xxx_wdt_open, .release = stmp3xxx_wdt_release, }; static struct miscdevice stmp3xxx_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &stmp3xxx_wdt_fops, }; static int __devinit stmp3xxx_wdt_probe(struct platform_device *pdev) { int ret = 0; if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) heartbeat = DEFAULT_HEARTBEAT; boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) & BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER; boot_status = !!boot_status; stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, REGS_RTC_BASE + HW_RTC_PERSISTENT1); wdt_disable(); /* disable for now */ ret = misc_register(&stmp3xxx_wdt_miscdev); if (ret < 0) { dev_err(&pdev->dev, "cannot register misc device\n"); return ret; } printk(KERN_INFO "stmp3xxx watchdog: initialized, heartbeat %d sec\n", heartbeat); return ret; } static int __devexit stmp3xxx_wdt_remove(struct platform_device *pdev) { misc_deregister(&stmp3xxx_wdt_miscdev); return 0; } #ifdef CONFIG_PM static int wdt_suspended; static u32 wdt_saved_time; static int stmp3xxx_wdt_suspend(struct platform_device *pdev, pm_message_t state) { if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) & BM_RTC_CTRL_WATCHDOGEN) { wdt_suspended = 1; wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG); wdt_disable(); } return 0; } static int stmp3xxx_wdt_resume(struct platform_device *pdev) { if (wdt_suspended) { wdt_enable(wdt_saved_time); wdt_suspended = 0; } return 0; } #else #define stmp3xxx_wdt_suspend NULL #define stmp3xxx_wdt_resume NULL #endif static struct platform_driver platform_wdt_driver = { .driver = { .name = "stmp3xxx_wdt", }, .probe = stmp3xxx_wdt_probe, .remove = __devexit_p(stmp3xxx_wdt_remove), .suspend = stmp3xxx_wdt_suspend, .resume = stmp3xxx_wdt_resume, }; static int __init stmp3xxx_wdt_init(void) { return platform_driver_register(&platform_wdt_driver); } static void __exit stmp3xxx_wdt_exit(void) { return platform_driver_unregister(&platform_wdt_driver); } module_init(stmp3xxx_wdt_init); module_exit(stmp3xxx_wdt_exit); MODULE_DESCRIPTION("STMP3XXX Watchdog Driver"); MODULE_LICENSE("GPL"); module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds from 1 to " __MODULE_STRING(MAX_HEARTBEAT) ", default " __MODULE_STRING(DEFAULT_HEARTBEAT)); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);