{******************************************************************************}
{ FileName............: Saa7146aInterface                                      }
{ Project.............: SAA7146A                                               }
{ Author(s)...........: MM                                                     }
{ Version.............: 2.02                                                   }
{------------------------------------------------------------------------------}
{  Interface unit for SAA7146A.                                                }
{                                                                              }
{  Copyright (C) 2003-2004  M.Majoor                                           }
{                                                                              }
{  This program is free software; you can redistribute it and/or               }
{  modify it under the terms of the GNU General Public License                 }
{  as published by the Free Software Foundation; either version 2              }
{  of the License, or (at your option) any later version.                      }
{                                                                              }
{  This program is distributed in the hope that it will be useful,             }
{  but WITHOUT ANY WARRANTY; without even the implied warranty of              }
{  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               }
{  GNU General Public License for more details.                                }
{                                                                              }
{  You should have received a copy of the GNU General Public License           }
{  along with this program; if not, write to the Free Software                 }
{  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. }
{                                                                              }
{------------------------------------------------------------------------------}
{                                                                              }
{ Version   Date    Comment                                                    }
{  2.00   20030629  - Initial release                                          }
{                   - Supports multiple cards                                  }
{  2.00a  20040507  - Just an improvement: Name from registry is converted to  }
{                     uppercase before checking it                             }
{  2.01   20040709  - Added 'stdcall' for functions exported by DLL            }
{                   - Using more DLL acceptable types (no var/string)          }
{                   - <try..except> surround external calls                    }
{                   - Handle storage changed so multiple handles for a single  }
{                     card are now handled too. Max 255 handles for all cards. }
{  2.02   20050723  - Added SUBSYS identification, so a device can be          }
{                     recognized                                               }
{                     <GetSubsysOfHandle> and <GetSubsysOfCard> added for this }
{******************************************************************************}

unit Saa7146aInterface;

interface
uses
  Saa7146aIoControl, SysUtils, Windows;

const
  MEDIA_GUID: TGUID = (D1: $4D36E96C; D2: $E325; D3: $11CE; D4: ($BF, $C1, $08,
    $00, $2B, $E1, $03, $18));
  DMASIZE  = 4096;                     // DMA page size
  MAXCARDS = 10;                       // Max number of cards

function Saa7146aGetNumberOfCards: Integer; stdcall;
function Saa7146aCreateFile                     (const Card  : Integer): THandle; stdcall;
procedure Saa7146aCloseHandle                   (const Handle: THandle); stdcall;
function Saa7146aGetDriverVersion               (const Handle: THandle;
                                                 MajorVersion: PWord;
                                                 Minorversion: PWord;
                                                 Build       : PDword;
                                                 DeviceName  : PChar): Boolean; stdcall;
function Saa7146aGetDmaStatus                   (Handle      : THandle;
                                                 DmaBuffer   : Dword;
                                                 var Status  : TSaa7146aGetDmaStatus): Boolean;
function Saa7146aWaitForNotification            (Handle      : THandle): Boolean;
function Saa7146aGenerateManualNotification     (Handle      : THandle): Boolean;
function Saa7146aReadFromSaa7146aRegister       (Handle      : THandle;
                                                 Address     : Dword;
                                                 var Data    : Dword): Boolean;
function Saa7146aWriteToSaa7146aRegister        (Handle      : THandle;
                                                 Address     : Dword;
                                                 Data        : Dword): Boolean;
function Saa7146aWriteWithMaskToSaa7146aRegister(Handle      : THandle;
                                                 Address     : Dword;
                                                 Mask        : Dword;
                                                 Data        : Dword): Boolean;
function Saa7146aAllocateDma                    (Handle      : THandle;
                                                 DmaSize     : Dword;
                                                 var Status  : TSaa7146aDmaBuffer): Boolean;
function Saa7146aReleaseDma                     (Handle      : THandle;
                                                 DmaBuffer   : Dword): Boolean;
function Saa7146aReadFromDma                    (Handle      : THandle;
                                                 Transfer    : TSaa7146aTransferBuffer): Boolean;
function Saa7146aWriteToDma                     (Handle      : THandle;
                                                 Transfer    : TSaa7146aTransferBuffer): Boolean;
function Saa7146aAllocateFifo                   (Handle      : THandle;
                                                 var Transfer: TSaa7146aFifoTransferBuffer): Boolean;
function Saa7146aReleaseFifo                    (Handle      : THandle;
                                                 var Transfer: TSaa7146aFifoTransferBuffer): Boolean;
function Saa7146aReadFromFifo                   (Handle      : THandle;
                                                 var Transfer: TSaa7146aFifoTransferBuffer): Boolean;
function Saa7146aWriteToIrqHandling             (Handle      : THandle;
                                                 Transfer    : TSaa7146aIRQTransferBuffer): Boolean;
function Saa7146aReadFromIrqHandling            (Handle      : THandle;
                                                 var Transfer: TSaa7146aIRQTransferBuffer): Boolean;
function Saa7146aGetCardOfHandle                (const Handle: THandle): Integer; stdcall;
function Saa7146aGetHandleOfCard                (const Card  : Integer): THandle; stdcall;
function Saa7146aGetSubsysOfHandle              (const Handle: THandle): Integer; stdcall;
function Saa7146aGetSubsysOfCard                (const Card  : Integer): Integer; stdcall;

{------------------------------------------------------------------------------
                             From SETUPAPI.PAS
 ------------------------------------------------------------------------------}
const
  ANYSIZE_ARRAY         = 1;
  SetupAPIdll           = 'SetupAPI.dll';
  SPDRP_HARDWAREID      = $00000001;
  DIGCF_PRESENT         = $00000002;
  DIGCF_DEVICEINTERFACE = $00000010;
  SPDRP_FRIENDLYNAME    = $0000000C;
  SPDRP_DEVICEDESC      = $00000000;
type
  ULONG_PTR = DWORD;
  HDEVINFO = Pointer;
  PSPDeviceInterfaceData = ^TSPDeviceInterfaceData;
  _SP_DEVICE_INTERFACE_DATA = packed record
    cbSize: DWORD;
    InterfaceClassGuid: TGUID;
    Flags: DWORD;
    Reserved: ULONG_PTR;
  end;
  TSPDeviceInterfaceData = _SP_DEVICE_INTERFACE_DATA;
  PSPDeviceInterfaceDetailDataA = ^TSPDeviceInterfaceDetailDataA;
  PSPDeviceInterfaceDetailData = PSPDeviceInterfaceDetailDataA;
  _SP_DEVICE_INTERFACE_DETAIL_DATA_A = packed record
    cbSize: DWORD;
    DevicePath: array[0..ANYSIZE_ARRAY - 1] of AnsiChar;
  end;
  _SP_DEVICE_INTERFACE_DETAIL_DATA_W = packed record
    cbSize: DWORD;
    DevicePath: array[0..ANYSIZE_ARRAY - 1] of WideChar;
  end;
  _SP_DEVICE_INTERFACE_DETAIL_DATA_ = _SP_DEVICE_INTERFACE_DETAIL_DATA_A;
  TSPDeviceInterfaceDetailDataA = _SP_DEVICE_INTERFACE_DETAIL_DATA_A;
  TSPDeviceInterfaceDetailData = TSPDeviceInterfaceDetailDataA;
  PSPDevInfoData = ^TSPDevInfoData;
  _SP_DEVINFO_DATA = packed record
    cbSize: DWORD;
    ClassGuid: TGUID;
    DevInst: DWORD;
    Reserved: ULONG_PTR;
  end;
  TSPDevInfoData = _SP_DEVINFO_DATA;
function SetupDiGetClassDevs(ClassGuid: PGUID; const Enumerator: PChar;
  hwndParent: HWND; Flags: DWORD): HDEVINFO; stdcall;
function SetupDiEnumDeviceInterfaces(DeviceInfoSet: HDEVINFO;
  DeviceInfoData: PSPDevInfoData; var InterfaceClassGuid: TGUID;
  MemberIndex: DWORD; var DeviceInterfaceData: TSPDeviceInterfaceData):
    LongBool; stdcall;
function SetupDiGetDeviceInterfaceDetail(DeviceInfoSet: HDEVINFO;
  DeviceInterfaceData: PSPDeviceInterfaceData;
  DeviceInterfaceDetailData: PSPDeviceInterfaceDetailDataA;
  DeviceInterfaceDetailDataSize: DWORD; RequiredSize: PDWORD;
  Device: PSPDevInfoData): LongBool; stdcall;
function SetupDiGetDeviceRegistryProperty(DeviceInfoSet: HDEVINFO;
  var DeviceInfoData: TSPDevInfoData; Property_: DWORD;
  PropertyRegDataType: PDWORD; PropertyBuffer: PBYTE; PropertyBufferSize: DWORD;
  RequiredSize: PDWORD): LongBool; stdcall;
function SetupDiDestroyDeviceInfoList(DeviceInfoSet: HDEVINFO): LongBool;
  stdcall;
function SetupDiGetClassDevs; external SetupAPIdll name 'SetupDiGetClassDevsA';
function SetupDiEnumDeviceInterfaces; external SetupAPIdll name
  'SetupDiEnumDeviceInterfaces';
function SetupDiGetDeviceInterfaceDetail; external SetupAPIdll name
  'SetupDiGetDeviceInterfaceDetailA';
function SetupDiGetDeviceRegistryProperty; external SetupAPIdll name
  'SetupDiGetDeviceRegistryPropertyA';
function SetupDiDestroyDeviceInfoList; external SetupAPIdll name
  'SetupDiDestroyDeviceInfoList';
{------------------------------------------------------------------------------
                            End of SETUPAPI.PAS
 ------------------------------------------------------------------------------}

implementation

type
  TCardHandles = record
    Card  : Integer;
    Handle: THandle;
    Subsys: Integer;
  end;

var
  Loop: Integer; // Loop variable
  Saa7146aNumberOfCardsDetected: Integer;
  Saa7146aNameForFileHandle    : array[0..MAXCARDS - 1] of string;
  Saa7146aSubsysOfCard         : array[0..MAXCARDS - 1] of string;
  Saa7146aDeviceFullNames      : array[0..MAXCARDS - 1] of string;
  Saa7146aHandles              : array[0..255] of TCardHandles;

  {------------------------------------------------------------------------------
    Params  : -
    Returns : <Result>        Number of cards detected
                              0 if no cards or no driver

    Descript: Get number of detected cards
    Notes   :
   ------------------------------------------------------------------------------}

function Saa7146aGetNumberOfCards: Integer; stdcall;
begin
  Result := Saa7146aNumberOfCardsDetected;
end;

{------------------------------------------------------------------------------
  Params  : <Handle>        CreateFile handle
  Returns : <Result>        True if valid results
            <MajorVersion>  Major version ID
            <MinorVersion>  Minor version ID
            <Build>         Build (date) in $YYYYMMDD notation
            <DeviceName>    Name of device (target buffer must have been allocated)

  Descript: Retrieve version information from driver.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGetDriverVersion(const Handle: THandle;
  MajorVersion: PWord; Minorversion: PWord; Build: PDword;
  DeviceName: PChar): Boolean; stdcall;
var
  ABuffer: TSaa7146aGetDriverVersion;
  BytesRead: Dword;
  Loop: Integer;
begin
  try
    Result := False;
    if not Assigned(MajorVersion) then
      Exit;
    if not Assigned(MinorVersion) then
      Exit;
    if not Assigned(Build) then
      Exit;
    if not Assigned(DeviceName) then
      Exit;
    if Handle = INVALID_HANDLE_VALUE then
      Exit;
    Result := DeviceIoControl(Handle,
      CIoctlSaa7146aGetDriverVersion,
      nil,
      0,
      @ABuffer,
      Sizeof(ABuffer),
      BytesRead,
      nil);
    if Result then
    begin
      MajorVersion^ := ABuffer.MajorVersion;
      MinorVersion^ := ABuffer.MinorVersion;
      Build^ := ABuffer.Build;
    end
    else
    begin
      MajorVersion^ := 0;
      MinorVersion^ := 0;
      Build^ := 0;
    end;
    // Try to find the correct name
    StrPCopy(DeviceName, Saa7146aDeviceFullNames[0]);
    for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
    begin
      if Saa7146aHandles[Loop].Handle = Handle then
        StrPCopy(DeviceName, Saa7146aDeviceFullNames[Loop]);
    end;
  except
    Result := False;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <DmaBuffer>  DMA buffer to get status for
  Returns : <Result>     True if valid results
            <Status>     Returned status

  Descript: Retrieve DMA status information from driver.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGetDmaStatus(Handle: THandle;
  DmaBuffer: Dword; var Status: TSaa7146aGetDmaStatus): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aGetDmaStatus,
    @DmaBuffer,
    Sizeof(DmaBuffer),
    @Status,
    Sizeof(Status),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <DmaSize>    Size of the DMA buffer to allocate
  Returns : <Result>     True if valid results
            <Status>     Returned status

  Descript: Allocate DMA buffer.
  Notes   : Needs to release it through <Saa7146aReleaseDma> using the returned
            <Identifier>
 ------------------------------------------------------------------------------}

function Saa7146aAllocateDma(Handle: THandle;
  DmaSize: Dword; var Status: TSaa7146aDmaBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aAllocateDma,
    @DmaSize,
    Sizeof(DmaSize),
    @Status,
    Sizeof(Status),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <DmaBuffer>  Buffer to release (0..31)
  Returns : <Result>     True if valid results

  Descript: Release DMA buffer.
  Notes   : The <DmaBuffer> is the returned <Identifier> of the status returned
            by <Saa7146aaAllocateDma>.
 ------------------------------------------------------------------------------}

function Saa7146aReleaseDma(Handle: THandle; DmaBuffer: Dword): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aReleaseDma,
    @DmaBuffer,
    Sizeof(DmaBuffer),
    nil,
    0,
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Buffer/size and so on
  Returns : <Result>     True if valid results

  Descript: Read from DMA buffer.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aReadFromDma(Handle: THandle;
  Transfer: TSaa7146aTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aReadFromDma,
    @Transfer,
    Sizeof(Transfer),
    nil,
    0,
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Buffer/size and so on
  Returns : <Result>     True if valid results

  Descript: Write to DMA buffer.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aWriteToDma(Handle: THandle;
  Transfer: TSaa7146aTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aWriteToDma,
    @Transfer,
    Sizeof(Transfer),
    nil,
    0,
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Buffer/size and so on
  Returns : <Result>     True if valid results

  Descript: Allocate Fifo buffer(s).
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aAllocateFifo(Handle: THandle;
  var Transfer: TSaa7146aFifoTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aAllocateFifo,
    @Transfer,
    Sizeof(Transfer),
    @Transfer,
    Sizeof(Transfer),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Buffer/size and so on
  Returns : <Result>     True if valid results

  Descript: Release Fifo buffer(s).
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aReleaseFifo(Handle: THandle;
  var Transfer: TSaa7146aFifoTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aReleaseFifo,
    @Transfer,
    Sizeof(Transfer),
    @Transfer,
    Sizeof(Transfer),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Buffer/size and so on
  Returns : <Result>     True if valid results

  Descript: Read Fifo buffer.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aReadFromFifo(Handle: THandle;
  var Transfer: TSaa7146aFifoTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aReadFromFifo,
    @Transfer,
    Sizeof(Transfer),
    @Transfer,
    Sizeof(Transfer),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Data
  Returns : <Result>     True if valid results

  Descript: Write IRQ handling.
  Notes   : You still need to enable the interrupt by means of the Interrupt
            enable register!!!
 ------------------------------------------------------------------------------}

function Saa7146aWriteToIrqHandling(Handle: THandle;
  Transfer: TSaa7146aIrqTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aWriteToIrqHandling,
    @Transfer,
    Sizeof(Transfer),
    nil,
    0,
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
            <Transfer>   Data
  Returns : <Result>     True if valid results

  Descript: Read IRQ handling.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aReadFromIrqHandling(Handle: THandle;
  var Transfer: TSaa7146aIrqTransferBuffer): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aReadFromIrqHandling,
    @Transfer,
    Sizeof(Transfer),
    @Transfer,
    Sizeof(Transfer),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>  CreateFile handle
  Returns : <Result>  True if success

  Descript: Wait for notification to occur.
  Notes   : The driver only support ONE notification at a time!!!
 ------------------------------------------------------------------------------}

function Saa7146aWaitForNotification(Handle: THandle): Boolean;
var
  BytesRead: Dword;
  hEvent: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aWaitForNotification,
    nil,
    0,
    @hEvent,
    Sizeof(hEvent),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>  CreateFile handle
  Returns : <Result>  True if success

  Descript: Manually activate the notification.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGenerateManualNotification(Handle: THandle): Boolean;
var
  BytesRead: Dword;
  Aux: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aGenerateManualNotification,
    @Aux, // Required, but currently not used
    Sizeof(Aux),
    nil,
    0,
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>   CreateFile handle
            <Address>  Register of SAA7146 to read (4 byte multiple)
  Returns : <Result>   True if valid results
            <Data>     Returned data

  Descript: Read data from SAA7146A.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aReadFromSaa7146aRegister(Handle: THandle;
  Address: Dword; var Data: Dword): Boolean;
var
  BytesRead: Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aReadFromSaa7146aRegister,
    @Address,
    Sizeof(Address),
    @Data,
    Sizeof(Data),
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>   CreateFile handle
            <Address>  Register of SAA7146 to write to (4 byte multiple)
            <Data>     Data to write
  Returns : <Result>   True if success

  Descript: Write data to SAA7146A.
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aWriteToSaa7146aRegister(Handle: THandle;
  Address: Dword; Data: Dword): Boolean;
var
  BytesRead: Dword;
  ABuffer: array[0..1] of Dword;
begin
  Result := False;
  if Handle = INVALID_HANDLE_VALUE then
    Exit;
  ABuffer[0] := Address;
  ABuffer[1] := Data;
  Result := DeviceIoControl(Handle,
    CIoctlSaa7146aWriteToSaa7146aRegister,
    @ABuffer,
    Sizeof(ABuffer),
    nil,
    0,
    BytesRead,
    nil);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>   CreateFile handle
            <Address>  Register of SAA7146 to write to (4 byte multiple)
            <Mask>     Mask data. Only the bits set to '1' will be adjusted
                       according to the <Data>.
            <Data>     Data to write
  Returns : <Result>   True if success

  Descript: Write data to SAA7146A, only changing the <Mask> bits.
  Notes   : This function requires both a READ and WRITE.
 ------------------------------------------------------------------------------}

function Saa7146aWriteWithMaskToSaa7146aRegister(Handle: THandle;
  Address: Dword; Mask: Dword; Data: Dword): Boolean;
var
  DataIn: Dword;
  DataOut: Dword;
  DataOr: Dword;
  DataAnd: Dword;
begin
  Result := False;
  if not Saa7146aReadFromSaa7146aRegister(Handle, Address, DataIn) then
    Exit;
  DataAnd := not Mask; // Bits to reset
  DataOr := Data and Mask; // Bits to set
  DataOut := (DataIn and DataAnd) or DataOr;
  Result := Saa7146aWriteToSaa7146aRegister(Handle, Address, DataOut);
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
  Returns : <Result>     Card number (<0 is none)

  Descript: Get card number which has the handle
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGetCardOfHandle(const Handle: THandle): Integer; stdcall;
var
  Loop: Integer;
begin
  try
    Result := -1;
    for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
    begin
      if Saa7146aHandles[Loop].Handle = Handle then
      begin
        Result := Saa7146aHandles[Loop].Card;
        Exit;
      end;
    end;
  except
    Result := -1;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Card>       Card number
  Returns : <Result>     CreateFile handle

  Descript: Get handle of card (first handle is returned)
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGetHandleOfCard(const Card: Integer): THandle; stdcall;
var
  Loop: Integer;
begin
  try
    Result := INVALID_HANDLE_VALUE;
    for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
    begin
      if Saa7146aHandles[Loop].Card = Card then
      begin
        Result := Saa7146aHandles[Loop].Handle;
        Exit;
      end;
    end;
  except
    Result := INVALID_HANDLE_VALUE;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Handle>     CreateFile handle
  Returns : <Result>     SUBSYS identifier

  Descript: Get SUBSYS identifier of the handle
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGetSubsysOfHandle(const Handle: THandle): Integer; stdcall;
var
  Loop: Integer;
begin
  try
    Result := -1;
    for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
    begin
      if Saa7146aHandles[Loop].Handle = Handle then
      begin
        Result := Saa7146aHandles[Loop].Subsys;
        Exit;
      end;
    end;
  except
    Result := -1;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Card>       Card number
  Returns : <Result>     Subsys identifier (0 if error)

  Descript: Get subsys of card
  Notes   :
 ------------------------------------------------------------------------------}

function Saa7146aGetSubsysOfCard(const Card: Integer): Integer; stdcall;
var
  Error: Integer;
begin
  Result := 0;
  try
    if Card < Low(Saa7146aSubsysOfCard) then
      Exit;
    if Card > High(Saa7146aSubsysOfCard) then
      Exit;
    Val('$' + Saa7146aSubsysOfCard[Card], Result, Error);
    if Error <> 0 then
      Result := 0;
  except
    Result := 0;
  end;
end;

{------------------------------------------------------------------------------
  Params  : -
  Returns : -

  Descript: Handle WDM support. WDM uses the class GUID and enumerating it. We
            get one driver at the time and have to check which card it is
            (the symbolic name consists of the class GUID so we can not use
            this to get which driver it is).
            We only get the symbolic links here because CreateFile and WDM
            access don't work well if mixed in the wrong sequence
            (i.e. CreateFile first and then doing WDM will fail -> First WDM
            and then CreateFile works).
 ------------------------------------------------------------------------------}

procedure WDMEnumeration;
var
  Info        : HDEVINFO;
  IfData      : TSPDeviceInterfaceData;
  AGuid       : TGUID;
  Detailed    : PSPDeviceInterfaceDetailData;
  DevInfo     : TSPDevInfoData;
  RLength     : Dword;
  DoAgain     : Boolean; // Repeat enumerating until no more devices
  Member      : Dword;
  RegType     : Dword;
  ABuf        : array[0..255] of Char;
  DeviceString: string;
begin
  DoAgain := True;
  Member := 0; // First device
  AGuid := MEDIA_GUID;
  // Get handle to relevant device information set
  Info := SetupDiGetClassDevs(@AGuid, nil, 0, DIGCF_PRESENT or
    DIGCF_DEVICEINTERFACE);
  if (Info <> Pointer(INVALID_HANDLE_VALUE)) then
  begin
    try
      while DoAgain do
      begin
        DoAgain := False;
        // Get interface data for the requested instance
        IfData.cbSize := sizeof(IfData);
        if SetupDiEnumDeviceInterfaces(Info, nil, AGuid, Member, IfData) = TRUE
          then
        begin
          DoAgain := true; // Keep trying next device
          inc(Member);
          RLength := 0;
          // First get size of required buffer for detailed information
          SetupDiGetDeviceInterfaceDetail(Info, @IfData, nil, 0, @RLength, nil);
          if (RLength <> 0) and (RLength < 2048) then // 2048 just for security
          begin
            // We know the size, now create buffer
            GetMem(Detailed, RLength);
            try
              // Now get detailed information in buffer: i.e. symbolic link name
              Detailed.cbSize := sizeof(TSPDeviceInterfaceDetailData);
              DevInfo.cbSize  := sizeof(TSPDevInfoData);
              if SetupDiGetDeviceInterfaceDetail(Info, @IfData, Detailed,
                RLength, @RLength, @DevInfo) = TRUE then
              begin
                // We now have all the information we need. The symbolic link will be something like:
                // W2000 : "\\?\root#unknown#000x#{4c....class GUID....}"  were 'x' is variable (enumerated)
                // W98/ME: "\\.\000000000000000x#{4c....class GUID....}"   were 'x' is variable (enumerated)
                // We do not know which driver we are looking at, so we have to investigate further.
                // Earlier we investigated the HardwareId but this does not always seem to work (at least not under ME).
                // We now use the friendly name of the driver. If that fails we try the description.
                if SetupDiGetDeviceRegistryProperty(Info, DevInfo,
                  SPDRP_FRIENDLYNAME, @RegType, @ABuf, sizeof(ABuf), @RLength) = True then
                begin
                  DeviceString := UpperCase(StrPas(ABuf));
                  if Pos('SAA7146A', DeviceString) <> 0 then
                  begin
                    Saa7146aNameForFileHandle[Saa7146aNumberOfCardsDetected] := StrPas(Detailed.DevicePath);
                    Saa7146aDeviceFullNames[Saa7146aNumberOfCardsDetected]   := DeviceString;
                    Saa7146aSubsysOfCard[Saa7146aNumberOfCardsDetected] := '';
                    // Get SUBSYS strings (we get a string array returned)
                    if SetupDiGetDeviceRegistryProperty(Info, DevInfo,
                      SPDRP_HARDWAREID, @RegType, @ABuf, sizeof(ABuf), @RLength) = True then
                    begin
                      // We assume the first string is the most 'complete' one
                      DeviceString := UpperCase(StrPas(ABuf));
                      if Pos('SUBSYS', DeviceString) <> 0 then
                      begin
                        Delete(DeviceString, 1, Pos('SUBSYS', DeviceString) + 6);
                        Delete(DeviceString, 9, 255);
                        Saa7146aSubsysOfCard[Saa7146aNumberOfCardsDetected] := DeviceString;
                      end;
                    end;
                    Inc(Saa7146aNumberOfCardsDetected);
                    if Saa7146aNumberOfCardsDetected >
                      High(Saa7146aNameForFileHandle) then
                      Exit;
                  end;
                end
                else if SetupDiGetDeviceRegistryProperty(Info, DevInfo,
                  SPDRP_DEVICEDESC, @RegType, @ABuf, sizeof(ABuf), @RLength) = TRUE
                  then
                begin
                  DeviceString := UpperCase(StrPas(ABuf));
                  if Pos('7146', DeviceString) <> 0 then
                  begin
                    Saa7146aNameForFileHandle[Saa7146aNumberOfCardsDetected] :=
                      StrPas(Detailed.DevicePath);
                    Saa7146aDeviceFullNames[Saa7146aNumberOfCardsDetected] :=
                      DeviceString;
                    Saa7146aSubsysOfCard[Saa7146aNumberOfCardsDetected] := '';
                    if SetupDiGetDeviceRegistryProperty(Info, DevInfo,
                      SPDRP_HARDWAREID, @RegType, @ABuf, sizeof(ABuf), @RLength) = True then
                    begin
                      DeviceString := UpperCase(StrPas(ABuf));
                      if Pos('SUBSYS', DeviceString) <> 0 then
                      begin
                        Delete(DeviceString, 1, Pos('SUBSYS', DeviceString) + 6);
                        Delete(DeviceString, 9, 255);
                        Saa7146aSubsysOfCard[Saa7146aNumberOfCardsDetected] := DeviceString;
                      end;
                    end;
                    Inc(Saa7146aNumberOfCardsDetected);
                    if Saa7146aNumberOfCardsDetected >
                      High(Saa7146aNameForFileHandle) then
                      Exit;
                  end;
                end;
              end;
            finally
              FreeMem(Detailed);
            end;
          end;
        end;
      end;
    finally
      SetupDiDestroyDeviceInfoList(Info);
    end;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Card>  Card to access
                    <0 will use the next available card which is not in use yet
  Returns : <Result> Opened handle.

  Descript: Open handle to driver.
 ------------------------------------------------------------------------------}

function Saa7146aCreateFile(const Card: Integer): THandle; stdcall;
var
  Loop   : Integer;
  UseCard: Integer;
  InUse  : array[0..MAXCARDS - 1] of Boolean;
  Empty  : Integer;
  Error  : Integer;
begin
  try
    Result  := INVALID_HANDLE_VALUE;
    UseCard := Card;
    // If find one ourselves
    if UseCard < Low(Saa7146aNameForFileHandle) then
    begin
      // Fill in array if cards in use
      for Loop := Low(InUse) to High(InUse) do
        InUse[Loop] := False;
      for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
        if Saa7146aHandles[Loop].Handle <> INVALID_HANDLE_VALUE then
        begin
          if Saa7146aHandles[Loop].Card in [Low(InUse)..High(InUse)] then
            InUse[Saa7146aHandles[Loop].Card] := True;
        end;
      UseCard := Low(Saa7146aNameForFileHandle);
      while (UseCard < Saa7146aNumberOfCardsDetected) and InUse[Usecard] do
        Inc(Usecard);
    end;
    if (UseCard >= 0) and (UseCard < Saa7146aNumberOfCardsDetected) then
    begin
      // Check if we have room to store handle
      Empty := Low(Saa7146aHandles);
      while (Saa7146aHandles[Empty].Handle <> INVALID_HANDLE_VALUE) and
            (Empty < High(Saa7146aHandles)) do
        Inc(Empty);
      if Empty = High(Saa7146aHandles) then
        Exit;
      Saa7146aHandles[Empty].Card := UseCard;
      Val('$' + Saa7146aSubsysOfCard[UseCard], Saa7146aHandles[Empty].Subsys, Error);
      // Open device handle to driver
      Saa7146aHandles[Empty].Handle := CreateFile(Pchar(Saa7146aNameForFileHandle[UseCard]),
        GENERIC_WRITE or GENERIC_READ, // Both read and write
        0,
        nil,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        0);
      Result := Saa7146aHandles[Empty].Handle;
    end;
  except
    Result := INVALID_HANDLE_VALUE;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Handle>  Handle to close
  Returns : -

  Descript: Close handle to driver.
 ------------------------------------------------------------------------------}

procedure Saa7146aCloseHandle(const Handle: THandle); stdcall;
var
  Loop: Integer;
begin
  try
    if Handle <> INVALID_HANDLE_VALUE then
    begin
      try
        CloseHandle(Handle);
      except
        // In case we close a handle which was already closed by some means ..
      end;
      // Make sure the local handle is reset too
      // The procedure might have been called externally and not from <Saa7146aCloseAllHandles>
      for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
        if Saa7146aHandles[Loop].Handle = Handle then
          Saa7146aHandles[Loop].Handle := INVALID_HANDLE_VALUE;
    end;
  except
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Handle>  Handle to close
  Returns : -

  Descript: Close handle to driver.
 ------------------------------------------------------------------------------}

procedure Saa7146aCloseAllHandles;
var
  Loop: Integer;
begin
  for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
    Saa7146aCloseHandle(Saa7146aHandles[Loop].Handle);
end;

initialization
  Saa7146aNumberOfCardsDetected := 0; // No cards detected by default
  for Loop := Low(Saa7146aNameForFileHandle) to High(Saa7146aNameForFileHandle)
    do
    Saa7146aNameForFileHandle[Loop] := CSaa7146aDriverName;
      // These are the names for NT 4.0 driver

  for Loop := Low(Saa7146aHandles) to High(Saa7146aHandles) do
    Saa7146aHandles[Loop].Handle := INVALID_HANDLE_VALUE;

  WDMEnumeration; // Try getting names for WDM drivers (overrule NT 4.0 names)

finalization
  begin
    Saa7146aCloseAllHandles;
  end;

end.

