/** @file
Copyright (c) 2020, Hisilicon Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "RX8900RealTimeClock.h"
extern I2C_DEVICE gRtcDevice;
STATIC BOOLEAN mRX8900Initialized = FALSE;
STATIC CONST CHAR16 mTimeZoneVariableName[] = L"RX8900RtcTimeZone";
STATIC CONST CHAR16 mDaylightVariableName[] = L"RX8900RtcDaylight";
EFI_STATUS
InitializeRX8900 (
VOID
)
{
EFI_STATUS Status;
unsigned char writeTemp;
Status = SwitchRtcI2cChannelAndLock ();
if (EFI_ERROR (Status)) {
goto EXIT;
}
Status = I2CInit (gRtcDevice.Socket, gRtcDevice.Port, Normal);
if (EFI_ERROR (Status)) {
goto EXIT;
}
writeTemp = RX8900_VDETOFF_SWOFF;
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_BACKUP_FUN, 1, &writeTemp);
if (EFI_ERROR (Status)) {
goto EXIT;
}
writeTemp = TEMPERATURE_COMPENSATION_2S;
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_CONTRLREG, 1, &writeTemp);
if (EFI_ERROR (Status)) {
goto EXIT;
}
writeTemp = OUTPUT_FREQUENCY_32768;
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_EXTENSIONREG, 1, &writeTemp);
if (EFI_ERROR (Status)) {
goto EXIT;
}
writeTemp = FLAG_REG_DEFAULT;
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_FLAGREG, 1, &writeTemp);
if (EFI_ERROR (Status)) {
goto EXIT;
}
writeTemp = RX8900_RAM_REG_DEFAULT;
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_RAM, 1, &writeTemp);
if (EFI_ERROR (Status)) {
goto EXIT;
}
mRX8900Initialized = TRUE;
EXIT:
ReleaseOwnershipOfRtc ();
return Status;
}
STATIC
INT16
GetTimeZone (
VOID
)
{
EFI_STATUS Status;
INT16 TimeZone;
UINTN Size;
TimeZone = EFI_UNSPECIFIED_TIMEZONE;
Size = sizeof (TimeZone);
Status = EfiGetVariable (
(CHAR16 *)mTimeZoneVariableName,
&gEfiCallerIdGuid,
NULL,
&Size,
(VOID *)&TimeZone
);
if (EFI_ERROR (Status)) {
TimeZone = EFI_UNSPECIFIED_TIMEZONE;
// The time zone variable does not exist in non-volatile storage, so create it.
Status = EfiSetVariable (
(CHAR16 *)mTimeZoneVariableName,
&gEfiCallerIdGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
Size,
(VOID *)&TimeZone
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "Failed to save %s variable, Status = %r\n",
mTimeZoneVariableName, Status));
}
} else {
// Check TimeZone bounds: -1440 to 1440 or 2047
if ((TimeZone < -EFI_TIMEOFFSET_TIMEZONE) || (TimeZone > EFI_TIMEOFFSET_TIMEZONE)) {
TimeZone = EFI_UNSPECIFIED_TIMEZONE;
}
}
return TimeZone;
}
STATIC
UINT8
GetDayLight (
VOID
)
{
EFI_STATUS Status;
UINT8 DayLight;
UINTN Size;
DayLight = 0;
// Get the current daylight information from non-volatile storage
Size = sizeof (DayLight);
Status = EfiGetVariable (
(CHAR16 *)mDaylightVariableName,
&gEfiCallerIdGuid,
NULL,
&Size,
(VOID *)&DayLight
);
if (EFI_ERROR (Status)) {
DayLight = 0;
// The daylight variable does not exist in non-volatile storage, so create it.
Status = EfiSetVariable (
(CHAR16 *)mDaylightVariableName,
&gEfiCallerIdGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
Size,
(VOID *)&DayLight
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "Failed to save %s variable, Status = %r\n",
mDaylightVariableName, Status));
}
}
return DayLight;
}
EFI_STATUS
EFIAPI
LibGetTime (
OUT EFI_TIME *Time,
OUT EFI_TIME_CAPABILITIES *Capabilities
)
{
EFI_STATUS Status;
UINT8 Temp[7] = {0};
UINT16 BaseYear = 2000;
UINTN EpochSeconds;
UINT8 TryCount = 0;
// Ensure Time is a valid pointer
if (Time == NULL) {
return EFI_INVALID_PARAMETER;
}
// Initialize the hardware if not already done
if (!mRX8900Initialized) {
Status = InitializeRX8900 ();
if (EFI_ERROR (Status)) {
return EFI_NOT_READY;
}
}
Status = SwitchRtcI2cChannelAndLock ();
if (EFI_ERROR (Status)) {
ReleaseOwnershipOfRtc ();
return Status;
}
do {
Status = I2CRead (&gRtcDevice, RX8900_REGADDR_SECONDS, 7, Temp);
if (EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
goto Err;
}
Time->Second = BcdToDecimal8 (Temp[0]);
Time->Minute = BcdToDecimal8 (Temp[1]);
Time->Hour = BcdToDecimal8 (Temp[2]);
Time->Day = BcdToDecimal8 (Temp[4]);
Time->Month = BcdToDecimal8 (Temp[5]);
Time->Year = BaseYear + BcdToDecimal8 (Temp[6]);
Time->Nanosecond = 0;
EpochSeconds = EfiTimeToEpoch (Time);
Time->TimeZone = GetTimeZone ();
// Adjust for the correct time zone
if (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) {
EpochSeconds += Time->TimeZone * SEC_PER_MIN;
}
Time->Daylight = GetDayLight ();
// 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);
if ((!IsTimeValid (Time)) || ((Time->Year - BaseYear) > 99) || (Time->Year < 2000)) {
DEBUG ((DEBUG_INFO, "LibGetTime: %d-%d-%d %d-%d-%d EpochSeconds:%llx is invalid time!\n",
Time->Second, Time->Minute, Time->Hour, Time->Day, Time->Month,
Time->Year, EpochSeconds));
Status = EFI_DEVICE_ERROR;
}
Err:
TryCount++;
} while ((TryCount < 3) && (EFI_ERROR (Status)));
ReleaseOwnershipOfRtc ();
return Status;
}
STATIC
EFI_STATUS
SetTimeToRX8900 (
IN EFI_TIME *Time
)
{
EFI_STATUS Status;
UINT8 Temp;
(VOID)MicroSecondDelay (RTC_DELAY_1000_MICROSECOND);
Temp = DecimalToBcd8 (Time->Second);
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_SECONDS, 1, &Temp);
if(EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
return Status;
}
Temp = DecimalToBcd8 (Time->Minute);
(VOID)MicroSecondDelay (RTC_DELAY_1000_MICROSECOND);
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_MIUTES, 1, &Temp);
if(EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
return Status;
}
Temp = DecimalToBcd8 (Time->Hour);
(VOID)MicroSecondDelay (RTC_DELAY_1000_MICROSECOND);
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_HOURS, 1, &Temp);
if(EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
return Status;
}
Temp = DecimalToBcd8 (Time->Day);
(VOID)MicroSecondDelay (RTC_DELAY_1000_MICROSECOND);
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_DATE, 1, &Temp);
if(EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
return Status;
}
Temp = DecimalToBcd8 (Time->Month);
(VOID)MicroSecondDelay (RTC_DELAY_1000_MICROSECOND);
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_MONTH, 1, &Temp);
if(EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
return Status;
}
Time->Year= Time->Year % 100;
Temp = Time->Year;
Temp = DecimalToBcd8 (Temp);
(VOID)MicroSecondDelay (RTC_DELAY_1000_MICROSECOND);
Status = I2CWrite (&gRtcDevice, RX8900_REGADDR_YEAR, 1, &Temp);
if(EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
return Status;
}
return Status;
}
EFI_STATUS
EFIAPI
LibSetTime (
IN EFI_TIME *Time
)
{
EFI_STATUS Status;
UINTN EpochSeconds;
// Initialize the hardware if not already done
if (!mRX8900Initialized) {
Status = InitializeRX8900 ();
if (EFI_ERROR (Status)) {
goto EXIT;
}
}
Status = SwitchRtcI2cChannelAndLock ();
if (EFI_ERROR (Status)) {
goto EXIT;
}
if(!IsTimeValid(Time)){
return EFI_INVALID_PARAMETER;
}
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;
}
// 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;
}
EpochToEfiTime (EpochSeconds, Time);
Status = SetTimeToRX8900 (Time);
if (EFI_ERROR (Status)) {
goto EXIT;
}
// Save the current time zone information into non-volatile storage
Status = EfiSetVariable (
(CHAR16 *)mTimeZoneVariableName,
&gEfiCallerIdGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof (Time->TimeZone),
(VOID *)&(Time->TimeZone)
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "LibSetTime: Failed to save %s variable, Status = %r\n",
mTimeZoneVariableName, Status));
goto EXIT;
}
// Save the current daylight information into non-volatile storage
Status = EfiSetVariable (
(CHAR16 *)mDaylightVariableName,
&gEfiCallerIdGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(Time->Daylight),
(VOID *)&(Time->Daylight)
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "LibSetTime: Failed to save %s variable, Status = %r\n",
mDaylightVariableName, Status));
goto EXIT;
}
EXIT:
ReleaseOwnershipOfRtc ();
return Status;
}
EFI_STATUS
EFIAPI
LibGetWakeupTime (
OUT BOOLEAN *Enabled,
OUT BOOLEAN *Pending,
OUT EFI_TIME *Time
)
{
// Not a required feature
return EFI_UNSUPPORTED;
}
EFI_STATUS
EFIAPI
LibSetWakeupTime (
IN BOOLEAN Enabled,
OUT EFI_TIME *Time
)
{
// Not a required feature
return EFI_UNSUPPORTED;
}
EFI_STATUS
EFIAPI
LibRtcInitialize (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HANDLE Handle;
EFI_TIME EfiTime;
// Setup the setters and getters
gRT->GetTime = LibGetTime;
gRT->SetTime = LibSetTime;
gRT->GetWakeupTime = LibGetWakeupTime;
gRT->SetWakeupTime = LibSetWakeupTime;
Status = gRT->GetTime (&EfiTime, NULL);
if (EFI_ERROR (Status) || (EfiTime.Year < 2000) || (EfiTime.Year > 2099) ||
(!IsTimeValid (&EfiTime))) {
EfiTime.Year = 2000;
EfiTime.Month = 1;
EfiTime.Day = 1;
EfiTime.Hour = 0;
EfiTime.Minute = 0;
EfiTime.Second = 0;
EfiTime.Nanosecond = 0;
EfiTime.Daylight = 0;
EfiTime.TimeZone = EFI_UNSPECIFIED_TIMEZONE;
Status = gRT->SetTime (&EfiTime);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SetTime Status : %r\n", Status));
}
}
Handle = NULL;
Status = gBS->InstallMultipleProtocolInterfaces (
&Handle,
&gEfiRealTimeClockArchProtocolGuid,
NULL,
NULL
);
return Status;
}
VOID
EFIAPI
LibRtcVirtualNotifyEvent (
IN EFI_EVENT Event,
IN VOID *Context
)
{
return;
}