Root/drivers/watchdog/stmp3xxx_wdt.c

1/*
2 * Watchdog driver for Freescale STMP37XX/STMP378X
3 *
4 * Author: Vitaly Wool <vital@embeddedalley.com>
5 *
6 * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved.
7 * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
8 */
9
10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11
12#include <linux/init.h>
13#include <linux/kernel.h>
14#include <linux/fs.h>
15#include <linux/miscdevice.h>
16#include <linux/watchdog.h>
17#include <linux/platform_device.h>
18#include <linux/spinlock.h>
19#include <linux/uaccess.h>
20#include <linux/module.h>
21
22#include <mach/platform.h>
23#include <mach/regs-rtc.h>
24
25#define DEFAULT_HEARTBEAT 19
26#define MAX_HEARTBEAT (0x10000000 >> 6)
27
28/* missing bitmask in headers */
29#define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER 0x80000000
30
31#define WDT_IN_USE 0
32#define WDT_OK_TO_CLOSE 1
33
34#define WDOG_COUNTER_RATE 1000 /* 1 kHz clock */
35
36static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock);
37static unsigned long wdt_status;
38static const bool nowayout = WATCHDOG_NOWAYOUT;
39static int heartbeat = DEFAULT_HEARTBEAT;
40static unsigned long boot_status;
41
42static void wdt_enable(u32 value)
43{
44    spin_lock(&stmp3xxx_wdt_io_lock);
45    __raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG);
46    stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
47    stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
48            REGS_RTC_BASE + HW_RTC_PERSISTENT1);
49    spin_unlock(&stmp3xxx_wdt_io_lock);
50}
51
52static void wdt_disable(void)
53{
54    spin_lock(&stmp3xxx_wdt_io_lock);
55    stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
56            REGS_RTC_BASE + HW_RTC_PERSISTENT1);
57    stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
58    spin_unlock(&stmp3xxx_wdt_io_lock);
59}
60
61static void wdt_ping(void)
62{
63    wdt_enable(heartbeat * WDOG_COUNTER_RATE);
64}
65
66static int stmp3xxx_wdt_open(struct inode *inode, struct file *file)
67{
68    if (test_and_set_bit(WDT_IN_USE, &wdt_status))
69        return -EBUSY;
70
71    clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
72    wdt_ping();
73
74    return nonseekable_open(inode, file);
75}
76
77static ssize_t stmp3xxx_wdt_write(struct file *file, const char __user *data,
78    size_t len, loff_t *ppos)
79{
80    if (len) {
81        if (!nowayout) {
82            size_t i;
83
84            clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
85
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                    set_bit(WDT_OK_TO_CLOSE, &wdt_status);
93            }
94        }
95        wdt_ping();
96    }
97
98    return len;
99}
100
101static const struct watchdog_info ident = {
102    .options = WDIOF_CARDRESET |
103              WDIOF_MAGICCLOSE |
104              WDIOF_SETTIMEOUT |
105              WDIOF_KEEPALIVEPING,
106    .identity = "STMP3XXX Watchdog",
107};
108
109static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd,
110    unsigned long arg)
111{
112    void __user *argp = (void __user *)arg;
113    int __user *p = argp;
114    int new_heartbeat, opts;
115    int ret = -ENOTTY;
116
117    switch (cmd) {
118    case WDIOC_GETSUPPORT:
119        ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
120        break;
121
122    case WDIOC_GETSTATUS:
123        ret = put_user(0, p);
124        break;
125
126    case WDIOC_GETBOOTSTATUS:
127        ret = put_user(boot_status, p);
128        break;
129
130    case WDIOC_SETOPTIONS:
131        if (get_user(opts, p)) {
132            ret = -EFAULT;
133            break;
134        }
135        if (opts & WDIOS_DISABLECARD)
136            wdt_disable();
137        else if (opts & WDIOS_ENABLECARD)
138            wdt_ping();
139        else {
140            pr_debug("%s: unknown option 0x%x\n", __func__, opts);
141            ret = -EINVAL;
142            break;
143        }
144        ret = 0;
145        break;
146
147    case WDIOC_KEEPALIVE:
148        wdt_ping();
149        ret = 0;
150        break;
151
152    case WDIOC_SETTIMEOUT:
153        if (get_user(new_heartbeat, p)) {
154            ret = -EFAULT;
155            break;
156        }
157        if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) {
158            ret = -EINVAL;
159            break;
160        }
161
162        heartbeat = new_heartbeat;
163        wdt_ping();
164        /* Fall through */
165
166    case WDIOC_GETTIMEOUT:
167        ret = put_user(heartbeat, p);
168        break;
169    }
170    return ret;
171}
172
173static int stmp3xxx_wdt_release(struct inode *inode, struct file *file)
174{
175    int ret = 0;
176
177    if (!nowayout) {
178        if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
179            wdt_ping();
180            pr_debug("%s: Device closed unexpectedly\n", __func__);
181            ret = -EINVAL;
182        } else {
183            wdt_disable();
184            clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
185        }
186    }
187    clear_bit(WDT_IN_USE, &wdt_status);
188
189    return ret;
190}
191
192static const struct file_operations stmp3xxx_wdt_fops = {
193    .owner = THIS_MODULE,
194    .llseek = no_llseek,
195    .write = stmp3xxx_wdt_write,
196    .unlocked_ioctl = stmp3xxx_wdt_ioctl,
197    .open = stmp3xxx_wdt_open,
198    .release = stmp3xxx_wdt_release,
199};
200
201static struct miscdevice stmp3xxx_wdt_miscdev = {
202    .minor = WATCHDOG_MINOR,
203    .name = "watchdog",
204    .fops = &stmp3xxx_wdt_fops,
205};
206
207static int __devinit stmp3xxx_wdt_probe(struct platform_device *pdev)
208{
209    int ret = 0;
210
211    if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
212        heartbeat = DEFAULT_HEARTBEAT;
213
214    boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) &
215            BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER;
216    boot_status = !!boot_status;
217    stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
218            REGS_RTC_BASE + HW_RTC_PERSISTENT1);
219    wdt_disable(); /* disable for now */
220
221    ret = misc_register(&stmp3xxx_wdt_miscdev);
222    if (ret < 0) {
223        dev_err(&pdev->dev, "cannot register misc device\n");
224        return ret;
225    }
226
227    pr_info("initialized, heartbeat %d sec\n", heartbeat);
228
229    return ret;
230}
231
232static int __devexit stmp3xxx_wdt_remove(struct platform_device *pdev)
233{
234    misc_deregister(&stmp3xxx_wdt_miscdev);
235    return 0;
236}
237
238#ifdef CONFIG_PM
239static int wdt_suspended;
240static u32 wdt_saved_time;
241
242static int stmp3xxx_wdt_suspend(struct platform_device *pdev,
243                pm_message_t state)
244{
245    if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) &
246        BM_RTC_CTRL_WATCHDOGEN) {
247        wdt_suspended = 1;
248        wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG);
249        wdt_disable();
250    }
251    return 0;
252}
253
254static int stmp3xxx_wdt_resume(struct platform_device *pdev)
255{
256    if (wdt_suspended) {
257        wdt_enable(wdt_saved_time);
258        wdt_suspended = 0;
259    }
260    return 0;
261}
262#else
263#define stmp3xxx_wdt_suspend NULL
264#define stmp3xxx_wdt_resume NULL
265#endif
266
267static struct platform_driver platform_wdt_driver = {
268    .driver = {
269        .name = "stmp3xxx_wdt",
270    },
271    .probe = stmp3xxx_wdt_probe,
272    .remove = __devexit_p(stmp3xxx_wdt_remove),
273    .suspend = stmp3xxx_wdt_suspend,
274    .resume = stmp3xxx_wdt_resume,
275};
276
277module_platform_driver(platform_wdt_driver);
278
279MODULE_DESCRIPTION("STMP3XXX Watchdog Driver");
280MODULE_LICENSE("GPL");
281
282module_param(heartbeat, int, 0);
283MODULE_PARM_DESC(heartbeat,
284         "Watchdog heartbeat period in seconds from 1 to "
285         __MODULE_STRING(MAX_HEARTBEAT) ", default "
286         __MODULE_STRING(DEFAULT_HEARTBEAT));
287
288MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
289

Archive Download this file



interactive