Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1/*
  2 *      Intel Atom E6xx Watchdog driver
  3 *
  4 *      Copyright (C) 2011 Alexander Stein
  5 *                <alexander.stein@systec-electronic.com>
  6 *
  7 *      This program is free software; you can redistribute it and/or
  8 *      modify it under the terms of version 2 of the GNU General
  9 *      Public License as published by the Free Software Foundation.
 10 *
 11 *      This program is distributed in the hope that it will be
 12 *      useful, but WITHOUT ANY WARRANTY; without even the implied
 13 *      warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 14 *      PURPOSE.  See the GNU General Public License for more details.
 15 *      You should have received a copy of the GNU General Public
 16 *      License along with this program; if not, write to the Free
 17 *      Software Foundation, Inc., 59 Temple Place - Suite 330,
 18 *      Boston, MA  02111-1307, USA.
 19 *      The full GNU General Public License is included in this
 20 *      distribution in the file called COPYING.
 21 *
 22 */
 23
 24#include <linux/module.h>
 25#include <linux/moduleparam.h>
 26#include <linux/platform_device.h>
 27#include <linux/io.h>
 28#include <linux/kernel.h>
 29#include <linux/types.h>
 30#include <linux/watchdog.h>
 31#include <linux/miscdevice.h>
 32#include <linux/seq_file.h>
 33#include <linux/debugfs.h>
 34#include <linux/uaccess.h>
 35#include <linux/spinlock.h>
 36
 37#define DRIVER_NAME "ie6xx_wdt"
 38
 39#define PV1	0x00
 40#define PV2	0x04
 41
 42#define RR0	0x0c
 43#define RR1	0x0d
 44#define WDT_RELOAD	0x01
 45#define WDT_TOUT	0x02
 46
 47#define WDTCR	0x10
 48#define WDT_PRE_SEL	0x04
 49#define WDT_RESET_SEL	0x08
 50#define WDT_RESET_EN	0x10
 51#define WDT_TOUT_EN	0x20
 52
 53#define DCR	0x14
 54
 55#define WDTLR	0x18
 56#define WDT_LOCK	0x01
 57#define WDT_ENABLE	0x02
 58#define WDT_TOUT_CNF	0x03
 59
 60#define MIN_TIME	1
 61#define MAX_TIME	(10 * 60) /* 10 minutes */
 62#define DEFAULT_TIME	60
 63
 64static unsigned int timeout = DEFAULT_TIME;
 65module_param(timeout, uint, 0);
 66MODULE_PARM_DESC(timeout,
 67		"Default Watchdog timer setting ("
 68		__MODULE_STRING(DEFAULT_TIME) "s)."
 69		"The range is from 1 to 600");
 70
 71static bool nowayout = WATCHDOG_NOWAYOUT;
 72module_param(nowayout, bool, 0);
 73MODULE_PARM_DESC(nowayout,
 74	"Watchdog cannot be stopped once started (default="
 75		__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 76
 77static u8 resetmode = 0x10;
 78module_param(resetmode, byte, 0);
 79MODULE_PARM_DESC(resetmode,
 80	"Resetmode bits: 0x08 warm reset (cold reset otherwise), "
 81	"0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
 82
 83static struct {
 84	unsigned short sch_wdtba;
 85	struct spinlock unlock_sequence;
 86#ifdef CONFIG_DEBUG_FS
 87	struct dentry *debugfs;
 88#endif
 89} ie6xx_wdt_data;
 90
 91/*
 92 * This is needed to write to preload and reload registers
 93 * struct ie6xx_wdt_data.unlock_sequence must be used
 94 * to prevent sequence interrupts
 95 */
 96static void ie6xx_wdt_unlock_registers(void)
 97{
 98	outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
 99	outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
100}
101
102static int ie6xx_wdt_ping(struct watchdog_device *wdd)
103{
104	spin_lock(&ie6xx_wdt_data.unlock_sequence);
105	ie6xx_wdt_unlock_registers();
106	outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
107	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
108	return 0;
109}
110
111static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
112{
113	u32 preload;
114	u64 clock;
115	u8 wdtcr;
116
117	/* Watchdog clock is PCI Clock (33MHz) */
118	clock = 33000000;
119	/* and the preload value is loaded into [34:15] of the down counter */
120	preload = (t * clock) >> 15;
121	/*
122	 * Manual states preload must be one less.
123	 * Does not wrap as t is at least 1
124	 */
125	preload -= 1;
126
127	spin_lock(&ie6xx_wdt_data.unlock_sequence);
128
129	/* Set ResetMode & Enable prescaler for range 10ms to 10 min */
130	wdtcr = resetmode & 0x38;
131	outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
132
133	ie6xx_wdt_unlock_registers();
134	outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
135
136	ie6xx_wdt_unlock_registers();
137	outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
138
139	ie6xx_wdt_unlock_registers();
140	outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
141
142	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
143
144	wdd->timeout = t;
145	return 0;
146}
147
148static int ie6xx_wdt_start(struct watchdog_device *wdd)
149{
150	ie6xx_wdt_set_timeout(wdd, wdd->timeout);
151
152	/* Enable the watchdog timer */
153	spin_lock(&ie6xx_wdt_data.unlock_sequence);
154	outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
155	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
156
157	return 0;
158}
159
160static int ie6xx_wdt_stop(struct watchdog_device *wdd)
161{
162	if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
163		return -1;
164
165	/* Disable the watchdog timer */
166	spin_lock(&ie6xx_wdt_data.unlock_sequence);
167	outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
168	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
169
170	return 0;
171}
172
173static const struct watchdog_info ie6xx_wdt_info = {
174	.identity =	"Intel Atom E6xx Watchdog",
175	.options =	WDIOF_SETTIMEOUT |
176			WDIOF_MAGICCLOSE |
177			WDIOF_KEEPALIVEPING,
178};
179
180static const struct watchdog_ops ie6xx_wdt_ops = {
181	.owner =	THIS_MODULE,
182	.start =	ie6xx_wdt_start,
183	.stop =		ie6xx_wdt_stop,
184	.ping =		ie6xx_wdt_ping,
185	.set_timeout =	ie6xx_wdt_set_timeout,
186};
187
188static struct watchdog_device ie6xx_wdt_dev = {
189	.info =		&ie6xx_wdt_info,
190	.ops =		&ie6xx_wdt_ops,
191	.min_timeout =	MIN_TIME,
192	.max_timeout =	MAX_TIME,
193};
194
195#ifdef CONFIG_DEBUG_FS
196
197static int ie6xx_wdt_dbg_show(struct seq_file *s, void *unused)
198{
199	seq_printf(s, "PV1   = 0x%08x\n",
200		inl(ie6xx_wdt_data.sch_wdtba + PV1));
201	seq_printf(s, "PV2   = 0x%08x\n",
202		inl(ie6xx_wdt_data.sch_wdtba + PV2));
203	seq_printf(s, "RR    = 0x%08x\n",
204		inw(ie6xx_wdt_data.sch_wdtba + RR0));
205	seq_printf(s, "WDTCR = 0x%08x\n",
206		inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
207	seq_printf(s, "DCR   = 0x%08x\n",
208		inl(ie6xx_wdt_data.sch_wdtba + DCR));
209	seq_printf(s, "WDTLR = 0x%08x\n",
210		inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
211
212	seq_printf(s, "\n");
213	return 0;
214}
215
216static int ie6xx_wdt_dbg_open(struct inode *inode, struct file *file)
217{
218	return single_open(file, ie6xx_wdt_dbg_show, NULL);
219}
220
221static const struct file_operations ie6xx_wdt_dbg_operations = {
222	.open		= ie6xx_wdt_dbg_open,
223	.read		= seq_read,
224	.llseek		= seq_lseek,
225	.release	= single_release,
226};
227
228static void __devinit ie6xx_wdt_debugfs_init(void)
229{
230	/* /sys/kernel/debug/ie6xx_wdt */
231	ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
232		S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_dbg_operations);
233}
234
235static void __devexit ie6xx_wdt_debugfs_exit(void)
236{
237	debugfs_remove(ie6xx_wdt_data.debugfs);
238}
239
240#else
241static void __devinit ie6xx_wdt_debugfs_init(void)
242{
243}
244
245static void __devexit ie6xx_wdt_debugfs_exit(void)
246{
247}
248#endif
249
250static int __devinit ie6xx_wdt_probe(struct platform_device *pdev)
251{
252	struct resource *res;
253	u8 wdtlr;
254	int ret;
255
256	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
257	if (!res)
258		return -ENODEV;
259
260	if (!request_region(res->start, resource_size(res), pdev->name)) {
261		dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
262			(u64)res->start);
263		return -EBUSY;
264	}
265
266	ie6xx_wdt_data.sch_wdtba = res->start;
267	dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
268
269	ie6xx_wdt_dev.timeout = timeout;
270	watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
271
272	spin_lock_init(&ie6xx_wdt_data.unlock_sequence);
273
274	wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR);
275	if (wdtlr & WDT_LOCK)
276		dev_warn(&pdev->dev,
277			"Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr);
278
279	ie6xx_wdt_debugfs_init();
280
281	ret = watchdog_register_device(&ie6xx_wdt_dev);
282	if (ret) {
283		dev_err(&pdev->dev,
284			"Watchdog timer: cannot register device (err =%d)\n",
285									ret);
286		goto misc_register_error;
287	}
288
289	return 0;
290
291misc_register_error:
292	ie6xx_wdt_debugfs_exit();
293	release_region(res->start, resource_size(res));
294	ie6xx_wdt_data.sch_wdtba = 0;
295	return ret;
296}
297
298static int __devexit ie6xx_wdt_remove(struct platform_device *pdev)
299{
300	struct resource *res;
301
302	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
303	ie6xx_wdt_stop(NULL);
304	watchdog_unregister_device(&ie6xx_wdt_dev);
305	ie6xx_wdt_debugfs_exit();
306	release_region(res->start, resource_size(res));
307	ie6xx_wdt_data.sch_wdtba = 0;
308
309	return 0;
310}
311
312static struct platform_driver ie6xx_wdt_driver = {
313	.probe		= ie6xx_wdt_probe,
314	.remove		= __devexit_p(ie6xx_wdt_remove),
315	.driver		= {
316		.name	= DRIVER_NAME,
317		.owner	= THIS_MODULE,
318	},
319};
320
321static int __init ie6xx_wdt_init(void)
322{
323	/* Check boot parameters to verify that their initial values */
324	/* are in range. */
325	if ((timeout < MIN_TIME) ||
326	    (timeout > MAX_TIME)) {
327		pr_err("Watchdog timer: value of timeout %d (dec) "
328		  "is out of range from %d to %d (dec)\n",
329		  timeout, MIN_TIME, MAX_TIME);
330		return -EINVAL;
331	}
332
333	return platform_driver_register(&ie6xx_wdt_driver);
334}
335
336static void __exit ie6xx_wdt_exit(void)
337{
338	platform_driver_unregister(&ie6xx_wdt_driver);
339}
340
341late_initcall(ie6xx_wdt_init);
342module_exit(ie6xx_wdt_exit);
343
344MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
345MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
346MODULE_LICENSE("GPL");
347MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
348MODULE_ALIAS("platform:" DRIVER_NAME);