309 lines
8.0 KiB
C
309 lines
8.0 KiB
C
/*
|
|
* drivers/video/tegra/host/gr3d/scale_emc.c
|
|
*
|
|
* Tegra Graphics Host 3D clock scaling
|
|
*
|
|
* Copyright (c) 2010-2015, NVIDIA Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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/>.
|
|
*/
|
|
|
|
/*
|
|
* 3d clock scaling mechanism
|
|
*
|
|
* module3d_notify_busy() is called upon submit, module3d_notify_idle() is
|
|
* called when all outstanding submits are completed. Both functions notify
|
|
* the governor about changed state.
|
|
*
|
|
* 3d.emc clock is scaled proportionately to 3d clock, with a quadratic-
|
|
* bezier-like factor added to pull 3d.emc rate a bit lower.
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/export.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/ftrace.h>
|
|
#include <soc/tegra/chip-id.h>
|
|
|
|
#include "chip_support.h"
|
|
#include "dev.h"
|
|
#include "scale_emc.h"
|
|
#include "nvhost_acm.h"
|
|
#include "nvhost_scale.h"
|
|
|
|
#define POW2(x) ((x) * (x))
|
|
|
|
/*
|
|
* 20.12 fixed point arithmetic
|
|
*/
|
|
|
|
static const int FXFRAC = 12;
|
|
static const int FX_HALF = (1 << 12) / 2;
|
|
|
|
#define INT_TO_FX(x) ((x) << FXFRAC)
|
|
#define FX_TO_INT(x) ((x) >> FXFRAC)
|
|
|
|
static int FXMUL(int x, int y);
|
|
static int FXDIV(int x, int y);
|
|
|
|
|
|
#define MHZ_TO_HZ(x) ((x) * 1000000)
|
|
#define HZ_TO_MHZ(x) ((x) / 1000000)
|
|
|
|
struct nvhost_gr3d_params {
|
|
struct nvhost_emc_params emc_params;
|
|
int clk_3d;
|
|
int clk_3d2;
|
|
int clk_3d_emc;
|
|
};
|
|
|
|
/* Convert clk index to struct clk * */
|
|
static inline struct clk *clk(struct nvhost_device_profile *profile, int index)
|
|
{
|
|
struct nvhost_device_data *pdata =
|
|
platform_get_drvdata(profile->pdev);
|
|
return pdata->clk[index];
|
|
}
|
|
|
|
long nvhost_scale_emc_get_emc_rate(struct nvhost_emc_params *emc_params,
|
|
long freq)
|
|
{
|
|
long hz;
|
|
|
|
freq = INT_TO_FX(HZ_TO_MHZ(freq));
|
|
hz = FXMUL(freq, emc_params->emc_slope) + emc_params->emc_offset;
|
|
|
|
if (!emc_params->linear)
|
|
hz -= FXMUL(emc_params->emc_dip_slope,
|
|
FXMUL(freq - emc_params->emc_xmid,
|
|
freq - emc_params->emc_xmid)) +
|
|
emc_params->emc_dip_offset;
|
|
|
|
hz = MHZ_TO_HZ(FX_TO_INT(hz + FX_HALF)); /* round to nearest */
|
|
hz = (hz < 0) ? 0 : hz;
|
|
|
|
return hz;
|
|
}
|
|
|
|
void nvhost_scale_emc_callback(struct nvhost_device_profile *profile,
|
|
unsigned long freq)
|
|
{
|
|
struct nvhost_gr3d_params *gr3d_params = profile->private_data;
|
|
struct nvhost_emc_params *emc_params = &gr3d_params->emc_params;
|
|
long hz;
|
|
long after;
|
|
|
|
/* Set EMC clockrate */
|
|
after = (long) clk_get_rate(clk(profile, gr3d_params->clk_3d));
|
|
hz = nvhost_scale_emc_get_emc_rate(emc_params, after);
|
|
nvhost_module_set_rate(profile->pdev, gr3d_params, hz,
|
|
gr3d_params->clk_3d_emc, NVHOST_CLOCK);
|
|
}
|
|
|
|
/*
|
|
* nvhost_scale_emc_calibrate_emc()
|
|
*
|
|
* Compute emc scaling parameters
|
|
*
|
|
* Remc = S * R3d + O - (Sd * (R3d - Rm)^2 + Od)
|
|
*
|
|
* Remc - 3d.emc rate
|
|
* R3d - 3d.cbus rate
|
|
* Rm - 3d.cbus 'middle' rate = (max + min)/2
|
|
* S - emc_slope
|
|
* O - emc_offset
|
|
* Sd - emc_dip_slope
|
|
* Od - emc_dip_offset
|
|
*
|
|
* this superposes a quadratic dip centered around the middle 3d
|
|
* frequency over a linear correlation of 3d.emc to 3d clock
|
|
* rates.
|
|
*
|
|
* S, O are chosen so that the maximum 3d rate produces the
|
|
* maximum 3d.emc rate exactly, and the minimum 3d rate produces
|
|
* at least the minimum 3d.emc rate.
|
|
*
|
|
* Sd and Od are chosen to produce the largest dip that will
|
|
* keep 3d.emc frequencies monotonously decreasing with 3d
|
|
* frequencies. To achieve this, the first derivative of Remc
|
|
* with respect to R3d should be zero for the minimal 3d rate:
|
|
*
|
|
* R'emc = S - 2 * Sd * (R3d - Rm)
|
|
* R'emc(R3d-min) = 0
|
|
* S = 2 * Sd * (R3d-min - Rm)
|
|
* = 2 * Sd * (R3d-min - R3d-max) / 2
|
|
*
|
|
* +------------------------------+
|
|
* | Sd = S / (R3d-min - R3d-max) |
|
|
* +------------------------------+
|
|
*
|
|
* dip = Sd * (R3d - Rm)^2 + Od
|
|
*
|
|
* requiring dip(R3d-min) = 0 and dip(R3d-max) = 0 gives
|
|
*
|
|
* Sd * (R3d-min - Rm)^2 + Od = 0
|
|
* Od = -Sd * ((R3d-min - R3d-max) / 2)^2
|
|
* = -Sd * ((R3d-min - R3d-max)^2) / 4
|
|
*
|
|
* +------------------------------+
|
|
* | Od = (emc-max - emc-min) / 4 |
|
|
* +------------------------------+
|
|
*
|
|
*/
|
|
|
|
void nvhost_scale_emc_calibrate_emc(struct nvhost_emc_params *emc_params,
|
|
struct clk *clk_3d, struct clk *clk_3d_emc,
|
|
bool linear_emc)
|
|
{
|
|
long correction;
|
|
unsigned long max_emc;
|
|
unsigned long min_emc;
|
|
unsigned long min_rate_3d;
|
|
unsigned long max_rate_3d;
|
|
|
|
max_emc = clk_round_rate(clk_3d_emc, UINT_MAX);
|
|
max_emc = INT_TO_FX(HZ_TO_MHZ(max_emc));
|
|
|
|
min_emc = clk_round_rate(clk_3d_emc, 0);
|
|
min_emc = INT_TO_FX(HZ_TO_MHZ(min_emc));
|
|
|
|
max_rate_3d = clk_round_rate(clk_3d, UINT_MAX);
|
|
max_rate_3d = INT_TO_FX(HZ_TO_MHZ(max_rate_3d));
|
|
|
|
min_rate_3d = clk_round_rate(clk_3d, 0);
|
|
min_rate_3d = INT_TO_FX(HZ_TO_MHZ(min_rate_3d));
|
|
|
|
emc_params->emc_slope =
|
|
FXDIV((max_emc - min_emc), (max_rate_3d - min_rate_3d));
|
|
emc_params->emc_offset = max_emc -
|
|
FXMUL(emc_params->emc_slope, max_rate_3d);
|
|
/* Guarantee max 3d rate maps to max emc rate */
|
|
emc_params->emc_offset += max_emc -
|
|
(FXMUL(emc_params->emc_slope, max_rate_3d) +
|
|
emc_params->emc_offset);
|
|
|
|
emc_params->linear = linear_emc;
|
|
if (linear_emc)
|
|
return;
|
|
|
|
emc_params->emc_dip_offset = (max_emc - min_emc) / 4;
|
|
emc_params->emc_dip_slope =
|
|
-FXDIV(emc_params->emc_slope, max_rate_3d - min_rate_3d);
|
|
emc_params->emc_xmid = (max_rate_3d + min_rate_3d) / 2;
|
|
correction =
|
|
emc_params->emc_dip_offset +
|
|
FXMUL(emc_params->emc_dip_slope,
|
|
FXMUL(max_rate_3d - emc_params->emc_xmid,
|
|
max_rate_3d - emc_params->emc_xmid));
|
|
emc_params->emc_dip_offset -= correction;
|
|
}
|
|
|
|
/*
|
|
* nvhost_scale_emc_init(dev)
|
|
*
|
|
* Initialise 3d clock scaling for the given device. This function installs
|
|
* pod_scaling governor to handle the clock scaling.
|
|
*/
|
|
|
|
void nvhost_scale_emc_init(struct platform_device *pdev)
|
|
{
|
|
struct nvhost_device_data *pdata = platform_get_drvdata(pdev);
|
|
struct nvhost_device_profile *profile;
|
|
struct nvhost_gr3d_params *gr3d_params;
|
|
|
|
nvhost_scale_init(pdev);
|
|
profile = pdata->power_profile;
|
|
if (!profile)
|
|
return;
|
|
|
|
gr3d_params = kzalloc(sizeof(*gr3d_params), GFP_KERNEL);
|
|
if (!gr3d_params)
|
|
goto err_allocate_gr3d_params;
|
|
|
|
gr3d_params->clk_3d = 0;
|
|
gr3d_params->clk_3d_emc = 1;
|
|
|
|
profile->private_data = gr3d_params;
|
|
|
|
nvhost_scale_emc_calibrate_emc(&gr3d_params->emc_params,
|
|
clk(profile, gr3d_params->clk_3d),
|
|
clk(profile, gr3d_params->clk_3d_emc),
|
|
pdata->linear_emc);
|
|
|
|
if (nvhost_module_add_client(pdev, gr3d_params))
|
|
nvhost_err(&pdev->dev, "failed to register as a acm client");
|
|
|
|
return;
|
|
|
|
err_allocate_gr3d_params:
|
|
nvhost_scale_deinit(pdev);
|
|
}
|
|
|
|
/*
|
|
* nvhost_scale_emc_deinit(dev)
|
|
*
|
|
* Stop 3d scaling for the given device.
|
|
*/
|
|
|
|
void nvhost_scale_emc_deinit(struct platform_device *pdev)
|
|
{
|
|
struct nvhost_device_data *pdata = platform_get_drvdata(pdev);
|
|
if (!pdata->power_profile)
|
|
return;
|
|
|
|
/* Remove devfreq from acm client list */
|
|
nvhost_module_remove_client(pdev, pdata->power_profile->private_data);
|
|
|
|
kfree(pdata->power_profile->private_data);
|
|
pdata->power_profile->private_data = NULL;
|
|
|
|
nvhost_scale_deinit(pdev);
|
|
}
|
|
|
|
/*
|
|
* 20.12 fixed point arithmetic
|
|
*
|
|
* int FXMUL(int x, int y)
|
|
* int FXDIV(int x, int y)
|
|
*/
|
|
|
|
static int FXMUL(int x, int y)
|
|
{
|
|
return ((long long) x * (long long) y) >> FXFRAC;
|
|
}
|
|
|
|
static int FXDIV(int x, int y)
|
|
{
|
|
/* long long div operation not supported, must shift manually. This
|
|
* would have been
|
|
*
|
|
* return (((long long) x) << FXFRAC) / (long long) y;
|
|
*/
|
|
int pos, t;
|
|
if (x == 0)
|
|
return 0;
|
|
|
|
/* find largest allowable right shift to numerator, limit to FXFRAC */
|
|
t = x < 0 ? -x : x;
|
|
pos = 31 - fls(t); /* fls can't be 32 if x != 0 */
|
|
if (pos > FXFRAC)
|
|
pos = FXFRAC;
|
|
|
|
y >>= FXFRAC - pos;
|
|
if (y == 0)
|
|
return 0x7FFFFFFF; /* overflow, return MAX_FIXED */
|
|
|
|
return (x << pos) / y;
|
|
}
|