{
  Copyright (C)
  2026 - Uladzimir Karpenka, EW8BAK

  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.
}

unit IIOBindings;

{
  Минимальный биндинг к libiio (классический API 0.x; в системе 0.26) с
  ДИНАМИЧЕСКОЙ загрузкой. Если libiio нет — IIOLoad возвращает False, и
  Pluto-бэкенд просто не участвует в discovery (приложение работает с HPSDR).

  Загружаем libiio.so / libiio.so.0 (Linux), iio.dll (Windows),
  libiio.dylib (macOS). Имена символов одинаковые на всех платформах.

  Покрыты функции для: scan (discovery), context create/destroy, поиск
  device/channel, чтение/запись атрибутов, буферы RX/TX. Этого набора хватает
  на фазы 1–4 (discovery → RX → управление → TX).
}

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface

uses
  dynlibs, ctypes, SysUtils;

type
  // Непрозрачные указатели libiio
  Piio_scan_context = Pointer;
  Piio_context_info = Pointer;
  PPiio_context_info = ^Piio_context_info;   // struct iio_context_info **
  Piio_context      = Pointer;
  Piio_device       = Pointer;
  Piio_channel      = Pointer;
  Piio_buffer       = Pointer;

var
  // ---- Scan / discovery ----
  iio_create_scan_context: function(backend: PAnsiChar; flags: cuint): Piio_scan_context; cdecl;
  iio_scan_context_destroy: procedure(ctx: Piio_scan_context); cdecl;
  iio_scan_context_get_info_list: function(ctx: Piio_scan_context;
    var info: PPiio_context_info): ptrint; cdecl;
  iio_context_info_list_free: procedure(info: PPiio_context_info); cdecl;
  iio_context_info_get_description: function(info: Piio_context_info): PAnsiChar; cdecl;
  iio_context_info_get_uri: function(info: Piio_context_info): PAnsiChar; cdecl;

  // ---- Context ----
  iio_create_context_from_uri: function(uri: PAnsiChar): Piio_context; cdecl;
  iio_context_destroy: procedure(ctx: Piio_context); cdecl;
  iio_context_get_name: function(ctx: Piio_context): PAnsiChar; cdecl;
  iio_context_find_device: function(ctx: Piio_context; name: PAnsiChar): Piio_device; cdecl;
  iio_context_get_attr_value: function(ctx: Piio_context; name: PAnsiChar): PAnsiChar; cdecl;

  // ---- Device / channel ----
  // output — C '_Bool' (1 байт). Передаём явный байт 0/1 (ByteBool/True давал
  // некорректное значение → output-каналы не находились).
  iio_device_find_channel: function(dev: Piio_device; name: PAnsiChar;
    output: cuchar): Piio_channel; cdecl;
  iio_device_set_kernel_buffers_count: function(dev: Piio_device; nb: cuint): cint; cdecl;
  iio_channel_enable: procedure(chn: Piio_channel); cdecl;
  iio_channel_disable: procedure(chn: Piio_channel); cdecl;

  // ---- Атрибуты ----
  iio_channel_attr_read_longlong: function(chn: Piio_channel; attr: PAnsiChar;
    out val: clonglong): cint; cdecl;
  iio_channel_attr_read_double: function(chn: Piio_channel; attr: PAnsiChar;
    out val: cdouble): cint; cdecl;
  iio_channel_attr_write_longlong: function(chn: Piio_channel; attr: PAnsiChar;
    val: clonglong): cint; cdecl;
  iio_channel_attr_write: function(chn: Piio_channel; attr: PAnsiChar;
    src: PAnsiChar): ptrint; cdecl;
  iio_channel_attr_write_bool: function(chn: Piio_channel; attr: PAnsiChar;
    val: ByteBool): cint; cdecl;
  iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
    val: cdouble): cint; cdecl;

  // ---- Атрибуты устройства (не канальные) ----
  // ad9361-phy: 'xo_correction' — фактическая частота опорного XO в Гц, какой её
  // считает драйвер. Правка = частотная калибровка всего чипа (RX и TX LO).
  iio_device_attr_read_longlong: function(dev: Piio_device; attr: PAnsiChar;
    out val: clonglong): cint; cdecl;
  iio_device_attr_write_longlong: function(dev: Piio_device; attr: PAnsiChar;
    val: clonglong): cint; cdecl;
  // 'calib_mode' — режим калибровок AD936x (строка): 'auto' = драйвер сам
  // переигрывает TX quad (утечка LO + квадратура) на каждой смене TX LO.
  iio_device_attr_write: function(dev: Piio_device; attr: PAnsiChar;
    src: PAnsiChar): ptrint; cdecl;

  // ---- Debug-атрибуты устройства (debugfs) ----
  // Нужны для выбора антенного разъёма у AD936x в режиме 1R1T:
  // 'adi,1rx-1tx-mode-use-rx-num'/'-tx-num' (какой физический канал кормит тракт)
  // + 'initialize' (драйвер заново прогоняет ad9361_setup из pdata).
  iio_device_debug_attr_read: function(dev: Piio_device; attr: PAnsiChar;
    dst: PAnsiChar; len: csize_t): ptrint; cdecl;
  iio_device_debug_attr_write: function(dev: Piio_device; attr: PAnsiChar;
    src: PAnsiChar): ptrint; cdecl;
  iio_device_debug_attr_write_longlong: function(dev: Piio_device; attr: PAnsiChar;
    val: clonglong): cint; cdecl;
  // Прямой доступ к регистрам (debugfs direct_reg_access) — быстрое переключение
  // антенного мультиплексора AD9361 (биты RX1/RX2 и TX1/TX2 регистров 0x3/0x2).
  iio_device_reg_read: function(dev: Piio_device; address: cuint32;
    out value: cuint32): cint; cdecl;
  iio_device_reg_write: function(dev: Piio_device; address: cuint32;
    value: cuint32): cint; cdecl;

  // ---- Буферы (RX refill / TX push) ----
  iio_device_create_buffer: function(dev: Piio_device; samples_count: csize_t;
    cyclic: ByteBool): Piio_buffer; cdecl;
  iio_buffer_destroy: procedure(buf: Piio_buffer); cdecl;
  iio_buffer_cancel: procedure(buf: Piio_buffer); cdecl;   // разблокирует refill/push
  iio_buffer_refill: function(buf: Piio_buffer): ptrint; cdecl;
  iio_buffer_push: function(buf: Piio_buffer): ptrint; cdecl;
  iio_buffer_first: function(buf: Piio_buffer; chn: Piio_channel): Pointer; cdecl;
  iio_buffer_step: function(buf: Piio_buffer): ptrint; cdecl;
  iio_buffer_end: function(buf: Piio_buffer): Pointer; cdecl;

  // ---- Утилиты ----
  iio_strerror: procedure(err: cint; dst: PAnsiChar; len: csize_t); cdecl;

  // ---- libad9361 (опционально) ----
  // ad9361_set_bb_rate: настраивает BBPLL + ЗАГРУЖАЕТ FIR-таблицу под частоту.
  // Без него прямая запись sampling_frequency на холодную даёт мусор (битый FIR).
  // nil если libad9361 не загружена.
  ad9361_set_bb_rate: function(dev: Piio_device; rate: culong): cint; cdecl;
  ad9361_set_trx_fir_enable: function(dev: Piio_device; enable: cint): cint; cdecl;

// Загружает libiio (один раз). True — готово к использованию. Идемпотентно.
function IIOLoad: Boolean;
// True, если libiio загружена и символы разрешены.
function IIOLoaded: Boolean;
// Расшифровка кода ошибки libiio (err — отрицательный errno).
function IIOStrError(err: cint): string;

implementation

var
  GLib:    TLibHandle = NilHandle;
  GLibAD:  TLibHandle = NilHandle;   // libad9361 (опционально)
  GLoaded: Boolean = False;

const
{$IFDEF WINDOWS}
  IIO_LIBS: array[0..1] of string = ('libiio.dll', 'iio.dll');
  AD_LIBS:  array[0..1] of string = ('libad9361.dll', 'ad9361.dll');
{$ELSE}
  {$IFDEF DARWIN}
  IIO_LIBS: array[0..1] of string = ('libiio.dylib', 'libiio.1.dylib');
  AD_LIBS:  array[0..1] of string = ('libad9361.dylib', 'libad9361.0.dylib');
  {$ELSE}
  IIO_LIBS: array[0..1] of string = ('libiio.so', 'libiio.so.0');
  AD_LIBS:  array[0..1] of string = ('libad9361.so', 'libad9361.so.0');
  {$ENDIF}
{$ENDIF}

function Sym(const Name: string): Pointer;
begin
  Result := GetProcedureAddress(GLib, Name);
  if Result = nil then
    raise Exception.CreateFmt('libiio: symbol "%s" not found', [Name]);
end;

function IIOLoaded: Boolean;
begin
  Result := GLoaded;
end;

function IIOLoad: Boolean;
var
  I: Integer;
begin
  if GLoaded then Exit(True);
  if GLib = NilHandle then
    for I := 0 to High(IIO_LIBS) do
    begin
      GLib := LoadLibrary(IIO_LIBS[I]);
      if GLib <> NilHandle then Break;
    end;
  if GLib = NilHandle then Exit(False);

  try
    iio_create_scan_context          := Sym('iio_create_scan_context');
    iio_scan_context_destroy         := Sym('iio_scan_context_destroy');
    iio_scan_context_get_info_list   := Sym('iio_scan_context_get_info_list');
    iio_context_info_list_free       := Sym('iio_context_info_list_free');
    iio_context_info_get_description := Sym('iio_context_info_get_description');
    iio_context_info_get_uri         := Sym('iio_context_info_get_uri');
    iio_create_context_from_uri      := Sym('iio_create_context_from_uri');
    iio_context_destroy              := Sym('iio_context_destroy');
    iio_context_get_name             := Sym('iio_context_get_name');
    iio_context_find_device          := Sym('iio_context_find_device');
    iio_context_get_attr_value       := Sym('iio_context_get_attr_value');
    iio_device_find_channel          := Sym('iio_device_find_channel');
    iio_device_set_kernel_buffers_count := Sym('iio_device_set_kernel_buffers_count');
    iio_channel_enable               := Sym('iio_channel_enable');
    iio_channel_disable              := Sym('iio_channel_disable');
    iio_channel_attr_read_longlong   := Sym('iio_channel_attr_read_longlong');
    iio_channel_attr_read_double     := Sym('iio_channel_attr_read_double');
    iio_channel_attr_write_longlong  := Sym('iio_channel_attr_write_longlong');
    iio_channel_attr_write           := Sym('iio_channel_attr_write');
    iio_channel_attr_write_bool      := Sym('iio_channel_attr_write_bool');
    iio_channel_attr_write_double    := Sym('iio_channel_attr_write_double');
    iio_device_attr_read_longlong    := Sym('iio_device_attr_read_longlong');
    iio_device_attr_write_longlong   := Sym('iio_device_attr_write_longlong');
    iio_device_attr_write            := Sym('iio_device_attr_write');
    iio_device_debug_attr_read       := Sym('iio_device_debug_attr_read');
    iio_device_debug_attr_write      := Sym('iio_device_debug_attr_write');
    iio_device_debug_attr_write_longlong := Sym('iio_device_debug_attr_write_longlong');
    iio_device_reg_read              := Sym('iio_device_reg_read');
    iio_device_reg_write             := Sym('iio_device_reg_write');
    iio_device_create_buffer         := Sym('iio_device_create_buffer');
    iio_buffer_destroy               := Sym('iio_buffer_destroy');
    iio_buffer_cancel                := Sym('iio_buffer_cancel');
    iio_buffer_refill                := Sym('iio_buffer_refill');
    iio_buffer_push                  := Sym('iio_buffer_push');
    iio_buffer_first                 := Sym('iio_buffer_first');
    iio_buffer_step                  := Sym('iio_buffer_step');
    iio_buffer_end                   := Sym('iio_buffer_end');
    iio_strerror                     := Sym('iio_strerror');

    // libad9361 — опционально (ad9361_set_bb_rate грузит FIR). Отсутствие не
    // фатально: ApplySampleRate откатится на прямую запись sampling_frequency.
    if GLibAD = NilHandle then
      for I := 0 to High(AD_LIBS) do
      begin
        GLibAD := LoadLibrary(AD_LIBS[I]);
        if GLibAD <> NilHandle then Break;
      end;
    if GLibAD <> NilHandle then
    begin
      ad9361_set_bb_rate        := GetProcedureAddress(GLibAD, 'ad9361_set_bb_rate');
      ad9361_set_trx_fir_enable := GetProcedureAddress(GLibAD, 'ad9361_set_trx_fir_enable');
    end;

    GLoaded := True;
  except
    on E: Exception do
    begin
      UnloadLibrary(GLib);
      GLib := NilHandle;
      GLoaded := False;
    end;
  end;
  Result := GLoaded;
end;

function IIOStrError(err: cint): string;
var
  Buf: array[0..255] of AnsiChar;
begin
  if GLoaded and Assigned(iio_strerror) then
  begin
    FillChar(Buf, SizeOf(Buf), 0);
    iio_strerror(Abs(err), @Buf[0], SizeOf(Buf));
    Result := string(PAnsiChar(@Buf[0]));
  end
  else
    Result := Format('iio error %d', [err]);
end;

finalization
  if GLibAD <> NilHandle then
  begin
    UnloadLibrary(GLibAD);
    GLibAD := NilHandle;
  end;
  if GLib <> NilHandle then
  begin
    UnloadLibrary(GLib);
    GLib := NilHandle;
    GLoaded := False;
  end;
end.
