utility/aspeed: Add gpio name to offset method

Useful for converting from the GPIO name listed in the chip or board
schematics to the offset which would be presented by the kernel. Made
constexpr so that the conversions can be done at compile time.

Ex:
    gpioplus::utility::aspeed::nameToOffset("B3") -> 11

Tested:
    Built and run through the unit test suite.

Change-Id: I9ec859bf8fb6c60f15e149779d1bd3eb94fc1bd6
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index 324af30..9154cb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,3 +46,4 @@
 /test/event
 /test/handle
 /test/internal/fd
+/test/utility/aspeed
diff --git a/src/Makefile.am b/src/Makefile.am
index d7872a6..e366846 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -20,3 +20,5 @@
 libgpioplus_la_SOURCES += gpioplus/internal/sys.cpp
 
 nobase_include_HEADERS += gpioplus/test/sys.hpp
+
+nobase_include_HEADERS += gpioplus/utility/aspeed.hpp
diff --git a/src/gpioplus/utility/aspeed.hpp b/src/gpioplus/utility/aspeed.hpp
new file mode 100644
index 0000000..d3acecc
--- /dev/null
+++ b/src/gpioplus/utility/aspeed.hpp
@@ -0,0 +1,67 @@
+#pragma once
+#include <stdexcept>
+#include <string_view>
+
+namespace gpioplus
+{
+namespace utility
+{
+namespace aspeed
+{
+
+/** @brief Converts an aspeed gpio label name into an offset usable
+ *         by a gpioplus line.
+ *         Ex. GPION3 -> nameToOffset("N3") -> 107
+ *
+ *         NOTE: This function is constexpr, but uses exceptions for error
+ *         cases. If you want compile time guarantees of validity, make sure
+ *         you assign to a constexpr lvalue. Otherwise the error will be
+ *         resolved at runtime.
+ *         Ex. constexpr uint32_t offset = nameToOffset("A7");
+ *
+ *  @param[in] name - The gpio chip which provides the events
+ *  @throws std::logic_error when provided an invalid name.
+ *  @return
+ */
+constexpr uint32_t nameToOffset(std::string_view name)
+{
+    // Validate the name [A-Z]+[0-9]
+    if (name.length() < 2)
+    {
+        throw std::logic_error("GPIO name is too short");
+    }
+
+    char octal = name[name.length() - 1];
+    if (octal < '0' || octal > '7')
+    {
+        throw std::logic_error("GPIO name must end with an octal");
+    }
+
+    auto letters = name.substr(0, name.length() - 1);
+    for (auto letter : letters)
+    {
+        if (letter < 'A' || letter > 'Z')
+        {
+            throw std::logic_error("GPIO name must start with letters");
+        }
+    }
+
+    // Limit the gpio to reasonable values
+    // Current chips have no more than AB7 or 224 gpios
+    if (name.length() > 3 || (name.length() == 3 && name[0] != 'A'))
+    {
+        throw std::logic_error("GPIO name probably not valid");
+    }
+
+    uint32_t offset = 0;
+    for (auto letter : letters)
+    {
+        offset = offset * 26 + (letter - 'A') + 1;
+    }
+    offset = (offset - 1) * 8 + (octal - '0');
+    return offset;
+}
+
+} // namespace aspeed
+} // namespace utility
+} // namespace gpioplus
diff --git a/test/Makefile.am b/test/Makefile.am
index 62fc099..28bab29 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -26,3 +26,8 @@
 internal_fd_SOURCES = internal/fd.cpp
 internal_fd_CPPFLAGS = $(gtest_cppflags)
 internal_fd_LDADD = $(gtest_ldadd)
+
+check_PROGRAMS += utility/aspeed
+utility_aspeed_SOURCES = utility/aspeed.cpp
+utility_aspeed_CPPFLAGS = $(gtest_cppflags)
+utility_aspeed_LDADD = $(gtest_ldadd)
diff --git a/test/utility/aspeed.cpp b/test/utility/aspeed.cpp
new file mode 100644
index 0000000..90580b5
--- /dev/null
+++ b/test/utility/aspeed.cpp
@@ -0,0 +1,46 @@
+#include <gpioplus/utility/aspeed.hpp>
+#include <gtest/gtest.h>
+
+namespace gpioplus
+{
+namespace utility
+{
+namespace aspeed
+{
+namespace
+{
+
+TEST(AspeedTest, NameToOffset)
+{
+    EXPECT_EQ(5, nameToOffset("A5"));
+    EXPECT_EQ(33, nameToOffset("E1"));
+    EXPECT_EQ(202, nameToOffset("Z2"));
+    EXPECT_EQ(208, nameToOffset("AA0"));
+    EXPECT_EQ(223, nameToOffset("AB7"));
+}
+
+TEST(AspeedTest, NameToOffsetShort)
+{
+    EXPECT_THROW(nameToOffset(""), std::logic_error);
+    EXPECT_THROW(nameToOffset("A"), std::logic_error);
+    EXPECT_THROW(nameToOffset("0"), std::logic_error);
+}
+
+TEST(AspeedTest, NameToOffsetBad)
+{
+    EXPECT_THROW(nameToOffset("00"), std::logic_error);
+    EXPECT_THROW(nameToOffset("AB"), std::logic_error);
+    EXPECT_THROW(nameToOffset(".1"), std::logic_error);
+    EXPECT_THROW(nameToOffset("A#"), std::logic_error);
+}
+
+TEST(AspeedTest, NameToOffsetMaybeBad)
+{
+    EXPECT_THROW(nameToOffset("BA0"), std::logic_error);
+    EXPECT_THROW(nameToOffset("AAA0"), std::logic_error);
+}
+
+} // namespace
+} // namespace aspeed
+} // namespace utility
+} // namespace gpioplus