]> git.proxmox.com Git - mirror_edk2.git/blame - ArmPlatformPkg/Drivers/SP805WatchdogDxe/SP805Watchdog.c
ArmPlatformPkg/SP805WatchdogDxe: switch to interrupt mode
[mirror_edk2.git] / ArmPlatformPkg / Drivers / SP805WatchdogDxe / SP805Watchdog.c
CommitLineData
1e57a462 1/** @file\r
2*\r
b2ce4a39 3* Copyright (c) 2011-2013, ARM Limited. All rights reserved.\r
e3fa3d83 4* Copyright (c) 2018, Linaro Limited. All rights reserved.\r
1e57a462 5*\r
6* This program and the accompanying materials\r
7* are licensed and made available under the terms and conditions of the BSD License\r
8* which accompanies this distribution. The full text of the license may be found at\r
9* http://opensource.org/licenses/bsd-license.php\r
10*\r
11* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
12* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
13*\r
14**/\r
15\r
16\r
17#include <PiDxe.h>\r
18\r
19#include <Library/BaseLib.h>\r
20#include <Library/BaseMemoryLib.h>\r
21#include <Library/DebugLib.h>\r
22#include <Library/IoLib.h>\r
1e57a462 23#include <Library/UefiBootServicesTableLib.h>\r
5afabd5e 24#include <Library/UefiRuntimeServicesTableLib.h>\r
1e57a462 25\r
5afabd5e 26#include <Protocol/HardwareInterrupt.h>\r
1e57a462 27#include <Protocol/WatchdogTimer.h>\r
b9fddfb8
AB
28\r
29#include "SP805Watchdog.h"\r
1e57a462 30\r
5afabd5e
AB
31STATIC EFI_EVENT mEfiExitBootServicesEvent;\r
32STATIC EFI_HARDWARE_INTERRUPT_PROTOCOL *mInterrupt;\r
33STATIC EFI_WATCHDOG_TIMER_NOTIFY mWatchdogNotify;\r
34STATIC UINT32 mTimerPeriod;\r
1e57a462 35\r
36/**\r
37 Make sure the SP805 registers are unlocked for writing.\r
38\r
39 Note: The SP805 Watchdog Timer supports locking of its registers,\r
40 i.e. it inhibits all writes to avoid rogue software accidentally\r
41 corrupting their contents.\r
42**/\r
b2ce4a39 43STATIC\r
1e57a462 44VOID\r
45SP805Unlock (\r
46 VOID\r
47 )\r
48{\r
e3fa3d83
AB
49 if (MmioRead32 (SP805_WDOG_LOCK_REG) == SP805_WDOG_LOCK_IS_LOCKED) {\r
50 MmioWrite32 (SP805_WDOG_LOCK_REG, SP805_WDOG_SPECIAL_UNLOCK_CODE);\r
1e57a462 51 }\r
52}\r
53\r
54/**\r
55 Make sure the SP805 registers are locked and can not be overwritten.\r
56\r
57 Note: The SP805 Watchdog Timer supports locking of its registers,\r
58 i.e. it inhibits all writes to avoid rogue software accidentally\r
59 corrupting their contents.\r
60**/\r
b2ce4a39 61STATIC\r
1e57a462 62VOID\r
63SP805Lock (\r
64 VOID\r
65 )\r
66{\r
e3fa3d83 67 if (MmioRead32 (SP805_WDOG_LOCK_REG) == SP805_WDOG_LOCK_IS_UNLOCKED) {\r
1e57a462 68 // To lock it, just write in any number (except the special unlock code).\r
e3fa3d83 69 MmioWrite32 (SP805_WDOG_LOCK_REG, SP805_WDOG_LOCK_IS_LOCKED);\r
1e57a462 70 }\r
71}\r
72\r
5afabd5e
AB
73STATIC\r
74VOID\r
75EFIAPI\r
76SP805InterruptHandler (\r
77 IN HARDWARE_INTERRUPT_SOURCE Source,\r
78 IN EFI_SYSTEM_CONTEXT SystemContext\r
79 )\r
80{\r
81 SP805Unlock ();\r
82 MmioWrite32 (SP805_WDOG_INT_CLR_REG, 0); // write of any value clears the irq\r
83 SP805Lock ();\r
84\r
85 mInterrupt->EndOfInterrupt (mInterrupt, Source);\r
86\r
87 //\r
88 // The notify function should be called with the elapsed number of ticks\r
89 // since the watchdog was armed, which should exceed the timer period.\r
90 // We don't actually know the elapsed number of ticks, so let's return\r
91 // the timer period plus 1.\r
92 //\r
93 if (mWatchdogNotify != NULL) {\r
94 mWatchdogNotify (mTimerPeriod + 1);\r
95 }\r
96\r
97 gRT->ResetSystem (EfiResetCold, EFI_TIMEOUT, 0, NULL);\r
98}\r
99\r
1e57a462 100/**\r
101 Stop the SP805 watchdog timer from counting down by disabling interrupts.\r
102**/\r
b2ce4a39 103STATIC\r
1e57a462 104VOID\r
105SP805Stop (\r
106 VOID\r
107 )\r
108{\r
109 // Disable interrupts\r
e3fa3d83
AB
110 if ((MmioRead32 (SP805_WDOG_CONTROL_REG) & SP805_WDOG_CTRL_INTEN) != 0) {\r
111 MmioAnd32 (SP805_WDOG_CONTROL_REG, ~SP805_WDOG_CTRL_INTEN);\r
1e57a462 112 }\r
113}\r
114\r
115/**\r
116 Starts the SP805 counting down by enabling interrupts.\r
117 The count down will start from the value stored in the Load register,\r
118 not from the value where it was previously stopped.\r
119**/\r
b2ce4a39 120STATIC\r
1e57a462 121VOID\r
122SP805Start (\r
123 VOID\r
124 )\r
125{\r
126 // Enable interrupts\r
e3fa3d83
AB
127 if ((MmioRead32 (SP805_WDOG_CONTROL_REG) & SP805_WDOG_CTRL_INTEN) == 0) {\r
128 MmioOr32 (SP805_WDOG_CONTROL_REG, SP805_WDOG_CTRL_INTEN);\r
1e57a462 129 }\r
130}\r
131\r
132/**\r
133 On exiting boot services we must make sure the SP805 Watchdog Timer\r
134 is stopped.\r
135**/\r
e3fa3d83 136STATIC\r
1e57a462 137VOID\r
138EFIAPI\r
139ExitBootServicesEvent (\r
140 IN EFI_EVENT Event,\r
141 IN VOID *Context\r
142 )\r
143{\r
e3fa3d83
AB
144 SP805Unlock ();\r
145 SP805Stop ();\r
146 SP805Lock ();\r
1e57a462 147}\r
148\r
149/**\r
150 This function registers the handler NotifyFunction so it is called every time\r
151 the watchdog timer expires. It also passes the amount of time since the last\r
152 handler call to the NotifyFunction.\r
153 If NotifyFunction is not NULL and a handler is not already registered,\r
154 then the new handler is registered and EFI_SUCCESS is returned.\r
155 If NotifyFunction is NULL, and a handler is already registered,\r
156 then that handler is unregistered.\r
157 If an attempt is made to register a handler when a handler is already registered,\r
158 then EFI_ALREADY_STARTED is returned.\r
159 If an attempt is made to unregister a handler when a handler is not registered,\r
160 then EFI_INVALID_PARAMETER is returned.\r
161\r
162 @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
163 @param NotifyFunction The function to call when a timer interrupt fires. This\r
164 function executes at TPL_HIGH_LEVEL. The DXE Core will\r
165 register a handler for the timer interrupt, so it can know\r
166 how much time has passed. This information is used to\r
167 signal timer based events. NULL will unregister the handler.\r
168\r
169 @retval EFI_SUCCESS The watchdog timer handler was registered.\r
170 @retval EFI_ALREADY_STARTED NotifyFunction is not NULL, and a handler is already\r
171 registered.\r
172 @retval EFI_INVALID_PARAMETER NotifyFunction is NULL, and a handler was not\r
173 previously registered.\r
174\r
175**/\r
e3fa3d83 176STATIC\r
1e57a462 177EFI_STATUS\r
178EFIAPI\r
179SP805RegisterHandler (\r
e3fa3d83 180 IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,\r
1e57a462 181 IN EFI_WATCHDOG_TIMER_NOTIFY NotifyFunction\r
182 )\r
183{\r
5afabd5e
AB
184 if (mWatchdogNotify == NULL && NotifyFunction == NULL) {\r
185 return EFI_INVALID_PARAMETER;\r
186 }\r
187\r
188 if (mWatchdogNotify != NULL && NotifyFunction != NULL) {\r
189 return EFI_ALREADY_STARTED;\r
190 }\r
191\r
192 mWatchdogNotify = NotifyFunction;\r
193 return EFI_SUCCESS;\r
1e57a462 194}\r
195\r
196/**\r
197\r
198 This function adjusts the period of timer interrupts to the value specified\r
199 by TimerPeriod. If the timer period is updated, then the selected timer\r
200 period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned. If\r
201 the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.\r
202 If an error occurs while attempting to update the timer period, then the\r
203 timer hardware will be put back in its state prior to this call, and\r
204 EFI_DEVICE_ERROR is returned. If TimerPeriod is 0, then the timer interrupt\r
205 is disabled. This is not the same as disabling the CPU's interrupts.\r
206 Instead, it must either turn off the timer hardware, or it must adjust the\r
207 interrupt controller so that a CPU interrupt is not generated when the timer\r
208 interrupt fires.\r
209\r
210 @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
211 @param TimerPeriod The rate to program the timer interrupt in 100 nS units. If\r
212 the timer hardware is not programmable, then EFI_UNSUPPORTED is\r
213 returned. If the timer is programmable, then the timer period\r
214 will be rounded up to the nearest timer period that is supported\r
215 by the timer hardware. If TimerPeriod is set to 0, then the\r
216 timer interrupts will be disabled.\r
217\r
218\r
219 @retval EFI_SUCCESS The timer period was changed.\r
220 @retval EFI_UNSUPPORTED The platform cannot change the period of the timer interrupt.\r
221 @retval EFI_DEVICE_ERROR The timer period could not be changed due to a device error.\r
222\r
223**/\r
e3fa3d83 224STATIC\r
1e57a462 225EFI_STATUS\r
226EFIAPI\r
227SP805SetTimerPeriod (\r
e3fa3d83 228 IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,\r
1e57a462 229 IN UINT64 TimerPeriod // In 100ns units\r
230 )\r
231{\r
e3fa3d83 232 EFI_STATUS Status;\r
1e57a462 233 UINT64 Ticks64bit;\r
234\r
e3fa3d83 235 SP805Unlock ();\r
1e57a462 236\r
e3fa3d83
AB
237 Status = EFI_SUCCESS;\r
238\r
239 if (TimerPeriod == 0) {\r
1e57a462 240 // This is a watchdog stop request\r
e3fa3d83 241 SP805Stop ();\r
1e57a462 242 } else {\r
243 // Calculate the Watchdog ticks required for a delay of (TimerTicks * 100) nanoseconds\r
5afabd5e 244 // The SP805 will count down to zero and generate an interrupt.\r
1e57a462 245 //\r
5afabd5e 246 // WatchdogTicks = ((TimerPeriod * 100 * SP805_CLOCK_FREQUENCY) / 1GHz);\r
1e57a462 247 //\r
248 // i.e.:\r
249 //\r
5afabd5e 250 // WatchdogTicks = (TimerPeriod * SP805_CLOCK_FREQUENCY) / 10 MHz ;\r
1e57a462 251\r
e3fa3d83 252 Ticks64bit = MultU64x32 (TimerPeriod, PcdGet32 (PcdSP805WatchdogClockFrequencyInHz));\r
5afabd5e 253 Ticks64bit = DivU64x32 (Ticks64bit, 10 * 1000 * 1000);\r
1e57a462 254\r
255 // The registers in the SP805 are only 32 bits\r
e3fa3d83 256 if (Ticks64bit > MAX_UINT32) {\r
1e57a462 257 // We could load the watchdog with the maximum supported value but\r
258 // if a smaller value was requested, this could have the watchdog\r
259 // triggering before it was intended.\r
260 // Better generate an error to let the caller know.\r
261 Status = EFI_DEVICE_ERROR;\r
262 goto EXIT;\r
263 }\r
264\r
265 // Update the watchdog with a 32-bit value.\r
e3fa3d83 266 MmioWrite32 (SP805_WDOG_LOAD_REG, (UINT32)Ticks64bit);\r
1e57a462 267\r
268 // Start the watchdog\r
e3fa3d83 269 SP805Start ();\r
1e57a462 270 }\r
271\r
5afabd5e
AB
272 mTimerPeriod = TimerPeriod;\r
273\r
e3fa3d83 274EXIT:\r
1e57a462 275 // Ensure the watchdog is locked before exiting.\r
e3fa3d83 276 SP805Lock ();\r
5afabd5e 277 ASSERT_EFI_ERROR (Status);\r
1e57a462 278 return Status;\r
279}\r
280\r
281/**\r
282 This function retrieves the period of timer interrupts in 100 ns units,\r
283 returns that value in TimerPeriod, and returns EFI_SUCCESS. If TimerPeriod\r
284 is NULL, then EFI_INVALID_PARAMETER is returned. If a TimerPeriod of 0 is\r
285 returned, then the timer is currently disabled.\r
286\r
287 @param This The EFI_TIMER_ARCH_PROTOCOL instance.\r
288 @param TimerPeriod A pointer to the timer period to retrieve in 100 ns units. If\r
289 0 is returned, then the timer is currently disabled.\r
290\r
291\r
292 @retval EFI_SUCCESS The timer period was returned in TimerPeriod.\r
293 @retval EFI_INVALID_PARAMETER TimerPeriod is NULL.\r
294\r
295**/\r
e3fa3d83 296STATIC\r
1e57a462 297EFI_STATUS\r
298EFIAPI\r
299SP805GetTimerPeriod (\r
e3fa3d83 300 IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,\r
1e57a462 301 OUT UINT64 *TimerPeriod\r
302 )\r
303{\r
1e57a462 304 if (TimerPeriod == NULL) {\r
305 return EFI_INVALID_PARAMETER;\r
306 }\r
307\r
5afabd5e 308 *TimerPeriod = mTimerPeriod;\r
e3fa3d83 309 return EFI_SUCCESS;\r
1e57a462 310}\r
311\r
312/**\r
313 Interface structure for the Watchdog Architectural Protocol.\r
314\r
315 @par Protocol Description:\r
316 This protocol provides a service to set the amount of time to wait\r
317 before firing the watchdog timer, and it also provides a service to\r
318 register a handler that is invoked when the watchdog timer fires.\r
319\r
320 @par When the watchdog timer fires, control will be passed to a handler\r
321 if one has been registered. If no handler has been registered,\r
322 or the registered handler returns, then the system will be\r
323 reset by calling the Runtime Service ResetSystem().\r
324\r
325 @param RegisterHandler\r
326 Registers a handler that will be called each time the\r
327 watchdogtimer interrupt fires. TimerPeriod defines the minimum\r
328 time between timer interrupts, so TimerPeriod will also\r
329 be the minimum time between calls to the registered\r
330 handler.\r
331 NOTE: If the watchdog resets the system in hardware, then\r
332 this function will not have any chance of executing.\r
333\r
334 @param SetTimerPeriod\r
335 Sets the period of the timer interrupt in 100 nS units.\r
336 This function is optional, and may return EFI_UNSUPPORTED.\r
337 If this function is supported, then the timer period will\r
338 be rounded up to the nearest supported timer period.\r
339\r
340 @param GetTimerPeriod\r
341 Retrieves the period of the timer interrupt in 100 nS units.\r
342\r
343**/\r
e3fa3d83
AB
344STATIC EFI_WATCHDOG_TIMER_ARCH_PROTOCOL mWatchdogTimer = {\r
345 SP805RegisterHandler,\r
346 SP805SetTimerPeriod,\r
347 SP805GetTimerPeriod\r
1e57a462 348};\r
349\r
350/**\r
351 Initialize the state information for the Watchdog Timer Architectural Protocol.\r
352\r
353 @param ImageHandle of the loaded driver\r
354 @param SystemTable Pointer to the System Table\r
355\r
356 @retval EFI_SUCCESS Protocol registered\r
357 @retval EFI_OUT_OF_RESOURCES Cannot allocate protocol data structure\r
358 @retval EFI_DEVICE_ERROR Hardware problems\r
359\r
360**/\r
361EFI_STATUS\r
362EFIAPI\r
363SP805Initialize (\r
364 IN EFI_HANDLE ImageHandle,\r
365 IN EFI_SYSTEM_TABLE *SystemTable\r
366 )\r
367{\r
368 EFI_STATUS Status;\r
369 EFI_HANDLE Handle;\r
370\r
5afabd5e
AB
371 // Find the interrupt controller protocol. ASSERT if not found.\r
372 Status = gBS->LocateProtocol (&gHardwareInterruptProtocolGuid, NULL,\r
373 (VOID **)&mInterrupt);\r
374 ASSERT_EFI_ERROR (Status);\r
375\r
1e57a462 376 // Unlock access to the SP805 registers\r
377 SP805Unlock ();\r
378\r
379 // Stop the watchdog from triggering unexpectedly\r
380 SP805Stop ();\r
381\r
382 // Set the watchdog to reset the board when triggered\r
5afabd5e 383 // This is a last resort in case the interrupt handler fails\r
e3fa3d83 384 if ((MmioRead32 (SP805_WDOG_CONTROL_REG) & SP805_WDOG_CTRL_RESEN) == 0) {\r
1e57a462 385 MmioOr32 (SP805_WDOG_CONTROL_REG, SP805_WDOG_CTRL_RESEN);\r
386 }\r
387\r
5afabd5e
AB
388 // Clear any pending interrupts\r
389 MmioWrite32 (SP805_WDOG_INT_CLR_REG, 0); // write of any value clears the irq\r
390\r
1e57a462 391 // Prohibit any rogue access to SP805 registers\r
e3fa3d83 392 SP805Lock ();\r
3402aac7 393\r
5afabd5e
AB
394 if (PcdGet32 (PcdSP805WatchdogInterrupt) > 0) {\r
395 Status = mInterrupt->RegisterInterruptSource (mInterrupt,\r
396 PcdGet32 (PcdSP805WatchdogInterrupt),\r
397 SP805InterruptHandler);\r
398 if (EFI_ERROR (Status)) {\r
399 DEBUG ((DEBUG_ERROR, "%a: failed to register watchdog interrupt - %r\n",\r
400 __FUNCTION__, Status));\r
401 return Status;\r
402 }\r
403 } else {\r
404 DEBUG ((DEBUG_WARN, "%a: no interrupt specified, running in RESET mode only\n",\r
405 __FUNCTION__));\r
406 }\r
407\r
1e57a462 408 //\r
409 // Make sure the Watchdog Timer Architectural Protocol has not been installed in the system yet.\r
410 // This will avoid conflicts with the universal watchdog\r
411 //\r
412 ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiWatchdogTimerArchProtocolGuid);\r
413\r
414 // Register for an ExitBootServicesEvent\r
e3fa3d83
AB
415 Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY,\r
416 ExitBootServicesEvent, NULL, &mEfiExitBootServicesEvent);\r
417 if (EFI_ERROR (Status)) {\r
1e57a462 418 Status = EFI_OUT_OF_RESOURCES;\r
419 goto EXIT;\r
420 }\r
421\r
422 // Install the Timer Architectural Protocol onto a new handle\r
423 Handle = NULL;\r
e3fa3d83 424 Status = gBS->InstallMultipleProtocolInterfaces (\r
1e57a462 425 &Handle,\r
e3fa3d83 426 &gEfiWatchdogTimerArchProtocolGuid, &mWatchdogTimer,\r
1e57a462 427 NULL\r
428 );\r
e3fa3d83 429 if (EFI_ERROR (Status)) {\r
1e57a462 430 Status = EFI_OUT_OF_RESOURCES;\r
431 goto EXIT;\r
432 }\r
433\r
434EXIT:\r
e3fa3d83 435 ASSERT_EFI_ERROR (Status);\r
1e57a462 436 return Status;\r
437}\r