/** @file
  Main file for Ps2 keyboard controller library.

  Copyright (c) 2019 - 2020, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "BdsPs2KbcLib.h"

GLOBAL_REMOVE_IF_UNREFERENCED PLATFORM_KEYBOARD_DEVICE_PATH  gKeyboardDevicePath = {
  gPciRootBridge,
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_PCI_DP,
      {
        (UINT8) (sizeof (PCI_DEVICE_PATH)),
        (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
      }
    },
    0, // Function, patched in EnumPs2Keyboard
    0  // Device, patched in EnumPs2Keyboard
  },
  {
    {
      ACPI_DEVICE_PATH,
      ACPI_DP,
      {
        (UINT8) (sizeof (ACPI_HID_DEVICE_PATH)),
        (UINT8) ((sizeof (ACPI_HID_DEVICE_PATH)) >> 8)
      }
    },
    EISA_PNP_ID(0x0303),
    0
  },
  gEndEntire
};

/**
  Check if PS2 keyboard is conntected, by sending ECHO command.
  @retval                       TRUE if connected FALSE otherwise
**/
BOOLEAN
DetectPs2Keyboard (
  VOID
  )
{
  UINT32                TimeOut;
  UINT8                 Data;
  UINT32                SumTimeOut;
  BOOLEAN               FoundPs2Kbc;

  TimeOut     = 0;
  FoundPs2Kbc       = FALSE;

  //
  // Wait for input buffer empty
  //
  for (TimeOut = 0; TimeOut < PS2_KEYBOARD_TIMEOUT; TimeOut += 30) {
    if ((IoRead8 (KEYBOARD_8042_STATUS_REGISTER) & 0x02) == 0) {
      FoundPs2Kbc = TRUE;
      break;
    }
    MicroSecondDelay (30);
  }

  if (!FoundPs2Kbc) {
    return FALSE;
  }

  //
  // Send echo command
  //
  IoWrite8 (KEYBOARD_8042_DATA_REGISTER, KBC_INPBUF_VIA60_KBECHO);

  //
  // Init variables
  //
  FoundPs2Kbc       = FALSE;
  TimeOut     = 0;
  SumTimeOut  = 0;
  Data = 0;

  //
  // Read from 8042 (multiple times if needed)
  // until the expected value appears
  // use SumTimeOut to control the iteration
  //
  while (1) {
    //
    // Perform a read
    //
    for (TimeOut = 0; TimeOut < PS2_KEYBOARD_TIMEOUT; TimeOut += 30) {
      if (IoRead8 (KEYBOARD_8042_STATUS_REGISTER) & 0x01) {
        Data = IoRead8 (KEYBOARD_8042_DATA_REGISTER);
        break;
      }
      MicroSecondDelay (30);
    }

    SumTimeOut += TimeOut;

    if (Data == KBC_INPBUF_VIA60_KBECHO) {
      FoundPs2Kbc = TRUE;
      break;
    }

    if (SumTimeOut >= PS2_KEYBOARD_WAITFORVALUE_TIMEOUT) {
      break;
    }
  }
  return FoundPs2Kbc;
}

/**
  Check if PS2 keyboard is conntected. If the result of first time is
  error, it will retry again.
  @retval                       TRUE if connected FALSE otherwise
**/
BOOLEAN
IsPs2KeyboardConnected (
  VOID
  )
{
  BOOLEAN Result;
  Result = DetectPs2Keyboard ();

  if (!Result) {
    //
    // If there is no ps2 keyboard detected for the 1st time, retry again.
    //
    Result = DetectPs2Keyboard ();
  }
  return Result;
}


/**
  Updates the ConIn variable with Ps2 Keyboard device path,
  if it doesn't already exists in ConIn and ConInDev.
**/
VOID
AddPs2Keyboard (
  VOID
  )
{
  SIO_PCI_ISA_BRIDGE_DEVICE_INFO *SioIsaInfo;

  DEBUG ((DEBUG_INFO, "[AddPs2Keyboard]\n"));

  SioIsaInfo = (SIO_PCI_ISA_BRIDGE_DEVICE_INFO*) FixedPcdGetPtr (PcdSuperIoPciIsaBridgeDevice);

  //
  // patch IsaBridge device and and function
  //
  gKeyboardDevicePath.IsaBridge.Device = SioIsaInfo->Device;
  gKeyboardDevicePath.IsaBridge.Function = SioIsaInfo->Funtion;

  //
  // Append Ps2 Keyboard into "ConIn"
  //
  EfiBootManagerUpdateConsoleVariable (ConIn, (EFI_DEVICE_PATH_PROTOCOL *) &gKeyboardDevicePath, NULL);

  //
  // Append Ps2 Keyboard into "ConInDev"
  //
  EfiBootManagerUpdateConsoleVariable (ConInDev, (EFI_DEVICE_PATH_PROTOCOL *) &gKeyboardDevicePath, NULL);
}


/**
  Constructor for the Ps2 keyboard controller library.

  @param ImageHandle    the image handle of the process
  @param SystemTable    the EFI System Table pointer

  @retval EFI_SUCCESS        the shell command handlers were installed sucessfully
  @retval EFI_UNSUPPORTED    the shell level required was not found.
**/
EFI_STATUS
EFIAPI
BdsPs2KbcLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  UINT8                               Ps2KbMsEnable;

  Ps2KbMsEnable = PcdGet8 (PcdPs2KbMsEnable);

  if (Ps2KbMsEnable == 0x1
    && IsPs2KeyboardConnected())
  {
      // add ps2 device path to ConIn and ConInDev
      AddPs2Keyboard ();
  }

  return EFI_SUCCESS;
}
