blob: 1ef03a69a084a792a49af8522a610e73bae1b7db [file] [log] [blame]
/*
* Copyright (C) 2011-2016 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence.
*
* A copy of the licence is included with the program, and can also be obtained from Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "mali_pm.h"
#include "mali_kernel_common.h"
#include "mali_osk.h"
#include "mali_osk_mali.h"
#include "mali_scheduler.h"
#include "mali_group.h"
#include "mali_pm_domain.h"
#include "mali_pmu.h"
#include "mali_executor.h"
#include "mali_control_timer.h"
#if defined(DEBUG)
u32 num_pm_runtime_resume = 0;
u32 num_pm_updates = 0;
u32 num_pm_updates_up = 0;
u32 num_pm_updates_down = 0;
#endif
#define MALI_PM_DOMAIN_DUMMY_MASK (1 << MALI_DOMAIN_INDEX_DUMMY)
/* lock protecting power state (including pm_domains) */
static _mali_osk_spinlock_irq_t *pm_lock_state = NULL;
/* the wanted domain mask (protected by pm_lock_state) */
static u32 pd_mask_wanted = 0;
/* used to deferring the actual power changes */
static _mali_osk_wq_work_t *pm_work = NULL;
/* lock protecting power change execution */
static _mali_osk_mutex_t *pm_lock_exec = NULL;
/* PMU domains which are actually powered on (protected by pm_lock_exec) */
static u32 pmu_mask_current = 0;
/*
* domains which marked as powered on (protected by pm_lock_exec)
* This can be different from pmu_mask_current right after GPU power on
* if the PMU domains default to powered up.
*/
static u32 pd_mask_current = 0;
static u16 domain_config[MALI_MAX_NUMBER_OF_DOMAINS] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1 << MALI_DOMAIN_INDEX_DUMMY
};
/* The relative core power cost */
#define MALI_GP_COST 3
#define MALI_PP_COST 6
#define MALI_L2_COST 1
/*
*We have MALI_MAX_NUMBER_OF_PP_PHYSICAL_CORES + 1 rows in this matrix
*because we mush store the mask of different pp cores: 0, 1, 2, 3, 4, 5, 6, 7, 8.
*/
static int mali_pm_domain_power_cost_result[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS + 1][MALI_MAX_NUMBER_OF_DOMAINS];
/*
* Keep track of runtime PM state, so that we know
* how to resume during OS resume.
*/
#ifdef CONFIG_PM_RUNTIME
static mali_bool mali_pm_runtime_active = MALI_FALSE;
#else
/* when kernel don't enable PM_RUNTIME, set the flag always true,
* for GPU will not power off by runtime */
static mali_bool mali_pm_runtime_active = MALI_TRUE;
#endif
static void mali_pm_state_lock(void);
static void mali_pm_state_unlock(void);
static _mali_osk_errcode_t mali_pm_create_pm_domains(void);
static void mali_pm_set_pmu_domain_config(void);
static u32 mali_pm_get_registered_cores_mask(void);
static void mali_pm_update_sync_internal(void);
static mali_bool mali_pm_common_suspend(void);
static void mali_pm_update_work(void *data);
#if defined(DEBUG)
const char *mali_pm_mask_to_string(u32 mask);
const char *mali_pm_group_stats_to_string(void);
#endif
_mali_osk_errcode_t mali_pm_initialize(void)
{
_mali_osk_errcode_t err;
struct mali_pmu_core *pmu;
pm_lock_state = _mali_osk_spinlock_irq_init(_MALI_OSK_LOCKFLAG_ORDERED,
_MALI_OSK_LOCK_ORDER_PM_STATE);
if (NULL == pm_lock_state) {
mali_pm_terminate();
return _MALI_OSK_ERR_FAULT;
}
pm_lock_exec = _mali_osk_mutex_init(_MALI_OSK_LOCKFLAG_ORDERED,
_MALI_OSK_LOCK_ORDER_PM_STATE);
if (NULL == pm_lock_exec) {
mali_pm_terminate();
return _MALI_OSK_ERR_FAULT;
}
pm_work = _mali_osk_wq_create_work(mali_pm_update_work, NULL);
if (NULL == pm_work) {
mali_pm_terminate();
return _MALI_OSK_ERR_FAULT;
}
pmu = mali_pmu_get_global_pmu_core();
if (NULL != pmu) {
/*
* We have a Mali PMU, set the correct domain
* configuration (default or custom)
*/
u32 registered_cores_mask;
mali_pm_set_pmu_domain_config();
registered_cores_mask = mali_pm_get_registered_cores_mask();
mali_pmu_set_registered_cores_mask(pmu, registered_cores_mask);
MALI_DEBUG_ASSERT(0 == pd_mask_wanted);
}
/* Create all power domains needed (at least one dummy domain) */
err = mali_pm_create_pm_domains();
if (_MALI_OSK_ERR_OK != err) {
mali_pm_terminate();
return err;
}
return _MALI_OSK_ERR_OK;
}
void mali_pm_terminate(void)
{
if (NULL != pm_work) {
_mali_osk_wq_delete_work(pm_work);
pm_work = NULL;
}
mali_pm_domain_terminate();
if (NULL != pm_lock_exec) {
_mali_osk_mutex_term(pm_lock_exec);
pm_lock_exec = NULL;
}
if (NULL != pm_lock_state) {
_mali_osk_spinlock_irq_term(pm_lock_state);
pm_lock_state = NULL;
}
}
struct mali_pm_domain *mali_pm_register_l2_cache(u32 domain_index,
struct mali_l2_cache_core *l2_cache)
{
struct mali_pm_domain *domain;
domain = mali_pm_domain_get_from_mask(domain_config[domain_index]);
if (NULL == domain) {
MALI_DEBUG_ASSERT(0 == domain_config[domain_index]);
domain = mali_pm_domain_get_from_index(
MALI_DOMAIN_INDEX_DUMMY);
domain_config[domain_index] = MALI_PM_DOMAIN_DUMMY_MASK;
} else {
MALI_DEBUG_ASSERT(0 != domain_config[domain_index]);
}
MALI_DEBUG_ASSERT(NULL != domain);
mali_pm_domain_add_l2_cache(domain, l2_cache);
return domain; /* return the actual domain this was registered in */
}
struct mali_pm_domain *mali_pm_register_group(u32 domain_index,
struct mali_group *group)
{
struct mali_pm_domain *domain;
domain = mali_pm_domain_get_from_mask(domain_config[domain_index]);
if (NULL == domain) {
MALI_DEBUG_ASSERT(0 == domain_config[domain_index]);
domain = mali_pm_domain_get_from_index(
MALI_DOMAIN_INDEX_DUMMY);
domain_config[domain_index] = MALI_PM_DOMAIN_DUMMY_MASK;
} else {
MALI_DEBUG_ASSERT(0 != domain_config[domain_index]);
}
MALI_DEBUG_ASSERT(NULL != domain);
mali_pm_domain_add_group(domain, group);
return domain; /* return the actual domain this was registered in */
}
mali_bool mali_pm_get_domain_refs(struct mali_pm_domain **domains,
struct mali_group **groups,
u32 num_domains)
{
mali_bool ret = MALI_TRUE; /* Assume all is powered on instantly */
u32 i;
mali_pm_state_lock();
for (i = 0; i < num_domains; i++) {
MALI_DEBUG_ASSERT_POINTER(domains[i]);
pd_mask_wanted |= mali_pm_domain_ref_get(domains[i]);
if (MALI_FALSE == mali_pm_domain_power_is_on(domains[i])) {
/*
* Tell caller that the corresponding group
* was not already powered on.
*/
ret = MALI_FALSE;
} else {
/*
* There is a time gap between we power on the domain and
* set the power state of the corresponding groups to be on.
*/
if (NULL != groups[i] &&
MALI_FALSE == mali_group_power_is_on(groups[i])) {
ret = MALI_FALSE;
}
}
}
MALI_DEBUG_PRINT(3, ("PM: wanted domain mask = 0x%08X (get refs)\n", pd_mask_wanted));
mali_pm_state_unlock();
return ret;
}
mali_bool mali_pm_put_domain_refs(struct mali_pm_domain **domains,
u32 num_domains)
{
u32 mask = 0;
mali_bool ret;
u32 i;
mali_pm_state_lock();
for (i = 0; i < num_domains; i++) {
MALI_DEBUG_ASSERT_POINTER(domains[i]);
mask |= mali_pm_domain_ref_put(domains[i]);
}
if (0 == mask) {
/* return false, all domains should still stay on */
ret = MALI_FALSE;
} else {
/* Assert that we are dealing with a change */
MALI_DEBUG_ASSERT((pd_mask_wanted & mask) == mask);
/* Update our desired domain mask */
pd_mask_wanted &= ~mask;
/* return true; one or more domains can now be powered down */
ret = MALI_TRUE;
}
MALI_DEBUG_PRINT(3, ("PM: wanted domain mask = 0x%08X (put refs)\n", pd_mask_wanted));
mali_pm_state_unlock();
return ret;
}
void mali_pm_init_begin(void)
{
struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();
_mali_osk_pm_dev_ref_get_sync();
/* Ensure all PMU domains are on */
if (NULL != pmu) {
mali_pmu_power_up_all(pmu);
}
}
void mali_pm_init_end(void)
{
struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();
/* Ensure all PMU domains are off */
if (NULL != pmu) {
mali_pmu_power_down_all(pmu);
}
_mali_osk_pm_dev_ref_put();
}
void mali_pm_update_sync(void)
{
mali_pm_exec_lock();
if (MALI_TRUE == mali_pm_runtime_active) {
/*
* Only update if GPU is powered on.
* Deactivation of the last group will result in both a
* deferred runtime PM suspend operation and
* deferred execution of this function.
* mali_pm_runtime_active will be false if runtime PM
* executed first and thus the GPU is now fully powered off.
*/
mali_pm_update_sync_internal();
}
mali_pm_exec_unlock();
}
void mali_pm_update_async(void)
{
_mali_osk_wq_schedule_work(pm_work);
}
void mali_pm_os_suspend(mali_bool os_suspend)
{
int ret;
MALI_DEBUG_PRINT(3, ("Mali PM: OS suspend\n"));
/* Suspend execution of all jobs, and go to inactive state */
mali_executor_suspend();
if (os_suspend) {
mali_control_timer_suspend(MALI_TRUE);
}
mali_pm_exec_lock();
ret = mali_pm_common_suspend();
MALI_DEBUG_ASSERT(MALI_TRUE == ret);
MALI_IGNORE(ret);
mali_pm_exec_unlock();
}
void mali_pm_os_resume(void)
{
struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();
MALI_DEBUG_PRINT(3, ("Mali PM: OS resume\n"));
mali_pm_exec_lock();
#if defined(DEBUG)
mali_pm_state_lock();
/* Assert that things are as we left them in os_suspend(). */
MALI_DEBUG_ASSERT(0 == pd_mask_wanted);
MALI_DEBUG_ASSERT(0 == pd_mask_current);
MALI_DEBUG_ASSERT(0 == pmu_mask_current);
MALI_DEBUG_ASSERT(MALI_TRUE == mali_pm_domain_all_unused());
mali_pm_state_unlock();
#endif
if (MALI_TRUE == mali_pm_runtime_active) {
/* Runtime PM was active, so reset PMU */
if (NULL != pmu) {
mali_pmu_reset(pmu);
pmu_mask_current = mali_pmu_get_mask(pmu);
MALI_DEBUG_PRINT(3, ("Mali PM: OS resume 0x%x \n", pmu_mask_current));
}
mali_pm_update_sync_internal();
}
mali_pm_exec_unlock();
/* Start executing jobs again */
mali_executor_resume();
}
mali_bool mali_pm_runtime_suspend(void)
{
mali_bool ret;
MALI_DEBUG_PRINT(3, ("Mali PM: Runtime suspend\n"));
mali_pm_exec_lock();
/*
* Put SW state directly into "off" state, and do not bother to power
* down each power domain, because entire GPU will be powered off
* when we return.
* For runtime PM suspend, in contrast to OS suspend, there is a race
* between this function and the mali_pm_update_sync_internal(), which
* is fine...
*/
ret = mali_pm_common_suspend();
if (MALI_TRUE == ret) {
mali_pm_runtime_active = MALI_FALSE;
} else {
/*
* Process the "power up" instead,
* which could have been "lost"
*/
mali_pm_update_sync_internal();
}
mali_pm_exec_unlock();
return ret;
}
void mali_pm_runtime_resume(void)
{
struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();
mali_pm_exec_lock();
mali_pm_runtime_active = MALI_TRUE;
#if defined(DEBUG)
++num_pm_runtime_resume;
mali_pm_state_lock();
/*
* Assert that things are as we left them in runtime_suspend(),
* except for pd_mask_wanted which normally will be the reason we
* got here (job queued => domains wanted)
*/
MALI_DEBUG_ASSERT(0 == pd_mask_current);
MALI_DEBUG_ASSERT(0 == pmu_mask_current);
mali_pm_state_unlock();
#endif
if (NULL != pmu) {
mali_pmu_reset(pmu);
pmu_mask_current = mali_pmu_get_mask(pmu);
MALI_DEBUG_PRINT(3, ("Mali PM: Runtime resume 0x%x \n", pmu_mask_current));
}
/*
* Normally we are resumed because a job has just been queued.
* pd_mask_wanted should thus be != 0.
* It is however possible for others to take a Mali Runtime PM ref
* without having a job queued.
* We should however always call mali_pm_update_sync_internal(),
* because this will take care of any potential mismatch between
* pmu_mask_current and pd_mask_current.
*/
mali_pm_update_sync_internal();
mali_pm_exec_unlock();
}
#if MALI_STATE_TRACKING
u32 mali_pm_dump_state_domain(struct mali_pm_domain *domain,
char *buf, u32 size)
{
int n = 0;
n += _mali_osk_snprintf(buf + n, size - n,
"\tPower domain: id %u\n",
mali_pm_domain_get_id(domain));
n += _mali_osk_snprintf(buf + n, size - n,
"\t\tMask: 0x%04x\n",
mali_pm_domain_get_mask(domain));
n += _mali_osk_snprintf(buf + n, size - n,
"\t\tUse count: %u\n",
mali_pm_domain_get_use_count(domain));
n += _mali_osk_snprintf(buf + n, size - n,
"\t\tCurrent power state: %s\n",
(mali_pm_domain_get_mask(domain) & pd_mask_current) ?
"On" : "Off");
n += _mali_osk_snprintf(buf + n, size - n,
"\t\tWanted power state: %s\n",
(mali_pm_domain_get_mask(domain) & pd_mask_wanted) ?
"On" : "Off");
return n;
}
#endif
static void mali_pm_state_lock(void)
{
_mali_osk_spinlock_irq_lock(pm_lock_state);
}
static void mali_pm_state_unlock(void)
{
_mali_osk_spinlock_irq_unlock(pm_lock_state);
}
void mali_pm_exec_lock(void)
{
_mali_osk_mutex_wait(pm_lock_exec);
}
void mali_pm_exec_unlock(void)
{
_mali_osk_mutex_signal(pm_lock_exec);
}
static void mali_pm_domain_power_up(u32 power_up_mask,
struct mali_group *groups_up[MALI_MAX_NUMBER_OF_GROUPS],
u32 *num_groups_up,
struct mali_l2_cache_core *l2_up[MALI_MAX_NUMBER_OF_L2_CACHE_CORES],
u32 *num_l2_up)
{
u32 domain_bit;
u32 notify_mask = power_up_mask;
MALI_DEBUG_ASSERT(0 != power_up_mask);
MALI_DEBUG_ASSERT_POINTER(groups_up);
MALI_DEBUG_ASSERT_POINTER(num_groups_up);
MALI_DEBUG_ASSERT(0 == *num_groups_up);
MALI_DEBUG_ASSERT_POINTER(l2_up);
MALI_DEBUG_ASSERT_POINTER(num_l2_up);
MALI_DEBUG_ASSERT(0 == *num_l2_up);
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_state);
MALI_DEBUG_PRINT(5,
("PM update: Powering up domains: . [%s]\n",
mali_pm_mask_to_string(power_up_mask)));
pd_mask_current |= power_up_mask;
domain_bit = _mali_osk_fls(notify_mask);
while (0 != domain_bit) {
u32 domain_id = domain_bit - 1;
struct mali_pm_domain *domain =
mali_pm_domain_get_from_index(
domain_id);
struct mali_l2_cache_core *l2_cache;
struct mali_l2_cache_core *l2_cache_tmp;
struct mali_group *group;
struct mali_group *group_tmp;
/* Mark domain as powered up */
mali_pm_domain_set_power_on(domain, MALI_TRUE);
/*
* Make a note of the L2 and/or group(s) to notify
* (need to release the PM state lock before doing so)
*/
_MALI_OSK_LIST_FOREACHENTRY(l2_cache,
l2_cache_tmp,
mali_pm_domain_get_l2_cache_list(
domain),
struct mali_l2_cache_core,
pm_domain_list) {
MALI_DEBUG_ASSERT(*num_l2_up <
MALI_MAX_NUMBER_OF_L2_CACHE_CORES);
l2_up[*num_l2_up] = l2_cache;
(*num_l2_up)++;
}
_MALI_OSK_LIST_FOREACHENTRY(group,
group_tmp,
mali_pm_domain_get_group_list(domain),
struct mali_group,
pm_domain_list) {
MALI_DEBUG_ASSERT(*num_groups_up <
MALI_MAX_NUMBER_OF_GROUPS);
groups_up[*num_groups_up] = group;
(*num_groups_up)++;
}
/* Remove current bit and find next */
notify_mask &= ~(1 << (domain_id));
domain_bit = _mali_osk_fls(notify_mask);
}
}
static void mali_pm_domain_power_down(u32 power_down_mask,
struct mali_group *groups_down[MALI_MAX_NUMBER_OF_GROUPS],
u32 *num_groups_down,
struct mali_l2_cache_core *l2_down[MALI_MAX_NUMBER_OF_L2_CACHE_CORES],
u32 *num_l2_down)
{
u32 domain_bit;
u32 notify_mask = power_down_mask;
MALI_DEBUG_ASSERT(0 != power_down_mask);
MALI_DEBUG_ASSERT_POINTER(groups_down);
MALI_DEBUG_ASSERT_POINTER(num_groups_down);
MALI_DEBUG_ASSERT(0 == *num_groups_down);
MALI_DEBUG_ASSERT_POINTER(l2_down);
MALI_DEBUG_ASSERT_POINTER(num_l2_down);
MALI_DEBUG_ASSERT(0 == *num_l2_down);
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_state);
MALI_DEBUG_PRINT(5,
("PM update: Powering down domains: [%s]\n",
mali_pm_mask_to_string(power_down_mask)));
pd_mask_current &= ~power_down_mask;
domain_bit = _mali_osk_fls(notify_mask);
while (0 != domain_bit) {
u32 domain_id = domain_bit - 1;
struct mali_pm_domain *domain =
mali_pm_domain_get_from_index(domain_id);
struct mali_l2_cache_core *l2_cache;
struct mali_l2_cache_core *l2_cache_tmp;
struct mali_group *group;
struct mali_group *group_tmp;
/* Mark domain as powered down */
mali_pm_domain_set_power_on(domain, MALI_FALSE);
/*
* Make a note of the L2s and/or groups to notify
* (need to release the PM state lock before doing so)
*/
_MALI_OSK_LIST_FOREACHENTRY(l2_cache,
l2_cache_tmp,
mali_pm_domain_get_l2_cache_list(domain),
struct mali_l2_cache_core,
pm_domain_list) {
MALI_DEBUG_ASSERT(*num_l2_down <
MALI_MAX_NUMBER_OF_L2_CACHE_CORES);
l2_down[*num_l2_down] = l2_cache;
(*num_l2_down)++;
}
_MALI_OSK_LIST_FOREACHENTRY(group,
group_tmp,
mali_pm_domain_get_group_list(domain),
struct mali_group,
pm_domain_list) {
MALI_DEBUG_ASSERT(*num_groups_down <
MALI_MAX_NUMBER_OF_GROUPS);
groups_down[*num_groups_down] = group;
(*num_groups_down)++;
}
/* Remove current bit and find next */
notify_mask &= ~(1 << (domain_id));
domain_bit = _mali_osk_fls(notify_mask);
}
}
/*
* Execute pending power domain changes
* pm_lock_exec lock must be taken by caller.
*/
static void mali_pm_update_sync_internal(void)
{
/*
* This should only be called in non-atomic context
* (normally as deferred work)
*
* Look at the pending power domain changes, and execute these.
* Make sure group and schedulers are notified about changes.
*/
struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();
u32 power_down_mask;
u32 power_up_mask;
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
#if defined(DEBUG)
++num_pm_updates;
#endif
/* Hold PM state lock while we look at (and obey) the wanted state */
mali_pm_state_lock();
MALI_DEBUG_PRINT(5, ("PM update pre: Wanted domain mask: .. [%s]\n",
mali_pm_mask_to_string(pd_mask_wanted)));
MALI_DEBUG_PRINT(5, ("PM update pre: Current domain mask: . [%s]\n",
mali_pm_mask_to_string(pd_mask_current)));
MALI_DEBUG_PRINT(5, ("PM update pre: Current PMU mask: .... [%s]\n",
mali_pm_mask_to_string(pmu_mask_current)));
MALI_DEBUG_PRINT(5, ("PM update pre: Group power stats: ... <%s>\n",
mali_pm_group_stats_to_string()));
/* Figure out which cores we need to power on */
power_up_mask = pd_mask_wanted &
(pd_mask_wanted ^ pd_mask_current);
if (0 != power_up_mask) {
u32 power_up_mask_pmu;
struct mali_group *groups_up[MALI_MAX_NUMBER_OF_GROUPS];
u32 num_groups_up = 0;
struct mali_l2_cache_core *
l2_up[MALI_MAX_NUMBER_OF_L2_CACHE_CORES];
u32 num_l2_up = 0;
u32 i;
#if defined(DEBUG)
++num_pm_updates_up;
#endif
/*
* Make sure dummy/global domain is always included when
* powering up, since this is controlled by runtime PM,
* and device power is on at this stage.
*/
power_up_mask |= MALI_PM_DOMAIN_DUMMY_MASK;
/* Power up only real PMU domains */
power_up_mask_pmu = power_up_mask & ~MALI_PM_DOMAIN_DUMMY_MASK;
/* But not those that happen to be powered on already */
power_up_mask_pmu &= (power_up_mask ^ pmu_mask_current) &
power_up_mask;
if (0 != power_up_mask_pmu) {
MALI_DEBUG_ASSERT(NULL != pmu);
pmu_mask_current |= power_up_mask_pmu;
mali_pmu_power_up(pmu, power_up_mask_pmu);
}
/*
* Put the domains themselves in power up state.
* We get the groups and L2s to notify in return.
*/
mali_pm_domain_power_up(power_up_mask,
groups_up, &num_groups_up,
l2_up, &num_l2_up);
/* Need to unlock PM state lock before notifying L2 + groups */
mali_pm_state_unlock();
/* Notify each L2 cache that we have be powered up */
for (i = 0; i < num_l2_up; i++) {
mali_l2_cache_power_up(l2_up[i]);
}
/*
* Tell execution module about all the groups we have
* powered up. Groups will be notified as a result of this.
*/
mali_executor_group_power_up(groups_up, num_groups_up);
/* Lock state again before checking for power down */
mali_pm_state_lock();
}
/* Figure out which cores we need to power off */
power_down_mask = pd_mask_current &
(pd_mask_wanted ^ pd_mask_current);
/*
* Never power down the dummy/global domain here. This is to be done
* from a suspend request (since this domain is only physicall powered
* down at that point)
*/
power_down_mask &= ~MALI_PM_DOMAIN_DUMMY_MASK;
if (0 != power_down_mask) {
u32 power_down_mask_pmu;
struct mali_group *groups_down[MALI_MAX_NUMBER_OF_GROUPS];
u32 num_groups_down = 0;
struct mali_l2_cache_core *
l2_down[MALI_MAX_NUMBER_OF_L2_CACHE_CORES];
u32 num_l2_down = 0;
u32 i;
#if defined(DEBUG)
++num_pm_updates_down;
#endif
/*
* Put the domains themselves in power down state.
* We get the groups and L2s to notify in return.
*/
mali_pm_domain_power_down(power_down_mask,
groups_down, &num_groups_down,
l2_down, &num_l2_down);
/* Need to unlock PM state lock before notifying L2 + groups */
mali_pm_state_unlock();
/*
* Tell execution module about all the groups we will be
* powering down. Groups will be notified as a result of this.
*/
if (0 < num_groups_down) {
mali_executor_group_power_down(groups_down, num_groups_down);
}
/* Notify each L2 cache that we will be powering down */
for (i = 0; i < num_l2_down; i++) {
mali_l2_cache_power_down(l2_down[i]);
}
/*
* Power down only PMU domains which should not stay on
* Some domains might for instance currently be incorrectly
* powered up if default domain power state is all on.
*/
power_down_mask_pmu = pmu_mask_current & (~pd_mask_current);
if (0 != power_down_mask_pmu) {
MALI_DEBUG_ASSERT(NULL != pmu);
pmu_mask_current &= ~power_down_mask_pmu;
mali_pmu_power_down(pmu, power_down_mask_pmu);
}
} else {
/*
* Power down only PMU domains which should not stay on
* Some domains might for instance currently be incorrectly
* powered up if default domain power state is all on.
*/
u32 power_down_mask_pmu;
/* No need for state lock since we'll only update PMU */
mali_pm_state_unlock();
power_down_mask_pmu = pmu_mask_current & (~pd_mask_current);
if (0 != power_down_mask_pmu) {
MALI_DEBUG_ASSERT(NULL != pmu);
pmu_mask_current &= ~power_down_mask_pmu;
mali_pmu_power_down(pmu, power_down_mask_pmu);
}
}
MALI_DEBUG_PRINT(5, ("PM update post: Current domain mask: . [%s]\n",
mali_pm_mask_to_string(pd_mask_current)));
MALI_DEBUG_PRINT(5, ("PM update post: Current PMU mask: .... [%s]\n",
mali_pm_mask_to_string(pmu_mask_current)));
MALI_DEBUG_PRINT(5, ("PM update post: Group power stats: ... <%s>\n",
mali_pm_group_stats_to_string()));
}
static mali_bool mali_pm_common_suspend(void)
{
mali_pm_state_lock();
if (0 != pd_mask_wanted) {
MALI_DEBUG_PRINT(5, ("PM: Aborting suspend operation\n\n\n"));
mali_pm_state_unlock();
return MALI_FALSE;
}
MALI_DEBUG_PRINT(5, ("PM suspend pre: Wanted domain mask: .. [%s]\n",
mali_pm_mask_to_string(pd_mask_wanted)));
MALI_DEBUG_PRINT(5, ("PM suspend pre: Current domain mask: . [%s]\n",
mali_pm_mask_to_string(pd_mask_current)));
MALI_DEBUG_PRINT(5, ("PM suspend pre: Current PMU mask: .... [%s]\n",
mali_pm_mask_to_string(pmu_mask_current)));
MALI_DEBUG_PRINT(5, ("PM suspend pre: Group power stats: ... <%s>\n",
mali_pm_group_stats_to_string()));
if (0 != pd_mask_current) {
/*
* We have still some domains powered on.
* It is for instance very normal that at least the
* dummy/global domain is marked as powered on at this point.
* (because it is physically powered on until this function
* returns)
*/
struct mali_group *groups_down[MALI_MAX_NUMBER_OF_GROUPS];
u32 num_groups_down = 0;
struct mali_l2_cache_core *
l2_down[MALI_MAX_NUMBER_OF_L2_CACHE_CORES];
u32 num_l2_down = 0;
u32 i;
/*
* Put the domains themselves in power down state.
* We get the groups and L2s to notify in return.
*/
mali_pm_domain_power_down(pd_mask_current,
groups_down,
&num_groups_down,
l2_down,
&num_l2_down);
MALI_DEBUG_ASSERT(0 == pd_mask_current);
MALI_DEBUG_ASSERT(MALI_TRUE == mali_pm_domain_all_unused());
/* Need to unlock PM state lock before notifying L2 + groups */
mali_pm_state_unlock();
/*
* Tell execution module about all the groups we will be
* powering down. Groups will be notified as a result of this.
*/
if (0 < num_groups_down) {
mali_executor_group_power_down(groups_down, num_groups_down);
}
/* Notify each L2 cache that we will be powering down */
for (i = 0; i < num_l2_down; i++) {
mali_l2_cache_power_down(l2_down[i]);
}
pmu_mask_current = 0;
} else {
MALI_DEBUG_ASSERT(0 == pmu_mask_current);
MALI_DEBUG_ASSERT(MALI_TRUE == mali_pm_domain_all_unused());
mali_pm_state_unlock();
}
MALI_DEBUG_PRINT(5, ("PM suspend post: Current domain mask: [%s]\n",
mali_pm_mask_to_string(pd_mask_current)));
MALI_DEBUG_PRINT(5, ("PM suspend post: Current PMU mask: ... [%s]\n",
mali_pm_mask_to_string(pmu_mask_current)));
MALI_DEBUG_PRINT(5, ("PM suspend post: Group power stats: .. <%s>\n",
mali_pm_group_stats_to_string()));
return MALI_TRUE;
}
static void mali_pm_update_work(void *data)
{
MALI_IGNORE(data);
mali_pm_update_sync();
}
static _mali_osk_errcode_t mali_pm_create_pm_domains(void)
{
int i;
/* Create all domains (including dummy domain) */
for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
if (0x0 == domain_config[i]) continue;
if (NULL == mali_pm_domain_create(domain_config[i])) {
return _MALI_OSK_ERR_NOMEM;
}
}
return _MALI_OSK_ERR_OK;
}
static void mali_pm_set_default_pm_domain_config(void)
{
MALI_DEBUG_ASSERT(0 != _mali_osk_resource_base_address());
/* GP core */
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_GP, NULL)) {
domain_config[MALI_DOMAIN_INDEX_GP] = 0x01;
}
/* PP0 - PP3 core */
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP0, NULL)) {
if (mali_is_mali400()) {
domain_config[MALI_DOMAIN_INDEX_PP0] = 0x01 << 2;
} else if (mali_is_mali450()) {
domain_config[MALI_DOMAIN_INDEX_PP0] = 0x01 << 1;
} else if (mali_is_mali470()) {
domain_config[MALI_DOMAIN_INDEX_PP0] = 0x01 << 0;
}
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP1, NULL)) {
if (mali_is_mali400()) {
domain_config[MALI_DOMAIN_INDEX_PP1] = 0x01 << 3;
} else if (mali_is_mali450()) {
domain_config[MALI_DOMAIN_INDEX_PP1] = 0x01 << 2;
} else if (mali_is_mali470()) {
domain_config[MALI_DOMAIN_INDEX_PP1] = 0x01 << 1;
}
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP2, NULL)) {
if (mali_is_mali400()) {
domain_config[MALI_DOMAIN_INDEX_PP2] = 0x01 << 4;
} else if (mali_is_mali450()) {
domain_config[MALI_DOMAIN_INDEX_PP2] = 0x01 << 2;
} else if (mali_is_mali470()) {
domain_config[MALI_DOMAIN_INDEX_PP2] = 0x01 << 1;
}
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP3, NULL)) {
if (mali_is_mali400()) {
domain_config[MALI_DOMAIN_INDEX_PP3] = 0x01 << 5;
} else if (mali_is_mali450()) {
domain_config[MALI_DOMAIN_INDEX_PP3] = 0x01 << 2;
} else if (mali_is_mali470()) {
domain_config[MALI_DOMAIN_INDEX_PP3] = 0x01 << 1;
}
}
/* PP4 - PP7 */
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP4, NULL)) {
domain_config[MALI_DOMAIN_INDEX_PP4] = 0x01 << 3;
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP5, NULL)) {
domain_config[MALI_DOMAIN_INDEX_PP5] = 0x01 << 3;
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP6, NULL)) {
domain_config[MALI_DOMAIN_INDEX_PP6] = 0x01 << 3;
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI_OFFSET_PP7, NULL)) {
domain_config[MALI_DOMAIN_INDEX_PP7] = 0x01 << 3;
}
/* L2gp/L2PP0/L2PP4 */
if (mali_is_mali400()) {
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI400_OFFSET_L2_CACHE0, NULL)) {
domain_config[MALI_DOMAIN_INDEX_L20] = 0x01 << 1;
}
} else if (mali_is_mali450()) {
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI450_OFFSET_L2_CACHE0, NULL)) {
domain_config[MALI_DOMAIN_INDEX_L20] = 0x01 << 0;
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI450_OFFSET_L2_CACHE1, NULL)) {
domain_config[MALI_DOMAIN_INDEX_L21] = 0x01 << 1;
}
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI450_OFFSET_L2_CACHE2, NULL)) {
domain_config[MALI_DOMAIN_INDEX_L22] = 0x01 << 3;
}
} else if (mali_is_mali470()) {
if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
MALI470_OFFSET_L2_CACHE1, NULL)) {
domain_config[MALI_DOMAIN_INDEX_L21] = 0x01 << 0;
}
}
}
static u32 mali_pm_get_registered_cores_mask(void)
{
int i = 0;
u32 mask = 0;
for (i = 0; i < MALI_DOMAIN_INDEX_DUMMY; i++) {
mask |= domain_config[i];
}
return mask;
}
static void mali_pm_set_pmu_domain_config(void)
{
int i = 0;
_mali_osk_device_data_pmu_config_get(domain_config, MALI_MAX_NUMBER_OF_DOMAINS - 1);
for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS - 1; i++) {
if (0 != domain_config[i]) {
MALI_DEBUG_PRINT(2, ("Using customer pmu config:\n"));
break;
}
}
if (MALI_MAX_NUMBER_OF_DOMAINS - 1 == i) {
MALI_DEBUG_PRINT(2, ("Using hw detect pmu config:\n"));
mali_pm_set_default_pm_domain_config();
}
for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS - 1; i++) {
if (domain_config[i]) {
MALI_DEBUG_PRINT(2, ("domain_config[%d] = 0x%x \n", i, domain_config[i]));
}
}
/* Can't override dummy domain mask */
domain_config[MALI_DOMAIN_INDEX_DUMMY] =
1 << MALI_DOMAIN_INDEX_DUMMY;
}
#if defined(DEBUG)
const char *mali_pm_mask_to_string(u32 mask)
{
static char bit_str[MALI_MAX_NUMBER_OF_DOMAINS + 1];
int bit;
int str_pos = 0;
/* Must be protected by lock since we use shared string buffer */
if (NULL != pm_lock_exec) {
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
}
for (bit = MALI_MAX_NUMBER_OF_DOMAINS - 1; bit >= 0; bit--) {
if (mask & (1 << bit)) {
bit_str[str_pos] = 'X';
} else {
bit_str[str_pos] = '-';
}
str_pos++;
}
bit_str[MALI_MAX_NUMBER_OF_DOMAINS] = '\0';
return bit_str;
}
const char *mali_pm_group_stats_to_string(void)
{
static char bit_str[MALI_MAX_NUMBER_OF_GROUPS + 1];
u32 num_groups = mali_group_get_glob_num_groups();
u32 i;
/* Must be protected by lock since we use shared string buffer */
if (NULL != pm_lock_exec) {
MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
}
for (i = 0; i < num_groups && i < MALI_MAX_NUMBER_OF_GROUPS; i++) {
struct mali_group *group;
group = mali_group_get_glob_group(i);
if (MALI_TRUE == mali_group_power_is_on(group)) {
bit_str[i] = 'X';
} else {
bit_str[i] = '-';
}
}
bit_str[i] = '\0';
return bit_str;
}
#endif
/*
* num_pp is the number of PP cores which will be powered on given this mask
* cost is the total power cost of cores which will be powered on given this mask
*/
static void mali_pm_stat_from_mask(u32 mask, u32 *num_pp, u32 *cost)
{
u32 i;
/* loop through all cores */
for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
if (!(domain_config[i] & mask)) {
continue;
}
switch (i) {
case MALI_DOMAIN_INDEX_GP:
*cost += MALI_GP_COST;
break;
case MALI_DOMAIN_INDEX_PP0: /* Fall through */
case MALI_DOMAIN_INDEX_PP1: /* Fall through */
case MALI_DOMAIN_INDEX_PP2: /* Fall through */
case MALI_DOMAIN_INDEX_PP3:
if (mali_is_mali400()) {
if ((domain_config[MALI_DOMAIN_INDEX_L20] & mask)
|| (domain_config[MALI_DOMAIN_INDEX_DUMMY]
== domain_config[MALI_DOMAIN_INDEX_L20])) {
*num_pp += 1;
}
} else {
if ((domain_config[MALI_DOMAIN_INDEX_L21] & mask)
|| (domain_config[MALI_DOMAIN_INDEX_DUMMY]
== domain_config[MALI_DOMAIN_INDEX_L21])) {
*num_pp += 1;
}
}
*cost += MALI_PP_COST;
break;
case MALI_DOMAIN_INDEX_PP4: /* Fall through */
case MALI_DOMAIN_INDEX_PP5: /* Fall through */
case MALI_DOMAIN_INDEX_PP6: /* Fall through */
case MALI_DOMAIN_INDEX_PP7:
MALI_DEBUG_ASSERT(mali_is_mali450());
if ((domain_config[MALI_DOMAIN_INDEX_L22] & mask)
|| (domain_config[MALI_DOMAIN_INDEX_DUMMY]
== domain_config[MALI_DOMAIN_INDEX_L22])) {
*num_pp += 1;
}
*cost += MALI_PP_COST;
break;
case MALI_DOMAIN_INDEX_L20: /* Fall through */
case MALI_DOMAIN_INDEX_L21: /* Fall through */
case MALI_DOMAIN_INDEX_L22:
*cost += MALI_L2_COST;
break;
}
}
}
void mali_pm_power_cost_setup(void)
{
/*
* Two parallel arrays which store the best domain mask and its cost
* The index is the number of PP cores, E.g. Index 0 is for 1 PP option,
* might have mask 0x2 and with cost of 1, lower cost is better
*/
u32 best_mask[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS] = { 0 };
u32 best_cost[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS] = { 0 };
/* Array cores_in_domain is used to store the total pp cores in each pm domain. */
u32 cores_in_domain[MALI_MAX_NUMBER_OF_DOMAINS] = { 0 };
/* Domain_count is used to represent the max domain we have.*/
u32 max_domain_mask = 0;
u32 max_domain_id = 0;
u32 always_on_pp_cores = 0;
u32 num_pp, cost, mask;
u32 i, j , k;
/* Initialize statistics */
for (i = 0; i < MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS; i++) {
best_mask[i] = 0;
best_cost[i] = 0xFFFFFFFF; /* lower cost is better */
}
for (i = 0; i < MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS + 1; i++) {
for (j = 0; j < MALI_MAX_NUMBER_OF_DOMAINS; j++) {
mali_pm_domain_power_cost_result[i][j] = 0;
}
}
/* Caculate number of pp cores of a given domain config. */
for (i = MALI_DOMAIN_INDEX_PP0; i <= MALI_DOMAIN_INDEX_PP7; i++) {
if (0 < domain_config[i]) {
/* Get the max domain mask value used to caculate power cost
* and we don't count in always on pp cores. */
if (MALI_PM_DOMAIN_DUMMY_MASK != domain_config[i]
&& max_domain_mask < domain_config[i]) {
max_domain_mask = domain_config[i];
}
if (MALI_PM_DOMAIN_DUMMY_MASK == domain_config[i]) {
always_on_pp_cores++;
}
}
}
max_domain_id = _mali_osk_fls(max_domain_mask);
/*
* Try all combinations of power domains and check how many PP cores
* they have and their power cost.
*/
for (mask = 0; mask < (1 << max_domain_id); mask++) {
num_pp = 0;
cost = 0;
mali_pm_stat_from_mask(mask, &num_pp, &cost);
/* This mask is usable for all MP1 up to num_pp PP cores, check statistics for all */
for (i = 0; i < num_pp; i++) {
if (best_cost[i] >= cost) {
best_cost[i] = cost;
best_mask[i] = mask;
}
}
}
/*
* If we want to enable x pp cores, if x is less than number of always_on pp cores,
* all of pp cores we will enable must be always_on pp cores.
*/
for (i = 0; i < mali_executor_get_num_cores_total(); i++) {
if (i < always_on_pp_cores) {
mali_pm_domain_power_cost_result[i + 1][MALI_MAX_NUMBER_OF_DOMAINS - 1]
= i + 1;
} else {
mali_pm_domain_power_cost_result[i + 1][MALI_MAX_NUMBER_OF_DOMAINS - 1]
= always_on_pp_cores;
}
}
/* In this loop, variable i represent for the number of non-always on pp cores we want to enabled. */
for (i = 0; i < (mali_executor_get_num_cores_total() - always_on_pp_cores); i++) {
if (best_mask[i] == 0) {
/* This MP variant is not available */
continue;
}
for (j = 0; j < MALI_MAX_NUMBER_OF_DOMAINS; j++) {
cores_in_domain[j] = 0;
}
for (j = MALI_DOMAIN_INDEX_PP0; j <= MALI_DOMAIN_INDEX_PP7; j++) {
if (0 < domain_config[j]
&& (MALI_PM_DOMAIN_DUMMY_MASK != domain_config[i])) {
cores_in_domain[_mali_osk_fls(domain_config[j]) - 1]++;
}
}
/* In this loop, j represent for the number we have already enabled.*/
for (j = 0; j <= i;) {
/* j used to visit all of domain to get the number of pp cores remained in it. */
for (k = 0; k < max_domain_id; k++) {
/* If domain k in best_mask[i] is enabled and this domain has extra pp cores,
* we know we must pick at least one pp core from this domain.
* And then we move to next enabled pm domain. */
if ((best_mask[i] & (0x1 << k)) && (0 < cores_in_domain[k])) {
cores_in_domain[k]--;
mali_pm_domain_power_cost_result[always_on_pp_cores + i + 1][k]++;
j++;
if (j > i) {
break;
}
}
}
}
}
}
/*
* When we are doing core scaling,
* this function is called to return the best mask to
* achieve the best pp group power cost.
*/
void mali_pm_get_best_power_cost_mask(int num_requested, int *dst)
{
MALI_DEBUG_ASSERT((mali_executor_get_num_cores_total() >= num_requested) && (0 <= num_requested));
_mali_osk_memcpy(dst, mali_pm_domain_power_cost_result[num_requested], MALI_MAX_NUMBER_OF_DOMAINS * sizeof(int));
}
u32 mali_pm_get_current_mask(void)
{
return pd_mask_current;
}
u32 mali_pm_get_wanted_mask(void)
{
return pd_mask_wanted;
}