# SPDX-FileCopyrightText: 2023 Frank Hunleth, Connor Rigby # # SPDX-License-Identifier: Apache-2.0 defmodule Circuits.GPIO.CDev do @moduledoc """ Circuits.GPIO backend that uses the Linux CDev for controlling GPIOs This is the default on Linux and Nerves. Nothing needs to be done to use it on those platforms. If you need to be explicit, here's the configuration to force it: ```elixir config :circuits_gpio, default_backend: Circuits.GPIO.CDev ``` It takes one option, `:test`, that can be set to `true` to compile the stub implementation that can be useful for testing. ```elixir config :circuits_gpio, default_backend: {Circuits.GPIO.CDev, test: true} ``` """ @behaviour Circuits.GPIO.Backend alias Circuits.GPIO.Backend alias Circuits.GPIO.Handle alias Circuits.GPIO.Nif defstruct [:ref] @impl Backend def enumerate(options) do cached = :persistent_term.get(__MODULE__, []) if cached == [] or options[:force_enumeration] do results = Nif.enumerate() :persistent_term.put(__MODULE__, results) results else cached end end defp find_by_index(gpios, index), do: Enum.at(gpios, index) defp find_by_tuple(gpios, {controller, label_or_index}) do Enum.find(gpios, fn %{location: {^controller, _}, label: ^label_or_index} -> true %{controller: ^controller, label: ^label_or_index} -> true %{location: {^controller, ^label_or_index}} -> true %{controller: ^controller, location: {_, ^label_or_index}} -> true _ -> false end) end defp find_by_label(gpios, label) do Enum.find(gpios, fn %{label: ^label} -> true _ -> false end) end defp retry_find(options, find_fun) do info = find_fun.(enumerate(options)) || find_fun.(enumerate([{:force_enumeration, true} | options])) if info, do: {:ok, info}, else: {:error, :not_found} end @impl Backend def identifiers(number, options) when is_integer(number) and number >= 0 do retry_find(options, &find_by_index(&1, number)) end def identifiers(line_label, options) when is_binary(line_label) do retry_find(options, &find_by_label(&1, line_label)) end def identifiers(tuple_spec, options) when is_tuple(tuple_spec) and tuple_size(tuple_spec) == 2 do retry_find(options, &find_by_tuple(&1, tuple_spec)) end def identifiers(_gpio_spec, _options) do {:error, :not_found} end @impl Backend def status(gpio_spec, options \\ []) do with {:ok, location} <- find_location(gpio_spec, options) do resolved_location = resolve_gpiochip(location) Nif.status(resolved_location) end end # Handle special case that doesn't require a lookup defp find_location({"gpiochip" <> _, line} = gpio_spec, _options) when is_integer(line) do {:ok, gpio_spec} end defp find_location(gpio_spec, options) do with {:ok, ids} <- identifiers(gpio_spec, options) do {:ok, ids.location} end end defp resolve_gpiochip({controller, line}) do in_slash_dev = Path.expand(controller, "/dev") if File.exists?(in_slash_dev), do: {in_slash_dev, line}, else: {controller, line} end @impl Backend def open(gpio_spec, direction, options) do value = Keyword.fetch!(options, :initial_value) pull_mode = Keyword.fetch!(options, :pull_mode) with {:ok, location} <- find_location(gpio_spec, options), resolved_location = resolve_gpiochip(location), {:ok, ref} <- Nif.open(gpio_spec, resolved_location, direction, value, pull_mode) do {:ok, %__MODULE__{ref: ref}} end end @impl Backend def backend_info() do Nif.backend_info() end defimpl Handle do @impl Handle def read(%Circuits.GPIO.CDev{ref: ref}) do Nif.read(ref) end @impl Handle def write(%Circuits.GPIO.CDev{ref: ref}, value) do Nif.write(ref, value) end @impl Handle def set_direction(%Circuits.GPIO.CDev{ref: ref}, direction) do Nif.set_direction(ref, direction) end @impl Handle def set_pull_mode(%Circuits.GPIO.CDev{ref: ref}, pull_mode) do Nif.set_pull_mode(ref, pull_mode) end @impl Handle def set_interrupts(%Circuits.GPIO.CDev{ref: ref}, trigger, options) do suppress_glitches = Keyword.get(options, :suppress_glitches, true) receiver = case Keyword.get(options, :receiver) do pid when is_pid(pid) -> pid name when is_atom(name) -> Process.whereis(name) || self() _ -> self() end Nif.set_interrupts(ref, trigger, suppress_glitches, receiver) end @impl Handle def close(%Circuits.GPIO.CDev{ref: ref}) do Nif.close(ref) end end end