From 4d81b10ba7047a4dbd63cfe33ac879ecf437e108 Mon Sep 17 00:00:00 2001
From: Mega Mind <68985133+megamind4089@users.noreply.github.com>
Date: Tue, 6 Oct 2020 15:52:21 +0800
Subject: [PATCH] Added driver to control the external power output

This PR adds support to control the external power output from controllers like nice!nano, nRFMicro etc

I have implemented based on my understanding of Pete suggestion on this feature.

Testing done:

    Tested by enabling and disabling the ext_power from application and verified
    Verified the application does not crash with boards that does not have ext_power support
    Note:
    I did not test this in nice!nano since I don't have the boards. Will get help from others once the behavior PR is up

Next Steps:

    Create a behavior PR to control enable/disable ext_power
---
 app/CMakeLists.txt                            |   1 +
 app/Kconfig                                   |   4 +
 app/boards/arm/nice_nano/nice_nano.dts        |   5 +
 app/boards/arm/nrfmicro/nrfmicro_11.dts       |   5 +
 .../arm/nrfmicro/nrfmicro_11_flipped.dts      |   5 +
 app/boards/arm/nrfmicro/nrfmicro_13.dts       |   5 +
 app/boards/arm/nrfmicro/pinmux.c              |  11 --
 .../zmk,behavior-sensor-rotate-key-press.yaml |   2 +-
 app/dts/bindings/zmk,ext-power-generic.yaml   |  20 ++++
 app/dts/bindings/zmk,keymap-sensors.yaml      |   5 +
 app/include/drivers/ext_power.h               | 104 ++++++++++++++++++
 app/src/ext_power_generic.c                   |  91 +++++++++++++++
 app/src/main.c                                |   9 ++
 13 files changed, 255 insertions(+), 12 deletions(-)
 create mode 100644 app/dts/bindings/zmk,ext-power-generic.yaml
 create mode 100644 app/include/drivers/ext_power.h
 create mode 100644 app/src/ext_power_generic.c

diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 3e59d751..31d28f57 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -31,6 +31,7 @@ target_sources(app PRIVATE src/sensors.c)
 target_sources_ifdef(CONFIG_ZMK_DISPLAY app PRIVATE src/display.c)
 target_sources(app PRIVATE src/event_manager.c)
 target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble_unpair_combo.c)
+target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/ext_power_generic.c)
 target_sources(app PRIVATE src/events/position_state_changed.c)
 target_sources(app PRIVATE src/events/keycode_state_changed.c)
 target_sources(app PRIVATE src/events/modifiers_state_changed.c)
diff --git a/app/Kconfig b/app/Kconfig
index edf58670..fca49124 100644
--- a/app/Kconfig
+++ b/app/Kconfig
@@ -95,6 +95,10 @@ config ZMK_IDLE_SLEEP_TIMEOUT
 
 endif
 
+config ZMK_EXT_POWER
+	bool "Enable support to control external power output"
+	default y
+
 config ZMK_DISPLAY
 	bool "ZMK display support"
 	default n
diff --git a/app/boards/arm/nice_nano/nice_nano.dts b/app/boards/arm/nice_nano/nice_nano.dts
index 3ffb0ea8..0538b1df 100644
--- a/app/boards/arm/nice_nano/nice_nano.dts
+++ b/app/boards/arm/nice_nano/nice_nano.dts
@@ -29,6 +29,11 @@
 		};
 	};
 
+	ext-power {
+		compatible = "zmk,ext-power-generic";
+		label = "EXT_POWER";
+		control-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
+	};
 };
 
 &gpiote {
diff --git a/app/boards/arm/nrfmicro/nrfmicro_11.dts b/app/boards/arm/nrfmicro/nrfmicro_11.dts
index 95bd8adc..87c650e1 100644
--- a/app/boards/arm/nrfmicro/nrfmicro_11.dts
+++ b/app/boards/arm/nrfmicro/nrfmicro_11.dts
@@ -26,6 +26,11 @@
 		};
 	};
 
+	ext-power {
+		compatible = "zmk,ext-power-generic";
+		label = "EXT_POWER";
+		control-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
+	};
 };
 
 &gpio0 {
diff --git a/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts b/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts
index 85693a8b..ea15b819 100644
--- a/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts
+++ b/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts
@@ -26,6 +26,11 @@
 		};
 	};
 
+	ext-power {
+		compatible = "zmk,ext-power-generic";
+		label = "EXT_POWER";
+		control-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
+	};
 };
 
 &gpio0 {
diff --git a/app/boards/arm/nrfmicro/nrfmicro_13.dts b/app/boards/arm/nrfmicro/nrfmicro_13.dts
index 95bd8adc..ef439462 100644
--- a/app/boards/arm/nrfmicro/nrfmicro_13.dts
+++ b/app/boards/arm/nrfmicro/nrfmicro_13.dts
@@ -26,6 +26,11 @@
 		};
 	};
 
+	ext-power {
+		compatible = "zmk,ext-power-generic";
+		label = "EXT_POWER";
+		control-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
+	};
 };
 
 &gpio0 {
diff --git a/app/boards/arm/nrfmicro/pinmux.c b/app/boards/arm/nrfmicro/pinmux.c
index 4e330b65..30117d0f 100644
--- a/app/boards/arm/nrfmicro/pinmux.c
+++ b/app/boards/arm/nrfmicro/pinmux.c
@@ -14,25 +14,14 @@
 static int pinmux_nrfmicro_init(struct device *port) {
     ARG_UNUSED(port);
 
-    struct device *p1 = device_get_binding("GPIO_1");
-
 #if CONFIG_BOARD_NRFMICRO_13
     struct device *p0 = device_get_binding("GPIO_0");
-    // enable EXT_VCC (use 0 for nRFMicro 1.3, use 1 for nRFMicro 1.1)
-    gpio_pin_configure(p1, 9, GPIO_OUTPUT);
-    gpio_pin_set(p1, 9, 0);
-
 #if CONFIG_BOARD_NRFMICRO_CHARGER
     gpio_pin_configure(p0, 5, GPIO_OUTPUT);
     gpio_pin_set(p0, 5, 0);
 #else
     gpio_pin_configure(p0, 5, GPIO_INPUT);
 #endif
-
-#else
-    // enable EXT_VCC (use 0 for nRFMicro 1.3, use 1 for nRFMicro 1.1)
-    gpio_pin_configure(p1, 9, GPIO_OUTPUT);
-    gpio_pin_set(p1, 9, 1);
 #endif
     return 0;
 }
diff --git a/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml
index bbf35373..6b339107 100644
--- a/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml
+++ b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml
@@ -1,4 +1,4 @@
-# Copyright (c) 2020, Pete Johanson
+# Copyright (c) 2020, The ZMK Contributors
 # SPDX-License-Identifier: MIT
 
 description: Sensor rotate key press/release behavior
diff --git a/app/dts/bindings/zmk,ext-power-generic.yaml b/app/dts/bindings/zmk,ext-power-generic.yaml
new file mode 100644
index 00000000..5a38a09a
--- /dev/null
+++ b/app/dts/bindings/zmk,ext-power-generic.yaml
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2020, The ZMK Contributors
+# SPDX-License-Identifier: MIT
+#
+
+description: |
+  Generic driver for controlling the external power output
+  by toggling the control-gpio pin status
+  (Only in supported hardware)
+
+compatible: "zmk,ext-power-generic"
+
+properties:
+  control-gpios:
+    type: phandle-array
+    required: true
+  label:
+    type: string
+    required: true
+
diff --git a/app/dts/bindings/zmk,keymap-sensors.yaml b/app/dts/bindings/zmk,keymap-sensors.yaml
index c56361d1..86ae5c22 100644
--- a/app/dts/bindings/zmk,keymap-sensors.yaml
+++ b/app/dts/bindings/zmk,keymap-sensors.yaml
@@ -1,3 +1,8 @@
+#
+# Copyright (c) 2020, The ZMK Contributors
+# SPDX-License-Identifier: MIT
+#
+
 description: |
   Allows defining the collection of sensors bound in the keymap layers
 
diff --git a/app/include/drivers/ext_power.h b/app/include/drivers/ext_power.h
new file mode 100644
index 00000000..6c1923e8
--- /dev/null
+++ b/app/include/drivers/ext_power.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <zephyr/types.h>
+#include <stddef.h>
+#include <device.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @cond INTERNAL_HIDDEN
+ *
+ * Behavior driver API definition and system call entry points.
+ *
+ * (Internal use only.)
+ */
+
+typedef int (*ext_power_enable_t)(struct device *dev);
+typedef int (*ext_power_disable_t)(struct device *dev);
+typedef int (*ext_power_get_t)(struct device *dev);
+
+__subsystem struct ext_power_api {
+    ext_power_enable_t enable;
+    ext_power_disable_t disable;
+    ext_power_get_t get;
+};
+/**
+ * @endcond
+ */
+
+/**
+ * @brief Enable the external power output
+ * @param dev Pointer to the device structure for the driver instance.
+ *
+ * @retval 0 If successful.
+ * @retval Negative errno code if failure.
+ */
+__syscall int ext_power_enable(struct device *dev);
+
+static inline int z_impl_ext_power_enable(struct device *dev) {
+    const struct ext_power_api *api = (const struct ext_power_api *)dev->driver_api;
+
+    if (api->enable == NULL) {
+        return -ENOTSUP;
+    }
+
+    return api->enable(dev);
+}
+
+/**
+ * @brief Disable the external power output
+ * @param dev Pointer to the device structure for the driver instance.
+ *
+ * @retval 0 If successful.
+ * @retval Negative errno code if failure.
+ */
+__syscall int ext_power_disable(struct device *dev);
+
+static inline int z_impl_ext_power_disable(struct device *dev) {
+    const struct ext_power_api *api = (const struct ext_power_api *)dev->driver_api;
+
+    if (api->disable == NULL) {
+        return -ENOTSUP;
+    }
+
+    return api->disable(dev);
+}
+
+/**
+ * @brief Get the current status of the external power output
+ * @param dev Pointer to the device structure for the driver instance.
+ *
+ * @retval 0 If ext power is disabled.
+ * @retval 1 if ext power is enabled.
+ * @retval Negative errno code if failure.
+ */
+__syscall int ext_power_get(struct device *dev);
+
+static inline int z_impl_ext_power_get(struct device *dev) {
+    const struct ext_power_api *api = (const struct ext_power_api *)dev->driver_api;
+
+    if (api->get == NULL) {
+        return -ENOTSUP;
+    }
+
+    return api->get(dev);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * @}
+ */
+
+#include <syscalls/ext_power.h>
diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c
new file mode 100644
index 00000000..48170304
--- /dev/null
+++ b/app/src/ext_power_generic.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_ext_power_generic
+
+#include <device.h>
+#include <init.h>
+#include <drivers/gpio.h>
+#include <drivers/ext_power.h>
+
+#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
+
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+struct ext_power_generic_config {
+    const char *label;
+    const u8_t pin;
+    const u8_t flags;
+};
+
+struct ext_power_generic_data {
+    struct device *gpio;
+    bool status;
+};
+
+static int ext_power_generic_enable(struct device *dev) {
+    struct ext_power_generic_data *data = dev->driver_data;
+    const struct ext_power_generic_config *config = dev->config_info;
+
+    if (gpio_pin_set(data->gpio, config->pin, 1)) {
+        LOG_WRN("Failed to set ext-power control pin");
+        return -EIO;
+    }
+    data->status = true;
+    return 0;
+}
+
+static int ext_power_generic_disable(struct device *dev) {
+    struct ext_power_generic_data *data = dev->driver_data;
+    const struct ext_power_generic_config *config = dev->config_info;
+
+    if (gpio_pin_set(data->gpio, config->pin, 0)) {
+        LOG_WRN("Failed to clear ext-power control pin");
+        return -EIO;
+    }
+    data->status = false;
+    return 0;
+}
+
+static int ext_power_generic_get(struct device *dev) {
+    struct ext_power_generic_data *data = dev->driver_data;
+    return data->status;
+}
+
+static int ext_power_generic_init(struct device *dev) {
+    struct ext_power_generic_data *data = dev->driver_data;
+    const struct ext_power_generic_config *config = dev->config_info;
+
+    data->gpio = device_get_binding(config->label);
+    if (data->gpio == NULL) {
+        LOG_ERR("Failed to get ext-power control device");
+        return -EINVAL;
+    }
+
+    if (gpio_pin_configure(data->gpio, config->pin, config->flags | GPIO_OUTPUT)) {
+        LOG_ERR("Failed to configure ext-power control pin");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static const struct ext_power_generic_config config = {
+    .label = DT_INST_GPIO_LABEL(0, control_gpios),
+    .pin = DT_INST_GPIO_PIN(0, control_gpios),
+    .flags = DT_INST_GPIO_FLAGS(0, control_gpios)};
+
+static struct ext_power_generic_data data = {.status = false};
+
+static const struct ext_power_api api = {.enable = ext_power_generic_enable,
+                                         .disable = ext_power_generic_disable,
+                                         .get = ext_power_generic_get};
+
+DEVICE_AND_API_INIT(ext_power_generic, DT_INST_LABEL(0), ext_power_generic_init, &data, &config,
+                    APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &api);
+
+#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
diff --git a/app/src/main.c b/app/src/main.c
index dca923e9..0551356d 100644
--- a/app/src/main.c
+++ b/app/src/main.c
@@ -15,16 +15,25 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
 #include <zmk/matrix.h>
 #include <zmk/kscan.h>
 #include <zmk/display.h>
+#include <drivers/ext_power.h>
 
 #define ZMK_KSCAN_DEV DT_LABEL(ZMK_MATRIX_NODE_ID)
 
 void main(void) {
+    struct device *ext_power;
     LOG_INF("Welcome to ZMK!\n");
 
     if (zmk_kscan_init(ZMK_KSCAN_DEV) != 0) {
         return;
     }
 
+    // Enable the external VCC output
+    ext_power = device_get_binding("EXT_POWER");
+    if (ext_power != NULL) {
+        const struct ext_power_api *ext_power_api = ext_power->driver_api;
+        ext_power_api->enable(ext_power);
+    }
+
 #ifdef CONFIG_ZMK_DISPLAY
     zmk_display_init();