/*
 * File: HdaController.h
 *
 * Copyright (c) 2018, 2020 John Davis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef _EFI_HDA_CONTROLLER_H_
#define _EFI_HDA_CONTROLLER_H_

#include "AudioDxe.h"
#include <IndustryStandard/HdaRegisters.h>

#include <Library/BaseOverflowLib.h>

//
// Consumed protocols.
//
#include <Protocol/PciIo.h>
#include <IndustryStandard/Pci.h>

//
// Structs.
//
typedef struct _HDA_CONTROLLER_DEV                HDA_CONTROLLER_DEV;
typedef struct _HDA_IO_PRIVATE_DATA               HDA_IO_PRIVATE_DATA;
typedef struct _HDA_CONTROLLER_INFO_PRIVATE_DATA  HDA_CONTROLLER_INFO_PRIVATE_DATA;

// Signature for private data structures.
#define HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE  SIGNATURE_32('H','d','a','C')

//
// PCI support.
//

// HDA controller is accessed via MMIO on BAR #0.
#define PCI_HDA_BAR  0

// Min supported version.
#define HDA_VERSION_MIN_MAJOR  0x1
#define HDA_VERSION_MIN_MINOR  0x0
#define HDA_MAX_CODECS         15

#define PCI_HDA_TCSEL_OFFSET    0x44
#define PCI_HDA_TCSEL_TC0_MASK  ~(BIT0 | BIT1 | BIT2)
#define PCI_HDA_DEVC_OFFSET     0x78
#define PCI_HDA_DEVC_NOSNOOPEN  BIT11

//
// CORB and RIRB.
//
// Entry sizes.
#define HDA_CORB_ENTRY_SIZE  sizeof(UINT32)
#define HDA_RIRB_ENTRY_SIZE  sizeof(UINT64)

// Misc.
#define HDA_CORB_VERB(Cad, Nid, Verb)  ((((UINT32)Cad) << 28) | (((UINT32)Nid) << 20) | (Verb & 0xFFFFF))
#define HDA_RIRB_RESP(Response)        ((UINT32)Response)
#define HDA_RIRB_CAD(Response)         ((Response >> 32) & 0xF)
#define HDA_RIRB_UNSOL(Response)       ((Response >> 36) & 0x1)

//
// Streams.
//
// Buffer Descriptor List Entry.
#pragma pack(1)
typedef struct {
  UINT32    Address;
  UINT32    AddressHigh;
  UINT32    Length;
  UINT32    InterruptOnCompletion;
} HDA_BDL_ENTRY;
#pragma pack()

// Buffer Descriptor List sizes. Max number of entries is 256, min is 2.
#define HDA_BDL_ENTRY_IOC    BIT0
#define HDA_BDL_ENTRY_COUNT  2
#define HDA_BDL_SIZE         (sizeof(HDA_BDL_ENTRY) * HDA_BDL_ENTRY_COUNT)
#define HDA_BDL_ENTRY_HALF   ((HDA_BDL_ENTRY_COUNT / 2) - 1)
#define HDA_BDL_ENTRY_LAST   (HDA_BDL_ENTRY_COUNT - 1)

// Buffer size and block size.
#define HDA_STREAM_BUF_SIZE        BASE_512KB
#define HDA_STREAM_BUF_SIZE_HALF   (HDA_STREAM_BUF_SIZE / 2)
#define HDA_BDL_BLOCKSIZE          (HDA_STREAM_BUF_SIZE / HDA_BDL_ENTRY_COUNT)
#define HDA_STREAM_POLL_TIME       (EFI_TIMER_PERIOD_MILLISECONDS(1))
#define HDA_STREAM_BUFFER_PADDING  0x200  // 512 byte pad.

#define HDA_STREAM_DMA_CHECK_THRESH  5

//
// DMA position structure.
//
#pragma pack(1)
typedef struct {
  //
  // Current DMA position of hardware.
  //
  UINT32    Position;
  //
  // Reserved.
  //
  UINT32    Reserved;
} HDA_DMA_POS_ENTRY;
#pragma pack()

#define HDA_STREAM_ID_MIN  1
#define HDA_STREAM_ID_MAX  15

//
// Stream structure. All streams are assumed to be output
//   (or configured for output, if a bidirectional stream).
//
typedef struct {
  //
  // Parent HDA controller.
  //
  HDA_CONTROLLER_DEV            *HdaDev;
  //
  // Stream index. This value is zero-based from the first stream on the controller.
  //
  UINT8                         Index;
  //
  // TRUE once Index is valid. Required for tearing down streams safely if there is
  // an error while setting them up. Assume nothing else for this or following streams
  // is set up or allocated if this is FALSE.
  //
  BOOLEAN                       HasIndex;
  //
  // Bidirectional stream? This requires a bit to set during reset to enable output.
  //
  BOOLEAN                       IsBidirectional;
  //
  // Use LPIB register instead of DMA position buffer.
  //
  BOOLEAN                       UseLpib;
  //
  // Count of times DMA position buffer usability is checked.
  //
  UINT32                        DmaCheckCount;
  //
  // Indicates whether DMA position buffer usability is complete.
  //   Ensures we don't accidentally fallback to LPIB if the stream position happens to be zero later on.
  //
  BOOLEAN                       DmaCheckComplete;
  //
  // Buffer Descriptor List.
  //
  HDA_BDL_ENTRY                 *BufferList;
  //
  // PCI memory mapping for BDL.
  //
  VOID                          *BufferListMapping;
  //
  // Physical address of BDL.
  //
  EFI_PHYSICAL_ADDRESS          BufferListPhysAddr;
  //
  // Buffer fed to BDL.
  //
  UINT8                         *BufferData;
  //
  // PCI memory mapping of BDL buffer.
  //
  VOID                          *BufferDataMapping;
  //
  // Physical address of BDL buffer.
  //
  EFI_PHYSICAL_ADDRESS          BufferDataPhysAddr;
  //
  // Pointer to source data buffer.
  //
  UINT8                         *BufferSource;
  //
  // Length of source data buffer.
  //
  UINT32                        BufferSourceLength;
  //
  // Current position in source data buffer.
  //
  UINT32                        BufferSourcePosition;
  //
  // Source buffer currently active?
  //
  BOOLEAN                       BufferActive;

  UINT32                        DmaPositionLast;
  UINT32                        DmaPositionTotal;
  UINT32                        DmaPositionChangedMax;

  // Timing elements for buffer filling.
  EFI_EVENT                     PollTimer;
  EFI_HDA_IO_STREAM_CALLBACK    Callback;
  VOID                          *CallbackContext1;
  VOID                          *CallbackContext2;
  VOID                          *CallbackContext3;
} HDA_STREAM;

//
// Ring buffer types.
//
// Command Output Ring Buffer - used for sending commands to codecs.
// Response Input Ring Buffer - used for receiving responses from codecs.
//
typedef enum {
  HDA_RING_BUFFER_TYPE_CORB,
  HDA_RING_BUFFER_TYPE_RIRB
} HDA_RING_BUFFER_TYPE;

//
// Ring buffer structure.
//
typedef struct {
  //
  // Parent HDA controller.
  //
  HDA_CONTROLLER_DEV      *HdaDev;
  //
  // Ring buffer type.
  //
  HDA_RING_BUFFER_TYPE    Type;
  //
  // Buffer backing.
  //
  VOID                    *Buffer;
  //
  // Buffer size.
  //
  UINTN                   BufferSize;
  //
  // Number of ring buffer entries.
  //
  UINT32                  EntryCount;
  //
  // Ring buffer PCI mapping.
  //
  VOID                    *Mapping;
  //
  // Physical address of ring buffer.
  //
  EFI_PHYSICAL_ADDRESS    PhysAddr;
  //
  // Pointer to current entry.
  //
  UINT16                  Pointer;
} HDA_RING_BUFFER;

typedef struct {
  EFI_HANDLE                  Handle;
  HDA_IO_PRIVATE_DATA         *PrivateData;
  EFI_DEVICE_PATH_PROTOCOL    *DevicePath;
} HDA_IO_CHILD;

//
// Quirks.
//

//
// QEMU must be run in interrupt mode not polling mode in order for CORB/RIRB to work
// at all. Unfortunately, this has to be set for all systems because CORB/RIRB must be
// set up to detect that we are on QEMU (at least if we rely only on reported controller
// and codec vendor ids, since QEMU controller incorrectly reports as Intel).
// TODO: If this causes problems, we may be able to use the previous quirk (which was
// commented out, but worked somewhat) of setting RINTCNT to 0xFF, in order to make
// QEMU run long enough to detect that it is QEMU and then only fully enable the rest
// of this quirk when needed.
// Problem:
//  https://github.com/qemu/qemu/blob/a3607d/hw/audio/intel-hda.c#L331
// Only available work-around:
//  https://github.com/qemu/qemu/blob/a3607d/hw/audio/intel-hda.c#L561
//
#define HDA_CONTROLLER_QUIRK_QEMU_1  BIT0

//
// Stream reset does not stay high when set.
// REF: https://gitlab.com/qemu-project/qemu/-/issues/757
//
#define HDA_CONTROLLER_QUIRK_QEMU_2  BIT1

//
// CORB reset does not stay high when set; affects VMware Fusion, but also
// some real hardware (at least Nvidia HDA controllers):
// REF: https://github.com/acidanthera/bugtracker/issues/1908
// For some years AudioDxe had this as default behaviour, and despite not
// being to Intel HDA spec., it seems like retaining this may work best.
//
#define HDA_CONTROLLER_QUIRK_CORB_NO_POLL_RESET  BIT2

#define HDA_CONTROLLER_QUIRK_INITIAL  ( \
  HDA_CONTROLLER_QUIRK_QEMU_1 | HDA_CONTROLLER_QUIRK_CORB_NO_POLL_RESET \
  )

struct _HDA_CONTROLLER_DEV {
  // Signature.
  UINTN                               Signature;

  // Consumed protocols and handles.
  EFI_PCI_IO_PROTOCOL                 *PciIo;
  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  EFI_DRIVER_BINDING_PROTOCOL         *DriverBinding;
  EFI_HANDLE                          ControllerHandle;
  UINT32                              OpenMode;

  // PCI.
  UINT64                              OriginalPciAttributes;
  BOOLEAN                             OriginalPciAttributesSaved;
  UINT16                              OriginalPciDeviceControl;
  BOOLEAN                             OriginalPciDeviceControlSaved;

  // Published info protocol.
  HDA_CONTROLLER_INFO_PRIVATE_DATA    *HdaControllerInfoData;
  HDA_IO_CHILD                        HdaIoChildren[HDA_MAX_CODECS];

  // Capabilites.
  UINT32                              VendorId;
  CHAR16                              *Name;
  UINT8                               MajorVersion;
  UINT8                               MinorVersion;
  UINT16                              Capabilities;

  HDA_RING_BUFFER                     Corb;
  HDA_RING_BUFFER                     Rirb;

  // Streams.
  UINT8                               StreamsCount;
  HDA_STREAM                          *Streams;

  // DMA positions.
  HDA_DMA_POS_ENTRY                   *DmaPositions;
  UINT32                              DmaPositionsSize;
  VOID                                *DmaPositionsMapping;
  EFI_PHYSICAL_ADDRESS                DmaPositionsPhysAddr;

  // Bitmap for stream ID allocation.
  UINT16                              StreamIdMapping;

  // Events.
  EFI_EVENT                           ResponsePollTimer;
  EFI_EVENT                           ExitBootServicesEvent;
  SPIN_LOCK                           SpinLock;

  // Required quirks.
  UINTN                               Quirks;
};

// HDA I/O private data.
struct _HDA_IO_PRIVATE_DATA {
  // Signature.
  UINTN                  Signature;

  // HDA I/O protocol.
  EFI_HDA_IO_PROTOCOL    HdaIo;
  UINT8                  HdaCodecAddress;

  // Assigned streams.
  HDA_STREAM             *HdaOutputStream;
  HDA_STREAM             *HdaInputStream;

  // HDA controller device.
  HDA_CONTROLLER_DEV     *HdaControllerDev;
};

#define HDA_IO_PRIVATE_DATA_FROM_THIS(This) \
  CR(This, HDA_IO_PRIVATE_DATA, HdaIo, HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE)

// HDA Codec Info private data.
struct _HDA_CONTROLLER_INFO_PRIVATE_DATA {
  // Signature.
  UINTN                               Signature;

  // HDA Codec Info protocol.
  EFI_HDA_CONTROLLER_INFO_PROTOCOL    HdaControllerInfo;

  // HDA controller device.
  HDA_CONTROLLER_DEV                  *HdaControllerDev;
};

#define HDA_CONTROLLER_INFO_PRIVATE_DATA_FROM_THIS(This) \
  CR(This, HDA_CONTROLLER_INFO_PRIVATE_DATA, HdaControllerInfo, HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE)

//
// HDA I/O protocol functions.
//
EFI_STATUS
EFIAPI
HdaControllerHdaIoGetAddress (
  IN  EFI_HDA_IO_PROTOCOL  *This,
  OUT UINT8                *CodecAddress
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoSendCommand (
  IN  EFI_HDA_IO_PROTOCOL  *This,
  IN  UINT8                Node,
  IN  UINT32               Verb,
  OUT UINT32               *Response
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoSendCommands (
  IN EFI_HDA_IO_PROTOCOL   *This,
  IN UINT8                 Node,
  IN EFI_HDA_IO_VERB_LIST  *Verbs
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoSetupStream (
  IN  EFI_HDA_IO_PROTOCOL       *This,
  IN  EFI_HDA_IO_PROTOCOL_TYPE  Type,
  IN  UINT16                    Format,
  OUT UINT8                     *StreamId
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoCloseStream (
  IN EFI_HDA_IO_PROTOCOL       *This,
  IN EFI_HDA_IO_PROTOCOL_TYPE  Type
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoGetStream (
  IN  EFI_HDA_IO_PROTOCOL       *This,
  IN  EFI_HDA_IO_PROTOCOL_TYPE  Type,
  OUT BOOLEAN                   *State
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoStartStream (
  IN EFI_HDA_IO_PROTOCOL         *This,
  IN EFI_HDA_IO_PROTOCOL_TYPE    Type,
  IN VOID                        *Buffer,
  IN UINTN                       BufferLength,
  IN UINTN                       BufferPosition OPTIONAL,
  IN EFI_HDA_IO_STREAM_CALLBACK  Callback OPTIONAL,
  IN VOID                        *Context1 OPTIONAL,
  IN VOID                        *Context2 OPTIONAL,
  IN VOID                        *Context3 OPTIONAL
  );

EFI_STATUS
EFIAPI
HdaControllerHdaIoStopStream (
  IN EFI_HDA_IO_PROTOCOL       *This,
  IN EFI_HDA_IO_PROTOCOL_TYPE  Type
  );

//
// HDA Controller Info protcol functions.
//
EFI_STATUS
EFIAPI
HdaControllerInfoGetName (
  IN  EFI_HDA_CONTROLLER_INFO_PROTOCOL  *This,
  OUT CONST CHAR16                      **Name
  );

EFI_STATUS
EFIAPI
HdaControllerInfoGetVendorId (
  IN  EFI_HDA_CONTROLLER_INFO_PROTOCOL  *This,
  OUT UINT32                            *VendorId
  );

//
// HDA controller internal functions.
//
VOID
EFIAPI
HdaControllerStreamOutputPollTimerHandler (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  );

EFI_STATUS
EFIAPI
HdaControllerReset (
  IN HDA_CONTROLLER_DEV  *HdaControllerDev,
  IN BOOLEAN             Restart
  );

EFI_STATUS
EFIAPI
HdaControllerSendCommands (
  IN HDA_CONTROLLER_DEV    *HdaDev,
  IN UINT8                 CodecAddress,
  IN UINT8                 Node,
  IN EFI_HDA_IO_VERB_LIST  *Verbs
  );

//
// Driver Binding protocol functions.
//
EFI_STATUS
EFIAPI
HdaControllerDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  );

EFI_STATUS
EFIAPI
HdaControllerDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  );

EFI_STATUS
EFIAPI
HdaControllerDriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer OPTIONAL
  );

EFI_STATUS
HdaControllerInitRingBuffer (
  IN HDA_RING_BUFFER       *HdaRingBuffer,
  IN HDA_CONTROLLER_DEV    *HdaDev,
  IN HDA_RING_BUFFER_TYPE  Type
  );

VOID
HdaControllerCleanupRingBuffer (
  IN HDA_RING_BUFFER       *HdaRingBuffer,
  IN HDA_RING_BUFFER_TYPE  Type
  );

EFI_STATUS
HdaControllerSetRingBufferState (
  IN HDA_RING_BUFFER       *HdaRingBuffer,
  IN BOOLEAN               Enable,
  IN HDA_RING_BUFFER_TYPE  Type
  );

EFI_STATUS
HdaControllerResetRingBuffer (
  IN HDA_RING_BUFFER  *HdaRingBuffer
  );

EFI_STATUS
HdaControllerInitStreams (
  IN HDA_CONTROLLER_DEV  *HdaDev
  );

BOOLEAN
HdaControllerResetStream (
  IN HDA_STREAM  *HdaStream
  );

VOID
HdaControllerCleanupStreams (
  IN HDA_CONTROLLER_DEV  *HdaDev
  );

BOOLEAN
HdaControllerGetStreamState (
  IN  HDA_STREAM  *HdaStream,
  OUT BOOLEAN     *Run
  );

BOOLEAN
HdaControllerSetStreamState (
  IN HDA_STREAM  *HdaStream,
  IN BOOLEAN     Run
  );

BOOLEAN
HdaControllerGetStreamLinkPos (
  IN  HDA_STREAM  *HdaStream,
  OUT UINT32      *Position
  );

BOOLEAN
HdaControllerGetStreamId (
  IN  HDA_STREAM  *HdaStream,
  OUT UINT8       *Index
  );

BOOLEAN
HdaControllerSetStreamId (
  IN HDA_STREAM  *HdaStream,
  IN UINT8       Index
  );

VOID
HdaControllerStreamIdle (
  IN HDA_STREAM  *HdaStream
  );

VOID
HdaControllerStreamAbort (
  IN HDA_STREAM  *HdaStream
  );

//
// Whether to restore NOSNOOPEN at exit.
//
extern
BOOLEAN
  gRestoreNoSnoop;

//
// Forced device path for HDA controller (ignore advertised class/subclass).
//
extern
EFI_DEVICE_PATH_PROTOCOL *
  gForcedControllerDevicePath;

#endif
