From c5a47c218d66070fa4bcd1c14b96e18537ffec4a Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Fri, 15 Oct 2010 17:25:33 +0300 Subject: [PATCH] gpu: pvr: add support for dynamic timing of SGX HW power down 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 Reviewed-by: Luc Verhaegen --- pvr/sgxinfokm.h | 7 +++++ pvr/sgxpower.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/pvr/sgxinfokm.h b/pvr/sgxinfokm.h index 63dfcae..6db6bb3 100644 --- a/pvr/sgxinfokm.h +++ b/pvr/sgxinfokm.h @@ -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); diff --git a/pvr/sgxpower.c b/pvr/sgxpower.c index f4cb52b..092ddfa 100644 --- a/pvr/sgxpower.c +++ b/pvr/sgxpower.c @@ -26,6 +26,7 @@ #include #include +#include #include "sgxdefs.h" #include "services_headers.h" @@ -34,6 +35,12 @@ #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; -- 2.39.5