Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
fabbfb9e | 2 | /* |
0d7b1014 | 3 | * mpc8xxx_wdt.c - MPC8xx/MPC83xx/MPC86xx watchdog userspace interface |
fabbfb9e KG |
4 | * |
5 | * Authors: Dave Updegraff <dave@cray.org> | |
5f3b2756 WVS |
6 | * Kumar Gala <galak@kernel.crashing.org> |
7 | * Attribution: from 83xx_wst: Florian Schirmer <jolt@tuxbox.org> | |
8 | * ..and from sc520_wdt | |
500c919e AV |
9 | * Copyright (c) 2008 MontaVista Software, Inc. |
10 | * Anton Vorontsov <avorontsov@ru.mvista.com> | |
fabbfb9e KG |
11 | * |
12 | * Note: it appears that you can only actually ENABLE or DISABLE the thing | |
13 | * once after POR. Once enabled, you cannot disable, and vice versa. | |
fabbfb9e KG |
14 | */ |
15 | ||
fabbfb9e KG |
16 | #include <linux/fs.h> |
17 | #include <linux/init.h> | |
18 | #include <linux/kernel.h> | |
cc85f87a RH |
19 | #include <linux/of.h> |
20 | #include <linux/platform_device.h> | |
fabbfb9e KG |
21 | #include <linux/module.h> |
22 | #include <linux/watchdog.h> | |
f26ef3dc AC |
23 | #include <linux/io.h> |
24 | #include <linux/uaccess.h> | |
ef8ab12e | 25 | #include <sysdev/fsl_soc.h> |
fabbfb9e | 26 | |
19ce9490 CL |
27 | #define WATCHDOG_TIMEOUT 10 |
28 | ||
59ca1b0d | 29 | struct mpc8xxx_wdt { |
fabbfb9e KG |
30 | __be32 res0; |
31 | __be32 swcrr; /* System watchdog control register */ | |
32 | #define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */ | |
19ce9490 | 33 | #define SWCRR_SWF 0x00000008 /* Software Watchdog Freeze (mpc8xx). */ |
fabbfb9e KG |
34 | #define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */ |
35 | #define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/ | |
36 | #define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */ | |
37 | __be32 swcnr; /* System watchdog count register */ | |
38 | u8 res1[2]; | |
39 | __be16 swsrr; /* System watchdog service register */ | |
40 | u8 res2[0xF0]; | |
41 | }; | |
42 | ||
59ca1b0d | 43 | struct mpc8xxx_wdt_type { |
500c919e AV |
44 | int prescaler; |
45 | bool hw_enabled; | |
38e48b71 | 46 | u32 rsr_mask; |
500c919e AV |
47 | }; |
48 | ||
7997ebad UKK |
49 | struct mpc8xxx_wdt_ddata { |
50 | struct mpc8xxx_wdt __iomem *base; | |
51 | struct watchdog_device wdd; | |
7997ebad | 52 | spinlock_t lock; |
19ce9490 | 53 | u16 swtc; |
7997ebad | 54 | }; |
fabbfb9e | 55 | |
19ce9490 | 56 | static u16 timeout; |
fabbfb9e | 57 | module_param(timeout, ushort, 0); |
f26ef3dc | 58 | MODULE_PARM_DESC(timeout, |
19ce9490 CL |
59 | "Watchdog timeout in seconds. (1<timeout<65535, default=" |
60 | __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | |
fabbfb9e | 61 | |
90ab5ee9 | 62 | static bool reset = 1; |
fabbfb9e | 63 | module_param(reset, bool, 0); |
f26ef3dc AC |
64 | MODULE_PARM_DESC(reset, |
65 | "Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset"); | |
fabbfb9e | 66 | |
86a1e189 WVS |
67 | static bool nowayout = WATCHDOG_NOWAYOUT; |
68 | module_param(nowayout, bool, 0); | |
500c919e AV |
69 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " |
70 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
71 | ||
7997ebad | 72 | static void mpc8xxx_wdt_keepalive(struct mpc8xxx_wdt_ddata *ddata) |
fabbfb9e KG |
73 | { |
74 | /* Ping the WDT */ | |
7997ebad UKK |
75 | spin_lock(&ddata->lock); |
76 | out_be16(&ddata->base->swsrr, 0x556c); | |
77 | out_be16(&ddata->base->swsrr, 0xaa39); | |
78 | spin_unlock(&ddata->lock); | |
fabbfb9e KG |
79 | } |
80 | ||
d5cfaf0a | 81 | static int mpc8xxx_wdt_start(struct watchdog_device *w) |
fabbfb9e | 82 | { |
7997ebad UKK |
83 | struct mpc8xxx_wdt_ddata *ddata = |
84 | container_of(w, struct mpc8xxx_wdt_ddata, wdd); | |
19ce9490 | 85 | u32 tmp = in_be32(&ddata->base->swcrr); |
fabbfb9e KG |
86 | |
87 | /* Good, fire up the show */ | |
19ce9490 CL |
88 | tmp &= ~(SWCRR_SWTC | SWCRR_SWF | SWCRR_SWEN | SWCRR_SWRI | SWCRR_SWPR); |
89 | tmp |= SWCRR_SWEN | SWCRR_SWPR | (ddata->swtc << 16); | |
90 | ||
fabbfb9e KG |
91 | if (reset) |
92 | tmp |= SWCRR_SWRI; | |
93 | ||
7997ebad | 94 | out_be32(&ddata->base->swcrr, tmp); |
fabbfb9e | 95 | |
19ce9490 CL |
96 | tmp = in_be32(&ddata->base->swcrr); |
97 | if (!(tmp & SWCRR_SWEN)) | |
98 | return -EOPNOTSUPP; | |
99 | ||
100 | ddata->swtc = tmp >> 16; | |
101 | set_bit(WDOG_HW_RUNNING, &ddata->wdd.status); | |
500c919e | 102 | |
d5cfaf0a | 103 | return 0; |
fabbfb9e KG |
104 | } |
105 | ||
d5cfaf0a | 106 | static int mpc8xxx_wdt_ping(struct watchdog_device *w) |
fabbfb9e | 107 | { |
7997ebad UKK |
108 | struct mpc8xxx_wdt_ddata *ddata = |
109 | container_of(w, struct mpc8xxx_wdt_ddata, wdd); | |
110 | ||
111 | mpc8xxx_wdt_keepalive(ddata); | |
fabbfb9e KG |
112 | return 0; |
113 | } | |
114 | ||
d5cfaf0a | 115 | static struct watchdog_info mpc8xxx_wdt_info = { |
19ce9490 | 116 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT, |
d5cfaf0a CL |
117 | .firmware_version = 1, |
118 | .identity = "MPC8xxx", | |
fabbfb9e KG |
119 | }; |
120 | ||
47b45c4a | 121 | static const struct watchdog_ops mpc8xxx_wdt_ops = { |
d5cfaf0a CL |
122 | .owner = THIS_MODULE, |
123 | .start = mpc8xxx_wdt_start, | |
124 | .ping = mpc8xxx_wdt_ping, | |
d5cfaf0a CL |
125 | }; |
126 | ||
2d991a16 | 127 | static int mpc8xxx_wdt_probe(struct platform_device *ofdev) |
fabbfb9e | 128 | { |
fabbfb9e | 129 | int ret; |
de5f7122 | 130 | struct resource *res; |
639397e4 | 131 | const struct mpc8xxx_wdt_type *wdt_type; |
7997ebad | 132 | struct mpc8xxx_wdt_ddata *ddata; |
ef8ab12e | 133 | u32 freq = fsl_get_sys_freq(); |
500c919e | 134 | bool enabled; |
79b10e09 | 135 | struct device *dev = &ofdev->dev; |
fabbfb9e | 136 | |
79b10e09 | 137 | wdt_type = of_device_get_match_data(dev); |
f0ded83b | 138 | if (!wdt_type) |
1c48a5c9 | 139 | return -EINVAL; |
1c48a5c9 | 140 | |
ef8ab12e AV |
141 | if (!freq || freq == -1) |
142 | return -EINVAL; | |
fabbfb9e | 143 | |
79b10e09 | 144 | ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); |
7997ebad UKK |
145 | if (!ddata) |
146 | return -ENOMEM; | |
147 | ||
0f0a6a28 | 148 | ddata->base = devm_platform_ioremap_resource(ofdev, 0); |
7997ebad UKK |
149 | if (IS_ERR(ddata->base)) |
150 | return PTR_ERR(ddata->base); | |
fabbfb9e | 151 | |
7997ebad | 152 | enabled = in_be32(&ddata->base->swcrr) & SWCRR_SWEN; |
500c919e | 153 | if (!enabled && wdt_type->hw_enabled) { |
79b10e09 | 154 | dev_info(dev, "could not be enabled in software\n"); |
72cd501e | 155 | return -ENODEV; |
500c919e AV |
156 | } |
157 | ||
38e48b71 CL |
158 | res = platform_get_resource(ofdev, IORESOURCE_MEM, 1); |
159 | if (res) { | |
160 | bool status; | |
161 | u32 __iomem *rsr = ioremap(res->start, resource_size(res)); | |
162 | ||
163 | if (!rsr) | |
164 | return -ENOMEM; | |
165 | ||
166 | status = in_be32(rsr) & wdt_type->rsr_mask; | |
167 | ddata->wdd.bootstatus = status ? WDIOF_CARDRESET : 0; | |
168 | /* clear reset status bits related to watchdog timer */ | |
169 | out_be32(rsr, wdt_type->rsr_mask); | |
170 | iounmap(rsr); | |
171 | ||
172 | dev_info(dev, "Last boot was %scaused by watchdog\n", | |
173 | status ? "" : "not "); | |
174 | } | |
175 | ||
7997ebad | 176 | spin_lock_init(&ddata->lock); |
7997ebad | 177 | |
0b9491b6 ZY |
178 | ddata->wdd.info = &mpc8xxx_wdt_info; |
179 | ddata->wdd.ops = &mpc8xxx_wdt_ops; | |
7997ebad | 180 | |
19ce9490 | 181 | ddata->wdd.timeout = WATCHDOG_TIMEOUT; |
79b10e09 | 182 | watchdog_init_timeout(&ddata->wdd, timeout, dev); |
50ffb53e | 183 | |
7997ebad | 184 | watchdog_set_nowayout(&ddata->wdd, nowayout); |
50ffb53e | 185 | |
19ce9490 CL |
186 | ddata->swtc = min(ddata->wdd.timeout * freq / wdt_type->prescaler, |
187 | 0xffffU); | |
500c919e AV |
188 | |
189 | /* | |
190 | * If the watchdog was previously enabled or we're running on | |
59ca1b0d | 191 | * MPC8xxx, we should ping the wdt from the kernel until the |
500c919e AV |
192 | * userspace handles it. |
193 | */ | |
194 | if (enabled) | |
19ce9490 CL |
195 | mpc8xxx_wdt_start(&ddata->wdd); |
196 | ||
197 | ddata->wdd.max_hw_heartbeat_ms = (ddata->swtc * wdt_type->prescaler) / | |
198 | (freq / 1000); | |
199 | ddata->wdd.min_timeout = ddata->wdd.max_hw_heartbeat_ms / 1000; | |
200 | if (ddata->wdd.timeout < ddata->wdd.min_timeout) | |
201 | ddata->wdd.timeout = ddata->wdd.min_timeout; | |
202 | ||
81df6db6 | 203 | ret = devm_watchdog_register_device(dev, &ddata->wdd); |
a2390273 | 204 | if (ret) |
19ce9490 | 205 | return ret; |
19ce9490 | 206 | |
79b10e09 CL |
207 | dev_info(dev, |
208 | "WDT driver for MPC8xxx initialized. mode:%s timeout=%d sec\n", | |
209 | reset ? "reset" : "interrupt", ddata->wdd.timeout); | |
7997ebad UKK |
210 | |
211 | platform_set_drvdata(ofdev, ddata); | |
fabbfb9e | 212 | return 0; |
fabbfb9e KG |
213 | } |
214 | ||
59ca1b0d | 215 | static const struct of_device_id mpc8xxx_wdt_match[] = { |
ef8ab12e AV |
216 | { |
217 | .compatible = "mpc83xx_wdt", | |
59ca1b0d | 218 | .data = &(struct mpc8xxx_wdt_type) { |
500c919e | 219 | .prescaler = 0x10000, |
38e48b71 | 220 | .rsr_mask = BIT(3), /* RSR Bit SWRS */ |
500c919e AV |
221 | }, |
222 | }, | |
223 | { | |
224 | .compatible = "fsl,mpc8610-wdt", | |
59ca1b0d | 225 | .data = &(struct mpc8xxx_wdt_type) { |
500c919e AV |
226 | .prescaler = 0x10000, |
227 | .hw_enabled = true, | |
38e48b71 | 228 | .rsr_mask = BIT(20), /* RSTRSCR Bit WDT_RR */ |
500c919e | 229 | }, |
ef8ab12e | 230 | }, |
0d7b1014 AV |
231 | { |
232 | .compatible = "fsl,mpc823-wdt", | |
233 | .data = &(struct mpc8xxx_wdt_type) { | |
234 | .prescaler = 0x800, | |
4af897fa | 235 | .hw_enabled = true, |
38e48b71 | 236 | .rsr_mask = BIT(28), /* RSR Bit SWRS */ |
0d7b1014 AV |
237 | }, |
238 | }, | |
ef8ab12e AV |
239 | {}, |
240 | }; | |
59ca1b0d | 241 | MODULE_DEVICE_TABLE(of, mpc8xxx_wdt_match); |
ef8ab12e | 242 | |
1c48a5c9 | 243 | static struct platform_driver mpc8xxx_wdt_driver = { |
59ca1b0d | 244 | .probe = mpc8xxx_wdt_probe, |
4018294b GL |
245 | .driver = { |
246 | .name = "mpc8xxx_wdt", | |
4018294b | 247 | .of_match_table = mpc8xxx_wdt_match, |
fabbfb9e KG |
248 | }, |
249 | }; | |
250 | ||
59ca1b0d | 251 | static int __init mpc8xxx_wdt_init(void) |
fabbfb9e | 252 | { |
1c48a5c9 | 253 | return platform_driver_register(&mpc8xxx_wdt_driver); |
fabbfb9e | 254 | } |
0d7b1014 | 255 | arch_initcall(mpc8xxx_wdt_init); |
fabbfb9e | 256 | |
59ca1b0d | 257 | static void __exit mpc8xxx_wdt_exit(void) |
fabbfb9e | 258 | { |
1c48a5c9 | 259 | platform_driver_unregister(&mpc8xxx_wdt_driver); |
fabbfb9e | 260 | } |
59ca1b0d | 261 | module_exit(mpc8xxx_wdt_exit); |
fabbfb9e KG |
262 | |
263 | MODULE_AUTHOR("Dave Updegraff, Kumar Gala"); | |
0d7b1014 AV |
264 | MODULE_DESCRIPTION("Driver for watchdog timer in MPC8xx/MPC83xx/MPC86xx " |
265 | "uProcessors"); | |
fabbfb9e | 266 | MODULE_LICENSE("GPL"); |