1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun * drivers/watchdog/m54xx_wdt.c
3*4882a593Smuzhiyun *
4*4882a593Smuzhiyun * Watchdog driver for ColdFire MCF547x & MCF548x processors
5*4882a593Smuzhiyun * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be>
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Adapted from the IXP4xx watchdog driver, which carries these notices:
8*4882a593Smuzhiyun *
9*4882a593Smuzhiyun * Author: Deepak Saxena <dsaxena@plexity.net>
10*4882a593Smuzhiyun *
11*4882a593Smuzhiyun * Copyright 2004 (c) MontaVista, Software, Inc.
12*4882a593Smuzhiyun * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
13*4882a593Smuzhiyun *
14*4882a593Smuzhiyun * This file is licensed under the terms of the GNU General Public
15*4882a593Smuzhiyun * License version 2. This program is licensed "as is" without any
16*4882a593Smuzhiyun * warranty of any kind, whether express or implied.
17*4882a593Smuzhiyun */
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun #include <linux/module.h>
22*4882a593Smuzhiyun #include <linux/moduleparam.h>
23*4882a593Smuzhiyun #include <linux/types.h>
24*4882a593Smuzhiyun #include <linux/kernel.h>
25*4882a593Smuzhiyun #include <linux/fs.h>
26*4882a593Smuzhiyun #include <linux/miscdevice.h>
27*4882a593Smuzhiyun #include <linux/watchdog.h>
28*4882a593Smuzhiyun #include <linux/init.h>
29*4882a593Smuzhiyun #include <linux/bitops.h>
30*4882a593Smuzhiyun #include <linux/ioport.h>
31*4882a593Smuzhiyun #include <linux/uaccess.h>
32*4882a593Smuzhiyun #include <linux/io.h>
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun #include <asm/coldfire.h>
35*4882a593Smuzhiyun #include <asm/m54xxsim.h>
36*4882a593Smuzhiyun #include <asm/m54xxgpt.h>
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
39*4882a593Smuzhiyun static unsigned int heartbeat = 30; /* (secs) Default is 0.5 minute */
40*4882a593Smuzhiyun static unsigned long wdt_status;
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun #define WDT_IN_USE 0
43*4882a593Smuzhiyun #define WDT_OK_TO_CLOSE 1
44*4882a593Smuzhiyun
wdt_enable(void)45*4882a593Smuzhiyun static void wdt_enable(void)
46*4882a593Smuzhiyun {
47*4882a593Smuzhiyun unsigned int gms0;
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun /* preserve GPIO usage, if any */
50*4882a593Smuzhiyun gms0 = __raw_readl(MCF_GPT_GMS0);
51*4882a593Smuzhiyun if (gms0 & MCF_GPT_GMS_TMS_GPIO)
52*4882a593Smuzhiyun gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK
53*4882a593Smuzhiyun | MCF_GPT_GMS_OD);
54*4882a593Smuzhiyun else
55*4882a593Smuzhiyun gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD;
56*4882a593Smuzhiyun __raw_writel(gms0, MCF_GPT_GMS0);
57*4882a593Smuzhiyun __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) |
58*4882a593Smuzhiyun MCF_GPT_GCIR_CNT(0xffff), MCF_GPT_GCIR0);
59*4882a593Smuzhiyun gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE;
60*4882a593Smuzhiyun __raw_writel(gms0, MCF_GPT_GMS0);
61*4882a593Smuzhiyun }
62*4882a593Smuzhiyun
wdt_disable(void)63*4882a593Smuzhiyun static void wdt_disable(void)
64*4882a593Smuzhiyun {
65*4882a593Smuzhiyun unsigned int gms0;
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun /* disable watchdog */
68*4882a593Smuzhiyun gms0 = __raw_readl(MCF_GPT_GMS0);
69*4882a593Smuzhiyun gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE);
70*4882a593Smuzhiyun __raw_writel(gms0, MCF_GPT_GMS0);
71*4882a593Smuzhiyun }
72*4882a593Smuzhiyun
wdt_keepalive(void)73*4882a593Smuzhiyun static void wdt_keepalive(void)
74*4882a593Smuzhiyun {
75*4882a593Smuzhiyun unsigned int gms0;
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun gms0 = __raw_readl(MCF_GPT_GMS0);
78*4882a593Smuzhiyun gms0 |= MCF_GPT_GMS_OCPW(0xA5);
79*4882a593Smuzhiyun __raw_writel(gms0, MCF_GPT_GMS0);
80*4882a593Smuzhiyun }
81*4882a593Smuzhiyun
m54xx_wdt_open(struct inode * inode,struct file * file)82*4882a593Smuzhiyun static int m54xx_wdt_open(struct inode *inode, struct file *file)
83*4882a593Smuzhiyun {
84*4882a593Smuzhiyun if (test_and_set_bit(WDT_IN_USE, &wdt_status))
85*4882a593Smuzhiyun return -EBUSY;
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
88*4882a593Smuzhiyun wdt_enable();
89*4882a593Smuzhiyun return stream_open(inode, file);
90*4882a593Smuzhiyun }
91*4882a593Smuzhiyun
m54xx_wdt_write(struct file * file,const char * data,size_t len,loff_t * ppos)92*4882a593Smuzhiyun static ssize_t m54xx_wdt_write(struct file *file, const char *data,
93*4882a593Smuzhiyun size_t len, loff_t *ppos)
94*4882a593Smuzhiyun {
95*4882a593Smuzhiyun if (len) {
96*4882a593Smuzhiyun if (!nowayout) {
97*4882a593Smuzhiyun size_t i;
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun for (i = 0; i != len; i++) {
102*4882a593Smuzhiyun char c;
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun if (get_user(c, data + i))
105*4882a593Smuzhiyun return -EFAULT;
106*4882a593Smuzhiyun if (c == 'V')
107*4882a593Smuzhiyun set_bit(WDT_OK_TO_CLOSE, &wdt_status);
108*4882a593Smuzhiyun }
109*4882a593Smuzhiyun }
110*4882a593Smuzhiyun wdt_keepalive();
111*4882a593Smuzhiyun }
112*4882a593Smuzhiyun return len;
113*4882a593Smuzhiyun }
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun static const struct watchdog_info ident = {
116*4882a593Smuzhiyun .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
117*4882a593Smuzhiyun WDIOF_KEEPALIVEPING,
118*4882a593Smuzhiyun .identity = "Coldfire M54xx Watchdog",
119*4882a593Smuzhiyun };
120*4882a593Smuzhiyun
m54xx_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)121*4882a593Smuzhiyun static long m54xx_wdt_ioctl(struct file *file, unsigned int cmd,
122*4882a593Smuzhiyun unsigned long arg)
123*4882a593Smuzhiyun {
124*4882a593Smuzhiyun int ret = -ENOTTY;
125*4882a593Smuzhiyun int time;
126*4882a593Smuzhiyun
127*4882a593Smuzhiyun switch (cmd) {
128*4882a593Smuzhiyun case WDIOC_GETSUPPORT:
129*4882a593Smuzhiyun ret = copy_to_user((struct watchdog_info *)arg, &ident,
130*4882a593Smuzhiyun sizeof(ident)) ? -EFAULT : 0;
131*4882a593Smuzhiyun break;
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun case WDIOC_GETSTATUS:
134*4882a593Smuzhiyun ret = put_user(0, (int *)arg);
135*4882a593Smuzhiyun break;
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun case WDIOC_GETBOOTSTATUS:
138*4882a593Smuzhiyun ret = put_user(0, (int *)arg);
139*4882a593Smuzhiyun break;
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun case WDIOC_KEEPALIVE:
142*4882a593Smuzhiyun wdt_keepalive();
143*4882a593Smuzhiyun ret = 0;
144*4882a593Smuzhiyun break;
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun case WDIOC_SETTIMEOUT:
147*4882a593Smuzhiyun ret = get_user(time, (int *)arg);
148*4882a593Smuzhiyun if (ret)
149*4882a593Smuzhiyun break;
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun if (time <= 0 || time > 30) {
152*4882a593Smuzhiyun ret = -EINVAL;
153*4882a593Smuzhiyun break;
154*4882a593Smuzhiyun }
155*4882a593Smuzhiyun
156*4882a593Smuzhiyun heartbeat = time;
157*4882a593Smuzhiyun wdt_enable();
158*4882a593Smuzhiyun fallthrough;
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun case WDIOC_GETTIMEOUT:
161*4882a593Smuzhiyun ret = put_user(heartbeat, (int *)arg);
162*4882a593Smuzhiyun break;
163*4882a593Smuzhiyun }
164*4882a593Smuzhiyun return ret;
165*4882a593Smuzhiyun }
166*4882a593Smuzhiyun
m54xx_wdt_release(struct inode * inode,struct file * file)167*4882a593Smuzhiyun static int m54xx_wdt_release(struct inode *inode, struct file *file)
168*4882a593Smuzhiyun {
169*4882a593Smuzhiyun if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
170*4882a593Smuzhiyun wdt_disable();
171*4882a593Smuzhiyun else {
172*4882a593Smuzhiyun pr_crit("Device closed unexpectedly - timer will not stop\n");
173*4882a593Smuzhiyun wdt_keepalive();
174*4882a593Smuzhiyun }
175*4882a593Smuzhiyun clear_bit(WDT_IN_USE, &wdt_status);
176*4882a593Smuzhiyun clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun return 0;
179*4882a593Smuzhiyun }
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun static const struct file_operations m54xx_wdt_fops = {
183*4882a593Smuzhiyun .owner = THIS_MODULE,
184*4882a593Smuzhiyun .llseek = no_llseek,
185*4882a593Smuzhiyun .write = m54xx_wdt_write,
186*4882a593Smuzhiyun .unlocked_ioctl = m54xx_wdt_ioctl,
187*4882a593Smuzhiyun .compat_ioctl = compat_ptr_ioctl,
188*4882a593Smuzhiyun .open = m54xx_wdt_open,
189*4882a593Smuzhiyun .release = m54xx_wdt_release,
190*4882a593Smuzhiyun };
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun static struct miscdevice m54xx_wdt_miscdev = {
193*4882a593Smuzhiyun .minor = WATCHDOG_MINOR,
194*4882a593Smuzhiyun .name = "watchdog",
195*4882a593Smuzhiyun .fops = &m54xx_wdt_fops,
196*4882a593Smuzhiyun };
197*4882a593Smuzhiyun
m54xx_wdt_init(void)198*4882a593Smuzhiyun static int __init m54xx_wdt_init(void)
199*4882a593Smuzhiyun {
200*4882a593Smuzhiyun if (!request_mem_region(MCF_GPT_GCIR0, 4, "Coldfire M54xx Watchdog")) {
201*4882a593Smuzhiyun pr_warn("I/O region busy\n");
202*4882a593Smuzhiyun return -EBUSY;
203*4882a593Smuzhiyun }
204*4882a593Smuzhiyun pr_info("driver is loaded\n");
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun return misc_register(&m54xx_wdt_miscdev);
207*4882a593Smuzhiyun }
208*4882a593Smuzhiyun
m54xx_wdt_exit(void)209*4882a593Smuzhiyun static void __exit m54xx_wdt_exit(void)
210*4882a593Smuzhiyun {
211*4882a593Smuzhiyun misc_deregister(&m54xx_wdt_miscdev);
212*4882a593Smuzhiyun release_mem_region(MCF_GPT_GCIR0, 4);
213*4882a593Smuzhiyun }
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun module_init(m54xx_wdt_init);
216*4882a593Smuzhiyun module_exit(m54xx_wdt_exit);
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>");
219*4882a593Smuzhiyun MODULE_DESCRIPTION("Coldfire M54xx Watchdog");
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun module_param(heartbeat, int, 0);
222*4882a593Smuzhiyun MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)");
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun module_param(nowayout, bool, 0);
225*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun MODULE_LICENSE("GPL");
228