gpu: pvr: add support for dynamic timing of SGX HW power down
authorImre Deak <imre.deak@nokia.com>
Fri, 15 Oct 2010 14:25:33 +0000 (17:25 +0300)
committerGrazvydas Ignotas <notasas@gmail.com>
Sun, 20 May 2012 18:09:42 +0000 (21:09 +0300)
This is needed by the next patch, which actually enables the support.

Currently the driver implements an aggressive power management policy
and powers down the HW very shortly (1 ms) after each completed command.
The resulting power-down and -up sequence between commands are relatively
long (~250usec), causing a delayed command execution and unnecessary CPU
load.

There is a deadline at the end of each display Vsync period, by which
all commands for the next frame must be completed. If the deadline is
missed FPS goes down. To increase the chance that we meet the deadline
we want to cut down on the above overhead.

One way to reduce the overhead is to get rid of the power-down/up
sequences between commands. The downside is the possibly higher power
consumption, so a solution has to minimize this side effect. Simply
increasing the power-down delay would result in a constant power
consumption increase. To see this let's consider the following two
cases given a 16 ms Vsync period.

Case a:

1.  SGX command#1 for frame#N    -  3 ms
2.  SGX idle                     -  3 ms
3.  SGX command#2 for frame#N    -  3 ms
4.  SGX idle                     -  3 ms
5.  SGX command#3 for frame#N    -  3 ms
6.  SGX idle                     -  1 ms
7.  Vsync
8.  SGX command#1 for frame#N+1  -  3 ms
9.  SGX idle                     -  3 ms
10. SGX command#2 for frame#N+1  -  3 ms
11. SGX idle                     -  3 ms
12. SGX command#3 for frame#N+1  -  3 ms
13. SGX idle                     -  1 ms
14. Vsync
... same pattern repeating for several frames

Here we need to increase the power-down delay to 4 ms, to avoid the
overhead at 2.,4.,9.,11. and to increase the chance to meet the deadlines
at 7. and 14.

Case b:

1.  SGX command#1 for frame#N    -  1 ms
2.  SGX idle                     -  1 ms
3.  SGX command#2 for frame#N    -  1 ms
4.  SGX idle                     -  1 ms
5.  SGX command#3 for frame#N    -  1 ms
6.  SGX idle                     - 11 ms
7.  Vsync
8.  SGX command#1 for frame#N+1  -  1 ms
9.  SGX idle                     -  1 ms
10. SGX command#2 for frame#N+1  -  1 ms
11. SGX idle                     -  1 ms
12. SGX command#3 for frame#N+1  -  1 ms
13. SGX idle                     - 11 ms
14. Vsync
... same pattern repeating for several frames

Here we don't have a risk of missing the deadlines, so we could
power-down immediately after 5. and 12. but we will delay the power-down
by 4 ms due to the constraint in case a. This energy waste will be
incured in each of the following frames having a similar command
scheduling pattern.

The solution in the following patch minimizes the energy waste while
achieving the original goal, by tracking "command bursts". In a burst
command execution periods are separated by idle periods of less than a
fixed amount of time (3 ms). Power-down is avoided between commands
within one burst, but it's performed immediately after the last command
of the burst. For example all commands in case a constitute one burst,
so we won't have any power-down there. In case b we have two bursts:
first burst consisting of 1.,3.,5. second burst consisting of 8.,10.,12.,
so we will power down immediately after 5. and 12.

In cases where commands are interleaved (well behaving applications)
we'll see power consumption decreasing due to the immediate power down
vs. the current 1ms delay. For other cases we might see a slight
increase in power consumption due to not powering down between commands,
but these are the very cases where we have a risk of not meeting the frame
deadlines, so the compromise looks like justified.

Signed-off-by: Imre Deak <imre.deak@nokia.com>
Reviewed-by: Luc Verhaegen <Luc.Verhaegen@basyskom.de>
pvr/sgxinfokm.h
pvr/sgxpower.c

index 63dfcae..6db6bb3 100644 (file)
@@ -130,6 +130,10 @@ struct PVRSRV_SGXDEV_INFO {
        u32 ui32TimeStamp;
        u32 ui32NumResets;
 
+       unsigned long long last_idle;
+       unsigned long long burst_start;
+       int burst_size;
+       int burst_cnt;
        int power_down_delay;
 
        struct PVRSRV_KERNEL_MEM_INFO *psKernelSGXHostCtlMemInfo;
@@ -217,6 +221,9 @@ enum PVRSRV_ERROR SGXInitialise(struct PVRSRV_SGXDEV_INFO *psDevInfo,
                                IMG_BOOL bHardwareRecovery);
 enum PVRSRV_ERROR SGXDeinitialise(void *hDevCookie);
 
+void sgx_mark_new_command(struct PVRSRV_DEVICE_NODE *node);
+void sgx_mark_power_down(struct PVRSRV_DEVICE_NODE *node);
+
 void SGXStartTimer(struct PVRSRV_SGXDEV_INFO *psDevInfo,
                   IMG_BOOL bStartOSTimer);
 
index f4cb52b..092ddfa 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <stddef.h>
 #include <linux/io.h>
+#include <linux/sched.h>
 
 #include "sgxdefs.h"
 #include "services_headers.h"
 #include "sgxutils.h"
 #include "pdump_km.h"
 
+#define MS_TO_NS(x)                    ((x) * 1000000ULL)
+#define SGX_CMD_BURST_THRESHOLD_NS     MS_TO_NS(3)
+#define SGX_CMD_BURST_MAX_SIZE         4
+#define SGX_POWER_DOWN_DELAY_LONG_MS   4
+#define SGX_POWER_DOWN_DELAY_SHORT_MS  0
+
 enum PVR_DEVICE_POWER_STATE {
 
        PVR_DEVICE_POWER_STATE_ON = 0,
@@ -86,6 +93,80 @@ static void sgx_set_pwrdown_delay(struct PVRSRV_DEVICE_NODE *node,
        }
 }
 
+static int sgx_calc_power_down_delay(struct PVRSRV_DEVICE_NODE *node)
+{
+       struct PVRSRV_SGXDEV_INFO *info = node->pvDevice;
+
+       /*
+        * Set the power down delay to short if the command to be executed is
+        * the last one in the burst, except if the burst size is at the
+        * maximum.
+        */
+       if (info->burst_size < SGX_CMD_BURST_MAX_SIZE &&
+           info->burst_cnt == info->burst_size)
+               return SGX_POWER_DOWN_DELAY_SHORT_MS;
+       else
+               return SGX_POWER_DOWN_DELAY_LONG_MS;
+
+}
+
+void sgx_mark_new_command(struct PVRSRV_DEVICE_NODE *node)
+{
+       struct PVRSRV_SGXDEV_INFO *info = node->pvDevice;
+       struct SGX_TIMING_INFORMATION tinfo = { 0 };
+       unsigned long long cmd_start;
+       bool new_burst = false;
+
+       cmd_start = cpu_clock(smp_processor_id());
+
+       if (unlikely(info->last_idle == info->burst_start)) {
+               /*
+                * This is the initial case, when we haven't yet any commands
+                * issued.
+                */
+               new_burst = true;
+       } else {
+               /*
+                * If the last idle occurred after the current burst started
+                * and the time since the idle is greater than the threshold
+                * allowed for delays within a burst then this is a new burst.
+                */
+               if (time_after64(info->last_idle, info->burst_start) &&
+                   cmd_start - info->last_idle > SGX_CMD_BURST_THRESHOLD_NS)
+                       new_burst = true;
+       }
+
+       if (new_burst) {
+               info->burst_start = cmd_start;
+               /*
+                * We predict the length of this new burst to be that of the
+                * previous burst.
+                */
+               info->burst_size = info->burst_cnt;
+               info->burst_cnt = 0;
+       } else if (info->burst_cnt < SGX_CMD_BURST_MAX_SIZE) {
+               info->burst_cnt++;
+       }
+
+       SysGetSGXTimingInformation(&tinfo);
+       sgx_set_pwrdown_delay(node, tinfo.ui32uKernelFreq,
+                             sgx_calc_power_down_delay(node));
+}
+
+void sgx_mark_power_down(struct PVRSRV_DEVICE_NODE *node)
+{
+       struct PVRSRV_SGXDEV_INFO *info = node->pvDevice;
+
+       info->last_idle = cpu_clock(smp_processor_id());
+       /*
+        * After the last command completes power down happens in a delayed
+        * manner. The current value of this delay is in power_down_delay.
+        * To get the command complete time - which is the actual idle start
+        * time - we have to deduct the amount of delay from the current time.
+        */
+       info->last_idle -= MS_TO_NS(info->power_down_delay);
+}
+
 static void SGXGetTimingInfo(struct PVRSRV_DEVICE_NODE *psDeviceNode)
 {
        struct PVRSRV_SGXDEV_INFO *psDevInfo = psDeviceNode->pvDevice;