Root/drivers/watchdog/ar7_wdt.c

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#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
46MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
47MODULE_DESCRIPTION(LONGNAME);
48MODULE_LICENSE("GPL");
49MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
50
51static int margin = 60;
52module_param(margin, int, 0);
53MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
54
55static bool nowayout = WATCHDOG_NOWAYOUT;
56module_param(nowayout, bool, 0);
57MODULE_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
62struct ar7_wdt {
63    u32 kick_lock;
64    u32 kick;
65    u32 change_lock;
66    u32 change;
67    u32 disable_lock;
68    u32 disable;
69    u32 prescale_lock;
70    u32 prescale;
71};
72
73static unsigned long wdt_is_open;
74static unsigned expect_close;
75static 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 */
81static struct resource *ar7_regs_wdt;
82/* Pointer to the remapped WDT IO space */
83static struct ar7_wdt *ar7_wdt;
84
85static struct clk *vbus_clk;
86
87static 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
100static 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
113static 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
126static 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
142static 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
159static 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
166static void ar7_wdt_disable_wdt(void)
167{
168    pr_debug("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        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
193static 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
217static 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 |
224                        WDIOF_MAGICCLOSE),
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
262static 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
271static struct miscdevice ar7_wdt_miscdev = {
272    .minor = WATCHDOG_MINOR,
273    .name = "watchdog",
274    .fops = &ar7_wdt_fops,
275};
276
277static int __devinit ar7_wdt_probe(struct platform_device *pdev)
278{
279    int rc;
280
281    ar7_regs_wdt =
282        platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
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
311out:
312    clk_put(vbus_clk);
313    vbus_clk = NULL;
314    return rc;
315}
316
317static 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
325static void ar7_wdt_shutdown(struct platform_device *pdev)
326{
327    if (!nowayout)
328        ar7_wdt_disable_wdt();
329}
330
331static 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
341module_platform_driver(ar7_wdt_driver);
342

Archive Download this file



interactive