Refactor power handling

Add reference counted ethosu_request_power(),
ethosu_release_power() functions. While there are
active requests, disable the Q-channel power gating
of the Ethos-U device, leaving the device powered on.
Note that clock gating is implemented to follow the
state of the power gating.

Add ethosu_soft_reset() function. Restore power-
and clock gating state after reset.

Refactor and simplify driver code to use the new
functions.

Change-Id: I9756572c5c3e51b2be244bcea856d88e890e2d40
diff --git a/include/ethosu_driver.h b/include/ethosu_driver.h
index 053b529..c48a3f2 100644
--- a/include/ethosu_driver.h
+++ b/include/ethosu_driver.h
@@ -74,11 +74,9 @@
     void *semaphore;
     uint64_t fast_memory;
     size_t fast_memory_size;
+    uint32_t power_request_counter;
     bool status_error;
-    bool dev_power_always_on;
     bool reserved;
-    uint8_t clock_request;
-    uint8_t power_request;
 };
 
 struct ethosu_driver_version
@@ -158,6 +156,24 @@
 void ethosu_deinit(struct ethosu_driver *drv);
 
 /**
+ * Soft resets the Ethos-U device.
+ */
+bool ethosu_soft_reset(struct ethosu_driver *drv);
+
+/**
+ * Request to disable Q-channel power gating of the Ethos-U device.
+ * Power requests are ref.counted. Increases count.
+ * (Note: clock gating is made to follow power gating)
+ */
+bool ethosu_request_power(struct ethosu_driver *drv);
+
+/**
+ * Release disable request for Q-channel power gating of the Ethos-U device.
+ * Power requests are ref.counted. Decreases count.
+ */
+void ethosu_release_power(struct ethosu_driver *drv);
+
+/**
  * Get Ethos-U driver version.
  */
 void ethosu_get_driver_version(struct ethosu_driver_version *ver);
@@ -209,11 +225,6 @@
 int ethosu_wait(struct ethosu_driver *drv, bool block);
 
 /**
- * Set Ethos-U power mode.
- */
-void ethosu_set_power_mode(struct ethosu_driver *drv, bool always_on);
-
-/**
  * Reserves a driver to execute inference with
  */
 struct ethosu_driver *ethosu_reserve_driver(void);
@@ -224,14 +235,6 @@
 void ethosu_release_driver(struct ethosu_driver *drv);
 
 /**
- * Set clock and power request bits
- */
-enum ethosu_error_codes set_clock_and_power_request(struct ethosu_driver *drv,
-                                                    enum ethosu_request_clients client,
-                                                    enum ethosu_clock_q_request clock_request,
-                                                    enum ethosu_power_q_request power_request);
-
-/**
  * Static inline for backwards-compatibility
  */
 static inline int ethosu_invoke_v2(const void *custom_data_ptr,
diff --git a/src/ethosu_device_u55_u65.c b/src/ethosu_device_u55_u65.c
index 31379fc..f16f9f8 100644
--- a/src/ethosu_device_u55_u65.c
+++ b/src/ethosu_device_u55_u65.c
@@ -209,6 +209,9 @@
 
 enum ethosu_error_codes ethosu_dev_soft_reset(struct ethosu_device *dev)
 {
+    // Note that after a soft-reset, the NPU is unconditionally
+    // powered until the next CMD gets written.
+
     struct reset_r reset;
 
     reset.word        = 0;
diff --git a/src/ethosu_driver.c b/src/ethosu_driver.c
index 316ed4d..ee9d08c 100644
--- a/src/ethosu_driver.c
+++ b/src/ethosu_driver.c
@@ -320,8 +320,6 @@
         }
     }
 
-    drv->job.state = ETHOSU_JOB_RUNNING;
-
     // Flush the cache if available on CPU.
     // The upcasting to uin32_t* is ok since the pointer never is dereferenced.
     // The base_addr_size is null if invoking from prior to invoke_V2, in that case
@@ -341,17 +339,14 @@
     }
 
     // Request power gating disabled during inference run
-    if (!drv->dev_power_always_on)
+    if (!ethosu_request_power(drv))
     {
-        // Will soft reset if security state or privilege level needs changing.
-        // Also note that any configurations done in the NPU prior to this point
-        // are lost in case power gating has been in effect.
-        set_clock_and_power_request(drv, ETHOSU_INFERENCE_REQUEST, ETHOSU_CLOCK_Q_ENABLE, ETHOSU_POWER_Q_DISABLE);
-
-        // Make sure AXI settings are applied
-        ethosu_dev_axi_init(drv->dev);
+        LOG_ERR("Failed to request power");
+        return -1;
     }
 
+    drv->job.state = ETHOSU_JOB_RUNNING;
+
     // Inference begin callback
     ethosu_inference_begin(drv, drv->job.user_arg);
 
@@ -406,8 +401,9 @@
         ethosu_semaphore = ethosu_semaphore_create();
     }
 
-    drv->fast_memory      = (uint32_t)fast_memory;
-    drv->fast_memory_size = fast_memory_size;
+    drv->fast_memory           = (uint32_t)fast_memory;
+    drv->fast_memory_size      = fast_memory_size;
+    drv->power_request_counter = 0;
 
     // Initialize the device and set requested security state and privilege mode
     drv->dev = ethosu_dev_init(base_address, secure_enable, privilege_enable);
@@ -418,20 +414,11 @@
         return -1;
     }
 
-    // Power always ON requested
-    if (drv->dev_power_always_on)
-    {
-        if (set_clock_and_power_request(drv, ETHOSU_INFERENCE_REQUEST, ETHOSU_CLOCK_Q_ENABLE, ETHOSU_POWER_Q_DISABLE) !=
-            ETHOSU_SUCCESS)
-        {
-            LOG_ERR("Failed to disable power-q for Ethos-U");
-            return -1;
-        }
-    }
-
     drv->semaphore    = ethosu_semaphore_create();
     drv->status_error = false;
 
+    ethosu_reset_job(drv);
+
     ethosu_register_driver(drv);
 
     return 0;
@@ -445,6 +432,56 @@
     drv->dev = NULL;
 }
 
+bool ethosu_soft_reset(struct ethosu_driver *drv)
+{
+    // Soft reset the NPU
+    if (ethosu_dev_soft_reset(drv->dev) != ETHOSU_SUCCESS)
+    {
+        LOG_ERR("Failed to soft-reset NPU");
+        return false;
+    }
+
+    // Update power and clock gating after the soft reset
+    ethosu_dev_set_clock_and_power(drv->dev,
+                                   drv->power_request_counter > 0 ? ETHOSU_CLOCK_Q_DISABLE : ETHOSU_CLOCK_Q_ENABLE,
+                                   drv->power_request_counter > 0 ? ETHOSU_POWER_Q_DISABLE : ETHOSU_POWER_Q_ENABLE);
+
+    return true;
+}
+
+bool ethosu_request_power(struct ethosu_driver *drv)
+{
+    // Check if this is the first power request, increase counter
+    if (drv->power_request_counter++ == 0)
+    {
+        // Always reset to a known state. Changes to requested
+        // security state/privilege mode if necessary.
+        if (ethosu_soft_reset(drv) == false)
+        {
+            LOG_ERR("Failed to request power for Ethos-U");
+            drv->power_request_counter--;
+            return false;
+        }
+    }
+    return true;
+}
+
+void ethosu_release_power(struct ethosu_driver *drv)
+{
+    if (drv->power_request_counter == 0)
+    {
+        LOG_WARN("No power request left to release, reference counter is 0");
+    }
+    else
+    {
+        // Decrement ref counter and enable power gating if no requests remain
+        if (--drv->power_request_counter == 0)
+        {
+            ethosu_dev_set_clock_and_power(drv->dev, ETHOSU_CLOCK_Q_ENABLE, ETHOSU_POWER_Q_ENABLE);
+        }
+    }
+}
+
 void ethosu_get_driver_version(struct ethosu_driver_version *ver)
 {
     assert(ver != NULL);
@@ -486,6 +523,9 @@
         // Inference done callback
         ethosu_inference_end(drv, drv->job.user_arg);
 
+        // Relase power gating disabled requirement
+        ethosu_release_power(drv);
+
         // Check NPU and interrupt status
         if (drv->status_error)
         {
@@ -493,21 +533,13 @@
             ethosu_dev_print_err_status(drv->dev);
 
             // Reset the NPU
-            (void)ethosu_dev_soft_reset(drv->dev);
+            (void)ethosu_soft_reset(drv);
             // NPU is no longer in error state
             drv->status_error = false;
 
             ret = -1;
         }
 
-        // Clear the clock/power gating disable request
-        if (!drv->dev_power_always_on)
-        {
-            // NOTE: Other requesters (like PMU) can be active, keeping
-            // clock/power gating disabled until no requests remain.
-            set_clock_and_power_request(drv, ETHOSU_INFERENCE_REQUEST, ETHOSU_CLOCK_Q_ENABLE, ETHOSU_POWER_Q_ENABLE);
-        }
-
         if (ret == 0)
         {
             // Invalidate cache
@@ -664,31 +696,6 @@
     return ethosu_wait(drv, true);
 }
 
-void ethosu_set_power_mode(struct ethosu_driver *drv, bool always_on)
-{
-    drv->dev_power_always_on = always_on;
-
-    if (always_on)
-    {
-        if (ethosu_dev_verify_access_state(drv->dev) == false)
-        {
-            // Reset to enter correct security state/privilege mode
-            if (ethosu_dev_soft_reset(drv->dev) == false)
-            {
-                LOG_ERR("Failed to set power mode for Ethos-U");
-                return;
-            }
-        }
-
-        ethosu_dev_set_clock_and_power(drv->dev, ETHOSU_CLOCK_Q_UNCHANGED, ETHOSU_POWER_Q_DISABLE);
-        ethosu_dev_axi_init(drv->dev);
-    }
-    else
-    {
-        ethosu_dev_set_clock_and_power(drv->dev, ETHOSU_CLOCK_Q_UNCHANGED, ETHOSU_POWER_Q_ENABLE);
-    }
-}
-
 struct ethosu_driver *ethosu_reserve_driver(void)
 {
     struct ethosu_driver *drv = NULL;
@@ -727,13 +734,12 @@
             if (ethosu_wait(drv, false) == 1)
             {
                 // Still running, soft reset the NPU and reset driver
-                ethosu_dev_soft_reset(drv->dev);
+                drv->power_request_counter = 0;
+                ethosu_soft_reset(drv);
                 ethosu_reset_job(drv);
                 drv->status_error = false;
                 /* TODO: feedback needed aout how to handle error (-1) return value */
                 ethosu_semaphore_give(drv->semaphore);
-                (void)set_clock_and_power_request(
-                    drv, ETHOSU_INFERENCE_REQUEST, ETHOSU_CLOCK_Q_ENABLE, ETHOSU_POWER_Q_ENABLE);
             }
         }
 
@@ -745,58 +751,3 @@
     /* TODO: feedback needed aout how to handle error (-1) return value */
     ethosu_mutex_unlock(ethosu_mutex);
 }
-
-enum ethosu_error_codes set_clock_and_power_request(struct ethosu_driver *drv,
-                                                    enum ethosu_request_clients client,
-                                                    enum ethosu_clock_q_request clock_request,
-                                                    enum ethosu_power_q_request power_request)
-{
-    // Keep track of which client requests clock gating to be disabled
-    if (clock_request == ETHOSU_CLOCK_Q_DISABLE)
-    {
-        drv->clock_request |= (1 << client);
-    }
-    else if (clock_request == ETHOSU_CLOCK_Q_ENABLE) // Remove client from bitmask
-    {
-        drv->clock_request &= ~(1 << client);
-    }
-
-    // Only enable clock gating when no client has asked for it to be disabled
-    clock_request = drv->clock_request == 0 ? ETHOSU_CLOCK_Q_ENABLE : ETHOSU_CLOCK_Q_DISABLE;
-
-    // Keep track of which client requests power gating to be disabled
-    if (power_request == ETHOSU_POWER_Q_DISABLE)
-    {
-        drv->power_request |= (1 << client);
-    }
-    else if (power_request == ETHOSU_POWER_Q_ENABLE)
-    {
-        drv->power_request &= ~(1 << client);
-    }
-
-    // Override if power has been requested to be always on
-    if (drv->dev_power_always_on == true)
-    {
-        power_request = ETHOSU_POWER_Q_DISABLE;
-    }
-    else
-    {
-        // Only enable power gating when no client has asked for it to be disabled
-        power_request = drv->power_request == 0 ? ETHOSU_POWER_Q_ENABLE : ETHOSU_POWER_Q_DISABLE;
-    }
-
-    // Verify security state and privilege mode if power is requested to be on
-    if (power_request == ETHOSU_POWER_Q_DISABLE)
-    {
-        if (ethosu_dev_verify_access_state(drv->dev) == false)
-        {
-            if (ethosu_dev_soft_reset(drv->dev) != ETHOSU_SUCCESS)
-            {
-                LOG_ERR("Failed to set clock and power q channels for Ethos-U");
-                return ETHOSU_GENERIC_FAILURE;
-            }
-        }
-    }
-    // Set clock and power
-    return ethosu_dev_set_clock_and_power(drv->dev, clock_request, power_request);
-}
diff --git a/src/ethosu_pmu.c b/src/ethosu_pmu.c
index e11636a..df69026 100644
--- a/src/ethosu_pmu.c
+++ b/src/ethosu_pmu.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2021 Arm Limited. All rights reserved.
+ * Copyright (c) 2019-2022 Arm Limited.
  *
  * SPDX-License-Identifier: Apache-2.0
  *
@@ -90,7 +90,7 @@
     LOG_DEBUG("Enable PMU");
     struct pmcr_r pmcr = {0};
     pmcr.cnt_en        = 1;
-    set_clock_and_power_request(drv, ETHOSU_PMU_REQUEST, ETHOSU_CLOCK_Q_DISABLE, ETHOSU_POWER_Q_DISABLE);
+    ethosu_request_power(drv);
     drv->dev->reg->PMCR.word = pmcr.word;
 }
 
@@ -98,7 +98,7 @@
 {
     LOG_DEBUG("Disable PMU");
     drv->dev->reg->PMCR.word = 0;
-    set_clock_and_power_request(drv, ETHOSU_PMU_REQUEST, ETHOSU_CLOCK_Q_ENABLE, ETHOSU_POWER_Q_ENABLE);
+    ethosu_release_power(drv);
 }
 
 uint32_t ETHOSU_PMU_Get_NumEventCounters(void)