Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.8.
  1/*
  2 * drivers/watchdog/ar7_wdt.c
  3 *
  4 * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
  5 * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
  6 *
  7 * Some code taken from:
  8 * National Semiconductor SCx200 Watchdog support
  9 * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
 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#include <linux/module.h>
 27#include <linux/moduleparam.h>
 28#include <linux/errno.h>
 29#include <linux/init.h>
 30#include <linux/miscdevice.h>
 31#include <linux/platform_device.h>
 32#include <linux/watchdog.h>
 33#include <linux/fs.h>
 34#include <linux/ioport.h>
 35#include <linux/io.h>
 36#include <linux/uaccess.h>
 37#include <linux/clk.h>
 38
 39#include <asm/addrspace.h>
 40#include <asm/mach-ar7/ar7.h>
 41
 42#define DRVNAME "ar7_wdt"
 43#define LONGNAME "TI AR7 Watchdog Timer"
 44
 45MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
 46MODULE_DESCRIPTION(LONGNAME);
 47MODULE_LICENSE("GPL");
 48MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 49
 50static int margin = 60;
 51module_param(margin, int, 0);
 52MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
 53
 54static int nowayout = WATCHDOG_NOWAYOUT;
 55module_param(nowayout, int, 0);
 56MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
 57
 58#define READ_REG(x) readl((void __iomem *)&(x))
 59#define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
 60
 61struct ar7_wdt {
 62	u32 kick_lock;
 63	u32 kick;
 64	u32 change_lock;
 65	u32 change;
 66	u32 disable_lock;
 67	u32 disable;
 68	u32 prescale_lock;
 69	u32 prescale;
 70};
 71
 72static unsigned long wdt_is_open;
 73static spinlock_t wdt_lock;
 74static unsigned expect_close;
 75
 76/* XXX currently fixed, allows max margin ~68.72 secs */
 77#define prescale_value 0xffff
 78
 79/* Resource of the WDT registers */
 80static struct resource *ar7_regs_wdt;
 81/* Pointer to the remapped WDT IO space */
 82static struct ar7_wdt *ar7_wdt;
 83
 84static struct clk *vbus_clk;
 85
 86static void ar7_wdt_kick(u32 value)
 87{
 88	WRITE_REG(ar7_wdt->kick_lock, 0x5555);
 89	if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
 90		WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
 91		if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
 92			WRITE_REG(ar7_wdt->kick, value);
 93			return;
 94		}
 95	}
 96	printk(KERN_ERR DRVNAME ": failed to unlock WDT kick reg\n");
 97}
 98
 99static void ar7_wdt_prescale(u32 value)
100{
101	WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
102	if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
103		WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
104		if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
105			WRITE_REG(ar7_wdt->prescale, value);
106			return;
107		}
108	}
109	printk(KERN_ERR DRVNAME ": failed to unlock WDT prescale reg\n");
110}
111
112static void ar7_wdt_change(u32 value)
113{
114	WRITE_REG(ar7_wdt->change_lock, 0x6666);
115	if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
116		WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
117		if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
118			WRITE_REG(ar7_wdt->change, value);
119			return;
120		}
121	}
122	printk(KERN_ERR DRVNAME ": failed to unlock WDT change reg\n");
123}
124
125static void ar7_wdt_disable(u32 value)
126{
127	WRITE_REG(ar7_wdt->disable_lock, 0x7777);
128	if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
129		WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
130		if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
131			WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
132			if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
133				WRITE_REG(ar7_wdt->disable, value);
134				return;
135			}
136		}
137	}
138	printk(KERN_ERR DRVNAME ": failed to unlock WDT disable reg\n");
139}
140
141static void ar7_wdt_update_margin(int new_margin)
142{
143	u32 change;
144	u32 vbus_rate;
145
146	vbus_rate = clk_get_rate(vbus_clk);
147	change = new_margin * (vbus_rate / prescale_value);
148	if (change < 1)
149		change = 1;
150	if (change > 0xffff)
151		change = 0xffff;
152	ar7_wdt_change(change);
153	margin = change * prescale_value / vbus_rate;
154	printk(KERN_INFO DRVNAME
155	       ": timer margin %d seconds (prescale %d, change %d, freq %d)\n",
156	       margin, prescale_value, change, vbus_rate);
157}
158
159static void ar7_wdt_enable_wdt(void)
160{
161	printk(KERN_DEBUG DRVNAME ": enabling watchdog timer\n");
162	ar7_wdt_disable(1);
163	ar7_wdt_kick(1);
164}
165
166static void ar7_wdt_disable_wdt(void)
167{
168	printk(KERN_DEBUG DRVNAME ": disabling watchdog timer\n");
169	ar7_wdt_disable(0);
170}
171
172static 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
183static int ar7_wdt_release(struct inode *inode, struct file *file)
184{
185	if (!expect_close)
186		printk(KERN_WARNING DRVNAME
187		": watchdog device closed unexpectedly,"
188		"will not disable the watchdog timer\n");
189	else if (!nowayout)
190		ar7_wdt_disable_wdt();
191	clear_bit(0, &wdt_is_open);
192	return 0;
193}
194
195static ssize_t ar7_wdt_write(struct file *file, const char *data,
196			     size_t len, loff_t *ppos)
197{
198	/* check for a magic close character */
199	if (len) {
200		size_t i;
201
202		spin_lock(&wdt_lock);
203		ar7_wdt_kick(1);
204		spin_unlock(&wdt_lock);
205
206		expect_close = 0;
207		for (i = 0; i < len; ++i) {
208			char c;
209			if (get_user(c, data + i))
210				return -EFAULT;
211			if (c == 'V')
212				expect_close = 1;
213		}
214
215	}
216	return len;
217}
218
219static long ar7_wdt_ioctl(struct file *file,
220					unsigned int cmd, unsigned long arg)
221{
222	static const struct watchdog_info ident = {
223		.identity = LONGNAME,
224		.firmware_version = 1,
225		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
226						WDIOF_MAGICCLOSE),
227	};
228	int new_margin;
229
230	switch (cmd) {
231	case WDIOC_GETSUPPORT:
232		if (copy_to_user((struct watchdog_info *)arg, &ident,
233				sizeof(ident)))
234			return -EFAULT;
235		return 0;
236	case WDIOC_GETSTATUS:
237	case WDIOC_GETBOOTSTATUS:
238		if (put_user(0, (int *)arg))
239			return -EFAULT;
240		return 0;
241	case WDIOC_KEEPALIVE:
242		ar7_wdt_kick(1);
243		return 0;
244	case WDIOC_SETTIMEOUT:
245		if (get_user(new_margin, (int *)arg))
246			return -EFAULT;
247		if (new_margin < 1)
248			return -EINVAL;
249
250		spin_lock(&wdt_lock);
251		ar7_wdt_update_margin(new_margin);
252		ar7_wdt_kick(1);
253		spin_unlock(&wdt_lock);
254
255	case WDIOC_GETTIMEOUT:
256		if (put_user(margin, (int *)arg))
257			return -EFAULT;
258		return 0;
259	default:
260		return -ENOTTY;
261	}
262}
263
264static const struct file_operations ar7_wdt_fops = {
265	.owner		= THIS_MODULE,
266	.write		= ar7_wdt_write,
267	.unlocked_ioctl	= ar7_wdt_ioctl,
268	.open		= ar7_wdt_open,
269	.release	= ar7_wdt_release,
270	.llseek		= no_llseek,
271};
272
273static struct miscdevice ar7_wdt_miscdev = {
274	.minor		= WATCHDOG_MINOR,
275	.name		= "watchdog",
276	.fops		= &ar7_wdt_fops,
277};
278
279static int __devinit ar7_wdt_probe(struct platform_device *pdev)
280{
281	int rc;
282
283	spin_lock_init(&wdt_lock);
284
285	ar7_regs_wdt =
286		platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
287	if (!ar7_regs_wdt) {
288		printk(KERN_ERR DRVNAME ": could not get registers resource\n");
289		rc = -ENODEV;
290		goto out;
291	}
292
293	if (!request_mem_region(ar7_regs_wdt->start,
294				resource_size(ar7_regs_wdt), LONGNAME)) {
295		printk(KERN_WARNING DRVNAME ": watchdog I/O region busy\n");
296		rc = -EBUSY;
297		goto out;
298	}
299
300	ar7_wdt = ioremap(ar7_regs_wdt->start, resource_size(ar7_regs_wdt));
301	if (!ar7_wdt) {
302		printk(KERN_ERR DRVNAME ": could not ioremap registers\n");
303		rc = -ENXIO;
304		goto out_mem_region;
305	}
306
307	vbus_clk = clk_get(NULL, "vbus");
308	if (IS_ERR(vbus_clk)) {
309		printk(KERN_ERR DRVNAME ": could not get vbus clock\n");
310		rc = PTR_ERR(vbus_clk);
311		goto out_mem_region;
312	}
313
314	ar7_wdt_disable_wdt();
315	ar7_wdt_prescale(prescale_value);
316	ar7_wdt_update_margin(margin);
317
318	rc = misc_register(&ar7_wdt_miscdev);
319	if (rc) {
320		printk(KERN_ERR DRVNAME ": unable to register misc device\n");
321		goto out_alloc;
322	}
323	goto out;
324
325out_alloc:
326	iounmap(ar7_wdt);
327out_mem_region:
328	release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt));
329out:
330	return rc;
331}
332
333static int __devexit ar7_wdt_remove(struct platform_device *pdev)
334{
335	misc_deregister(&ar7_wdt_miscdev);
336	iounmap(ar7_wdt);
337	release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt));
338
339	return 0;
340}
341
342static void ar7_wdt_shutdown(struct platform_device *pdev)
343{
344	if (!nowayout)
345		ar7_wdt_disable_wdt();
346}
347
348static struct platform_driver ar7_wdt_driver = {
349	.probe = ar7_wdt_probe,
350	.remove = __devexit_p(ar7_wdt_remove),
351	.shutdown = ar7_wdt_shutdown,
352	.driver = {
353		.owner = THIS_MODULE,
354		.name = "ar7_wdt",
355	},
356};
357
358static int __init ar7_wdt_init(void)
359{
360	return platform_driver_register(&ar7_wdt_driver);
361}
362
363static void __exit ar7_wdt_cleanup(void)
364{
365	platform_driver_unregister(&ar7_wdt_driver);
366}
367
368module_init(ar7_wdt_init);
369module_exit(ar7_wdt_cleanup);