# 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.Line
alias Circuits.GPIO.Nif
defstruct [:ref]
@impl Backend
def enumerate() do
Nif.enumerate()
end
defp normalize_gpio_spec(number) when is_integer(number) do
info = enumerate() |> Enum.at(number)
if info, do: {:ok, info.gpio_spec}, else: {:error, :not_found}
end
defp normalize_gpio_spec(line_label) when is_binary(line_label) do
spec =
Enum.find_value(enumerate(), fn
%Line{gpio_spec: spec, label: {_chip_label, ^line_label}} -> spec
_ -> false
end)
if spec, do: {:ok, spec}, else: {:error, :not_found}
end
defp normalize_gpio_spec({chip_label, line_label})
when is_binary(chip_label) and is_binary(line_label) do
spec =
Enum.find_value(enumerate(), fn
%Line{gpio_spec: spec, label: {^chip_label, ^line_label}} -> spec
_ -> false
end)
if spec, do: {:ok, spec}, else: {:error, :not_found}
end
defp normalize_gpio_spec({controller, line}) when is_binary(controller) and is_integer(line) do
{:ok, {controller, line}}
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, normalized_spec} <- normalize_gpio_spec(gpio_spec),
resolved_spec = resolve_gpiochip(normalized_spec),
{:ok, ref} <- Nif.open(gpio_spec, resolved_spec, direction, value, pull_mode) do
{:ok, %__MODULE__{ref: ref}}
end
end
@impl Backend
def info() do
Nif.info() |> Map.put(:name, __MODULE__)
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
@impl Handle
def info(%Circuits.GPIO.CDev{ref: ref}) do
%{gpio_spec: Nif.gpio_spec(ref), pin_number: Nif.pin_number(ref)}
end
end
end