/** @file
  Main file of the MMC Dxe driver. The driver entrypoint is defined into this file.

  Copyright (c) 2011-2013, ARM Limited. All rights reserved.

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

**/

#include <Protocol/DevicePath.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DevicePathLib.h>

#include "Mmc.h"

EFI_BLOCK_IO_MEDIA mMmcMediaTemplate = {
  SIGNATURE_32('m','m','c','o'),            // MediaId
  TRUE,                                     // RemovableMedia
  FALSE,                                    // MediaPresent
  FALSE,                                    // LogicalPartition
  FALSE,                                    // ReadOnly
  FALSE,                                    // WriteCaching
  512,                                      // BlockSize
  4,                                        // IoAlign
  0,                                        // Pad
  0                                         // LastBlock
};

//
// This device structure is serviced as a header.
// Its next field points to the first root bridge device node.
//
LIST_ENTRY  mMmcHostPool;

/**
  Event triggered by the timer to check if any cards have been removed
  or if new ones have been plugged in
**/

EFI_EVENT gCheckCardsEvent;

/**
  Initialize the MMC Host Pool to support multiple MMC devices
**/
VOID
InitializeMmcHostPool (
  VOID
  )
{
  InitializeListHead (&mMmcHostPool);
}

/**
  Insert a new Mmc Host controller to the pool
**/
VOID
InsertMmcHost (
  IN MMC_HOST_INSTANCE      *MmcHostInstance
  )
{
  InsertTailList (&mMmcHostPool, &(MmcHostInstance->Link));
}

/*
  Remove a new Mmc Host controller to the pool
*/
VOID
RemoveMmcHost (
  IN MMC_HOST_INSTANCE      *MmcHostInstance
  )
{
  RemoveEntryList (&(MmcHostInstance->Link));
}

MMC_HOST_INSTANCE* CreateMmcHostInstance (
  IN EFI_MMC_HOST_PROTOCOL* MmcHost
  )
{
  EFI_STATUS          Status;
  MMC_HOST_INSTANCE*  MmcHostInstance;
  EFI_DEVICE_PATH_PROTOCOL    *NewDevicePathNode;
  EFI_DEVICE_PATH_PROTOCOL    *DevicePath;

  MmcHostInstance = AllocateZeroPool (sizeof (MMC_HOST_INSTANCE));
  if (MmcHostInstance == NULL) {
    return NULL;
  }

  MmcHostInstance->Signature = MMC_HOST_INSTANCE_SIGNATURE;

  MmcHostInstance->State = MmcHwInitializationState;

  MmcHostInstance->BlockIo.Media = AllocateCopyPool (sizeof(EFI_BLOCK_IO_MEDIA), &mMmcMediaTemplate);
  if (MmcHostInstance->BlockIo.Media == NULL) {
    goto FREE_INSTANCE;
  }

  MmcHostInstance->BlockIo.Revision = EFI_BLOCK_IO_INTERFACE_REVISION;
  MmcHostInstance->BlockIo.Reset = MmcReset;
  MmcHostInstance->BlockIo.ReadBlocks = MmcReadBlocks;
  MmcHostInstance->BlockIo.WriteBlocks = MmcWriteBlocks;
  MmcHostInstance->BlockIo.FlushBlocks = MmcFlushBlocks;

  MmcHostInstance->MmcHost = MmcHost;

  // Create DevicePath for the new MMC Host
  Status = MmcHost->BuildDevicePath (MmcHost, &NewDevicePathNode);
  if (EFI_ERROR (Status)) {
    goto FREE_MEDIA;
  }

  DevicePath = (EFI_DEVICE_PATH_PROTOCOL *) AllocatePool (END_DEVICE_PATH_LENGTH);
  if (DevicePath == NULL) {
    goto FREE_MEDIA;
  }

  SetDevicePathEndNode (DevicePath);
  MmcHostInstance->DevicePath = AppendDevicePathNode (DevicePath, NewDevicePathNode);

  // Publish BlockIO protocol interface
  Status = gBS->InstallMultipleProtocolInterfaces (
                &MmcHostInstance->MmcHandle,
                &gEfiBlockIoProtocolGuid,&MmcHostInstance->BlockIo,
                &gEfiDevicePathProtocolGuid,MmcHostInstance->DevicePath,
                NULL
                );
  if (EFI_ERROR(Status)) {
    goto FREE_DEVICE_PATH;
  }

  return MmcHostInstance;

FREE_DEVICE_PATH:
  FreePool(DevicePath);

FREE_MEDIA:
  FreePool(MmcHostInstance->BlockIo.Media);

FREE_INSTANCE:
  FreePool(MmcHostInstance);

  return NULL;
}

EFI_STATUS DestroyMmcHostInstance (
  IN MMC_HOST_INSTANCE* MmcHostInstance
  )
{
  EFI_STATUS Status;

  // Uninstall Protocol Interfaces
  Status = gBS->UninstallMultipleProtocolInterfaces (
        MmcHostInstance->MmcHandle,
        &gEfiBlockIoProtocolGuid,&(MmcHostInstance->BlockIo),
        &gEfiDevicePathProtocolGuid,MmcHostInstance->DevicePath,
        NULL
        );
  ASSERT_EFI_ERROR (Status);

  // Free Memory allocated for the instance
  if (MmcHostInstance->BlockIo.Media) {
    FreePool(MmcHostInstance->BlockIo.Media);
  }
  if (MmcHostInstance->CardInfo.ECSDData) {
    FreePages (MmcHostInstance->CardInfo.ECSDData, EFI_SIZE_TO_PAGES (sizeof (ECSD)));
  }
  FreePool (MmcHostInstance);

  return Status;
}

/**
  This function checks if the controller implement the Mmc Host and the Device Path Protocols
**/
EFI_STATUS
EFIAPI
MmcDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                      Status;
  //EFI_DEVICE_PATH_PROTOCOL        *ParentDevicePath;
  EFI_MMC_HOST_PROTOCOL           *MmcHost;
  EFI_DEV_PATH_PTR                Node;

  //
  // Check RemainingDevicePath validation
  //
  if (RemainingDevicePath != NULL) {
    //
    // Check if RemainingDevicePath is the End of Device Path Node,
    // if yes, go on checking other conditions
    //
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      //
      // If RemainingDevicePath isn't the End of Device Path Node,
      // check its validation
      //
      Node.DevPath = RemainingDevicePath;
      if (Node.DevPath->Type != HARDWARE_DEVICE_PATH ||
        Node.DevPath->SubType != HW_VENDOR_DP      ||
        DevicePathNodeLength(Node.DevPath) != sizeof(VENDOR_DEVICE_PATH)) {
          return EFI_UNSUPPORTED;
      }
    }
  }

  //
  // Check if Mmc Host protocol is installed by platform
  //
  Status = gBS->OpenProtocol (
                Controller,
                &gEmbeddedMmcHostProtocolGuid,
                (VOID **) &MmcHost,
                This->DriverBindingHandle,
                Controller,
                EFI_OPEN_PROTOCOL_BY_DRIVER
                );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close the Mmc Host used to perform the supported test
  //
  gBS->CloseProtocol (
      Controller,
      &gEmbeddedMmcHostProtocolGuid,
      This->DriverBindingHandle,
      Controller
      );

  return EFI_SUCCESS;
}

/**

**/
EFI_STATUS
EFIAPI
MmcDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS              Status;
  MMC_HOST_INSTANCE       *MmcHostInstance;
  EFI_MMC_HOST_PROTOCOL   *MmcHost;

  //
  // Check RemainingDevicePath validation
  //
  if (RemainingDevicePath != NULL) {
    //
    // Check if RemainingDevicePath is the End of Device Path Node,
    // if yes, return EFI_SUCCESS
    //
    if (IsDevicePathEnd (RemainingDevicePath)) {
      return EFI_SUCCESS;
    }
  }

  //
  // Get the Mmc Host protocol
  //
  Status = gBS->OpenProtocol (
                Controller,
                &gEmbeddedMmcHostProtocolGuid,
                (VOID **) &MmcHost,
                This->DriverBindingHandle,
                Controller,
                EFI_OPEN_PROTOCOL_BY_DRIVER
                );
  if (EFI_ERROR (Status)) {
    if (Status == EFI_ALREADY_STARTED) {
      return EFI_SUCCESS;
    }
    return Status;
  }

  MmcHostInstance = CreateMmcHostInstance(MmcHost);
  if (MmcHostInstance != NULL) {
    // Add the handle to the pool
    InsertMmcHost (MmcHostInstance);

    MmcHostInstance->Initialized = FALSE;

    // Detect card presence now
    CheckCardsCallback (NULL, NULL);
  }

  return EFI_SUCCESS;
}

/**

**/
EFI_STATUS
EFIAPI
MmcDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL   *This,
  IN  EFI_HANDLE                    Controller,
  IN  UINTN                         NumberOfChildren,
  IN  EFI_HANDLE                    *ChildHandleBuffer
  )
{
  EFI_STATUS          Status = EFI_SUCCESS;
  LIST_ENTRY          *CurrentLink;
  MMC_HOST_INSTANCE   *MmcHostInstance;

  MMC_TRACE("MmcDriverBindingStop()");

  // For each MMC instance
  CurrentLink = mMmcHostPool.ForwardLink;
  while (CurrentLink != NULL && CurrentLink != &mMmcHostPool && (Status == EFI_SUCCESS)) {
    MmcHostInstance = MMC_HOST_INSTANCE_FROM_LINK(CurrentLink);
    ASSERT(MmcHostInstance != NULL);

    // Close gEmbeddedMmcHostProtocolGuid
    Status = gBS->CloseProtocol (
                Controller,
                &gEmbeddedMmcHostProtocolGuid,
                This->DriverBindingHandle,
                Controller
                );

    // Remove MMC Host Instance from the pool
    RemoveMmcHost (MmcHostInstance);

    // Destroy MmcHostInstance
    DestroyMmcHostInstance (MmcHostInstance);
  }

  return Status;
}

VOID
EFIAPI
CheckCardsCallback (
  IN  EFI_EVENT   Event,
  IN  VOID        *Context
  )
{
  LIST_ENTRY          *CurrentLink;
  MMC_HOST_INSTANCE   *MmcHostInstance;
  EFI_STATUS          Status;

  CurrentLink = mMmcHostPool.ForwardLink;
  while (CurrentLink != NULL && CurrentLink != &mMmcHostPool) {
    MmcHostInstance = MMC_HOST_INSTANCE_FROM_LINK(CurrentLink);
    ASSERT(MmcHostInstance != NULL);

    if (MmcHostInstance->MmcHost->IsCardPresent (MmcHostInstance->MmcHost) == !MmcHostInstance->Initialized) {
      MmcHostInstance->State = MmcHwInitializationState;
      MmcHostInstance->BlockIo.Media->MediaPresent = !MmcHostInstance->Initialized;
      MmcHostInstance->Initialized = !MmcHostInstance->Initialized;

      if (MmcHostInstance->BlockIo.Media->MediaPresent) {
        InitializeMmcDevice (MmcHostInstance);
      }

      Status = gBS->ReinstallProtocolInterface (
                    (MmcHostInstance->MmcHandle),
                    &gEfiBlockIoProtocolGuid,
                    &(MmcHostInstance->BlockIo),
                    &(MmcHostInstance->BlockIo)
                    );

      if (EFI_ERROR(Status)) {
        Print(L"MMC Card: Error reinstalling BlockIo interface\n");
      }
    }

    CurrentLink = CurrentLink->ForwardLink;
  }
}


EFI_DRIVER_BINDING_PROTOCOL gMmcDriverBinding = {
  MmcDriverBindingSupported,
  MmcDriverBindingStart,
  MmcDriverBindingStop,
  0xa,
  NULL,
  NULL
};

/**

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

  //
  // Initializes MMC Host pool
  //
  InitializeMmcHostPool ();

  //
  // Install driver model protocol(s).
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gMmcDriverBinding,
           ImageHandle,
           &gMmcComponentName,
           &gMmcComponentName2
           );
  ASSERT_EFI_ERROR (Status);

  // Install driver diagnostics
  Status = gBS->InstallMultipleProtocolInterfaces (
                &ImageHandle,
                &gEfiDriverDiagnostics2ProtocolGuid,&gMmcDriverDiagnostics2,
                NULL
                );
  ASSERT_EFI_ERROR (Status);

  // Use a timer to detect if a card has been plugged in or removed
  Status = gBS->CreateEvent (
                EVT_NOTIFY_SIGNAL | EVT_TIMER,
                TPL_CALLBACK,
                CheckCardsCallback,
                NULL,
                &gCheckCardsEvent);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->SetTimer(
                gCheckCardsEvent,
                TimerPeriodic,
                (UINT64)(10*1000*200)); // 200 ms
  ASSERT_EFI_ERROR (Status);

  return Status;
}
