/*******************************************************************************
Copyright (C) 2016 Marvell International Ltd.

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

*******************************************************************************/
#include "MvSpiOrionDxe.h"

SPI_MASTER *mSpiMasterInstance;

STATIC
EFI_STATUS
SpiSetBaudRate (
  IN SPI_DEVICE *Slave,
  IN UINT32 CpuClock,
  IN UINT32 MaxFreq
  )
{
  UINT32 Spr, BestSpr, Sppr, BestSppr, ClockDivider, Match, Reg, MinBaudDiff;
  UINTN SpiRegBase = Slave->HostRegisterBaseAddress;

  MinBaudDiff = 0xFFFFFFFF;
  BestSppr = 0;

  //Spr is in range 1-15 and Sppr in range 0-8
  for (Spr = 1; Spr <= 15; Spr++) {
    for (Sppr = 0; Sppr <= 7; Sppr++) {
      ClockDivider = Spr * (1 << Sppr);

      if ((CpuClock / ClockDivider) > MaxFreq) {
        continue;
      }

      if ((CpuClock / ClockDivider) == MaxFreq) {
        BestSpr = Spr;
        BestSppr = Sppr;
        Match = 1;
        break;
        }

      if ((MaxFreq - (CpuClock / ClockDivider)) < MinBaudDiff) {
        MinBaudDiff = (MaxFreq - (CpuClock / ClockDivider));
        BestSpr = Spr;
        BestSppr = Sppr;
      }
    }

    if (Match == 1) {
      break;
    }
  }

  if (BestSpr == 0) {
    return (EFI_INVALID_PARAMETER);
  }

  Reg = MmioRead32 (SpiRegBase + SPI_CONF_REG);
  Reg &= ~(SPI_SPR_MASK | SPI_SPPR_0_MASK | SPI_SPPR_HI_MASK);
  Reg |= (BestSpr << SPI_SPR_OFFSET) |
         ((BestSppr & 0x1) << SPI_SPPR_0_OFFSET) |
         ((BestSppr >> 1) << SPI_SPPR_HI_OFFSET);
  MmioWrite32 (SpiRegBase + SPI_CONF_REG, Reg);

  return EFI_SUCCESS;
}

STATIC
VOID
SpiSetCs (
  IN SPI_DEVICE *Slave
  )
{
  UINT32 Reg;
  UINTN  SpiRegBase = Slave->HostRegisterBaseAddress;

  Reg = MmioRead32 (SpiRegBase + SPI_CTRL_REG);
  Reg &= ~SPI_CS_NUM_MASK;
  Reg |= (Slave->Cs << SPI_CS_NUM_OFFSET);
  MmioWrite32 (SpiRegBase + SPI_CTRL_REG, Reg);
}

STATIC
VOID
SpiActivateCs (
  IN SPI_DEVICE *Slave
  )
{
  UINT32 Reg;
  UINTN  SpiRegBase = Slave->HostRegisterBaseAddress;

  SpiSetCs(Slave);
  Reg = MmioRead32 (SpiRegBase + SPI_CTRL_REG);
  Reg |= SPI_CS_EN_MASK;
  MmioWrite32(SpiRegBase + SPI_CTRL_REG, Reg);
}

STATIC
VOID
SpiDeactivateCs (
  IN SPI_DEVICE *Slave
  )
{
  UINT32 Reg;
  UINTN  SpiRegBase = Slave->HostRegisterBaseAddress;

  Reg = MmioRead32 (SpiRegBase + SPI_CTRL_REG);
  Reg &= ~SPI_CS_EN_MASK;
  MmioWrite32(SpiRegBase + SPI_CTRL_REG, Reg);
}

STATIC
VOID
SpiSetupTransfer (
  IN MARVELL_SPI_MASTER_PROTOCOL *This,
  IN SPI_DEVICE *Slave
  )
{
  SPI_MASTER *SpiMaster;
  UINT32 Reg, CoreClock, SpiMaxFreq;
  UINTN SpiRegBase;

  SpiMaster = SPI_MASTER_FROM_SPI_MASTER_PROTOCOL (This);

  // Initialize values from PCDs
  SpiRegBase  = Slave->HostRegisterBaseAddress;
  CoreClock   = Slave->CoreClock;
  SpiMaxFreq  = Slave->MaxFreq;

  EfiAcquireLock (&SpiMaster->Lock);

  Reg = MmioRead32 (SpiRegBase + SPI_CONF_REG);
  Reg |= SPI_BYTE_LENGTH;
  MmioWrite32 (SpiRegBase + SPI_CONF_REG, Reg);

  SpiSetCs(Slave);

  SpiSetBaudRate (Slave, CoreClock, SpiMaxFreq);

  Reg = MmioRead32 (SpiRegBase + SPI_CONF_REG);
  Reg &= ~(SPI_CPOL_MASK | SPI_CPHA_MASK | SPI_TXLSBF_MASK | SPI_RXLSBF_MASK);

  switch (Slave->Mode) {
  case SPI_MODE0:
    break;
  case SPI_MODE1:
    Reg |= SPI_CPHA_MASK;
    break;
  case SPI_MODE2:
    Reg |= SPI_CPOL_MASK;
    break;
  case SPI_MODE3:
    Reg |= SPI_CPOL_MASK;
    Reg |= SPI_CPHA_MASK;
    break;
  }

  MmioWrite32 (SpiRegBase + SPI_CONF_REG, Reg);

  EfiReleaseLock (&SpiMaster->Lock);
}

EFI_STATUS
EFIAPI
MvSpiTransfer (
  IN MARVELL_SPI_MASTER_PROTOCOL *This,
  IN SPI_DEVICE *Slave,
  IN UINTN DataByteCount,
  IN VOID *DataOut,
  IN VOID *DataIn,
  IN UINTN Flag
  )
{
  SPI_MASTER *SpiMaster;
  UINT64  Length;
  UINT32  Iterator, Reg;
  UINT8   *DataOutPtr = (UINT8 *)DataOut;
  UINT8   *DataInPtr  = (UINT8 *)DataIn;
  UINT8   DataToSend  = 0;
  UINTN   SpiRegBase;

  SpiMaster = SPI_MASTER_FROM_SPI_MASTER_PROTOCOL (This);

  SpiRegBase = Slave->HostRegisterBaseAddress;

  Length = 8 * DataByteCount;

  if (!EfiAtRuntime ()) {
    EfiAcquireLock (&SpiMaster->Lock);
  }

  if (Flag & SPI_TRANSFER_BEGIN) {
    SpiActivateCs (Slave);
  }

  // Set 8-bit mode
  Reg = MmioRead32 (SpiRegBase + SPI_CONF_REG);
  Reg &= ~SPI_BYTE_LENGTH;
  MmioWrite32 (SpiRegBase + SPI_CONF_REG, Reg);

  while (Length > 0) {
    if (DataOut != NULL) {
      DataToSend = *DataOutPtr & 0xFF;
    }
    // Transmit Data
    MmioWrite32 (SpiRegBase + SPI_INT_CAUSE_REG, 0x0);
    MmioWrite32 (SpiRegBase + SPI_DATA_OUT_REG, DataToSend);
    // Wait for memory ready
    for (Iterator = 0; Iterator < SPI_TIMEOUT; Iterator++) {
      if (MmioRead32 (SpiRegBase + SPI_INT_CAUSE_REG)) {
        if (DataInPtr != NULL) {
          *DataInPtr = MmioRead32 (SpiRegBase + SPI_DATA_IN_REG);
          DataInPtr++;
        }
        if (DataOutPtr != NULL) {
          DataOutPtr++;
        }
        Length -= 8;
        break;
      }
    }

    if (Iterator >= SPI_TIMEOUT) {
      DEBUG ((DEBUG_ERROR, "%a: Timeout\n", __FUNCTION__));
      return EFI_TIMEOUT;
    }
  }

  if (Flag & SPI_TRANSFER_END) {
    SpiDeactivateCs (Slave);
  }

  if (!EfiAtRuntime ()) {
    EfiReleaseLock (&SpiMaster->Lock);
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
MvSpiReadWrite (
  IN  MARVELL_SPI_MASTER_PROTOCOL *This,
  IN  SPI_DEVICE *Slave,
  IN  UINT8 *Cmd,
  IN  UINTN CmdSize,
  IN  UINT8 *DataOut,
  OUT UINT8 *DataIn,
  IN  UINTN DataSize
  )
{
  EFI_STATUS Status;

  Status = MvSpiTransfer (This, Slave, CmdSize, Cmd, NULL, SPI_TRANSFER_BEGIN);
  if (EFI_ERROR (Status)) {
    Print (L"Spi Transfer Error\n");
    return EFI_DEVICE_ERROR;
  }

  Status = MvSpiTransfer (This, Slave, DataSize, DataOut, DataIn, SPI_TRANSFER_END);
  if (EFI_ERROR (Status)) {
    Print (L"Spi Transfer Error\n");
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
MvSpiInit (
  IN MARVELL_SPI_MASTER_PROTOCOL * This
  )
{

  return EFI_SUCCESS;
}

SPI_DEVICE *
EFIAPI
MvSpiSetupSlave (
  IN MARVELL_SPI_MASTER_PROTOCOL *This,
  IN SPI_DEVICE *Slave,
  IN UINTN Cs,
  IN SPI_MODE Mode
  )
{
  if (!Slave) {
    Slave = AllocateZeroPool (sizeof(SPI_DEVICE));
    if (Slave == NULL) {
      DEBUG((DEBUG_ERROR, "Cannot allocate memory\n"));
      return NULL;
    }

    Slave->Cs   = Cs;
    Slave->Mode = Mode;
  }

  Slave->HostRegisterBaseAddress = PcdGet32 (PcdSpiRegBase);
  Slave->CoreClock = PcdGet32 (PcdSpiClockFrequency);
  Slave->MaxFreq = PcdGet32 (PcdSpiMaxFrequency);

  SpiSetupTransfer (This, Slave);

  return Slave;
}

EFI_STATUS
EFIAPI
MvSpiFreeSlave (
  IN SPI_DEVICE *Slave
  )
{
  FreePool (Slave);

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
MvSpiConfigRuntime (
  IN SPI_DEVICE *Slave
  )
{
  EFI_STATUS Status;
  UINTN AlignedAddress;

  //
  // Host register base may be not aligned to the page size,
  // which is not accepted when setting memory space attributes.
  // Add one aligned page of memory space which covers the host
  // controller registers.
  //
  AlignedAddress = Slave->HostRegisterBaseAddress & ~(SIZE_4KB - 1);

  Status = gDS->AddMemorySpace (EfiGcdMemoryTypeMemoryMappedIo,
                  AlignedAddress,
                  SIZE_4KB,
                  EFI_MEMORY_UC | EFI_MEMORY_RUNTIME);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Failed to add memory space\n", __FUNCTION__));
    return Status;
  }

  Status = gDS->SetMemorySpaceAttributes (AlignedAddress,
                  SIZE_4KB,
                  EFI_MEMORY_UC | EFI_MEMORY_RUNTIME);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Failed to set memory attributes\n", __FUNCTION__));
    gDS->RemoveMemorySpace (AlignedAddress, SIZE_4KB);
    return Status;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
SpiMasterInitProtocol (
  IN MARVELL_SPI_MASTER_PROTOCOL *SpiMasterProtocol
  )
{

  SpiMasterProtocol->Init        = MvSpiInit;
  SpiMasterProtocol->SetupDevice = MvSpiSetupSlave;
  SpiMasterProtocol->FreeDevice  = MvSpiFreeSlave;
  SpiMasterProtocol->Transfer    = MvSpiTransfer;
  SpiMasterProtocol->ReadWrite   = MvSpiReadWrite;
  SpiMasterProtocol->ConfigRuntime = MvSpiConfigRuntime;

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
MvSpiOrionEntryPoint (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_STATUS  Status;

  mSpiMasterInstance = AllocateRuntimeZeroPool (sizeof (SPI_MASTER));
  if (mSpiMasterInstance == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  EfiInitializeLock (&mSpiMasterInstance->Lock, TPL_NOTIFY);

  SpiMasterInitProtocol (&mSpiMasterInstance->SpiMasterProtocol);

  mSpiMasterInstance->Signature = SPI_MASTER_SIGNATURE;

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &(mSpiMasterInstance->Handle),
                  &gMarvellSpiMasterProtocolGuid,
                  &(mSpiMasterInstance->SpiMasterProtocol),
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    FreePool (mSpiMasterInstance);
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}
