diff options
author | Atsushi Nemoto <anemo@mba.ocn.ne.jp> | 2007-07-17 07:05:04 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-17 13:23:09 -0400 |
commit | 617780d290bd6eb2b260928c6acff5b7c6084154 (patch) | |
tree | 74738cadd5a79613b6c7fbc0187743a9fe4d2a6b /drivers/rtc | |
parent | caaff562e0ba44a7991ee8322fa4a6891d939757 (diff) |
rtc: watchdog support for rtc-m41t80 driver
Add a watchdog driver interface to rtc-m41t80 driver. This is derived from
works by Alexander Bigga <ab@mycable.de>
Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
Signed-off-by: Alexander Bigga <ab@mycable.de>
Cc: David Brownell <david-b@pacbell.net>
Acked-by: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/rtc/rtc-m41t80.c | 288 |
2 files changed, 295 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index fb9ce745cd30..359323378c55 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -224,6 +224,13 @@ config RTC_DRV_M41T80 | |||
224 | This driver can also be built as a module. If so, the module | 224 | This driver can also be built as a module. If so, the module |
225 | will be called rtc-m41t80. | 225 | will be called rtc-m41t80. |
226 | 226 | ||
227 | config RTC_DRV_M41T80_WDT | ||
228 | bool "ST M41T80 series RTC watchdog timer" | ||
229 | depends on RTC_DRV_M41T80 | ||
230 | help | ||
231 | If you say Y here you will get support for the | ||
232 | watchdog timer in ST M41T80 RTC chips series. | ||
233 | |||
227 | comment "SPI RTC drivers" | 234 | comment "SPI RTC drivers" |
228 | depends on RTC_CLASS && SPI_MASTER | 235 | depends on RTC_CLASS && SPI_MASTER |
229 | 236 | ||
diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index 4875a4444216..80c4a8463065 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c | |||
@@ -20,6 +20,13 @@ | |||
20 | #include <linux/i2c.h> | 20 | #include <linux/i2c.h> |
21 | #include <linux/rtc.h> | 21 | #include <linux/rtc.h> |
22 | #include <linux/bcd.h> | 22 | #include <linux/bcd.h> |
23 | #ifdef CONFIG_RTC_DRV_M41T80_WDT | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | #include <linux/reboot.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/ioctl.h> | ||
29 | #endif | ||
23 | 30 | ||
24 | #define M41T80_REG_SSEC 0 | 31 | #define M41T80_REG_SSEC 0 |
25 | #define M41T80_REG_SEC 1 | 32 | #define M41T80_REG_SEC 1 |
@@ -480,6 +487,268 @@ static int m41t80_sysfs_register(struct device *dev) | |||
480 | } | 487 | } |
481 | #endif | 488 | #endif |
482 | 489 | ||
490 | #ifdef CONFIG_RTC_DRV_M41T80_WDT | ||
491 | /* | ||
492 | ***************************************************************************** | ||
493 | * | ||
494 | * Watchdog Driver | ||
495 | * | ||
496 | ***************************************************************************** | ||
497 | */ | ||
498 | static struct i2c_client *save_client; | ||
499 | |||
500 | /* Default margin */ | ||
501 | #define WD_TIMO 60 /* 1..31 seconds */ | ||
502 | |||
503 | static int wdt_margin = WD_TIMO; | ||
504 | module_param(wdt_margin, int, 0); | ||
505 | MODULE_PARM_DESC(wdt_margin, "Watchdog timeout in seconds (default 60s)"); | ||
506 | |||
507 | static unsigned long wdt_is_open; | ||
508 | static int boot_flag; | ||
509 | |||
510 | /** | ||
511 | * wdt_ping: | ||
512 | * | ||
513 | * Reload counter one with the watchdog timeout. We don't bother reloading | ||
514 | * the cascade counter. | ||
515 | */ | ||
516 | static void wdt_ping(void) | ||
517 | { | ||
518 | unsigned char i2c_data[2]; | ||
519 | struct i2c_msg msgs1[1] = { | ||
520 | { | ||
521 | .addr = save_client->addr, | ||
522 | .flags = 0, | ||
523 | .len = 2, | ||
524 | .buf = i2c_data, | ||
525 | }, | ||
526 | }; | ||
527 | i2c_data[0] = 0x09; /* watchdog register */ | ||
528 | |||
529 | if (wdt_margin > 31) | ||
530 | i2c_data[1] = (wdt_margin & 0xFC) | 0x83; /* resolution = 4s */ | ||
531 | else | ||
532 | /* | ||
533 | * WDS = 1 (0x80), mulitplier = WD_TIMO, resolution = 1s (0x02) | ||
534 | */ | ||
535 | i2c_data[1] = wdt_margin<<2 | 0x82; | ||
536 | |||
537 | i2c_transfer(save_client->adapter, msgs1, 1); | ||
538 | } | ||
539 | |||
540 | /** | ||
541 | * wdt_disable: | ||
542 | * | ||
543 | * disables watchdog. | ||
544 | */ | ||
545 | static void wdt_disable(void) | ||
546 | { | ||
547 | unsigned char i2c_data[2], i2c_buf[0x10]; | ||
548 | struct i2c_msg msgs0[2] = { | ||
549 | { | ||
550 | .addr = save_client->addr, | ||
551 | .flags = 0, | ||
552 | .len = 1, | ||
553 | .buf = i2c_data, | ||
554 | }, | ||
555 | { | ||
556 | .addr = save_client->addr, | ||
557 | .flags = I2C_M_RD, | ||
558 | .len = 1, | ||
559 | .buf = i2c_buf, | ||
560 | }, | ||
561 | }; | ||
562 | struct i2c_msg msgs1[1] = { | ||
563 | { | ||
564 | .addr = save_client->addr, | ||
565 | .flags = 0, | ||
566 | .len = 2, | ||
567 | .buf = i2c_data, | ||
568 | }, | ||
569 | }; | ||
570 | |||
571 | i2c_data[0] = 0x09; | ||
572 | i2c_transfer(save_client->adapter, msgs0, 2); | ||
573 | |||
574 | i2c_data[0] = 0x09; | ||
575 | i2c_data[1] = 0x00; | ||
576 | i2c_transfer(save_client->adapter, msgs1, 1); | ||
577 | } | ||
578 | |||
579 | /** | ||
580 | * wdt_write: | ||
581 | * @file: file handle to the watchdog | ||
582 | * @buf: buffer to write (unused as data does not matter here | ||
583 | * @count: count of bytes | ||
584 | * @ppos: pointer to the position to write. No seeks allowed | ||
585 | * | ||
586 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
587 | * write of data will do, as we we don't define content meaning. | ||
588 | */ | ||
589 | static ssize_t wdt_write(struct file *file, const char __user *buf, | ||
590 | size_t count, loff_t *ppos) | ||
591 | { | ||
592 | /* Can't seek (pwrite) on this device | ||
593 | if (ppos != &file->f_pos) | ||
594 | return -ESPIPE; | ||
595 | */ | ||
596 | if (count) { | ||
597 | wdt_ping(); | ||
598 | return 1; | ||
599 | } | ||
600 | return 0; | ||
601 | } | ||
602 | |||
603 | static ssize_t wdt_read(struct file *file, char __user *buf, | ||
604 | size_t count, loff_t *ppos) | ||
605 | { | ||
606 | return 0; | ||
607 | } | ||
608 | |||
609 | /** | ||
610 | * wdt_ioctl: | ||
611 | * @inode: inode of the device | ||
612 | * @file: file handle to the device | ||
613 | * @cmd: watchdog command | ||
614 | * @arg: argument pointer | ||
615 | * | ||
616 | * The watchdog API defines a common set of functions for all watchdogs | ||
617 | * according to their available features. We only actually usefully support | ||
618 | * querying capabilities and current status. | ||
619 | */ | ||
620 | static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
621 | unsigned long arg) | ||
622 | { | ||
623 | int new_margin, rv; | ||
624 | static struct watchdog_info ident = { | ||
625 | .options = WDIOF_POWERUNDER | WDIOF_KEEPALIVEPING | | ||
626 | WDIOF_SETTIMEOUT, | ||
627 | .firmware_version = 1, | ||
628 | .identity = "M41T80 WTD" | ||
629 | }; | ||
630 | |||
631 | switch (cmd) { | ||
632 | case WDIOC_GETSUPPORT: | ||
633 | return copy_to_user((struct watchdog_info __user *)arg, &ident, | ||
634 | sizeof(ident)) ? -EFAULT : 0; | ||
635 | |||
636 | case WDIOC_GETSTATUS: | ||
637 | case WDIOC_GETBOOTSTATUS: | ||
638 | return put_user(boot_flag, (int __user *)arg); | ||
639 | case WDIOC_KEEPALIVE: | ||
640 | wdt_ping(); | ||
641 | return 0; | ||
642 | case WDIOC_SETTIMEOUT: | ||
643 | if (get_user(new_margin, (int __user *)arg)) | ||
644 | return -EFAULT; | ||
645 | /* Arbitrary, can't find the card's limits */ | ||
646 | if (new_margin < 1 || new_margin > 124) | ||
647 | return -EINVAL; | ||
648 | wdt_margin = new_margin; | ||
649 | wdt_ping(); | ||
650 | /* Fall */ | ||
651 | case WDIOC_GETTIMEOUT: | ||
652 | return put_user(wdt_margin, (int __user *)arg); | ||
653 | |||
654 | case WDIOC_SETOPTIONS: | ||
655 | if (copy_from_user(&rv, (int __user *)arg, sizeof(int))) | ||
656 | return -EFAULT; | ||
657 | |||
658 | if (rv & WDIOS_DISABLECARD) { | ||
659 | printk(KERN_INFO | ||
660 | "rtc-m41t80: disable watchdog\n"); | ||
661 | wdt_disable(); | ||
662 | } | ||
663 | |||
664 | if (rv & WDIOS_ENABLECARD) { | ||
665 | printk(KERN_INFO | ||
666 | "rtc-m41t80: enable watchdog\n"); | ||
667 | wdt_ping(); | ||
668 | } | ||
669 | |||
670 | return -EINVAL; | ||
671 | } | ||
672 | return -ENOTTY; | ||
673 | } | ||
674 | |||
675 | /** | ||
676 | * wdt_open: | ||
677 | * @inode: inode of device | ||
678 | * @file: file handle to device | ||
679 | * | ||
680 | */ | ||
681 | static int wdt_open(struct inode *inode, struct file *file) | ||
682 | { | ||
683 | if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) { | ||
684 | if (test_and_set_bit(0, &wdt_is_open)) | ||
685 | return -EBUSY; | ||
686 | /* | ||
687 | * Activate | ||
688 | */ | ||
689 | wdt_is_open = 1; | ||
690 | return 0; | ||
691 | } | ||
692 | return -ENODEV; | ||
693 | } | ||
694 | |||
695 | /** | ||
696 | * wdt_close: | ||
697 | * @inode: inode to board | ||
698 | * @file: file handle to board | ||
699 | * | ||
700 | */ | ||
701 | static int wdt_release(struct inode *inode, struct file *file) | ||
702 | { | ||
703 | if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) | ||
704 | clear_bit(0, &wdt_is_open); | ||
705 | return 0; | ||
706 | } | ||
707 | |||
708 | /** | ||
709 | * notify_sys: | ||
710 | * @this: our notifier block | ||
711 | * @code: the event being reported | ||
712 | * @unused: unused | ||
713 | * | ||
714 | * Our notifier is called on system shutdowns. We want to turn the card | ||
715 | * off at reboot otherwise the machine will reboot again during memory | ||
716 | * test or worse yet during the following fsck. This would suck, in fact | ||
717 | * trust me - if it happens it does suck. | ||
718 | */ | ||
719 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
720 | void *unused) | ||
721 | { | ||
722 | if (code == SYS_DOWN || code == SYS_HALT) | ||
723 | /* Disable Watchdog */ | ||
724 | wdt_disable(); | ||
725 | return NOTIFY_DONE; | ||
726 | } | ||
727 | |||
728 | static const struct file_operations wdt_fops = { | ||
729 | .owner = THIS_MODULE, | ||
730 | .read = wdt_read, | ||
731 | .ioctl = wdt_ioctl, | ||
732 | .write = wdt_write, | ||
733 | .open = wdt_open, | ||
734 | .release = wdt_release, | ||
735 | }; | ||
736 | |||
737 | static struct miscdevice wdt_dev = { | ||
738 | .minor = WATCHDOG_MINOR, | ||
739 | .name = "watchdog", | ||
740 | .fops = &wdt_fops, | ||
741 | }; | ||
742 | |||
743 | /* | ||
744 | * The WDT card needs to learn about soft shutdowns in order to | ||
745 | * turn the timebomb registers off. | ||
746 | */ | ||
747 | static struct notifier_block wdt_notifier = { | ||
748 | .notifier_call = wdt_notify_sys, | ||
749 | }; | ||
750 | #endif /* CONFIG_RTC_DRV_M41T80_WDT */ | ||
751 | |||
483 | /* | 752 | /* |
484 | ***************************************************************************** | 753 | ***************************************************************************** |
485 | * | 754 | * |
@@ -572,6 +841,19 @@ static int m41t80_probe(struct i2c_client *client) | |||
572 | if (rc) | 841 | if (rc) |
573 | goto exit; | 842 | goto exit; |
574 | 843 | ||
844 | #ifdef CONFIG_RTC_DRV_M41T80_WDT | ||
845 | if (chip->features & M41T80_FEATURE_HT) { | ||
846 | rc = misc_register(&wdt_dev); | ||
847 | if (rc) | ||
848 | goto exit; | ||
849 | rc = register_reboot_notifier(&wdt_notifier); | ||
850 | if (rc) { | ||
851 | misc_deregister(&wdt_dev); | ||
852 | goto exit; | ||
853 | } | ||
854 | save_client = client; | ||
855 | } | ||
856 | #endif | ||
575 | return 0; | 857 | return 0; |
576 | 858 | ||
577 | st_err: | 859 | st_err: |
@@ -595,6 +877,12 @@ static int m41t80_remove(struct i2c_client *client) | |||
595 | struct m41t80_data *clientdata = i2c_get_clientdata(client); | 877 | struct m41t80_data *clientdata = i2c_get_clientdata(client); |
596 | struct rtc_device *rtc = clientdata->rtc; | 878 | struct rtc_device *rtc = clientdata->rtc; |
597 | 879 | ||
880 | #ifdef CONFIG_RTC_DRV_M41T80_WDT | ||
881 | if (clientdata->chip->features & M41T80_FEATURE_HT) { | ||
882 | misc_deregister(&wdt_dev); | ||
883 | unregister_reboot_notifier(&wdt_notifier); | ||
884 | } | ||
885 | #endif | ||
598 | if (rtc) | 886 | if (rtc) |
599 | rtc_device_unregister(rtc); | 887 | rtc_device_unregister(rtc); |
600 | kfree(clientdata); | 888 | kfree(clientdata); |