/** @file
  EFI Driver following Driver Binding Protocol for Winbond Wpcn381U device.

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2010 - 2016 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains 'Framework Code' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may not be 
  modified, except as allowed by additional terms of your license agreement.

@par Specification Reference:
**/

#include "SioDriver.h"


//
// This driver is for ACPI(PNP0A03,0)/PCI(0x1f,0)
//
//
//  Sio Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL mSioDriver = {
  SioDriverSupported,
  SioDriverStart,
  SioDriverStop,
  1,
  NULL,
  NULL
};

//
// The list of the created SIO_DEV
//
LIST_ENTRY                  mSioDevPool = INITIALIZE_LIST_HEAD_VARIABLE (mSioDevPool);

//
// Template structure to create SIO_DEV
//
SIO_DEV                     mSioDevTemplate = {
  SIO_DEV_SIGNATURE,        // Signature
  NULL,                     // PciHandle
  {
    0x00000000,             // HID
    0x00000000              // UID
  },                 
  NULL,                     // Handle
  {                         // Sio Instance
    SioRegisterAccess,
    SioGetResources,
    SioSetResources,
    SioPossibleResources,
    SioModify
  },
  NULL,                     // DevicePath
  {
    NULL,                   // ForwardLink
    NULL,                   // BackLink
  }
};

//
// Template ACPI_HID_DEVICE_PATH structure to create device path
//
ACPI_HID_DEVICE_PATH        mAcpiNodeTemplate = {
  {
    ACPI_DEVICE_PATH,       // Type
    ACPI_DP,                // SubType
    {
      sizeof (ACPI_HID_DEVICE_PATH),  // Length[0]
      0                               // Length[1]
    }
  },
  0x00000000,               // HID
  0x00000000                // UID
};


/**
  The user Entry Point for module Lpc47m17x. The user code starts with this function.

  @param[in]  ImageHandle    The firmware allocated handle for the EFI image.
  @param[in]  SystemTable    A pointer to the EFI System Table.
  
  @retval     EFI_SUCCESS    The entry point is executed successfully.
  @retval     other          Some error occurs when executing this entry point.
**/
EFI_STATUS
EFIAPI
SioDriverEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  if (EFI_ERROR (SioInit())) {
    return EFI_UNSUPPORTED;
  } else {

    //
    // Install protocols
    //
    return EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &mSioDriver,
             ImageHandle,
             &mSioComponentName,
             &mSioComponentName2
             );
  }
}


/**
  Test to see if this driver supports Controller Handle.

  @param[in]  This                Protocol instance pointer.
  @param[in]  Controller          Handle of device to test
  @param[in]  RemainingDevicePath Optional parameter use to pick a specific child
                                  device to start.

  @retval     EFI_SUCCESS         This driver supports this device
  @retval     EFI_ALREADY_STARTED This driver is already running on this device
  @retval     other               This driver does not support this device
**/
EFI_STATUS
EFIAPI
SioDriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                Status;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath;
  ACPI_HID_DEVICE_PATH      *AcpiNode;
  PCI_TYPE00                Pci;
  UINTN                     Index;
  EFI_SIO_ACPI_DEVICE_ID    *Devices;
  UINTN                     Count;
  UINTN                     SegmentNumber;
  UINTN                     BusNumber;
  UINTN                     DeviceNumber;
  UINTN                     FunctionNumber;
  
  //
  // If RemainingDevicePath is not NULL, it should verify that the first device
  // path node in RemainingDevicePath is an ACPI Device path node which is a 
  // legal Device Path Node for this bus driver's children.
  //
  if (RemainingDevicePath != NULL) {
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      if ((RemainingDevicePath->Type != ACPI_DEVICE_PATH) ||
          (((RemainingDevicePath->SubType != ACPI_DP) || (DevicePathNodeLength (RemainingDevicePath) != sizeof (ACPI_HID_DEVICE_PATH))) &&
          ((RemainingDevicePath->SubType != ACPI_EXTENDED_DP) || (DevicePathNodeLength (RemainingDevicePath) != sizeof (ACPI_EXTENDED_HID_DEVICE_PATH))))
          ) {
        return EFI_UNSUPPORTED;
      }

      DeviceGetList (&Devices, &Count);
      if (Devices == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }
      AcpiNode = (ACPI_HID_DEVICE_PATH *) RemainingDevicePath;
      for (Index = 0; Index < Count; Index++) {
        if ((AcpiNode->HID == Devices[Index].HID) &&
            (AcpiNode->UID == Devices[Index].UID)) {
          break;
        }
      }
      FreePool (Devices);
      if (Index == Count) {
        return EFI_UNSUPPORTED;
      }
    }
  }

  //
  // See if the parent device path can be opened BY_DRIVER
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  gBS->CloseProtocol (
         Controller,
         &gEfiDevicePathProtocolGuid,
         This->DriverBindingHandle,
         Controller
         );

  //
  // Get PciIo protocol instance
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (!EFI_ERROR (Status)) {
    Status = PciIo->Pci.Read (
                          PciIo,
                          EfiPciIoWidthUint32,
                          0,
                          sizeof (Pci) / sizeof (UINT32),
                          &Pci
                          );
    ASSERT_EFI_ERROR (Status);

    Status = EFI_UNSUPPORTED;
    if ((Pci.Hdr.Command & (EFI_PCI_COMMAND_IO_SPACE | EFI_PCI_COMMAND_MEMORY_SPACE))
                        == (EFI_PCI_COMMAND_IO_SPACE | EFI_PCI_COMMAND_MEMORY_SPACE)
       ) {
      if (Pci.Hdr.ClassCode[2] == PCI_CLASS_BRIDGE) {
        //
        // See if this is a standard PCI to ISA Bridge from the Base Code and Class Code
        //
        if (Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA) {
          Status = EFI_SUCCESS;
        }

        //
        // See if this is an Intel PCI to ISA Bridge in Positive Decode Mode
        //
        if ((Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA_PDECODE) &&
            (Pci.Hdr.VendorId == 0x8086)) {
          //
          // See if this is on Function #0 to avoid false positive on
          // PCI_CLASS_BRIDGE_OTHER that has the same value as
          // PCI_CLASS_BRIDGE_ISA_PDECODE
          //
          Status = PciIo->GetLocation (
                            PciIo,
                            &SegmentNumber,
                            &BusNumber,
                            &DeviceNumber,
                            &FunctionNumber
                            );
          if (!EFI_ERROR (Status) && (FunctionNumber == 0)) {
            Status = EFI_SUCCESS;
          } else {
            Status = EFI_UNSUPPORTED;
          }
        }
      }
    }

    gBS->CloseProtocol (
           Controller,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
  }
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  return EFI_SUCCESS;
}


/**
  Destroy the SIO controller handle.

  @param[in]  ChildHandle     The SIO controller handle.

  @retval     EFI_SUCCESS     The SIO controller handle is destroyed successfully.
**/
EFI_STATUS
SioDestroyDevice (
  IN EFI_HANDLE                ChildHandle
  )
{
  EFI_STATUS                Status;
  SIO_DEV                   *SioDev;
  EFI_SIO_PROTOCOL          *Sio;
  EFI_PCI_IO_PROTOCOL       *PciIo;

  Status = gBS->HandleProtocol (
                  ChildHandle,
                  &gEfiSioProtocolGuid,
                  (VOID **) &Sio
                  );
  ASSERT_EFI_ERROR (Status);

  SioDev = SIO_DEV_FROM_THIS (Sio);
  
  Status = gBS->CloseProtocol (
                  SioDev->PciHandle,
                  &gEfiPciIoProtocolGuid,
                  mSioDriver.DriverBindingHandle,
                  ChildHandle
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->UninstallMultipleProtocolInterfaces (
                  ChildHandle,
                  &gEfiDevicePathProtocolGuid,
                  SioDev->DevicePath,
                  &gEfiSioProtocolGuid,
                  &SioDev->Sio,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    gBS->OpenProtocol (
           SioDev->PciHandle,
           &gEfiPciIoProtocolGuid,
           (VOID **) &PciIo,
           mSioDriver.DriverBindingHandle,
           ChildHandle,
           EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
           );
    return Status;
  }

  RemoveEntryList (&SioDev->Link);
  FreePool (SioDev->DevicePath);
  FreePool (SioDev);
  return EFI_SUCCESS;
}


/**
  Create the SIO controller handle.

  @param[in]  Controller       The parent PCI controller handle.
  @param[in]  Device           Pointer to EFI_SIO_ACPI_DEVICE_ID.
  @param[in]  ParentDevicePath The device path of the parent controller.
  @param[out] PciIo            The PciIo instance of the parent controller.
**/
VOID
SioCreateDevice (
  IN  EFI_HANDLE                Controller,
  IN  EFI_SIO_ACPI_DEVICE_ID    *Device,
  IN  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath,
  OUT EFI_PCI_IO_PROTOCOL       *PciIo
  )
{
  EFI_STATUS                Status;
  SIO_DEV                   *SioDev;

  DeviceEnable (Device);

  SioDev = AllocateCopyPool (sizeof (SIO_DEV), &mSioDevTemplate);
  ASSERT (SioDev != NULL);
  if (SioDev == NULL) {
    return;
  }
  InsertHeadList (&mSioDevPool, &SioDev->Link);

  SioDev->PciHandle       = Controller;

  CopyMem (&SioDev->Device, Device, sizeof (*Device));

  mAcpiNodeTemplate.HID = Device->HID;
  mAcpiNodeTemplate.UID = Device->UID;
  SioDev->DevicePath = AppendDevicePathNode (ParentDevicePath, (EFI_DEVICE_PATH_PROTOCOL *) &mAcpiNodeTemplate);
  ASSERT (SioDev->DevicePath != NULL);

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &SioDev->Handle,
                  &gEfiSioProtocolGuid,        &SioDev->Sio,
                  &gEfiDevicePathProtocolGuid, SioDev->DevicePath,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  mSioDriver.DriverBindingHandle,
                  SioDev->Handle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
  ASSERT_EFI_ERROR (Status);
}


/**
  Start this driver on ControllerHandle.

  @param[in]  This                 Protocol instance pointer.
  @param[in]  Controller           Handle of device to bind driver to
  @param[in]  RemainingDevicePath  Optional parameter use to pick a specific child
                                   device to start.

  @retval     EFI_SUCCESS          This driver is added to ControllerHandle
  @retval     EFI_ALREADY_STARTED  This driver is already running on ControllerHandle
  @retval     other                This driver does not support this device
**/
EFI_STATUS
EFIAPI
SioDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                          Status;
  EFI_PCI_IO_PROTOCOL                 *PciIo;
  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL            *ParentDevicePath;
  EFI_SIO_ACPI_DEVICE_ID              *Devices;
  SIO_DEV                             *SioDev;
  UINTN                               Count;
  UINTN                               Index;
  ACPI_HID_DEVICE_PATH                *AcpiNode;
  BOOLEAN                             *HasCreated;
  BOOLEAN                             *RequestCreate;
  LIST_ENTRY                          *Node;  

  HasCreated    = NULL;
  RequestCreate = NULL;
  //
  // Get the ISA bridge's Device Path
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    return Status;
  }

  //
  // Get Pci IO
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    gBS->CloseProtocol (
           Controller,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    return Status;
  }

  if ((RemainingDevicePath != NULL) && IsDevicePathEnd (RemainingDevicePath)) {
    return EFI_SUCCESS;
  }

  DeviceGetList (&Devices, &Count);
  if (Devices == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit_Start;
  }
  HasCreated    = AllocatePool (sizeof (BOOLEAN) * Count);
  ASSERT (HasCreated != NULL);
  if (HasCreated == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit_Start;
  }
  RequestCreate = AllocatePool (sizeof (BOOLEAN) * Count);
  ASSERT (RequestCreate != NULL);
  if (RequestCreate == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit_Start;
  }

  //
  // Assume no children has been created.
  // Assume the SIO interface hasn't been initialized.
  //
  ZeroMem (HasCreated, sizeof (BOOLEAN) * Count);

  if (Status == EFI_ALREADY_STARTED) {
    for (Node = GetFirstNode (&mSioDevPool);
         !IsNull (&mSioDevPool, Node); 
         Node = GetNextNode (&mSioDevPool, Node)
        ) {
      SioDev = CR (Node, SIO_DEV, Link, SIO_DEV_SIGNATURE);
      Status = gBS->HandleProtocol (
                      SioDev->PciHandle,
                      &gEfiDevicePathProtocolGuid,
                      (VOID **) &DevicePath
                      );
      ASSERT_EFI_ERROR (Status);

      //
      // See if they are under the same PCI to ISA Bridge
      //
      if (CompareMem (DevicePath, ParentDevicePath, GetDevicePathSize (DevicePath)) == 0) {
        for (Index = 0; Index < Count; Index++) {
          if (CompareMem (&SioDev->Device, &Devices[Index], sizeof (EFI_SIO_ACPI_DEVICE_ID)) == 0) {
            HasCreated[Index] = TRUE;
            break;
          }
        }
      }
    }
  }

  AcpiNode = (ACPI_HID_DEVICE_PATH *) RemainingDevicePath;
  for (Index = 0; Index < Count; Index++) {
    if ((AcpiNode == NULL) ||
        ((AcpiNode->HID == Devices[Index].HID) && (AcpiNode->UID == Devices[Index].UID))
       ) {
      RequestCreate[Index] = TRUE;
    } else {
      RequestCreate[Index] = FALSE;
    }
  }

  for (Index = 0; Index < Count; Index++) {
    if (RequestCreate[Index] && !HasCreated[Index]) {
      SioCreateDevice (Controller, &Devices[Index], ParentDevicePath, PciIo);
    }
  }
Exit_Start:
  if (Devices != NULL) {
    FreePool (Devices);
  }
  if (HasCreated != NULL) {
    FreePool (HasCreated);
  }
  if (RequestCreate != NULL) {
    FreePool (RequestCreate);
  }

  return Status;
}


/**
  Stop this driver on ControllerHandle.

  @param[in]  This              Protocol instance pointer.
  @param[in]  Controller        Handle of device to stop driver on
  @param[in]  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                                children is zero stop the entire bus driver.
  @param[in]  ChildHandleBuffer List of Child Handles to Stop.

  @retval     EFI_SUCCESS       This driver is removed ControllerHandle
  @retval     other             This driver was not removed from this device
**/
EFI_STATUS
EFIAPI
SioDriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     Controller,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  )
{
  EFI_STATUS  Status;
  UINTN       Index;
  BOOLEAN     AllChildrenStopped;

  if (NumberOfChildren == 0) {
    gBS->CloseProtocol (
           Controller,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    gBS->CloseProtocol (
           Controller,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    return EFI_SUCCESS;
  }

  AllChildrenStopped = TRUE;
  for (Index = 0; Index < NumberOfChildren; Index++) {
    Status = SioDestroyDevice (ChildHandleBuffer[Index]);
    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (AllChildrenStopped) {
    return EFI_SUCCESS;
  } else {
    return EFI_DEVICE_ERROR;
  }
}
