ArmPlatformPkg/PL031RealTimeClockLib: Implement PL031 RTC drive

git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@11793 6f19259b-4bc3-4df7-8a09-765794883524
This commit is contained in:
oliviermartin
2011-06-11 11:20:27 +00:00
parent 99127e9699
commit 0f4386e775
5 changed files with 511 additions and 16 deletions

View File

@@ -1,10 +1,10 @@
/** @file
Implement EFI RealTimeClock runtime services via RTC Lib.
Currently this driver does not support runtime virtual calling.
Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
@@ -15,12 +15,218 @@
**/
#include <Base.h>
#include <Uefi.h>
#include <PiDxe.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiLib.h>
#include <Library/IoLib.h>
#include <Library/RealTimeClockLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/ArmPlatformSysConfigLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Protocol/RealTimeClock.h>
#include <Guid/GlobalVariable.h>
#include <ArmPlatform.h>
#include <Drivers/PL031RealTimeClock.h>
CHAR16 mTimeZoneVariableName[] = L"PL031_TimeZone";
CHAR16 mDaylightVariableName[] = L"PL031_Daylight";
BOOLEAN mPL031Initialized = FALSE;
EFI_STATUS
IdentifyPL031 (
VOID
)
{
EFI_STATUS Status;
// Check if this is a PrimeCell Peripheral
if( ( MmioRead8( PL031_RTC_PCELL_ID0 ) != 0x0D )
|| ( MmioRead8( PL031_RTC_PCELL_ID1 ) != 0xF0 )
|| ( MmioRead8( PL031_RTC_PCELL_ID2 ) != 0x05 )
|| ( MmioRead8( PL031_RTC_PCELL_ID3 ) != 0xB1 ) ) {
Status = EFI_NOT_FOUND;
goto EXIT;
}
// Check if this PrimeCell Peripheral is the SP805 Watchdog Timer
if( ( MmioRead8( PL031_RTC_PERIPH_ID0 ) != 0x31 )
|| ( MmioRead8( PL031_RTC_PERIPH_ID1 ) != 0x10 )
|| (( MmioRead8( PL031_RTC_PERIPH_ID2 ) & 0xF) != 0x04 )
|| ( MmioRead8( PL031_RTC_PERIPH_ID3 ) != 0x00 ) ) {
Status = EFI_NOT_FOUND;
goto EXIT;
}
Status = EFI_SUCCESS;
EXIT:
return Status;
}
EFI_STATUS
InitializePL031 (
VOID
)
{
EFI_STATUS Status;
// Prepare the hardware
Status = IdentifyPL031();
if (EFI_ERROR (Status)) {
goto EXIT;
}
// Ensure interrupts are masked. We do not want RTC interrupts in UEFI
if ( (MmioRead32( PL031_RTC_IMSC_IRQ_MASK_SET_CLEAR_REGISTER ) & PL031_SET_IRQ_MASK) != PL031_SET_IRQ_MASK ) {
MmioOr32( PL031_RTC_IMSC_IRQ_MASK_SET_CLEAR_REGISTER, PL031_SET_IRQ_MASK);
}
// Clear any existing interrupts
if ( (MmioRead32( PL031_RTC_RIS_RAW_IRQ_STATUS_REGISTER ) & PL031_IRQ_TRIGGERED) == PL031_IRQ_TRIGGERED ) {
MmioOr32( PL031_RTC_ICR_IRQ_CLEAR_REGISTER, PL031_CLEAR_IRQ);
}
// Start the clock counter
if ( (MmioRead32( PL031_RTC_CR_CONTROL_REGISTER ) & PL031_RTC_ENABLED) != PL031_RTC_ENABLED ) {
MmioOr32( PL031_RTC_CR_CONTROL_REGISTER, PL031_RTC_ENABLED);
}
mPL031Initialized = TRUE;
EXIT:
return Status;
}
/**
Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME
**/
VOID
EpochToEfiTime (
IN UINTN EpochSeconds,
OUT EFI_TIME *Time
)
{
UINTN a;
UINTN b;
UINTN c;
UINTN d;
UINTN g;
UINTN j;
UINTN m;
UINTN y;
UINTN da;
UINTN db;
UINTN dc;
UINTN dg;
UINTN hh;
UINTN mm;
UINTN ss;
UINTN J;
if( Time->Daylight == TRUE) {
}
J = (EpochSeconds / 86400) + 2440588;
j = J + 32044;
g = j / 146097;
dg = j % 146097;
c = (((dg / 36524) + 1) * 3) / 4;
dc = dg - (c * 36524);
b = dc / 1461;
db = dc % 1461;
a = (((db / 365) + 1) * 3) / 4;
da = db - (a * 365);
y = (g * 400) + (c * 100) + (b * 4) + a;
m = (((da * 5) + 308) / 153) - 2;
d = da - (((m + 4) * 153) / 5) + 122;
Time->Year = y - 4800 + ((m + 2) / 12);
Time->Month = ((m + 2) % 12) + 1;
Time->Day = d + 1;
ss = EpochSeconds % 60;
a = (EpochSeconds - ss) / 60;
mm = a % 60;
b = (a - mm) / 60;
hh = b % 24;
Time->Hour = hh;
Time->Minute = mm;
Time->Second = ss;
Time->Nanosecond = 0;
}
/**
Converts EFI_TIME to Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC)
**/
UINTN
EfiTimeToEpoch (
IN EFI_TIME *Time
)
{
UINTN a;
UINTN y;
UINTN m;
UINTN JulianDate; // Absolute Julian Date representation of the supplied Time
UINTN EpochDays; // Number of days elapsed since EPOCH_JULIAN_DAY
UINTN EpochSeconds;
a = (14 - Time->Month) / 12 ;
y = Time->Year + 4800 - a;
m = Time->Month + (12*a) - 3;
JulianDate = Time->Day + ((153*m + 2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045;
ASSERT( JulianDate > EPOCH_JULIAN_DATE );
EpochDays = JulianDate - EPOCH_JULIAN_DATE;
EpochSeconds = (EpochDays * SEC_PER_DAY) + ((UINTN)Time->Hour * SEC_PER_HOUR) + (Time->Minute * SEC_PER_MIN) + Time->Second;
return EpochSeconds;
}
BOOLEAN
IsLeapYear (
IN EFI_TIME *Time
)
{
if (Time->Year % 4 == 0) {
if (Time->Year % 100 == 0) {
if (Time->Year % 400 == 0) {
return TRUE;
} else {
return FALSE;
}
} else {
return TRUE;
}
} else {
return FALSE;
}
}
BOOLEAN
DayValid (
IN EFI_TIME *Time
)
{
INTN DayOfMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (Time->Day < 1 ||
Time->Day > DayOfMonth[Time->Month - 1] ||
(Time->Month == 2 && (!IsLeapYear (Time) && Time->Day > 28))
) {
return FALSE;
}
return TRUE;
}
/**
Returns the current time and date information, and the time-keeping capabilities
@@ -39,13 +245,122 @@ EFI_STATUS
EFIAPI
LibGetTime (
OUT EFI_TIME *Time,
OUT EFI_TIME_CAPABILITIES *Capabilities
OUT EFI_TIME_CAPABILITIES *Capabilities
)
{
//
// Fill in Time and Capabilities via data from you RTC
//
return EFI_DEVICE_ERROR;
EFI_STATUS Status = EFI_SUCCESS;
UINTN EpochSeconds;
INT16 *TimeZone = 0;
UINTN *Daylight = 0;
// Initialize the hardware if not already done
if( !mPL031Initialized ) {
Status = InitializePL031();
if (EFI_ERROR (Status)) {
goto EXIT;
}
}
// Snapshot the time as early in the function call as possible
// On some platforms we may have access to a battery backed up hardware clock.
// If such RTC exists try to use it first.
Status = ArmPlatformSysConfigGet (SYS_CFG_RTC, &EpochSeconds);
if (Status == EFI_UNSUPPORTED) {
// Battery backed up hardware RTC does not exist, revert to PL031
EpochSeconds = MmioRead32( PL031_RTC_DR_DATA_REGISTER );
Status = EFI_SUCCESS;
} else if (EFI_ERROR (Status)) {
// Battery backed up hardware RTC exists but could not be read due to error. Abort.
goto EXIT;
} else {
// Battery backed up hardware RTC exists and we read the time correctly from it.
// Now sync the PL031 to the new time.
MmioWrite32( PL031_RTC_LR_LOAD_REGISTER, EpochSeconds);
}
// Ensure Time is a valid pointer
if( Time == NULL ) {
Status = EFI_INVALID_PARAMETER;
goto EXIT;
}
// Get the current time zone information from non-volatile storage
TimeZone = (INT16 *)GetVariable(mTimeZoneVariableName, &gEfiGlobalVariableGuid);
if( TimeZone == NULL ) {
// The time zone variable does not exist in non-volatile storage, so create it.
Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
// Store it
Status = gRT->SetVariable (
mTimeZoneVariableName,
&gEfiGlobalVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(Time->TimeZone),
&(Time->TimeZone)
);
if (EFI_ERROR (Status)) {
DEBUG((EFI_D_ERROR,"LibGetTime: ERROR: TimeZone\n"));
goto EXIT;
}
} else {
// Got the time zone
Time->TimeZone = *TimeZone;
FreePool(TimeZone);
// Check TimeZone bounds: -1440 to 1440 or 2047
if( (( Time->TimeZone < -1440 ) || ( Time->TimeZone > 1440 ))
&& ( Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) ) {
Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
}
// Adjust for the correct time zone
if( Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE ) {
EpochSeconds += Time->TimeZone * SEC_PER_MIN;
}
}
// Get the current daylight information from non-volatile storage
Daylight = (UINTN *)GetVariable(mDaylightVariableName, &gEfiGlobalVariableGuid);
if( Daylight == NULL ) {
// The daylight variable does not exist in non-volatile storage, so create it.
Time->Daylight = 0;
// Store it
Status = gRT->SetVariable (
mDaylightVariableName,
&gEfiGlobalVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(Time->Daylight),
&(Time->Daylight)
);
if (EFI_ERROR (Status)) {
DEBUG((EFI_D_ERROR,"LibGetTime: ERROR: Daylight\n"));
goto EXIT;
}
} else {
// Got the daylight information
Time->Daylight = *Daylight;
FreePool(Daylight);
// Adjust for the correct period
if( (Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT ) {
// Convert to adjusted time, i.e. spring forwards one hour
EpochSeconds += SEC_PER_HOUR;
}
}
// Convert from internal 32-bit time to UEFI time
EpochToEfiTime( EpochSeconds, Time );
// Update the Capabilities info
if( Capabilities != NULL ) {
Capabilities->Resolution = PL031_COUNTS_PER_SECOND; /* PL031 runs at frequency 1Hz */
Capabilities->Accuracy = PL031_PPM_ACCURACY; /* Accuracy in ppm multiplied by 1,000,000, e.g. for 50ppm set 50,000,000 */
Capabilities->SetsToZero = FALSE; /* FALSE: Setting the time does not clear the values below the resolution level */
}
EXIT:
return Status;
}
@@ -62,13 +377,107 @@ LibGetTime (
EFI_STATUS
EFIAPI
LibSetTime (
IN EFI_TIME *Time
IN EFI_TIME *Time
)
{
EFI_STATUS Status;
UINTN EpochSeconds;
// Because the PL031 is a 32-bit counter counting seconds,
// the maximum time span is just over 136 years.
// Time is stored in Unix Epoch format, so it starts in 1970,
// Therefore it can not exceed the year 2106.
// This is not a problem for UEFI, as the current spec limits the years
// to the range 1998 .. 2011
// Check the input parameters' range.
if ( ( Time->Year < 1998 ) ||
( Time->Year > 2099 ) ||
( Time->Month < 1 ) ||
( Time->Month > 12 ) ||
(!DayValid (Time) ) ||
( Time->Hour > 23 ) ||
( Time->Minute > 59 ) ||
( Time->Second > 59 ) ||
( Time->Nanosecond > 999999999 ) ||
( !((Time->TimeZone == EFI_UNSPECIFIED_TIMEZONE) || ((Time->TimeZone >= -1440) && (Time->TimeZone <= 1440))) ) ||
( Time->Daylight & (~(EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT)) )
) {
Status = EFI_INVALID_PARAMETER;
goto EXIT;
}
// Initialize the hardware if not already done
if( !mPL031Initialized ) {
Status = InitializePL031();
if (EFI_ERROR (Status)) {
goto EXIT;
}
}
EpochSeconds = EfiTimeToEpoch( Time );
// Adjust for the correct time zone, i.e. convert to UTC time zone
if( Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE ) {
EpochSeconds -= Time->TimeZone * SEC_PER_MIN;
}
// TODO: Automatic Daylight activation
// Adjust for the correct period
if( (Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT ) {
// Convert to un-adjusted time, i.e. fall back one hour
EpochSeconds -= SEC_PER_HOUR;
}
// On some platforms we may have access to a battery backed up hardware clock.
//
// Use Time, to set the time in your RTC hardware
// If such RTC exists then it must be updated first, before the PL031,
// to minimise any time drift. This is important because the battery backed-up
// RTC maintains the master time for the platform across reboots.
//
return EFI_DEVICE_ERROR;
// If such RTC does not exist then the following function returns UNSUPPORTED.
Status = ArmPlatformSysConfigSet (SYS_CFG_RTC, EpochSeconds);
if ((EFI_ERROR (Status)) && (Status != EFI_UNSUPPORTED)){
// Any status message except SUCCESS and UNSUPPORTED indicates a hardware failure.
goto EXIT;
}
// Set the PL031
MmioWrite32( PL031_RTC_LR_LOAD_REGISTER, EpochSeconds);
// The accesses to Variable Services can be very slow, because we may be writing to Flash.
// Do this after having set the RTC.
// Save the current time zone information into non-volatile storage
Status = gRT->SetVariable (
mTimeZoneVariableName,
&gEfiGlobalVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(Time->TimeZone),
&(Time->TimeZone)
);
if (EFI_ERROR (Status)) {
DEBUG((EFI_D_ERROR,"LibSetTime: ERROR: TimeZone\n"));
goto EXIT;
}
// Save the current daylight information into non-volatile storage
Status = gRT->SetVariable (
mDaylightVariableName,
&gEfiGlobalVariableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(Time->Daylight),
&(Time->Daylight)
);
if (EFI_ERROR (Status)) {
DEBUG((EFI_D_ERROR,"LibSetTime: ERROR: Daylight\n"));
goto EXIT;
}
EXIT:
return Status;
}
@@ -140,10 +549,24 @@ LibRtcInitialize (
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Do some initialization if reqruied to turn on the RTC
//
return EFI_SUCCESS;
EFI_STATUS Status;
EFI_HANDLE Handle;
// Setup the setters and getters
gRT->GetTime = LibGetTime;
gRT->SetTime = LibSetTime;
gRT->GetWakeupTime = LibGetWakeupTime;
gRT->SetWakeupTime = LibSetWakeupTime;
// Install the protocol
Handle = NULL;
Status = gBS->InstallMultipleProtocolInterfaces (
&Handle,
&gEfiRealTimeClockArchProtocolGuid, NULL,
NULL
);
return Status;
}
@@ -164,8 +587,8 @@ LibRtcVirtualNotifyEvent (
{
//
// Only needed if you are going to support the OS calling RTC functions in virtual mode.
// You will need to call EfiConvertPointer (). To convert any stored physical addresses
// to virtual address. After the OS transistions to calling in virtual mode, all future
// You will need to call EfiConvertPointer (). To convert any stored physical addresses
// to virtual address. After the OS transitions to calling in virtual mode, all future
// runtime calls will be made in virtual mode.
//
return;