/** @file

  SPI bus DXE driver

  Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <Base.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SpiConfiguration.h>
#include <Protocol/SpiHc.h>
#include <Protocol/SpiIo.h>
#include "SpiBus.h"

/**
  Entry point of the Spi Bus layer

  @param[in] ImageHandle  Image handle of this driver.
  @param[in] SystemTable  Pointer to standard EFI system table.

  @retval EFI_SUCCESS           Succeed.
  @retval EFI_DEVICE_ERROR      SpiPeripheral is NULL.
  @retval EFI_NOT_FOUND         Fail to locate SpiHcProtocol or SpiIoConfigurationProtocol
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate SpiIoChip
**/
EFI_STATUS
EFIAPI
SpiBusEntry (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                      Status;
  SPI_IO_CHIP                     *SpiChip;
  EFI_SPI_HC_PROTOCOL             *SpiHc;
  EFI_SPI_CONFIGURATION_PROTOCOL  *SpiConfiguration;
  EFI_SPI_PERIPHERAL              *SpiPeripheral;
  EFI_SPI_BUS                     *Bus;
  UINTN                           BusIndex;
  UINTN                           HcIndex;
  EFI_HANDLE                      *SpiHcHandles;
  UINTN                           HandleCount;
  EFI_DEVICE_PATH_PROTOCOL        *SpiHcDevicePath;

  DEBUG ((DEBUG_VERBOSE, "%a - ENTRY\n", __func__));

  // Get all SPI HC protocols, could be multiple SPI HC's on a single platform
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiSpiHcProtocolGuid,
                  NULL,
                  &HandleCount,
                  &SpiHcHandles
                  );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VERBOSE, "No SpiHcProtocol is found\n"));
    return EFI_NOT_FOUND;
  }

  // Locate the SPI Configuration Protocol
  Status = gBS->LocateProtocol (
                  &gEfiSpiConfigurationProtocolGuid,
                  NULL,
                  (VOID **)&SpiConfiguration
                  );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VERBOSE, "No SpiConfigurationProtocol is found\n"));
    Status = EFI_NOT_FOUND;
    goto Exit;
  }

  // Parse through Hc protocols, find correct device path
  for (HcIndex = 0; HcIndex < HandleCount; HcIndex++) {
    Status = gBS->HandleProtocol (
                    SpiHcHandles[HcIndex],
                    &gEfiDevicePathProtocolGuid,
                    (VOID **)&SpiHcDevicePath
                    );

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_VERBOSE, "Error locating EFI device path for this SPI controller, status=%r \n", Status));
      continue; // Continue searching
    }

    // Parse through SpiConfiguration's SpiBuses, find matching devicepath for SpiHc
    for (BusIndex = 0; BusIndex < SpiConfiguration->BusCount; BusIndex++) {
      Bus = (EFI_SPI_BUS *)SpiConfiguration->Buslist[BusIndex];
      if (!DevicePathsAreEqual (SpiHcDevicePath, Bus->ControllerPath)) {
        DEBUG ((DEBUG_VERBOSE, "SpiHc and SpiConfig device paths dont match, continue parsing\n"));
        continue;
      }

      DEBUG ((
        DEBUG_VERBOSE,
        "%a: Found matching device paths, Enumerating SPI BUS: %s with DevicePath: %s\n",
        __func__,
        Bus->FriendlyName,
        ConvertDevicePathToText (SpiHcDevicePath, FALSE, FALSE)
        ));

      // Get SpiHc from the SpiHcHandles
      Status = gBS->HandleProtocol (
                      SpiHcHandles[HcIndex],
                      &gEfiSpiHcProtocolGuid,
                      (VOID **)&SpiHc
                      );

      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_VERBOSE, "%a - Error getting SpiHc from Handle\n", __func__));
        goto Exit;
      }

      SpiPeripheral = (EFI_SPI_PERIPHERAL *)Bus->Peripherallist;
      if (SpiPeripheral != NULL) {
        do {
          DEBUG ((
            DEBUG_VERBOSE,
            "%a: Installing SPI IO protocol for %s, by %s, PN=%s\n",
            __func__,
            SpiPeripheral->FriendlyName,
            SpiPeripheral->SpiPart->Vendor,
            SpiPeripheral->SpiPart->PartNumber
            ));
          // Allocate the SPI IO Device
          SpiChip = AllocateZeroPool (sizeof (SPI_IO_CHIP));
          ASSERT (SpiChip != NULL);
          if (SpiChip != NULL) {
            // Fill in the SpiChip
            SpiChip->Signature                      = SPI_IO_SIGNATURE;
            SpiChip->SpiConfig                      = SpiConfiguration;
            SpiChip->SpiHc                          = SpiHc;
            SpiChip->SpiBus                         = Bus;
            SpiChip->Protocol.SpiPeripheral         = SpiPeripheral;
            SpiChip->Protocol.OriginalSpiPeripheral = SpiPeripheral;
            SpiChip->Protocol.FrameSizeSupportMask  = SpiHc->FrameSizeSupportMask;
            SpiChip->Protocol.MaximumTransferBytes  = SpiHc->MaximumTransferBytes;
            if ((SpiHc->Attributes & HC_TRANSFER_SIZE_INCLUDES_ADDRESS) != 0) {
              SpiChip->Protocol.Attributes |= SPI_IO_TRANSFER_SIZE_INCLUDES_ADDRESS;
            }

            if ((SpiHc->Attributes & HC_TRANSFER_SIZE_INCLUDES_OPCODE) != 0) {
              SpiChip->Protocol.Attributes |= SPI_IO_TRANSFER_SIZE_INCLUDES_OPCODE;
            }

            if ((SpiHc->Attributes & HC_SUPPORTS_8_BIT_DATA_BUS_WIDTH) != 0) {
              SpiChip->Protocol.Attributes |= SPI_IO_SUPPORTS_8_BIT_DATA_BUS_WIDTH;
            }

            if ((SpiHc->Attributes & HC_SUPPORTS_4_BIT_DATA_BUS_WIDTH) != 0) {
              SpiChip->Protocol.Attributes |= SPI_IO_SUPPORTS_4_BIT_DATA_BUS_WIDTH;
            }

            if ((SpiHc->Attributes & HC_SUPPORTS_2_BIT_DATA_BUS_WIDTH) != 0) {
              SpiChip->Protocol.Attributes |= SPI_IO_SUPPORTS_2_BIT_DATA_BUS_WIDTH;
            }

            SpiChip->Protocol.Transaction         = Transaction;
            SpiChip->Protocol.UpdateSpiPeripheral = UpdateSpiPeripheral;
            // Install the SPI IO Protocol
            Status = gBS->InstallProtocolInterface (
                            &SpiChip->Handle,
                            (GUID *)SpiPeripheral->SpiPeripheralDriverGuid,
                            EFI_NATIVE_INTERFACE,
                            &SpiChip->Protocol
                            );
            if (EFI_ERROR (Status)) {
              DEBUG ((DEBUG_VERBOSE, "%a - Error installing SpiIoProtocol\n", __func__));
              continue;
            }
          } else {
            Status = EFI_OUT_OF_RESOURCES;
            DEBUG ((
              DEBUG_ERROR,
              "%a: Out of Memory resources\n",
              __func__
              ));
            break;
          }

          SpiPeripheral = (EFI_SPI_PERIPHERAL *)SpiPeripheral->NextSpiPeripheral;
        } while (SpiPeripheral != NULL);
      } else {
        Status = EFI_DEVICE_ERROR;
      }
    }
  }

Exit:

  gBS->FreePool (SpiHcHandles);

  DEBUG ((DEBUG_VERBOSE, "%a - EXIT (Status = %r)\n", __func__, Status));
  return Status;
}
