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 if (data[len-1] == 'V') 134 set_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status); 135 else 136 clear_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status); 137 138 jz4740_wdt_service(); 139 } 140 141 return len; 142 } 143 144 static const struct watchdog_info ident = { 145 .options = WDIOF_KEEPALIVEPING, 146 .identity = "jz4740 Watchdog", 147 }; 148 149 static long jz4740_wdt_ioctl(struct file *file, 150 unsigned int cmd, unsigned long arg) 151 { 152 int ret = -ENOTTY; 153 int heartbeat_seconds; 154 155 switch (cmd) { 156 case WDIOC_GETSUPPORT: 157 ret = copy_to_user((struct watchdog_info *)arg, &ident, 158 sizeof(ident)) ? -EFAULT : 0; 159 break; 160 161 case WDIOC_GETSTATUS: 162 case WDIOC_GETBOOTSTATUS: 163 ret = put_user(0, (int *)arg); 164 break; 165 166 case WDIOC_KEEPALIVE: 167 jz4740_wdt_service(); 168 return 0; 169 170 case WDIOC_SETTIMEOUT: 171 if (get_user(heartbeat_seconds, (int __user *)arg)) 172 return -EFAULT; 173 174 jz4740_wdt_set_heartbeat(heartbeat_seconds); 175 return 0; 176 177 case WDIOC_GETTIMEOUT: 178 return put_user(heartbeat, (int *)arg); 179 180 default: 181 break; 182 } 183 184 return ret; 185 } 186 187 static int jz4740_wdt_release(struct inode *inode, struct file *file) 188 { 189 jz4740_wdt_service(); 190 191 if (test_and_clear_bit(WDT_OK_TO_CLOSE, &jz4740_wdt.status)) 192 jz4740_wdt_disable(); 193 194 clear_bit(WDT_IN_USE, &jz4740_wdt.status); 195 return 0; 196 } 197 198 static const struct file_operations jz4740_wdt_fops = { 199 .owner = THIS_MODULE, 200 .llseek = no_llseek, 201 .write = jz4740_wdt_write, 202 .unlocked_ioctl = jz4740_wdt_ioctl, 203 .open = jz4740_wdt_open, 204 .release = jz4740_wdt_release, 205 }; 206 207 static struct miscdevice jz4740_wdt_miscdev = { 208 .minor = WATCHDOG_MINOR, 209 .name = "watchdog", 210 .fops = &jz4740_wdt_fops, 211 }; 212 213 static int __devinit jz4740_wdt_probe(struct platform_device *pdev) 214 { 215 int ret = 0, size; 216 struct resource *res; 217 struct device *dev = &pdev->dev; 218 219 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 220 if (res == NULL) { 221 dev_err(dev, "failed to get memory region resource\n"); 222 return -ENXIO; 223 } 224 225 size = resource_size(res); 226 jz4740_wdt.mem = request_mem_region(res->start, size, pdev->name); 227 if (jz4740_wdt.mem == NULL) { 228 dev_err(dev, "failed to get memory region\n"); 229 return -EBUSY; 230 } 231 232 jz4740_wdt.base = ioremap_nocache(res->start, size); 233 if (jz4740_wdt.base == NULL) { 234 dev_err(dev, "failed to map memory region\n"); 235 ret = -EBUSY; 236 goto err_release_region; 237 } 238 239 jz4740_wdt.rtc_clk = clk_get(NULL, "rtc"); 240 if (IS_ERR(jz4740_wdt.rtc_clk)) { 241 dev_err(dev, "cannot find RTC clock\n"); 242 ret = PTR_ERR(jz4740_wdt.rtc_clk); 243 goto err_iounmap; 244 } 245 246 ret = misc_register(&jz4740_wdt_miscdev); 247 if (ret < 0) { 248 dev_err(dev, "cannot register misc device\n"); 249 goto err_disable_clk; 250 } 251 252 return 0; 253 254 err_disable_clk: 255 clk_put(jz4740_wdt.rtc_clk); 256 err_iounmap: 257 iounmap(jz4740_wdt.base); 258 err_release_region: 259 release_mem_region(jz4740_wdt.mem->start, 260 resource_size(jz4740_wdt.mem)); 261 return ret; 262 } 263 264 265 static int __devexit jz4740_wdt_remove(struct platform_device *pdev) 266 { 267 jz4740_wdt_disable(); 268 misc_deregister(&jz4740_wdt_miscdev); 269 clk_put(jz4740_wdt.rtc_clk); 270 271 iounmap(jz4740_wdt.base); 272 jz4740_wdt.base = NULL; 273 274 release_mem_region(jz4740_wdt.mem->start, 275 resource_size(jz4740_wdt.mem)); 276 jz4740_wdt.mem = NULL; 277 278 return 0; 279 } 280 281 282 static struct platform_driver jz4740_wdt_driver = { 283 .probe = jz4740_wdt_probe, 284 .remove = __devexit_p(jz4740_wdt_remove), 285 .driver = { 286 .name = "jz4740-wdt", 287 .owner = THIS_MODULE, 288 }, 289 }; 290 291 292 static int __init jz4740_wdt_init(void) 293 { 294 return platform_driver_register(&jz4740_wdt_driver); 295 } 296 module_init(jz4740_wdt_init); 297 298 static void __exit jz4740_wdt_exit(void) 299 { 300 platform_driver_unregister(&jz4740_wdt_driver); 301 } 302 module_exit(jz4740_wdt_exit); 303 304 MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 305 MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 306 307 module_param(heartbeat, int, 0); 308 MODULE_PARM_DESC(heartbeat, 309 "Watchdog heartbeat period in seconds from 1 to " 310 __MODULE_STRING(MAX_HEARTBEAT) ", default " 311 __MODULE_STRING(DEFAULT_HEARTBEAT)); 312 313 MODULE_LICENSE("GPL"); 314 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 315 MODULE_ALIAS("platform:jz4740-wdt"); 316