/** @file

  The UHCI register operation routines.

Copyright (c) 2007 - 2010, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "Uhci.h"


/**
  Map address of request structure buffer.

  @param  Uhc                The UHCI device.
  @param  Request            The user request buffer.
  @param  MappedAddr         Mapped address of request.
  @param  Map                Identificaion of this mapping to return.

  @return EFI_SUCCESS        Success.
  @return EFI_DEVICE_ERROR   Fail to map the user request.

**/
EFI_STATUS
UhciMapUserRequest (
  IN  USB_HC_DEV          *Uhc,
  IN  OUT VOID            *Request,
  OUT UINT8               **MappedAddr,
  OUT VOID                **Map
  )
{
  EFI_STATUS            Status;
  UINTN                 Len;
  EFI_PHYSICAL_ADDRESS  PhyAddr;

  Len    = sizeof (EFI_USB_DEVICE_REQUEST);
  Status = Uhc->PciIo->Map (
                         Uhc->PciIo,
                         EfiPciIoOperationBusMasterRead,
                         Request,
                         &Len,
                         &PhyAddr,
                         Map
                         );

  if (!EFI_ERROR (Status)) {
    *MappedAddr = (UINT8 *) (UINTN) PhyAddr;
  }

  return Status;
}


/**
  Map address of user data buffer.

  @param  Uhc                The UHCI device.
  @param  Direction          Direction of the data transfer.
  @param  Data               The user data buffer.
  @param  Len                Length of the user data.
  @param  PktId              Packet identificaion.
  @param  MappedAddr         Mapped address to return.
  @param  Map                Identificaion of this mapping to return.

  @return EFI_SUCCESS        Success.
  @return EFI_DEVICE_ERROR   Fail to map the user data.

**/
EFI_STATUS
UhciMapUserData (
  IN  USB_HC_DEV              *Uhc,
  IN  EFI_USB_DATA_DIRECTION  Direction,
  IN  VOID                    *Data,
  IN  OUT UINTN               *Len,
  OUT UINT8                   *PktId,
  OUT UINT8                   **MappedAddr,
  OUT VOID                    **Map
  )
{
  EFI_STATUS            Status;
  EFI_PHYSICAL_ADDRESS  PhyAddr;

  Status = EFI_SUCCESS;

  switch (Direction) {
  case EfiUsbDataIn:
    //
    // BusMasterWrite means cpu read
    //
    *PktId = INPUT_PACKET_ID;
    Status = Uhc->PciIo->Map (
                           Uhc->PciIo,
                           EfiPciIoOperationBusMasterWrite,
                           Data,
                           Len,
                           &PhyAddr,
                           Map
                           );

    if (EFI_ERROR (Status)) {
      goto EXIT;
    }

    *MappedAddr = (UINT8 *) (UINTN) PhyAddr;
    break;

  case EfiUsbDataOut:
    *PktId = OUTPUT_PACKET_ID;
    Status = Uhc->PciIo->Map (
                           Uhc->PciIo,
                           EfiPciIoOperationBusMasterRead,
                           Data,
                           Len,
                           &PhyAddr,
                           Map
                           );

    if (EFI_ERROR (Status)) {
      goto EXIT;
    }

    *MappedAddr = (UINT8 *) (UINTN) PhyAddr;
    break;

  case EfiUsbNoData:
    if ((Len != NULL) && (*Len != 0)) {
      Status    = EFI_INVALID_PARAMETER;
      goto EXIT;
    }

    *PktId      = OUTPUT_PACKET_ID;
    *MappedAddr = NULL;
    *Map        = NULL;
    break;

  default:
    Status      = EFI_INVALID_PARAMETER;
  }

EXIT:
  return Status;
}


/**
  Link the TD To QH.

  @param  Uhc         The UHCI device.
  @param  Qh          The queue head for the TD to link to.
  @param  Td          The TD to link.

**/
VOID
UhciLinkTdToQh (
  IN USB_HC_DEV           *Uhc,
  IN UHCI_QH_SW           *Qh,
  IN UHCI_TD_SW           *Td
  )
{
  EFI_PHYSICAL_ADDRESS  PhyAddr;

  PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Td, sizeof (UHCI_TD_HW));

  ASSERT ((Qh != NULL) && (Td != NULL));

  Qh->QhHw.VerticalLink = QH_VLINK (PhyAddr, FALSE);
  Qh->TDs               = (VOID *) Td;
}


/**
  Unlink TD from the QH.

  @param  Qh          The queue head to unlink from.
  @param  Td          The TD to unlink.

**/
VOID
UhciUnlinkTdFromQh (
  IN UHCI_QH_SW           *Qh,
  IN UHCI_TD_SW           *Td
  )
{
  ASSERT ((Qh != NULL) && (Td != NULL));

  Qh->QhHw.VerticalLink = QH_VLINK (NULL, TRUE);
  Qh->TDs               = NULL;
}


/**
  Append a new TD To the previous TD.

  @param  Uhc         The UHCI device.
  @param  PrevTd      Previous UHCI_TD_SW to be linked to.
  @param  ThisTd      TD to link.

**/
VOID
UhciAppendTd (
  IN USB_HC_DEV     *Uhc,
  IN UHCI_TD_SW     *PrevTd,
  IN UHCI_TD_SW     *ThisTd
  )
{
  EFI_PHYSICAL_ADDRESS  PhyAddr;

  PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, ThisTd, sizeof (UHCI_TD_HW));

  ASSERT ((PrevTd != NULL) && (ThisTd != NULL));

  PrevTd->TdHw.NextLink = TD_LINK (PhyAddr, TRUE, FALSE);
  PrevTd->NextTd        = (VOID *) ThisTd;
}


/**
  Delete a list of TDs.

  @param  Uhc         The UHCI device.
  @param  FirstTd     TD link list head.

  @return None.

**/
VOID
UhciDestoryTds (
  IN USB_HC_DEV           *Uhc,
  IN UHCI_TD_SW           *FirstTd
  )
{
  UHCI_TD_SW            *NextTd;
  UHCI_TD_SW            *ThisTd;

  NextTd = FirstTd;

  while (NextTd != NULL) {
    ThisTd  = NextTd;
    NextTd  = ThisTd->NextTd;
    UsbHcFreeMem (Uhc->MemPool, ThisTd, sizeof (UHCI_TD_SW));
  }
}


/**
  Create an initialize a new queue head.

  @param  Uhc         The UHCI device.
  @param  Interval    The polling interval for the queue.

  @return The newly created queue header.

**/
UHCI_QH_SW *
UhciCreateQh (
  IN  USB_HC_DEV        *Uhc,
  IN  UINTN             Interval
  )
{
  UHCI_QH_SW            *Qh;

  Qh = UsbHcAllocateMem (Uhc->MemPool, sizeof (UHCI_QH_SW));

  if (Qh == NULL) {
    return NULL;
  }

  Qh->QhHw.HorizonLink  = QH_HLINK (NULL, TRUE);
  Qh->QhHw.VerticalLink = QH_VLINK (NULL, TRUE);
  Qh->Interval          = UhciConvertPollRate(Interval);
  Qh->TDs               = NULL;
  Qh->NextQh            = NULL;

  return Qh;
}


/**
  Create and intialize a TD.

  @param  Uhc         The UHCI device.

  @return The newly allocated and initialized TD.

**/
UHCI_TD_SW *
UhciCreateTd (
  IN  USB_HC_DEV          *Uhc
  )
{
  UHCI_TD_SW              *Td;

  Td     = UsbHcAllocateMem (Uhc->MemPool, sizeof (UHCI_TD_SW));
  if (Td == NULL) {
    return NULL;
  }

  Td->TdHw.NextLink = TD_LINK (NULL, FALSE, TRUE);
  Td->NextTd        = NULL;
  Td->Data          = NULL;
  Td->DataLen       = 0;

  return Td;
}


/**
  Create and initialize a TD for Setup Stage of a control transfer.

  @param  Uhc         The UHCI device.
  @param  DevAddr     Device address.
  @param  Request     A pointer to cpu memory address of Device request.
  @param  RequestPhy  A pointer to pci memory address of Device request.
  @param  IsLow       Full speed or low speed.

  @return The created setup Td Pointer.

**/
UHCI_TD_SW *
UhciCreateSetupTd (
  IN  USB_HC_DEV          *Uhc,
  IN  UINT8               DevAddr,
  IN  UINT8               *Request,
  IN  UINT8               *RequestPhy,
  IN  BOOLEAN             IsLow
  )
{
  UHCI_TD_SW              *Td;

  Td = UhciCreateTd (Uhc);

  if (Td == NULL) {
    return NULL;
  }

  Td->TdHw.NextLink     = TD_LINK (NULL, TRUE, TRUE);
  Td->TdHw.ShortPacket  = FALSE;
  Td->TdHw.IsIsoch      = FALSE;
  Td->TdHw.IntOnCpl     = FALSE;
  Td->TdHw.ErrorCount   = 0x03;
  Td->TdHw.Status      |= USBTD_ACTIVE;
  Td->TdHw.DataToggle   = 0;
  Td->TdHw.EndPoint     = 0;
  Td->TdHw.LowSpeed     = IsLow ? 1 : 0;
  Td->TdHw.DeviceAddr   = DevAddr & 0x7F;
  Td->TdHw.MaxPacketLen = (UINT32) (sizeof (EFI_USB_DEVICE_REQUEST) - 1);
  Td->TdHw.PidCode      = SETUP_PACKET_ID;
  Td->TdHw.DataBuffer   = (UINT32) (UINTN) RequestPhy;

  Td->Data              = Request;
  Td->DataLen           = (UINT16) sizeof (EFI_USB_DEVICE_REQUEST);

  return Td;
}


/**
  Create a TD for data.

  @param  Uhc         The UHCI device.
  @param  DevAddr     Device address.
  @param  Endpoint    Endpoint number.
  @param  DataPtr     A pointer to cpu memory address of Data buffer.
  @param  DataPhyPtr  A pointer to pci memory address of Data buffer.
  @param  Len         Data length.
  @param  PktId       Packet ID.
  @param  Toggle      Data toggle value.
  @param  IsLow       Full speed or low speed.

  @return Data Td pointer if success, otherwise NULL.

**/
UHCI_TD_SW *
UhciCreateDataTd (
  IN  USB_HC_DEV          *Uhc,
  IN  UINT8               DevAddr,
  IN  UINT8               Endpoint,
  IN  UINT8               *DataPtr,
  IN  UINT8               *DataPhyPtr,
  IN  UINTN               Len,
  IN  UINT8               PktId,
  IN  UINT8               Toggle,
  IN  BOOLEAN             IsLow
  )
{
  UHCI_TD_SW  *Td;

  //
  // Code as length - 1, and the max valid length is 0x500
  //
  ASSERT (Len <= 0x500);

  Td  = UhciCreateTd (Uhc);

  if (Td == NULL) {
    return NULL;
  }

  Td->TdHw.NextLink     = TD_LINK (NULL, TRUE, TRUE);
  Td->TdHw.ShortPacket  = FALSE;
  Td->TdHw.IsIsoch      = FALSE;
  Td->TdHw.IntOnCpl     = FALSE;
  Td->TdHw.ErrorCount   = 0x03;
  Td->TdHw.Status       = USBTD_ACTIVE;
  Td->TdHw.LowSpeed     = IsLow ? 1 : 0;
  Td->TdHw.DataToggle   = Toggle & 0x01;
  Td->TdHw.EndPoint     = Endpoint & 0x0F;
  Td->TdHw.DeviceAddr   = DevAddr & 0x7F;
  Td->TdHw.MaxPacketLen = (UINT32) (Len - 1);
  Td->TdHw.PidCode      = (UINT8) PktId;
  Td->TdHw.DataBuffer   = (UINT32) (UINTN) DataPhyPtr;

  Td->Data              = DataPtr;
  Td->DataLen           = (UINT16) Len;

  return Td;
}


/**
  Create TD for the Status Stage of control transfer.

  @param  Uhc         The UHCI device.
  @param  DevAddr     Device address.
  @param  PktId       Packet ID.
  @param  IsLow       Full speed or low speed.

  @return Status Td Pointer.

**/
UHCI_TD_SW *
UhciCreateStatusTd (
  IN  USB_HC_DEV          *Uhc,
  IN  UINT8               DevAddr,
  IN  UINT8               PktId,
  IN  BOOLEAN             IsLow
  )
{
  UHCI_TD_SW              *Td;

  Td = UhciCreateTd (Uhc);

  if (Td == NULL) {
    return NULL;
  }

  Td->TdHw.NextLink     = TD_LINK (NULL, TRUE, TRUE);
  Td->TdHw.ShortPacket  = FALSE;
  Td->TdHw.IsIsoch      = FALSE;
  Td->TdHw.IntOnCpl     = FALSE;
  Td->TdHw.ErrorCount   = 0x03;
  Td->TdHw.Status      |= USBTD_ACTIVE;
  Td->TdHw.MaxPacketLen = 0x7FF;      //0x7FF: there is no data (refer to UHCI spec)
  Td->TdHw.DataToggle   = 1;
  Td->TdHw.EndPoint     = 0;
  Td->TdHw.LowSpeed     = IsLow ? 1 : 0;
  Td->TdHw.DeviceAddr   = DevAddr & 0x7F;
  Td->TdHw.PidCode      = (UINT8) PktId;
  Td->TdHw.DataBuffer   = (UINT32) (UINTN) NULL;

  Td->Data              = NULL;
  Td->DataLen           = 0;

  return Td;
}


/**
  Create Tds list for Control Transfer.

  @param  Uhc         The UHCI device.
  @param  DeviceAddr  The device address.
  @param  DataPktId   Packet Identification of Data Tds.
  @param  Request     A pointer to cpu memory address of request structure buffer to transfer.
  @param  RequestPhy  A pointer to pci memory address of request structure buffer to transfer.
  @param  Data        A pointer to cpu memory address of user data buffer to transfer.
  @param  DataPhy     A pointer to pci memory address of user data buffer to transfer.
  @param  DataLen     Length of user data to transfer.
  @param  MaxPacket   Maximum packet size for control transfer.
  @param  IsLow       Full speed or low speed.

  @return The Td list head for the control transfer.

**/
UHCI_TD_SW *
UhciCreateCtrlTds (
  IN USB_HC_DEV           *Uhc,
  IN UINT8                DeviceAddr,
  IN UINT8                DataPktId,
  IN UINT8                *Request,
  IN UINT8                *RequestPhy,
  IN UINT8                *Data,
  IN UINT8                *DataPhy,
  IN UINTN                DataLen,
  IN UINT8                MaxPacket,
  IN BOOLEAN              IsLow
  )
{
  UHCI_TD_SW                *SetupTd;
  UHCI_TD_SW                *FirstDataTd;
  UHCI_TD_SW                *DataTd;
  UHCI_TD_SW                *PrevDataTd;
  UHCI_TD_SW                *StatusTd;
  UINT8                     DataToggle;
  UINT8                     StatusPktId;
  UINTN                     ThisTdLen;


  DataTd      = NULL;
  SetupTd     = NULL;
  FirstDataTd = NULL;
  PrevDataTd  = NULL;
  StatusTd    = NULL;

  //
  // Create setup packets for the transfer
  //
  SetupTd = UhciCreateSetupTd (Uhc, DeviceAddr, Request, RequestPhy, IsLow);

  if (SetupTd == NULL) {
    return NULL;
  }

  //
  // Create data packets for the transfer
  //
  DataToggle = 1;

  while (DataLen > 0) {
    //
    // PktSize is the data load size in each Td.
    //
    ThisTdLen = (DataLen > MaxPacket ? MaxPacket : DataLen);

    DataTd = UhciCreateDataTd (
               Uhc,
               DeviceAddr,
               0,
               Data,  //cpu memory address
               DataPhy, //Pci memory address
               ThisTdLen,
               DataPktId,
               DataToggle,
               IsLow
               );

    if (DataTd == NULL) {
      goto FREE_TD;
    }

    if (FirstDataTd == NULL) {
      FirstDataTd         = DataTd;
      FirstDataTd->NextTd = NULL;
    } else {
      UhciAppendTd (Uhc, PrevDataTd, DataTd);
    }

    DataToggle ^= 1;
    PrevDataTd = DataTd;
    Data += ThisTdLen;
    DataPhy += ThisTdLen;
    DataLen -= ThisTdLen;
  }

  //
  // Status packet is on the opposite direction to data packets
  //
  if (OUTPUT_PACKET_ID == DataPktId) {
    StatusPktId = INPUT_PACKET_ID;
  } else {
    StatusPktId = OUTPUT_PACKET_ID;
  }

  StatusTd = UhciCreateStatusTd (Uhc, DeviceAddr, StatusPktId, IsLow);

  if (StatusTd == NULL) {
    goto FREE_TD;
  }

  //
  // Link setup Td -> data Tds -> status Td together
  //
  if (FirstDataTd != NULL) {
    UhciAppendTd (Uhc, SetupTd, FirstDataTd);
    UhciAppendTd (Uhc, PrevDataTd, StatusTd);
  } else {
    UhciAppendTd (Uhc, SetupTd, StatusTd);
  }

  return SetupTd;

FREE_TD:
  if (SetupTd != NULL) {
    UhciDestoryTds (Uhc, SetupTd);
  }

  if (FirstDataTd != NULL) {
    UhciDestoryTds (Uhc, FirstDataTd);
  }

  return NULL;
}


/**
  Create Tds list for Bulk/Interrupt Transfer.

  @param  Uhc         USB_HC_DEV.
  @param  DevAddr     Address of Device.
  @param  EndPoint    Endpoint Number.
  @param  PktId       Packet Identification of Data Tds.
  @param  Data        A pointer to cpu memory address of user data buffer to transfer.
  @param  DataPhy     A pointer to pci memory address of user data buffer to transfer.
  @param  DataLen     Length of user data to transfer.
  @param  DataToggle  Data Toggle Pointer.
  @param  MaxPacket   Maximum packet size for Bulk/Interrupt transfer.
  @param  IsLow       Is Low Speed Device.

  @return The Tds list head for the bulk transfer.

**/
UHCI_TD_SW *
UhciCreateBulkOrIntTds (
  IN USB_HC_DEV           *Uhc,
  IN UINT8                DevAddr,
  IN UINT8                EndPoint,
  IN UINT8                PktId,
  IN UINT8                *Data,
  IN UINT8                *DataPhy,
  IN UINTN                DataLen,
  IN OUT UINT8            *DataToggle,
  IN UINT8                MaxPacket,
  IN BOOLEAN              IsLow
  )
{
  UHCI_TD_SW              *DataTd;
  UHCI_TD_SW              *FirstDataTd;
  UHCI_TD_SW              *PrevDataTd;
  UINTN                   ThisTdLen;

  DataTd      = NULL;
  FirstDataTd = NULL;
  PrevDataTd  = NULL;

  //
  // Create data packets for the transfer
  //
  while (DataLen > 0) {
    //
    // PktSize is the data load size that each Td.
    //
    ThisTdLen = DataLen;

    if (DataLen > MaxPacket) {
      ThisTdLen = MaxPacket;
    }

    DataTd = UhciCreateDataTd (
               Uhc,
               DevAddr,
               EndPoint,
               Data,
               DataPhy,
               ThisTdLen,
               PktId,
               *DataToggle,
               IsLow
               );

    if (DataTd == NULL) {
      goto FREE_TD;
    }

    if (PktId == INPUT_PACKET_ID) {
      DataTd->TdHw.ShortPacket = TRUE;
    }

    if (FirstDataTd == NULL) {
      FirstDataTd         = DataTd;
      FirstDataTd->NextTd = NULL;
    } else {
      UhciAppendTd (Uhc, PrevDataTd, DataTd);
    }

    *DataToggle ^= 1;
    PrevDataTd   = DataTd;
    Data        += ThisTdLen;
    DataPhy     += ThisTdLen;
    DataLen     -= ThisTdLen;
  }

  return FirstDataTd;

FREE_TD:
  if (FirstDataTd != NULL) {
    UhciDestoryTds (Uhc, FirstDataTd);
  }

  return NULL;
}
