diff options
Diffstat (limited to 'drivers/char/watchdog/mv64x60_wdt.c')
-rw-r--r-- | drivers/char/watchdog/mv64x60_wdt.c | 218 |
1 files changed, 140 insertions, 78 deletions
diff --git a/drivers/char/watchdog/mv64x60_wdt.c b/drivers/char/watchdog/mv64x60_wdt.c index b887cdb01334..0365c317f7e1 100644 --- a/drivers/char/watchdog/mv64x60_wdt.c +++ b/drivers/char/watchdog/mv64x60_wdt.c @@ -23,61 +23,101 @@ #include <linux/watchdog.h> #include <linux/platform_device.h> -#include <asm/mv64x60.h> +#include <linux/mv643xx.h> #include <asm/uaccess.h> #include <asm/io.h> -/* MV64x60 WDC (config) register access definitions */ -#define MV64x60_WDC_CTL1_MASK (3 << 24) -#define MV64x60_WDC_CTL1(val) ((val & 3) << 24) -#define MV64x60_WDC_CTL2_MASK (3 << 26) -#define MV64x60_WDC_CTL2(val) ((val & 3) << 26) +#define MV64x60_WDT_WDC_OFFSET 0 + +/* + * The watchdog configuration register contains a pair of 2-bit fields, + * 1. a reload field, bits 27-26, which triggers a reload of + * the countdown register, and + * 2. an enable field, bits 25-24, which toggles between + * enabling and disabling the watchdog timer. + * Bit 31 is a read-only field which indicates whether the + * watchdog timer is currently enabled. + * + * The low 24 bits contain the timer reload value. + */ +#define MV64x60_WDC_ENABLE_SHIFT 24 +#define MV64x60_WDC_SERVICE_SHIFT 26 +#define MV64x60_WDC_ENABLED_SHIFT 31 + +#define MV64x60_WDC_ENABLED_TRUE 1 +#define MV64x60_WDC_ENABLED_FALSE 0 /* Flags bits */ #define MV64x60_WDOG_FLAG_OPENED 0 -#define MV64x60_WDOG_FLAG_ENABLED 1 static unsigned long wdt_flags; static int wdt_status; -static void __iomem *mv64x60_regs; +static void __iomem *mv64x60_wdt_regs; static int mv64x60_wdt_timeout; +static int mv64x60_wdt_count; +static unsigned int bus_clk; +static char expect_close; +static DEFINE_SPINLOCK(mv64x60_wdt_spinlock); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -static void mv64x60_wdt_reg_write(u32 val) +static int mv64x60_wdt_toggle_wdc(int enabled_predicate, int field_shift) { - /* Allow write only to CTL1 / CTL2 fields, retaining values in - * other fields. - */ - u32 data = readl(mv64x60_regs + MV64x60_WDT_WDC); - data &= ~(MV64x60_WDC_CTL1_MASK | MV64x60_WDC_CTL2_MASK); - data |= val; - writel(data, mv64x60_regs + MV64x60_WDT_WDC); + u32 data; + u32 enabled; + int ret = 0; + + spin_lock(&mv64x60_wdt_spinlock); + data = readl(mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); + enabled = (data >> MV64x60_WDC_ENABLED_SHIFT) & 1; + + /* only toggle the requested field if enabled state matches predicate */ + if ((enabled ^ enabled_predicate) == 0) { + /* We write a 1, then a 2 -- to the appropriate field */ + data = (1 << field_shift) | mv64x60_wdt_count; + writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); + + data = (2 << field_shift) | mv64x60_wdt_count; + writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); + ret = 1; + } + spin_unlock(&mv64x60_wdt_spinlock); + + return ret; } static void mv64x60_wdt_service(void) { - /* Write 01 followed by 10 to CTL2 */ - mv64x60_wdt_reg_write(MV64x60_WDC_CTL2(0x01)); - mv64x60_wdt_reg_write(MV64x60_WDC_CTL2(0x02)); + mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, + MV64x60_WDC_SERVICE_SHIFT); +} + +static void mv64x60_wdt_handler_enable(void) +{ + if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_FALSE, + MV64x60_WDC_ENABLE_SHIFT)) { + mv64x60_wdt_service(); + printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n"); + } } static void mv64x60_wdt_handler_disable(void) { - if (test_and_clear_bit(MV64x60_WDOG_FLAG_ENABLED, &wdt_flags)) { - /* Write 01 followed by 10 to CTL1 */ - mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x01)); - mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x02)); + if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, + MV64x60_WDC_ENABLE_SHIFT)) printk(KERN_NOTICE "mv64x60_wdt: watchdog deactivated\n"); - } } -static void mv64x60_wdt_handler_enable(void) +static void mv64x60_wdt_set_timeout(unsigned int timeout) { - if (!test_and_set_bit(MV64x60_WDOG_FLAG_ENABLED, &wdt_flags)) { - /* Write 01 followed by 10 to CTL1 */ - mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x01)); - mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x02)); - printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n"); - } + /* maximum bus cycle count is 0xFFFFFFFF */ + if (timeout > 0xFFFFFFFF / bus_clk) + timeout = 0xFFFFFFFF / bus_clk; + + mv64x60_wdt_count = timeout * bus_clk >> 8; + mv64x60_wdt_timeout = timeout; } static int mv64x60_wdt_open(struct inode *inode, struct file *file) @@ -85,21 +125,24 @@ static int mv64x60_wdt_open(struct inode *inode, struct file *file) if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags)) return -EBUSY; - mv64x60_wdt_service(); - mv64x60_wdt_handler_enable(); + if (nowayout) + __module_get(THIS_MODULE); - nonseekable_open(inode, file); + mv64x60_wdt_handler_enable(); - return 0; + return nonseekable_open(inode, file); } static int mv64x60_wdt_release(struct inode *inode, struct file *file) { - mv64x60_wdt_service(); - -#if !defined(CONFIG_WATCHDOG_NOWAYOUT) - mv64x60_wdt_handler_disable(); -#endif + if (expect_close == 42) + mv64x60_wdt_handler_disable(); + else { + printk(KERN_CRIT + "mv64x60_wdt: unexpected close, not stopping timer!\n"); + mv64x60_wdt_service(); + } + expect_close = 0; clear_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags); @@ -109,8 +152,22 @@ static int mv64x60_wdt_release(struct inode *inode, struct file *file) static ssize_t mv64x60_wdt_write(struct file *file, const char __user *data, size_t len, loff_t * ppos) { - if (len) + if (len) { + if (!nowayout) { + size_t i; + + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } mv64x60_wdt_service(); + } return len; } @@ -119,9 +176,12 @@ static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int timeout; + int options; void __user *argp = (void __user *)arg; static struct watchdog_info info = { - .options = WDIOF_KEEPALIVEPING, + .options = WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, .firmware_version = 0, .identity = "MV64x60 watchdog", }; @@ -143,7 +203,15 @@ static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file, return -EOPNOTSUPP; case WDIOC_SETOPTIONS: - return -EOPNOTSUPP; + if (get_user(options, (int __user *)argp)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) + mv64x60_wdt_handler_disable(); + + if (options & WDIOS_ENABLECARD) + mv64x60_wdt_handler_enable(); + break; case WDIOC_KEEPALIVE: mv64x60_wdt_service(); @@ -151,11 +219,13 @@ static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file, break; case WDIOC_SETTIMEOUT: - return -EOPNOTSUPP; + if (get_user(timeout, (int __user *)argp)) + return -EFAULT; + mv64x60_wdt_set_timeout(timeout); + /* Fall through */ case WDIOC_GETTIMEOUT: - timeout = mv64x60_wdt_timeout * HZ; - if (put_user(timeout, (int __user *)argp)) + if (put_user(mv64x60_wdt_timeout, (int __user *)argp)) return -EFAULT; break; @@ -184,18 +254,33 @@ static struct miscdevice mv64x60_wdt_miscdev = { static int __devinit mv64x60_wdt_probe(struct platform_device *dev) { struct mv64x60_wdt_pdata *pdata = dev->dev.platform_data; - int bus_clk = 133; + struct resource *r; + int timeout = 10; - mv64x60_wdt_timeout = 10; + bus_clk = 133; /* in MHz */ if (pdata) { - mv64x60_wdt_timeout = pdata->timeout; + timeout = pdata->timeout; bus_clk = pdata->bus_clk; } - mv64x60_regs = mv64x60_get_bridge_vbase(); + /* Since bus_clk is truncated MHz, actual frequency could be + * up to 1MHz higher. Round up, since it's better to time out + * too late than too soon. + */ + bus_clk++; + bus_clk *= 1000000; /* convert to Hz */ + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!r) + return -ENODEV; - writel((mv64x60_wdt_timeout * (bus_clk * 1000000)) >> 8, - mv64x60_regs + MV64x60_WDT_WDC); + mv64x60_wdt_regs = ioremap(r->start, r->end - r->start + 1); + if (mv64x60_wdt_regs == NULL) + return -ENOMEM; + + mv64x60_wdt_set_timeout(timeout); + + mv64x60_wdt_handler_disable(); /* in case timer was already running */ return misc_register(&mv64x60_wdt_miscdev); } @@ -204,9 +289,10 @@ static int __devexit mv64x60_wdt_remove(struct platform_device *dev) { misc_deregister(&mv64x60_wdt_miscdev); - mv64x60_wdt_service(); mv64x60_wdt_handler_disable(); + iounmap(mv64x60_wdt_regs); + return 0; } @@ -219,40 +305,16 @@ static struct platform_driver mv64x60_wdt_driver = { }, }; -static struct platform_device *mv64x60_wdt_dev; - static int __init mv64x60_wdt_init(void) { - int ret; - printk(KERN_INFO "MV64x60 watchdog driver\n"); - mv64x60_wdt_dev = platform_device_alloc(MV64x60_WDT_NAME, -1); - if (!mv64x60_wdt_dev) { - ret = -ENOMEM; - goto out; - } - - ret = platform_device_add(mv64x60_wdt_dev); - if (ret) { - platform_device_put(mv64x60_wdt_dev); - goto out; - } - - ret = platform_driver_register(&mv64x60_wdt_driver); - if (ret) { - platform_device_unregister(mv64x60_wdt_dev); - goto out; - } - - out: - return ret; + return platform_driver_register(&mv64x60_wdt_driver); } static void __exit mv64x60_wdt_exit(void) { platform_driver_unregister(&mv64x60_wdt_driver); - platform_device_unregister(mv64x60_wdt_dev); } module_init(mv64x60_wdt_init); |