diff options
author | John Crispin <blogic@openwrt.org> | 2011-05-05 17:00:23 -0400 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2011-05-19 04:55:43 -0400 |
commit | 2f58b8d04e680ec13157ba6eee44455438c56d5f (patch) | |
tree | 243a5af8cc075e3b26c0b79946d1a706afb108b4 /drivers | |
parent | f1f0ceaada9d040a41023017c87abb1d651b44af (diff) |
MIPS: Lantiq: Add watchdog support
This patch adds the driver for the watchdog found inside the Lantiq SoC family.
Signed-off-by: John Crispin <blogic@openwrt.org>
Signed-off-by: Ralph Hempel <ralph.hempel@lantiq.com>
Cc: Wim Van Sebroeck <wim@iguana.be>
Cc: linux-mips@linux-mips.org
Cc: linux-watchdog@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/2327/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/watchdog/Kconfig | 6 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/lantiq_wdt.c | 261 |
3 files changed, 268 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1b0f98bc51b5..022f9eb0b7bf 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -990,6 +990,12 @@ config BCM63XX_WDT | |||
990 | To compile this driver as a loadable module, choose M here. | 990 | To compile this driver as a loadable module, choose M here. |
991 | The module will be called bcm63xx_wdt. | 991 | The module will be called bcm63xx_wdt. |
992 | 992 | ||
993 | config LANTIQ_WDT | ||
994 | tristate "Lantiq SoC watchdog" | ||
995 | depends on LANTIQ | ||
996 | help | ||
997 | Hardware driver for the Lantiq SoC Watchdog Timer. | ||
998 | |||
993 | # PARISC Architecture | 999 | # PARISC Architecture |
994 | 1000 | ||
995 | # POWERPC Architecture | 1001 | # POWERPC Architecture |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3f8608b922a7..ed26f7094e47 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -123,6 +123,7 @@ obj-$(CONFIG_AR7_WDT) += ar7_wdt.o | |||
123 | obj-$(CONFIG_TXX9_WDT) += txx9wdt.o | 123 | obj-$(CONFIG_TXX9_WDT) += txx9wdt.o |
124 | obj-$(CONFIG_OCTEON_WDT) += octeon-wdt.o | 124 | obj-$(CONFIG_OCTEON_WDT) += octeon-wdt.o |
125 | octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o | 125 | octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o |
126 | obj-$(CONFIG_LANTIQ_WDT) += lantiq_wdt.o | ||
126 | 127 | ||
127 | # PARISC Architecture | 128 | # PARISC Architecture |
128 | 129 | ||
diff --git a/drivers/watchdog/lantiq_wdt.c b/drivers/watchdog/lantiq_wdt.c new file mode 100644 index 000000000000..7d82adac1cb2 --- /dev/null +++ b/drivers/watchdog/lantiq_wdt.c | |||
@@ -0,0 +1,261 @@ | |||
1 | /* | ||
2 | * This program is free software; you can redistribute it and/or modify it | ||
3 | * under the terms of the GNU General Public License version 2 as published | ||
4 | * by the Free Software Foundation. | ||
5 | * | ||
6 | * Copyright (C) 2010 John Crispin <blogic@openwrt.org> | ||
7 | * Based on EP93xx wdt driver | ||
8 | */ | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/fs.h> | ||
12 | #include <linux/miscdevice.h> | ||
13 | #include <linux/watchdog.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/uaccess.h> | ||
16 | #include <linux/clk.h> | ||
17 | #include <linux/io.h> | ||
18 | |||
19 | #include <lantiq.h> | ||
20 | |||
21 | /* Section 3.4 of the datasheet | ||
22 | * The password sequence protects the WDT control register from unintended | ||
23 | * write actions, which might cause malfunction of the WDT. | ||
24 | * | ||
25 | * essentially the following two magic passwords need to be written to allow | ||
26 | * IO access to the WDT core | ||
27 | */ | ||
28 | #define LTQ_WDT_PW1 0x00BE0000 | ||
29 | #define LTQ_WDT_PW2 0x00DC0000 | ||
30 | |||
31 | #define LTQ_WDT_CR 0x0 /* watchdog control register */ | ||
32 | #define LTQ_WDT_SR 0x8 /* watchdog status register */ | ||
33 | |||
34 | #define LTQ_WDT_SR_EN (0x1 << 31) /* enable bit */ | ||
35 | #define LTQ_WDT_SR_PWD (0x3 << 26) /* turn on power */ | ||
36 | #define LTQ_WDT_SR_CLKDIV (0x3 << 24) /* turn on clock and set */ | ||
37 | /* divider to 0x40000 */ | ||
38 | #define LTQ_WDT_DIVIDER 0x40000 | ||
39 | #define LTQ_MAX_TIMEOUT ((1 << 16) - 1) /* the reload field is 16 bit */ | ||
40 | |||
41 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
42 | |||
43 | static void __iomem *ltq_wdt_membase; | ||
44 | static unsigned long ltq_io_region_clk_rate; | ||
45 | |||
46 | static unsigned long ltq_wdt_bootstatus; | ||
47 | static unsigned long ltq_wdt_in_use; | ||
48 | static int ltq_wdt_timeout = 30; | ||
49 | static int ltq_wdt_ok_to_close; | ||
50 | |||
51 | static void | ||
52 | ltq_wdt_enable(void) | ||
53 | { | ||
54 | ltq_wdt_timeout = ltq_wdt_timeout * | ||
55 | (ltq_io_region_clk_rate / LTQ_WDT_DIVIDER) + 0x1000; | ||
56 | if (ltq_wdt_timeout > LTQ_MAX_TIMEOUT) | ||
57 | ltq_wdt_timeout = LTQ_MAX_TIMEOUT; | ||
58 | |||
59 | /* write the first password magic */ | ||
60 | ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR); | ||
61 | /* write the second magic plus the configuration and new timeout */ | ||
62 | ltq_w32(LTQ_WDT_SR_EN | LTQ_WDT_SR_PWD | LTQ_WDT_SR_CLKDIV | | ||
63 | LTQ_WDT_PW2 | ltq_wdt_timeout, ltq_wdt_membase + LTQ_WDT_CR); | ||
64 | } | ||
65 | |||
66 | static void | ||
67 | ltq_wdt_disable(void) | ||
68 | { | ||
69 | /* write the first password magic */ | ||
70 | ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR); | ||
71 | /* write the second password magic with no config | ||
72 | * this turns the watchdog off | ||
73 | */ | ||
74 | ltq_w32(LTQ_WDT_PW2, ltq_wdt_membase + LTQ_WDT_CR); | ||
75 | } | ||
76 | |||
77 | static ssize_t | ||
78 | ltq_wdt_write(struct file *file, const char __user *data, | ||
79 | size_t len, loff_t *ppos) | ||
80 | { | ||
81 | if (len) { | ||
82 | if (!nowayout) { | ||
83 | size_t i; | ||
84 | |||
85 | ltq_wdt_ok_to_close = 0; | ||
86 | for (i = 0; i != len; i++) { | ||
87 | char c; | ||
88 | |||
89 | if (get_user(c, data + i)) | ||
90 | return -EFAULT; | ||
91 | if (c == 'V') | ||
92 | ltq_wdt_ok_to_close = 1; | ||
93 | else | ||
94 | ltq_wdt_ok_to_close = 0; | ||
95 | } | ||
96 | } | ||
97 | ltq_wdt_enable(); | ||
98 | } | ||
99 | |||
100 | return len; | ||
101 | } | ||
102 | |||
103 | static struct watchdog_info ident = { | ||
104 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | | ||
105 | WDIOF_CARDRESET, | ||
106 | .identity = "ltq_wdt", | ||
107 | }; | ||
108 | |||
109 | static long | ||
110 | ltq_wdt_ioctl(struct file *file, | ||
111 | unsigned int cmd, unsigned long arg) | ||
112 | { | ||
113 | int ret = -ENOTTY; | ||
114 | |||
115 | switch (cmd) { | ||
116 | case WDIOC_GETSUPPORT: | ||
117 | ret = copy_to_user((struct watchdog_info __user *)arg, &ident, | ||
118 | sizeof(ident)) ? -EFAULT : 0; | ||
119 | break; | ||
120 | |||
121 | case WDIOC_GETBOOTSTATUS: | ||
122 | ret = put_user(ltq_wdt_bootstatus, (int __user *)arg); | ||
123 | break; | ||
124 | |||
125 | case WDIOC_GETSTATUS: | ||
126 | ret = put_user(0, (int __user *)arg); | ||
127 | break; | ||
128 | |||
129 | case WDIOC_SETTIMEOUT: | ||
130 | ret = get_user(ltq_wdt_timeout, (int __user *)arg); | ||
131 | if (!ret) | ||
132 | ltq_wdt_enable(); | ||
133 | /* intentional drop through */ | ||
134 | case WDIOC_GETTIMEOUT: | ||
135 | ret = put_user(ltq_wdt_timeout, (int __user *)arg); | ||
136 | break; | ||
137 | |||
138 | case WDIOC_KEEPALIVE: | ||
139 | ltq_wdt_enable(); | ||
140 | ret = 0; | ||
141 | break; | ||
142 | } | ||
143 | return ret; | ||
144 | } | ||
145 | |||
146 | static int | ||
147 | ltq_wdt_open(struct inode *inode, struct file *file) | ||
148 | { | ||
149 | if (test_and_set_bit(0, <q_wdt_in_use)) | ||
150 | return -EBUSY; | ||
151 | ltq_wdt_in_use = 1; | ||
152 | ltq_wdt_enable(); | ||
153 | |||
154 | return nonseekable_open(inode, file); | ||
155 | } | ||
156 | |||
157 | static int | ||
158 | ltq_wdt_release(struct inode *inode, struct file *file) | ||
159 | { | ||
160 | if (ltq_wdt_ok_to_close) | ||
161 | ltq_wdt_disable(); | ||
162 | else | ||
163 | pr_err("ltq_wdt: watchdog closed without warning\n"); | ||
164 | ltq_wdt_ok_to_close = 0; | ||
165 | clear_bit(0, <q_wdt_in_use); | ||
166 | |||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static const struct file_operations ltq_wdt_fops = { | ||
171 | .owner = THIS_MODULE, | ||
172 | .write = ltq_wdt_write, | ||
173 | .unlocked_ioctl = ltq_wdt_ioctl, | ||
174 | .open = ltq_wdt_open, | ||
175 | .release = ltq_wdt_release, | ||
176 | .llseek = no_llseek, | ||
177 | }; | ||
178 | |||
179 | static struct miscdevice ltq_wdt_miscdev = { | ||
180 | .minor = WATCHDOG_MINOR, | ||
181 | .name = "watchdog", | ||
182 | .fops = <q_wdt_fops, | ||
183 | }; | ||
184 | |||
185 | static int __init | ||
186 | ltq_wdt_probe(struct platform_device *pdev) | ||
187 | { | ||
188 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
189 | struct clk *clk; | ||
190 | |||
191 | if (!res) { | ||
192 | dev_err(&pdev->dev, "cannot obtain I/O memory region"); | ||
193 | return -ENOENT; | ||
194 | } | ||
195 | res = devm_request_mem_region(&pdev->dev, res->start, | ||
196 | resource_size(res), dev_name(&pdev->dev)); | ||
197 | if (!res) { | ||
198 | dev_err(&pdev->dev, "cannot request I/O memory region"); | ||
199 | return -EBUSY; | ||
200 | } | ||
201 | ltq_wdt_membase = devm_ioremap_nocache(&pdev->dev, res->start, | ||
202 | resource_size(res)); | ||
203 | if (!ltq_wdt_membase) { | ||
204 | dev_err(&pdev->dev, "cannot remap I/O memory region\n"); | ||
205 | return -ENOMEM; | ||
206 | } | ||
207 | |||
208 | /* we do not need to enable the clock as it is always running */ | ||
209 | clk = clk_get(&pdev->dev, "io"); | ||
210 | WARN_ON(!clk); | ||
211 | ltq_io_region_clk_rate = clk_get_rate(clk); | ||
212 | clk_put(clk); | ||
213 | |||
214 | if (ltq_reset_cause() == LTQ_RST_CAUSE_WDTRST) | ||
215 | ltq_wdt_bootstatus = WDIOF_CARDRESET; | ||
216 | |||
217 | return misc_register(<q_wdt_miscdev); | ||
218 | } | ||
219 | |||
220 | static int __devexit | ||
221 | ltq_wdt_remove(struct platform_device *pdev) | ||
222 | { | ||
223 | misc_deregister(<q_wdt_miscdev); | ||
224 | |||
225 | if (ltq_wdt_membase) | ||
226 | iounmap(ltq_wdt_membase); | ||
227 | |||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | |||
232 | static struct platform_driver ltq_wdt_driver = { | ||
233 | .remove = __devexit_p(ltq_wdt_remove), | ||
234 | .driver = { | ||
235 | .name = "ltq_wdt", | ||
236 | .owner = THIS_MODULE, | ||
237 | }, | ||
238 | }; | ||
239 | |||
240 | static int __init | ||
241 | init_ltq_wdt(void) | ||
242 | { | ||
243 | return platform_driver_probe(<q_wdt_driver, ltq_wdt_probe); | ||
244 | } | ||
245 | |||
246 | static void __exit | ||
247 | exit_ltq_wdt(void) | ||
248 | { | ||
249 | return platform_driver_unregister(<q_wdt_driver); | ||
250 | } | ||
251 | |||
252 | module_init(init_ltq_wdt); | ||
253 | module_exit(exit_ltq_wdt); | ||
254 | |||
255 | module_param(nowayout, int, 0); | ||
256 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
257 | |||
258 | MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); | ||
259 | MODULE_DESCRIPTION("Lantiq SoC Watchdog"); | ||
260 | MODULE_LICENSE("GPL"); | ||
261 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||