1 /* 2 * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 3 * JZ4740 Watchdog driver 4 * 5 * This program is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License as published by the 7 * Free Software Foundation; either version 2 of the License, or (at your 8 * option) any later version. 9 * 10 * You should have received a copy of the GNU General Public License along 11 * with this program; if not, write to the Free Software Foundation, Inc., 12 * 675 Mass Ave, Cambridge, MA 02139, USA. 13 * 14 */ 15 16 #include <linux/module.h> 17 #include <linux/moduleparam.h> 18 #include <linux/types.h> 19 #include <linux/kernel.h> 20 #include <linux/fs.h> 21 #include <linux/miscdevice.h> 22 #include <linux/watchdog.h> 23 #include <linux/init.h> 24 #include <linux/bitops.h> 25 #include <linux/platform_device.h> 26 #include <linux/spinlock.h> 27 #include <linux/uaccess.h> 28 #include <linux/io.h> 29 #include <linux/device.h> 30 #include <linux/clk.h> 31 #include <linux/slab.h> 32 33 #include <asm/mach-jz4740/timer.h> 34 35 #define JZ_REG_WDT_TIMER_DATA 0x0 36 #define JZ_REG_WDT_COUNTER_ENABLE 0x4 37 #define JZ_REG_WDT_TIMER_COUNTER 0x8 38 #define JZ_REG_WDT_TIMER_CONTROL 0xC 39 40 #define JZ_WDT_CLOCK_PCLK 0x1 41 #define JZ_WDT_CLOCK_RTC 0x2 42 #define JZ_WDT_CLOCK_EXT 0x4 43 44 #define WDT_IN_USE 0 45 #define WDT_OK_TO_CLOSE 1 46 47 #define JZ_WDT_CLOCK_DIV_SHIFT 3 48 49 #define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) 50 #define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) 51 #define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) 52 #define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) 53 #define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) 54 #define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) 55 56 #define DEFAULT_HEARTBEAT 5 57 #define MAX_HEARTBEAT 2048 58 59 static struct { 60 void __iomem *base; 61 struct resource *mem; 62 struct clk *rtc_clk; 63 unsigned long status; 64 } jz4740_wdt; 65 66 static int heartbeat = DEFAULT_HEARTBEAT; 67 68 69 static void jz4740_wdt_service(void) 70 { 71 writew(0x0, jz4740_wdt.base + JZ_REG_WDT_TIMER_COUNTER); 72 } 73 74 static void jz4740_wdt_set_heartbeat(int new_heartbeat) 75 { 76 unsigned int rtc_clk_rate; 77 unsigned int timeout_value; 78 unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; 79 80 heartbeat = new_heartbeat; 81 82 rtc_clk_rate = clk_get_rate(jz4740_wdt.rtc_clk); 83 84 timeout_value = rtc_clk_rate * heartbeat; 85 while (timeout_value > 0xffff) { 86 if (clock_div == JZ_WDT_CLOCK_DIV_1024) { 87 /* Requested timeout too high; 88 * use highest possible value. */ 89 timeout_value = 0xffff; 90 break; 91 } 92 timeout_value >>= 2; 93 clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); 94 } 95 96 writeb(0x0, jz4740_wdt.base + JZ_REG_WDT_COUNTER_ENABLE); 97 writew(clock_div, jz4740_wdt.base + JZ_REG_WDT_TIMER_CONTROL); 98 99 writew((u16)timeout_value, jz4740_wdt.base + JZ_REG_WDT_TIMER_DATA); 100 writew(0x0, jz4740_wdt.base + JZ_REG_WDT_TIMER_COUNTER); 101 writew(clock_div | JZ_WDT_CLOCK_RTC, 102 jz4740_wdt.base + JZ_REG_WDT_TIMER_CONTROL); 103 104 writeb(0x1, jz4740_wdt.base + JZ_REG_WDT_COUNTER_ENABLE); 105 } 106 107 static void jz4740_wdt_enable(void) 108 { 109 jz4740_timer_enable_watchdog(); 110 jz4740_wdt_set_heartbeat(heartbeat); 111 } 112 113 static void jz4740_wdt_disable(void) 114 { 115 jz4740_timer_disable_watchdog(); 116 writeb(0x0, jz4740_wdt.base + JZ_REG_WDT_COUNTER_ENABLE); 117 } 118 119 static int jz4740_wdt_open(struct inode *inode, struct file *file) 120 { 121 if (test_and_set_bit(WDT_IN_USE, &jz4740_wdt.status)) 122 return -EBUSY; 123 124 jz4740_wdt_enable(); 125 126 return nonseekable_open(inode, file); 127 } 128 129 static ssize_t jz4740_wdt_write(struct file *file, const char *data, 130 size_t len, loff_t *ppos) 131 { 132 if (len) { 133 size_t i; 134 135 clear_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status); 136 for (i = 0; i != len; i++) { 137 char c; 138 139 if (get_user(c, data + i)) 140 return -EFAULT; 141 142 if (c == 'V') 143 set_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status); 144 } 145 jz4740_wdt_service(); 146 } 147 148 return len; 149 } 150 151 static const struct watchdog_info ident = { 152 .options = WDIOF_KEEPALIVEPING, 153 .identity = "jz4740 Watchdog", 154 }; 155 156 static long jz4740_wdt_ioctl(struct file *file, 157 unsigned int cmd, unsigned long arg) 158 { 159 int ret = -ENOTTY; 160 int heartbeat_seconds; 161 162 switch (cmd) { 163 case WDIOC_GETSUPPORT: 164 ret = copy_to_user((struct watchdog_info *)arg, &ident, 165 sizeof(ident)) ? -EFAULT : 0; 166 break; 167 168 case WDIOC_GETSTATUS: 169 case WDIOC_GETBOOTSTATUS: 170 ret = put_user(0, (int *)arg); 171 break; 172 173 case WDIOC_KEEPALIVE: 174 jz4740_wdt_service(); 175 return 0; 176 177 case WDIOC_SETTIMEOUT: 178 if (get_user(heartbeat_seconds, (int __user *)arg)) 179 return -EFAULT; 180 181 jz4740_wdt_set_heartbeat(heartbeat_seconds); 182 return 0; 183 184 case WDIOC_GETTIMEOUT: 185 return put_user(heartbeat, (int *)arg); 186 187 default: 188 break; 189 } 190 191 return ret; 192 } 193 194 static int jz4740_wdt_release(struct inode *inode, struct file *file) 195 { 196 jz4740_wdt_service(); 197 198 if (test_and_clear_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status)) 199 jz4740_wdt_disable(); 200 201 clear_bit(WDT_IN_USE, &jz4740_wdt.status); 202 return 0; 203 } 204 205 static const struct file_operations jz4740_wdt_fops = { 206 .owner = THIS_MODULE, 207 .llseek = no_llseek, 208 .write = jz4740_wdt_write, 209 .unlocked_ioctl = jz4740_wdt_ioctl, 210 .open = jz4740_wdt_open, 211 .release = jz4740_wdt_release, 212 }; 213 214 static struct miscdevice jz4740_wdt_miscdev = { 215 .minor = WATCHDOG_MINOR, 216 .name = "watchdog", 217 .fops = &jz4740_wdt_fops, 218 }; 219 220 static int __devinit jz4740_wdt_probe(struct platform_device *pdev) 221 { 222 int ret = 0, size; 223 struct resource *res; 224 struct device *dev = &pdev->dev; 225 226 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 227 if (res == NULL) { 228 dev_err(dev, "failed to get memory region resource\n"); 229 return -ENXIO; 230 } 231 232 size = resource_size(res); 233 jz4740_wdt.mem = request_mem_region(res->start, size, pdev->name); 234 if (jz4740_wdt.mem == NULL) { 235 dev_err(dev, "failed to get memory region\n"); 236 return -EBUSY; 237 } 238 239 jz4740_wdt.base = ioremap_nocache(res->start, size); 240 if (jz4740_wdt.base == NULL) { 241 dev_err(dev, "failed to map memory region\n"); 242 ret = -EBUSY; 243 goto err_release_region; 244 } 245 246 jz4740_wdt.rtc_clk = clk_get(NULL, "rtc"); 247 if (IS_ERR(jz4740_wdt.rtc_clk)) { 248 dev_err(dev, "cannot find RTC clock\n"); 249 ret = PTR_ERR(jz4740_wdt.rtc_clk); 250 goto err_iounmap; 251 } 252 253 ret = misc_register(&jz4740_wdt_miscdev); 254 if (ret < 0) { 255 dev_err(dev, "cannot register misc device\n"); 256 goto err_disable_clk; 257 } 258 259 return 0; 260 261 err_disable_clk: 262 clk_put(jz4740_wdt.rtc_clk); 263 err_iounmap: 264 iounmap(jz4740_wdt.base); 265 err_release_region: 266 release_mem_region(jz4740_wdt.mem->start, 267 resource_size(jz4740_wdt.mem)); 268 return ret; 269 } 270 271 272 static int __devexit jz4740_wdt_remove(struct platform_device *pdev) 273 { 274 jz4740_wdt_disable(); 275 misc_deregister(&jz4740_wdt_miscdev); 276 clk_put(jz4740_wdt.rtc_clk); 277 278 iounmap(jz4740_wdt.base); 279 jz4740_wdt.base = NULL; 280 281 release_mem_region(jz4740_wdt.mem->start, 282 resource_size(jz4740_wdt.mem)); 283 jz4740_wdt.mem = NULL; 284 285 return 0; 286 } 287 288 289 static struct platform_driver jz4740_wdt_driver = { 290 .probe = jz4740_wdt_probe, 291 .remove = __devexit_p(jz4740_wdt_remove), 292 .driver = { 293 .name = "jz4740-wdt", 294 .owner = THIS_MODULE, 295 }, 296 }; 297 298 module_platform_driver(jz4740_wdt_driver); 299 300 MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 301 MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 302 303 module_param(heartbeat, int, 0); 304 MODULE_PARM_DESC(heartbeat, 305 "Watchdog heartbeat period in seconds from 1 to " 306 __MODULE_STRING(MAX_HEARTBEAT) ", default " 307 __MODULE_STRING(DEFAULT_HEARTBEAT)); 308 309 MODULE_LICENSE("GPL"); 310 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 311 MODULE_ALIAS("platform:jz4740-wdt"); 312