/** @file

  Copyright (c) 2018, Linaro, Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/I2cBusConfigurationManagement.h>
#include <Protocol/I2cEnumerate.h>
#include <Protocol/I2cMaster.h>
#include <Protocol/Mezzanine.h>

STATIC MEZZANINE_PROTOCOL     *mMezzanine;

typedef struct {
  EFI_I2C_ENUMERATE_PROTOCOL                    I2cEnumerate;
  EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL I2cConfigManagement;
  EFI_HANDLE                                    I2cMasterHandle;
  UINT32                                        BusFrequency;
  UINTN                                         NumDevices;
  CONST EFI_I2C_DEVICE                          *Devices;
} I2C_BUS;

STATIC
EFI_STATUS
EFIAPI
I2cEnumerate (
  IN CONST EFI_I2C_ENUMERATE_PROTOCOL *This,
  IN OUT CONST EFI_I2C_DEVICE         **Device
  )
{
  I2C_BUS     *Bus;

  if (Device == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Bus = BASE_CR (This, I2C_BUS, I2cEnumerate);

  if (Bus->NumDevices == 0 ||
      (Bus->NumDevices > 0 && *Device == &Bus->Devices[Bus->NumDevices - 1])) {
    *Device = NULL;
  } else if (*Device == NULL) {
    *Device = &Bus->Devices[0];
  } else if (Bus->NumDevices > 1 &&
             *Device >= &Bus->Devices[0] &&
             *Device < &Bus->Devices[Bus->NumDevices - 1]) {
    ++*Device;
  } else {
    return EFI_NO_MAPPING;
  }
  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
I2cGetBusFrequency (
  IN CONST EFI_I2C_ENUMERATE_PROTOCOL *This,
  IN UINTN                            I2cBusConfiguration,
  OUT UINTN                           *BusClockHertz
  )
{
  I2C_BUS     *Bus;

  if (BusClockHertz == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (I2cBusConfiguration > 0) {
    return EFI_NO_MAPPING;
  }

  Bus = BASE_CR (This, I2C_BUS, I2cEnumerate);

  *BusClockHertz = Bus->BusFrequency;

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
EnableI2cBusConfiguration (
  IN CONST EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL  *This,
  IN UINTN                                                I2cBusConfiguration,
  IN EFI_EVENT                                            Event       OPTIONAL,
  IN EFI_STATUS                                           *I2cStatus  OPTIONAL
  )
{
  EFI_I2C_MASTER_PROTOCOL *I2cMaster;
  EFI_STATUS              Status;
  UINTN                   BusClockHertz;
  I2C_BUS                 *Bus;

  if (I2cBusConfiguration > 0) {
    return EFI_NO_MAPPING;
  }

  Bus = BASE_CR (This, I2C_BUS, I2cConfigManagement);

  Status = gBS->HandleProtocol (Bus->I2cMasterHandle,
                  &gEfiI2cMasterProtocolGuid, (VOID **)&I2cMaster);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: gBS->HandleProtocol() failed - %r\n",
      __FUNCTION__, Status));
    return Status;
  }

  BusClockHertz = Bus->BusFrequency;
  Status = I2cMaster->SetBusFrequency (I2cMaster, &BusClockHertz);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: I2cMaster->SetBusFrequency() failed - %r\n",
      __FUNCTION__, Status));
    return Status;
  }

  if (Event != NULL) {
    *I2cStatus = EFI_SUCCESS;
    gBS->SignalEvent (Event);
  }
  return EFI_SUCCESS;
}

STATIC I2C_BUS mI2cBus0 = {
  { I2cEnumerate, I2cGetBusFrequency },
  { EnableI2cBusConfiguration },
  NULL,
  FixedPcdGet32 (PcdI2c0BusFrequencyHz),
  0,
  NULL,
};

STATIC I2C_BUS mI2cBus1 = {
  { I2cEnumerate, I2cGetBusFrequency },
  { EnableI2cBusConfiguration },
  NULL,
  FixedPcdGet32 (PcdI2c1BusFrequencyHz),
  0,
  NULL,
};

STATIC
VOID
RegisterI2cBus (
  IN  EFI_GUID                *Guid,
  IN  I2C_BUS                 *I2cBus,
  IN  UINTN                   NumDevices,
  IN  CONST EFI_I2C_DEVICE    *Devices
  )
{
  EFI_STATUS    Status;
  UINTN         BufferSize;

  BufferSize = sizeof (EFI_HANDLE);
  Status = gBS->LocateHandle (ByProtocol, Guid, NULL, &BufferSize,
                  &I2cBus->I2cMasterHandle);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "%a: gBS->LocateHandle() failed - %r\n", __FUNCTION__,
      Status));
    return;
  }

  I2cBus->NumDevices = NumDevices;
  I2cBus->Devices = Devices;

  Status = gBS->InstallMultipleProtocolInterfaces (&I2cBus->I2cMasterHandle,
                  &gEfiI2cEnumerateProtocolGuid,
                  &I2cBus->I2cEnumerate,
                  &gEfiI2cBusConfigurationManagementProtocolGuid,
                  &I2cBus->I2cConfigManagement,
                  NULL);
  ASSERT_EFI_ERROR (Status);
}

STATIC
VOID
EFIAPI
OnEndOfDxe (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  gBS->CloseEvent (Event);
  gBS->ConnectController (mI2cBus0.I2cMasterHandle, NULL, NULL, TRUE);
  gBS->ConnectController (mI2cBus1.I2cMasterHandle, NULL, NULL, TRUE);
}

EFI_STATUS
EFIAPI
EntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS    Status;
  EFI_EVENT     EndOfDxeEvent;

  Status = gBS->LocateProtocol (&g96BoardsMezzanineProtocolGuid, NULL,
                  (VOID **)&mMezzanine);
  ASSERT_EFI_ERROR (Status);

  RegisterI2cBus (&g96BoardsI2c0MasterGuid, &mI2cBus0,
    mMezzanine->I2c0NumDevices, mMezzanine->I2c0DeviceArray);
  RegisterI2cBus (&g96BoardsI2c1MasterGuid, &mI2cBus1,
    mMezzanine->I2c1NumDevices, mMezzanine->I2c1DeviceArray);

  Status = gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, TPL_CALLBACK, OnEndOfDxe,
                  NULL, &gEfiEndOfDxeEventGroupGuid, &EndOfDxeEvent);
  ASSERT_EFI_ERROR (Status);

  return EFI_SUCCESS;
}
