lib/wafer/drivers/elixir_ale/spi.ex

defmodule Wafer.Driver.ElixirALE.SPI do
  defstruct ~w[bus pid]a
  @behaviour Wafer.Conn
  alias Wafer.Driver.ElixirALE.SPI.Wrapper

  @moduledoc """
  A connection to a chip via ElixirALE's SPI driver.

  Implements the `Wafer.Conn` behaviour as well as the `Wafer.SPI` protocol.
  """

  @type t :: %__MODULE__{bus: binary, pid: pid}

  @type options :: [option | driver_option]
  @type option :: {:bus_name, binary}
  # These options are passed unchanged to the underlying driver.
  @type driver_option ::
          {:mode, 0..3}
          | {:bits_per_word, 0..16}
          | {:speed_hz, pos_integer}
          | {:delay_us, non_neg_integer}

  @doc """
  Acquire a connection to a peripheral using the ElixirALE' SPI driver on the
  specified bus and address.
  """
  @spec acquire(options) :: {:ok, t} | {:error, reason :: any}
  def acquire(opts) when is_list(opts) do
    with {:ok, bus} when is_binary(bus) <- Keyword.fetch(opts, :bus_name),
         {:ok, pid} when is_pid(pid) <-
           Wrapper.start_link(bus, Keyword.delete(opts, :bus_name), []) do
      {:ok, %__MODULE__{bus: bus, pid: pid}}
    else
      :error -> {:error, "ElixirALE.SPI requires a `bus_name` option"}
      {:error, reason} -> {:error, reason}
    end
  end
end

defimpl Wafer.Release, for: Wafer.Driver.ElixirALE.SPI do
  alias Wafer.Driver.ElixirALE.SPI.Wrapper
  alias Wafer.Driver.ElixirALE.SPI

  @doc """
  Close the SPI bus connection.
  """
  @spec release(SPI.t()) :: :ok | {:error, reason :: any}
  def release(%SPI{pid: pid} = _conn) when is_pid(pid), do: Wrapper.release(pid)
end

defimpl Wafer.SPI, for: Wafer.Driver.ElixirALE.SPI do
  alias Wafer.Driver.ElixirALE.SPI.Wrapper

  def transfer(%{pid: pid} = conn, data) when is_pid(pid) and is_binary(data) do
    case Wrapper.transfer(pid, data) do
      read_data when is_binary(read_data) and byte_size(read_data) == byte_size(data) ->
        {:ok, read_data, conn}

      {:error, reason} ->
        {:error, reason}

      other ->
        {:error, "Invalid response from driver: #{inspect(other)}"}
    end
  end
end

defimpl Wafer.DeviceID, for: Wafer.Driver.ElixirALE.SPI do
  def id(%{bus: bus}), do: {Wafer.Driver.ElixirALE.SPI, bus}
end