text: /** @file
  
    Copyright (c) 2017, Linaro, Ltd. All rights reserved.<BR>
  
    SPDX-License-Identifier: BSD-2-Clause-Patent
  
  **/
  
  #include <PiDxe.h>
  #include <Library/DebugLib.h>
  #include <Library/IoLib.h>
  #include <Library/RealTimeClockLib.h>
  #include <Library/UefiBootServicesTableLib.h>
  #include <Library/UefiLib.h>
  #include <Library/UefiRuntimeLib.h>
  #include <Protocol/I2cMaster.h>
  
  #define SLAVE_ADDRESS             (FixedPcdGet8 (PcdI2cSlaveAddress))
  #define PCF8563_DATA_REG_OFFSET   0x2
  
  #define PCF8563_CLOCK_INVALID     0x80
  #define PCF8563_SECONDS_MASK      0x7f
  #define PCF8563_MINUTES_MASK      0x7f
  #define PCF8563_HOURS_MASK        0x3f
  #define PCF8563_DAYS_MASK         0x3f
  #define PCF8563_WEEKDAYS_MASK     0x07
  #define PCF8563_MONTHS_MASK       0x1f
  #define PCF8563_CENTURY_MASK      0x80
  
  //
  // The PCF8563 has a 'century' flag, which means it could theoretically span
  // 200 years. However, it treats all years divisible by 4 as leap years,
  // including the years 1900 and 2100 (which are not leap years), so the only
  // sane base year is 2000 (which was a leap year).
  //
  #define EPOCH_BASE                2000
  
  STATIC EFI_HANDLE                 mI2cMasterHandle;
  STATIC VOID                       *mI2cMasterEventRegistration;
  STATIC EFI_I2C_MASTER_PROTOCOL    *mI2cMaster;
  STATIC EFI_EVENT                  mRtcVirtualAddrChangeEvent;
  
  #pragma pack(1)
  typedef struct {
    UINT8                           VL_seconds;
    UINT8                           Minutes;
    UINT8                           Hours;
    UINT8                           Days;
    UINT8                           Weekdays;
    UINT8                           Century_months;
    UINT8                           Years;
  } RTC_DATETIME;
  
  typedef struct {
    UINT8                           Reg;
    RTC_DATETIME                    DateTime;
  } RTC_SET_DATETIME_PACKET;
  #pragma pack()
  
  typedef struct {
    UINTN                           OperationCount;
    EFI_I2C_OPERATION               SetAddressOp;
    EFI_I2C_OPERATION               GetDateTimeOp;
  } RTC_GET_I2C_REQUEST;
  
  typedef EFI_I2C_REQUEST_PACKET    RTC_SET_I2C_REQUEST;
  /**
    Returns the current time and date information, and the time-keeping
    capabilities of the hardware platform.
  
    @param  Time                  A pointer to storage to receive a snapshot of
                                  the current time.
    @param  Capabilities          An optional pointer to a buffer to receive the
                                  real time clock device's capabilities.
  
    @retval EFI_SUCCESS           The operation completed successfully.
    @retval EFI_INVALID_PARAMETER Time is NULL.
    @retval EFI_DEVICE_ERROR      The time could not be retrieved due to hardware
                                  error.
  
  **/
  EFI_STATUS
  EFIAPI
  LibGetTime (
    OUT EFI_TIME                *Time,
    OUT EFI_TIME_CAPABILITIES   *Capabilities
    )
  {
    RTC_GET_I2C_REQUEST         Op;
    RTC_DATETIME                DateTime;
    EFI_STATUS                  Status;
    UINT8                       Reg;
  
    if (Time == NULL) {
      return EFI_INVALID_PARAMETER;
    }
  
    if (mI2cMaster == NULL) {
      return EFI_DEVICE_ERROR;
    }
  
    Reg = PCF8563_DATA_REG_OFFSET;
  
    Op.OperationCount = 2;
  
    Op.SetAddressOp.Flags = 0;
    Op.SetAddressOp.LengthInBytes = 1;
    Op.SetAddressOp.Buffer = &Reg;
  
    Op.GetDateTimeOp.Flags = I2C_FLAG_READ;
    Op.GetDateTimeOp.LengthInBytes = sizeof (RTC_DATETIME);
    Op.GetDateTimeOp.Buffer = (VOID *)&DateTime;
  
    Status = mI2cMaster->StartRequest (mI2cMaster, SLAVE_ADDRESS,
                           (VOID *)&Op, NULL, NULL);
    if (EFI_ERROR (Status)) {
      return EFI_DEVICE_ERROR;
    }
  
    Time->Nanosecond = 0;
  
    if ((DateTime.VL_seconds & PCF8563_CLOCK_INVALID) != 0) {
        Time->Second  = 0;
        Time->Minute  = 0;
        Time->Hour    = 0;
        Time->Day     = 1;
        Time->Month   = 1;
        Time->Year    = EPOCH_BASE;
    } else {
        Time->Second  = BcdToDecimal8 (DateTime.VL_seconds & PCF8563_SECONDS_MASK);
        Time->Minute  = BcdToDecimal8 (DateTime.Minutes & PCF8563_MINUTES_MASK);
        Time->Hour    = BcdToDecimal8 (DateTime.Hours & PCF8563_HOURS_MASK);
        Time->Day     = BcdToDecimal8 (DateTime.Days & PCF8563_DAYS_MASK);
        Time->Month   = BcdToDecimal8 (DateTime.Century_months & PCF8563_MONTHS_MASK);
        Time->Year    = BcdToDecimal8 (DateTime.Years) + EPOCH_BASE;
  
        if (DateTime.Century_months & PCF8563_CENTURY_MASK) {
            Time->Year += 100;
        }
    }
  
    if (Capabilities != NULL) {
      Capabilities->Resolution = 1;
      Capabilities->Accuracy = 0;
      Capabilities->SetsToZero = TRUE;
    }
    return EFI_SUCCESS;
  }
  
  
  /**
    Sets the current local time and date information.
  
    @param  Time                  A pointer to the current time.
  
    @retval EFI_SUCCESS           The operation completed successfully.
    @retval EFI_INVALID_PARAMETER A time field is out of range.
    @retval EFI_DEVICE_ERROR      The time could not be set due due to hardware
                                  error.
  
  **/
  EFI_STATUS
  EFIAPI
  LibSetTime (
    IN EFI_TIME                *Time
    )
  {
    RTC_SET_I2C_REQUEST         Op;
    RTC_SET_DATETIME_PACKET     Packet;
    EFI_STATUS                  Status;
  
    if (mI2cMaster == NULL) {
      return EFI_DEVICE_ERROR;
    }
  
    Packet.DateTime.VL_seconds          = DecimalToBcd8 (Time->Second);
    Packet.DateTime.Minutes             = DecimalToBcd8 (Time->Minute);
    Packet.DateTime.Hours               = DecimalToBcd8 (Time->Hour);
    Packet.DateTime.Days                = DecimalToBcd8 (Time->Day);
    Packet.DateTime.Weekdays            = 0;
    Packet.DateTime.Century_months      = DecimalToBcd8 (Time->Month);
    Packet.DateTime.Years               = DecimalToBcd8 (Time->Year % 100);
    if (Time->Year >= EPOCH_BASE + 100) {
      if (Time->Year >= EPOCH_BASE + 200) {
        return EFI_DEVICE_ERROR;
      }
      Packet.DateTime.Century_months    |= PCF8563_CENTURY_MASK;
    }
  
    Packet.Reg = PCF8563_DATA_REG_OFFSET;
  
    Op.OperationCount                   = 1;
    Op.Operation[0].Flags               = 0;
    Op.Operation[0].LengthInBytes       = sizeof (RTC_SET_DATETIME_PACKET);
    Op.Operation[0].Buffer              = (VOID *)&Packet;
  
    Status = mI2cMaster->StartRequest (mI2cMaster, SLAVE_ADDRESS,
                           (VOID *)&Op, NULL, NULL);
    if (EFI_ERROR (Status)) {
      return EFI_DEVICE_ERROR;
    }
    return EFI_SUCCESS;
  }
  
  
  /**
    Returns the current wakeup alarm clock setting.
  
    @param  Enabled               Indicates if the alarm is currently enabled or
                                  disabled.
    @param  Pending               Indicates if the alarm signal is pending and
                                  requires acknowledgement.
    @param  Time                  The current alarm setting.
  
    @retval EFI_SUCCESS           The alarm settings were returned.
    @retval EFI_INVALID_PARAMETER Any parameter is NULL.
    @retval EFI_DEVICE_ERROR      The wakeup time could not be retrieved due to a
                                  hardware error.
    @retval EFI_UNSUPPORTED       A wakeup timer is not supported on this
                                  platform.
  
  **/
  EFI_STATUS
  EFIAPI
  LibGetWakeupTime (
    OUT BOOLEAN     *Enabled,
    OUT BOOLEAN     *Pending,
    OUT EFI_TIME    *Time
    )
  {
    //
    // Currently unimplemented. The PCF8563 does not support setting the alarm
    // for an arbitrary date/time, but only for a minute/hour/day/weekday
    // combination. It should also depend on a platform specific setting that
    // indicates whether the PCF8563's interrupt line is connected in a way that
    // allows it to power up the system in the first place.
    //
    return EFI_UNSUPPORTED;
  }
  
  
  /**
    Sets the system wakeup alarm clock time.
  
    @param  Enabled               Enable or disable the wakeup alarm.
    @param  Time                  If Enable is TRUE, the time to set the wakeup
                                  alarm for.
  
    @retval EFI_SUCCESS           If Enable is TRUE, then the wakeup alarm was
                                  enabled. If Enable is FALSE, then the wakeup
                                  alarm was disabled.
    @retval EFI_INVALID_PARAMETER A time field is out of range.
    @retval EFI_DEVICE_ERROR      The wakeup time could not be set due to a
                                  hardware error.
    @retval EFI_UNSUPPORTED       A wakeup timer is not supported on this
                                  platform.
  
  **/
  EFI_STATUS
  EFIAPI
  LibSetWakeupTime (
    IN BOOLEAN      Enabled,
    OUT EFI_TIME    *Time
    )
  {
    // see comment above
    return EFI_UNSUPPORTED;
  }
  
  STATIC
  VOID
  I2cMasterRegistrationEvent (
    IN  EFI_EVENT       Event,
    IN  VOID            *Context
    )
  {
    EFI_HANDLE                Handle;
    UINTN                     BufferSize;
    EFI_STATUS                Status;
    EFI_I2C_MASTER_PROTOCOL   *I2cMaster;
    UINTN                     BusFrequency;
  
    //
    // Try to connect the newly registered driver to our handle.
    //
    do {
      BufferSize = sizeof (EFI_HANDLE);
      Status = gBS->LocateHandle (ByRegisterNotify,
                                  &gEfiI2cMasterProtocolGuid,
                                  mI2cMasterEventRegistration,
                                  &BufferSize,
                                  &Handle);
      if (EFI_ERROR (Status)) {
        if (Status != EFI_NOT_FOUND) {
          DEBUG ((DEBUG_WARN, "%a: gBS->LocateHandle () returned %r\n",
            __FUNCTION__, Status));
        }
        break;
      }
  
      if (Handle != mI2cMasterHandle) {
        continue;
      }
  
      DEBUG ((DEBUG_INFO, "%a: found I2C master!\n", __FUNCTION__));
  
      gBS->CloseEvent (Event);
  
      Status = gBS->OpenProtocol (mI2cMasterHandle, &gEfiI2cMasterProtocolGuid,
                      (VOID **)&I2cMaster, gImageHandle, NULL,
                      EFI_OPEN_PROTOCOL_EXCLUSIVE);
      ASSERT_EFI_ERROR (Status);
  
      Status = I2cMaster->Reset (I2cMaster);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "%a: I2CMaster->Reset () failed - %r\n",
          __FUNCTION__, Status));
        break;
      }
  
      BusFrequency = FixedPcdGet16 (PcdI2cBusFrequency);
      Status = I2cMaster->SetBusFrequency (I2cMaster, &BusFrequency);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "%a: I2CMaster->SetBusFrequency () failed - %r\n",
          __FUNCTION__, Status));
        break;
      }
  
      mI2cMaster = I2cMaster;
      break;
    } while (TRUE);
  }
  
  /**
    Fixup internal data so that EFI can be call in virtual mode.
    Call the passed in Child Notify event and convert any pointers in
    lib to virtual mode.
  
    @param[in]    Event   The Event that is being processed
    @param[in]    Context Event Context
  **/
  VOID
  EFIAPI
  LibRtcVirtualNotifyEvent (
    IN EFI_EVENT        Event,
    IN VOID             *Context
    )
  {
    EfiConvertPointer (0x0, (VOID **)&mI2cMaster);
  }
  
  /**
    Library entry point
  
    @param  ImageHandle           Handle that identifies the loaded image.
    @param  SystemTable           System Table for this image.
  
    @retval EFI_SUCCESS           The operation completed successfully.
  
  **/
  EFI_STATUS
  EFIAPI
  LibRtcInitialize (
    IN EFI_HANDLE                            ImageHandle,
    IN EFI_SYSTEM_TABLE                      *SystemTable
    )
  {
    EFI_STATUS          Status;
    UINTN               BufferSize;
  
    //
    // Find the handle that marks the controller
    // that will provide the I2C master protocol.
    //
    BufferSize = sizeof (EFI_HANDLE);
    Status = gBS->LocateHandle (ByProtocol,
                    &gPcf8563RealTimeClockLibI2cMasterProtocolGuid, NULL,
                    &BufferSize, &mI2cMasterHandle);
    ASSERT_EFI_ERROR (Status);
  
    //
    // Register a protocol registration notification callback on the I2C master
    // protocol. This will notify us even if the protocol instance we are looking
    // for has already been installed.
    //
    EfiCreateProtocolNotifyEvent (
      &gEfiI2cMasterProtocolGuid,
      TPL_CALLBACK,
      I2cMasterRegistrationEvent,
      NULL,
      &mI2cMasterEventRegistration);
  
    //
    // Register for the virtual address change event
    //
    Status = gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, TPL_NOTIFY,
                    LibRtcVirtualNotifyEvent, NULL,
                    &gEfiEventVirtualAddressChangeGuid,
                    &mRtcVirtualAddrChangeEvent);
    ASSERT_EFI_ERROR (Status);
  
    return EFI_SUCCESS;
  }
  