diff options
author | Hans de Goede <hdegoede@redhat.com> | 2012-05-22 05:40:24 -0400 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2012-05-30 01:55:38 -0400 |
commit | fb551405c0f8e15d6fc7ae6e16a5e15382f8b8ac (patch) | |
tree | 79b8e6dc8ee5180e74a368748f2fac94f525f4e8 /drivers/hwmon/sch56xx-common.c | |
parent | e907df32725204d6d2cb79b872529911c8eadcdf (diff) |
watchdog: sch56xx: Use watchdog core
Convert sch56xx drivers to the generic watchdog core.
Note this patch depends on the "watchdog: Add multiple device support" patch
from Alan Cox.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/hwmon/sch56xx-common.c')
-rw-r--r-- | drivers/hwmon/sch56xx-common.c | 371 |
1 files changed, 58 insertions, 313 deletions
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index ce52fc57d41d..419a8e8f5191 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c | |||
@@ -66,15 +66,9 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |||
66 | 66 | ||
67 | struct sch56xx_watchdog_data { | 67 | struct sch56xx_watchdog_data { |
68 | u16 addr; | 68 | u16 addr; |
69 | u32 revision; | ||
70 | struct mutex *io_lock; | 69 | struct mutex *io_lock; |
71 | struct mutex watchdog_lock; | 70 | struct watchdog_info wdinfo; |
72 | struct list_head list; /* member of the watchdog_data_list */ | 71 | struct watchdog_device wddev; |
73 | struct kref kref; | ||
74 | struct miscdevice watchdog_miscdev; | ||
75 | unsigned long watchdog_is_open; | ||
76 | char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ | ||
77 | char watchdog_expect_close; | ||
78 | u8 watchdog_preset; | 72 | u8 watchdog_preset; |
79 | u8 watchdog_control; | 73 | u8 watchdog_control; |
80 | u8 watchdog_output_enable; | 74 | u8 watchdog_output_enable; |
@@ -82,15 +76,6 @@ struct sch56xx_watchdog_data { | |||
82 | 76 | ||
83 | static struct platform_device *sch56xx_pdev; | 77 | static struct platform_device *sch56xx_pdev; |
84 | 78 | ||
85 | /* | ||
86 | * Somewhat ugly :( global data pointer list with all sch56xx devices, so that | ||
87 | * we can find our device data as when using misc_register there is no other | ||
88 | * method to get to ones device data from the open fop. | ||
89 | */ | ||
90 | static LIST_HEAD(watchdog_data_list); | ||
91 | /* Note this lock not only protect list access, but also data.kref access */ | ||
92 | static DEFINE_MUTEX(watchdog_data_mutex); | ||
93 | |||
94 | /* Super I/O functions */ | 79 | /* Super I/O functions */ |
95 | static inline int superio_inb(int base, int reg) | 80 | static inline int superio_inb(int base, int reg) |
96 | { | 81 | { |
@@ -272,22 +257,13 @@ EXPORT_SYMBOL(sch56xx_read_virtual_reg12); | |||
272 | * Watchdog routines | 257 | * Watchdog routines |
273 | */ | 258 | */ |
274 | 259 | ||
275 | /* | 260 | static int watchdog_set_timeout(struct watchdog_device *wddev, |
276 | * Release our data struct when the platform device has been released *and* | 261 | unsigned int timeout) |
277 | * all references to our watchdog device are released. | ||
278 | */ | ||
279 | static void sch56xx_watchdog_release_resources(struct kref *r) | ||
280 | { | ||
281 | struct sch56xx_watchdog_data *data = | ||
282 | container_of(r, struct sch56xx_watchdog_data, kref); | ||
283 | kfree(data); | ||
284 | } | ||
285 | |||
286 | static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, | ||
287 | int timeout) | ||
288 | { | 262 | { |
289 | int ret, resolution; | 263 | struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); |
264 | unsigned int resolution; | ||
290 | u8 control; | 265 | u8 control; |
266 | int ret; | ||
291 | 267 | ||
292 | /* 1 second or 60 second resolution? */ | 268 | /* 1 second or 60 second resolution? */ |
293 | if (timeout <= 255) | 269 | if (timeout <= 255) |
@@ -298,12 +274,6 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, | |||
298 | if (timeout < resolution || timeout > (resolution * 255)) | 274 | if (timeout < resolution || timeout > (resolution * 255)) |
299 | return -EINVAL; | 275 | return -EINVAL; |
300 | 276 | ||
301 | mutex_lock(&data->watchdog_lock); | ||
302 | if (!data->addr) { | ||
303 | ret = -ENODEV; | ||
304 | goto leave; | ||
305 | } | ||
306 | |||
307 | if (resolution == 1) | 277 | if (resolution == 1) |
308 | control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC; | 278 | control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC; |
309 | else | 279 | else |
@@ -316,7 +286,7 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, | |||
316 | control); | 286 | control); |
317 | mutex_unlock(data->io_lock); | 287 | mutex_unlock(data->io_lock); |
318 | if (ret) | 288 | if (ret) |
319 | goto leave; | 289 | return ret; |
320 | 290 | ||
321 | data->watchdog_control = control; | 291 | data->watchdog_control = control; |
322 | } | 292 | } |
@@ -326,38 +296,17 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, | |||
326 | * the watchdog countdown. | 296 | * the watchdog countdown. |
327 | */ | 297 | */ |
328 | data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); | 298 | data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); |
299 | wddev->timeout = data->watchdog_preset * resolution; | ||
329 | 300 | ||
330 | ret = data->watchdog_preset * resolution; | 301 | return 0; |
331 | leave: | ||
332 | mutex_unlock(&data->watchdog_lock); | ||
333 | return ret; | ||
334 | } | ||
335 | |||
336 | static int watchdog_get_timeout(struct sch56xx_watchdog_data *data) | ||
337 | { | ||
338 | int timeout; | ||
339 | |||
340 | mutex_lock(&data->watchdog_lock); | ||
341 | if (data->watchdog_control & SCH56XX_WDOG_TIME_BASE_SEC) | ||
342 | timeout = data->watchdog_preset; | ||
343 | else | ||
344 | timeout = data->watchdog_preset * 60; | ||
345 | mutex_unlock(&data->watchdog_lock); | ||
346 | |||
347 | return timeout; | ||
348 | } | 302 | } |
349 | 303 | ||
350 | static int watchdog_start(struct sch56xx_watchdog_data *data) | 304 | static int watchdog_start(struct watchdog_device *wddev) |
351 | { | 305 | { |
306 | struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); | ||
352 | int ret; | 307 | int ret; |
353 | u8 val; | 308 | u8 val; |
354 | 309 | ||
355 | mutex_lock(&data->watchdog_lock); | ||
356 | if (!data->addr) { | ||
357 | ret = -ENODEV; | ||
358 | goto leave_unlock_watchdog; | ||
359 | } | ||
360 | |||
361 | /* | 310 | /* |
362 | * The sch56xx's watchdog cannot really be started / stopped | 311 | * The sch56xx's watchdog cannot really be started / stopped |
363 | * it is always running, but we can avoid the timer expiring | 312 | * it is always running, but we can avoid the timer expiring |
@@ -405,39 +354,29 @@ static int watchdog_start(struct sch56xx_watchdog_data *data) | |||
405 | 354 | ||
406 | leave: | 355 | leave: |
407 | mutex_unlock(data->io_lock); | 356 | mutex_unlock(data->io_lock); |
408 | leave_unlock_watchdog: | ||
409 | mutex_unlock(&data->watchdog_lock); | ||
410 | return ret; | 357 | return ret; |
411 | } | 358 | } |
412 | 359 | ||
413 | static int watchdog_trigger(struct sch56xx_watchdog_data *data) | 360 | static int watchdog_trigger(struct watchdog_device *wddev) |
414 | { | 361 | { |
362 | struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); | ||
415 | int ret; | 363 | int ret; |
416 | 364 | ||
417 | mutex_lock(&data->watchdog_lock); | ||
418 | if (!data->addr) { | ||
419 | ret = -ENODEV; | ||
420 | goto leave; | ||
421 | } | ||
422 | |||
423 | /* Reset the watchdog countdown counter */ | 365 | /* Reset the watchdog countdown counter */ |
424 | mutex_lock(data->io_lock); | 366 | mutex_lock(data->io_lock); |
425 | ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, | 367 | ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, |
426 | data->watchdog_preset); | 368 | data->watchdog_preset); |
427 | mutex_unlock(data->io_lock); | 369 | mutex_unlock(data->io_lock); |
428 | leave: | 370 | |
429 | mutex_unlock(&data->watchdog_lock); | ||
430 | return ret; | 371 | return ret; |
431 | } | 372 | } |
432 | 373 | ||
433 | static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data) | 374 | static int watchdog_stop(struct watchdog_device *wddev) |
434 | { | 375 | { |
376 | struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); | ||
435 | int ret = 0; | 377 | int ret = 0; |
436 | u8 val; | 378 | u8 val; |
437 | 379 | ||
438 | if (!data->addr) | ||
439 | return -ENODEV; | ||
440 | |||
441 | if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) { | 380 | if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) { |
442 | val = data->watchdog_output_enable & | 381 | val = data->watchdog_output_enable & |
443 | ~SCH56XX_WDOG_OUTPUT_ENABLE; | 382 | ~SCH56XX_WDOG_OUTPUT_ENABLE; |
@@ -455,184 +394,19 @@ static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data) | |||
455 | return ret; | 394 | return ret; |
456 | } | 395 | } |
457 | 396 | ||
458 | static int watchdog_stop(struct sch56xx_watchdog_data *data) | 397 | static const struct watchdog_ops watchdog_ops = { |
459 | { | 398 | .owner = THIS_MODULE, |
460 | int ret; | 399 | .start = watchdog_start, |
461 | 400 | .stop = watchdog_stop, | |
462 | mutex_lock(&data->watchdog_lock); | 401 | .ping = watchdog_trigger, |
463 | ret = watchdog_stop_unlocked(data); | 402 | .set_timeout = watchdog_set_timeout, |
464 | mutex_unlock(&data->watchdog_lock); | ||
465 | |||
466 | return ret; | ||
467 | } | ||
468 | |||
469 | static int watchdog_release(struct inode *inode, struct file *filp) | ||
470 | { | ||
471 | struct sch56xx_watchdog_data *data = filp->private_data; | ||
472 | |||
473 | if (data->watchdog_expect_close) { | ||
474 | watchdog_stop(data); | ||
475 | data->watchdog_expect_close = 0; | ||
476 | } else { | ||
477 | watchdog_trigger(data); | ||
478 | pr_crit("unexpected close, not stopping watchdog!\n"); | ||
479 | } | ||
480 | |||
481 | clear_bit(0, &data->watchdog_is_open); | ||
482 | |||
483 | mutex_lock(&watchdog_data_mutex); | ||
484 | kref_put(&data->kref, sch56xx_watchdog_release_resources); | ||
485 | mutex_unlock(&watchdog_data_mutex); | ||
486 | |||
487 | return 0; | ||
488 | } | ||
489 | |||
490 | static int watchdog_open(struct inode *inode, struct file *filp) | ||
491 | { | ||
492 | struct sch56xx_watchdog_data *pos, *data = NULL; | ||
493 | int ret, watchdog_is_open; | ||
494 | |||
495 | /* | ||
496 | * We get called from drivers/char/misc.c with misc_mtx hold, and we | ||
497 | * call misc_register() from sch56xx_watchdog_probe() with | ||
498 | * watchdog_data_mutex hold, as misc_register() takes the misc_mtx | ||
499 | * lock, this is a possible deadlock, so we use mutex_trylock here. | ||
500 | */ | ||
501 | if (!mutex_trylock(&watchdog_data_mutex)) | ||
502 | return -ERESTARTSYS; | ||
503 | list_for_each_entry(pos, &watchdog_data_list, list) { | ||
504 | if (pos->watchdog_miscdev.minor == iminor(inode)) { | ||
505 | data = pos; | ||
506 | break; | ||
507 | } | ||
508 | } | ||
509 | /* Note we can never not have found data, so we don't check for this */ | ||
510 | watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); | ||
511 | if (!watchdog_is_open) | ||
512 | kref_get(&data->kref); | ||
513 | mutex_unlock(&watchdog_data_mutex); | ||
514 | |||
515 | if (watchdog_is_open) | ||
516 | return -EBUSY; | ||
517 | |||
518 | filp->private_data = data; | ||
519 | |||
520 | /* Start the watchdog */ | ||
521 | ret = watchdog_start(data); | ||
522 | if (ret) { | ||
523 | watchdog_release(inode, filp); | ||
524 | return ret; | ||
525 | } | ||
526 | |||
527 | return nonseekable_open(inode, filp); | ||
528 | } | ||
529 | |||
530 | static ssize_t watchdog_write(struct file *filp, const char __user *buf, | ||
531 | size_t count, loff_t *offset) | ||
532 | { | ||
533 | int ret; | ||
534 | struct sch56xx_watchdog_data *data = filp->private_data; | ||
535 | |||
536 | if (count) { | ||
537 | if (!nowayout) { | ||
538 | size_t i; | ||
539 | |||
540 | /* Clear it in case it was set with a previous write */ | ||
541 | data->watchdog_expect_close = 0; | ||
542 | |||
543 | for (i = 0; i != count; i++) { | ||
544 | char c; | ||
545 | if (get_user(c, buf + i)) | ||
546 | return -EFAULT; | ||
547 | if (c == 'V') | ||
548 | data->watchdog_expect_close = 1; | ||
549 | } | ||
550 | } | ||
551 | ret = watchdog_trigger(data); | ||
552 | if (ret) | ||
553 | return ret; | ||
554 | } | ||
555 | return count; | ||
556 | } | ||
557 | |||
558 | static long watchdog_ioctl(struct file *filp, unsigned int cmd, | ||
559 | unsigned long arg) | ||
560 | { | ||
561 | struct watchdog_info ident = { | ||
562 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, | ||
563 | .identity = "sch56xx watchdog" | ||
564 | }; | ||
565 | int i, ret = 0; | ||
566 | struct sch56xx_watchdog_data *data = filp->private_data; | ||
567 | |||
568 | switch (cmd) { | ||
569 | case WDIOC_GETSUPPORT: | ||
570 | ident.firmware_version = data->revision; | ||
571 | if (!nowayout) | ||
572 | ident.options |= WDIOF_MAGICCLOSE; | ||
573 | if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) | ||
574 | ret = -EFAULT; | ||
575 | break; | ||
576 | |||
577 | case WDIOC_GETSTATUS: | ||
578 | case WDIOC_GETBOOTSTATUS: | ||
579 | ret = put_user(0, (int __user *)arg); | ||
580 | break; | ||
581 | |||
582 | case WDIOC_KEEPALIVE: | ||
583 | ret = watchdog_trigger(data); | ||
584 | break; | ||
585 | |||
586 | case WDIOC_GETTIMEOUT: | ||
587 | i = watchdog_get_timeout(data); | ||
588 | ret = put_user(i, (int __user *)arg); | ||
589 | break; | ||
590 | |||
591 | case WDIOC_SETTIMEOUT: | ||
592 | if (get_user(i, (int __user *)arg)) { | ||
593 | ret = -EFAULT; | ||
594 | break; | ||
595 | } | ||
596 | ret = watchdog_set_timeout(data, i); | ||
597 | if (ret >= 0) | ||
598 | ret = put_user(ret, (int __user *)arg); | ||
599 | break; | ||
600 | |||
601 | case WDIOC_SETOPTIONS: | ||
602 | if (get_user(i, (int __user *)arg)) { | ||
603 | ret = -EFAULT; | ||
604 | break; | ||
605 | } | ||
606 | |||
607 | if (i & WDIOS_DISABLECARD) | ||
608 | ret = watchdog_stop(data); | ||
609 | else if (i & WDIOS_ENABLECARD) | ||
610 | ret = watchdog_trigger(data); | ||
611 | else | ||
612 | ret = -EINVAL; | ||
613 | break; | ||
614 | |||
615 | default: | ||
616 | ret = -ENOTTY; | ||
617 | } | ||
618 | return ret; | ||
619 | } | ||
620 | |||
621 | static const struct file_operations watchdog_fops = { | ||
622 | .owner = THIS_MODULE, | ||
623 | .llseek = no_llseek, | ||
624 | .open = watchdog_open, | ||
625 | .release = watchdog_release, | ||
626 | .write = watchdog_write, | ||
627 | .unlocked_ioctl = watchdog_ioctl, | ||
628 | }; | 403 | }; |
629 | 404 | ||
630 | struct sch56xx_watchdog_data *sch56xx_watchdog_register( | 405 | struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent, |
631 | u16 addr, u32 revision, struct mutex *io_lock, int check_enabled) | 406 | u16 addr, u32 revision, struct mutex *io_lock, int check_enabled) |
632 | { | 407 | { |
633 | struct sch56xx_watchdog_data *data; | 408 | struct sch56xx_watchdog_data *data; |
634 | int i, err, control, output_enable; | 409 | int err, control, output_enable; |
635 | const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; | ||
636 | 410 | ||
637 | /* Cache the watchdog registers */ | 411 | /* Cache the watchdog registers */ |
638 | mutex_lock(io_lock); | 412 | mutex_lock(io_lock); |
@@ -656,82 +430,53 @@ struct sch56xx_watchdog_data *sch56xx_watchdog_register( | |||
656 | return NULL; | 430 | return NULL; |
657 | 431 | ||
658 | data->addr = addr; | 432 | data->addr = addr; |
659 | data->revision = revision; | ||
660 | data->io_lock = io_lock; | 433 | data->io_lock = io_lock; |
661 | data->watchdog_control = control; | ||
662 | data->watchdog_output_enable = output_enable; | ||
663 | mutex_init(&data->watchdog_lock); | ||
664 | INIT_LIST_HEAD(&data->list); | ||
665 | kref_init(&data->kref); | ||
666 | |||
667 | err = watchdog_set_timeout(data, 60); | ||
668 | if (err < 0) | ||
669 | goto error; | ||
670 | 434 | ||
671 | /* | 435 | strlcpy(data->wdinfo.identity, "sch56xx watchdog", |
672 | * We take the data_mutex lock early so that watchdog_open() cannot | 436 | sizeof(data->wdinfo.identity)); |
673 | * run when misc_register() has completed, but we've not yet added | 437 | data->wdinfo.firmware_version = revision; |
674 | * our data to the watchdog_data_list. | 438 | data->wdinfo.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT; |
675 | */ | 439 | if (!nowayout) |
676 | mutex_lock(&watchdog_data_mutex); | 440 | data->wdinfo.options |= WDIOF_MAGICCLOSE; |
677 | for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { | 441 | |
678 | /* Register our watchdog part */ | 442 | data->wddev.info = &data->wdinfo; |
679 | snprintf(data->watchdog_name, sizeof(data->watchdog_name), | 443 | data->wddev.ops = &watchdog_ops; |
680 | "watchdog%c", (i == 0) ? '\0' : ('0' + i)); | 444 | data->wddev.parent = parent; |
681 | data->watchdog_miscdev.name = data->watchdog_name; | 445 | data->wddev.timeout = 60; |
682 | data->watchdog_miscdev.fops = &watchdog_fops; | 446 | data->wddev.min_timeout = 1; |
683 | data->watchdog_miscdev.minor = watchdog_minors[i]; | 447 | data->wddev.max_timeout = 255 * 60; |
684 | err = misc_register(&data->watchdog_miscdev); | 448 | if (nowayout) |
685 | if (err == -EBUSY) | 449 | data->wddev.status |= WDOG_NO_WAY_OUT; |
686 | continue; | 450 | if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) |
687 | if (err) | 451 | data->wddev.status |= WDOG_ACTIVE; |
688 | break; | 452 | |
453 | /* Since the watchdog uses a downcounter there is no register to read | ||
454 | the BIOS set timeout from (if any was set at all) -> | ||
455 | Choose a preset which will give us a 1 minute timeout */ | ||
456 | if (control & SCH56XX_WDOG_TIME_BASE_SEC) | ||
457 | data->watchdog_preset = 60; /* seconds */ | ||
458 | else | ||
459 | data->watchdog_preset = 1; /* minute */ | ||
689 | 460 | ||
690 | list_add(&data->list, &watchdog_data_list); | 461 | data->watchdog_control = control; |
691 | pr_info("Registered /dev/%s chardev major 10, minor: %d\n", | 462 | data->watchdog_output_enable = output_enable; |
692 | data->watchdog_name, watchdog_minors[i]); | ||
693 | break; | ||
694 | } | ||
695 | mutex_unlock(&watchdog_data_mutex); | ||
696 | 463 | ||
464 | watchdog_set_drvdata(&data->wddev, data); | ||
465 | err = watchdog_register_device(&data->wddev); | ||
697 | if (err) { | 466 | if (err) { |
698 | pr_err("Registering watchdog chardev: %d\n", err); | 467 | pr_err("Registering watchdog chardev: %d\n", err); |
699 | goto error; | 468 | kfree(data); |
700 | } | 469 | return NULL; |
701 | if (i == ARRAY_SIZE(watchdog_minors)) { | ||
702 | pr_warn("Couldn't register watchdog (no free minor)\n"); | ||
703 | goto error; | ||
704 | } | 470 | } |
705 | 471 | ||
706 | return data; | 472 | return data; |
707 | |||
708 | error: | ||
709 | kfree(data); | ||
710 | return NULL; | ||
711 | } | 473 | } |
712 | EXPORT_SYMBOL(sch56xx_watchdog_register); | 474 | EXPORT_SYMBOL(sch56xx_watchdog_register); |
713 | 475 | ||
714 | void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data) | 476 | void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data) |
715 | { | 477 | { |
716 | mutex_lock(&watchdog_data_mutex); | 478 | watchdog_unregister_device(&data->wddev); |
717 | misc_deregister(&data->watchdog_miscdev); | 479 | kfree(data); |
718 | list_del(&data->list); | ||
719 | mutex_unlock(&watchdog_data_mutex); | ||
720 | |||
721 | mutex_lock(&data->watchdog_lock); | ||
722 | if (data->watchdog_is_open) { | ||
723 | pr_warn("platform device unregistered with watchdog " | ||
724 | "open! Stopping watchdog.\n"); | ||
725 | watchdog_stop_unlocked(data); | ||
726 | } | ||
727 | /* Tell the wdog start/stop/trigger functions our dev is gone */ | ||
728 | data->addr = 0; | ||
729 | data->io_lock = NULL; | ||
730 | mutex_unlock(&data->watchdog_lock); | ||
731 | |||
732 | mutex_lock(&watchdog_data_mutex); | ||
733 | kref_put(&data->kref, sch56xx_watchdog_release_resources); | ||
734 | mutex_unlock(&watchdog_data_mutex); | ||
735 | } | 480 | } |
736 | EXPORT_SYMBOL(sch56xx_watchdog_unregister); | 481 | EXPORT_SYMBOL(sch56xx_watchdog_unregister); |
737 | 482 | ||