| Donald Johnson | 57539c1 | 2011-02-02 16:20:16 +0000 | [diff] [blame] | 1 | /* | 
|  | 2 | *      Intel_SCU 0.2:  An Intel SCU IOH Based Watchdog Device | 
|  | 3 | *			for Intel part #(s): | 
|  | 4 | *				- AF82MP20 PCH | 
|  | 5 | * | 
|  | 6 | *      Copyright (C) 2009-2010 Intel Corporation. All rights reserved. | 
|  | 7 | * | 
|  | 8 | *      This program is free software; you can redistribute it and/or | 
|  | 9 | *      modify it under the terms of version 2 of the GNU General | 
|  | 10 | *      Public License as published by the Free Software Foundation. | 
|  | 11 | * | 
|  | 12 | *      This program is distributed in the hope that it will be | 
|  | 13 | *      useful, but WITHOUT ANY WARRANTY; without even the implied | 
|  | 14 | *      warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR | 
|  | 15 | *      PURPOSE.  See the GNU General Public License for more details. | 
|  | 16 | *      You should have received a copy of the GNU General Public | 
|  | 17 | *      License along with this program; if not, write to the Free | 
|  | 18 | *      Software Foundation, Inc., 59 Temple Place - Suite 330, | 
|  | 19 | *      Boston, MA  02111-1307, USA. | 
|  | 20 | *      The full GNU General Public License is included in this | 
|  | 21 | *      distribution in the file called COPYING. | 
|  | 22 | * | 
|  | 23 | */ | 
|  | 24 |  | 
|  | 25 | #include <linux/compiler.h> | 
|  | 26 | #include <linux/module.h> | 
|  | 27 | #include <linux/kernel.h> | 
|  | 28 | #include <linux/moduleparam.h> | 
|  | 29 | #include <linux/types.h> | 
|  | 30 | #include <linux/miscdevice.h> | 
|  | 31 | #include <linux/watchdog.h> | 
|  | 32 | #include <linux/fs.h> | 
|  | 33 | #include <linux/notifier.h> | 
|  | 34 | #include <linux/reboot.h> | 
|  | 35 | #include <linux/init.h> | 
|  | 36 | #include <linux/jiffies.h> | 
|  | 37 | #include <linux/uaccess.h> | 
|  | 38 | #include <linux/slab.h> | 
|  | 39 | #include <linux/io.h> | 
|  | 40 | #include <linux/interrupt.h> | 
|  | 41 | #include <linux/delay.h> | 
|  | 42 | #include <linux/sched.h> | 
|  | 43 | #include <linux/signal.h> | 
|  | 44 | #include <linux/sfi.h> | 
| Donald Johnson | 57539c1 | 2011-02-02 16:20:16 +0000 | [diff] [blame] | 45 | #include <asm/irq.h> | 
| Arun Sharma | 6006349 | 2011-07-26 16:09:06 -0700 | [diff] [blame] | 46 | #include <linux/atomic.h> | 
| Donald Johnson | 57539c1 | 2011-02-02 16:20:16 +0000 | [diff] [blame] | 47 | #include <asm/intel_scu_ipc.h> | 
|  | 48 | #include <asm/apb_timer.h> | 
|  | 49 | #include <asm/mrst.h> | 
|  | 50 |  | 
|  | 51 | #include "intel_scu_watchdog.h" | 
|  | 52 |  | 
|  | 53 | /* Bounds number of times we will retry loading time count */ | 
|  | 54 | /* This retry is a work around for a silicon bug.	   */ | 
|  | 55 | #define MAX_RETRY 16 | 
|  | 56 |  | 
|  | 57 | #define IPC_SET_WATCHDOG_TIMER	0xF8 | 
|  | 58 |  | 
|  | 59 | static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN; | 
|  | 60 | module_param(timer_margin, int, 0); | 
|  | 61 | MODULE_PARM_DESC(timer_margin, | 
|  | 62 | "Watchdog timer margin" | 
|  | 63 | "Time between interrupt and resetting the system" | 
|  | 64 | "The range is from 1 to 160" | 
|  | 65 | "This is the time for all keep alives to arrive"); | 
|  | 66 |  | 
|  | 67 | static int timer_set = DEFAULT_TIME; | 
|  | 68 | module_param(timer_set, int, 0); | 
|  | 69 | MODULE_PARM_DESC(timer_set, | 
|  | 70 | "Default Watchdog timer setting" | 
|  | 71 | "Complete cycle time" | 
|  | 72 | "The range is from 1 to 170" | 
|  | 73 | "This is the time for all keep alives to arrive"); | 
|  | 74 |  | 
|  | 75 | /* After watchdog device is closed, check force_boot. If: | 
|  | 76 | * force_boot == 0, then force boot on next watchdog interrupt after close, | 
|  | 77 | * force_boot == 1, then force boot immediately when device is closed. | 
|  | 78 | */ | 
|  | 79 | static int force_boot; | 
|  | 80 | module_param(force_boot, int, 0); | 
|  | 81 | MODULE_PARM_DESC(force_boot, | 
|  | 82 | "A value of 1 means that the driver will reboot" | 
|  | 83 | "the system immediately if the /dev/watchdog device is closed" | 
|  | 84 | "A value of 0 means that when /dev/watchdog device is closed" | 
|  | 85 | "the watchdog timer will be refreshed for one more interval" | 
|  | 86 | "of length: timer_set. At the end of this interval, the" | 
|  | 87 | "watchdog timer will reset the system." | 
|  | 88 | ); | 
|  | 89 |  | 
|  | 90 | /* there is only one device in the system now; this can be made into | 
|  | 91 | * an array in the future if we have more than one device */ | 
|  | 92 |  | 
|  | 93 | static struct intel_scu_watchdog_dev watchdog_device; | 
|  | 94 |  | 
|  | 95 | /* Forces restart, if force_reboot is set */ | 
|  | 96 | static void watchdog_fire(void) | 
|  | 97 | { | 
|  | 98 | if (force_boot) { | 
|  | 99 | printk(KERN_CRIT PFX "Initiating system reboot.\n"); | 
|  | 100 | emergency_restart(); | 
|  | 101 | printk(KERN_CRIT PFX "Reboot didn't ?????\n"); | 
|  | 102 | } | 
|  | 103 |  | 
|  | 104 | else { | 
|  | 105 | printk(KERN_CRIT PFX "Immediate Reboot Disabled\n"); | 
|  | 106 | printk(KERN_CRIT PFX | 
|  | 107 | "System will reset when watchdog timer times out!\n"); | 
|  | 108 | } | 
|  | 109 | } | 
|  | 110 |  | 
|  | 111 | static int check_timer_margin(int new_margin) | 
|  | 112 | { | 
|  | 113 | if ((new_margin < MIN_TIME_CYCLE) || | 
|  | 114 | (new_margin > MAX_TIME - timer_set)) { | 
|  | 115 | pr_debug("Watchdog timer: value of new_margin %d is out of the range %d to %d\n", | 
|  | 116 | new_margin, MIN_TIME_CYCLE, MAX_TIME - timer_set); | 
|  | 117 | return -EINVAL; | 
|  | 118 | } | 
|  | 119 | return 0; | 
|  | 120 | } | 
|  | 121 |  | 
|  | 122 | /* | 
|  | 123 | * IPC operations | 
|  | 124 | */ | 
|  | 125 | static int watchdog_set_ipc(int soft_threshold, int threshold) | 
|  | 126 | { | 
|  | 127 | u32	*ipc_wbuf; | 
|  | 128 | u8	 cbuf[16] = { '\0' }; | 
|  | 129 | int	 ipc_ret = 0; | 
|  | 130 |  | 
|  | 131 | ipc_wbuf = (u32 *)&cbuf; | 
|  | 132 | ipc_wbuf[0] = soft_threshold; | 
|  | 133 | ipc_wbuf[1] = threshold; | 
|  | 134 |  | 
|  | 135 | ipc_ret = intel_scu_ipc_command( | 
|  | 136 | IPC_SET_WATCHDOG_TIMER, | 
|  | 137 | 0, | 
|  | 138 | ipc_wbuf, | 
|  | 139 | 2, | 
|  | 140 | NULL, | 
|  | 141 | 0); | 
|  | 142 |  | 
|  | 143 | if (ipc_ret != 0) | 
|  | 144 | pr_err("Error setting SCU watchdog timer: %x\n", ipc_ret); | 
|  | 145 |  | 
|  | 146 | return ipc_ret; | 
|  | 147 | }; | 
|  | 148 |  | 
|  | 149 | /* | 
|  | 150 | *      Intel_SCU operations | 
|  | 151 | */ | 
|  | 152 |  | 
|  | 153 | /* timer interrupt handler */ | 
|  | 154 | static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id) | 
|  | 155 | { | 
|  | 156 | int int_status; | 
|  | 157 | int_status = ioread32(watchdog_device.timer_interrupt_status_addr); | 
|  | 158 |  | 
|  | 159 | pr_debug("Watchdog timer: irq, int_status: %x\n", int_status); | 
|  | 160 |  | 
|  | 161 | if (int_status != 0) | 
|  | 162 | return IRQ_NONE; | 
|  | 163 |  | 
|  | 164 | /* has the timer been started? If not, then this is spurious */ | 
|  | 165 | if (watchdog_device.timer_started == 0) { | 
|  | 166 | pr_debug("Watchdog timer: spurious interrupt received\n"); | 
|  | 167 | return IRQ_HANDLED; | 
|  | 168 | } | 
|  | 169 |  | 
|  | 170 | /* temporarily disable the timer */ | 
|  | 171 | iowrite32(0x00000002, watchdog_device.timer_control_addr); | 
|  | 172 |  | 
|  | 173 | /* set the timer to the threshold */ | 
|  | 174 | iowrite32(watchdog_device.threshold, | 
|  | 175 | watchdog_device.timer_load_count_addr); | 
|  | 176 |  | 
|  | 177 | /* allow the timer to run */ | 
|  | 178 | iowrite32(0x00000003, watchdog_device.timer_control_addr); | 
|  | 179 |  | 
|  | 180 | return IRQ_HANDLED; | 
|  | 181 | } | 
|  | 182 |  | 
|  | 183 | static int intel_scu_keepalive(void) | 
|  | 184 | { | 
|  | 185 |  | 
|  | 186 | /* read eoi register - clears interrupt */ | 
|  | 187 | ioread32(watchdog_device.timer_clear_interrupt_addr); | 
|  | 188 |  | 
|  | 189 | /* temporarily disable the timer */ | 
|  | 190 | iowrite32(0x00000002, watchdog_device.timer_control_addr); | 
|  | 191 |  | 
|  | 192 | /* set the timer to the soft_threshold */ | 
|  | 193 | iowrite32(watchdog_device.soft_threshold, | 
|  | 194 | watchdog_device.timer_load_count_addr); | 
|  | 195 |  | 
|  | 196 | /* allow the timer to run */ | 
|  | 197 | iowrite32(0x00000003, watchdog_device.timer_control_addr); | 
|  | 198 |  | 
|  | 199 | return 0; | 
|  | 200 | } | 
|  | 201 |  | 
|  | 202 | static int intel_scu_stop(void) | 
|  | 203 | { | 
|  | 204 | iowrite32(0, watchdog_device.timer_control_addr); | 
|  | 205 | return 0; | 
|  | 206 | } | 
|  | 207 |  | 
|  | 208 | static int intel_scu_set_heartbeat(u32 t) | 
|  | 209 | { | 
|  | 210 | int			 ipc_ret; | 
|  | 211 | int			 retry_count; | 
|  | 212 | u32			 soft_value; | 
|  | 213 | u32			 hw_pre_value; | 
|  | 214 | u32			 hw_value; | 
|  | 215 |  | 
|  | 216 | watchdog_device.timer_set = t; | 
|  | 217 | watchdog_device.threshold = | 
|  | 218 | timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; | 
|  | 219 | watchdog_device.soft_threshold = | 
|  | 220 | (watchdog_device.timer_set - timer_margin) | 
|  | 221 | * watchdog_device.timer_tbl_ptr->freq_hz; | 
|  | 222 |  | 
|  | 223 | pr_debug("Watchdog timer: set_heartbeat: timer freq is %d\n", | 
|  | 224 | watchdog_device.timer_tbl_ptr->freq_hz); | 
|  | 225 | pr_debug("Watchdog timer: set_heartbeat: timer_set is %x (hex)\n", | 
|  | 226 | watchdog_device.timer_set); | 
|  | 227 | pr_debug("Watchdog timer: set_hearbeat: timer_margin is %x (hex)\n", | 
|  | 228 | timer_margin); | 
|  | 229 | pr_debug("Watchdog timer: set_heartbeat: threshold is %x (hex)\n", | 
|  | 230 | watchdog_device.threshold); | 
|  | 231 | pr_debug("Watchdog timer: set_heartbeat: soft_threshold is %x (hex)\n", | 
|  | 232 | watchdog_device.soft_threshold); | 
|  | 233 |  | 
|  | 234 | /* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */ | 
|  | 235 | /* watchdog timing come out right. */ | 
|  | 236 | watchdog_device.threshold = | 
|  | 237 | watchdog_device.threshold / FREQ_ADJUSTMENT; | 
|  | 238 | watchdog_device.soft_threshold = | 
|  | 239 | watchdog_device.soft_threshold / FREQ_ADJUSTMENT; | 
|  | 240 |  | 
|  | 241 | /* temporarily disable the timer */ | 
|  | 242 | iowrite32(0x00000002, watchdog_device.timer_control_addr); | 
|  | 243 |  | 
|  | 244 | /* send the threshold and soft_threshold via IPC to the processor */ | 
|  | 245 | ipc_ret = watchdog_set_ipc(watchdog_device.soft_threshold, | 
|  | 246 | watchdog_device.threshold); | 
|  | 247 |  | 
|  | 248 | if (ipc_ret != 0) { | 
|  | 249 | /* Make sure the watchdog timer is stopped */ | 
|  | 250 | intel_scu_stop(); | 
|  | 251 | return ipc_ret; | 
|  | 252 | } | 
|  | 253 |  | 
|  | 254 | /* Soft Threshold set loop. Early versions of silicon did */ | 
|  | 255 | /* not always set this count correctly.  This loop checks */ | 
|  | 256 | /* the value and retries if it was not set correctly.     */ | 
|  | 257 |  | 
|  | 258 | retry_count = 0; | 
|  | 259 | soft_value = watchdog_device.soft_threshold & 0xFFFF0000; | 
|  | 260 | do { | 
|  | 261 |  | 
|  | 262 | /* Make sure timer is stopped */ | 
|  | 263 | intel_scu_stop(); | 
|  | 264 |  | 
|  | 265 | if (MAX_RETRY < retry_count++) { | 
|  | 266 | /* Unable to set timer value */ | 
|  | 267 | pr_err("Watchdog timer: Unable to set timer\n"); | 
|  | 268 | return -ENODEV; | 
|  | 269 | } | 
|  | 270 |  | 
|  | 271 | /* set the timer to the soft threshold */ | 
|  | 272 | iowrite32(watchdog_device.soft_threshold, | 
|  | 273 | watchdog_device.timer_load_count_addr); | 
|  | 274 |  | 
|  | 275 | /* read count value before starting timer */ | 
|  | 276 | hw_pre_value = ioread32(watchdog_device.timer_load_count_addr); | 
|  | 277 | hw_pre_value = hw_pre_value & 0xFFFF0000; | 
|  | 278 |  | 
|  | 279 | /* Start the timer */ | 
|  | 280 | iowrite32(0x00000003, watchdog_device.timer_control_addr); | 
|  | 281 |  | 
|  | 282 | /* read the value the time loaded into its count reg */ | 
|  | 283 | hw_value = ioread32(watchdog_device.timer_load_count_addr); | 
|  | 284 | hw_value = hw_value & 0xFFFF0000; | 
|  | 285 |  | 
|  | 286 |  | 
|  | 287 | } while (soft_value != hw_value); | 
|  | 288 |  | 
|  | 289 | watchdog_device.timer_started = 1; | 
|  | 290 |  | 
|  | 291 | return 0; | 
|  | 292 | } | 
|  | 293 |  | 
|  | 294 | /* | 
|  | 295 | * /dev/watchdog handling | 
|  | 296 | */ | 
|  | 297 |  | 
|  | 298 | static int intel_scu_open(struct inode *inode, struct file *file) | 
|  | 299 | { | 
|  | 300 |  | 
|  | 301 | /* Set flag to indicate that watchdog device is open */ | 
|  | 302 | if (test_and_set_bit(0, &watchdog_device.driver_open)) | 
|  | 303 | return -EBUSY; | 
|  | 304 |  | 
|  | 305 | /* Check for reopen of driver. Reopens are not allowed */ | 
|  | 306 | if (watchdog_device.driver_closed) | 
|  | 307 | return -EPERM; | 
|  | 308 |  | 
|  | 309 | return nonseekable_open(inode, file); | 
|  | 310 | } | 
|  | 311 |  | 
|  | 312 | static int intel_scu_release(struct inode *inode, struct file *file) | 
|  | 313 | { | 
|  | 314 | /* | 
|  | 315 | * This watchdog should not be closed, after the timer | 
|  | 316 | * is started with the WDIPC_SETTIMEOUT ioctl | 
|  | 317 | * If force_boot is set watchdog_fire() will cause an | 
|  | 318 | * immediate reset. If force_boot is not set, the watchdog | 
|  | 319 | * timer is refreshed for one more interval. At the end | 
|  | 320 | * of that interval, the watchdog timer will reset the system. | 
|  | 321 | */ | 
|  | 322 |  | 
|  | 323 | if (!test_and_clear_bit(0, &watchdog_device.driver_open)) { | 
|  | 324 | pr_debug("Watchdog timer: intel_scu_release, without open\n"); | 
|  | 325 | return -ENOTTY; | 
|  | 326 | } | 
|  | 327 |  | 
|  | 328 | if (!watchdog_device.timer_started) { | 
|  | 329 | /* Just close, since timer has not been started */ | 
|  | 330 | pr_debug("Watchdog timer: closed, without starting timer\n"); | 
|  | 331 | return 0; | 
|  | 332 | } | 
|  | 333 |  | 
|  | 334 | printk(KERN_CRIT PFX | 
|  | 335 | "Unexpected close of /dev/watchdog!\n"); | 
|  | 336 |  | 
|  | 337 | /* Since the timer was started, prevent future reopens */ | 
|  | 338 | watchdog_device.driver_closed = 1; | 
|  | 339 |  | 
|  | 340 | /* Refresh the timer for one more interval */ | 
|  | 341 | intel_scu_keepalive(); | 
|  | 342 |  | 
|  | 343 | /* Reboot system (if force_boot is set) */ | 
|  | 344 | watchdog_fire(); | 
|  | 345 |  | 
|  | 346 | /* We should only reach this point if force_boot is not set */ | 
|  | 347 | return 0; | 
|  | 348 | } | 
|  | 349 |  | 
|  | 350 | static ssize_t intel_scu_write(struct file *file, | 
|  | 351 | char const *data, | 
|  | 352 | size_t len, | 
|  | 353 | loff_t *ppos) | 
|  | 354 | { | 
|  | 355 |  | 
|  | 356 | if (watchdog_device.timer_started) | 
|  | 357 | /* Watchdog already started, keep it alive */ | 
|  | 358 | intel_scu_keepalive(); | 
|  | 359 | else | 
|  | 360 | /* Start watchdog with timer value set by init */ | 
|  | 361 | intel_scu_set_heartbeat(watchdog_device.timer_set); | 
|  | 362 |  | 
|  | 363 | return len; | 
|  | 364 | } | 
|  | 365 |  | 
|  | 366 | static long intel_scu_ioctl(struct file *file, | 
|  | 367 | unsigned int cmd, | 
|  | 368 | unsigned long arg) | 
|  | 369 | { | 
|  | 370 | void __user *argp = (void __user *)arg; | 
|  | 371 | u32 __user *p = argp; | 
|  | 372 | u32 new_margin; | 
|  | 373 |  | 
|  | 374 |  | 
|  | 375 | static const struct watchdog_info ident = { | 
|  | 376 | .options =          WDIOF_SETTIMEOUT | 
|  | 377 | | WDIOF_KEEPALIVEPING, | 
|  | 378 | .firmware_version = 0,  /* @todo Get from SCU via | 
|  | 379 | ipc_get_scu_fw_version()? */ | 
|  | 380 | .identity =         "Intel_SCU IOH Watchdog"  /* len < 32 */ | 
|  | 381 | }; | 
|  | 382 |  | 
|  | 383 | switch (cmd) { | 
|  | 384 | case WDIOC_GETSUPPORT: | 
|  | 385 | return copy_to_user(argp, | 
|  | 386 | &ident, | 
|  | 387 | sizeof(ident)) ? -EFAULT : 0; | 
|  | 388 | case WDIOC_GETSTATUS: | 
|  | 389 | case WDIOC_GETBOOTSTATUS: | 
|  | 390 | return put_user(0, p); | 
|  | 391 | case WDIOC_KEEPALIVE: | 
|  | 392 | intel_scu_keepalive(); | 
|  | 393 |  | 
|  | 394 | return 0; | 
|  | 395 | case WDIOC_SETTIMEOUT: | 
|  | 396 | if (get_user(new_margin, p)) | 
|  | 397 | return -EFAULT; | 
|  | 398 |  | 
|  | 399 | if (check_timer_margin(new_margin)) | 
|  | 400 | return -EINVAL; | 
|  | 401 |  | 
|  | 402 | if (intel_scu_set_heartbeat(new_margin)) | 
|  | 403 | return -EINVAL; | 
|  | 404 | return 0; | 
|  | 405 | case WDIOC_GETTIMEOUT: | 
|  | 406 | return put_user(watchdog_device.soft_threshold, p); | 
|  | 407 |  | 
|  | 408 | default: | 
|  | 409 | return -ENOTTY; | 
|  | 410 | } | 
|  | 411 | } | 
|  | 412 |  | 
|  | 413 | /* | 
|  | 414 | *      Notifier for system down | 
|  | 415 | */ | 
|  | 416 | static int intel_scu_notify_sys(struct notifier_block *this, | 
|  | 417 | unsigned long code, | 
|  | 418 | void *another_unused) | 
|  | 419 | { | 
|  | 420 | if (code == SYS_DOWN || code == SYS_HALT) | 
|  | 421 | /* Turn off the watchdog timer. */ | 
|  | 422 | intel_scu_stop(); | 
|  | 423 | return NOTIFY_DONE; | 
|  | 424 | } | 
|  | 425 |  | 
|  | 426 | /* | 
|  | 427 | *      Kernel Interfaces | 
|  | 428 | */ | 
|  | 429 | static const struct file_operations intel_scu_fops = { | 
|  | 430 | .owner          = THIS_MODULE, | 
|  | 431 | .llseek         = no_llseek, | 
|  | 432 | .write          = intel_scu_write, | 
|  | 433 | .unlocked_ioctl = intel_scu_ioctl, | 
|  | 434 | .open           = intel_scu_open, | 
|  | 435 | .release        = intel_scu_release, | 
|  | 436 | }; | 
|  | 437 |  | 
|  | 438 | static int __init intel_scu_watchdog_init(void) | 
|  | 439 | { | 
|  | 440 | int ret; | 
|  | 441 | u32 __iomem *tmp_addr; | 
|  | 442 |  | 
|  | 443 | /* | 
|  | 444 | * We don't really need to check this as the SFI timer get will fail | 
|  | 445 | * but if we do so we can exit with a clearer reason and no noise. | 
|  | 446 | * | 
|  | 447 | * If it isn't an intel MID device then it doesn't have this watchdog | 
|  | 448 | */ | 
|  | 449 | if (!mrst_identify_cpu()) | 
|  | 450 | return -ENODEV; | 
|  | 451 |  | 
|  | 452 | /* Check boot parameters to verify that their initial values */ | 
|  | 453 | /* are in range. */ | 
|  | 454 | /* Check value of timer_set boot parameter */ | 
|  | 455 | if ((timer_set < MIN_TIME_CYCLE) || | 
|  | 456 | (timer_set > MAX_TIME - MIN_TIME_CYCLE)) { | 
|  | 457 | pr_err("Watchdog timer: value of timer_set %x (hex) " | 
|  | 458 | "is out of range from %x to %x (hex)\n", | 
|  | 459 | timer_set, MIN_TIME_CYCLE, MAX_TIME - MIN_TIME_CYCLE); | 
|  | 460 | return -EINVAL; | 
|  | 461 | } | 
|  | 462 |  | 
|  | 463 | /* Check value of timer_margin boot parameter */ | 
|  | 464 | if (check_timer_margin(timer_margin)) | 
|  | 465 | return -EINVAL; | 
|  | 466 |  | 
|  | 467 | watchdog_device.timer_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1); | 
|  | 468 |  | 
|  | 469 | if (watchdog_device.timer_tbl_ptr == NULL) { | 
|  | 470 | pr_debug("Watchdog timer - Intel SCU watchdog: timer is not available\n"); | 
|  | 471 | return -ENODEV; | 
|  | 472 | } | 
|  | 473 | /* make sure the timer exists */ | 
|  | 474 | if (watchdog_device.timer_tbl_ptr->phys_addr == 0) { | 
|  | 475 | pr_debug("Watchdog timer - Intel SCU watchdog - timer %d does not have valid physical memory\n", | 
|  | 476 | sfi_mtimer_num); | 
|  | 477 | return -ENODEV; | 
|  | 478 | } | 
|  | 479 |  | 
|  | 480 | if (watchdog_device.timer_tbl_ptr->irq == 0) { | 
|  | 481 | pr_debug("Watchdog timer: timer %d invalid irq\n", | 
|  | 482 | sfi_mtimer_num); | 
|  | 483 | return -ENODEV; | 
|  | 484 | } | 
|  | 485 |  | 
|  | 486 | tmp_addr = ioremap_nocache(watchdog_device.timer_tbl_ptr->phys_addr, | 
|  | 487 | 20); | 
|  | 488 |  | 
|  | 489 | if (tmp_addr == NULL) { | 
|  | 490 | pr_debug("Watchdog timer: timer unable to ioremap\n"); | 
|  | 491 | return -ENOMEM; | 
|  | 492 | } | 
|  | 493 |  | 
|  | 494 | watchdog_device.timer_load_count_addr = tmp_addr++; | 
|  | 495 | watchdog_device.timer_current_value_addr = tmp_addr++; | 
|  | 496 | watchdog_device.timer_control_addr = tmp_addr++; | 
|  | 497 | watchdog_device.timer_clear_interrupt_addr = tmp_addr++; | 
|  | 498 | watchdog_device.timer_interrupt_status_addr = tmp_addr++; | 
|  | 499 |  | 
|  | 500 | /* Set the default time values in device structure */ | 
|  | 501 |  | 
|  | 502 | watchdog_device.timer_set = timer_set; | 
|  | 503 | watchdog_device.threshold = | 
|  | 504 | timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; | 
|  | 505 | watchdog_device.soft_threshold = | 
|  | 506 | (watchdog_device.timer_set - timer_margin) | 
|  | 507 | * watchdog_device.timer_tbl_ptr->freq_hz; | 
|  | 508 |  | 
|  | 509 |  | 
|  | 510 | watchdog_device.intel_scu_notifier.notifier_call = | 
|  | 511 | intel_scu_notify_sys; | 
|  | 512 |  | 
|  | 513 | ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier); | 
|  | 514 | if (ret) { | 
|  | 515 | pr_err("Watchdog timer: cannot register notifier %d)\n", ret); | 
|  | 516 | goto register_reboot_error; | 
|  | 517 | } | 
|  | 518 |  | 
|  | 519 | watchdog_device.miscdev.minor = WATCHDOG_MINOR; | 
|  | 520 | watchdog_device.miscdev.name = "watchdog"; | 
|  | 521 | watchdog_device.miscdev.fops = &intel_scu_fops; | 
|  | 522 |  | 
|  | 523 | ret = misc_register(&watchdog_device.miscdev); | 
|  | 524 | if (ret) { | 
|  | 525 | pr_err("Watchdog timer: cannot register miscdev %d err =%d\n", | 
|  | 526 | WATCHDOG_MINOR, ret); | 
|  | 527 | goto misc_register_error; | 
|  | 528 | } | 
|  | 529 |  | 
|  | 530 | ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq, | 
|  | 531 | watchdog_timer_interrupt, | 
|  | 532 | IRQF_SHARED, "watchdog", | 
|  | 533 | &watchdog_device.timer_load_count_addr); | 
|  | 534 | if (ret) { | 
|  | 535 | pr_err("Watchdog timer: error requesting irq %d\n", ret); | 
|  | 536 | goto request_irq_error; | 
|  | 537 | } | 
|  | 538 | /* Make sure timer is disabled before returning */ | 
|  | 539 | intel_scu_stop(); | 
|  | 540 | return 0; | 
|  | 541 |  | 
|  | 542 | /* error cleanup */ | 
|  | 543 |  | 
|  | 544 | request_irq_error: | 
|  | 545 | misc_deregister(&watchdog_device.miscdev); | 
|  | 546 | misc_register_error: | 
|  | 547 | unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); | 
|  | 548 | register_reboot_error: | 
|  | 549 | intel_scu_stop(); | 
|  | 550 | iounmap(watchdog_device.timer_load_count_addr); | 
|  | 551 | return ret; | 
|  | 552 | } | 
|  | 553 |  | 
|  | 554 | static void __exit intel_scu_watchdog_exit(void) | 
|  | 555 | { | 
|  | 556 |  | 
|  | 557 | misc_deregister(&watchdog_device.miscdev); | 
|  | 558 | unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); | 
|  | 559 | /* disable the timer */ | 
|  | 560 | iowrite32(0x00000002, watchdog_device.timer_control_addr); | 
|  | 561 | iounmap(watchdog_device.timer_load_count_addr); | 
|  | 562 | } | 
|  | 563 |  | 
|  | 564 | late_initcall(intel_scu_watchdog_init); | 
|  | 565 | module_exit(intel_scu_watchdog_exit); | 
|  | 566 |  | 
|  | 567 | MODULE_AUTHOR("Intel Corporation"); | 
|  | 568 | MODULE_DESCRIPTION("Intel SCU Watchdog Device Driver"); | 
|  | 569 | MODULE_LICENSE("GPL"); | 
|  | 570 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | 
|  | 571 | MODULE_VERSION(WDT_VER); |