1618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
2618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * PIKA FPGA based Watchdog Timer
3618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan *
4618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Copyright (c) 2008 PIKA Technologies
5618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan *   Sean MacLennan <smaclennan@pikatech.com>
6618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
7618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
827c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
927c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches
10618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/init.h>
11618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/errno.h>
12618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/module.h>
13618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/moduleparam.h>
14618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/types.h>
15618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/kernel.h>
16618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/fs.h>
17618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/miscdevice.h>
18618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/watchdog.h>
19618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/reboot.h>
20618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/jiffies.h>
21618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/timer.h>
22618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/bitops.h>
23618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/uaccess.h>
24618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/io.h>
25c11eede69b6ad0ac44ebc1e021a8d2699c5f1f8fRob Herring#include <linux/of_address.h>
26618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#include <linux/of_platform.h>
27618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
28618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#define DRV_NAME "PIKA-WDT"
29618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
30618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/* Hardware timeout in seconds */
31618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#define WDT_HW_TIMEOUT 2
32618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
33618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/* Timer heartbeat (500ms) */
34618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#define WDT_TIMEOUT	(HZ/2)
35618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
36618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/* User land timeout */
37618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan#define WDT_HEARTBEAT 15
38618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic int heartbeat = WDT_HEARTBEAT;
39618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanmodule_param(heartbeat, int, 0);
40618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanMODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
41618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	"(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
42618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
4386a1e1896c2710402e29a875d8d830244274244dWim Van Sebroeckstatic bool nowayout = WATCHDOG_NOWAYOUT;
4486a1e1896c2710402e29a875d8d830244274244dWim Van Sebroeckmodule_param(nowayout, bool, 0);
45618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
46618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
47618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
48618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic struct {
49618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	void __iomem *fpga;
50618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	unsigned long next_heartbeat;	/* the next_heartbeat for the timer */
51618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	unsigned long open;
52618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	char expect_close;
53618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	int bootstatus;
54618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	struct timer_list timer;	/* The timer that pings the watchdog */
55618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan} pikawdt_private;
56618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
5735c79780064976cf9d7537a00e59f97c2061fa7dSean MacLennanstatic struct watchdog_info ident = {
58618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.identity	= DRV_NAME,
59618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.options	= WDIOF_CARDRESET |
60618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			  WDIOF_SETTIMEOUT |
61618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			  WDIOF_KEEPALIVEPING |
62618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			  WDIOF_MAGICCLOSE,
63618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan};
64618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
65618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
66618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Reload the watchdog timer.  (ie, pat the watchdog)
67618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
68618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic inline void pikawdt_reset(void)
69618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
70618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* -- FPGA: Reset Control Register (32bit R/W) (Offset: 0x14) --
71618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 * Bit 7,    WTCHDG_EN: When set to 1, the watchdog timer is enabled.
72618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 *           Once enabled, it cannot be disabled. The watchdog can be
73618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 *           kicked by performing any write access to the reset
74618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 *           control register (this register).
75618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 * Bit 8-11, WTCHDG_TIMEOUT_SEC: Sets the watchdog timeout value in
76618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 *           seconds. Valid ranges are 1 to 15 seconds. The value can
77618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 *           be modified dynamically.
78618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 */
79618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	unsigned reset = in_be32(pikawdt_private.fpga + 0x14);
80618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* enable with max timeout - 15 seconds */
81618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	reset |= (1 << 7) + (WDT_HW_TIMEOUT << 8);
82618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	out_be32(pikawdt_private.fpga + 0x14, reset);
83618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
84618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
85618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
86618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Timer tick
87618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
88618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic void pikawdt_ping(unsigned long data)
89618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
90618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (time_before(jiffies, pikawdt_private.next_heartbeat) ||
91618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			(!nowayout && !pikawdt_private.open)) {
92618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		pikawdt_reset();
93618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		mod_timer(&pikawdt_private.timer, jiffies + WDT_TIMEOUT);
94618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	} else
9527c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_crit("I will reset your machine !\n");
96618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
97618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
98618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
99618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic void pikawdt_keepalive(void)
100618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
101618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	pikawdt_private.next_heartbeat = jiffies + heartbeat * HZ;
102618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
103618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
104618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic void pikawdt_start(void)
105618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
106618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	pikawdt_keepalive();
107618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	mod_timer(&pikawdt_private.timer, jiffies + WDT_TIMEOUT);
108618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
109618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
110618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
111618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Watchdog device is opened, and watchdog starts running.
112618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
113618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic int pikawdt_open(struct inode *inode, struct file *file)
114618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
115618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* /dev/watchdog can only be opened once */
116618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (test_and_set_bit(0, &pikawdt_private.open))
117618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return -EBUSY;
118618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
119618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	pikawdt_start();
120618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
121618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	return nonseekable_open(inode, file);
122618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
123618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
124618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
125618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Close the watchdog device.
126618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
127618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic int pikawdt_release(struct inode *inode, struct file *file)
128618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
129618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* stop internal ping */
130618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (!pikawdt_private.expect_close)
131618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		del_timer(&pikawdt_private.timer);
132618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
133618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	clear_bit(0, &pikawdt_private.open);
134618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	pikawdt_private.expect_close = 0;
135618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	return 0;
136618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
137618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
138618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
139618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Pat the watchdog whenever device is written to.
140618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
141618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic ssize_t pikawdt_write(struct file *file, const char __user *data,
142618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			     size_t len, loff_t *ppos)
143618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
144618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (!len)
145618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return 0;
146618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
147618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* Scan for magic character */
148618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (!nowayout) {
149618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		size_t i;
150618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
151618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		pikawdt_private.expect_close = 0;
152618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
153618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		for (i = 0; i < len; i++) {
154618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			char c;
155618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			if (get_user(c, data + i))
156618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan				return -EFAULT;
157618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			if (c == 'V') {
158618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan				pikawdt_private.expect_close = 42;
159618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan				break;
160618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			}
161618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		}
162618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
163618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
164618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	pikawdt_keepalive();
165618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
166618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	return len;
167618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
168618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
169618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan/*
170618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan * Handle commands from user-space.
171618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan */
172618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic long pikawdt_ioctl(struct file *file,
173618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		unsigned int cmd, unsigned long arg)
174618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
175618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	void __user *argp = (void __user *)arg;
176618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	int __user *p = argp;
177618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	int new_value;
178618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
179618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	switch (cmd) {
180618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	case WDIOC_GETSUPPORT:
181618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
182618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
183618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	case WDIOC_GETSTATUS:
184618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return put_user(0, p);
185618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
186618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	case WDIOC_GETBOOTSTATUS:
187618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return put_user(pikawdt_private.bootstatus, p);
188618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
189618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	case WDIOC_KEEPALIVE:
190618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		pikawdt_keepalive();
191618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return 0;
192618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
193618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	case WDIOC_SETTIMEOUT:
194618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		if (get_user(new_value, p))
195618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan			return -EFAULT;
196618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
197618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		heartbeat = new_value;
198618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		pikawdt_keepalive();
199618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
200618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return put_user(new_value, p);  /* return current value */
201618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
202618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	case WDIOC_GETTIMEOUT:
203618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return put_user(heartbeat, p);
204618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
205618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	return -ENOTTY;
206618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
207618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
208618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
209618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic const struct file_operations pikawdt_fops = {
210618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.owner		= THIS_MODULE,
211618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.llseek		= no_llseek,
212618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.open		= pikawdt_open,
213618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.release	= pikawdt_release,
214618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.write		= pikawdt_write,
215618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.unlocked_ioctl	= pikawdt_ioctl,
216618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan};
217618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
218618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic struct miscdevice pikawdt_miscdev = {
219618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.minor	= WATCHDOG_MINOR,
220618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.name	= "watchdog",
221618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	.fops	= &pikawdt_fops,
222618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan};
223618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
224618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic int __init pikawdt_init(void)
225618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
226618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	struct device_node *np;
227618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	void __iomem *fpga;
228618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	static u32 post1;
229618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	int ret;
230618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
231618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	np = of_find_compatible_node(NULL, NULL, "pika,fpga");
232618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (np == NULL) {
23327c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("Unable to find fpga\n");
234618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return -ENOENT;
235618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
236618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
237618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	pikawdt_private.fpga = of_iomap(np, 0);
238618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	of_node_put(np);
239618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (pikawdt_private.fpga == NULL) {
24027c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("Unable to map fpga\n");
241618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		return -ENOMEM;
242618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
243618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
244618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	ident.firmware_version = in_be32(pikawdt_private.fpga + 0x1c) & 0xffff;
245618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
246618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* POST information is in the sd area. */
247618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	np = of_find_compatible_node(NULL, NULL, "pika,fpga-sd");
248618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (np == NULL) {
24927c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("Unable to find fpga-sd\n");
250618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		ret = -ENOENT;
251618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		goto out;
252618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
253618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
254618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	fpga = of_iomap(np, 0);
255618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	of_node_put(np);
256618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (fpga == NULL) {
25727c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("Unable to map fpga-sd\n");
258618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		ret = -ENOMEM;
259618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		goto out;
260618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
261618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
262618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	/* -- FPGA: POST Test Results Register 1 (32bit R/W) (Offset: 0x4040) --
263618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 * Bit 31,   WDOG: Set to 1 when the last reset was caused by a watchdog
264618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 *           timeout.
265618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	 */
266618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	post1 = in_be32(fpga + 0x40);
267618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (post1 & 0x80000000)
268618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		pikawdt_private.bootstatus = WDIOF_CARDRESET;
269618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
270618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	iounmap(fpga);
271618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
272618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	setup_timer(&pikawdt_private.timer, pikawdt_ping, 0);
273618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
274618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	ret = misc_register(&pikawdt_miscdev);
275618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	if (ret) {
27627c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("Unable to register miscdev\n");
277618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan		goto out;
278618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	}
279618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
28027c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches	pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
28127c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		heartbeat, nowayout);
282618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	return 0;
283618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
284618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanout:
285618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	iounmap(pikawdt_private.fpga);
286618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	return ret;
287618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
288618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
289618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanstatic void __exit pikawdt_exit(void)
290618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan{
291618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	misc_deregister(&pikawdt_miscdev);
292618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
293618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan	iounmap(pikawdt_private.fpga);
294618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan}
295618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
296618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanmodule_init(pikawdt_init);
297618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanmodule_exit(pikawdt_exit);
298618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennan
299618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanMODULE_AUTHOR("Sean MacLennan <smaclennan@pikatech.com>");
300618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanMODULE_DESCRIPTION("PIKA FPGA based Watchdog Timer");
301618efba999d0e7f4bcde93231dcb9a748223c6e3Sean MacLennanMODULE_LICENSE("GPL");
302