diff options
-rw-r--r-- | arch/m68k/include/asm/m548xgpt.h | 2 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 56 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 6 | ||||
-rw-r--r-- | drivers/watchdog/alim1535_wdt.c | 2 | ||||
-rw-r--r-- | drivers/watchdog/alim7101_wdt.c | 2 | ||||
-rw-r--r-- | drivers/watchdog/ath79_wdt.c | 305 | ||||
-rw-r--r-- | drivers/watchdog/booke_wdt.c | 35 | ||||
-rw-r--r-- | drivers/watchdog/f71808e_wdt.c | 78 | ||||
-rw-r--r-- | drivers/watchdog/iTCO_wdt.c | 12 | ||||
-rw-r--r-- | drivers/watchdog/ks8695_wdt.c | 2 | ||||
-rw-r--r-- | drivers/watchdog/m548x_wdt.c | 227 | ||||
-rw-r--r-- | drivers/watchdog/nv_tco.c | 512 | ||||
-rw-r--r-- | drivers/watchdog/nv_tco.h | 64 | ||||
-rw-r--r-- | drivers/watchdog/sp5100_tco.c | 480 | ||||
-rw-r--r-- | drivers/watchdog/sp5100_tco.h | 41 | ||||
-rw-r--r-- | drivers/watchdog/w83627hf_wdt.c | 8 |
16 files changed, 1798 insertions, 34 deletions
diff --git a/arch/m68k/include/asm/m548xgpt.h b/arch/m68k/include/asm/m548xgpt.h index c8ef158a1c4..33b2eef90f0 100644 --- a/arch/m68k/include/asm/m548xgpt.h +++ b/arch/m68k/include/asm/m548xgpt.h | |||
@@ -59,11 +59,13 @@ | |||
59 | #define MCF_GPT_GMS_GPIO_INPUT (0x00000000) | 59 | #define MCF_GPT_GMS_GPIO_INPUT (0x00000000) |
60 | #define MCF_GPT_GMS_GPIO_OUTLO (0x00000020) | 60 | #define MCF_GPT_GMS_GPIO_OUTLO (0x00000020) |
61 | #define MCF_GPT_GMS_GPIO_OUTHI (0x00000030) | 61 | #define MCF_GPT_GMS_GPIO_OUTHI (0x00000030) |
62 | #define MCF_GPT_GMS_GPIO_MASK (0x00000030) | ||
62 | #define MCF_GPT_GMS_TMS_DISABLE (0x00000000) | 63 | #define MCF_GPT_GMS_TMS_DISABLE (0x00000000) |
63 | #define MCF_GPT_GMS_TMS_INCAPT (0x00000001) | 64 | #define MCF_GPT_GMS_TMS_INCAPT (0x00000001) |
64 | #define MCF_GPT_GMS_TMS_OUTCAPT (0x00000002) | 65 | #define MCF_GPT_GMS_TMS_OUTCAPT (0x00000002) |
65 | #define MCF_GPT_GMS_TMS_PWM (0x00000003) | 66 | #define MCF_GPT_GMS_TMS_PWM (0x00000003) |
66 | #define MCF_GPT_GMS_TMS_GPIO (0x00000004) | 67 | #define MCF_GPT_GMS_TMS_GPIO (0x00000004) |
68 | #define MCF_GPT_GMS_TMS_MASK (0x00000007) | ||
67 | 69 | ||
68 | /* Bit definitions and macros for MCF_GPT_GCIR */ | 70 | /* Bit definitions and macros for MCF_GPT_GCIR */ |
69 | #define MCF_GPT_GCIR_CNT(x) (((x)&0x0000FFFF)<<0) | 71 | #define MCF_GPT_GCIR_CNT(x) (((x)&0x0000FFFF)<<0) |
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index a5ad77ef426..2e2400e7322 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -409,15 +409,26 @@ config ALIM7101_WDT | |||
409 | Most people will say N. | 409 | Most people will say N. |
410 | 410 | ||
411 | config F71808E_WDT | 411 | config F71808E_WDT |
412 | tristate "Fintek F71808E, F71882FG and F71889FG Watchdog" | 412 | tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog" |
413 | depends on X86 && EXPERIMENTAL | 413 | depends on X86 && EXPERIMENTAL |
414 | help | 414 | help |
415 | This is the driver for the hardware watchdog on the Fintek | 415 | This is the driver for the hardware watchdog on the Fintek |
416 | F71808E, F71882FG and F71889FG Super I/O controllers. | 416 | F71808E, F71862FG, F71869, F71882FG and F71889FG Super I/O controllers. |
417 | 417 | ||
418 | You can compile this driver directly into the kernel, or use | 418 | You can compile this driver directly into the kernel, or use |
419 | it as a module. The module will be called f71808e_wdt. | 419 | it as a module. The module will be called f71808e_wdt. |
420 | 420 | ||
421 | config SP5100_TCO | ||
422 | tristate "AMD/ATI SP5100 TCO Timer/Watchdog" | ||
423 | depends on X86 && PCI | ||
424 | ---help--- | ||
425 | Hardware watchdog driver for the AMD/ATI SP5100 chipset. The TCO | ||
426 | (Total Cost of Ownership) timer is a watchdog timer that will reboot | ||
427 | the machine after its expiration. The expiration time can be | ||
428 | configured with the "heartbeat" parameter. | ||
429 | |||
430 | To compile this driver as a module, choose M here: the | ||
431 | module will be called sp5100_tco. | ||
421 | 432 | ||
422 | config GEODE_WDT | 433 | config GEODE_WDT |
423 | tristate "AMD Geode CS5535/CS5536 Watchdog" | 434 | tristate "AMD Geode CS5535/CS5536 Watchdog" |
@@ -631,6 +642,24 @@ config PC87413_WDT | |||
631 | 642 | ||
632 | Most people will say N. | 643 | Most people will say N. |
633 | 644 | ||
645 | config NV_TCO | ||
646 | tristate "nVidia TCO Timer/Watchdog" | ||
647 | depends on X86 && PCI | ||
648 | ---help--- | ||
649 | Hardware driver for the TCO timer built into the nVidia Hub family | ||
650 | (such as the MCP51). The TCO (Total Cost of Ownership) timer is a | ||
651 | watchdog timer that will reboot the machine after its second | ||
652 | expiration. The expiration time can be configured with the | ||
653 | "heartbeat" parameter. | ||
654 | |||
655 | On some motherboards the driver may fail to reset the chipset's | ||
656 | NO_REBOOT flag which prevents the watchdog from rebooting the | ||
657 | machine. If this is the case you will get a kernel message like | ||
658 | "failed to reset NO_REBOOT flag, reboot disabled by hardware". | ||
659 | |||
660 | To compile this driver as a module, choose M here: the | ||
661 | module will be called nv_tco. | ||
662 | |||
634 | config RDC321X_WDT | 663 | config RDC321X_WDT |
635 | tristate "RDC R-321x SoC watchdog" | 664 | tristate "RDC R-321x SoC watchdog" |
636 | depends on X86_RDC321X | 665 | depends on X86_RDC321X |
@@ -722,14 +751,15 @@ config SMSC37B787_WDT | |||
722 | Most people will say N. | 751 | Most people will say N. |
723 | 752 | ||
724 | config W83627HF_WDT | 753 | config W83627HF_WDT |
725 | tristate "W83627HF Watchdog Timer" | 754 | tristate "W83627HF/W83627DHG Watchdog Timer" |
726 | depends on X86 | 755 | depends on X86 |
727 | ---help--- | 756 | ---help--- |
728 | This is the driver for the hardware watchdog on the W83627HF chipset | 757 | This is the driver for the hardware watchdog on the W83627HF chipset |
729 | as used in Advantech PC-9578 and Tyan S2721-533 motherboards | 758 | as used in Advantech PC-9578 and Tyan S2721-533 motherboards |
730 | (and likely others). This watchdog simply watches your kernel to | 759 | (and likely others). The driver also supports the W83627DHG chip. |
731 | make sure it doesn't freeze, and if it does, it reboots your computer | 760 | This watchdog simply watches your kernel to make sure it doesn't |
732 | after a certain amount of time. | 761 | freeze, and if it does, it reboots your computer after a certain |
762 | amount of time. | ||
733 | 763 | ||
734 | To compile this driver as a module, choose M here: the | 764 | To compile this driver as a module, choose M here: the |
735 | module will be called w83627hf_wdt. | 765 | module will be called w83627hf_wdt. |
@@ -832,10 +862,22 @@ config SBC_EPX_C3_WATCHDOG | |||
832 | 862 | ||
833 | # M68K Architecture | 863 | # M68K Architecture |
834 | 864 | ||
835 | # M68KNOMMU Architecture | 865 | config M548x_WATCHDOG |
866 | tristate "MCF548x watchdog support" | ||
867 | depends on M548x | ||
868 | help | ||
869 | To compile this driver as a module, choose M here: the | ||
870 | module will be called m548x_wdt. | ||
836 | 871 | ||
837 | # MIPS Architecture | 872 | # MIPS Architecture |
838 | 873 | ||
874 | config ATH79_WDT | ||
875 | tristate "Atheros AR71XX/AR724X/AR913X hardware watchdog" | ||
876 | depends on ATH79 | ||
877 | help | ||
878 | Hardware driver for the built-in watchdog timer on the Atheros | ||
879 | AR71XX/AR724X/AR913X SoCs. | ||
880 | |||
839 | config BCM47XX_WDT | 881 | config BCM47XX_WDT |
840 | tristate "Broadcom BCM47xx Watchdog Timer" | 882 | tristate "Broadcom BCM47xx Watchdog Timer" |
841 | depends on BCM47XX | 883 | depends on BCM47XX |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 4b0ef386229..dd776651917 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -68,6 +68,7 @@ obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o | |||
68 | obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o | 68 | obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o |
69 | obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o | 69 | obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o |
70 | obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o | 70 | obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o |
71 | obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o | ||
71 | obj-$(CONFIG_GEODE_WDT) += geodewdt.o | 72 | obj-$(CONFIG_GEODE_WDT) += geodewdt.o |
72 | obj-$(CONFIG_SC520_WDT) += sc520_wdt.o | 73 | obj-$(CONFIG_SC520_WDT) += sc520_wdt.o |
73 | obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o | 74 | obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o |
@@ -86,6 +87,7 @@ obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o | |||
86 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o | 87 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o |
87 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o | 88 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o |
88 | obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o | 89 | obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o |
90 | obj-$(CONFIG_NV_TCO) += nv_tco.o | ||
89 | obj-$(CONFIG_RDC321X_WDT) += rdc321x_wdt.o | 91 | obj-$(CONFIG_RDC321X_WDT) += rdc321x_wdt.o |
90 | obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o | 92 | obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o |
91 | obj-$(CONFIG_SBC8360_WDT) += sbc8360.o | 93 | obj-$(CONFIG_SBC8360_WDT) += sbc8360.o |
@@ -104,10 +106,10 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o | |||
104 | # M32R Architecture | 106 | # M32R Architecture |
105 | 107 | ||
106 | # M68K Architecture | 108 | # M68K Architecture |
107 | 109 | obj-$(CONFIG_M548x_WATCHDOG) += m548x_wdt.o | |
108 | # M68KNOMMU Architecture | ||
109 | 110 | ||
110 | # MIPS Architecture | 111 | # MIPS Architecture |
112 | obj-$(CONFIG_ATH79_WDT) += ath79_wdt.o | ||
111 | obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o | 113 | obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o |
112 | obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o | 114 | obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o |
113 | obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o | 115 | obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o |
diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c index 1e9caea8ff8..fa4d3603355 100644 --- a/drivers/watchdog/alim1535_wdt.c +++ b/drivers/watchdog/alim1535_wdt.c | |||
@@ -301,7 +301,7 @@ static int ali_notify_sys(struct notifier_block *this, | |||
301 | * want to register another driver on the same PCI id. | 301 | * want to register another driver on the same PCI id. |
302 | */ | 302 | */ |
303 | 303 | ||
304 | static struct pci_device_id ali_pci_tbl[] = { | 304 | static struct pci_device_id ali_pci_tbl[] __used = { |
305 | { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,}, | 305 | { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,}, |
306 | { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,}, | 306 | { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,}, |
307 | { 0, }, | 307 | { 0, }, |
diff --git a/drivers/watchdog/alim7101_wdt.c b/drivers/watchdog/alim7101_wdt.c index d8d4da9a483..4b7a2b4138e 100644 --- a/drivers/watchdog/alim7101_wdt.c +++ b/drivers/watchdog/alim7101_wdt.c | |||
@@ -430,7 +430,7 @@ err_out: | |||
430 | module_init(alim7101_wdt_init); | 430 | module_init(alim7101_wdt_init); |
431 | module_exit(alim7101_wdt_unload); | 431 | module_exit(alim7101_wdt_unload); |
432 | 432 | ||
433 | static struct pci_device_id alim7101_pci_tbl[] __devinitdata = { | 433 | static struct pci_device_id alim7101_pci_tbl[] __devinitdata __used = { |
434 | { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) }, | 434 | { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) }, |
435 | { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) }, | 435 | { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) }, |
436 | { } | 436 | { } |
diff --git a/drivers/watchdog/ath79_wdt.c b/drivers/watchdog/ath79_wdt.c new file mode 100644 index 00000000000..725c84bfdd7 --- /dev/null +++ b/drivers/watchdog/ath79_wdt.c | |||
@@ -0,0 +1,305 @@ | |||
1 | /* | ||
2 | * Atheros AR71XX/AR724X/AR913X built-in hardware watchdog timer. | ||
3 | * | ||
4 | * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org> | ||
5 | * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> | ||
6 | * | ||
7 | * This driver was based on: drivers/watchdog/ixp4xx_wdt.c | ||
8 | * Author: Deepak Saxena <dsaxena@plexity.net> | ||
9 | * Copyright 2004 (c) MontaVista, Software, Inc. | ||
10 | * | ||
11 | * which again was based on sa1100 driver, | ||
12 | * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or modify it | ||
15 | * under the terms of the GNU General Public License version 2 as published | ||
16 | * by the Free Software Foundation. | ||
17 | * | ||
18 | */ | ||
19 | |||
20 | #include <linux/bitops.h> | ||
21 | #include <linux/errno.h> | ||
22 | #include <linux/fs.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/miscdevice.h> | ||
26 | #include <linux/module.h> | ||
27 | #include <linux/moduleparam.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/types.h> | ||
30 | #include <linux/watchdog.h> | ||
31 | #include <linux/clk.h> | ||
32 | #include <linux/err.h> | ||
33 | |||
34 | #include <asm/mach-ath79/ath79.h> | ||
35 | #include <asm/mach-ath79/ar71xx_regs.h> | ||
36 | |||
37 | #define DRIVER_NAME "ath79-wdt" | ||
38 | |||
39 | #define WDT_TIMEOUT 15 /* seconds */ | ||
40 | |||
41 | #define WDOG_CTRL_LAST_RESET BIT(31) | ||
42 | #define WDOG_CTRL_ACTION_MASK 3 | ||
43 | #define WDOG_CTRL_ACTION_NONE 0 /* no action */ | ||
44 | #define WDOG_CTRL_ACTION_GPI 1 /* general purpose interrupt */ | ||
45 | #define WDOG_CTRL_ACTION_NMI 2 /* NMI */ | ||
46 | #define WDOG_CTRL_ACTION_FCR 3 /* full chip reset */ | ||
47 | |||
48 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
49 | module_param(nowayout, int, 0); | ||
50 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " | ||
51 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
52 | |||
53 | static int timeout = WDT_TIMEOUT; | ||
54 | module_param(timeout, int, 0); | ||
55 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds " | ||
56 | "(default=" __MODULE_STRING(WDT_TIMEOUT) "s)"); | ||
57 | |||
58 | static unsigned long wdt_flags; | ||
59 | |||
60 | #define WDT_FLAGS_BUSY 0 | ||
61 | #define WDT_FLAGS_EXPECT_CLOSE 1 | ||
62 | |||
63 | static struct clk *wdt_clk; | ||
64 | static unsigned long wdt_freq; | ||
65 | static int boot_status; | ||
66 | static int max_timeout; | ||
67 | |||
68 | static inline void ath79_wdt_keepalive(void) | ||
69 | { | ||
70 | ath79_reset_wr(AR71XX_RESET_REG_WDOG, wdt_freq * timeout); | ||
71 | } | ||
72 | |||
73 | static inline void ath79_wdt_enable(void) | ||
74 | { | ||
75 | ath79_wdt_keepalive(); | ||
76 | ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_FCR); | ||
77 | } | ||
78 | |||
79 | static inline void ath79_wdt_disable(void) | ||
80 | { | ||
81 | ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_NONE); | ||
82 | } | ||
83 | |||
84 | static int ath79_wdt_set_timeout(int val) | ||
85 | { | ||
86 | if (val < 1 || val > max_timeout) | ||
87 | return -EINVAL; | ||
88 | |||
89 | timeout = val; | ||
90 | ath79_wdt_keepalive(); | ||
91 | |||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | static int ath79_wdt_open(struct inode *inode, struct file *file) | ||
96 | { | ||
97 | if (test_and_set_bit(WDT_FLAGS_BUSY, &wdt_flags)) | ||
98 | return -EBUSY; | ||
99 | |||
100 | clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags); | ||
101 | ath79_wdt_enable(); | ||
102 | |||
103 | return nonseekable_open(inode, file); | ||
104 | } | ||
105 | |||
106 | static int ath79_wdt_release(struct inode *inode, struct file *file) | ||
107 | { | ||
108 | if (test_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags)) | ||
109 | ath79_wdt_disable(); | ||
110 | else { | ||
111 | pr_crit(DRIVER_NAME ": device closed unexpectedly, " | ||
112 | "watchdog timer will not stop!\n"); | ||
113 | ath79_wdt_keepalive(); | ||
114 | } | ||
115 | |||
116 | clear_bit(WDT_FLAGS_BUSY, &wdt_flags); | ||
117 | clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags); | ||
118 | |||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | static ssize_t ath79_wdt_write(struct file *file, const char *data, | ||
123 | size_t len, loff_t *ppos) | ||
124 | { | ||
125 | if (len) { | ||
126 | if (!nowayout) { | ||
127 | size_t i; | ||
128 | |||
129 | clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags); | ||
130 | |||
131 | for (i = 0; i != len; i++) { | ||
132 | char c; | ||
133 | |||
134 | if (get_user(c, data + i)) | ||
135 | return -EFAULT; | ||
136 | |||
137 | if (c == 'V') | ||
138 | set_bit(WDT_FLAGS_EXPECT_CLOSE, | ||
139 | &wdt_flags); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | ath79_wdt_keepalive(); | ||
144 | } | ||
145 | |||
146 | return len; | ||
147 | } | ||
148 | |||
149 | static const struct watchdog_info ath79_wdt_info = { | ||
150 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | | ||
151 | WDIOF_MAGICCLOSE | WDIOF_CARDRESET, | ||
152 | .firmware_version = 0, | ||
153 | .identity = "ATH79 watchdog", | ||
154 | }; | ||
155 | |||
156 | static long ath79_wdt_ioctl(struct file *file, unsigned int cmd, | ||
157 | unsigned long arg) | ||
158 | { | ||
159 | void __user *argp = (void __user *)arg; | ||
160 | int __user *p = argp; | ||
161 | int err; | ||
162 | int t; | ||
163 | |||
164 | switch (cmd) { | ||
165 | case WDIOC_GETSUPPORT: | ||
166 | err = copy_to_user(argp, &ath79_wdt_info, | ||
167 | sizeof(ath79_wdt_info)) ? -EFAULT : 0; | ||
168 | break; | ||
169 | |||
170 | case WDIOC_GETSTATUS: | ||
171 | err = put_user(0, p); | ||
172 | break; | ||
173 | |||
174 | case WDIOC_GETBOOTSTATUS: | ||
175 | err = put_user(boot_status, p); | ||
176 | break; | ||
177 | |||
178 | case WDIOC_KEEPALIVE: | ||
179 | ath79_wdt_keepalive(); | ||
180 | err = 0; | ||
181 | break; | ||
182 | |||
183 | case WDIOC_SETTIMEOUT: | ||
184 | err = get_user(t, p); | ||
185 | if (err) | ||
186 | break; | ||
187 | |||
188 | err = ath79_wdt_set_timeout(t); | ||
189 | if (err) | ||
190 | break; | ||
191 | |||
192 | /* fallthrough */ | ||
193 | case WDIOC_GETTIMEOUT: | ||
194 | err = put_user(timeout, p); | ||
195 | break; | ||
196 | |||
197 | default: | ||
198 | err = -ENOTTY; | ||
199 | break; | ||
200 | } | ||
201 | |||
202 | return err; | ||
203 | } | ||
204 | |||
205 | static const struct file_operations ath79_wdt_fops = { | ||
206 | .owner = THIS_MODULE, | ||
207 | .llseek = no_llseek, | ||
208 | .write = ath79_wdt_write, | ||
209 | .unlocked_ioctl = ath79_wdt_ioctl, | ||
210 | .open = ath79_wdt_open, | ||
211 | .release = ath79_wdt_release, | ||
212 | }; | ||
213 | |||
214 | static struct miscdevice ath79_wdt_miscdev = { | ||
215 | .minor = WATCHDOG_MINOR, | ||
216 | .name = "watchdog", | ||
217 | .fops = &ath79_wdt_fops, | ||
218 | }; | ||
219 | |||
220 | static int __devinit ath79_wdt_probe(struct platform_device *pdev) | ||
221 | { | ||
222 | u32 ctrl; | ||
223 | int err; | ||
224 | |||
225 | wdt_clk = clk_get(&pdev->dev, "wdt"); | ||
226 | if (IS_ERR(wdt_clk)) | ||
227 | return PTR_ERR(wdt_clk); | ||
228 | |||
229 | err = clk_enable(wdt_clk); | ||
230 | if (err) | ||
231 | goto err_clk_put; | ||
232 | |||
233 | wdt_freq = clk_get_rate(wdt_clk); | ||
234 | if (!wdt_freq) { | ||
235 | err = -EINVAL; | ||
236 | goto err_clk_disable; | ||
237 | } | ||
238 | |||
239 | max_timeout = (0xfffffffful / wdt_freq); | ||
240 | if (timeout < 1 || timeout > max_timeout) { | ||
241 | timeout = max_timeout; | ||
242 | dev_info(&pdev->dev, | ||
243 | "timeout value must be 0 < timeout < %d, using %d\n", | ||
244 | max_timeout, timeout); | ||
245 | } | ||
246 | |||
247 | ctrl = ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL); | ||
248 | boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0; | ||
249 | |||
250 | err = misc_register(&ath79_wdt_miscdev); | ||
251 | if (err) { | ||
252 | dev_err(&pdev->dev, | ||
253 | "unable to register misc device, err=%d\n", err); | ||
254 | goto err_clk_disable; | ||
255 | } | ||
256 | |||
257 | return 0; | ||
258 | |||
259 | err_clk_disable: | ||
260 | clk_disable(wdt_clk); | ||
261 | err_clk_put: | ||
262 | clk_put(wdt_clk); | ||
263 | return err; | ||
264 | } | ||
265 | |||
266 | static int __devexit ath79_wdt_remove(struct platform_device *pdev) | ||
267 | { | ||
268 | misc_deregister(&ath79_wdt_miscdev); | ||
269 | clk_disable(wdt_clk); | ||
270 | clk_put(wdt_clk); | ||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | static void ath97_wdt_shutdown(struct platform_device *pdev) | ||
275 | { | ||
276 | ath79_wdt_disable(); | ||
277 | } | ||
278 | |||
279 | static struct platform_driver ath79_wdt_driver = { | ||
280 | .remove = __devexit_p(ath79_wdt_remove), | ||
281 | .shutdown = ath97_wdt_shutdown, | ||
282 | .driver = { | ||
283 | .name = DRIVER_NAME, | ||
284 | .owner = THIS_MODULE, | ||
285 | }, | ||
286 | }; | ||
287 | |||
288 | static int __init ath79_wdt_init(void) | ||
289 | { | ||
290 | return platform_driver_probe(&ath79_wdt_driver, ath79_wdt_probe); | ||
291 | } | ||
292 | module_init(ath79_wdt_init); | ||
293 | |||
294 | static void __exit ath79_wdt_exit(void) | ||
295 | { | ||
296 | platform_driver_unregister(&ath79_wdt_driver); | ||
297 | } | ||
298 | module_exit(ath79_wdt_exit); | ||
299 | |||
300 | MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver"); | ||
301 | MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org"); | ||
302 | MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org"); | ||
303 | MODULE_LICENSE("GPL v2"); | ||
304 | MODULE_ALIAS("platform:" DRIVER_NAME); | ||
305 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c index d11ffb091b0..7e7ec9c35b6 100644 --- a/drivers/watchdog/booke_wdt.c +++ b/drivers/watchdog/booke_wdt.c | |||
@@ -85,6 +85,22 @@ static unsigned int sec_to_period(unsigned int secs) | |||
85 | return 0; | 85 | return 0; |
86 | } | 86 | } |
87 | 87 | ||
88 | static void __booke_wdt_set(void *data) | ||
89 | { | ||
90 | u32 val; | ||
91 | |||
92 | val = mfspr(SPRN_TCR); | ||
93 | val &= ~WDTP_MASK; | ||
94 | val |= WDTP(booke_wdt_period); | ||
95 | |||
96 | mtspr(SPRN_TCR, val); | ||
97 | } | ||
98 | |||
99 | static void booke_wdt_set(void) | ||
100 | { | ||
101 | on_each_cpu(__booke_wdt_set, NULL, 0); | ||
102 | } | ||
103 | |||
88 | static void __booke_wdt_ping(void *data) | 104 | static void __booke_wdt_ping(void *data) |
89 | { | 105 | { |
90 | mtspr(SPRN_TSR, TSR_ENW|TSR_WIS); | 106 | mtspr(SPRN_TSR, TSR_ENW|TSR_WIS); |
@@ -181,8 +197,7 @@ static long booke_wdt_ioctl(struct file *file, | |||
181 | #else | 197 | #else |
182 | booke_wdt_period = tmp; | 198 | booke_wdt_period = tmp; |
183 | #endif | 199 | #endif |
184 | mtspr(SPRN_TCR, (mfspr(SPRN_TCR) & ~WDTP_MASK) | | 200 | booke_wdt_set(); |
185 | WDTP(booke_wdt_period)); | ||
186 | return 0; | 201 | return 0; |
187 | case WDIOC_GETTIMEOUT: | 202 | case WDIOC_GETTIMEOUT: |
188 | return put_user(booke_wdt_period, p); | 203 | return put_user(booke_wdt_period, p); |
@@ -193,8 +208,15 @@ static long booke_wdt_ioctl(struct file *file, | |||
193 | return 0; | 208 | return 0; |
194 | } | 209 | } |
195 | 210 | ||
211 | /* wdt_is_active stores wether or not the /dev/watchdog device is opened */ | ||
212 | static unsigned long wdt_is_active; | ||
213 | |||
196 | static int booke_wdt_open(struct inode *inode, struct file *file) | 214 | static int booke_wdt_open(struct inode *inode, struct file *file) |
197 | { | 215 | { |
216 | /* /dev/watchdog can only be opened once */ | ||
217 | if (test_and_set_bit(0, &wdt_is_active)) | ||
218 | return -EBUSY; | ||
219 | |||
198 | spin_lock(&booke_wdt_lock); | 220 | spin_lock(&booke_wdt_lock); |
199 | if (booke_wdt_enabled == 0) { | 221 | if (booke_wdt_enabled == 0) { |
200 | booke_wdt_enabled = 1; | 222 | booke_wdt_enabled = 1; |
@@ -210,8 +232,17 @@ static int booke_wdt_open(struct inode *inode, struct file *file) | |||
210 | 232 | ||
211 | static int booke_wdt_release(struct inode *inode, struct file *file) | 233 | static int booke_wdt_release(struct inode *inode, struct file *file) |
212 | { | 234 | { |
235 | #ifndef CONFIG_WATCHDOG_NOWAYOUT | ||
236 | /* Normally, the watchdog is disabled when /dev/watchdog is closed, but | ||
237 | * if CONFIG_WATCHDOG_NOWAYOUT is defined, then it means that the | ||
238 | * watchdog should remain enabled. So we disable it only if | ||
239 | * CONFIG_WATCHDOG_NOWAYOUT is not defined. | ||
240 | */ | ||
213 | on_each_cpu(__booke_wdt_disable, NULL, 0); | 241 | on_each_cpu(__booke_wdt_disable, NULL, 0); |
214 | booke_wdt_enabled = 0; | 242 | booke_wdt_enabled = 0; |
243 | #endif | ||
244 | |||
245 | clear_bit(0, &wdt_is_active); | ||
215 | 246 | ||
216 | return 0; | 247 | return 0; |
217 | } | 248 | } |
diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c index 65e579635db..d4d8d1fdccc 100644 --- a/drivers/watchdog/f71808e_wdt.c +++ b/drivers/watchdog/f71808e_wdt.c | |||
@@ -42,18 +42,21 @@ | |||
42 | #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ | 42 | #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ |
43 | #define SIO_REG_DEVREV 0x22 /* Device revision */ | 43 | #define SIO_REG_DEVREV 0x22 /* Device revision */ |
44 | #define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ | 44 | #define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ |
45 | #define SIO_REG_ROM_ADDR_SEL 0x27 /* ROM address select */ | ||
46 | #define SIO_REG_MFUNCT1 0x29 /* Multi function select 1 */ | ||
47 | #define SIO_REG_MFUNCT2 0x2a /* Multi function select 2 */ | ||
48 | #define SIO_REG_MFUNCT3 0x2b /* Multi function select 3 */ | ||
45 | #define SIO_REG_ENABLE 0x30 /* Logical device enable */ | 49 | #define SIO_REG_ENABLE 0x30 /* Logical device enable */ |
46 | #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ | 50 | #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ |
47 | 51 | ||
48 | #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ | 52 | #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ |
49 | #define SIO_F71808_ID 0x0901 /* Chipset ID */ | 53 | #define SIO_F71808_ID 0x0901 /* Chipset ID */ |
50 | #define SIO_F71858_ID 0x0507 /* Chipset ID */ | 54 | #define SIO_F71858_ID 0x0507 /* Chipset ID */ |
51 | #define SIO_F71862_ID 0x0601 /* Chipset ID */ | 55 | #define SIO_F71862_ID 0x0601 /* Chipset ID */ |
56 | #define SIO_F71869_ID 0x0814 /* Chipset ID */ | ||
52 | #define SIO_F71882_ID 0x0541 /* Chipset ID */ | 57 | #define SIO_F71882_ID 0x0541 /* Chipset ID */ |
53 | #define SIO_F71889_ID 0x0723 /* Chipset ID */ | 58 | #define SIO_F71889_ID 0x0723 /* Chipset ID */ |
54 | 59 | ||
55 | #define F71882FG_REG_START 0x01 | ||
56 | |||
57 | #define F71808FG_REG_WDO_CONF 0xf0 | 60 | #define F71808FG_REG_WDO_CONF 0xf0 |
58 | #define F71808FG_REG_WDT_CONF 0xf5 | 61 | #define F71808FG_REG_WDT_CONF 0xf5 |
59 | #define F71808FG_REG_WD_TIME 0xf6 | 62 | #define F71808FG_REG_WD_TIME 0xf6 |
@@ -70,13 +73,15 @@ | |||
70 | #define WATCHDOG_MAX_TIMEOUT (60 * 255) | 73 | #define WATCHDOG_MAX_TIMEOUT (60 * 255) |
71 | #define WATCHDOG_PULSE_WIDTH 125 /* 125 ms, default pulse width for | 74 | #define WATCHDOG_PULSE_WIDTH 125 /* 125 ms, default pulse width for |
72 | watchdog signal */ | 75 | watchdog signal */ |
76 | #define WATCHDOG_F71862FG_PIN 63 /* default watchdog reset output | ||
77 | pin number 63 */ | ||
73 | 78 | ||
74 | static unsigned short force_id; | 79 | static unsigned short force_id; |
75 | module_param(force_id, ushort, 0); | 80 | module_param(force_id, ushort, 0); |
76 | MODULE_PARM_DESC(force_id, "Override the detected device ID"); | 81 | MODULE_PARM_DESC(force_id, "Override the detected device ID"); |
77 | 82 | ||
78 | static const int max_timeout = WATCHDOG_MAX_TIMEOUT; | 83 | static const int max_timeout = WATCHDOG_MAX_TIMEOUT; |
79 | static int timeout = 60; /* default timeout in seconds */ | 84 | static int timeout = WATCHDOG_TIMEOUT; /* default timeout in seconds */ |
80 | module_param(timeout, int, 0); | 85 | module_param(timeout, int, 0); |
81 | MODULE_PARM_DESC(timeout, | 86 | MODULE_PARM_DESC(timeout, |
82 | "Watchdog timeout in seconds. 1<= timeout <=" | 87 | "Watchdog timeout in seconds. 1<= timeout <=" |
@@ -89,6 +94,12 @@ MODULE_PARM_DESC(pulse_width, | |||
89 | "Watchdog signal pulse width. 0(=level), 1 ms, 25 ms, 125 ms or 5000 ms" | 94 | "Watchdog signal pulse width. 0(=level), 1 ms, 25 ms, 125 ms or 5000 ms" |
90 | " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")"); | 95 | " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")"); |
91 | 96 | ||
97 | static unsigned int f71862fg_pin = WATCHDOG_F71862FG_PIN; | ||
98 | module_param(f71862fg_pin, uint, 0); | ||
99 | MODULE_PARM_DESC(f71862fg_pin, | ||
100 | "Watchdog f71862fg reset output pin configuration. Choose pin 56 or 63" | ||
101 | " (default=" __MODULE_STRING(WATCHDOG_F71862FG_PIN)")"); | ||
102 | |||
92 | static int nowayout = WATCHDOG_NOWAYOUT; | 103 | static int nowayout = WATCHDOG_NOWAYOUT; |
93 | module_param(nowayout, bool, 0444); | 104 | module_param(nowayout, bool, 0444); |
94 | MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); | 105 | MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); |
@@ -98,12 +109,13 @@ module_param(start_withtimeout, uint, 0); | |||
98 | MODULE_PARM_DESC(start_withtimeout, "Start watchdog timer on module load with" | 109 | MODULE_PARM_DESC(start_withtimeout, "Start watchdog timer on module load with" |
99 | " given initial timeout. Zero (default) disables this feature."); | 110 | " given initial timeout. Zero (default) disables this feature."); |
100 | 111 | ||
101 | enum chips { f71808fg, f71858fg, f71862fg, f71882fg, f71889fg }; | 112 | enum chips { f71808fg, f71858fg, f71862fg, f71869, f71882fg, f71889fg }; |
102 | 113 | ||
103 | static const char *f71808e_names[] = { | 114 | static const char *f71808e_names[] = { |
104 | "f71808fg", | 115 | "f71808fg", |
105 | "f71858fg", | 116 | "f71858fg", |
106 | "f71862fg", | 117 | "f71862fg", |
118 | "f71869", | ||
107 | "f71882fg", | 119 | "f71882fg", |
108 | "f71889fg", | 120 | "f71889fg", |
109 | }; | 121 | }; |
@@ -282,6 +294,28 @@ exit_unlock: | |||
282 | return err; | 294 | return err; |
283 | } | 295 | } |
284 | 296 | ||
297 | static int f71862fg_pin_configure(unsigned short ioaddr) | ||
298 | { | ||
299 | /* When ioaddr is non-zero the calling function has to take care of | ||
300 | mutex handling and superio preparation! */ | ||
301 | |||
302 | if (f71862fg_pin == 63) { | ||
303 | if (ioaddr) { | ||
304 | /* SPI must be disabled first to use this pin! */ | ||
305 | superio_clear_bit(ioaddr, SIO_REG_ROM_ADDR_SEL, 6); | ||
306 | superio_set_bit(ioaddr, SIO_REG_MFUNCT3, 4); | ||
307 | } | ||
308 | } else if (f71862fg_pin == 56) { | ||
309 | if (ioaddr) | ||
310 | superio_set_bit(ioaddr, SIO_REG_MFUNCT1, 1); | ||
311 | } else { | ||
312 | printk(KERN_ERR DRVNAME ": Invalid argument f71862fg_pin=%d\n", | ||
313 | f71862fg_pin); | ||
314 | return -EINVAL; | ||
315 | } | ||
316 | return 0; | ||
317 | } | ||
318 | |||
285 | static int watchdog_start(void) | 319 | static int watchdog_start(void) |
286 | { | 320 | { |
287 | /* Make sure we don't die as soon as the watchdog is enabled below */ | 321 | /* Make sure we don't die as soon as the watchdog is enabled below */ |
@@ -299,19 +333,30 @@ static int watchdog_start(void) | |||
299 | switch (watchdog.type) { | 333 | switch (watchdog.type) { |
300 | case f71808fg: | 334 | case f71808fg: |
301 | /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */ | 335 | /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */ |
302 | superio_clear_bit(watchdog.sioaddr, 0x2a, 3); | 336 | superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT2, 3); |
303 | superio_clear_bit(watchdog.sioaddr, 0x2b, 3); | 337 | superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 3); |
338 | break; | ||
339 | |||
340 | case f71862fg: | ||
341 | err = f71862fg_pin_configure(watchdog.sioaddr); | ||
342 | if (err) | ||
343 | goto exit_superio; | ||
344 | break; | ||
345 | |||
346 | case f71869: | ||
347 | /* GPIO14 --> WDTRST# */ | ||
348 | superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 4); | ||
304 | break; | 349 | break; |
305 | 350 | ||
306 | case f71882fg: | 351 | case f71882fg: |
307 | /* Set pin 56 to WDTRST# */ | 352 | /* Set pin 56 to WDTRST# */ |
308 | superio_set_bit(watchdog.sioaddr, 0x29, 1); | 353 | superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 1); |
309 | break; | 354 | break; |
310 | 355 | ||
311 | case f71889fg: | 356 | case f71889fg: |
312 | /* set pin 40 to WDTRST# */ | 357 | /* set pin 40 to WDTRST# */ |
313 | superio_outb(watchdog.sioaddr, 0x2b, | 358 | superio_outb(watchdog.sioaddr, SIO_REG_MFUNCT3, |
314 | superio_inb(watchdog.sioaddr, 0x2b) & 0xcf); | 359 | superio_inb(watchdog.sioaddr, SIO_REG_MFUNCT3) & 0xcf); |
315 | break; | 360 | break; |
316 | 361 | ||
317 | default: | 362 | default: |
@@ -711,16 +756,19 @@ static int __init f71808e_find(int sioaddr) | |||
711 | case SIO_F71808_ID: | 756 | case SIO_F71808_ID: |
712 | watchdog.type = f71808fg; | 757 | watchdog.type = f71808fg; |
713 | break; | 758 | break; |
759 | case SIO_F71862_ID: | ||
760 | watchdog.type = f71862fg; | ||
761 | err = f71862fg_pin_configure(0); /* validate module parameter */ | ||
762 | break; | ||
763 | case SIO_F71869_ID: | ||
764 | watchdog.type = f71869; | ||
765 | break; | ||
714 | case SIO_F71882_ID: | 766 | case SIO_F71882_ID: |
715 | watchdog.type = f71882fg; | 767 | watchdog.type = f71882fg; |
716 | break; | 768 | break; |
717 | case SIO_F71889_ID: | 769 | case SIO_F71889_ID: |
718 | watchdog.type = f71889fg; | 770 | watchdog.type = f71889fg; |
719 | break; | 771 | break; |
720 | case SIO_F71862_ID: | ||
721 | /* These have a watchdog, though it isn't implemented (yet). */ | ||
722 | err = -ENOSYS; | ||
723 | goto exit; | ||
724 | case SIO_F71858_ID: | 772 | case SIO_F71858_ID: |
725 | /* Confirmed (by datasheet) not to have a watchdog. */ | 773 | /* Confirmed (by datasheet) not to have a watchdog. */ |
726 | err = -ENODEV; | 774 | err = -ENODEV; |
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index b8838d2c67a..2c6c2b4ad8b 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * intel TCO Watchdog Driver | 2 | * intel TCO Watchdog Driver |
3 | * | 3 | * |
4 | * (c) Copyright 2006-2009 Wim Van Sebroeck <wim@iguana.be>. | 4 | * (c) Copyright 2006-2010 Wim Van Sebroeck <wim@iguana.be>. |
5 | * | 5 | * |
6 | * This program is free software; you can redistribute it and/or | 6 | * This program is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU General Public License | 7 | * modify it under the terms of the GNU General Public License |
@@ -26,13 +26,15 @@ | |||
26 | * document number 301473-002, 301474-026: 82801F (ICH6) | 26 | * document number 301473-002, 301474-026: 82801F (ICH6) |
27 | * document number 313082-001, 313075-006: 631xESB, 632xESB | 27 | * document number 313082-001, 313075-006: 631xESB, 632xESB |
28 | * document number 307013-003, 307014-024: 82801G (ICH7) | 28 | * document number 307013-003, 307014-024: 82801G (ICH7) |
29 | * document number 322896-001, 322897-001: NM10 | ||
29 | * document number 313056-003, 313057-017: 82801H (ICH8) | 30 | * document number 313056-003, 313057-017: 82801H (ICH8) |
30 | * document number 316972-004, 316973-012: 82801I (ICH9) | 31 | * document number 316972-004, 316973-012: 82801I (ICH9) |
31 | * document number 319973-002, 319974-002: 82801J (ICH10) | 32 | * document number 319973-002, 319974-002: 82801J (ICH10) |
32 | * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH) | 33 | * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH) |
33 | * document number 320066-003, 320257-008: EP80597 (IICH) | 34 | * document number 320066-003, 320257-008: EP80597 (IICH) |
34 | * document number TBD : Cougar Point (CPT) | 35 | * document number 324645-001, 324646-001: Cougar Point (CPT) |
35 | * document number TBD : Patsburg (PBG) | 36 | * document number TBD : Patsburg (PBG) |
37 | * document number TBD : DH89xxCC | ||
36 | */ | 38 | */ |
37 | 39 | ||
38 | /* | 40 | /* |
@@ -85,6 +87,7 @@ enum iTCO_chipsets { | |||
85 | TCO_ICH7DH, /* ICH7DH */ | 87 | TCO_ICH7DH, /* ICH7DH */ |
86 | TCO_ICH7M, /* ICH7-M & ICH7-U */ | 88 | TCO_ICH7M, /* ICH7-M & ICH7-U */ |
87 | TCO_ICH7MDH, /* ICH7-M DH */ | 89 | TCO_ICH7MDH, /* ICH7-M DH */ |
90 | TCO_NM10, /* NM10 */ | ||
88 | TCO_ICH8, /* ICH8 & ICH8R */ | 91 | TCO_ICH8, /* ICH8 & ICH8R */ |
89 | TCO_ICH8DH, /* ICH8DH */ | 92 | TCO_ICH8DH, /* ICH8DH */ |
90 | TCO_ICH8DO, /* ICH8DO */ | 93 | TCO_ICH8DO, /* ICH8DO */ |
@@ -149,6 +152,7 @@ enum iTCO_chipsets { | |||
149 | TCO_CPT31, /* Cougar Point */ | 152 | TCO_CPT31, /* Cougar Point */ |
150 | TCO_PBG1, /* Patsburg */ | 153 | TCO_PBG1, /* Patsburg */ |
151 | TCO_PBG2, /* Patsburg */ | 154 | TCO_PBG2, /* Patsburg */ |
155 | TCO_DH89XXCC, /* DH89xxCC */ | ||
152 | }; | 156 | }; |
153 | 157 | ||
154 | static struct { | 158 | static struct { |
@@ -174,6 +178,7 @@ static struct { | |||
174 | {"ICH7DH", 2}, | 178 | {"ICH7DH", 2}, |
175 | {"ICH7-M or ICH7-U", 2}, | 179 | {"ICH7-M or ICH7-U", 2}, |
176 | {"ICH7-M DH", 2}, | 180 | {"ICH7-M DH", 2}, |
181 | {"NM10", 2}, | ||
177 | {"ICH8 or ICH8R", 2}, | 182 | {"ICH8 or ICH8R", 2}, |
178 | {"ICH8DH", 2}, | 183 | {"ICH8DH", 2}, |
179 | {"ICH8DO", 2}, | 184 | {"ICH8DO", 2}, |
@@ -238,6 +243,7 @@ static struct { | |||
238 | {"Cougar Point", 2}, | 243 | {"Cougar Point", 2}, |
239 | {"Patsburg", 2}, | 244 | {"Patsburg", 2}, |
240 | {"Patsburg", 2}, | 245 | {"Patsburg", 2}, |
246 | {"DH89xxCC", 2}, | ||
241 | {NULL, 0} | 247 | {NULL, 0} |
242 | }; | 248 | }; |
243 | 249 | ||
@@ -291,6 +297,7 @@ static struct pci_device_id iTCO_wdt_pci_tbl[] = { | |||
291 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_30, TCO_ICH7DH)}, | 297 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_30, TCO_ICH7DH)}, |
292 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_1, TCO_ICH7M)}, | 298 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_1, TCO_ICH7M)}, |
293 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_31, TCO_ICH7MDH)}, | 299 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_31, TCO_ICH7MDH)}, |
300 | { ITCO_PCI_DEVICE(0x27bc, TCO_NM10)}, | ||
294 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_0, TCO_ICH8)}, | 301 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_0, TCO_ICH8)}, |
295 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_2, TCO_ICH8DH)}, | 302 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_2, TCO_ICH8DH)}, |
296 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_3, TCO_ICH8DO)}, | 303 | { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_3, TCO_ICH8DO)}, |
@@ -355,6 +362,7 @@ static struct pci_device_id iTCO_wdt_pci_tbl[] = { | |||
355 | { ITCO_PCI_DEVICE(0x1c5f, TCO_CPT31)}, | 362 | { ITCO_PCI_DEVICE(0x1c5f, TCO_CPT31)}, |
356 | { ITCO_PCI_DEVICE(0x1d40, TCO_PBG1)}, | 363 | { ITCO_PCI_DEVICE(0x1d40, TCO_PBG1)}, |
357 | { ITCO_PCI_DEVICE(0x1d41, TCO_PBG2)}, | 364 | { ITCO_PCI_DEVICE(0x1d41, TCO_PBG2)}, |
365 | { ITCO_PCI_DEVICE(0x2310, TCO_DH89XXCC)}, | ||
358 | { 0, }, /* End of list */ | 366 | { 0, }, /* End of list */ |
359 | }; | 367 | }; |
360 | MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl); | 368 | MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl); |
diff --git a/drivers/watchdog/ks8695_wdt.c b/drivers/watchdog/ks8695_wdt.c index 2852bb2e3fd..811471903e8 100644 --- a/drivers/watchdog/ks8695_wdt.c +++ b/drivers/watchdog/ks8695_wdt.c | |||
@@ -21,7 +21,7 @@ | |||
21 | #include <linux/watchdog.h> | 21 | #include <linux/watchdog.h> |
22 | #include <linux/io.h> | 22 | #include <linux/io.h> |
23 | #include <linux/uaccess.h> | 23 | #include <linux/uaccess.h> |
24 | #include <mach/timex.h> | 24 | #include <mach/hardware.h> |
25 | #include <mach/regs-timer.h> | 25 | #include <mach/regs-timer.h> |
26 | 26 | ||
27 | #define WDT_DEFAULT_TIME 5 /* seconds */ | 27 | #define WDT_DEFAULT_TIME 5 /* seconds */ |
diff --git a/drivers/watchdog/m548x_wdt.c b/drivers/watchdog/m548x_wdt.c new file mode 100644 index 00000000000..cabbcfe1c84 --- /dev/null +++ b/drivers/watchdog/m548x_wdt.c | |||
@@ -0,0 +1,227 @@ | |||
1 | /* | ||
2 | * drivers/watchdog/m548x_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for ColdFire MCF548x processors | ||
5 | * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be> | ||
6 | * | ||
7 | * Adapted from the IXP4xx watchdog driver, which carries these notices: | ||
8 | * | ||
9 | * Author: Deepak Saxena <dsaxena@plexity.net> | ||
10 | * | ||
11 | * Copyright 2004 (c) MontaVista, Software, Inc. | ||
12 | * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> | ||
13 | * | ||
14 | * This file is licensed under the terms of the GNU General Public | ||
15 | * License version 2. This program is licensed "as is" without any | ||
16 | * warranty of any kind, whether express or implied. | ||
17 | */ | ||
18 | |||
19 | #include <linux/module.h> | ||
20 | #include <linux/moduleparam.h> | ||
21 | #include <linux/types.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/fs.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/bitops.h> | ||
28 | #include <linux/ioport.h> | ||
29 | #include <linux/uaccess.h> | ||
30 | |||
31 | #include <asm/coldfire.h> | ||
32 | #include <asm/m548xsim.h> | ||
33 | #include <asm/m548xgpt.h> | ||
34 | |||
35 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
36 | static unsigned int heartbeat = 30; /* (secs) Default is 0.5 minute */ | ||
37 | static unsigned long wdt_status; | ||
38 | |||
39 | #define WDT_IN_USE 0 | ||
40 | #define WDT_OK_TO_CLOSE 1 | ||
41 | |||
42 | static void wdt_enable(void) | ||
43 | { | ||
44 | unsigned int gms0; | ||
45 | |||
46 | /* preserve GPIO usage, if any */ | ||
47 | gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); | ||
48 | if (gms0 & MCF_GPT_GMS_TMS_GPIO) | ||
49 | gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK | ||
50 | | MCF_GPT_GMS_OD); | ||
51 | else | ||
52 | gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD; | ||
53 | __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); | ||
54 | __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) | | ||
55 | MCF_GPT_GCIR_CNT(0xffff), MCF_MBAR + MCF_GPT_GCIR0); | ||
56 | gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE; | ||
57 | __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); | ||
58 | } | ||
59 | |||
60 | static void wdt_disable(void) | ||
61 | { | ||
62 | unsigned int gms0; | ||
63 | |||
64 | /* disable watchdog */ | ||
65 | gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); | ||
66 | gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE); | ||
67 | __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); | ||
68 | } | ||
69 | |||
70 | static void wdt_keepalive(void) | ||
71 | { | ||
72 | unsigned int gms0; | ||
73 | |||
74 | gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); | ||
75 | gms0 |= MCF_GPT_GMS_OCPW(0xA5); | ||
76 | __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); | ||
77 | } | ||
78 | |||
79 | static int m548x_wdt_open(struct inode *inode, struct file *file) | ||
80 | { | ||
81 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
82 | return -EBUSY; | ||
83 | |||
84 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
85 | wdt_enable(); | ||
86 | return nonseekable_open(inode, file); | ||
87 | } | ||
88 | |||
89 | static ssize_t m548x_wdt_write(struct file *file, const char *data, | ||
90 | size_t len, loff_t *ppos) | ||
91 | { | ||
92 | if (len) { | ||
93 | if (!nowayout) { | ||
94 | size_t i; | ||
95 | |||
96 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
97 | |||
98 | for (i = 0; i != len; i++) { | ||
99 | char c; | ||
100 | |||
101 | if (get_user(c, data + i)) | ||
102 | return -EFAULT; | ||
103 | if (c == 'V') | ||
104 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
105 | } | ||
106 | } | ||
107 | wdt_keepalive(); | ||
108 | } | ||
109 | return len; | ||
110 | } | ||
111 | |||
112 | static const struct watchdog_info ident = { | ||
113 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | | ||
114 | WDIOF_KEEPALIVEPING, | ||
115 | .identity = "Coldfire M548x Watchdog", | ||
116 | }; | ||
117 | |||
118 | static long m548x_wdt_ioctl(struct file *file, unsigned int cmd, | ||
119 | unsigned long arg) | ||
120 | { | ||
121 | int ret = -ENOTTY; | ||
122 | int time; | ||
123 | |||
124 | switch (cmd) { | ||
125 | case WDIOC_GETSUPPORT: | ||
126 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
127 | sizeof(ident)) ? -EFAULT : 0; | ||
128 | break; | ||
129 | |||
130 | case WDIOC_GETSTATUS: | ||
131 | ret = put_user(0, (int *)arg); | ||
132 | break; | ||
133 | |||
134 | case WDIOC_GETBOOTSTATUS: | ||
135 | ret = put_user(0, (int *)arg); | ||
136 | break; | ||
137 | |||
138 | case WDIOC_KEEPALIVE: | ||
139 | wdt_keepalive(); | ||
140 | ret = 0; | ||
141 | break; | ||
142 | |||
143 | case WDIOC_SETTIMEOUT: | ||
144 | ret = get_user(time, (int *)arg); | ||
145 | if (ret) | ||
146 | break; | ||
147 | |||
148 | if (time <= 0 || time > 30) { | ||
149 | ret = -EINVAL; | ||
150 | break; | ||
151 | } | ||
152 | |||
153 | heartbeat = time; | ||
154 | wdt_enable(); | ||
155 | /* Fall through */ | ||
156 | |||
157 | case WDIOC_GETTIMEOUT: | ||
158 | ret = put_user(heartbeat, (int *)arg); | ||
159 | break; | ||
160 | } | ||
161 | return ret; | ||
162 | } | ||
163 | |||
164 | static int m548x_wdt_release(struct inode *inode, struct file *file) | ||
165 | { | ||
166 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | ||
167 | wdt_disable(); | ||
168 | else { | ||
169 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | ||
170 | "timer will not stop\n"); | ||
171 | wdt_keepalive(); | ||
172 | } | ||
173 | clear_bit(WDT_IN_USE, &wdt_status); | ||
174 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
175 | |||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | |||
180 | static const struct file_operations m548x_wdt_fops = { | ||
181 | .owner = THIS_MODULE, | ||
182 | .llseek = no_llseek, | ||
183 | .write = m548x_wdt_write, | ||
184 | .unlocked_ioctl = m548x_wdt_ioctl, | ||
185 | .open = m548x_wdt_open, | ||
186 | .release = m548x_wdt_release, | ||
187 | }; | ||
188 | |||
189 | static struct miscdevice m548x_wdt_miscdev = { | ||
190 | .minor = WATCHDOG_MINOR, | ||
191 | .name = "watchdog", | ||
192 | .fops = &m548x_wdt_fops, | ||
193 | }; | ||
194 | |||
195 | static int __init m548x_wdt_init(void) | ||
196 | { | ||
197 | if (!request_mem_region(MCF_MBAR + MCF_GPT_GCIR0, 4, | ||
198 | "Coldfire M548x Watchdog")) { | ||
199 | printk(KERN_WARNING | ||
200 | "Coldfire M548x Watchdog : I/O region busy\n"); | ||
201 | return -EBUSY; | ||
202 | } | ||
203 | printk(KERN_INFO "ColdFire watchdog driver is loaded.\n"); | ||
204 | |||
205 | return misc_register(&m548x_wdt_miscdev); | ||
206 | } | ||
207 | |||
208 | static void __exit m548x_wdt_exit(void) | ||
209 | { | ||
210 | misc_deregister(&m548x_wdt_miscdev); | ||
211 | release_mem_region(MCF_MBAR + MCF_GPT_GCIR0, 4); | ||
212 | } | ||
213 | |||
214 | module_init(m548x_wdt_init); | ||
215 | module_exit(m548x_wdt_exit); | ||
216 | |||
217 | MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>"); | ||
218 | MODULE_DESCRIPTION("Coldfire M548x Watchdog"); | ||
219 | |||
220 | module_param(heartbeat, int, 0); | ||
221 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)"); | ||
222 | |||
223 | module_param(nowayout, int, 0); | ||
224 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
225 | |||
226 | MODULE_LICENSE("GPL"); | ||
227 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/nv_tco.c b/drivers/watchdog/nv_tco.c new file mode 100644 index 00000000000..1a50aa7079b --- /dev/null +++ b/drivers/watchdog/nv_tco.c | |||
@@ -0,0 +1,512 @@ | |||
1 | /* | ||
2 | * nv_tco 0.01: TCO timer driver for NV chipsets | ||
3 | * | ||
4 | * (c) Copyright 2005 Google Inc., All Rights Reserved. | ||
5 | * | ||
6 | * Based off i8xx_tco.c: | ||
7 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights | ||
8 | * Reserved. | ||
9 | * http://www.kernelconcepts.de | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version | ||
14 | * 2 of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * TCO timer driver for NV chipsets | ||
17 | * based on softdog.c by Alan Cox <alan@redhat.com> | ||
18 | */ | ||
19 | |||
20 | /* | ||
21 | * Includes, defines, variables, module parameters, ... | ||
22 | */ | ||
23 | |||
24 | #include <linux/module.h> | ||
25 | #include <linux/moduleparam.h> | ||
26 | #include <linux/types.h> | ||
27 | #include <linux/miscdevice.h> | ||
28 | #include <linux/watchdog.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/fs.h> | ||
31 | #include <linux/pci.h> | ||
32 | #include <linux/ioport.h> | ||
33 | #include <linux/jiffies.h> | ||
34 | #include <linux/platform_device.h> | ||
35 | #include <linux/uaccess.h> | ||
36 | #include <linux/io.h> | ||
37 | |||
38 | #include "nv_tco.h" | ||
39 | |||
40 | /* Module and version information */ | ||
41 | #define TCO_VERSION "0.01" | ||
42 | #define TCO_MODULE_NAME "NV_TCO" | ||
43 | #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION | ||
44 | #define PFX TCO_MODULE_NAME ": " | ||
45 | |||
46 | /* internal variables */ | ||
47 | static unsigned int tcobase; | ||
48 | static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ | ||
49 | static unsigned long timer_alive; | ||
50 | static char tco_expect_close; | ||
51 | static struct pci_dev *tco_pci; | ||
52 | |||
53 | /* the watchdog platform device */ | ||
54 | static struct platform_device *nv_tco_platform_device; | ||
55 | |||
56 | /* module parameters */ | ||
57 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */ | ||
58 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
59 | module_param(heartbeat, int, 0); | ||
60 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, " | ||
61 | "default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
62 | |||
63 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
64 | module_param(nowayout, int, 0); | ||
65 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started" | ||
66 | " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
67 | |||
68 | /* | ||
69 | * Some TCO specific functions | ||
70 | */ | ||
71 | static inline unsigned char seconds_to_ticks(int seconds) | ||
72 | { | ||
73 | /* the internal timer is stored as ticks which decrement | ||
74 | * every 0.6 seconds */ | ||
75 | return (seconds * 10) / 6; | ||
76 | } | ||
77 | |||
78 | static void tco_timer_start(void) | ||
79 | { | ||
80 | u32 val; | ||
81 | unsigned long flags; | ||
82 | |||
83 | spin_lock_irqsave(&tco_lock, flags); | ||
84 | val = inl(TCO_CNT(tcobase)); | ||
85 | val &= ~TCO_CNT_TCOHALT; | ||
86 | outl(val, TCO_CNT(tcobase)); | ||
87 | spin_unlock_irqrestore(&tco_lock, flags); | ||
88 | } | ||
89 | |||
90 | static void tco_timer_stop(void) | ||
91 | { | ||
92 | u32 val; | ||
93 | unsigned long flags; | ||
94 | |||
95 | spin_lock_irqsave(&tco_lock, flags); | ||
96 | val = inl(TCO_CNT(tcobase)); | ||
97 | val |= TCO_CNT_TCOHALT; | ||
98 | outl(val, TCO_CNT(tcobase)); | ||
99 | spin_unlock_irqrestore(&tco_lock, flags); | ||
100 | } | ||
101 | |||
102 | static void tco_timer_keepalive(void) | ||
103 | { | ||
104 | unsigned long flags; | ||
105 | |||
106 | spin_lock_irqsave(&tco_lock, flags); | ||
107 | outb(0x01, TCO_RLD(tcobase)); | ||
108 | spin_unlock_irqrestore(&tco_lock, flags); | ||
109 | } | ||
110 | |||
111 | static int tco_timer_set_heartbeat(int t) | ||
112 | { | ||
113 | int ret = 0; | ||
114 | unsigned char tmrval; | ||
115 | unsigned long flags; | ||
116 | u8 val; | ||
117 | |||
118 | /* | ||
119 | * note seconds_to_ticks(t) > t, so if t > 0x3f, so is | ||
120 | * tmrval=seconds_to_ticks(t). Check that the count in seconds isn't | ||
121 | * out of range on it's own (to avoid overflow in tmrval). | ||
122 | */ | ||
123 | if (t < 0 || t > 0x3f) | ||
124 | return -EINVAL; | ||
125 | tmrval = seconds_to_ticks(t); | ||
126 | |||
127 | /* "Values of 0h-3h are ignored and should not be attempted" */ | ||
128 | if (tmrval > 0x3f || tmrval < 0x04) | ||
129 | return -EINVAL; | ||
130 | |||
131 | /* Write new heartbeat to watchdog */ | ||
132 | spin_lock_irqsave(&tco_lock, flags); | ||
133 | val = inb(TCO_TMR(tcobase)); | ||
134 | val &= 0xc0; | ||
135 | val |= tmrval; | ||
136 | outb(val, TCO_TMR(tcobase)); | ||
137 | val = inb(TCO_TMR(tcobase)); | ||
138 | |||
139 | if ((val & 0x3f) != tmrval) | ||
140 | ret = -EINVAL; | ||
141 | spin_unlock_irqrestore(&tco_lock, flags); | ||
142 | |||
143 | if (ret) | ||
144 | return ret; | ||
145 | |||
146 | heartbeat = t; | ||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | /* | ||
151 | * /dev/watchdog handling | ||
152 | */ | ||
153 | |||
154 | static int nv_tco_open(struct inode *inode, struct file *file) | ||
155 | { | ||
156 | /* /dev/watchdog can only be opened once */ | ||
157 | if (test_and_set_bit(0, &timer_alive)) | ||
158 | return -EBUSY; | ||
159 | |||
160 | /* Reload and activate timer */ | ||
161 | tco_timer_keepalive(); | ||
162 | tco_timer_start(); | ||
163 | return nonseekable_open(inode, file); | ||
164 | } | ||
165 | |||
166 | static int nv_tco_release(struct inode *inode, struct file *file) | ||
167 | { | ||
168 | /* Shut off the timer */ | ||
169 | if (tco_expect_close == 42) { | ||
170 | tco_timer_stop(); | ||
171 | } else { | ||
172 | printk(KERN_CRIT PFX "Unexpected close, not stopping " | ||
173 | "watchdog!\n"); | ||
174 | tco_timer_keepalive(); | ||
175 | } | ||
176 | clear_bit(0, &timer_alive); | ||
177 | tco_expect_close = 0; | ||
178 | return 0; | ||
179 | } | ||
180 | |||
181 | static ssize_t nv_tco_write(struct file *file, const char __user *data, | ||
182 | size_t len, loff_t *ppos) | ||
183 | { | ||
184 | /* See if we got the magic character 'V' and reload the timer */ | ||
185 | if (len) { | ||
186 | if (!nowayout) { | ||
187 | size_t i; | ||
188 | |||
189 | /* | ||
190 | * note: just in case someone wrote the magic character | ||
191 | * five months ago... | ||
192 | */ | ||
193 | tco_expect_close = 0; | ||
194 | |||
195 | /* | ||
196 | * scan to see whether or not we got the magic | ||
197 | * character | ||
198 | */ | ||
199 | for (i = 0; i != len; i++) { | ||
200 | char c; | ||
201 | if (get_user(c, data + i)) | ||
202 | return -EFAULT; | ||
203 | if (c == 'V') | ||
204 | tco_expect_close = 42; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | /* someone wrote to us, we should reload the timer */ | ||
209 | tco_timer_keepalive(); | ||
210 | } | ||
211 | return len; | ||
212 | } | ||
213 | |||
214 | static long nv_tco_ioctl(struct file *file, unsigned int cmd, | ||
215 | unsigned long arg) | ||
216 | { | ||
217 | int new_options, retval = -EINVAL; | ||
218 | int new_heartbeat; | ||
219 | void __user *argp = (void __user *)arg; | ||
220 | int __user *p = argp; | ||
221 | static const struct watchdog_info ident = { | ||
222 | .options = WDIOF_SETTIMEOUT | | ||
223 | WDIOF_KEEPALIVEPING | | ||
224 | WDIOF_MAGICCLOSE, | ||
225 | .firmware_version = 0, | ||
226 | .identity = TCO_MODULE_NAME, | ||
227 | }; | ||
228 | |||
229 | switch (cmd) { | ||
230 | case WDIOC_GETSUPPORT: | ||
231 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
232 | case WDIOC_GETSTATUS: | ||
233 | case WDIOC_GETBOOTSTATUS: | ||
234 | return put_user(0, p); | ||
235 | case WDIOC_SETOPTIONS: | ||
236 | if (get_user(new_options, p)) | ||
237 | return -EFAULT; | ||
238 | if (new_options & WDIOS_DISABLECARD) { | ||
239 | tco_timer_stop(); | ||
240 | retval = 0; | ||
241 | } | ||
242 | if (new_options & WDIOS_ENABLECARD) { | ||
243 | tco_timer_keepalive(); | ||
244 | tco_timer_start(); | ||
245 | retval = 0; | ||
246 | } | ||
247 | return retval; | ||
248 | case WDIOC_KEEPALIVE: | ||
249 | tco_timer_keepalive(); | ||
250 | return 0; | ||
251 | case WDIOC_SETTIMEOUT: | ||
252 | if (get_user(new_heartbeat, p)) | ||
253 | return -EFAULT; | ||
254 | if (tco_timer_set_heartbeat(new_heartbeat)) | ||
255 | return -EINVAL; | ||
256 | tco_timer_keepalive(); | ||
257 | /* Fall through */ | ||
258 | case WDIOC_GETTIMEOUT: | ||
259 | return put_user(heartbeat, p); | ||
260 | default: | ||
261 | return -ENOTTY; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | /* | ||
266 | * Kernel Interfaces | ||
267 | */ | ||
268 | |||
269 | static const struct file_operations nv_tco_fops = { | ||
270 | .owner = THIS_MODULE, | ||
271 | .llseek = no_llseek, | ||
272 | .write = nv_tco_write, | ||
273 | .unlocked_ioctl = nv_tco_ioctl, | ||
274 | .open = nv_tco_open, | ||
275 | .release = nv_tco_release, | ||
276 | }; | ||
277 | |||
278 | static struct miscdevice nv_tco_miscdev = { | ||
279 | .minor = WATCHDOG_MINOR, | ||
280 | .name = "watchdog", | ||
281 | .fops = &nv_tco_fops, | ||
282 | }; | ||
283 | |||
284 | /* | ||
285 | * Data for PCI driver interface | ||
286 | * | ||
287 | * This data only exists for exporting the supported | ||
288 | * PCI ids via MODULE_DEVICE_TABLE. We do not actually | ||
289 | * register a pci_driver, because someone else might one day | ||
290 | * want to register another driver on the same PCI id. | ||
291 | */ | ||
292 | static struct pci_device_id tco_pci_tbl[] = { | ||
293 | { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS, | ||
294 | PCI_ANY_ID, PCI_ANY_ID, }, | ||
295 | { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS, | ||
296 | PCI_ANY_ID, PCI_ANY_ID, }, | ||
297 | { 0, }, /* End of list */ | ||
298 | }; | ||
299 | MODULE_DEVICE_TABLE(pci, tco_pci_tbl); | ||
300 | |||
301 | /* | ||
302 | * Init & exit routines | ||
303 | */ | ||
304 | |||
305 | static unsigned char __init nv_tco_getdevice(void) | ||
306 | { | ||
307 | struct pci_dev *dev = NULL; | ||
308 | u32 val; | ||
309 | |||
310 | /* Find the PCI device */ | ||
311 | for_each_pci_dev(dev) { | ||
312 | if (pci_match_id(tco_pci_tbl, dev) != NULL) { | ||
313 | tco_pci = dev; | ||
314 | break; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | if (!tco_pci) | ||
319 | return 0; | ||
320 | |||
321 | /* Find the base io port */ | ||
322 | pci_read_config_dword(tco_pci, 0x64, &val); | ||
323 | val &= 0xffff; | ||
324 | if (val == 0x0001 || val == 0x0000) { | ||
325 | /* Something is wrong here, bar isn't setup */ | ||
326 | printk(KERN_ERR PFX "failed to get tcobase address\n"); | ||
327 | return 0; | ||
328 | } | ||
329 | val &= 0xff00; | ||
330 | tcobase = val + 0x40; | ||
331 | |||
332 | if (!request_region(tcobase, 0x10, "NV TCO")) { | ||
333 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
334 | tcobase); | ||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | /* Set a reasonable heartbeat before we stop the timer */ | ||
339 | tco_timer_set_heartbeat(30); | ||
340 | |||
341 | /* | ||
342 | * Stop the TCO before we change anything so we don't race with | ||
343 | * a zeroed timer. | ||
344 | */ | ||
345 | tco_timer_keepalive(); | ||
346 | tco_timer_stop(); | ||
347 | |||
348 | /* Disable SMI caused by TCO */ | ||
349 | if (!request_region(MCP51_SMI_EN(tcobase), 4, "NV TCO")) { | ||
350 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
351 | MCP51_SMI_EN(tcobase)); | ||
352 | goto out; | ||
353 | } | ||
354 | val = inl(MCP51_SMI_EN(tcobase)); | ||
355 | val &= ~MCP51_SMI_EN_TCO; | ||
356 | outl(val, MCP51_SMI_EN(tcobase)); | ||
357 | val = inl(MCP51_SMI_EN(tcobase)); | ||
358 | release_region(MCP51_SMI_EN(tcobase), 4); | ||
359 | if (val & MCP51_SMI_EN_TCO) { | ||
360 | printk(KERN_ERR PFX "Could not disable SMI caused by TCO\n"); | ||
361 | goto out; | ||
362 | } | ||
363 | |||
364 | /* Check chipset's NO_REBOOT bit */ | ||
365 | pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val); | ||
366 | val |= MCP51_SMBUS_SETUP_B_TCO_REBOOT; | ||
367 | pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val); | ||
368 | pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val); | ||
369 | if (!(val & MCP51_SMBUS_SETUP_B_TCO_REBOOT)) { | ||
370 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot " | ||
371 | "disabled by hardware\n"); | ||
372 | goto out; | ||
373 | } | ||
374 | |||
375 | return 1; | ||
376 | out: | ||
377 | release_region(tcobase, 0x10); | ||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | static int __devinit nv_tco_init(struct platform_device *dev) | ||
382 | { | ||
383 | int ret; | ||
384 | |||
385 | /* Check whether or not the hardware watchdog is there */ | ||
386 | if (!nv_tco_getdevice()) | ||
387 | return -ENODEV; | ||
388 | |||
389 | /* Check to see if last reboot was due to watchdog timeout */ | ||
390 | printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n", | ||
391 | inl(TCO_STS(tcobase)) & TCO_STS_TCO2TO_STS ? "" : "not "); | ||
392 | |||
393 | /* Clear out the old status */ | ||
394 | outl(TCO_STS_RESET, TCO_STS(tcobase)); | ||
395 | |||
396 | /* | ||
397 | * Check that the heartbeat value is within it's range. | ||
398 | * If not, reset to the default. | ||
399 | */ | ||
400 | if (tco_timer_set_heartbeat(heartbeat)) { | ||
401 | heartbeat = WATCHDOG_HEARTBEAT; | ||
402 | tco_timer_set_heartbeat(heartbeat); | ||
403 | printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, " | ||
404 | "using %d\n", heartbeat); | ||
405 | } | ||
406 | |||
407 | ret = misc_register(&nv_tco_miscdev); | ||
408 | if (ret != 0) { | ||
409 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d " | ||
410 | "(err=%d)\n", WATCHDOG_MINOR, ret); | ||
411 | goto unreg_region; | ||
412 | } | ||
413 | |||
414 | clear_bit(0, &timer_alive); | ||
415 | |||
416 | tco_timer_stop(); | ||
417 | |||
418 | printk(KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec " | ||
419 | "(nowayout=%d)\n", tcobase, heartbeat, nowayout); | ||
420 | |||
421 | return 0; | ||
422 | |||
423 | unreg_region: | ||
424 | release_region(tcobase, 0x10); | ||
425 | return ret; | ||
426 | } | ||
427 | |||
428 | static void __devexit nv_tco_cleanup(void) | ||
429 | { | ||
430 | u32 val; | ||
431 | |||
432 | /* Stop the timer before we leave */ | ||
433 | if (!nowayout) | ||
434 | tco_timer_stop(); | ||
435 | |||
436 | /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ | ||
437 | pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val); | ||
438 | val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT; | ||
439 | pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val); | ||
440 | pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val); | ||
441 | if (val & MCP51_SMBUS_SETUP_B_TCO_REBOOT) { | ||
442 | printk(KERN_CRIT PFX "Couldn't unset REBOOT bit. Machine may " | ||
443 | "soon reset\n"); | ||
444 | } | ||
445 | |||
446 | /* Deregister */ | ||
447 | misc_deregister(&nv_tco_miscdev); | ||
448 | release_region(tcobase, 0x10); | ||
449 | } | ||
450 | |||
451 | static int __devexit nv_tco_remove(struct platform_device *dev) | ||
452 | { | ||
453 | if (tcobase) | ||
454 | nv_tco_cleanup(); | ||
455 | |||
456 | return 0; | ||
457 | } | ||
458 | |||
459 | static void nv_tco_shutdown(struct platform_device *dev) | ||
460 | { | ||
461 | tco_timer_stop(); | ||
462 | } | ||
463 | |||
464 | static struct platform_driver nv_tco_driver = { | ||
465 | .probe = nv_tco_init, | ||
466 | .remove = __devexit_p(nv_tco_remove), | ||
467 | .shutdown = nv_tco_shutdown, | ||
468 | .driver = { | ||
469 | .owner = THIS_MODULE, | ||
470 | .name = TCO_MODULE_NAME, | ||
471 | }, | ||
472 | }; | ||
473 | |||
474 | static int __init nv_tco_init_module(void) | ||
475 | { | ||
476 | int err; | ||
477 | |||
478 | printk(KERN_INFO PFX "NV TCO WatchDog Timer Driver v%s\n", | ||
479 | TCO_VERSION); | ||
480 | |||
481 | err = platform_driver_register(&nv_tco_driver); | ||
482 | if (err) | ||
483 | return err; | ||
484 | |||
485 | nv_tco_platform_device = platform_device_register_simple( | ||
486 | TCO_MODULE_NAME, -1, NULL, 0); | ||
487 | if (IS_ERR(nv_tco_platform_device)) { | ||
488 | err = PTR_ERR(nv_tco_platform_device); | ||
489 | goto unreg_platform_driver; | ||
490 | } | ||
491 | |||
492 | return 0; | ||
493 | |||
494 | unreg_platform_driver: | ||
495 | platform_driver_unregister(&nv_tco_driver); | ||
496 | return err; | ||
497 | } | ||
498 | |||
499 | static void __exit nv_tco_cleanup_module(void) | ||
500 | { | ||
501 | platform_device_unregister(nv_tco_platform_device); | ||
502 | platform_driver_unregister(&nv_tco_driver); | ||
503 | printk(KERN_INFO PFX "NV TCO Watchdog Module Unloaded.\n"); | ||
504 | } | ||
505 | |||
506 | module_init(nv_tco_init_module); | ||
507 | module_exit(nv_tco_cleanup_module); | ||
508 | |||
509 | MODULE_AUTHOR("Mike Waychison"); | ||
510 | MODULE_DESCRIPTION("TCO timer driver for NV chipsets"); | ||
511 | MODULE_LICENSE("GPL"); | ||
512 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/nv_tco.h b/drivers/watchdog/nv_tco.h new file mode 100644 index 00000000000..c2d1d04e055 --- /dev/null +++ b/drivers/watchdog/nv_tco.h | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * nv_tco: TCO timer driver for nVidia chipsets. | ||
3 | * | ||
4 | * (c) Copyright 2005 Google Inc., All Rights Reserved. | ||
5 | * | ||
6 | * Supported Chipsets: | ||
7 | * - MCP51/MCP55 | ||
8 | * | ||
9 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights | ||
10 | * Reserved. | ||
11 | * http://www.kernelconcepts.de | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version | ||
16 | * 2 of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * Neither kernel concepts nor Nils Faerber admit liability nor provide | ||
19 | * warranty for any of this software. This material is provided | ||
20 | * "AS-IS" and at no charge. | ||
21 | * | ||
22 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de> | ||
23 | * developed for | ||
24 | * Jentro AG, Haar/Munich (Germany) | ||
25 | * | ||
26 | * TCO timer driver for NV chipsets | ||
27 | * based on softdog.c by Alan Cox <alan@redhat.com> | ||
28 | */ | ||
29 | |||
30 | /* | ||
31 | * Some address definitions for the TCO | ||
32 | */ | ||
33 | |||
34 | #define TCO_RLD(base) ((base) + 0x00) /* TCO Timer Reload and Current Value */ | ||
35 | #define TCO_TMR(base) ((base) + 0x01) /* TCO Timer Initial Value */ | ||
36 | |||
37 | #define TCO_STS(base) ((base) + 0x04) /* TCO Status Register */ | ||
38 | /* | ||
39 | * TCO Boot Status bit: set on TCO reset, reset by software or standby | ||
40 | * power-good (survives reboots), unfortunately this bit is never | ||
41 | * set. | ||
42 | */ | ||
43 | # define TCO_STS_BOOT_STS (1 << 9) | ||
44 | /* | ||
45 | * first and 2nd timeout status bits, these also survive a warm boot, | ||
46 | * and they work, so we use them. | ||
47 | */ | ||
48 | # define TCO_STS_TCO_INT_STS (1 << 1) | ||
49 | # define TCO_STS_TCO2TO_STS (1 << 10) | ||
50 | # define TCO_STS_RESET (TCO_STS_BOOT_STS | TCO_STS_TCO2TO_STS | \ | ||
51 | TCO_STS_TCO_INT_STS) | ||
52 | |||
53 | #define TCO_CNT(base) ((base) + 0x08) /* TCO Control Register */ | ||
54 | # define TCO_CNT_TCOHALT (1 << 12) | ||
55 | |||
56 | #define MCP51_SMBUS_SETUP_B 0xe8 | ||
57 | # define MCP51_SMBUS_SETUP_B_TCO_REBOOT (1 << 25) | ||
58 | |||
59 | /* | ||
60 | * The SMI_EN register is at the base io address + 0x04, | ||
61 | * while TCOBASE is + 0x40. | ||
62 | */ | ||
63 | #define MCP51_SMI_EN(base) ((base) - 0x40 + 0x04) | ||
64 | # define MCP51_SMI_EN_TCO ((1 << 4) | (1 << 5)) | ||
diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c new file mode 100644 index 00000000000..808372883e8 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.c | |||
@@ -0,0 +1,480 @@ | |||
1 | /* | ||
2 | * sp5100_tco : TCO timer driver for sp5100 chipsets | ||
3 | * | ||
4 | * (c) Copyright 2009 Google Inc., All Rights Reserved. | ||
5 | * | ||
6 | * Based on i8xx_tco.c: | ||
7 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights | ||
8 | * Reserved. | ||
9 | * http://www.kernelconcepts.de | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version | ||
14 | * 2 of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide" | ||
17 | */ | ||
18 | |||
19 | /* | ||
20 | * Includes, defines, variables, module parameters, ... | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/moduleparam.h> | ||
25 | #include <linux/types.h> | ||
26 | #include <linux/miscdevice.h> | ||
27 | #include <linux/watchdog.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/fs.h> | ||
30 | #include <linux/pci.h> | ||
31 | #include <linux/ioport.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/uaccess.h> | ||
34 | #include <linux/io.h> | ||
35 | |||
36 | #include "sp5100_tco.h" | ||
37 | |||
38 | /* Module and version information */ | ||
39 | #define TCO_VERSION "0.01" | ||
40 | #define TCO_MODULE_NAME "SP5100 TCO timer" | ||
41 | #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION | ||
42 | #define PFX TCO_MODULE_NAME ": " | ||
43 | |||
44 | /* internal variables */ | ||
45 | static void __iomem *tcobase; | ||
46 | static unsigned int pm_iobase; | ||
47 | static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ | ||
48 | static unsigned long timer_alive; | ||
49 | static char tco_expect_close; | ||
50 | static struct pci_dev *sp5100_tco_pci; | ||
51 | |||
52 | /* the watchdog platform device */ | ||
53 | static struct platform_device *sp5100_tco_platform_device; | ||
54 | |||
55 | /* module parameters */ | ||
56 | |||
57 | #define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */ | ||
58 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
59 | module_param(heartbeat, int, 0); | ||
60 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" | ||
61 | __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
62 | |||
63 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
64 | module_param(nowayout, int, 0); | ||
65 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started" | ||
66 | " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
67 | |||
68 | /* | ||
69 | * Some TCO specific functions | ||
70 | */ | ||
71 | static void tco_timer_start(void) | ||
72 | { | ||
73 | u32 val; | ||
74 | unsigned long flags; | ||
75 | |||
76 | spin_lock_irqsave(&tco_lock, flags); | ||
77 | val = readl(SP5100_WDT_CONTROL(tcobase)); | ||
78 | val |= SP5100_WDT_START_STOP_BIT; | ||
79 | writel(val, SP5100_WDT_CONTROL(tcobase)); | ||
80 | spin_unlock_irqrestore(&tco_lock, flags); | ||
81 | } | ||
82 | |||
83 | static void tco_timer_stop(void) | ||
84 | { | ||
85 | u32 val; | ||
86 | unsigned long flags; | ||
87 | |||
88 | spin_lock_irqsave(&tco_lock, flags); | ||
89 | val = readl(SP5100_WDT_CONTROL(tcobase)); | ||
90 | val &= ~SP5100_WDT_START_STOP_BIT; | ||
91 | writel(val, SP5100_WDT_CONTROL(tcobase)); | ||
92 | spin_unlock_irqrestore(&tco_lock, flags); | ||
93 | } | ||
94 | |||
95 | static void tco_timer_keepalive(void) | ||
96 | { | ||
97 | u32 val; | ||
98 | unsigned long flags; | ||
99 | |||
100 | spin_lock_irqsave(&tco_lock, flags); | ||
101 | val = readl(SP5100_WDT_CONTROL(tcobase)); | ||
102 | val |= SP5100_WDT_TRIGGER_BIT; | ||
103 | writel(val, SP5100_WDT_CONTROL(tcobase)); | ||
104 | spin_unlock_irqrestore(&tco_lock, flags); | ||
105 | } | ||
106 | |||
107 | static int tco_timer_set_heartbeat(int t) | ||
108 | { | ||
109 | unsigned long flags; | ||
110 | |||
111 | if (t < 0 || t > 0xffff) | ||
112 | return -EINVAL; | ||
113 | |||
114 | /* Write new heartbeat to watchdog */ | ||
115 | spin_lock_irqsave(&tco_lock, flags); | ||
116 | writel(t, SP5100_WDT_COUNT(tcobase)); | ||
117 | spin_unlock_irqrestore(&tco_lock, flags); | ||
118 | |||
119 | heartbeat = t; | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | /* | ||
124 | * /dev/watchdog handling | ||
125 | */ | ||
126 | |||
127 | static int sp5100_tco_open(struct inode *inode, struct file *file) | ||
128 | { | ||
129 | /* /dev/watchdog can only be opened once */ | ||
130 | if (test_and_set_bit(0, &timer_alive)) | ||
131 | return -EBUSY; | ||
132 | |||
133 | /* Reload and activate timer */ | ||
134 | tco_timer_start(); | ||
135 | tco_timer_keepalive(); | ||
136 | return nonseekable_open(inode, file); | ||
137 | } | ||
138 | |||
139 | static int sp5100_tco_release(struct inode *inode, struct file *file) | ||
140 | { | ||
141 | /* Shut off the timer. */ | ||
142 | if (tco_expect_close == 42) { | ||
143 | tco_timer_stop(); | ||
144 | } else { | ||
145 | printk(KERN_CRIT PFX | ||
146 | "Unexpected close, not stopping watchdog!\n"); | ||
147 | tco_timer_keepalive(); | ||
148 | } | ||
149 | clear_bit(0, &timer_alive); | ||
150 | tco_expect_close = 0; | ||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | static ssize_t sp5100_tco_write(struct file *file, const char __user *data, | ||
155 | size_t len, loff_t *ppos) | ||
156 | { | ||
157 | /* See if we got the magic character 'V' and reload the timer */ | ||
158 | if (len) { | ||
159 | if (!nowayout) { | ||
160 | size_t i; | ||
161 | |||
162 | /* note: just in case someone wrote the magic character | ||
163 | * five months ago... */ | ||
164 | tco_expect_close = 0; | ||
165 | |||
166 | /* scan to see whether or not we got the magic character | ||
167 | */ | ||
168 | for (i = 0; i != len; i++) { | ||
169 | char c; | ||
170 | if (get_user(c, data + i)) | ||
171 | return -EFAULT; | ||
172 | if (c == 'V') | ||
173 | tco_expect_close = 42; | ||
174 | } | ||
175 | } | ||
176 | |||
177 | /* someone wrote to us, we should reload the timer */ | ||
178 | tco_timer_keepalive(); | ||
179 | } | ||
180 | return len; | ||
181 | } | ||
182 | |||
183 | static long sp5100_tco_ioctl(struct file *file, unsigned int cmd, | ||
184 | unsigned long arg) | ||
185 | { | ||
186 | int new_options, retval = -EINVAL; | ||
187 | int new_heartbeat; | ||
188 | void __user *argp = (void __user *)arg; | ||
189 | int __user *p = argp; | ||
190 | static const struct watchdog_info ident = { | ||
191 | .options = WDIOF_SETTIMEOUT | | ||
192 | WDIOF_KEEPALIVEPING | | ||
193 | WDIOF_MAGICCLOSE, | ||
194 | .firmware_version = 0, | ||
195 | .identity = TCO_MODULE_NAME, | ||
196 | }; | ||
197 | |||
198 | switch (cmd) { | ||
199 | case WDIOC_GETSUPPORT: | ||
200 | return copy_to_user(argp, &ident, | ||
201 | sizeof(ident)) ? -EFAULT : 0; | ||
202 | case WDIOC_GETSTATUS: | ||
203 | case WDIOC_GETBOOTSTATUS: | ||
204 | return put_user(0, p); | ||
205 | case WDIOC_SETOPTIONS: | ||
206 | if (get_user(new_options, p)) | ||
207 | return -EFAULT; | ||
208 | if (new_options & WDIOS_DISABLECARD) { | ||
209 | tco_timer_stop(); | ||
210 | retval = 0; | ||
211 | } | ||
212 | if (new_options & WDIOS_ENABLECARD) { | ||
213 | tco_timer_start(); | ||
214 | tco_timer_keepalive(); | ||
215 | retval = 0; | ||
216 | } | ||
217 | return retval; | ||
218 | case WDIOC_KEEPALIVE: | ||
219 | tco_timer_keepalive(); | ||
220 | return 0; | ||
221 | case WDIOC_SETTIMEOUT: | ||
222 | if (get_user(new_heartbeat, p)) | ||
223 | return -EFAULT; | ||
224 | if (tco_timer_set_heartbeat(new_heartbeat)) | ||
225 | return -EINVAL; | ||
226 | tco_timer_keepalive(); | ||
227 | /* Fall through */ | ||
228 | case WDIOC_GETTIMEOUT: | ||
229 | return put_user(heartbeat, p); | ||
230 | default: | ||
231 | return -ENOTTY; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | /* | ||
236 | * Kernel Interfaces | ||
237 | */ | ||
238 | |||
239 | static const struct file_operations sp5100_tco_fops = { | ||
240 | .owner = THIS_MODULE, | ||
241 | .llseek = no_llseek, | ||
242 | .write = sp5100_tco_write, | ||
243 | .unlocked_ioctl = sp5100_tco_ioctl, | ||
244 | .open = sp5100_tco_open, | ||
245 | .release = sp5100_tco_release, | ||
246 | }; | ||
247 | |||
248 | static struct miscdevice sp5100_tco_miscdev = { | ||
249 | .minor = WATCHDOG_MINOR, | ||
250 | .name = "watchdog", | ||
251 | .fops = &sp5100_tco_fops, | ||
252 | }; | ||
253 | |||
254 | /* | ||
255 | * Data for PCI driver interface | ||
256 | * | ||
257 | * This data only exists for exporting the supported | ||
258 | * PCI ids via MODULE_DEVICE_TABLE. We do not actually | ||
259 | * register a pci_driver, because someone else might | ||
260 | * want to register another driver on the same PCI id. | ||
261 | */ | ||
262 | static struct pci_device_id sp5100_tco_pci_tbl[] = { | ||
263 | { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID, | ||
264 | PCI_ANY_ID, }, | ||
265 | { 0, }, /* End of list */ | ||
266 | }; | ||
267 | MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); | ||
268 | |||
269 | /* | ||
270 | * Init & exit routines | ||
271 | */ | ||
272 | |||
273 | static unsigned char __devinit sp5100_tco_setupdevice(void) | ||
274 | { | ||
275 | struct pci_dev *dev = NULL; | ||
276 | u32 val; | ||
277 | |||
278 | /* Match the PCI device */ | ||
279 | for_each_pci_dev(dev) { | ||
280 | if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) { | ||
281 | sp5100_tco_pci = dev; | ||
282 | break; | ||
283 | } | ||
284 | } | ||
285 | |||
286 | if (!sp5100_tco_pci) | ||
287 | return 0; | ||
288 | |||
289 | /* Request the IO ports used by this driver */ | ||
290 | pm_iobase = SP5100_IO_PM_INDEX_REG; | ||
291 | if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, "SP5100 TCO")) { | ||
292 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
293 | pm_iobase); | ||
294 | goto exit; | ||
295 | } | ||
296 | |||
297 | /* Find the watchdog base address. */ | ||
298 | outb(SP5100_PM_WATCHDOG_BASE3, SP5100_IO_PM_INDEX_REG); | ||
299 | val = inb(SP5100_IO_PM_DATA_REG); | ||
300 | outb(SP5100_PM_WATCHDOG_BASE2, SP5100_IO_PM_INDEX_REG); | ||
301 | val = val << 8 | inb(SP5100_IO_PM_DATA_REG); | ||
302 | outb(SP5100_PM_WATCHDOG_BASE1, SP5100_IO_PM_INDEX_REG); | ||
303 | val = val << 8 | inb(SP5100_IO_PM_DATA_REG); | ||
304 | outb(SP5100_PM_WATCHDOG_BASE0, SP5100_IO_PM_INDEX_REG); | ||
305 | /* Low three bits of BASE0 are reserved. */ | ||
306 | val = val << 8 | (inb(SP5100_IO_PM_DATA_REG) & 0xf8); | ||
307 | |||
308 | tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); | ||
309 | if (tcobase == 0) { | ||
310 | printk(KERN_ERR PFX "failed to get tcobase address\n"); | ||
311 | goto unreg_region; | ||
312 | } | ||
313 | |||
314 | /* Enable watchdog decode bit */ | ||
315 | pci_read_config_dword(sp5100_tco_pci, | ||
316 | SP5100_PCI_WATCHDOG_MISC_REG, | ||
317 | &val); | ||
318 | |||
319 | val |= SP5100_PCI_WATCHDOG_DECODE_EN; | ||
320 | |||
321 | pci_write_config_dword(sp5100_tco_pci, | ||
322 | SP5100_PCI_WATCHDOG_MISC_REG, | ||
323 | val); | ||
324 | |||
325 | /* Enable Watchdog timer and set the resolution to 1 sec. */ | ||
326 | outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG); | ||
327 | val = inb(SP5100_IO_PM_DATA_REG); | ||
328 | val |= SP5100_PM_WATCHDOG_SECOND_RES; | ||
329 | val &= ~SP5100_PM_WATCHDOG_DISABLE; | ||
330 | outb(val, SP5100_IO_PM_DATA_REG); | ||
331 | |||
332 | /* Check that the watchdog action is set to reset the system. */ | ||
333 | val = readl(SP5100_WDT_CONTROL(tcobase)); | ||
334 | val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; | ||
335 | writel(val, SP5100_WDT_CONTROL(tcobase)); | ||
336 | |||
337 | /* Set a reasonable heartbeat before we stop the timer */ | ||
338 | tco_timer_set_heartbeat(heartbeat); | ||
339 | |||
340 | /* | ||
341 | * Stop the TCO before we change anything so we don't race with | ||
342 | * a zeroed timer. | ||
343 | */ | ||
344 | tco_timer_stop(); | ||
345 | |||
346 | /* Done */ | ||
347 | return 1; | ||
348 | |||
349 | iounmap(tcobase); | ||
350 | unreg_region: | ||
351 | release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); | ||
352 | exit: | ||
353 | return 0; | ||
354 | } | ||
355 | |||
356 | static int __devinit sp5100_tco_init(struct platform_device *dev) | ||
357 | { | ||
358 | int ret; | ||
359 | u32 val; | ||
360 | |||
361 | /* Check whether or not the hardware watchdog is there. If found, then | ||
362 | * set it up. | ||
363 | */ | ||
364 | if (!sp5100_tco_setupdevice()) | ||
365 | return -ENODEV; | ||
366 | |||
367 | /* Check to see if last reboot was due to watchdog timeout */ | ||
368 | printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n", | ||
369 | readl(SP5100_WDT_CONTROL(tcobase)) & SP5100_PM_WATCHDOG_FIRED ? | ||
370 | "" : "not "); | ||
371 | |||
372 | /* Clear out the old status */ | ||
373 | val = readl(SP5100_WDT_CONTROL(tcobase)); | ||
374 | val &= ~SP5100_PM_WATCHDOG_FIRED; | ||
375 | writel(val, SP5100_WDT_CONTROL(tcobase)); | ||
376 | |||
377 | /* | ||
378 | * Check that the heartbeat value is within it's range. | ||
379 | * If not, reset to the default. | ||
380 | */ | ||
381 | if (tco_timer_set_heartbeat(heartbeat)) { | ||
382 | heartbeat = WATCHDOG_HEARTBEAT; | ||
383 | tco_timer_set_heartbeat(heartbeat); | ||
384 | } | ||
385 | |||
386 | ret = misc_register(&sp5100_tco_miscdev); | ||
387 | if (ret != 0) { | ||
388 | printk(KERN_ERR PFX "cannot register miscdev on minor=" | ||
389 | "%d (err=%d)\n", | ||
390 | WATCHDOG_MINOR, ret); | ||
391 | goto exit; | ||
392 | } | ||
393 | |||
394 | clear_bit(0, &timer_alive); | ||
395 | |||
396 | printk(KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec" | ||
397 | " (nowayout=%d)\n", | ||
398 | tcobase, heartbeat, nowayout); | ||
399 | |||
400 | return 0; | ||
401 | |||
402 | exit: | ||
403 | iounmap(tcobase); | ||
404 | release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); | ||
405 | return ret; | ||
406 | } | ||
407 | |||
408 | static void __devexit sp5100_tco_cleanup(void) | ||
409 | { | ||
410 | /* Stop the timer before we leave */ | ||
411 | if (!nowayout) | ||
412 | tco_timer_stop(); | ||
413 | |||
414 | /* Deregister */ | ||
415 | misc_deregister(&sp5100_tco_miscdev); | ||
416 | iounmap(tcobase); | ||
417 | release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); | ||
418 | } | ||
419 | |||
420 | static int __devexit sp5100_tco_remove(struct platform_device *dev) | ||
421 | { | ||
422 | if (tcobase) | ||
423 | sp5100_tco_cleanup(); | ||
424 | return 0; | ||
425 | } | ||
426 | |||
427 | static void sp5100_tco_shutdown(struct platform_device *dev) | ||
428 | { | ||
429 | tco_timer_stop(); | ||
430 | } | ||
431 | |||
432 | static struct platform_driver sp5100_tco_driver = { | ||
433 | .probe = sp5100_tco_init, | ||
434 | .remove = __devexit_p(sp5100_tco_remove), | ||
435 | .shutdown = sp5100_tco_shutdown, | ||
436 | .driver = { | ||
437 | .owner = THIS_MODULE, | ||
438 | .name = TCO_MODULE_NAME, | ||
439 | }, | ||
440 | }; | ||
441 | |||
442 | static int __init sp5100_tco_init_module(void) | ||
443 | { | ||
444 | int err; | ||
445 | |||
446 | printk(KERN_INFO PFX "SP5100 TCO WatchDog Timer Driver v%s\n", | ||
447 | TCO_VERSION); | ||
448 | |||
449 | err = platform_driver_register(&sp5100_tco_driver); | ||
450 | if (err) | ||
451 | return err; | ||
452 | |||
453 | sp5100_tco_platform_device = platform_device_register_simple( | ||
454 | TCO_MODULE_NAME, -1, NULL, 0); | ||
455 | if (IS_ERR(sp5100_tco_platform_device)) { | ||
456 | err = PTR_ERR(sp5100_tco_platform_device); | ||
457 | goto unreg_platform_driver; | ||
458 | } | ||
459 | |||
460 | return 0; | ||
461 | |||
462 | unreg_platform_driver: | ||
463 | platform_driver_unregister(&sp5100_tco_driver); | ||
464 | return err; | ||
465 | } | ||
466 | |||
467 | static void __exit sp5100_tco_cleanup_module(void) | ||
468 | { | ||
469 | platform_device_unregister(sp5100_tco_platform_device); | ||
470 | platform_driver_unregister(&sp5100_tco_driver); | ||
471 | printk(KERN_INFO PFX "SP5100 TCO Watchdog Module Unloaded.\n"); | ||
472 | } | ||
473 | |||
474 | module_init(sp5100_tco_init_module); | ||
475 | module_exit(sp5100_tco_cleanup_module); | ||
476 | |||
477 | MODULE_AUTHOR("Priyanka Gupta"); | ||
478 | MODULE_DESCRIPTION("TCO timer driver for SP5100 chipset"); | ||
479 | MODULE_LICENSE("GPL"); | ||
480 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h new file mode 100644 index 00000000000..a5a16cc90a3 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /* | ||
2 | * sp5100_tco: TCO timer driver for sp5100 chipsets. | ||
3 | * | ||
4 | * (c) Copyright 2009 Google Inc., All Rights Reserved. | ||
5 | * | ||
6 | * TCO timer driver for sp5100 chipsets | ||
7 | */ | ||
8 | |||
9 | /* | ||
10 | * Some address definitions for the Watchdog | ||
11 | */ | ||
12 | |||
13 | #define SP5100_WDT_MEM_MAP_SIZE 0x08 | ||
14 | #define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */ | ||
15 | #define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */ | ||
16 | |||
17 | #define SP5100_WDT_START_STOP_BIT 1 | ||
18 | #define SP5100_WDT_TRIGGER_BIT (1 << 7) | ||
19 | |||
20 | #define SP5100_PCI_WATCHDOG_MISC_REG 0x41 | ||
21 | #define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3) | ||
22 | |||
23 | #define SP5100_PM_IOPORTS_SIZE 0x02 | ||
24 | |||
25 | /* These two IO registers are hardcoded and there doesn't seem to be a way to | ||
26 | * read them from a register. | ||
27 | */ | ||
28 | #define SP5100_IO_PM_INDEX_REG 0xCD6 | ||
29 | #define SP5100_IO_PM_DATA_REG 0xCD7 | ||
30 | |||
31 | #define SP5100_PM_WATCHDOG_CONTROL 0x69 | ||
32 | #define SP5100_PM_WATCHDOG_BASE0 0x6C | ||
33 | #define SP5100_PM_WATCHDOG_BASE1 0x6D | ||
34 | #define SP5100_PM_WATCHDOG_BASE2 0x6E | ||
35 | #define SP5100_PM_WATCHDOG_BASE3 0x6F | ||
36 | |||
37 | #define SP5100_PM_WATCHDOG_FIRED (1 << 1) | ||
38 | #define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2) | ||
39 | |||
40 | #define SP5100_PM_WATCHDOG_DISABLE 1 | ||
41 | #define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1) | ||
diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c index 0f5288df009..e5c91d4404e 100644 --- a/drivers/watchdog/w83627hf_wdt.c +++ b/drivers/watchdog/w83627hf_wdt.c | |||
@@ -42,7 +42,7 @@ | |||
42 | 42 | ||
43 | #include <asm/system.h> | 43 | #include <asm/system.h> |
44 | 44 | ||
45 | #define WATCHDOG_NAME "w83627hf/thf/hg WDT" | 45 | #define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT" |
46 | #define PFX WATCHDOG_NAME ": " | 46 | #define PFX WATCHDOG_NAME ": " |
47 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | 47 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ |
48 | 48 | ||
@@ -89,7 +89,7 @@ static void w83627hf_select_wd_register(void) | |||
89 | c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */ | 89 | c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */ |
90 | outb_p(0x2b, WDT_EFER); | 90 | outb_p(0x2b, WDT_EFER); |
91 | outb_p(c, WDT_EFDR); /* set GPIO3 to WDT0 */ | 91 | outb_p(c, WDT_EFDR); /* set GPIO3 to WDT0 */ |
92 | } else if (c == 0x88) { /* W83627EHF */ | 92 | } else if (c == 0x88 || c == 0xa0) { /* W83627EHF / W83627DHG */ |
93 | outb_p(0x2d, WDT_EFER); /* select GPIO5 */ | 93 | outb_p(0x2d, WDT_EFER); /* select GPIO5 */ |
94 | c = inb_p(WDT_EFDR) & ~0x01; /* PIN77 -> WDT0# */ | 94 | c = inb_p(WDT_EFDR) & ~0x01; /* PIN77 -> WDT0# */ |
95 | outb_p(0x2d, WDT_EFER); | 95 | outb_p(0x2d, WDT_EFER); |
@@ -129,6 +129,8 @@ static void w83627hf_init(void) | |||
129 | t = inb_p(WDT_EFDR); /* read CRF5 */ | 129 | t = inb_p(WDT_EFDR); /* read CRF5 */ |
130 | t &= ~0x0C; /* set second mode & disable keyboard | 130 | t &= ~0x0C; /* set second mode & disable keyboard |
131 | turning off watchdog */ | 131 | turning off watchdog */ |
132 | t |= 0x02; /* enable the WDTO# output low pulse | ||
133 | to the KBRST# pin (PIN60) */ | ||
132 | outb_p(t, WDT_EFDR); /* Write back to CRF5 */ | 134 | outb_p(t, WDT_EFDR); /* Write back to CRF5 */ |
133 | 135 | ||
134 | outb_p(0xF7, WDT_EFER); /* Select CRF7 */ | 136 | outb_p(0xF7, WDT_EFER); /* Select CRF7 */ |
@@ -321,7 +323,7 @@ static int __init wdt_init(void) | |||
321 | { | 323 | { |
322 | int ret; | 324 | int ret; |
323 | 325 | ||
324 | printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n"); | 326 | printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG/DHG Super I/O chip initialising.\n"); |
325 | 327 | ||
326 | if (wdt_set_heartbeat(timeout)) { | 328 | if (wdt_set_heartbeat(timeout)) { |
327 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | 329 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); |