/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_gpu.h"
#include "msm_gem.h"
#include "msm_mmu.h"
#include "msm_fence.h"
#include <generated/utsrelease.h>
#include <linux/string_helpers.h>
#include <linux/pm_opp.h>
#include <linux/devfreq.h>
#include <linux/devcoredump.h>
/*
* Power Management:
*/
static int msm_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct msm_gpu *gpu = platform_get_drvdata(to_platform_device(dev));
struct dev_pm_opp *opp;
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp))
return PTR_ERR(opp);
clk_set_rate(gpu->core_clk, *freq);
dev_pm_opp_put(opp);
return 0;
}
static int msm_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *status)
{
struct msm_gpu *gpu = platform_get_drvdata(to_platform_device(dev));
u64 cycles;
u32 freq = ((u32) status->current_frequency) / 1000000;
ktime_t time;
status->current_frequency = (unsigned long) clk_get_rate(gpu->core_clk);
gpu->funcs->gpu_busy(gpu, &cycles);
status->busy_time = ((u32) (cycles - gpu->devfreq.busy_cycles)) / freq;
gpu->devfreq.busy_cycles = cycles;
time = ktime_get();
status->total_time = ktime_us_delta(time, gpu->devfreq.time);
gpu->devfreq.time = time;
return 0;
}
static int msm_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct msm_gpu *gpu = platform_get_drvdata(to_platform_device(dev));
*freq = (unsigned long) clk_get_rate(gpu->core_clk);
return 0;
}
static struct devfreq_dev_profile msm_devfreq_profile = {
.polling_ms = 10,
.target = msm_devfreq_target,
.get_dev_status = msm_devfreq_get_dev_status,
.get_cur_freq = msm_devfreq_get_cur_freq,
};
static void msm_devfreq_init(struct msm_gpu *gpu)
{
/* We need target support to do devfreq */
if (!gpu->funcs->gpu_busy)
return;
msm_devfreq_profile.initial_freq = gpu->fast_rate;
/*
* Don't set the freq_table or max_state and let devfreq build the table
* from OPP
*/
gpu->devfreq.devfreq = devm_devfreq_add_device(&gpu->pdev->dev,
&msm_devfreq_profile, "simple_ondemand", NULL);
if (IS_ERR(gpu->devfreq.devfreq)) {
dev_err(&gpu->pdev->dev, "Couldn't initialize GPU devfreq\n");
gpu->devfreq.devfreq = NULL;
}
}
static int enable_pwrrail(struct msm_gpu *gpu)
{
struct drm_device *dev = gpu->dev;
int ret = 0;
if (gpu->gpu_reg) {
ret = regulator_enable(gpu->gpu_reg);
if (ret) {
dev_err(dev->dev, "failed to enable 'gpu_reg': %d\n", ret);
return ret;
}
}
if (gpu->gpu_cx) {
ret = regulator_enable(gpu->gpu_cx);
if (ret) {
dev_err(dev->dev, "failed to enable 'gpu_cx': %d\n", ret);
return ret;
}
}
return 0;
}
static int disable_pwrrail(struct msm_gpu *gpu)
{
if (gpu->gpu_cx)
regulator_disable(gpu->gpu_cx);
if (gpu->gpu_reg)
regulator_disable(gpu->gpu_reg);
return 0;
}
static int enable_clk(struct msm_gpu *gpu)
{
if (gpu->core_clk && gpu->fast_rate)
clk_set_rate(gpu->core_clk, gpu->fast_rate);
/* Set the RBBM timer rate to 19.2Mhz */
if (gpu->rbbmtimer_clk)
clk_set_rate(gpu->rbbmtimer_clk, 19200000);
return clk_bulk_prepare_enable(gpu->nr_clocks, gpu->grp_clks);
}
static int disable_clk