1 /* 2 * Intel Atom E6xx Watchdog driver 3 * 4 * Copyright (C) 2011 Alexander Stein 5 * <alexander.stein@systec-electronic.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of version 2 of the GNU General 9 * Public License as published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be 12 * useful, but WITHOUT ANY WARRANTY; without even the implied 13 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 14 * PURPOSE. See the GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public 16 * License along with this program; if not, write to the Free 17 * Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 * The full GNU General Public License is included in this 20 * distribution in the file called COPYING. 21 * 22 */ 23 24 #include <linux/module.h> 25 #include <linux/moduleparam.h> 26 #include <linux/platform_device.h> 27 #include <linux/io.h> 28 #include <linux/kernel.h> 29 #include <linux/types.h> 30 #include <linux/watchdog.h> 31 #include <linux/miscdevice.h> 32 #include <linux/seq_file.h> 33 #include <linux/debugfs.h> 34 #include <linux/uaccess.h> 35 #include <linux/spinlock.h> 36 37 #define DRIVER_NAME "ie6xx_wdt" 38 39 #define PV1 0x00 40 #define PV2 0x04 41 42 #define RR0 0x0c 43 #define RR1 0x0d 44 #define WDT_RELOAD 0x01 45 #define WDT_TOUT 0x02 46 47 #define WDTCR 0x10 48 #define WDT_PRE_SEL 0x04 49 #define WDT_RESET_SEL 0x08 50 #define WDT_RESET_EN 0x10 51 #define WDT_TOUT_EN 0x20 52 53 #define DCR 0x14 54 55 #define WDTLR 0x18 56 #define WDT_LOCK 0x01 57 #define WDT_ENABLE 0x02 58 #define WDT_TOUT_CNF 0x03 59 60 #define MIN_TIME 1 61 #define MAX_TIME (10 * 60) /* 10 minutes */ 62 #define DEFAULT_TIME 60 63 64 static unsigned int timeout = DEFAULT_TIME; 65 module_param(timeout, uint, 0); 66 MODULE_PARM_DESC(timeout, 67 "Default Watchdog timer setting (" 68 __MODULE_STRING(DEFAULT_TIME) "s)." 69 "The range is from 1 to 600"); 70 71 static bool nowayout = WATCHDOG_NOWAYOUT; 72 module_param(nowayout, bool, 0); 73 MODULE_PARM_DESC(nowayout, 74 "Watchdog cannot be stopped once started (default=" 75 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 76 77 static u8 resetmode = 0x10; 78 module_param(resetmode, byte, 0); 79 MODULE_PARM_DESC(resetmode, 80 "Resetmode bits: 0x08 warm reset (cold reset otherwise), " 81 "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)"); 82 83 static struct { 84 unsigned short sch_wdtba; 85 struct spinlock unlock_sequence; 86 #ifdef CONFIG_DEBUG_FS 87 struct dentry *debugfs; 88 #endif 89 } ie6xx_wdt_data; 90 91 /* 92 * This is needed to write to preload and reload registers 93 * struct ie6xx_wdt_data.unlock_sequence must be used 94 * to prevent sequence interrupts 95 */ 96 static void ie6xx_wdt_unlock_registers(void) 97 { 98 outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0); 99 outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0); 100 } 101 102 static int ie6xx_wdt_ping(struct watchdog_device *wdd) 103 { 104 spin_lock(&ie6xx_wdt_data.unlock_sequence); 105 ie6xx_wdt_unlock_registers(); 106 outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1); 107 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 108 return 0; 109 } 110 111 static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) 112 { 113 u32 preload; 114 u64 clock; 115 u8 wdtcr; 116 117 /* Watchdog clock is PCI Clock (33MHz) */ 118 clock = 33000000; 119 /* and the preload value is loaded into [34:15] of the down counter */ 120 preload = (t * clock) >> 15; 121 /* 122 * Manual states preload must be one less. 123 * Does not wrap as t is at least 1 124 */ 125 preload -= 1; 126 127 spin_lock(&ie6xx_wdt_data.unlock_sequence); 128 129 /* Set ResetMode & Enable prescaler for range 10ms to 10 min */ 130 wdtcr = resetmode & 0x38; 131 outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR); 132 133 ie6xx_wdt_unlock_registers(); 134 outl(0, ie6xx_wdt_data.sch_wdtba + PV1); 135 136 ie6xx_wdt_unlock_registers(); 137 outl(preload, ie6xx_wdt_data.sch_wdtba + PV2); 138 139 ie6xx_wdt_unlock_registers(); 140 outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1); 141 142 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 143 144 wdd->timeout = t; 145 return 0; 146 } 147 148 static int ie6xx_wdt_start(struct watchdog_device *wdd) 149 { 150 ie6xx_wdt_set_timeout(wdd, wdd->timeout); 151 152 /* Enable the watchdog timer */ 153 spin_lock(&ie6xx_wdt_data.unlock_sequence); 154 outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR); 155 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 156 157 return 0; 158 } 159 160 static int ie6xx_wdt_stop(struct watchdog_device *wdd) 161 { 162 if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK) 163 return -1; 164 165 /* Disable the watchdog timer */ 166 spin_lock(&ie6xx_wdt_data.unlock_sequence); 167 outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR); 168 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 169 170 return 0; 171 } 172 173 static const struct watchdog_info ie6xx_wdt_info = { 174 .identity = "Intel Atom E6xx Watchdog", 175 .options = WDIOF_SETTIMEOUT | 176 WDIOF_MAGICCLOSE | 177 WDIOF_KEEPALIVEPING, 178 }; 179 180 static const struct watchdog_ops ie6xx_wdt_ops = { 181 .owner = THIS_MODULE, 182 .start = ie6xx_wdt_start, 183 .stop = ie6xx_wdt_stop, 184 .ping = ie6xx_wdt_ping, 185 .set_timeout = ie6xx_wdt_set_timeout, 186 }; 187 188 static struct watchdog_device ie6xx_wdt_dev = { 189 .info = &ie6xx_wdt_info, 190 .ops = &ie6xx_wdt_ops, 191 .min_timeout = MIN_TIME, 192 .max_timeout = MAX_TIME, 193 }; 194 195 #ifdef CONFIG_DEBUG_FS 196 197 static int ie6xx_wdt_dbg_show(struct seq_file *s, void *unused) 198 { 199 seq_printf(s, "PV1 = 0x%08x\n", 200 inl(ie6xx_wdt_data.sch_wdtba + PV1)); 201 seq_printf(s, "PV2 = 0x%08x\n", 202 inl(ie6xx_wdt_data.sch_wdtba + PV2)); 203 seq_printf(s, "RR = 0x%08x\n", 204 inw(ie6xx_wdt_data.sch_wdtba + RR0)); 205 seq_printf(s, "WDTCR = 0x%08x\n", 206 inw(ie6xx_wdt_data.sch_wdtba + WDTCR)); 207 seq_printf(s, "DCR = 0x%08x\n", 208 inl(ie6xx_wdt_data.sch_wdtba + DCR)); 209 seq_printf(s, "WDTLR = 0x%08x\n", 210 inw(ie6xx_wdt_data.sch_wdtba + WDTLR)); 211 212 seq_printf(s, "\n"); 213 return 0; 214 } 215 216 static int ie6xx_wdt_dbg_open(struct inode *inode, struct file *file) 217 { 218 return single_open(file, ie6xx_wdt_dbg_show, NULL); 219 } 220 221 static const struct file_operations ie6xx_wdt_dbg_operations = { 222 .open = ie6xx_wdt_dbg_open, 223 .read = seq_read, 224 .llseek = seq_lseek, 225 .release = single_release, 226 }; 227 228 static void ie6xx_wdt_debugfs_init(void) 229 { 230 /* /sys/kernel/debug/ie6xx_wdt */ 231 ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt", 232 S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_dbg_operations); 233 } 234 235 static void ie6xx_wdt_debugfs_exit(void) 236 { 237 debugfs_remove(ie6xx_wdt_data.debugfs); 238 } 239 240 #else 241 static void ie6xx_wdt_debugfs_init(void) 242 { 243 } 244 245 static void ie6xx_wdt_debugfs_exit(void) 246 { 247 } 248 #endif 249 250 static int ie6xx_wdt_probe(struct platform_device *pdev) 251 { 252 struct resource *res; 253 u8 wdtlr; 254 int ret; 255 256 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 257 if (!res) 258 return -ENODEV; 259 260 if (!request_region(res->start, resource_size(res), pdev->name)) { 261 dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n", 262 (u64)res->start); 263 return -EBUSY; 264 } 265 266 ie6xx_wdt_data.sch_wdtba = res->start; 267 dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba); 268 269 ie6xx_wdt_dev.timeout = timeout; 270 watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout); 271 272 spin_lock_init(&ie6xx_wdt_data.unlock_sequence); 273 274 wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR); 275 if (wdtlr & WDT_LOCK) 276 dev_warn(&pdev->dev, 277 "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr); 278 279 ie6xx_wdt_debugfs_init(); 280 281 ret = watchdog_register_device(&ie6xx_wdt_dev); 282 if (ret) { 283 dev_err(&pdev->dev, 284 "Watchdog timer: cannot register device (err =%d)\n", 285 ret); 286 goto misc_register_error; 287 } 288 289 return 0; 290 291 misc_register_error: 292 ie6xx_wdt_debugfs_exit(); 293 release_region(res->start, resource_size(res)); 294 ie6xx_wdt_data.sch_wdtba = 0; 295 return ret; 296 } 297 298 static int ie6xx_wdt_remove(struct platform_device *pdev) 299 { 300 struct resource *res; 301 302 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 303 ie6xx_wdt_stop(NULL); 304 watchdog_unregister_device(&ie6xx_wdt_dev); 305 ie6xx_wdt_debugfs_exit(); 306 release_region(res->start, resource_size(res)); 307 ie6xx_wdt_data.sch_wdtba = 0; 308 309 return 0; 310 } 311 312 static struct platform_driver ie6xx_wdt_driver = { 313 .probe = ie6xx_wdt_probe, 314 .remove = ie6xx_wdt_remove, 315 .driver = { 316 .name = DRIVER_NAME, 317 .owner = THIS_MODULE, 318 }, 319 }; 320 321 static int __init ie6xx_wdt_init(void) 322 { 323 /* Check boot parameters to verify that their initial values */ 324 /* are in range. */ 325 if ((timeout < MIN_TIME) || 326 (timeout > MAX_TIME)) { 327 pr_err("Watchdog timer: value of timeout %d (dec) " 328 "is out of range from %d to %d (dec)\n", 329 timeout, MIN_TIME, MAX_TIME); 330 return -EINVAL; 331 } 332 333 return platform_driver_register(&ie6xx_wdt_driver); 334 } 335 336 static void __exit ie6xx_wdt_exit(void) 337 { 338 platform_driver_unregister(&ie6xx_wdt_driver); 339 } 340 341 late_initcall(ie6xx_wdt_init); 342 module_exit(ie6xx_wdt_exit); 343 344 MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>"); 345 MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver"); 346 MODULE_LICENSE("GPL"); 347 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 348 MODULE_ALIAS("platform:" DRIVER_NAME); 349