| /* |
| * 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. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #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 <linux/module.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 bool 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 unexpectedly\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 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; |
| } |
| |
| pr_info("initialized, heartbeat %d sec\n", heartbeat); |
| |
| return ret; |
| } |
| |
| static int 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 = stmp3xxx_wdt_remove, |
| .suspend = stmp3xxx_wdt_suspend, |
| .resume = stmp3xxx_wdt_resume, |
| }; |
| |
| module_platform_driver(platform_wdt_driver); |
| |
| 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); |