pw_gpio: Add new module and interfaces
NOTE: this module will be renamed `pw_digital_io` immediately in a
follow-up change.
The Digital IO interface represents individual GPIO lines that support
some combination of input, output, and/or interrupt functionality.
The choice of supported capability, and most other configuration details
are left up to the backend implementation.
Change-Id: I27437464ff918592e69a0bdbcbe005c68f2e9ef5
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/94100
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Anton Markov <amarkov@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b41d6f3..6dcc56f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,6 +40,7 @@
add_subdirectory(pw_cpu_exception_cortex_m EXCLUDE_FROM_ALL)
add_subdirectory(pw_file EXCLUDE_FROM_ALL)
add_subdirectory(pw_function EXCLUDE_FROM_ALL)
+add_subdirectory(pw_gpio EXCLUDE_FROM_ALL)
add_subdirectory(pw_hdlc EXCLUDE_FROM_ALL)
add_subdirectory(pw_interrupt EXCLUDE_FROM_ALL)
add_subdirectory(pw_interrupt_cortex_m EXCLUDE_FROM_ALL)
diff --git a/PIGWEED_MODULES b/PIGWEED_MODULES
index 7bf9e03..7e8d572 100644
--- a/PIGWEED_MODULES
+++ b/PIGWEED_MODULES
@@ -38,6 +38,7 @@
pw_file
pw_function
pw_fuzzer
+pw_gpio
pw_hdlc
pw_hex_dump
pw_i2c
diff --git a/pw_build/generated_pigweed_modules_lists.gni b/pw_build/generated_pigweed_modules_lists.gni
index 8a80c64..1ec4b65 100644
--- a/pw_build/generated_pigweed_modules_lists.gni
+++ b/pw_build/generated_pigweed_modules_lists.gni
@@ -68,6 +68,7 @@
dir_pw_file = get_path_info("../pw_file", "abspath")
dir_pw_function = get_path_info("../pw_function", "abspath")
dir_pw_fuzzer = get_path_info("../pw_fuzzer", "abspath")
+ dir_pw_gpio = get_path_info("../pw_gpio", "abspath")
dir_pw_hdlc = get_path_info("../pw_hdlc", "abspath")
dir_pw_hex_dump = get_path_info("../pw_hex_dump", "abspath")
dir_pw_i2c = get_path_info("../pw_i2c", "abspath")
@@ -203,6 +204,7 @@
dir_pw_file,
dir_pw_function,
dir_pw_fuzzer,
+ dir_pw_gpio,
dir_pw_hdlc,
dir_pw_hex_dump,
dir_pw_i2c,
@@ -304,6 +306,7 @@
"$dir_pw_file:tests",
"$dir_pw_function:tests",
"$dir_pw_fuzzer:tests",
+ "$dir_pw_gpio:tests",
"$dir_pw_hdlc:tests",
"$dir_pw_hex_dump:tests",
"$dir_pw_i2c:tests",
@@ -395,6 +398,7 @@
"$dir_pw_file:docs",
"$dir_pw_function:docs",
"$dir_pw_fuzzer:docs",
+ "$dir_pw_gpio:docs",
"$dir_pw_hdlc:docs",
"$dir_pw_hex_dump:docs",
"$dir_pw_i2c:docs",
diff --git a/pw_gpio/BUILD.bazel b/pw_gpio/BUILD.bazel
new file mode 100644
index 0000000..c84638c
--- /dev/null
+++ b/pw_gpio/BUILD.bazel
@@ -0,0 +1,48 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+ "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+ name = "pw_gpio",
+ srcs = ["gpio.cc"],
+ hdrs = [
+ "public/pw_gpio/gpio.h",
+ "public/pw_gpio/internal/conversions.h",
+ ],
+ includes = ["public"],
+ deps = [
+ "//pw_assert",
+ "//pw_function",
+ "//pw_result",
+ "//pw_status",
+ ],
+)
+
+pw_cc_test(
+ name = "gpio_test",
+ srcs = ["gpio_test.cc"],
+ deps = [
+ ":pw_gpio",
+ "//pw_unit_test",
+ ],
+)
diff --git a/pw_gpio/BUILD.gn b/pw_gpio/BUILD.gn
new file mode 100644
index 0000000..3d8d7ff
--- /dev/null
+++ b/pw_gpio/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_toolchain/generate_toolchain.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("public_include_path") {
+ include_dirs = [ "public" ]
+ visibility = [ ":*" ]
+}
+
+pw_source_set("pw_gpio") {
+ public_configs = [ ":public_include_path" ]
+ public = [
+ "public/pw_gpio/gpio.h",
+ "public/pw_gpio/internal/conversions.h",
+ ]
+ sources = [ "gpio.cc" ]
+ public_deps = [
+ dir_pw_assert,
+ dir_pw_function,
+ dir_pw_result,
+ dir_pw_status,
+ ]
+}
+
+pw_doc_group("docs") {
+ sources = [ "docs.rst" ]
+}
+
+pw_test_group("tests") {
+ tests = [ ":gpio_test" ]
+}
+
+pw_test("gpio_test") {
+ sources = [ "gpio_test.cc" ]
+ deps = [ ":pw_gpio" ]
+}
diff --git a/pw_gpio/CMakeLists.txt b/pw_gpio/CMakeLists.txt
new file mode 100644
index 0000000..66b93dc
--- /dev/null
+++ b/pw_gpio/CMakeLists.txt
@@ -0,0 +1,43 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+
+pw_add_module_library(pw_gpio
+ HEADERS
+ public/pw_gpio/gpio.h
+ public/pw_gpio/internal/conversions.h
+ PUBLIC_INCLUDES
+ public
+ SOURCES
+ gpio.cc
+ PUBLIC_DEPS
+ pw_assert
+ pw_function
+ pw_result
+ pw_status
+)
+if(Zephyr_FOUND AND CONFIG_PIGWEED_GPIO)
+ zephyr_link_libraries(pw_gpio)
+endif()
+
+pw_add_test(pw_gpio.stream_test
+ SOURCES
+ gpio_test.cc
+ DEPS
+ pw_gpio
+ GROUPS
+ modules
+ pw_gpio
+)
diff --git a/pw_gpio/OWNERS b/pw_gpio/OWNERS
new file mode 100644
index 0000000..dcdb6bd
--- /dev/null
+++ b/pw_gpio/OWNERS
@@ -0,0 +1 @@
+amarkov@google.com
diff --git a/pw_gpio/README.md b/pw_gpio/README.md
new file mode 100644
index 0000000..7aee495
--- /dev/null
+++ b/pw_gpio/README.md
@@ -0,0 +1,7 @@
+This directory contains the `pw` Digital IO Hardware Abstraction Layer (HAL).
+This HAL defines interfaces for working with Digital IO lines that provide
+different combinations of capabilities (input, output, and/or interrupts).
+Hardware specific backends provide implementations of these interfaces for
+different hardware platforms.
+
+Warning: This module is under construction and may not be ready for use.
diff --git a/pw_gpio/docs.rst b/pw_gpio/docs.rst
new file mode 100644
index 0000000..7e1ad1e
--- /dev/null
+++ b/pw_gpio/docs.rst
@@ -0,0 +1,283 @@
+.. _module-pw_gpio:
+
+.. cpp:namespace-push:: pw::gpio
+
+=======
+pw_gpio
+=======
+.. warning::
+ This module is under construction and may not be ready for use.
+
+``pw_gpio`` provides a set of interfaces for using General Purpose Input and
+Output (GPIO) lines for Digital IO. This module can either be used directly by
+the application code or wrapped in a device driver for more complex peripherals.
+
+--------
+Overview
+--------
+The interfaces provide an abstract concept of a **Digital IO line**. The
+interfaces abstract away details about the hardware and platform-specific
+drivers. A platform-specific backend is responsible for configuring lines and
+providing an implementation of the interface that matches the capabilities and
+intended usage of the line.
+
+Example API usage:
+
+.. code-block:: cpp
+
+ using namespace pw::gpio;
+
+ Status UpdateLedFromSwitch(const DigitalIn& switch, DigitalOut& led) {
+ PW_TRY_ASSIGN(const DigitalIo::State state, switch.GetState());
+ return led.SetState(state);
+ }
+
+ Status ListenForButtonPress(DigitalInterrupt& button) {
+ PW_TRY(button.SetInterruptHandler(Trigger::kActivatingEdge,
+ [](State sampled_state) {
+ // Handle the button press.
+ // NOTE: this may run in an interrupt context!
+ }));
+ return button.EnableInterruptHandler();
+ }
+
+-------------------
+pw::gpio Interfaces
+-------------------
+There are 3 basic capabilities of a Digital IO line:
+
+* Input - Get the state of the line.
+* Output - Set the state of the line.
+* Interrupt - Register a handler that is called when a trigger happens.
+
+.. note:: **Capabilities** refer to how the line is intended to be used in a
+ particular device given its actual physical wiring, rather than the
+ theoretical capabilities of the hardware.
+
+Additionally, all lines can be *enabled* and *disabled*:
+
+* Enable - tell the hardware to apply power to an output line, connect any
+ pull-up/down resistors, etc.
+* Disable - tell the hardware to stop applying power and return the line to its
+ default state. This may save power or allow some other component to drive a
+ shared line.
+
+.. note:: The initial state of a line is implementation-defined and may not
+ match either the "enabled" or "disabled" state. Users of the API who need
+ to ensure the line is disabled (ex. output is not driving the line) should
+ explicitly call ``Disable()``.
+
+Functionality overview
+======================
+The following table summarizes the interfaces and their required functionality:
+
+.. list-table::
+ :header-rows: 1
+ :stub-columns: 1
+
+ * -
+ - Interrupts Not Required
+ - Interrupts Required
+ * - Input/Output Not Required
+ -
+ - :cpp:class:`DigitalInterrupt`
+ * - Input Required
+ - :cpp:class:`DigitalIn`
+ - :cpp:class:`DigitalInInterrupt`
+ * - Output Required
+ - :cpp:class:`DigitalOut`
+ - :cpp:class:`DigitalOutInterrupt`
+ * - Input/Output Required
+ - :cpp:class:`DigitalInOut`
+ - :cpp:class:`DigitalInOutInterrupt`
+
+Synchronization requirements
+============================
+* An instance of a line has exclusive ownership of that line and may be used
+ independently of other line objects without additional synchronization.
+* Access to a single line instance must be synchronized at the application
+ level. For example, by wrapping the line instance in ``pw::Borrowable``.
+* Unless otherwise stated, the line interface must not be used from within an
+ interrupt context.
+
+------------
+Design Notes
+------------
+The interfaces are intended to support many but not all use cases, and they do
+not cover every possible type of functionality supported by the hardware. There
+will be edge cases that require the backend to expose some additional (custom)
+interfaces, or require the use of a lower-level API.
+
+Examples of intended use cases:
+
+* Do input and output on lines that have two logical states - active and
+ inactive - regardless of the underlying hardware configuration.
+
+ * Example: Read the state of a switch.
+ * Example: Control a simple LED with on/off.
+ * Example: Activate/deactivate power for a peripheral.
+ * Example: Trigger reset of an I2C bus.
+
+* Run code based on an external interrupt.
+
+ * Example: Trigger when a hardware switch is flipped.
+ * Example: Trigger when device is connected to external power.
+ * Example: Handle data ready signals from peripherals connected to
+ I2C/SPI/etc.
+
+* Enable and disable lines as part of a high-level policy:
+
+ * Example: For power management - disable lines to use less power.
+ * Example: To support shared lines used for multiple purposes (ex. GPIO or
+ I2C).
+
+Examples of use cases we want to allow but don't explicitly support in the API:
+
+* Software-controlled pull up/down resistors, high drive, polarity controls,
+ etc.
+
+ * It's up to the backend implementation to expose configuration for these
+ settings.
+ * Enabling a line should set it into the state that is configured in the
+ backend.
+
+* Level-triggered interrupts on RTOS platforms.
+
+ * We explicitly support disabling the interrupt handler while in the context
+ of the handler.
+ * Otherwise, it's up to the backend to provide any additional level-trigger
+ support.
+
+Examples of uses cases we explicitly don't plan to support:
+
+* Using Digital IO to simulate serial interfaces like I2C (bit banging), or any
+ use cases requiring exact timing and access to line voltage, clock controls,
+ etc.
+* Mode selection - controlling hardware multiplexing or logically switching from
+ GPIO to I2C mode.
+
+API decisions that have been deferred:
+
+* Supporting operations on multiple lines in parallel - for example to simulate
+ a memory register or other parallel interface.
+* Helpers to support different patterns for interrupt handlers - running in the
+ interrupt context, dispatching to a dedicated thread, using a pw_sync
+ primitive, etc.
+
+The following sub-sections discuss specific design decisions in detail.
+
+States vs. voltage levels
+=========================
+Digital IO line values are represented as **active** and **inactive** states.
+These states abstract away the actual electrical level and other physical
+properties of the line. This allows applications to interact with Digital IO
+lines across targets that may have different physical configurations. It is up
+to the backend to provide a consistent definition of state.
+
+Interrupt handling
+==================
+Interrupt handling is part of this API. The alternative was to have a separate
+API for interrupts. We wanted to have a single object that refers to each line
+and represents all the functionality that is available on the line.
+
+Interrupt triggers are configured through the ``SetInterruptHandler`` method.
+The type of trigger is tightly coupled to what the handler wants to do with that
+trigger.
+
+The handler is passed the latest known sampled state of the line. Otherwise
+handlers running in an interrupt context cannot query the state of the line.
+
+Class Hierarchy
+===============
+``pw_gpio`` contains a 2-level hierarchy of classes.
+
+* ``DigitalIoOptional`` acts as the base class and represents a line that does
+ not guarantee any particular functionality is available.
+
+ * This should be rarely used in APIs. Prefer to use one of the derived
+ classes.
+ * This class is never extended outside this module. Extend one of the derived
+ classes.
+
+* Derived classes represent a line with a particular combination of
+ functionality.
+
+ * Use a specific class in APIs to represent the requirements.
+ * Extend the specific class that has the actual capabilities of the line.
+
+In the future, we may support additional for classes that describe lines with
+**optional** functionality. For example, ``DigitalInOptionalInterrupt`` could
+describe a line that supports input and optionally supports interrupts.
+
+When using any classes with optional functionality, including
+``DigitalIoOptional``, you must check that a functionality is available using
+the ``provides_*`` runtime flags. Calling a method that is not supported will
+trigger ``PW_CRASH``.
+
+We define the public API through non-virtual methods declared in
+``DigitalIoOptional``. These methods delegate to private pure virtual methods.
+
+Type Conversions
+================
+Conversions are provided between classes with compatible requirements. For
+example:
+
+.. code-block:: cpp
+
+ DigitalInInterrupt& in_interrupt_line;
+ DigitalIn& in_line = in_interrupt_line;
+
+ DigitalInInterrupt* in_interrupt_line_ptr;
+ DigitalIn* in_line_ptr = &in_interrupt_line_ptr->as<DigitalIn>();
+
+Asynchronous APIs
+=================
+At present, ``pw_gpio`` is synchronous. All the API calls are expected to block
+until the operation is complete. This is desirable for simple GPIO chips that
+are controlled through direct register access. However, this may be undesirable
+for GPIO extenders controlled through I2C or another shared bus.
+
+The API may be extended in the future to add asynchronous capabilities, or a
+separate asynchronous API may be created.
+
+Backend Implemention Notes
+==========================
+* Derived classes explicitly list the non-virtual methods as public or private
+ depending on the supported set of functionality. For example, ``DigitalIn``
+ declare ``GetState`` public and ``SetState`` private.
+* Derived classes that exclude a particular functionality provide a private,
+ final implementation of the unsupported virtual method that crashes if it is
+ called. For example, ``DigitalIoIn`` implements ``DoSetState`` to trigger
+ ``PW_CRASH``.
+* Backend implementations provide real implementation for the remaining pure
+ virtual functions of the class they extend.
+* Classes that support optional functionality make the non-virtual optional
+ methods public, but they do not provide an implementation for the pure virtual
+ functions. These classes are never extended.
+* Backend implementations **must** check preconditions for each operations. For
+ example, check that the line is actually enabled before trying to get/set the
+ state of the line. Otherwise return ``pw::Status::FailedPrecondition()``.
+* Backends *may* leave the line in an uninitialized state after construction,
+ but implementors are strongly encouraged to initialize the line to a known
+ state.
+
+ * If backends initialize the line, it must be initialized to the disabled
+ state. i.e. the same state it would be in after calling ``Enable()``
+ followed by ``Disable()``.
+ * Calling ``Disable()`` on an uninitialized line must put it into the disabled
+ state.
+
+------------
+Dependencies
+------------
+* :ref:`module-pw_assert`
+* :ref:`module-pw_function`
+* :ref:`module-pw_result`
+* :ref:`module-pw_status`
+
+.. cpp:namespace-pop::
+
+Zephyr
+======
+To enable ``pw_gpio`` for Zephyr add ``CONFIG_PIGWEED_GPIO=y`` to the
+project's configuration.
diff --git a/pw_gpio/gpio.cc b/pw_gpio/gpio.cc
new file mode 100644
index 0000000..a5ce209
--- /dev/null
+++ b/pw_gpio/gpio.cc
@@ -0,0 +1,60 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_gpio/gpio.h"
+
+namespace pw::gpio {
+
+Status DigitalInterrupt::DoSetState(State) {
+ PW_CRASH("DoSetState not implemented");
+}
+Result<State> DigitalInterrupt::DoGetState() {
+ PW_CRASH("DoGetState not implemented");
+}
+
+Status DigitalIn::DoSetState(State) { PW_CRASH("DoSetState not implemented"); }
+Status DigitalIn::DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) {
+ PW_CRASH("DoSetInterruptHandler not implemented");
+}
+Status DigitalIn::DoEnableInterruptHandler(bool) {
+ PW_CRASH("DoEnableInterruptHandler not implemented");
+}
+
+Status DigitalInInterrupt::DoSetState(State) {
+ PW_CRASH("DoSetState not implemented");
+}
+
+Result<State> DigitalOut::DoGetState() {
+ PW_CRASH("DoGetState not implemented");
+}
+Status DigitalOut::DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) {
+ PW_CRASH("DoSetInterruptHandler not implemented");
+}
+Status DigitalOut::DoEnableInterruptHandler(bool) {
+ PW_CRASH("DoEnableInterruptHandler not implemented");
+}
+
+Result<State> DigitalOutInterrupt::DoGetState() {
+ PW_CRASH("DoGetState not implemented");
+}
+
+Status DigitalInOut::DoSetInterruptHandler(InterruptTrigger,
+ InterruptHandler&&) {
+ PW_CRASH("DoSetInterruptHandler not implemented");
+}
+Status DigitalInOut::DoEnableInterruptHandler(bool) {
+ PW_CRASH("DoEnableInterruptHandler not implemented");
+}
+
+} // namespace pw::gpio
diff --git a/pw_gpio/gpio_test.cc b/pw_gpio/gpio_test.cc
new file mode 100644
index 0000000..9a8c115
--- /dev/null
+++ b/pw_gpio/gpio_test.cc
@@ -0,0 +1,296 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_gpio/gpio.h"
+
+#include "gtest/gtest.h"
+#include "pw_status/status.h"
+
+namespace pw::gpio {
+namespace {
+
+// The base class should be compact.
+static_assert(sizeof(DigitalIoOptional) <= 2 * sizeof(void*),
+ "DigitalIo should be no larger than two pointers (vtable pointer "
+ "& packed members)");
+
+// Skeleton implementations to test DigitalIo methods.
+class TestDigitalInterrupt : public DigitalInterrupt {
+ public:
+ TestDigitalInterrupt() = default;
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) override {
+ return OkStatus();
+ }
+ Status DoEnableInterruptHandler(bool) override { return OkStatus(); }
+};
+
+class TestDigitalIn : public DigitalIn {
+ public:
+ TestDigitalIn() : state_(State::kInactive) {}
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+ Result<State> DoGetState() override { return state_; }
+
+ const State state_;
+};
+
+class TestDigitalInInterrupt : public DigitalInInterrupt {
+ public:
+ TestDigitalInInterrupt() : state_(State::kInactive) {}
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+ Result<State> DoGetState() override { return state_; }
+
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) override {
+ return OkStatus();
+ }
+ Status DoEnableInterruptHandler(bool) override { return OkStatus(); }
+
+ const State state_;
+};
+
+class TestDigitalOut : public DigitalOut {
+ public:
+ TestDigitalOut() {}
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+ Status DoSetState(State) override { return OkStatus(); }
+};
+
+class TestDigitalOutInterrupt : public DigitalOutInterrupt {
+ public:
+ TestDigitalOutInterrupt() {}
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+ Status DoSetState(State) override { return OkStatus(); }
+
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) override {
+ return OkStatus();
+ }
+ Status DoEnableInterruptHandler(bool) override { return OkStatus(); }
+};
+
+class TestDigitalInOut : public DigitalInOut {
+ public:
+ TestDigitalInOut() : state_(State::kInactive) {}
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+ Result<State> DoGetState() override { return state_; }
+ Status DoSetState(State state) override {
+ state_ = state;
+ return OkStatus();
+ }
+
+ State state_;
+};
+
+class TestDigitalInOutInterrupt : public DigitalInOutInterrupt {
+ public:
+ TestDigitalInOutInterrupt() : state_(State::kInactive) {}
+
+ private:
+ Status DoEnable(bool) override { return OkStatus(); }
+ Result<State> DoGetState() override { return state_; }
+ Status DoSetState(State state) override {
+ state_ = state;
+ return OkStatus();
+ }
+
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) override {
+ return OkStatus();
+ }
+ Status DoEnableInterruptHandler(bool) override { return OkStatus(); }
+
+ State state_;
+};
+
+// Test conversions between different interfaces.
+static_assert(!std::is_convertible<TestDigitalInterrupt, DigitalIn&>());
+static_assert(!std::is_convertible<TestDigitalInterrupt, DigitalOut&>());
+static_assert(
+ !std::is_convertible<TestDigitalInterrupt, DigitalInInterrupt&>());
+static_assert(
+ !std::is_convertible<TestDigitalInterrupt, DigitalOutInterrupt&>());
+static_assert(
+ !std::is_convertible<TestDigitalInterrupt, DigitalInOutInterrupt&>());
+
+static_assert(!std::is_convertible<TestDigitalIn, DigitalOut&>());
+static_assert(!std::is_convertible<TestDigitalIn, DigitalInterrupt&>());
+static_assert(!std::is_convertible<TestDigitalIn, DigitalInInterrupt&>());
+static_assert(!std::is_convertible<TestDigitalIn, DigitalOutInterrupt&>());
+
+static_assert(std::is_convertible<TestDigitalInInterrupt, DigitalIn&>());
+static_assert(!std::is_convertible<TestDigitalInInterrupt, DigitalOut&>());
+static_assert(std::is_convertible<TestDigitalInInterrupt, DigitalInterrupt&>());
+static_assert(
+ !std::is_convertible<TestDigitalInInterrupt, DigitalOutInterrupt&>());
+
+static_assert(!std::is_convertible<TestDigitalOut, DigitalIn&>());
+static_assert(!std::is_convertible<TestDigitalOut, DigitalInterrupt&>());
+static_assert(!std::is_convertible<TestDigitalOut, DigitalInInterrupt&>());
+static_assert(!std::is_convertible<TestDigitalOut, DigitalOutInterrupt&>());
+
+static_assert(!std::is_convertible<TestDigitalOutInterrupt, DigitalIn&>());
+static_assert(std::is_convertible<TestDigitalOutInterrupt, DigitalOut&>());
+static_assert(
+ std::is_convertible<TestDigitalOutInterrupt, DigitalInterrupt&>());
+static_assert(
+ !std::is_convertible<TestDigitalOutInterrupt, DigitalInInterrupt&>());
+
+static_assert(std::is_convertible<TestDigitalInOut, DigitalIn&>());
+static_assert(std::is_convertible<TestDigitalInOut, DigitalOut&>());
+static_assert(!std::is_convertible<TestDigitalInOut, DigitalInterrupt&>());
+static_assert(!std::is_convertible<TestDigitalInOut, DigitalInInterrupt&>());
+static_assert(!std::is_convertible<TestDigitalInOut, DigitalOutInterrupt&>());
+
+static_assert(std::is_convertible<TestDigitalInOutInterrupt, DigitalIn&>());
+static_assert(std::is_convertible<TestDigitalInOutInterrupt, DigitalOut&>());
+static_assert(
+ std::is_convertible<TestDigitalInOutInterrupt, DigitalInterrupt&>());
+static_assert(
+ std::is_convertible<TestDigitalInOutInterrupt, DigitalInInterrupt&>());
+static_assert(
+ std::is_convertible<TestDigitalInOutInterrupt, DigitalOutInterrupt&>());
+
+void FakeInterruptHandler(State) {}
+
+void TestInput(DigitalIoOptional& line) {
+ ASSERT_EQ(OkStatus(), line.Enable());
+
+ auto state_result = line.GetState();
+ ASSERT_EQ(OkStatus(), state_result.status());
+ ASSERT_EQ(State::kInactive, state_result.value());
+
+ ASSERT_EQ(OkStatus(), line.Disable());
+}
+
+void TestOutput(DigitalIoOptional& line) {
+ ASSERT_EQ(OkStatus(), line.Enable());
+
+ ASSERT_EQ(OkStatus(), line.SetState(State::kActive));
+
+ ASSERT_EQ(OkStatus(), line.Disable());
+}
+
+void TestOutputReadback(DigitalIoOptional& line) {
+ ASSERT_EQ(OkStatus(), line.Enable());
+
+ ASSERT_EQ(OkStatus(), line.SetState(State::kActive));
+ auto state_result = line.GetState();
+ ASSERT_EQ(OkStatus(), state_result.status());
+ ASSERT_EQ(State::kActive, state_result.value());
+
+ ASSERT_EQ(OkStatus(), line.Disable());
+}
+
+void TestInterrupt(DigitalIoOptional& line) {
+ ASSERT_EQ(OkStatus(), line.Enable());
+
+ ASSERT_EQ(OkStatus(),
+ line.SetInterruptHandler(InterruptTrigger::kBothEdges,
+ FakeInterruptHandler));
+ ASSERT_EQ(OkStatus(), line.EnableInterruptHandler());
+ ASSERT_EQ(OkStatus(), line.EnableInterruptHandler());
+ ASSERT_EQ(OkStatus(), line.DisableInterruptHandler());
+ ASSERT_EQ(OkStatus(), line.ClearInterruptHandler());
+
+ ASSERT_EQ(OkStatus(), line.Disable());
+}
+
+TEST(Digital, Interrupt) {
+ TestDigitalInterrupt line;
+
+ ASSERT_EQ(false, line.provides_input());
+ ASSERT_EQ(false, line.provides_output());
+ ASSERT_EQ(true, line.provides_interrupt());
+
+ TestInterrupt(line);
+}
+
+TEST(Digital, In) {
+ TestDigitalIn line;
+
+ ASSERT_EQ(true, line.provides_input());
+ ASSERT_EQ(false, line.provides_output());
+ ASSERT_EQ(false, line.provides_interrupt());
+
+ TestInput(line);
+}
+
+TEST(Digital, InInterrupt) {
+ TestDigitalInInterrupt line;
+
+ ASSERT_EQ(true, line.provides_input());
+ ASSERT_EQ(false, line.provides_output());
+ ASSERT_EQ(true, line.provides_interrupt());
+
+ TestInput(line);
+ TestInterrupt(line);
+}
+
+TEST(Digital, Out) {
+ TestDigitalOut line;
+
+ ASSERT_EQ(false, line.provides_input());
+ ASSERT_EQ(true, line.provides_output());
+ ASSERT_EQ(false, line.provides_interrupt());
+
+ TestOutput(line);
+}
+
+TEST(Digital, OutInterrupt) {
+ TestDigitalOutInterrupt line;
+
+ ASSERT_EQ(false, line.provides_input());
+ ASSERT_EQ(true, line.provides_output());
+ ASSERT_EQ(true, line.provides_interrupt());
+
+ TestOutput(line);
+ TestInterrupt(line);
+}
+
+TEST(Digital, InOut) {
+ TestDigitalInOut line;
+
+ ASSERT_EQ(true, line.provides_input());
+ ASSERT_EQ(true, line.provides_output());
+ ASSERT_EQ(false, line.provides_interrupt());
+
+ TestInput(line);
+ TestOutputReadback(line);
+}
+
+TEST(DigitalIo, InOutInterrupt) {
+ TestDigitalInOutInterrupt line;
+
+ ASSERT_EQ(true, line.provides_input());
+ ASSERT_EQ(true, line.provides_output());
+ ASSERT_EQ(true, line.provides_interrupt());
+
+ TestInput(line);
+ TestOutputReadback(line);
+ TestInterrupt(line);
+}
+
+} // namespace
+} // namespace pw::gpio
diff --git a/pw_gpio/public/pw_gpio/gpio.h b/pw_gpio/public/pw_gpio/gpio.h
new file mode 100644
index 0000000..786c496
--- /dev/null
+++ b/pw_gpio/public/pw_gpio/gpio.h
@@ -0,0 +1,447 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_assert/check.h"
+#include "pw_function/function.h"
+#include "pw_gpio/internal/conversions.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+#include "pw_status/try.h"
+
+namespace pw::gpio {
+
+// The logical state of a digital line.
+enum class State : bool {
+ kActive = true,
+ kInactive = false,
+};
+
+// The triggering configuration for an interrupt handler.
+enum class InterruptTrigger : int {
+ // Trigger on transition from kInactive to kActive.
+ kActivatingEdge,
+ // Trigger on transition from kActive to kInactive.
+ kDeactivatingEdge,
+ // Trigger on any state transition between kActive and kInactive.
+ kBothEdges,
+};
+
+// Interrupt handling function. The argument contains the latest known state of
+// the line. It is backend-specific if, when, and how this state is updated.
+using InterruptHandler = ::pw::Function<void(State sampled_state)>;
+
+// A digital I/O line that may support input, output, and interrupts, but makes
+// no guarantees about whether any operations are supported. You must check the
+// various provides_* flags before calling optional methods. Unsupported methods
+// invoke PW_CRASH.
+//
+// All methods are potentially blocking. Unless otherwise specified, access from
+// multiple threads to a single line must be externally synchronized - for
+// example using pw::Borrowable. Unless otherwise specified, none of the methods
+// are safe to call from an interrupt handler. Therefore, this abstraction may
+// not be suitable for bitbanging and other low-level uses of GPIO.
+//
+// Note that the initial state of a line is not guaranteed to be consistent with
+// either the "enabled" or "disabled" state. Users of the API who need to ensure
+// the line is disabled (ex. output not driving the line) should call Disable.
+//
+// This class should almost never be used in APIs directly. Instead, use one of
+// the derived classes that explicitly supports the functionality that your
+// API needs.
+//
+// This class cannot be extended directly. Instead, extend one of the
+// derived classes that explicitly support the functionality that you want to
+// implement.
+//
+class DigitalIoOptional {
+ public:
+ virtual ~DigitalIoOptional() = default;
+
+ // True if input (getting state) is supported.
+ constexpr bool provides_input() const { return config_.input; }
+ // True if output (setting state) is supported.
+ constexpr bool provides_output() const { return config_.output; }
+ // True if interrupt handlers can be registered.
+ constexpr bool provides_interrupt() const { return config_.interrupt; }
+
+ // Get the state of the line.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Returns:
+ //
+ // OK - an active or inactive state.
+ // FAILED_PRECONDITION - The line has not been enabled.
+ // Other status codes as defined by the backend.
+ //
+ Result<State> GetState() { return DoGetState(); }
+
+ // Set the state of the line.
+ //
+ // Callers are responsible to wait for the voltage level to settle after this
+ // call returns.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Returns:
+ //
+ // OK - the state has been set.
+ // FAILED_PRECONDITION - The line has not been enabled.
+ // Other status codes as defined by the backend.
+ //
+ Status SetState(State state) { return DoSetState(state); }
+
+ // Set an interrupt handler to execute when an interrupt is triggered, and
+ // Configure the condition for triggering the interrupt.
+ //
+ // The handler is executed in a backend-specific context - this may be a
+ // system interrupt handler or a shared notification thread. Do not do any
+ // blocking or expensive work in the handler. The only universally safe
+ // operations are the IRQ-safe functions on pw_sync primitives.
+ //
+ // In particular, it is NOT safe to get the state of a GPIO line - either from
+ // this line or any other DigitalIoOptional instance - inside the handler.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Precondition: no handler is currently set.
+ //
+ // Returns:
+ // OK - the interrupt handler was configured.
+ // INVALID_ARGUMENT - handler is empty.
+ // Other status codes as defined by the backend.
+ //
+ Status SetInterruptHandler(InterruptTrigger trigger,
+ InterruptHandler&& handler) {
+ if (handler == nullptr) {
+ return Status::InvalidArgument();
+ }
+ return DoSetInterruptHandler(trigger, std::move(handler));
+ }
+
+ // Clear the interrupt handler and disable interrupts if enabled.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Returns:
+ // OK - the itnerrupt handler was cleared.
+ // Other status codes as defined by the backend.
+ //
+ Status ClearInterruptHandler() {
+ return DoSetInterruptHandler(InterruptTrigger::kActivatingEdge, nullptr);
+ }
+
+ // Enable interrupts which will trigger the interrupt handler.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Precondition: a handler has been set using SetInterruptHandler.
+ //
+ // Returns:
+ // OK - the interrupt handler was configured.
+ // FAILED_PRECONDITION - The line has not been enabled.
+ // Other status codes as defined by the backend.
+ //
+ Status EnableInterruptHandler() { return DoEnableInterruptHandler(true); }
+
+ // Disable the interrupt handler. This is a no-op if interrupts are disabled.
+ //
+ // This method can be called inside the interrupt handler for this line
+ // without any external synchronization. However, the exact behavior is
+ // backend-specific. There may be queued events that will trigger the handler
+ // again after this call returns.
+ //
+ // Returns:
+ // OK - the interrupt handler was configured.
+ // Other status codes as defined by the backend.
+ //
+ Status DisableInterruptHandler() { return DoEnableInterruptHandler(false); }
+
+ // Enable the line to initialize it into the default state as determined by
+ // the backend. This may enable pull-up/down resistors, drive the line high or
+ // low, etc. The line must be enabled before getting/setting the state
+ // or enabling interrupts.
+ //
+ // Callers are responsible to wait for the voltage level to settle after this
+ // call returns.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Returns:
+ // OK - the line is enabled and ready for use.
+ // Other status codes as defined by the backend.
+ //
+ Status Enable() { return DoEnable(true); }
+
+ // Disable the line to power down any pull-up/down resistors and disconnect
+ // from any voltage sources. This is usually done to save power. Interrupt
+ // handlers are automatically disabled.
+ //
+ // This method is not thread-safe and cannot be used in interrupt handlers.
+ //
+ // Returns:
+ // OK - the line is disabled.
+ // Other status codes as defined by the backend.
+ //
+ Status Disable() { return DoEnable(false); }
+
+ private:
+ friend class DigitalInterrupt;
+ friend class DigitalIn;
+ friend class DigitalInInterrupt;
+ friend class DigitalOut;
+ friend class DigitalOutInterrupt;
+ friend class DigitalInOut;
+ friend class DigitalInOutInterrupt;
+
+ // Private constructor so that only friends can extend us.
+ constexpr DigitalIoOptional(internal::Provides config) : config_(config) {}
+
+ // Implemented by derived classes to provide different functionality.
+ // See the documentation of the public functions for requirements.
+ virtual Status DoEnable(bool enable) = 0;
+ virtual Result<State> DoGetState() = 0;
+ virtual Status DoSetState(State level) = 0;
+ virtual Status DoSetInterruptHandler(InterruptTrigger trigger,
+ InterruptHandler&& handler) = 0;
+ virtual Status DoEnableInterruptHandler(bool enable) = 0;
+
+ // The configuration of this line.
+ const internal::Provides config_;
+};
+
+// A digital I/O line that supports only interrupts.
+//
+// The input and output methods are hidden and must not be called.
+//
+// Use this class in APIs when only interrupt functionality is required.
+// Extend this class to implement a line that only supports interrupts.
+//
+template <>
+struct internal::Requires<class DigitalInterrupt> {
+ static constexpr bool input = false;
+ static constexpr bool output = false;
+ static constexpr bool interrupt = true;
+};
+class DigitalInterrupt
+ : public DigitalIoOptional,
+ public internal::Conversions<DigitalInterrupt, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::SetInterruptHandler;
+
+ protected:
+ constexpr DigitalInterrupt()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInterrupt>()) {}
+
+ private:
+ // Unavailable functionality
+ using DigitalIoOptional::GetState;
+ using DigitalIoOptional::SetState;
+
+ // These overrides invoke PW_CRASH.
+ Status DoSetState(State) final;
+ Result<State> DoGetState() final;
+};
+
+// A digital I/O line that supports only input (getting state).
+//
+// The output and interrupt methods are hidden and must not be called.
+//
+// Use this class in APIs when only input functionality is required.
+// Extend this class to implement a line that only supports getting state.
+//
+class DigitalIn : public DigitalIoOptional,
+ public internal::Conversions<DigitalIn, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::GetState;
+
+ protected:
+ constexpr DigitalIn()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalIn>()) {}
+
+ private:
+ // Unavailable functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::SetInterruptHandler;
+ using DigitalIoOptional::SetState;
+
+ // These overrides invoke PW_CRASH.
+ Status DoSetState(State) final;
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) final;
+ Status DoEnableInterruptHandler(bool) final;
+};
+
+// An input line that supports interrupts.
+//
+// The output methods are hidden and must not be called.
+//
+// Use in APIs when input and interrupt functionality is required.
+//
+// Extend this class to implement a line that supports input (getting state) and
+// listening for interrupts at the same time.
+//
+class DigitalInInterrupt
+ : public DigitalIoOptional,
+ public internal::Conversions<DigitalInInterrupt, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::GetState;
+ using DigitalIoOptional::SetInterruptHandler;
+
+ protected:
+ constexpr DigitalInInterrupt()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInInterrupt>()) {}
+
+ private:
+ // Unavailable functionality
+ using DigitalIoOptional::SetState;
+
+ // These overrides invoke PW_CRASH.
+ Status DoSetState(State) final;
+};
+
+// A digital I/O line that supports only output (setting state).
+//
+// Input and interrupt functions are hidden and must not be called.
+//
+// Use in APIs when only output functionality is required.
+// Extend this class to implement a line that supports output only.
+//
+class DigitalOut : public DigitalIoOptional,
+ public internal::Conversions<DigitalOut, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::SetState;
+
+ protected:
+ constexpr DigitalOut()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalOut>()) {}
+
+ private:
+ // Unavailable functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::GetState;
+ using DigitalIoOptional::SetInterruptHandler;
+
+ // These overrides invoke PW_CRASH.
+ Result<State> DoGetState() final;
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) final;
+ Status DoEnableInterruptHandler(bool) final;
+};
+
+// A digital I/O line that supports output and interrupts.
+//
+// Input methods are hidden and must not be called.
+//
+// Use in APIs when output and interrupt functionality is required. For
+// example, to represent a two-way signalling line.
+//
+// Extend this class to implement a line that supports both output and
+// listening for interrupts at the same time.
+//
+class DigitalOutInterrupt
+ : public DigitalIoOptional,
+ public internal::Conversions<DigitalOutInterrupt, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::SetInterruptHandler;
+ using DigitalIoOptional::SetState;
+
+ protected:
+ constexpr DigitalOutInterrupt()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalOutInterrupt>()) {}
+
+ private:
+ // Unavailable functionality
+ using DigitalIoOptional::GetState;
+
+ // These overrides invoke PW_CRASH.
+ Result<State> DoGetState() final;
+};
+
+// A digital I/O line that supports both input and output.
+//
+// Use in APIs when both input and output functionality is required. For
+// example, to represent a line which is shared by multiple controllers.
+//
+// Extend this class to implement a line that supports both input and output at
+// the same time.
+//
+class DigitalInOut
+ : public DigitalIoOptional,
+ public internal::Conversions<DigitalInOut, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::GetState;
+ using DigitalIoOptional::SetState;
+
+ protected:
+ constexpr DigitalInOut()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInOut>()) {}
+
+ private:
+ // Unavailable functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::SetInterruptHandler;
+
+ // These overrides invoke PW_CRASH.
+ Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) final;
+ Status DoEnableInterruptHandler(bool) final;
+};
+
+// A line that supports input, output, and interrupts.
+//
+// Use in APIs when input, output, and interrupts are required. For example to
+// represent a two-way shared line with state transition notifications.
+//
+// Extend this class to implement a line that supports all the functionality at
+// the same time.
+//
+class DigitalInOutInterrupt
+ : public DigitalIoOptional,
+ public internal::Conversions<DigitalInOutInterrupt, DigitalIoOptional> {
+ public:
+ // Available functionality
+ using DigitalIoOptional::ClearInterruptHandler;
+ using DigitalIoOptional::DisableInterruptHandler;
+ using DigitalIoOptional::EnableInterruptHandler;
+ using DigitalIoOptional::GetState;
+ using DigitalIoOptional::SetInterruptHandler;
+ using DigitalIoOptional::SetState;
+
+ protected:
+ constexpr DigitalInOutInterrupt()
+ : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInOutInterrupt>()) {
+ }
+};
+
+} // namespace pw::gpio
diff --git a/pw_gpio/public/pw_gpio/internal/conversions.h b/pw_gpio/public/pw_gpio/internal/conversions.h
new file mode 100644
index 0000000..99595de
--- /dev/null
+++ b/pw_gpio/public/pw_gpio/internal/conversions.h
@@ -0,0 +1,141 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <type_traits>
+
+namespace pw::gpio {
+namespace internal {
+
+// A type trait that describes the functionality required by a particular type
+// of line, and also the functionality that we assume is always provided by an
+// instance of that line. Used by Converter to determine if a conversion should
+// be allowed.
+//
+// Specialize this for each type of DigitalIO line by defining a required
+// functionality as `true` and optional functionality as `false`.
+//
+// Specializations must define the following fields:
+// static constexpr bool input;
+// static constexpr bool output;
+// static constexpr bool interrupt;
+//
+template <typename T>
+struct Requires;
+
+// Concrete struct describing the available functionality of a line at runtime.
+struct Provides {
+ bool input;
+ bool output;
+ bool interrupt;
+};
+
+// Returns the functionality always provided by the given type.
+template <typename T>
+constexpr Provides AlwaysProvidedBy() {
+ return {
+ .input = Requires<T>::input,
+ .output = Requires<T>::output,
+ .interrupt = Requires<T>::interrupt,
+ };
+}
+
+// Provides conversion operators between line objects based on the available
+// functionality.
+template <typename Self, typename CommonBase>
+class Conversions {
+ private:
+ // Static check to enable conversions from `Self` to `T`.
+ template <
+ typename T,
+ typename = std::enable_if_t<std::is_base_of_v<CommonBase, T>>,
+ typename = std::enable_if_t<Requires<Self>::input || !Requires<T>::input>,
+ typename =
+ std::enable_if_t<Requires<Self>::output || !Requires<T>::output>,
+ typename = std::enable_if_t<Requires<Self>::interrupt ||
+ !Requires<T>::interrupt>>
+ struct Enabled {};
+
+ public:
+ template <typename T, typename = Enabled<T>>
+ constexpr operator T&() {
+ return as<T>();
+ }
+
+ template <typename T, typename = Enabled<T>>
+ constexpr operator const T&() const {
+ return as<T>();
+ }
+
+ template <typename T, typename = Enabled<T>>
+ constexpr T& as() {
+ return static_cast<T&>(static_cast<CommonBase&>(static_cast<Self&>(*this)));
+ }
+
+ template <typename T, typename = Enabled<T>>
+ constexpr const T& as() const {
+ return static_cast<const T&>(
+ static_cast<const CommonBase&>(static_cast<const Self&>(*this)));
+ }
+};
+
+} // namespace internal
+
+// Specializations of Requires for each of the line types.
+// These live outside the `internal` namespace so that the forward class
+// declarations are in the correct namespace.
+
+template <>
+struct internal::Requires<class DigitalIn> {
+ static constexpr bool input = true;
+ static constexpr bool output = false;
+ static constexpr bool interrupt = false;
+};
+
+template <>
+struct internal::Requires<class DigitalInInterrupt> {
+ static constexpr bool input = true;
+ static constexpr bool output = false;
+ static constexpr bool interrupt = true;
+};
+
+template <>
+struct internal::Requires<class DigitalOut> {
+ static constexpr bool input = false;
+ static constexpr bool output = true;
+ static constexpr bool interrupt = false;
+};
+
+template <>
+struct internal::Requires<class DigitalOutInterrupt> {
+ static constexpr bool input = false;
+ static constexpr bool output = true;
+ static constexpr bool interrupt = true;
+};
+
+template <>
+struct internal::Requires<class DigitalInOut> {
+ static constexpr bool input = true;
+ static constexpr bool output = true;
+ static constexpr bool interrupt = false;
+};
+
+template <>
+struct internal::Requires<class DigitalInOutInterrupt> {
+ static constexpr bool input = true;
+ static constexpr bool output = true;
+ static constexpr bool interrupt = true;
+};
+
+} // namespace pw::gpio