Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ar7_wdt.c
Go to the documentation of this file.
1 /*
2  * drivers/watchdog/ar7_wdt.c
3  *
4  * Copyright (C) 2007 Nicolas Thill <[email protected]>
5  * Copyright (c) 2005 Enrik Berkhan <[email protected]>
6  *
7  * Some code taken from:
8  * National Semiconductor SCx200 Watchdog support
9  * Copyright (c) 2001,2002 Christer Weinigel <[email protected]>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24  */
25 
26 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
27 
28 #include <linux/module.h>
29 #include <linux/moduleparam.h>
30 #include <linux/errno.h>
31 #include <linux/init.h>
32 #include <linux/miscdevice.h>
33 #include <linux/platform_device.h>
34 #include <linux/watchdog.h>
35 #include <linux/fs.h>
36 #include <linux/ioport.h>
37 #include <linux/io.h>
38 #include <linux/uaccess.h>
39 #include <linux/clk.h>
40 
41 #include <asm/addrspace.h>
42 #include <asm/mach-ar7/ar7.h>
43 
44 #define LONGNAME "TI AR7 Watchdog Timer"
45 
46 MODULE_AUTHOR("Nicolas Thill <[email protected]>");
48 MODULE_LICENSE("GPL");
50 
51 static int margin = 60;
52 module_param(margin, int, 0);
53 MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
54 
55 static bool nowayout = WATCHDOG_NOWAYOUT;
56 module_param(nowayout, bool, 0);
57 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
58 
59 #define READ_REG(x) readl((void __iomem *)&(x))
60 #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
61 
62 struct ar7_wdt {
71 };
72 
73 static unsigned long wdt_is_open;
74 static unsigned expect_close;
75 static DEFINE_SPINLOCK(wdt_lock);
76 
77 /* XXX currently fixed, allows max margin ~68.72 secs */
78 #define prescale_value 0xffff
79 
80 /* Resource of the WDT registers */
81 static struct resource *ar7_regs_wdt;
82 /* Pointer to the remapped WDT IO space */
83 static struct ar7_wdt *ar7_wdt;
84 
85 static struct clk *vbus_clk;
86 
87 static void ar7_wdt_kick(u32 value)
88 {
89  WRITE_REG(ar7_wdt->kick_lock, 0x5555);
90  if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
91  WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
92  if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
93  WRITE_REG(ar7_wdt->kick, value);
94  return;
95  }
96  }
97  pr_err("failed to unlock WDT kick reg\n");
98 }
99 
100 static void ar7_wdt_prescale(u32 value)
101 {
102  WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
103  if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
104  WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
105  if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
106  WRITE_REG(ar7_wdt->prescale, value);
107  return;
108  }
109  }
110  pr_err("failed to unlock WDT prescale reg\n");
111 }
112 
113 static void ar7_wdt_change(u32 value)
114 {
115  WRITE_REG(ar7_wdt->change_lock, 0x6666);
116  if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
117  WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
118  if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
119  WRITE_REG(ar7_wdt->change, value);
120  return;
121  }
122  }
123  pr_err("failed to unlock WDT change reg\n");
124 }
125 
126 static void ar7_wdt_disable(u32 value)
127 {
128  WRITE_REG(ar7_wdt->disable_lock, 0x7777);
129  if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
130  WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
131  if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
132  WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
133  if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
134  WRITE_REG(ar7_wdt->disable, value);
135  return;
136  }
137  }
138  }
139  pr_err("failed to unlock WDT disable reg\n");
140 }
141 
142 static void ar7_wdt_update_margin(int new_margin)
143 {
144  u32 change;
145  u32 vbus_rate;
146 
147  vbus_rate = clk_get_rate(vbus_clk);
148  change = new_margin * (vbus_rate / prescale_value);
149  if (change < 1)
150  change = 1;
151  if (change > 0xffff)
152  change = 0xffff;
153  ar7_wdt_change(change);
154  margin = change * prescale_value / vbus_rate;
155  pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
156  margin, prescale_value, change, vbus_rate);
157 }
158 
159 static void ar7_wdt_enable_wdt(void)
160 {
161  pr_debug("enabling watchdog timer\n");
162  ar7_wdt_disable(1);
163  ar7_wdt_kick(1);
164 }
165 
166 static void ar7_wdt_disable_wdt(void)
167 {
168  pr_debug("disabling watchdog timer\n");
169  ar7_wdt_disable(0);
170 }
171 
172 static int ar7_wdt_open(struct inode *inode, struct file *file)
173 {
174  /* only allow one at a time */
175  if (test_and_set_bit(0, &wdt_is_open))
176  return -EBUSY;
177  ar7_wdt_enable_wdt();
178  expect_close = 0;
179 
180  return nonseekable_open(inode, file);
181 }
182 
183 static int ar7_wdt_release(struct inode *inode, struct file *file)
184 {
185  if (!expect_close)
186  pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
187  else if (!nowayout)
188  ar7_wdt_disable_wdt();
189  clear_bit(0, &wdt_is_open);
190  return 0;
191 }
192 
193 static ssize_t ar7_wdt_write(struct file *file, const char *data,
194  size_t len, loff_t *ppos)
195 {
196  /* check for a magic close character */
197  if (len) {
198  size_t i;
199 
200  spin_lock(&wdt_lock);
201  ar7_wdt_kick(1);
202  spin_unlock(&wdt_lock);
203 
204  expect_close = 0;
205  for (i = 0; i < len; ++i) {
206  char c;
207  if (get_user(c, data + i))
208  return -EFAULT;
209  if (c == 'V')
210  expect_close = 1;
211  }
212 
213  }
214  return len;
215 }
216 
217 static long ar7_wdt_ioctl(struct file *file,
218  unsigned int cmd, unsigned long arg)
219 {
220  static const struct watchdog_info ident = {
221  .identity = LONGNAME,
222  .firmware_version = 1,
223  .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
225  };
226  int new_margin;
227 
228  switch (cmd) {
229  case WDIOC_GETSUPPORT:
230  if (copy_to_user((struct watchdog_info *)arg, &ident,
231  sizeof(ident)))
232  return -EFAULT;
233  return 0;
234  case WDIOC_GETSTATUS:
235  case WDIOC_GETBOOTSTATUS:
236  if (put_user(0, (int *)arg))
237  return -EFAULT;
238  return 0;
239  case WDIOC_KEEPALIVE:
240  ar7_wdt_kick(1);
241  return 0;
242  case WDIOC_SETTIMEOUT:
243  if (get_user(new_margin, (int *)arg))
244  return -EFAULT;
245  if (new_margin < 1)
246  return -EINVAL;
247 
248  spin_lock(&wdt_lock);
249  ar7_wdt_update_margin(new_margin);
250  ar7_wdt_kick(1);
251  spin_unlock(&wdt_lock);
252 
253  case WDIOC_GETTIMEOUT:
254  if (put_user(margin, (int *)arg))
255  return -EFAULT;
256  return 0;
257  default:
258  return -ENOTTY;
259  }
260 }
261 
262 static const struct file_operations ar7_wdt_fops = {
263  .owner = THIS_MODULE,
264  .write = ar7_wdt_write,
265  .unlocked_ioctl = ar7_wdt_ioctl,
266  .open = ar7_wdt_open,
267  .release = ar7_wdt_release,
268  .llseek = no_llseek,
269 };
270 
271 static struct miscdevice ar7_wdt_miscdev = {
272  .minor = WATCHDOG_MINOR,
273  .name = "watchdog",
274  .fops = &ar7_wdt_fops,
275 };
276 
277 static int __devinit ar7_wdt_probe(struct platform_device *pdev)
278 {
279  int rc;
280 
281  ar7_regs_wdt =
283  if (!ar7_regs_wdt) {
284  pr_err("could not get registers resource\n");
285  return -ENODEV;
286  }
287 
288  ar7_wdt = devm_request_and_ioremap(&pdev->dev, ar7_regs_wdt);
289  if (!ar7_wdt) {
290  pr_err("could not ioremap registers\n");
291  return -ENXIO;
292  }
293 
294  vbus_clk = clk_get(NULL, "vbus");
295  if (IS_ERR(vbus_clk)) {
296  pr_err("could not get vbus clock\n");
297  return PTR_ERR(vbus_clk);
298  }
299 
300  ar7_wdt_disable_wdt();
301  ar7_wdt_prescale(prescale_value);
302  ar7_wdt_update_margin(margin);
303 
304  rc = misc_register(&ar7_wdt_miscdev);
305  if (rc) {
306  pr_err("unable to register misc device\n");
307  goto out;
308  }
309  return 0;
310 
311 out:
312  clk_put(vbus_clk);
313  vbus_clk = NULL;
314  return rc;
315 }
316 
317 static int __devexit ar7_wdt_remove(struct platform_device *pdev)
318 {
319  misc_deregister(&ar7_wdt_miscdev);
320  clk_put(vbus_clk);
321  vbus_clk = NULL;
322  return 0;
323 }
324 
325 static void ar7_wdt_shutdown(struct platform_device *pdev)
326 {
327  if (!nowayout)
328  ar7_wdt_disable_wdt();
329 }
330 
331 static struct platform_driver ar7_wdt_driver = {
332  .probe = ar7_wdt_probe,
333  .remove = __devexit_p(ar7_wdt_remove),
334  .shutdown = ar7_wdt_shutdown,
335  .driver = {
336  .owner = THIS_MODULE,
337  .name = "ar7_wdt",
338  },
339 };
340 
341 module_platform_driver(ar7_wdt_driver);