From 2fb78caa55db4487cf60f195358e416905eac569 Mon Sep 17 00:00:00 2001
From: trilader <trilader@schroedingers-bit.net>
Date: Mon, 19 Aug 2019 10:37:58 +0200
Subject: [PATCH] feat(epicardium): Add (wristband) GPIO module

---
 epicardium/epicardium.h        | 131 +++++++++++++++++++++++++++++++++
 epicardium/modules/gpio.c      | 100 +++++++++++++++++++++++++
 epicardium/modules/meson.build |   1 +
 3 files changed, 232 insertions(+)
 create mode 100644 epicardium/modules/gpio.c

diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index ce254cb6..96684ae3 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -90,6 +90,11 @@ typedef _Bool bool;
 #define API_LIGHT_SENSOR_STOP      0x82
 
 #define API_BUTTONS_READ           0x90
+
+#define API_GPIO_SET_PIN_MODE      0xA0
+#define API_GPIO_GET_PIN_MODE      0xA1
+#define API_GPIO_WRITE_PIN         0xA2
+#define API_GPIO_READ_PIN          0xA3
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -338,6 +343,132 @@ enum epic_button {
  */
 API(API_BUTTONS_READ, uint8_t epic_buttons_read(uint8_t mask));
 
+/**
+ * Wristband GPIO
+ * ==============
+ */
+
+/** GPIO pins IDs */
+enum gpio_pin {
+    /** ``1``, Wristband connector 1 */
+    GPIO_WRISTBAND_1 = 1,
+    /** ``2``, Wristband connector 2 */
+    GPIO_WRISTBAND_2 = 2,
+    /** ``3``, Wristband connector 3 */
+    GPIO_WRISTBAND_3 = 3,
+    /** ``4``, Wristband connector 4 */
+    GPIO_WRISTBAND_4 = 4,
+};
+
+/** GPIO pin modes */
+enum gpio_mode {
+    /** Configure the pin as input */
+    GPIO_MODE_IN = (1<<0),
+    /** Configure the pin as output */
+    GPIO_MODE_OUT = (1<<1),
+
+    /** Enable the internal pull-up resistor */
+    GPIO_PULL_UP = (1<<6),
+    /** Enable the internal pull-down resistor */
+    GPIO_PULL_DOWN = (1<<7),
+};
+
+/**
+ * Set the mode of a card10 GPIO pin.
+ *
+ * :c:func:`epic_gpio_set_pin_mode` will set the pin specified by ``pin`` to the mode ``mode``.
+ * If the specified pin ID is not valid this function will do nothing.
+ *
+ * **Example:**
+ *
+ * .. code-block:: cpp
+ *
+ *    #include "epicardium.h"
+ *
+ *    // Configure wristband pin 1 as output.
+ *    if (epic_gpio_set_pin_mode(GPIO_WRISTBAND_1, GPIO_MODE_OUT)) {
+ *        // Do your error handling here...
+ *    }
+ *
+ * :param uint8_t pin: ID of the pin to configure. Use on of the IDs defined in :c:type:`gpio_pin`.
+ * :param uint8_t mode: Mode to be configured. Use a combination of the :c:type:`gpio_mode` flags.
+ * :returns: ``0`` if the mode was set, ``-EINVAL`` if ``pin`` is not valid or the mode could not be set.
+ */
+API(API_GPIO_SET_PIN_MODE, int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode));
+
+/**
+ * Get the mode of a card10 GPIO pin.
+ *
+ * :c:func:`epic_gpio_get_pin_mode` will get the current mode of the GPIO pin specified by ``pin``.
+ *
+ * **Example:**
+ *
+ * .. code-block:: cpp
+ *
+ *    #include "epicardium.h"
+ *
+ *    // Get the mode of wristband pin 1.
+ *    int mode = epic_gpio_get_pin_mode(GPIO_WRISTBAND_1);
+ *    if (mode < 0) {
+ *        // Do your error handling here...
+ *    } else {
+ *        // Do something with the queried mode information
+ *    }
+ *
+ * :param uint8_t pin: ID of the pin to get the configuration of. Use on of the IDs defined in :c:type:`gpio_pin`.
+ * :returns: Configuration byte for the specified pin or ``-EINVAL`` if the pin is not valid.
+ */
+API(API_GPIO_GET_PIN_MODE, int epic_gpio_get_pin_mode(uint8_t pin));
+
+/**
+ * Write value to a card10 GPIO pin,
+ *
+ * :c:func:`epic_gpio_write_pin` will set the value of the GPIO pin described by ``pin`` to either on or off depending on ``on``.
+ *
+ * **Example:**
+ *
+ * .. code-block:: cpp
+ *
+ *    #include "epicardium.h"
+ *
+ *    // Get the mode of wristband pin 1.
+ *    int mode = epic_gpio_get_pin_mode(GPIO_WRISTBAND_1);
+ *    if (mode < 0) {
+ *        // Do your error handling here...
+ *    } else {
+ *        // Do something with the queried mode information
+ *    }
+ *
+ * :param uint8_t pin: ID of the pin to get the configuration of. Use on of the IDs defined in :c:type:`gpio_pin`.
+ * :param bool on: Sets the pin to either true (on/high) or false (off/low).
+ * :returns: ``0`` on succcess, ``-EINVAL`` if ``pin`` is not valid or is not configured as an output.
+ */
+API(API_GPIO_WRITE_PIN, int epic_gpio_write_pin(uint8_t pin, bool on));
+
+/**
+ * Read value of a card10 GPIO pin.
+ *
+ * :c:func:`epic_gpio_read_pin` will get the value of the GPIO pin described by ``pin``.
+ *
+ * **Example:**
+ *
+ * .. code-block:: cpp
+ *
+ *    #include "epicardium.h"
+ *
+ *    // Get the current value of wristband pin 1.
+ *    uint32_t value = epic_gpio_read_pin(GPIO_WRISTBAND_1);
+ *    if (mode == -EINVAL) {
+ *        // Do your error handling here...
+ *    } else {
+ *        // Do something with the current value
+ *    }
+ *
+ * :param uint8_t pin: ID of the pin to get the configuration of. Use on of the IDs defined in :c:type:`gpio_pin`.
+ * :returns: ``-EINVAL`` if ``pin`` is not valid, an integer value otherwise.
+ */
+API(API_GPIO_READ_PIN, uint32_t epic_gpio_read_pin(uint8_t pin));
+
 /**
  * LEDs
  * ====
diff --git a/epicardium/modules/gpio.c b/epicardium/modules/gpio.c
new file mode 100644
index 00000000..b6bc4f2c
--- /dev/null
+++ b/epicardium/modules/gpio.c
@@ -0,0 +1,100 @@
+#include "epicardium.h"
+#include "gpio.h"
+#include "max32665.h"
+#include "mxc_errors.h"
+
+/*
+ * Despite what the schematic (currently, 2019-08-18) says these are the correct
+ * pins for wristband GPIO 1-4 (not 0-3 as the schematic states)
+ */
+static gpio_cfg_t gpio_configs[] = {
+	[GPIO_WRISTBAND_1] = { PORT_0, PIN_21, GPIO_FUNC_OUT, GPIO_PAD_NONE },
+	[GPIO_WRISTBAND_2] = { PORT_0, PIN_22, GPIO_FUNC_OUT, GPIO_PAD_NONE },
+	[GPIO_WRISTBAND_3] = { PORT_0, PIN_29, GPIO_FUNC_OUT, GPIO_PAD_NONE },
+	[GPIO_WRISTBAND_4] = { PORT_0, PIN_20, GPIO_FUNC_OUT, GPIO_PAD_NONE },
+};
+
+int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode)
+{
+	if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
+		return -EINVAL;
+
+	gpio_cfg_t *cfg = &gpio_configs[pin];
+
+	bool is_input  = (mode & GPIO_MODE_IN) == GPIO_MODE_IN;
+	bool is_output = (mode & GPIO_MODE_OUT) == GPIO_MODE_OUT;
+
+	// Pins can't be input and output at the same time.
+	if (is_input && is_output)
+		return -EINVAL;
+
+	uint32_t func_value = 0;
+	if (is_input)
+		func_value |= GPIO_FUNC_IN;
+	if (is_output)
+		func_value |= GPIO_FUNC_OUT;
+
+	uint32_t pad_value = 0;
+	if (mode & GPIO_PULL_UP)
+		pad_value |= GPIO_PAD_PULL_UP;
+	if (mode & GPIO_PULL_DOWN)
+		pad_value |= GPIO_PAD_PULL_DOWN;
+
+	cfg->func = func_value;
+	cfg->pad  = pad_value;
+
+	if (GPIO_Config(cfg) != E_NO_ERROR)
+		return -EINVAL;
+	return 0;
+}
+
+int epic_gpio_get_pin_mode(uint8_t pin)
+{
+	if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
+		return -EINVAL;
+
+	gpio_cfg_t *cfg = &gpio_configs[pin];
+	int res         = 0;
+	if ((cfg->func & GPIO_FUNC_IN) == GPIO_FUNC_IN)
+		res |= GPIO_MODE_IN;
+	if ((cfg->func & GPIO_FUNC_OUT) == GPIO_FUNC_OUT)
+		res |= GPIO_MODE_OUT;
+	if ((cfg->pad & GPIO_PAD_PULL_UP) == GPIO_PAD_PULL_UP)
+		res |= GPIO_PULL_UP;
+	if ((cfg->pad & GPIO_PAD_PULL_DOWN) == GPIO_PAD_PULL_DOWN)
+		res |= GPIO_PULL_DOWN;
+
+	return res;
+}
+
+int epic_gpio_write_pin(uint8_t pin, bool on)
+{
+	if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
+		return -EINVAL;
+
+	gpio_cfg_t *cfg = &gpio_configs[pin];
+	if ((cfg->func & GPIO_FUNC_IN) == GPIO_FUNC_IN)
+		return -EINVAL;
+
+	if (on)
+		GPIO_OutSet(cfg);
+	else
+		GPIO_OutClr(cfg);
+
+	return 0;
+}
+
+uint32_t epic_gpio_read_pin(uint8_t pin)
+{
+	if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
+		return -EINVAL;
+
+	gpio_cfg_t *cfg = &gpio_configs[pin];
+	if ((cfg->func & GPIO_FUNC_OUT) == GPIO_FUNC_OUT) {
+		return GPIO_OutGet(cfg);
+	} else if ((cfg->func & GPIO_FUNC_IN) == GPIO_FUNC_IN) {
+		return GPIO_InGet(cfg);
+	} else {
+		return -EINVAL;
+	}
+}
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index 3389a409..61e0cc63 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -3,6 +3,7 @@ module_sources = files(
   'dispatcher.c',
   'display.c',
   'fileops.c',
+  'gpio.c',
   'hardware.c',
   'leds.c',
   'lifecycle.c',
-- 
GitLab