/**
*
*  Copyright (c) 2018, Marvell International Ltd. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include "MvGpioDxe.h"

STATIC MV_GPIO *mGpioInstance;

STATIC MV_GPIO_DEVICE_PATH mDevicePathTemplate = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      {
        (UINT8) (sizeof (VENDOR_DEVICE_PATH) +
                 sizeof (MV_GPIO_DRIVER_TYPE)),
        (UINT8) ((sizeof (VENDOR_DEVICE_PATH) +
                 sizeof (MV_GPIO_DRIVER_TYPE)) >> 8),
      },
    },
    EFI_CALLER_ID_GUID
  },
  MV_GPIO_DRIVER_TYPE_SOC_CONTROLLER,
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      sizeof(EFI_DEVICE_PATH_PROTOCOL),
      0
    }
  }
};

#if !defined(MDEPKG_NDEBUG)
/**

Routine Description:

  Verifies if controller index / GPIO pin values
  are within proper boundaries.

Arguments:

  ControllerIndex - index of controller
  GpioPin - which pin to read

Returns:

  EFI_SUCCESS           - GPIO pin / controller index are proper
  EFI_INVALID_PARAMETER - GPIO pin / controller index is out of range
**/
STATIC
EFI_STATUS
MvGpioValidate (
  IN UINTN ControllerIndex,
  IN UINTN GpioPin
  )
{
  if (ControllerIndex >= mGpioInstance->GpioDeviceCount) {
    DEBUG ((DEBUG_ERROR,
      "%a: Invalid GPIO ControllerIndex: %d\n",
      __FUNCTION__,
      ControllerIndex));
    return EFI_INVALID_PARAMETER;
  }

  if (GpioPin >= mGpioInstance->SoCGpio[ControllerIndex].InternalGpioCount) {
    DEBUG ((DEBUG_ERROR,
      "%a: GPIO pin #%d not available in Controller#%d\n",
      __FUNCTION__,
      GpioPin,
      ControllerIndex));
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}
#endif

/**

Routine Description:

  Gets the mode (function) of a GPIO pin

Arguments:

  This  - pointer to protocol
  Gpio  - which pin
  Mode  - pointer to output mode value

Returns:

  EFI_SUCCESS           - mode value retrieved
  EFI_INVALID_PARAMETER - Mode is a null pointer or Gpio pin is out of range

**/
STATIC
EFI_STATUS
MvGpioGetMode (
  IN  EMBEDDED_GPIO       *This,
  IN  EMBEDDED_GPIO_PIN    Gpio,
  OUT EMBEDDED_GPIO_MODE  *Mode
  )
{
  UINTN ControllerIndex;
  UINTN BaseAddress;
  UINTN GpioPin;

  GpioPin = GPIO_PIN (Gpio);
  ControllerIndex = GPIO_PORT (Gpio);

  ASSERT_EFI_ERROR (MvGpioValidate (ControllerIndex, GpioPin));

  BaseAddress = mGpioInstance->SoCGpio[ControllerIndex].RegisterBase;

  if (MmioRead32 (BaseAddress + MV_GPIO_OUT_EN_REG) & (1 << GpioPin)) {
    *Mode = GPIO_MODE_INPUT;
  } else {
    if (MmioRead32 (BaseAddress + MV_GPIO_DATA_IN_REG) & (1 << GpioPin)) {
      *Mode = GPIO_MODE_OUTPUT_1;
    } else {
      *Mode = GPIO_MODE_OUTPUT_0;
    }
  }

  return EFI_SUCCESS;
}

/**

Routine Description:

  Gets the state of a GPIO pin

Arguments:

  This  - pointer to protocol
  Gpio  - which pin to read
  Value - state of the pin

Returns:

  EFI_SUCCESS           - GPIO state returned in Value
  EFI_INVALID_PARAMETER - Value is NULL pointer or Gpio pin is out of range
**/
STATIC
EFI_STATUS
MvGpioGet (
  IN  EMBEDDED_GPIO      *This,
  IN  EMBEDDED_GPIO_PIN   Gpio,
  OUT UINTN              *Value
  )
{
  UINTN ControllerIndex;
  UINTN BaseAddress;
  UINTN GpioPin;

  GpioPin = GPIO_PIN (Gpio);
  ControllerIndex = GPIO_PORT (Gpio);

  ASSERT_EFI_ERROR (MvGpioValidate (ControllerIndex, GpioPin));

  BaseAddress = mGpioInstance->SoCGpio[ControllerIndex].RegisterBase;

  if (MmioRead32 (BaseAddress + MV_GPIO_DATA_IN_REG) & (1 << GpioPin)) {
    *Value = 1;
  } else {
    *Value = 0;
  }

  return EFI_SUCCESS;
}

/**

Routine Description:

  Sets the state of a GPIO pin

Arguments:

  This  - pointer to protocol
  Gpio  - which pin to modify
  Mode  - mode to set

Returns:

  EFI_SUCCESS           - GPIO set as requested
  EFI_UNSUPPORTED       - Mode is not supported
  EFI_INVALID_PARAMETER - Gpio pin is out of range
**/
STATIC
EFI_STATUS
MvGpioSet (
  IN EMBEDDED_GPIO       *This,
  IN EMBEDDED_GPIO_PIN    Gpio,
  IN EMBEDDED_GPIO_MODE   Mode
  )
{
  UINTN ControllerIndex;
  UINTN BaseAddress;
  UINTN GpioPin;

  GpioPin = GPIO_PIN (Gpio);
  ControllerIndex = GPIO_PORT (Gpio);

  ASSERT_EFI_ERROR (MvGpioValidate (ControllerIndex, GpioPin));

  BaseAddress = mGpioInstance->SoCGpio[ControllerIndex].RegisterBase;

  switch (Mode) {
  case GPIO_MODE_OUTPUT_0:
    MmioAnd32 (BaseAddress + MV_GPIO_DATA_OUT_REG, ~(1 << GpioPin));
    MmioAnd32 (BaseAddress + MV_GPIO_OUT_EN_REG, ~(1 << GpioPin));
    break;

  case GPIO_MODE_OUTPUT_1:
    MmioOr32 (BaseAddress + MV_GPIO_DATA_OUT_REG, (1 << GpioPin));
    MmioAnd32 (BaseAddress + MV_GPIO_OUT_EN_REG, ~(1 << GpioPin));
    break;

  case GPIO_MODE_INPUT:
    MmioOr32 (BaseAddress + MV_GPIO_OUT_EN_REG, (1 << GpioPin));
    break;

  default:
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**

Routine Description:

  Sets the pull-up / pull-down resistor of a GPIO pin

Arguments:

  This  - pointer to protocol
  Gpio  - which pin
  Direction - pull-up, pull-down, or none

Returns:

  EFI_UNSUPPORTED - Can not perform the requested operation

**/
EFI_STATUS
EFIAPI
MvGpioSetPull (
  IN  EMBEDDED_GPIO       *This,
  IN  EMBEDDED_GPIO_PIN   Gpio,
  IN  EMBEDDED_GPIO_PULL  Direction
  )
{
  return EFI_UNSUPPORTED;
}

STATIC
VOID
MvGpioInitProtocol (
  IN EMBEDDED_GPIO *GpioProtocol
  )
{
  GpioProtocol->Get     = MvGpioGet;
  GpioProtocol->Set     = MvGpioSet;
  GpioProtocol->GetMode = MvGpioGetMode;
  GpioProtocol->SetPull = MvGpioSetPull;
}

EFI_STATUS
EFIAPI
MvGpioEntryPoint (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  MARVELL_BOARD_DESC_PROTOCOL *MvBoardProtocol;
  MV_BOARD_GPIO_DESCRIPTION *GpioDescription;
  MV_GPIO_DEVICE_PATH *GpioDevicePath;
  EFI_STATUS Status;

  GpioDevicePath = AllocateCopyPool (sizeof (MV_GPIO_DEVICE_PATH),
                     &mDevicePathTemplate);
  if (GpioDevicePath == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  mGpioInstance = AllocateZeroPool (sizeof (MV_GPIO));
  if (mGpioInstance == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrGpioInstanceAlloc;
  }

  /* Obtain list of available controllers */
  Status = gBS->LocateProtocol (&gMarvellBoardDescProtocolGuid,
                  NULL,
                  (VOID **)&MvBoardProtocol);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
      "%a: Cannot locate BoardDesc protocol\n",
      __FUNCTION__));
    goto ErrLocateBoardDesc;
  }

  Status = MvBoardProtocol->GpioDescriptionGet (MvBoardProtocol,
             &GpioDescription);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
      "%a: Cannot get GPIO board desc from BoardDesc protocol\n",
      __FUNCTION__));
    goto ErrLocateBoardDesc;
  }

  mGpioInstance->Signature = MV_GPIO_SIGNATURE;
  mGpioInstance->GpioDeviceCount = GpioDescription->GpioDeviceCount;
  mGpioInstance->SoCGpio = GpioDescription->SoCGpio;

  MvGpioInitProtocol (&mGpioInstance->GpioProtocol);

  Status = gBS->InstallMultipleProtocolInterfaces (&(mGpioInstance->Handle),
                  &gEmbeddedGpioProtocolGuid,
                  &(mGpioInstance->GpioProtocol),
                  &gEfiDevicePathProtocolGuid,
                  (EFI_DEVICE_PATH_PROTOCOL *)GpioDevicePath,
                  NULL);
  if (EFI_ERROR (Status)) {
    goto ErrLocateBoardDesc;
  }

  return EFI_SUCCESS;

ErrLocateBoardDesc:
  gBS->FreePool (mGpioInstance);

ErrGpioInstanceAlloc:
  gBS->FreePool (GpioDevicePath);

  return Status;
}
