From 3082455aecc98532807aba3226321a94d2ed4b0c Mon Sep 17 00:00:00 2001
From: Nick <nick.win999@gmail.com>
Date: Fri, 23 Oct 2020 00:45:59 -0500
Subject: [PATCH] Refactor driver to use Sensor API

---
 .../arm/bluemicro840/bluemicro840_v1.dts      |   8 +
 app/boards/arm/nice_nano/nice_nano.dts        |   1 +
 app/boards/arm/nrfmicro/nrfmicro_13.dts       |   8 +
 app/drivers/zephyr/battery_voltage_divider.c  | 187 +++++++++++++-----
 .../bindings/zmk,battery-voltage-divider.yaml |   5 +
 5 files changed, 162 insertions(+), 47 deletions(-)

diff --git a/app/boards/arm/bluemicro840/bluemicro840_v1.dts b/app/boards/arm/bluemicro840/bluemicro840_v1.dts
index c693662a..ac8ba56e 100644
--- a/app/boards/arm/bluemicro840/bluemicro840_v1.dts
+++ b/app/boards/arm/bluemicro840/bluemicro840_v1.dts
@@ -29,6 +29,14 @@
 		};
 	};
 
+	vbatt {
+		compatible = "zmk,battery-voltage-divider";
+		label = "VOLTAGE_DIVIDER";
+		io-channels = <&adc 7>;
+		output-ohms = <2000000>;
+		full-ohms = <(2000000 + 806000)>;
+	};
+
 };
 
 &gpio0 {
diff --git a/app/boards/arm/nice_nano/nice_nano.dts b/app/boards/arm/nice_nano/nice_nano.dts
index 18312ecc..72804e3c 100644
--- a/app/boards/arm/nice_nano/nice_nano.dts
+++ b/app/boards/arm/nice_nano/nice_nano.dts
@@ -31,6 +31,7 @@
 
 	vbatt {
 		compatible = "zmk,battery-voltage-divider";
+		label = "VOLTAGE_DIVIDER";
 		io-channels = <&adc 2>;
 		output-ohms = <2000000>;
 		full-ohms = <(2000000 + 806000)>;
diff --git a/app/boards/arm/nrfmicro/nrfmicro_13.dts b/app/boards/arm/nrfmicro/nrfmicro_13.dts
index 95bd8adc..840014ad 100644
--- a/app/boards/arm/nrfmicro/nrfmicro_13.dts
+++ b/app/boards/arm/nrfmicro/nrfmicro_13.dts
@@ -26,6 +26,14 @@
 		};
 	};
 
+	vbatt {
+		compatible = "zmk,battery-voltage-divider";
+		label = "VOLTAGE_DIVIDER";
+		io-channels = <&adc 2>;
+		output-ohms = <2000000>;
+		full-ohms = <(2000000 + 820000)>;
+	};
+
 };
 
 &gpio0 {
diff --git a/app/drivers/zephyr/battery_voltage_divider.c b/app/drivers/zephyr/battery_voltage_divider.c
index 34de6cf1..abe7cb5f 100644
--- a/app/drivers/zephyr/battery_voltage_divider.c
+++ b/app/drivers/zephyr/battery_voltage_divider.c
@@ -16,97 +16,190 @@
 
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
-#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
-
-#define VBATT DT_PATH(vbatt)
-
-struct battery_config {
-    struct device *adc;
-    struct adc_channel_cfg acc;
-    struct adc_sequence as;
-    int16_t adc_raw;
+struct io_channel_config {
+	const char *label;
+	uint8_t channel;
 };
 
-static struct battery_config battery_config;
+struct gpio_channel_config {
+	const char *label;
+	uint8_t pin;
+	uint8_t flags;
+};
 
-static int lithium_ion_mv_to_pct(int16_t bat_mv) {
+struct bvd_config {
+	struct io_channel_config io_channel;
+	struct gpio_channel_config power_gpios;
+	uint32_t output_ohm;
+	uint32_t full_ohm;
+};
+
+struct bvd_data {
+    struct device *adc;
+    struct device *gpio;
+    struct adc_channel_cfg acc;
+    struct adc_sequence as;
+    uint16_t adc_raw;
+    uint16_t voltage;
+    uint8_t state_of_charge;
+};
+
+static uint8_t lithium_ion_mv_to_pct(int16_t bat_mv) {
     // Magic function that maps mV to this discharge graph from adafruit:
     // https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages
     return round(106.818 +
                  (-0.032685 - 106.818) / pow(1 + pow(bat_mv / 3679.35, 58.979), 0.347386));
 }
 
-static void battery_read(struct k_work *workd) {
-    struct battery_config *cfg = &battery_config;
-    struct adc_sequence *as = &cfg->as;
+static int bvd_sample_fetch(struct device *dev, enum sensor_channel chan) {
+    struct bvd_data *drv_data = dev->driver_data;
+    const struct bvd_config *drv_cfg = dev->config_info;
+    struct adc_sequence *as = &drv_data->as;
 
-    int rc = adc_read(cfg->adc, as);
+    int rc = 0;
+
+    // Enable power GPIO if present
+    if (drv_data->gpio) {
+        rc = gpio_pin_set(drv_data->gpio, drv_cfg->power_gpios.pin, 1);
+
+        if (rc != 0) {
+            LOG_DBG("Failed to enable ADC power GPIO: %d", rc);
+            return rc;
+        }
+    }
+
+    // Read ADC
+    rc = adc_read(drv_data->adc, as);
     as->calibrate = false;
+
     if (rc == 0) {
-        int32_t val = cfg->adc_raw;
+        int32_t val = drv_data->adc_raw;
 
-        adc_raw_to_millivolts(adc_ref_internal(cfg->adc), cfg->acc.gain, as->resolution, &val);
+        adc_raw_to_millivolts(adc_ref_internal(drv_data->adc), drv_data->acc.gain, as->resolution, &val);
 
-        rc = val * (uint64_t)DT_PROP(VBATT, full_ohms) / DT_PROP(VBATT, output_ohms);
-        LOG_DBG("ADC raw %d ~ %d mV => %d mV\n", cfg->adc_raw, val, rc);
-        int percent = lithium_ion_mv_to_pct(rc);
+        uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm;
+        LOG_DBG("ADC raw %d ~ %d mV => %d mV\n", drv_data->adc_raw, val, millivolts);
+        uint8_t percent = lithium_ion_mv_to_pct(millivolts);
         LOG_DBG("Percent: %d", percent);
+
+        drv_data->voltage = millivolts;
+        drv_data->state_of_charge = percent;
     } else {
         LOG_DBG("Failed to read ADC: %d", rc);
     }
+
+    // Disable power GPIO if present
+    if (drv_data->gpio) {
+        rc = gpio_pin_set(drv_data->gpio, drv_cfg->power_gpios.pin, 0);
+
+        if (rc != 0) {
+            LOG_DBG("Failed to disable ADC power GPIO: %d", rc);
+        }  
+    }
+
+    return rc;
 }
 
-K_WORK_DEFINE(battery_work, battery_read);
+static int bvd_channel_get(struct device *dev, enum sensor_channel chan,
+                            struct sensor_value *val) {
+    struct bvd_data *drv_data = dev->driver_data;
 
-static void battery_handler(struct k_timer *timer) { k_work_submit(&battery_work); }
+    switch(chan) {
+    case SENSOR_CHAN_GAUGE_VOLTAGE:
+        val->val1 = drv_data->voltage / 1000;
+        val->val2 = (drv_data->voltage % 1000) * 1000U;
+        break;
 
-K_TIMER_DEFINE(battery_tick, battery_handler, NULL);
+    case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
+        val->val1 = drv_data->state_of_charge;
+        val->val2 = 0;
+        break;
 
-static int battery_setup(struct device *_arg) {
-    struct battery_config *cfg = &battery_config;
-    struct adc_sequence *as = &cfg->as;
-    struct adc_channel_cfg *acc = &cfg->acc;
+    default:
+        return -ENOTSUP;
+    }
 
-    cfg->adc = device_get_binding(DT_IO_CHANNELS_LABEL(VBATT));
+    return 0;    
+}
 
-    if (cfg->adc == NULL) {
-        LOG_ERR("ADC %s failed to retrieve", DT_IO_CHANNELS_LABEL(VBATT));
+static const struct sensor_driver_api bvd_api = {
+    .sample_fetch = bvd_sample_fetch,
+    .channel_get = bvd_channel_get,
+};
+
+
+static int bvd_init(struct device *dev) {
+    struct bvd_data *drv_data = dev->driver_data;
+    const struct bvd_config *drv_cfg = dev->config_info;
+
+    drv_data->adc = device_get_binding(drv_cfg->io_channel.label);
+
+    if (drv_data->adc == NULL) {
+        LOG_ERR("ADC %s failed to retrieve", drv_cfg->io_channel.label);
         return -ENOENT;
     }
 
-    *as = (struct adc_sequence){
+    int rc = 0;
+
+    if (drv_cfg->power_gpios.label) {
+		drv_data->gpio = device_get_binding(drv_cfg->power_gpios.label);
+		if (drv_data->gpio == NULL) {
+			LOG_ERR("Failed to get GPIO %s", drv_cfg->power_gpios.label);
+			return -ENOENT;
+		}
+		rc = gpio_pin_configure(drv_data->gpio, drv_cfg->power_gpios.pin,
+					GPIO_OUTPUT_INACTIVE | drv_cfg->power_gpios.flags);
+		if (rc != 0) {
+			LOG_ERR("Failed to control feed %s.%u: %d",
+				drv_cfg->power_gpios.label, drv_cfg->power_gpios.pin, rc);
+			return rc;
+		}
+	}
+
+    drv_data->as = (struct adc_sequence){
         .channels = BIT(0),
-        .buffer = &cfg->adc_raw,
-        .buffer_size = sizeof(cfg->adc_raw),
+        .buffer = &drv_data->adc_raw,
+        .buffer_size = sizeof(drv_data->adc_raw),
         .oversampling = 4,
         .calibrate = true,
     };
 
 #ifdef CONFIG_ADC_NRFX_SAADC
-    *acc = (struct adc_channel_cfg){
+    drv_data->acc = (struct adc_channel_cfg){
         .gain = ADC_GAIN_1_5,
         .reference = ADC_REF_INTERNAL,
         .acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
-        .input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + DT_IO_CHANNELS_INPUT(VBATT),
+        .input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + drv_cfg->io_channel.channel,
     };
 
-    as->resolution = 12;
+    drv_data->as.resolution = 12;
 #else
 #error Unsupported ADC
 #endif
 
-    int adc_rc = adc_channel_setup(cfg->adc, acc);
-    LOG_DBG("AIN%u setup returned %d", DT_IO_CHANNELS_INPUT(VBATT), adc_rc);
+    rc = adc_channel_setup(drv_data->adc, &drv_data->acc);
+    LOG_DBG("AIN%u setup returned %d", drv_cfg->io_channel.channel, rc);
 
-    if (adc_rc != 0) {
-        return adc_rc;
-    }
-
-    k_timer_start(&battery_tick, K_NO_WAIT, K_SECONDS(5));
-
-    return 0;
+    return rc;
 }
 
-SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
+static struct bvd_data bvd_data;
+static const struct bvd_config bvd_cfg = {
+	.io_channel = {
+		DT_INST_IO_CHANNELS_LABEL(0),
+		DT_INST_IO_CHANNELS_INPUT(0),
+	},
+#if DT_INST_NODE_HAS_PROP(0, power_gpios)
+	.power_gpios = {
+		DT_INST_GPIO_LABEL(0, power_gpios),
+		DT_INST_PIN(0, power_gpios),
+		DT_INST_FLAGS(0, power_gpios),
+	},
+#endif
+	.output_ohm = DT_INST_PROP(0, output_ohms),
+	.full_ohm = DT_INST_PROP(0, full_ohms),
+};
 
-#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
\ No newline at end of file
+DEVICE_AND_API_INIT(bvd_dev, DT_INST_LABEL(0), &bvd_init,
+		&bvd_data, &bvd_cfg, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,
+		&bvd_api);
diff --git a/app/drivers/zephyr/dts/bindings/zmk,battery-voltage-divider.yaml b/app/drivers/zephyr/dts/bindings/zmk,battery-voltage-divider.yaml
index f6e06427..3f391d78 100644
--- a/app/drivers/zephyr/dts/bindings/zmk,battery-voltage-divider.yaml
+++ b/app/drivers/zephyr/dts/bindings/zmk,battery-voltage-divider.yaml
@@ -6,4 +6,9 @@ description: Battery SoC monitoring using voltage divider
 compatible: "zmk,battery-voltage-divider"
 
 include: voltage-divider.yaml
+
+properties:
+  label:
+    required: true
+    type: string
   
\ No newline at end of file