1/* 2 * Copyright (c) 2009 Nuvoton technology corporation. 3 * 4 * Wan ZongShun <mcuos.com@gmail.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation;version 2 of the License. 9 * 10 */ 11 12#include <linux/bitops.h> 13#include <linux/errno.h> 14#include <linux/fs.h> 15#include <linux/io.h> 16#include <linux/clk.h> 17#include <linux/kernel.h> 18#include <linux/miscdevice.h> 19#include <linux/module.h> 20#include <linux/moduleparam.h> 21#include <linux/platform_device.h> 22#include <linux/slab.h> 23#include <linux/interrupt.h> 24#include <linux/types.h> 25#include <linux/watchdog.h> 26#include <linux/uaccess.h> 27 28#define REG_WTCR 0x1c 29#define WTCLK (0x01 << 10) 30#define WTE (0x01 << 7) /*wdt enable*/ 31#define WTIS (0x03 << 4) 32#define WTIF (0x01 << 3) 33#define WTRF (0x01 << 2) 34#define WTRE (0x01 << 1) 35#define WTR (0x01 << 0) 36/* 37 * The watchdog time interval can be calculated via following formula: 38 * WTIS real time interval (formula) 39 * 0x00 ((2^ 14 ) * ((external crystal freq) / 256))seconds 40 * 0x01 ((2^ 16 ) * ((external crystal freq) / 256))seconds 41 * 0x02 ((2^ 18 ) * ((external crystal freq) / 256))seconds 42 * 0x03 ((2^ 20 ) * ((external crystal freq) / 256))seconds 43 * 44 * The external crystal freq is 15Mhz in the nuc900 evaluation board. 45 * So 0x00 = +-0.28 seconds, 0x01 = +-1.12 seconds, 0x02 = +-4.48 seconds, 46 * 0x03 = +- 16.92 seconds.. 47 */ 48#define WDT_HW_TIMEOUT 0x02 49#define WDT_TIMEOUT (HZ/2) 50#define WDT_HEARTBEAT 15 51 52static int heartbeat = WDT_HEARTBEAT; 53module_param(heartbeat, int, 0); 54MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " 55 "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); 56 57static bool nowayout = WATCHDOG_NOWAYOUT; 58module_param(nowayout, bool, 0); 59MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 60 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 61 62struct nuc900_wdt { 63 struct clk *wdt_clock; 64 struct platform_device *pdev; 65 void __iomem *wdt_base; 66 char expect_close; 67 struct timer_list timer; 68 spinlock_t wdt_lock; 69 unsigned long next_heartbeat; 70}; 71 72static unsigned long nuc900wdt_busy; 73static struct nuc900_wdt *nuc900_wdt; 74 75static inline void nuc900_wdt_keepalive(void) 76{ 77 unsigned int val; 78 79 spin_lock(&nuc900_wdt->wdt_lock); 80 81 val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR); 82 val |= (WTR | WTIF); 83 __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR); 84 85 spin_unlock(&nuc900_wdt->wdt_lock); 86} 87 88static inline void nuc900_wdt_start(void) 89{ 90 unsigned int val; 91 92 spin_lock(&nuc900_wdt->wdt_lock); 93 94 val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR); 95 val |= (WTRE | WTE | WTR | WTCLK | WTIF); 96 val &= ~WTIS; 97 val |= (WDT_HW_TIMEOUT << 0x04); 98 __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR); 99 100 spin_unlock(&nuc900_wdt->wdt_lock); 101 102 nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ; 103 mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT); 104} 105 106static inline void nuc900_wdt_stop(void) 107{ 108 unsigned int val; 109 110 del_timer(&nuc900_wdt->timer); 111 112 spin_lock(&nuc900_wdt->wdt_lock); 113 114 val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR); 115 val &= ~WTE; 116 __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR); 117 118 spin_unlock(&nuc900_wdt->wdt_lock); 119} 120 121static inline void nuc900_wdt_ping(void) 122{ 123 nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ; 124} 125 126static int nuc900_wdt_open(struct inode *inode, struct file *file) 127{ 128 129 if (test_and_set_bit(0, &nuc900wdt_busy)) 130 return -EBUSY; 131 132 nuc900_wdt_start(); 133 134 return nonseekable_open(inode, file); 135} 136 137static int nuc900_wdt_close(struct inode *inode, struct file *file) 138{ 139 if (nuc900_wdt->expect_close == 42) 140 nuc900_wdt_stop(); 141 else { 142 dev_crit(&nuc900_wdt->pdev->dev, 143 "Unexpected close, not stopping watchdog!\n"); 144 nuc900_wdt_ping(); 145 } 146 147 nuc900_wdt->expect_close = 0; 148 clear_bit(0, &nuc900wdt_busy); 149 return 0; 150} 151 152static const struct watchdog_info nuc900_wdt_info = { 153 .identity = "nuc900 watchdog", 154 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 155 WDIOF_MAGICCLOSE, 156}; 157 158static long nuc900_wdt_ioctl(struct file *file, 159 unsigned int cmd, unsigned long arg) 160{ 161 void __user *argp = (void __user *)arg; 162 int __user *p = argp; 163 int new_value; 164 165 switch (cmd) { 166 case WDIOC_GETSUPPORT: 167 return copy_to_user(argp, &nuc900_wdt_info, 168 sizeof(nuc900_wdt_info)) ? -EFAULT : 0; 169 case WDIOC_GETSTATUS: 170 case WDIOC_GETBOOTSTATUS: 171 return put_user(0, p); 172 173 case WDIOC_KEEPALIVE: 174 nuc900_wdt_ping(); 175 return 0; 176 177 case WDIOC_SETTIMEOUT: 178 if (get_user(new_value, p)) 179 return -EFAULT; 180 181 heartbeat = new_value; 182 nuc900_wdt_ping(); 183 184 return put_user(new_value, p); 185 case WDIOC_GETTIMEOUT: 186 return put_user(heartbeat, p); 187 default: 188 return -ENOTTY; 189 } 190} 191 192static ssize_t nuc900_wdt_write(struct file *file, const char __user *data, 193 size_t len, loff_t *ppos) 194{ 195 if (!len) 196 return 0; 197 198 /* Scan for magic character */ 199 if (!nowayout) { 200 size_t i; 201 202 nuc900_wdt->expect_close = 0; 203 204 for (i = 0; i < len; i++) { 205 char c; 206 if (get_user(c, data + i)) 207 return -EFAULT; 208 if (c == 'V') { 209 nuc900_wdt->expect_close = 42; 210 break; 211 } 212 } 213 } 214 215 nuc900_wdt_ping(); 216 return len; 217} 218 219static void nuc900_wdt_timer_ping(unsigned long data) 220{ 221 if (time_before(jiffies, nuc900_wdt->next_heartbeat)) { 222 nuc900_wdt_keepalive(); 223 mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT); 224 } else 225 dev_warn(&nuc900_wdt->pdev->dev, "Will reset the machine !\n"); 226} 227 228static const struct file_operations nuc900wdt_fops = { 229 .owner = THIS_MODULE, 230 .llseek = no_llseek, 231 .unlocked_ioctl = nuc900_wdt_ioctl, 232 .open = nuc900_wdt_open, 233 .release = nuc900_wdt_close, 234 .write = nuc900_wdt_write, 235}; 236 237static struct miscdevice nuc900wdt_miscdev = { 238 .minor = WATCHDOG_MINOR, 239 .name = "watchdog", 240 .fops = &nuc900wdt_fops, 241}; 242 243static int nuc900wdt_probe(struct platform_device *pdev) 244{ 245 struct resource *res; 246 int ret = 0; 247 248 nuc900_wdt = devm_kzalloc(&pdev->dev, sizeof(*nuc900_wdt), 249 GFP_KERNEL); 250 if (!nuc900_wdt) 251 return -ENOMEM; 252 253 nuc900_wdt->pdev = pdev; 254 255 spin_lock_init(&nuc900_wdt->wdt_lock); 256 257 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 258 nuc900_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 259 if (IS_ERR(nuc900_wdt->wdt_base)) 260 return PTR_ERR(nuc900_wdt->wdt_base); 261 262 nuc900_wdt->wdt_clock = devm_clk_get(&pdev->dev, NULL); 263 if (IS_ERR(nuc900_wdt->wdt_clock)) { 264 dev_err(&pdev->dev, "failed to find watchdog clock source\n"); 265 return PTR_ERR(nuc900_wdt->wdt_clock); 266 } 267 268 clk_enable(nuc900_wdt->wdt_clock); 269 270 setup_timer(&nuc900_wdt->timer, nuc900_wdt_timer_ping, 0); 271 272 ret = misc_register(&nuc900wdt_miscdev); 273 if (ret) { 274 dev_err(&pdev->dev, "err register miscdev on minor=%d (%d)\n", 275 WATCHDOG_MINOR, ret); 276 goto err_clk; 277 } 278 279 return 0; 280 281err_clk: 282 clk_disable(nuc900_wdt->wdt_clock); 283 return ret; 284} 285 286static int nuc900wdt_remove(struct platform_device *pdev) 287{ 288 misc_deregister(&nuc900wdt_miscdev); 289 290 clk_disable(nuc900_wdt->wdt_clock); 291 292 return 0; 293} 294 295static struct platform_driver nuc900wdt_driver = { 296 .probe = nuc900wdt_probe, 297 .remove = nuc900wdt_remove, 298 .driver = { 299 .name = "nuc900-wdt", 300 }, 301}; 302 303module_platform_driver(nuc900wdt_driver); 304 305MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); 306MODULE_DESCRIPTION("Watchdog driver for NUC900"); 307MODULE_LICENSE("GPL"); 308MODULE_ALIAS("platform:nuc900-wdt"); 309