Blame view

drivers/watchdog/via_wdt.c 7.46 KB
dc3c56b70   Marc Vertes   watchdog: new dri...
1
2
3
4
5
6
7
8
9
10
11
12
  /*
   * VIA Chipset Watchdog Driver
   *
   * Copyright (C) 2011 Sigfox
   * License terms: GNU General Public License (GPL) version 2
   * Author: Marc Vertes <marc.vertes@sigfox.com>
   * Based on a preliminary version from Harald Welte <HaraldWelte@viatech.com>
   * Timer code by Wim Van Sebroeck <wim@iguana.be>
   *
   * Caveat: PnP must be enabled in BIOS to allow full access to watchdog
   * control registers. If not, the watchdog must be configured in BIOS manually.
   */
27c766aaa   Joe Perches   watchdog: Use pr_...
13
14
  
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
dc3c56b70   Marc Vertes   watchdog: new dri...
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  #include <linux/device.h>
  #include <linux/io.h>
  #include <linux/jiffies.h>
  #include <linux/module.h>
  #include <linux/pci.h>
  #include <linux/timer.h>
  #include <linux/watchdog.h>
  
  /* Configuration registers relative to the pci device */
  #define VIA_WDT_MMIO_BASE	0xe8	/* MMIO region base address */
  #define VIA_WDT_CONF		0xec	/* watchdog enable state */
  
  /* Relevant bits for the VIA_WDT_CONF register */
  #define VIA_WDT_CONF_ENABLE	0x01	/* 1: enable watchdog */
  #define VIA_WDT_CONF_MMIO	0x02	/* 1: enable watchdog MMIO */
  
  /*
   * The MMIO region contains the watchog control register and the
   * hardware timer counter.
   */
  #define VIA_WDT_MMIO_LEN	8	/* MMIO region length in bytes */
  #define VIA_WDT_CTL		0	/* MMIO addr+0: state/control reg. */
  #define VIA_WDT_COUNT		4	/* MMIO addr+4: timer counter reg. */
  
  /* Bits for the VIA_WDT_CTL register */
  #define VIA_WDT_RUNNING		0x01	/* 0: stop, 1: running */
  #define VIA_WDT_FIRED		0x02	/* 1: restarted by expired watchdog */
  #define VIA_WDT_PWROFF		0x04	/* 0: reset, 1: poweroff */
  #define VIA_WDT_DISABLED	0x08	/* 1: timer is disabled */
  #define VIA_WDT_TRIGGER		0x80	/* 1: start a new countdown */
  
  /* Hardware heartbeat in seconds */
  #define WDT_HW_HEARTBEAT 1
  
  /* Timer heartbeat (500ms) */
  #define WDT_HEARTBEAT	(HZ/2)	/* should be <= ((WDT_HW_HEARTBEAT*HZ)/2) */
  
  /* User space timeout in seconds */
  #define WDT_TIMEOUT_MAX	1023	/* approx. 17 min. */
  #define WDT_TIMEOUT	60
  static int timeout = WDT_TIMEOUT;
  module_param(timeout, int, 0);
  MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, between 1 and 1023 "
  	"(default = " __MODULE_STRING(WDT_TIMEOUT) ")");
86a1e1896   Wim Van Sebroeck   watchdog: nowayou...
59
60
  static bool nowayout = WATCHDOG_NOWAYOUT;
  module_param(nowayout, bool, 0);
dc3c56b70   Marc Vertes   watchdog: new dri...
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  	"(default = " __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  
  static struct watchdog_device wdt_dev;
  static struct resource wdt_res;
  static void __iomem *wdt_mem;
  static unsigned int mmio;
  static void wdt_timer_tick(unsigned long data);
  static DEFINE_TIMER(timer, wdt_timer_tick, 0, 0);
  					/* The timer that pings the watchdog */
  static unsigned long next_heartbeat;	/* the next_heartbeat for the timer */
  
  static inline void wdt_reset(void)
  {
  	unsigned int ctl = readl(wdt_mem);
  
  	writel(ctl | VIA_WDT_TRIGGER, wdt_mem);
  }
  
  /*
   * Timer tick: the timer will make sure that the watchdog timer hardware
   * is being reset in time. The conditions to do this are:
   *  1) the watchog timer has been started and /dev/watchdog is open
   *     and there is still time left before userspace should send the
   *     next heartbeat/ping. (note: the internal heartbeat is much smaller
   *     then the external/userspace heartbeat).
   *  2) the watchdog timer has been stopped by userspace.
   */
  static void wdt_timer_tick(unsigned long data)
  {
  	if (time_before(jiffies, next_heartbeat) ||
257f8c4aa   Viresh Kumar   watchdog: Add wat...
92
  	   (!watchdog_active(&wdt_dev))) {
dc3c56b70   Marc Vertes   watchdog: new dri...
93
94
95
96
97
98
99
100
101
102
  		wdt_reset();
  		mod_timer(&timer, jiffies + WDT_HEARTBEAT);
  	} else
  		pr_crit("I will reboot your machine !
  ");
  }
  
  static int wdt_ping(struct watchdog_device *wdd)
  {
  	/* calculate when the next userspace timeout will be */
0197c1c49   Wim Van Sebroeck   watchdog: fix set...
103
  	next_heartbeat = jiffies + wdd->timeout * HZ;
dc3c56b70   Marc Vertes   watchdog: new dri...
104
105
106
107
108
109
  	return 0;
  }
  
  static int wdt_start(struct watchdog_device *wdd)
  {
  	unsigned int ctl = readl(wdt_mem);
0197c1c49   Wim Van Sebroeck   watchdog: fix set...
110
  	writel(wdd->timeout, wdt_mem + VIA_WDT_COUNT);
dc3c56b70   Marc Vertes   watchdog: new dri...
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  	writel(ctl | VIA_WDT_RUNNING | VIA_WDT_TRIGGER, wdt_mem);
  	wdt_ping(wdd);
  	mod_timer(&timer, jiffies + WDT_HEARTBEAT);
  	return 0;
  }
  
  static int wdt_stop(struct watchdog_device *wdd)
  {
  	unsigned int ctl = readl(wdt_mem);
  
  	writel(ctl & ~VIA_WDT_RUNNING, wdt_mem);
  	return 0;
  }
  
  static int wdt_set_timeout(struct watchdog_device *wdd,
  			   unsigned int new_timeout)
  {
dc3c56b70   Marc Vertes   watchdog: new dri...
128
  	writel(new_timeout, wdt_mem + VIA_WDT_COUNT);
0197c1c49   Wim Van Sebroeck   watchdog: fix set...
129
  	wdd->timeout = new_timeout;
dc3c56b70   Marc Vertes   watchdog: new dri...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  	return 0;
  }
  
  static const struct watchdog_info wdt_info = {
  	.identity =	"VIA watchdog",
  	.options =	WDIOF_CARDRESET |
  			WDIOF_SETTIMEOUT |
  			WDIOF_MAGICCLOSE |
  			WDIOF_KEEPALIVEPING,
  };
  
  static const struct watchdog_ops wdt_ops = {
  	.owner =	THIS_MODULE,
  	.start =	wdt_start,
  	.stop =		wdt_stop,
  	.ping =		wdt_ping,
  	.set_timeout =	wdt_set_timeout,
  };
  
  static struct watchdog_device wdt_dev = {
  	.info =		&wdt_info,
  	.ops =		&wdt_ops,
f6dd94f81   Axel Lin   watchdog: via_wdt...
152
153
  	.min_timeout =	1,
  	.max_timeout =	WDT_TIMEOUT_MAX,
dc3c56b70   Marc Vertes   watchdog: new dri...
154
  };
2d991a164   Bill Pemberton   watchdog: remove ...
155
  static int wdt_probe(struct pci_dev *pdev,
dc3c56b70   Marc Vertes   watchdog: new dri...
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
  			       const struct pci_device_id *ent)
  {
  	unsigned char conf;
  	int ret = -ENODEV;
  
  	if (pci_enable_device(pdev)) {
  		dev_err(&pdev->dev, "cannot enable PCI device
  ");
  		return -ENODEV;
  	}
  
  	/*
  	 * Allocate a MMIO region which contains watchdog control register
  	 * and counter, then configure the watchdog to use this region.
  	 * This is possible only if PnP is properly enabled in BIOS.
  	 * If not, the watchdog must be configured in BIOS manually.
  	 */
  	if (allocate_resource(&iomem_resource, &wdt_res, VIA_WDT_MMIO_LEN,
  			      0xf0000000, 0xffffff00, 0xff, NULL, NULL)) {
  		dev_err(&pdev->dev, "MMIO allocation failed
  ");
  		goto err_out_disable_device;
  	}
  
  	pci_write_config_dword(pdev, VIA_WDT_MMIO_BASE, wdt_res.start);
  	pci_read_config_byte(pdev, VIA_WDT_CONF, &conf);
  	conf |= VIA_WDT_CONF_ENABLE | VIA_WDT_CONF_MMIO;
  	pci_write_config_byte(pdev, VIA_WDT_CONF, conf);
  
  	pci_read_config_dword(pdev, VIA_WDT_MMIO_BASE, &mmio);
  	if (mmio) {
  		dev_info(&pdev->dev, "VIA Chipset watchdog MMIO: %x
  ", mmio);
  	} else {
  		dev_err(&pdev->dev, "MMIO setting failed. Check BIOS.
  ");
  		goto err_out_resource;
  	}
  
  	if (!request_mem_region(mmio, VIA_WDT_MMIO_LEN, "via_wdt")) {
  		dev_err(&pdev->dev, "MMIO region busy
  ");
  		goto err_out_resource;
  	}
  
  	wdt_mem = ioremap(mmio, VIA_WDT_MMIO_LEN);
  	if (wdt_mem == NULL) {
  		dev_err(&pdev->dev, "cannot remap VIA wdt MMIO registers
  ");
  		goto err_out_release;
  	}
5ce9c371c   Wim Van Sebroeck   watchdog: Use mod...
207
208
  	if (timeout < 1 || timeout > WDT_TIMEOUT_MAX)
  		timeout = WDT_TIMEOUT;
dc3c56b70   Marc Vertes   watchdog: new dri...
209
  	wdt_dev.timeout = timeout;
6551881c8   Pratyush Anand   Watchdog: Fix par...
210
  	wdt_dev.parent = &pdev->dev;
dc3c56b70   Marc Vertes   watchdog: new dri...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
  	watchdog_set_nowayout(&wdt_dev, nowayout);
  	if (readl(wdt_mem) & VIA_WDT_FIRED)
  		wdt_dev.bootstatus |= WDIOF_CARDRESET;
  
  	ret = watchdog_register_device(&wdt_dev);
  	if (ret)
  		goto err_out_iounmap;
  
  	/* start triggering, in case of watchdog already enabled by BIOS */
  	mod_timer(&timer, jiffies + WDT_HEARTBEAT);
  	return 0;
  
  err_out_iounmap:
  	iounmap(wdt_mem);
  err_out_release:
  	release_mem_region(mmio, VIA_WDT_MMIO_LEN);
  err_out_resource:
  	release_resource(&wdt_res);
  err_out_disable_device:
  	pci_disable_device(pdev);
  	return ret;
  }
4b12b896c   Bill Pemberton   watchdog: remove ...
233
  static void wdt_remove(struct pci_dev *pdev)
dc3c56b70   Marc Vertes   watchdog: new dri...
234
235
  {
  	watchdog_unregister_device(&wdt_dev);
3813ff8b3   Julia Lawall   watchdog: via_wdt...
236
  	del_timer_sync(&timer);
dc3c56b70   Marc Vertes   watchdog: new dri...
237
238
239
240
241
  	iounmap(wdt_mem);
  	release_mem_region(mmio, VIA_WDT_MMIO_LEN);
  	release_resource(&wdt_res);
  	pci_disable_device(pdev);
  }
bc17f9dcb   Jingoo Han   watchdog: remove ...
242
  static const struct pci_device_id wdt_pci_table[] = {
dc3c56b70   Marc Vertes   watchdog: new dri...
243
244
245
246
247
248
249
250
251
252
  	{ PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_CX700) },
  	{ PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX800) },
  	{ PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX855) },
  	{ 0 }
  };
  
  static struct pci_driver wdt_driver = {
  	.name		= "via_wdt",
  	.id_table	= wdt_pci_table,
  	.probe		= wdt_probe,
82268714b   Bill Pemberton   watchdog: remove ...
253
  	.remove		= wdt_remove,
dc3c56b70   Marc Vertes   watchdog: new dri...
254
  };
5ce9c371c   Wim Van Sebroeck   watchdog: Use mod...
255
  module_pci_driver(wdt_driver);
dc3c56b70   Marc Vertes   watchdog: new dri...
256
257
258
259
  
  MODULE_AUTHOR("Marc Vertes");
  MODULE_DESCRIPTION("Driver for watchdog timer on VIA chipset");
  MODULE_LICENSE("GPL");