]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPlatformPkg/Library/PL031RealTimeClockLib/PL031RealTimeClockLib.c
ArmPlatformPkg/PL031RealTimeClockLib: Implement PL031 RTC drive
[mirror_edk2.git] / ArmPlatformPkg / Library / PL031RealTimeClockLib / PL031RealTimeClockLib.c
1 /** @file
2 Implement EFI RealTimeClock runtime services via RTC Lib.
3
4 Currently this driver does not support runtime virtual calling.
5
6 Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR>
7
8 This program and the accompanying materials
9 are licensed and made available under the terms and conditions of the BSD License
10 which accompanies this distribution. The full text of the license may be found at
11 http://opensource.org/licenses/bsd-license.php
12
13 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
14 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
15
16 **/
17
18 #include <Base.h>
19 #include <Uefi.h>
20 #include <PiDxe.h>
21 #include <Library/BaseLib.h>
22 #include <Library/DebugLib.h>
23 #include <Library/UefiLib.h>
24 #include <Library/IoLib.h>
25 #include <Library/RealTimeClockLib.h>
26 #include <Library/MemoryAllocationLib.h>
27 #include <Library/ArmPlatformSysConfigLib.h>
28 #include <Library/UefiBootServicesTableLib.h>
29 #include <Library/UefiRuntimeServicesTableLib.h>
30 #include <Protocol/RealTimeClock.h>
31 #include <Guid/GlobalVariable.h>
32 #include <ArmPlatform.h>
33 #include <Drivers/PL031RealTimeClock.h>
34
35 CHAR16 mTimeZoneVariableName[] = L"PL031_TimeZone";
36 CHAR16 mDaylightVariableName[] = L"PL031_Daylight";
37 BOOLEAN mPL031Initialized = FALSE;
38
39 EFI_STATUS
40 IdentifyPL031 (
41 VOID
42 )
43 {
44 EFI_STATUS Status;
45
46 // Check if this is a PrimeCell Peripheral
47 if( ( MmioRead8( PL031_RTC_PCELL_ID0 ) != 0x0D )
48 || ( MmioRead8( PL031_RTC_PCELL_ID1 ) != 0xF0 )
49 || ( MmioRead8( PL031_RTC_PCELL_ID2 ) != 0x05 )
50 || ( MmioRead8( PL031_RTC_PCELL_ID3 ) != 0xB1 ) ) {
51 Status = EFI_NOT_FOUND;
52 goto EXIT;
53 }
54
55 // Check if this PrimeCell Peripheral is the SP805 Watchdog Timer
56 if( ( MmioRead8( PL031_RTC_PERIPH_ID0 ) != 0x31 )
57 || ( MmioRead8( PL031_RTC_PERIPH_ID1 ) != 0x10 )
58 || (( MmioRead8( PL031_RTC_PERIPH_ID2 ) & 0xF) != 0x04 )
59 || ( MmioRead8( PL031_RTC_PERIPH_ID3 ) != 0x00 ) ) {
60 Status = EFI_NOT_FOUND;
61 goto EXIT;
62 }
63
64 Status = EFI_SUCCESS;
65
66 EXIT:
67 return Status;
68 }
69
70 EFI_STATUS
71 InitializePL031 (
72 VOID
73 )
74 {
75 EFI_STATUS Status;
76
77 // Prepare the hardware
78 Status = IdentifyPL031();
79 if (EFI_ERROR (Status)) {
80 goto EXIT;
81 }
82
83 // Ensure interrupts are masked. We do not want RTC interrupts in UEFI
84 if ( (MmioRead32( PL031_RTC_IMSC_IRQ_MASK_SET_CLEAR_REGISTER ) & PL031_SET_IRQ_MASK) != PL031_SET_IRQ_MASK ) {
85 MmioOr32( PL031_RTC_IMSC_IRQ_MASK_SET_CLEAR_REGISTER, PL031_SET_IRQ_MASK);
86 }
87
88 // Clear any existing interrupts
89 if ( (MmioRead32( PL031_RTC_RIS_RAW_IRQ_STATUS_REGISTER ) & PL031_IRQ_TRIGGERED) == PL031_IRQ_TRIGGERED ) {
90 MmioOr32( PL031_RTC_ICR_IRQ_CLEAR_REGISTER, PL031_CLEAR_IRQ);
91 }
92
93 // Start the clock counter
94 if ( (MmioRead32( PL031_RTC_CR_CONTROL_REGISTER ) & PL031_RTC_ENABLED) != PL031_RTC_ENABLED ) {
95 MmioOr32( PL031_RTC_CR_CONTROL_REGISTER, PL031_RTC_ENABLED);
96 }
97
98 mPL031Initialized = TRUE;
99
100 EXIT:
101 return Status;
102 }
103
104 /**
105 Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME
106 **/
107 VOID
108 EpochToEfiTime (
109 IN UINTN EpochSeconds,
110 OUT EFI_TIME *Time
111 )
112 {
113 UINTN a;
114 UINTN b;
115 UINTN c;
116 UINTN d;
117 UINTN g;
118 UINTN j;
119 UINTN m;
120 UINTN y;
121 UINTN da;
122 UINTN db;
123 UINTN dc;
124 UINTN dg;
125 UINTN hh;
126 UINTN mm;
127 UINTN ss;
128 UINTN J;
129
130 if( Time->Daylight == TRUE) {
131
132 }
133
134 J = (EpochSeconds / 86400) + 2440588;
135 j = J + 32044;
136 g = j / 146097;
137 dg = j % 146097;
138 c = (((dg / 36524) + 1) * 3) / 4;
139 dc = dg - (c * 36524);
140 b = dc / 1461;
141 db = dc % 1461;
142 a = (((db / 365) + 1) * 3) / 4;
143 da = db - (a * 365);
144 y = (g * 400) + (c * 100) + (b * 4) + a;
145 m = (((da * 5) + 308) / 153) - 2;
146 d = da - (((m + 4) * 153) / 5) + 122;
147
148 Time->Year = y - 4800 + ((m + 2) / 12);
149 Time->Month = ((m + 2) % 12) + 1;
150 Time->Day = d + 1;
151
152 ss = EpochSeconds % 60;
153 a = (EpochSeconds - ss) / 60;
154 mm = a % 60;
155 b = (a - mm) / 60;
156 hh = b % 24;
157
158 Time->Hour = hh;
159 Time->Minute = mm;
160 Time->Second = ss;
161 Time->Nanosecond = 0;
162
163 }
164
165 /**
166 Converts EFI_TIME to Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC)
167 **/
168 UINTN
169 EfiTimeToEpoch (
170 IN EFI_TIME *Time
171 )
172 {
173 UINTN a;
174 UINTN y;
175 UINTN m;
176 UINTN JulianDate; // Absolute Julian Date representation of the supplied Time
177 UINTN EpochDays; // Number of days elapsed since EPOCH_JULIAN_DAY
178 UINTN EpochSeconds;
179
180 a = (14 - Time->Month) / 12 ;
181 y = Time->Year + 4800 - a;
182 m = Time->Month + (12*a) - 3;
183
184 JulianDate = Time->Day + ((153*m + 2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045;
185
186 ASSERT( JulianDate > EPOCH_JULIAN_DATE );
187 EpochDays = JulianDate - EPOCH_JULIAN_DATE;
188
189 EpochSeconds = (EpochDays * SEC_PER_DAY) + ((UINTN)Time->Hour * SEC_PER_HOUR) + (Time->Minute * SEC_PER_MIN) + Time->Second;
190
191 return EpochSeconds;
192 }
193
194 BOOLEAN
195 IsLeapYear (
196 IN EFI_TIME *Time
197 )
198 {
199 if (Time->Year % 4 == 0) {
200 if (Time->Year % 100 == 0) {
201 if (Time->Year % 400 == 0) {
202 return TRUE;
203 } else {
204 return FALSE;
205 }
206 } else {
207 return TRUE;
208 }
209 } else {
210 return FALSE;
211 }
212 }
213
214 BOOLEAN
215 DayValid (
216 IN EFI_TIME *Time
217 )
218 {
219 INTN DayOfMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
220
221 if (Time->Day < 1 ||
222 Time->Day > DayOfMonth[Time->Month - 1] ||
223 (Time->Month == 2 && (!IsLeapYear (Time) && Time->Day > 28))
224 ) {
225 return FALSE;
226 }
227
228 return TRUE;
229 }
230
231 /**
232 Returns the current time and date information, and the time-keeping capabilities
233 of the hardware platform.
234
235 @param Time A pointer to storage to receive a snapshot of the current time.
236 @param Capabilities An optional pointer to a buffer to receive the real time clock
237 device's capabilities.
238
239 @retval EFI_SUCCESS The operation completed successfully.
240 @retval EFI_INVALID_PARAMETER Time is NULL.
241 @retval EFI_DEVICE_ERROR The time could not be retrieved due to hardware error.
242
243 **/
244 EFI_STATUS
245 EFIAPI
246 LibGetTime (
247 OUT EFI_TIME *Time,
248 OUT EFI_TIME_CAPABILITIES *Capabilities
249 )
250 {
251 EFI_STATUS Status = EFI_SUCCESS;
252 UINTN EpochSeconds;
253 INT16 *TimeZone = 0;
254 UINTN *Daylight = 0;
255
256 // Initialize the hardware if not already done
257 if( !mPL031Initialized ) {
258 Status = InitializePL031();
259 if (EFI_ERROR (Status)) {
260 goto EXIT;
261 }
262 }
263
264 // Snapshot the time as early in the function call as possible
265 // On some platforms we may have access to a battery backed up hardware clock.
266 // If such RTC exists try to use it first.
267 Status = ArmPlatformSysConfigGet (SYS_CFG_RTC, &EpochSeconds);
268 if (Status == EFI_UNSUPPORTED) {
269 // Battery backed up hardware RTC does not exist, revert to PL031
270 EpochSeconds = MmioRead32( PL031_RTC_DR_DATA_REGISTER );
271 Status = EFI_SUCCESS;
272 } else if (EFI_ERROR (Status)) {
273 // Battery backed up hardware RTC exists but could not be read due to error. Abort.
274 goto EXIT;
275 } else {
276 // Battery backed up hardware RTC exists and we read the time correctly from it.
277 // Now sync the PL031 to the new time.
278 MmioWrite32( PL031_RTC_LR_LOAD_REGISTER, EpochSeconds);
279 }
280
281 // Ensure Time is a valid pointer
282 if( Time == NULL ) {
283 Status = EFI_INVALID_PARAMETER;
284 goto EXIT;
285 }
286
287 // Get the current time zone information from non-volatile storage
288 TimeZone = (INT16 *)GetVariable(mTimeZoneVariableName, &gEfiGlobalVariableGuid);
289
290 if( TimeZone == NULL ) {
291 // The time zone variable does not exist in non-volatile storage, so create it.
292 Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
293 // Store it
294 Status = gRT->SetVariable (
295 mTimeZoneVariableName,
296 &gEfiGlobalVariableGuid,
297 EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
298 sizeof(Time->TimeZone),
299 &(Time->TimeZone)
300 );
301 if (EFI_ERROR (Status)) {
302 DEBUG((EFI_D_ERROR,"LibGetTime: ERROR: TimeZone\n"));
303 goto EXIT;
304 }
305 } else {
306 // Got the time zone
307 Time->TimeZone = *TimeZone;
308 FreePool(TimeZone);
309
310 // Check TimeZone bounds: -1440 to 1440 or 2047
311 if( (( Time->TimeZone < -1440 ) || ( Time->TimeZone > 1440 ))
312 && ( Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) ) {
313 Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
314 }
315
316 // Adjust for the correct time zone
317 if( Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE ) {
318 EpochSeconds += Time->TimeZone * SEC_PER_MIN;
319 }
320 }
321
322 // Get the current daylight information from non-volatile storage
323 Daylight = (UINTN *)GetVariable(mDaylightVariableName, &gEfiGlobalVariableGuid);
324
325 if( Daylight == NULL ) {
326 // The daylight variable does not exist in non-volatile storage, so create it.
327 Time->Daylight = 0;
328 // Store it
329 Status = gRT->SetVariable (
330 mDaylightVariableName,
331 &gEfiGlobalVariableGuid,
332 EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
333 sizeof(Time->Daylight),
334 &(Time->Daylight)
335 );
336 if (EFI_ERROR (Status)) {
337 DEBUG((EFI_D_ERROR,"LibGetTime: ERROR: Daylight\n"));
338 goto EXIT;
339 }
340 } else {
341 // Got the daylight information
342 Time->Daylight = *Daylight;
343 FreePool(Daylight);
344
345 // Adjust for the correct period
346 if( (Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT ) {
347 // Convert to adjusted time, i.e. spring forwards one hour
348 EpochSeconds += SEC_PER_HOUR;
349 }
350 }
351
352 // Convert from internal 32-bit time to UEFI time
353 EpochToEfiTime( EpochSeconds, Time );
354
355 // Update the Capabilities info
356 if( Capabilities != NULL ) {
357 Capabilities->Resolution = PL031_COUNTS_PER_SECOND; /* PL031 runs at frequency 1Hz */
358 Capabilities->Accuracy = PL031_PPM_ACCURACY; /* Accuracy in ppm multiplied by 1,000,000, e.g. for 50ppm set 50,000,000 */
359 Capabilities->SetsToZero = FALSE; /* FALSE: Setting the time does not clear the values below the resolution level */
360 }
361
362 EXIT:
363 return Status;
364 }
365
366
367 /**
368 Sets the current local time and date information.
369
370 @param Time A pointer to the current time.
371
372 @retval EFI_SUCCESS The operation completed successfully.
373 @retval EFI_INVALID_PARAMETER A time field is out of range.
374 @retval EFI_DEVICE_ERROR The time could not be set due due to hardware error.
375
376 **/
377 EFI_STATUS
378 EFIAPI
379 LibSetTime (
380 IN EFI_TIME *Time
381 )
382 {
383 EFI_STATUS Status;
384 UINTN EpochSeconds;
385
386 // Because the PL031 is a 32-bit counter counting seconds,
387 // the maximum time span is just over 136 years.
388 // Time is stored in Unix Epoch format, so it starts in 1970,
389 // Therefore it can not exceed the year 2106.
390 // This is not a problem for UEFI, as the current spec limits the years
391 // to the range 1998 .. 2011
392
393 // Check the input parameters' range.
394 if ( ( Time->Year < 1998 ) ||
395 ( Time->Year > 2099 ) ||
396 ( Time->Month < 1 ) ||
397 ( Time->Month > 12 ) ||
398 (!DayValid (Time) ) ||
399 ( Time->Hour > 23 ) ||
400 ( Time->Minute > 59 ) ||
401 ( Time->Second > 59 ) ||
402 ( Time->Nanosecond > 999999999 ) ||
403 ( !((Time->TimeZone == EFI_UNSPECIFIED_TIMEZONE) || ((Time->TimeZone >= -1440) && (Time->TimeZone <= 1440))) ) ||
404 ( Time->Daylight & (~(EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT)) )
405 ) {
406 Status = EFI_INVALID_PARAMETER;
407 goto EXIT;
408 }
409
410 // Initialize the hardware if not already done
411 if( !mPL031Initialized ) {
412 Status = InitializePL031();
413 if (EFI_ERROR (Status)) {
414 goto EXIT;
415 }
416 }
417
418 EpochSeconds = EfiTimeToEpoch( Time );
419
420 // Adjust for the correct time zone, i.e. convert to UTC time zone
421 if( Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE ) {
422 EpochSeconds -= Time->TimeZone * SEC_PER_MIN;
423 }
424
425 // TODO: Automatic Daylight activation
426
427 // Adjust for the correct period
428 if( (Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT ) {
429 // Convert to un-adjusted time, i.e. fall back one hour
430 EpochSeconds -= SEC_PER_HOUR;
431 }
432
433 // On some platforms we may have access to a battery backed up hardware clock.
434 //
435 // If such RTC exists then it must be updated first, before the PL031,
436 // to minimise any time drift. This is important because the battery backed-up
437 // RTC maintains the master time for the platform across reboots.
438 //
439 // If such RTC does not exist then the following function returns UNSUPPORTED.
440 Status = ArmPlatformSysConfigSet (SYS_CFG_RTC, EpochSeconds);
441 if ((EFI_ERROR (Status)) && (Status != EFI_UNSUPPORTED)){
442 // Any status message except SUCCESS and UNSUPPORTED indicates a hardware failure.
443 goto EXIT;
444 }
445
446
447 // Set the PL031
448 MmioWrite32( PL031_RTC_LR_LOAD_REGISTER, EpochSeconds);
449
450 // The accesses to Variable Services can be very slow, because we may be writing to Flash.
451 // Do this after having set the RTC.
452
453 // Save the current time zone information into non-volatile storage
454 Status = gRT->SetVariable (
455 mTimeZoneVariableName,
456 &gEfiGlobalVariableGuid,
457 EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
458 sizeof(Time->TimeZone),
459 &(Time->TimeZone)
460 );
461 if (EFI_ERROR (Status)) {
462 DEBUG((EFI_D_ERROR,"LibSetTime: ERROR: TimeZone\n"));
463 goto EXIT;
464 }
465
466 // Save the current daylight information into non-volatile storage
467 Status = gRT->SetVariable (
468 mDaylightVariableName,
469 &gEfiGlobalVariableGuid,
470 EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
471 sizeof(Time->Daylight),
472 &(Time->Daylight)
473 );
474 if (EFI_ERROR (Status)) {
475 DEBUG((EFI_D_ERROR,"LibSetTime: ERROR: Daylight\n"));
476 goto EXIT;
477 }
478
479 EXIT:
480 return Status;
481 }
482
483
484 /**
485 Returns the current wakeup alarm clock setting.
486
487 @param Enabled Indicates if the alarm is currently enabled or disabled.
488 @param Pending Indicates if the alarm signal is pending and requires acknowledgement.
489 @param Time The current alarm setting.
490
491 @retval EFI_SUCCESS The alarm settings were returned.
492 @retval EFI_INVALID_PARAMETER Any parameter is NULL.
493 @retval EFI_DEVICE_ERROR The wakeup time could not be retrieved due to a hardware error.
494
495 **/
496 EFI_STATUS
497 EFIAPI
498 LibGetWakeupTime (
499 OUT BOOLEAN *Enabled,
500 OUT BOOLEAN *Pending,
501 OUT EFI_TIME *Time
502 )
503 {
504 // Not a required feature
505 return EFI_UNSUPPORTED;
506 }
507
508
509 /**
510 Sets the system wakeup alarm clock time.
511
512 @param Enabled Enable or disable the wakeup alarm.
513 @param Time If Enable is TRUE, the time to set the wakeup alarm for.
514
515 @retval EFI_SUCCESS If Enable is TRUE, then the wakeup alarm was enabled. If
516 Enable is FALSE, then the wakeup alarm was disabled.
517 @retval EFI_INVALID_PARAMETER A time field is out of range.
518 @retval EFI_DEVICE_ERROR The wakeup time could not be set due to a hardware error.
519 @retval EFI_UNSUPPORTED A wakeup timer is not supported on this platform.
520
521 **/
522 EFI_STATUS
523 EFIAPI
524 LibSetWakeupTime (
525 IN BOOLEAN Enabled,
526 OUT EFI_TIME *Time
527 )
528 {
529 // Not a required feature
530 return EFI_UNSUPPORTED;
531 }
532
533
534
535 /**
536 This is the declaration of an EFI image entry point. This can be the entry point to an application
537 written to this specification, an EFI boot service driver, or an EFI runtime driver.
538
539 @param ImageHandle Handle that identifies the loaded image.
540 @param SystemTable System Table for this image.
541
542 @retval EFI_SUCCESS The operation completed successfully.
543
544 **/
545 EFI_STATUS
546 EFIAPI
547 LibRtcInitialize (
548 IN EFI_HANDLE ImageHandle,
549 IN EFI_SYSTEM_TABLE *SystemTable
550 )
551 {
552 EFI_STATUS Status;
553 EFI_HANDLE Handle;
554
555 // Setup the setters and getters
556 gRT->GetTime = LibGetTime;
557 gRT->SetTime = LibSetTime;
558 gRT->GetWakeupTime = LibGetWakeupTime;
559 gRT->SetWakeupTime = LibSetWakeupTime;
560
561 // Install the protocol
562 Handle = NULL;
563 Status = gBS->InstallMultipleProtocolInterfaces (
564 &Handle,
565 &gEfiRealTimeClockArchProtocolGuid, NULL,
566 NULL
567 );
568
569 return Status;
570 }
571
572
573 /**
574 Fixup internal data so that EFI can be call in virtual mode.
575 Call the passed in Child Notify event and convert any pointers in
576 lib to virtual mode.
577
578 @param[in] Event The Event that is being processed
579 @param[in] Context Event Context
580 **/
581 VOID
582 EFIAPI
583 LibRtcVirtualNotifyEvent (
584 IN EFI_EVENT Event,
585 IN VOID *Context
586 )
587 {
588 //
589 // Only needed if you are going to support the OS calling RTC functions in virtual mode.
590 // You will need to call EfiConvertPointer (). To convert any stored physical addresses
591 // to virtual address. After the OS transitions to calling in virtual mode, all future
592 // runtime calls will be made in virtual mode.
593 //
594 return;
595 }