M7350/kernel/drivers/gpu/msm/kgsl_pwrscale_msm.c
2024-09-09 08:52:07 +00:00

270 lines
6.8 KiB
C

/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/slab.h>
#include <mach/msm_dcvs.h>
#include "kgsl.h"
#include "kgsl_pwrscale.h"
#include "kgsl_device.h"
#include "a2xx_reg.h"
#include "kgsl_trace.h"
struct msm_priv {
struct kgsl_device *device;
int enabled;
unsigned int cur_freq;
unsigned int req_level;
int floor_level;
struct msm_dcvs_core_info *core_info;
int gpu_busy;
int dcvs_core_id;
};
/* reference to be used in idle and freq callbacks */
static struct msm_priv *the_msm_priv;
static int msm_idle_enable(int type_core_num,
enum msm_core_control_event event)
{
struct msm_priv *priv = the_msm_priv;
switch (event) {
case MSM_DCVS_ENABLE_IDLE_PULSE:
priv->enabled = true;
break;
case MSM_DCVS_DISABLE_IDLE_PULSE:
priv->enabled = false;
break;
case MSM_DCVS_ENABLE_HIGH_LATENCY_MODES:
case MSM_DCVS_DISABLE_HIGH_LATENCY_MODES:
break;
}
return 0;
}
/* Set the requested frequency if it is within 5MHz (delta) of a
* supported frequency.
*/
static int msm_set_freq(int core_num, unsigned int freq)
{
int i, delta = 5000000;
struct msm_priv *priv = the_msm_priv;
struct kgsl_device *device = priv->device;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
/* msm_dcvs manager uses frequencies in kHz */
freq *= 1000;
for (i = 0; i < pwr->num_pwrlevels; i++)
if (abs(pwr->pwrlevels[i].gpu_freq - freq) < delta)
break;
if (i == pwr->num_pwrlevels)
return 0;
mutex_lock(&device->mutex);
priv->req_level = i;
if (priv->req_level <= priv->floor_level) {
kgsl_pwrctrl_pwrlevel_change(device, priv->req_level);
priv->cur_freq = pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq;
}
mutex_unlock(&device->mutex);
/* return current frequency in kHz */
return priv->cur_freq / 1000;
}
static int msm_set_min_freq(int core_num, unsigned int freq)
{
int i, delta = 5000000;
struct msm_priv *priv = the_msm_priv;
struct kgsl_device *device = priv->device;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
/* msm_dcvs manager uses frequencies in kHz */
freq *= 1000;
for (i = 0; i < pwr->num_pwrlevels; i++)
if (abs(pwr->pwrlevels[i].gpu_freq - freq) < delta)
break;
if (i == pwr->num_pwrlevels)
return 0;
mutex_lock(&device->mutex);
priv->floor_level = i;
if (priv->floor_level <= priv->req_level)
kgsl_pwrctrl_pwrlevel_change(device, priv->floor_level);
else if (priv->floor_level > priv->req_level)
kgsl_pwrctrl_pwrlevel_change(device, priv->req_level);
priv->cur_freq = pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq;
mutex_unlock(&device->mutex);
/* return current frequency in kHz */
return priv->cur_freq / 1000;
}
static unsigned int msm_get_freq(int core_num)
{
struct msm_priv *priv = the_msm_priv;
/* return current frequency in kHz */
return priv->cur_freq / 1000;
}
static void msm_busy(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct msm_priv *priv = pwrscale->priv;
if (priv->enabled && !priv->gpu_busy) {
msm_dcvs_idle(priv->dcvs_core_id, MSM_DCVS_IDLE_EXIT, 0);
trace_kgsl_mpdcvs(device, 1);
priv->gpu_busy = 1;
}
return;
}
static void msm_idle(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct msm_priv *priv = pwrscale->priv;
if (priv->enabled && priv->gpu_busy)
if (device->ftbl->isidle(device)) {
msm_dcvs_idle(priv->dcvs_core_id,
MSM_DCVS_IDLE_ENTER, 0);
trace_kgsl_mpdcvs(device, 0);
priv->gpu_busy = 0;
}
return;
}
static void msm_sleep(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct msm_priv *priv = pwrscale->priv;
if (priv->enabled && priv->gpu_busy) {
msm_dcvs_idle(priv->dcvs_core_id, MSM_DCVS_IDLE_ENTER, 0);
trace_kgsl_mpdcvs(device, 0);
priv->gpu_busy = 0;
}
return;
}
static void msm_set_io_fraction(struct kgsl_device *device,
unsigned int value)
{
int i;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
for (i = 0; i < pwr->num_pwrlevels; i++)
pwr->pwrlevels[i].io_fraction = value;
}
static void msm_restore_io_fraction(struct kgsl_device *device)
{
int i;
struct kgsl_device_platform_data *pdata =
kgsl_device_get_drvdata(device);
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
for (i = 0; i < pdata->num_levels; i++)
pwr->pwrlevels[i].io_fraction =
pdata->pwrlevel[i].io_fraction;
}
static int msm_init(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct msm_priv *priv;
struct msm_dcvs_freq_entry *tbl;
int i, ret = -EINVAL, low_level;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
struct platform_device *pdev =
container_of(device->parentdev, struct platform_device, dev);
struct kgsl_device_platform_data *pdata = pdev->dev.platform_data;
if (the_msm_priv) {
priv = pwrscale->priv = the_msm_priv;
} else {
priv = pwrscale->priv = kzalloc(sizeof(struct msm_priv),
GFP_KERNEL);
if (pwrscale->priv == NULL)
return -ENOMEM;
priv->core_info = pdata->core_info;
tbl = priv->core_info->freq_tbl;
priv->floor_level = pwr->num_pwrlevels - 1;
/* Fill in frequency table from low to high, reversing order. */
low_level = pwr->num_pwrlevels - KGSL_PWRLEVEL_LAST_OFFSET;
for (i = 0; i <= low_level; i++)
tbl[i].freq =
pwr->pwrlevels[low_level - i].gpu_freq / 1000;
priv->dcvs_core_id =
msm_dcvs_register_core(MSM_DCVS_CORE_TYPE_GPU,
0,
priv->core_info,
msm_set_freq, msm_get_freq, msm_idle_enable,
msm_set_min_freq,
priv->core_info->sensors[0]);
if (priv->dcvs_core_id < 0) {
KGSL_PWR_ERR(device, "msm_dcvs_register_core failed");
goto err;
}
the_msm_priv = priv;
}
priv->device = device;
ret = msm_dcvs_freq_sink_start(priv->dcvs_core_id);
if (ret >= 0) {
if (device->ftbl->isidle(device)) {
priv->gpu_busy = 0;
msm_dcvs_idle(priv->dcvs_core_id,
MSM_DCVS_IDLE_ENTER, 0);
} else {
priv->gpu_busy = 1;
}
msm_set_io_fraction(device, 0);
return 0;
}
KGSL_PWR_ERR(device, "msm_dcvs_freq_sink_register failed\n");
err:
if (!the_msm_priv)
kfree(pwrscale->priv);
pwrscale->priv = NULL;
return ret;
}
static void msm_close(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct msm_priv *priv = pwrscale->priv;
if (pwrscale->priv == NULL)
return;
msm_dcvs_freq_sink_stop(priv->dcvs_core_id);
pwrscale->priv = NULL;
msm_restore_io_fraction(device);
}
struct kgsl_pwrscale_policy kgsl_pwrscale_policy_msm = {
.name = "msm",
.init = msm_init,
.idle = msm_idle,
.busy = msm_busy,
.sleep = msm_sleep,
.close = msm_close,
};