/**
 * @file UsbTransfer.c
 * @brief Wrapper of UEFI USB bulk and control transfer interface for USB DisplayLink driver.
 *
 * Copyright (c) 2018-2019, DisplayLink (UK) Ltd. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-2-Clause-Patent
 *
**/

#include "UsbDisplayLink.h"

/**
 * Write the data to the DisplayLink device using the USBIO protocol.
 * @param UsbDisplayLinkDev
 * @param Buffer
 * @param DataLen
 * @param USBStatus
 * @return
  * EFI_SUCCESS   The bulk transfer has been successfully executed.
  * EFI_INVALID_PARAMETER   If DeviceEndpoint is not valid.
  * EFI_INVALID_PARAMETER   Data is NULL.
  * EFI_INVALID_PARAMETER   DataLength is NULL.
  * EFI_INVALID_PARAMETER   Status is NULL.
  * EFI_OUT_OF_RESOURCES  The request could not be completed due to a lack of resources.
  * EFI_TIMEOUT   The bulk transfer cannot be completed within Timeout timeframe.
  * EFI_DEVICE_ERROR  The transfer failed other than timeout, and the transfer status is returned in Status.
 */
EFI_STATUS
DlUsbBulkWrite (
    IN USB_DISPLAYLINK_DEV* UsbDisplayLinkDev,
    IN CONST UINT8* Buffer,
    IN UINTN DataLen,
    OUT UINT32 *USBStatus
    )
{
  EFI_STATUS Status;
  Status = UsbDisplayLinkDev->UsbIo->UsbBulkTransfer (
    UsbDisplayLinkDev->UsbIo,
    UsbDisplayLinkDev->BulkOutEndpointDescriptor.EndpointAddress,
    (VOID*)Buffer,
    &DataLen,
    DISPLAYLINK_USB_BULK_TIMEOUT,
    USBStatus);

  return Status;
}

/**
* Read data from the DisplayLink device using the USBIO protocol.
* @param UsbDisplayLinkDev
* @param Buffer
* @param BufferLen
* @return 0 if an error occurred or 0 bytes were read, otherwise the number of bytes read
 */
UINTN
DlUsbBulkRead (
    IN USB_DISPLAYLINK_DEV* UsbDisplayLinkDev,
    IN UINT8* Buffer,
    IN UINTN BufferLen
    )
{
  UINT32 Result;
  UINTN ReadLen;
  EFI_STATUS Status;

  ReadLen = BufferLen;

  Status = UsbDisplayLinkDev->UsbIo->UsbBulkTransfer (
    UsbDisplayLinkDev->UsbIo,
    UsbDisplayLinkDev->BulkInEndpointDescriptor.EndpointAddress,
    Buffer,
    &ReadLen,
    DISPLAYLINK_USB_BULK_TIMEOUT,
    &Result);

  if (EFI_ERROR (Status)) {
    return 0;
  }

  return ReadLen;
}


/**
Send a control message (e.g set video mode) message to the DisplayLink device.

@param  Device                   USB device handle.
@param  request                  Request type, e.g. SET_VIDEO_MODE
@param  value
@param  controlMsg               Pointer to the message to send.
@param  controlMsgLen            Length of the message.

@retval EFI_SUCCESS            Successfully sent message.

**/
EFI_STATUS
DlUsbSendControlWriteMessage (
  IN USB_DISPLAYLINK_DEV *Device,
  IN UINT8 Request,
  IN UINT16 Value,
  IN CONST VOID *ControlMsg,
  IN UINT16 ControlMsgLen
  )
{
  EFI_STATUS             Status;
  UINT32                 UsbStatus;
  EFI_USB_DEVICE_REQUEST UsbRequest;

  ZeroMem (&UsbRequest, sizeof (UsbRequest));
  UsbRequest.RequestType = USB_REQ_TYPE_VENDOR | USB_TARGET_INTERFACE;
  UsbRequest.Index = Device->InterfaceDescriptor.InterfaceNumber;
  UsbRequest.Request = Request;
  UsbRequest.Value = Value;
  UsbRequest.Length = ControlMsgLen;

  Status = Device->UsbIo->UsbControlTransfer (
    Device->UsbIo,
    &UsbRequest,
    EfiUsbDataOut,
    DISPLAYLINK_USB_CTRL_TIMEOUT,
    (VOID *)ControlMsg,
    ControlMsgLen,
    &UsbStatus);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "USB write control transfer failed - %r (USB status x%x).\n", Status, UsbStatus));
    Status = EFI_DEVICE_ERROR;
  }
  return Status;
}


/**
Request data from the DisplayLink device (e.g. the monitor EDID)

@param  Device                   USB device handle.
@param  request                  Request type, e.g. GET_OUTPUT_EDID
@param  value
@param  controlMsg               Pointer to the message to send.
@param  controlMsgLen            Length of the message.

@retval EFI_SUCCESS            Successfully sent message.

**/
EFI_STATUS
DlUsbSendControlReadMessage (
  IN USB_DISPLAYLINK_DEV *Device,
  IN UINT8 Request,
  IN UINT16 Value,
  OUT VOID *ControlMsg,
  IN UINT16 ControlMsgLen
  )
{
  EFI_STATUS             Status;
  UINT32                 UsbStatus;
  EFI_USB_DEVICE_REQUEST UsbRequest;

  ZeroMem (&UsbRequest, sizeof (UsbRequest));
  UsbRequest.RequestType = USB_REQ_TYPE_VENDOR | USB_TARGET_INTERFACE | USB_ENDPOINT_DIR_IN;
  UsbRequest.Request = Request;
  UsbRequest.Value = Value;
  UsbRequest.Index = Device->InterfaceDescriptor.InterfaceNumber;
  UsbRequest.Length = ControlMsgLen;

  Status = Device->UsbIo->UsbControlTransfer (
    Device->UsbIo,
    &UsbRequest,
    EfiUsbDataIn,
    DISPLAYLINK_USB_CTRL_TIMEOUT,
    (VOID *)ControlMsg,
    ControlMsgLen,
    &UsbStatus);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "USB read control transfer failed - %r (USB status x%x).\n", Status, UsbStatus));
    Status = EFI_DEVICE_ERROR;
  }
  return Status;
}
