/*++ Copyright (c) 2015 Minoca Corp. All Rights Reserved Module Name: rtc.c Abstract: This module implements support for speaking to the RTC module on the AM335x SoC. Author: Evan Green 23-Sep-2015 Environment: Firmware --*/ // // ------------------------------------------------------------------- Includes // #include #include #include "../bbonefw.h" // // --------------------------------------------------------------------- Macros // #define AM3_READ_RTC(_Register) \ *(volatile UINT32 *)(EfiAm335RtcBase + (_Register)) #define AM3_WRITE_RTC(_Register, _Value) \ *((volatile UINT32 *)(EfiAm335RtcBase + (_Register))) = (_Value) // // ---------------------------------------------------------------- Definitions // // // Define the default cutoff year guess between the twentieth and twenty first // century. // #define AM3_CENTURY_CUTOFF_YEAR 70 // // Define some constants to stuff into scratch 0 that indicate the time zone // minutes are being stored there. // #define AM3_SCRATCH0_MAGIC 0x5F4A0000 #define AM3_SCRATCH0_MAGIC_MASK 0xFFFF0000 #define AM3_SCRATCH0_TIME_ZONE_MASK 0x0000FFFF // // Define some constants to stuff into scratch 1 that indicate the daylight bit // is stored there. // #define AM3_SCRATCH1_MAGIC 0xB13C0000 #define AM3_SCRATCH1_MAGIC_MASK 0xFFFF0000 #define AM3_SCRATCH1_DAYLIGHT 0x00008000 #define AM3_SCRATCH1_CENTURY_MASK 0x000000FF // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // VOID EfipAm335WaitForNonBusyEdge ( VOID ); VOID EfipAm335LockRtc ( BOOLEAN Lock ); // // -------------------------------------------------------------------- Globals // // // Define the pointer to the RTC base, which will get virtualized when going // to runtime. // VOID *EfiAm335RtcBase = (VOID *)AM335_RTC_BASE; // // ------------------------------------------------------------------ Functions // EFIAPI EFI_STATUS EfipAm335GetTime ( EFI_TIME *Time, EFI_TIME_CAPABILITIES *Capabilities ) /*++ Routine Description: This routine returns the current time and dat information, and timekeeping capabilities of the hardware platform. Arguments: Time - Supplies a pointer where the current time will be returned. Capabilities - Supplies an optional pointer where the capabilities will be returned on success. Return Value: EFI_SUCCESS on success. EFI_INVALID_PARAMETER if the time parameter was NULL. EFI_DEVICE_ERROR if there was a hardware error accessing the device. --*/ { UINT32 Control; UINT32 Value; UINT32 Year; if (Capabilities != NULL) { Capabilities->Resolution = 1; Capabilities->Accuracy = 0; Capabilities->SetsToZero = FALSE; } Control = AM3_READ_RTC(Am335RtcControl); // // The RTC cannot be turned back on once it's off. // if ((Control & AM335_RTC_CONTROL_RTC_DISABLE) != 0) { return EFI_DEVICE_ERROR; } // // Values are in BCD, and all values snap as soon as the seconds register // is read, so there's no need to worry about tearing. // Value = AM3_READ_RTC(Am335RtcSeconds); Time->Second = EFI_BCD_TO_BINARY(Value); Value = AM3_READ_RTC(Am335RtcMinutes); Time->Minute = EFI_BCD_TO_BINARY(Value); // // Handle post meridiem, which have value in the range 1-12, or 24-hour // mode, which will just be 0-23. // Value = AM3_READ_RTC(Am335RtcHours); Time->Hour = EFI_BCD_TO_BINARY(Value & ~AM335_RTC_HOURS_PM); if ((Control & AM335_RTC_CONTROL_12_HOUR_MODE) != 0) { if (Time->Hour == 12) { Time->Hour = 0; } if ((Value & AM335_RTC_HOURS_PM) != 0) { Time->Hour += 12; } } Value = AM3_READ_RTC(Am335RtcDays); Time->Day = EFI_BCD_TO_BINARY(Value); Value = AM3_READ_RTC(Am335RtcMonths); Time->Month = EFI_BCD_TO_BINARY(Value); Value = AM3_READ_RTC(Am335RtcYears); Year = EFI_BCD_TO_BINARY(Value); // // The time zone might be stored in scratch 0 if it was this firmware that // wrote it last. // Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE; Value = AM3_READ_RTC(Am335RtcScratch0); if ((Value & AM3_SCRATCH0_MAGIC_MASK) == AM3_SCRATCH0_MAGIC) { Time->TimeZone = (INT16)(Value & AM3_SCRATCH0_TIME_ZONE_MASK); } // // The daylight bit and century might be stored in scratch 1. // Time->Daylight = 0; Value = AM3_READ_RTC(Am335RtcScratch1); if ((Value & AM3_SCRATCH1_MAGIC_MASK) == AM3_SCRATCH1_MAGIC) { if ((Value & AM3_SCRATCH1_DAYLIGHT) != 0) { Time->Daylight = 1; } Time->Year = ((Value & AM3_SCRATCH1_CENTURY_MASK) * 100) + Year; // // Scratch 1 doesn't have any known data in it, so take a guess. // } else { if (Year >= AM3_CENTURY_CUTOFF_YEAR) { Time->Year = 1900 + Year; } else { Time->Year = 2000 + Year; } } Time->Nanosecond = 0; return EFI_SUCCESS; } EFIAPI EFI_STATUS EfipAm335SetTime ( EFI_TIME *Time ) /*++ Routine Description: This routine sets the current local time and date information. Arguments: Time - Supplies a pointer to the time to set. Return Value: EFI_SUCCESS on success. EFI_INVALID_PARAMETER if a time field is out of range. EFI_DEVICE_ERROR if there was a hardware error accessing the device. --*/ { UINT32 Century; UINT32 Control; UINT32 Status; UINT32 Value; UINT32 Year; Control = AM3_READ_RTC(Am335RtcControl); if ((Control & AM335_RTC_CONTROL_RTC_DISABLE) != 0) { return EFI_DEVICE_ERROR; } EfipAm335LockRtc(FALSE); // // Stop the clock, wait for it to really stop, program the time, // start the clock. // Control = 0; AM3_WRITE_RTC(Am335RtcControl, 0); do { Status = AM3_READ_RTC(Am335RtcStatus); } while ((Status & AM335_RTC_STATUS_RUN) != 0); Century = 19; Year = Time->Year - 1900; while (Year >= 100) { Century += 1; Year -= 100; } Value = AM3_SCRATCH0_MAGIC | (Time->TimeZone & AM3_SCRATCH0_TIME_ZONE_MASK); AM3_WRITE_RTC(Am335RtcScratch0, Value); Value = AM3_SCRATCH1_MAGIC | (Century & AM3_SCRATCH1_CENTURY_MASK); if (Time->Daylight != 0) { Value |= AM3_SCRATCH1_DAYLIGHT; } AM3_WRITE_RTC(Am335RtcScratch1, Value); AM3_WRITE_RTC(Am335RtcYears, EFI_BINARY_TO_BCD(Year)); AM3_WRITE_RTC(Am335RtcMonths, EFI_BINARY_TO_BCD(Time->Month)); AM3_WRITE_RTC(Am335RtcDays, EFI_BINARY_TO_BCD(Time->Day)); AM3_WRITE_RTC(Am335RtcHours, EFI_BINARY_TO_BCD(Time->Hour)); AM3_WRITE_RTC(Am335RtcMinutes, EFI_BINARY_TO_BCD(Time->Minute)); AM3_WRITE_RTC(Am335RtcSeconds, EFI_BINARY_TO_BCD(Time->Second)); Control = AM335_RTC_CONTROL_RUN; AM3_WRITE_RTC(Am335RtcControl, Control); do { Status = AM3_READ_RTC(Am335RtcStatus); } while ((Status & AM335_RTC_STATUS_RUN) == 0); EfipAm335LockRtc(TRUE); return EFI_SUCCESS; } EFIAPI EFI_STATUS EfipAm335GetWakeupTime ( BOOLEAN *Enabled, BOOLEAN *Pending, EFI_TIME *Time ) /*++ Routine Description: This routine gets the current wake alarm setting. Arguments: Enabled - Supplies a pointer that receives a boolean indicating if the alarm is currently enabled or disabled. Pending - Supplies a pointer that receives a boolean indicating if the alarm signal is pending and requires acknowledgement. Time - Supplies a pointer that receives the current wake time. Return Value: EFI_SUCCESS on success. EFI_INVALID_PARAMETER if any parameter is NULL. EFI_DEVICE_ERROR if there was a hardware error accessing the device. EFI_UNSUPPORTED if the wakeup timer is not supported on this platform. --*/ { UINT32 Control; UINT32 Interrupts; UINT32 Status; UINT32 Value; UINT32 Year; Control = AM3_READ_RTC(Am335RtcControl); if ((Control & AM335_RTC_CONTROL_RTC_DISABLE) != 0) { return EFI_DEVICE_ERROR; } // // Unlock the RTC for that one potential access to clear the status bit. // EfipAm335LockRtc(FALSE); EfipAm335WaitForNonBusyEdge(); // // Values are in BCD, and all values snap as soon as the seconds register // is read, so there's no need to worry about tearing. // Value = AM3_READ_RTC(Am335RtcAlarmSeconds); Time->Second = EFI_BCD_TO_BINARY(Value); Value = AM3_READ_RTC(Am335RtcAlarmMinutes); Time->Minute = EFI_BCD_TO_BINARY(Value); // // Handle post meridiem, which have value in the range 1-12, or 24-hour // mode, which will just be 0-23. // Value = AM3_READ_RTC(Am335RtcAlarmHours); Time->Hour = EFI_BCD_TO_BINARY(Value & ~AM335_RTC_HOURS_PM); if ((Control & AM335_RTC_CONTROL_12_HOUR_MODE) != 0) { if (Time->Hour == 12) { Time->Hour = 0; } if ((Value & AM335_RTC_HOURS_PM) != 0) { Time->Hour += 12; } } Value = AM3_READ_RTC(Am335RtcAlarmDays); Time->Day = EFI_BCD_TO_BINARY(Value); Value = AM3_READ_RTC(Am335RtcAlarmMonths); Time->Month = EFI_BCD_TO_BINARY(Value); Value = AM3_READ_RTC(Am335RtcAlarmYears); Year = EFI_BCD_TO_BINARY(Value); Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE; Time->Daylight = 0; if (Year >= AM3_CENTURY_CUTOFF_YEAR) { Time->Year = 1900 + Year; } else { Time->Year = 2000 + Year; } Time->Nanosecond = 0; Interrupts = AM3_READ_RTC(Am335RtcInterruptEnable); *Enabled = FALSE; if ((Interrupts & AM335_RTC_INTERRUPT_ALARM) != 0) { *Enabled = TRUE; } Status = AM3_READ_RTC(Am335RtcStatus); *Pending = FALSE; if ((Status & AM335_RTC_STATUS_ALARM) != 0) { *Pending = TRUE; Status &= ~AM335_RTC_STATUS_ALARM; AM3_WRITE_RTC(Am335RtcStatus, Status); } EfipAm335LockRtc(FALSE); return EFI_SUCCESS; } EFIAPI EFI_STATUS EfipAm335SetWakeupTime ( BOOLEAN Enable, EFI_TIME *Time ) /*++ Routine Description: This routine sets the current wake alarm setting. Arguments: Enable - Supplies a boolean enabling or disabling the wakeup timer. Time - Supplies an optional pointer to the time to set. This parameter is only optional if the enable parameter is FALSE. Return Value: EFI_SUCCESS on success. EFI_INVALID_PARAMETER if a time field is out of range. EFI_DEVICE_ERROR if there was a hardware error accessing the device. EFI_UNSUPPORTED if the wakeup timer is not supported on this platform. --*/ { UINT32 Control; UINT32 Hour; UINT32 Interrupts; UINT32 Year; Control = AM3_READ_RTC(Am335RtcControl); if ((Control & AM335_RTC_CONTROL_RTC_DISABLE) != 0) { return EFI_DEVICE_ERROR; } if ((Control & AM335_RTC_CONTROL_12_HOUR_MODE) != 0) { Hour = Time->Hour; if (Hour == 0) { Hour = EFI_BINARY_TO_BCD(12); } else if (Hour >= 12) { Hour = EFI_BINARY_TO_BCD(Hour - 12); Hour |= AM335_RTC_HOURS_PM; } } else { Hour = EFI_BINARY_TO_BCD(Time->Hour); } Year = Time->Year - 1900; while (Year >= 100) { Year -= 100; } EfipAm335LockRtc(FALSE); EfipAm335WaitForNonBusyEdge(); AM3_WRITE_RTC(Am335RtcAlarmYears, EFI_BINARY_TO_BCD(Year)); AM3_WRITE_RTC(Am335RtcAlarmMonths, EFI_BINARY_TO_BCD(Time->Month)); AM3_WRITE_RTC(Am335RtcAlarmDays, EFI_BINARY_TO_BCD(Time->Day)); AM3_WRITE_RTC(Am335RtcAlarmHours, Hour); AM3_WRITE_RTC(Am335RtcAlarmMinutes, EFI_BINARY_TO_BCD(Time->Minute)); AM3_WRITE_RTC(Am335RtcAlarmSeconds, EFI_BINARY_TO_BCD(Time->Second)); Interrupts = AM3_READ_RTC(Am335RtcInterruptEnable); Interrupts &= ~AM335_RTC_INTERRUPT_ALARM; if (Enable != FALSE) { Interrupts |= AM335_RTC_INTERRUPT_ALARM; } AM3_WRITE_RTC(Am335RtcInterruptEnable, Interrupts); EfipAm335LockRtc(TRUE); return EFI_SUCCESS; } // // --------------------------------------------------------- Internal Functions // VOID EfipAm335WaitForNonBusyEdge ( VOID ) /*++ Routine Description: This routine waits for the falling edge of a the busy bit in the RTC. This could take up to two seconds if a falling edge was just missed. Arguments: None. Return Value: None. --*/ { UINT32 Status; // // The busy bit will never go up if it's not running. // Status = AM3_READ_RTC(Am335RtcStatus); if ((Status & AM335_RTC_STATUS_RUN) == 0) { return; } // // Wait for a the busy bit to go high so that the start of a falling edge // can be observed. // do { Status = AM3_READ_RTC(Am335RtcStatus); } while ((Status & AM335_RTC_STATUS_BUSY) == 0); // // Now wait for a falling edge. // do { Status = AM3_READ_RTC(Am335RtcStatus); } while ((Status & AM335_RTC_STATUS_BUSY) != 0); return; } VOID EfipAm335LockRtc ( BOOLEAN Lock ) /*++ Routine Description: This routine locks or unlocks write access to the RTC. Arguments: Lock - Supplies a boolean indicating whether to prevent write access (TRUE) or allow it (FALSE). Return Value: None. --*/ { // // To lock it, write the correct kick 0 value, but the incorrect kick 1 // value. According to the state machine diagram, that's the best way to // get to locked, even if the current state is somehow unlocked. // AM3_WRITE_RTC(Am335RtcKick0, AM335_RTC_KICK0_KEY); if (Lock != FALSE) { AM3_WRITE_RTC(Am335RtcKick1, 0xFFFFFFFF); // // Write the correct kick value to unlock the RTC. // } else { AM3_WRITE_RTC(Am335RtcKick1, AM335_RTC_KICK1_KEY); } return; }