1008 lines
27 KiB
C
1008 lines
27 KiB
C
/**************************************************************************
|
|
* Copyright (c) 2012, Intel Corporation.
|
|
* All Rights Reserved.
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* Authors:
|
|
* Dale B. Stimson <dale.b.stimson@intel.com>
|
|
* Javier Torres Castillo <javier.torres.castillo@intel.com>
|
|
*
|
|
* df_rgx.c - devfreq driver for IMG rgx graphics in Tangier.
|
|
* Description:
|
|
* Early devfreq driver for rgx. Utilization measures and on-demand
|
|
* frequency control will be added later. For now, only thermal
|
|
* conditions and sysfs file inputs are taken into account.
|
|
*
|
|
* This driver currently only allows frequencies between 200MHz and
|
|
* 533 MHz.
|
|
*
|
|
* This driver observes the limits set by the values in:
|
|
*
|
|
* sysfs file initial value (KHz)
|
|
* --------------------------------- -------------------
|
|
* /sys/class/devfreq/dfrgx/min_freq 200000
|
|
* /sys/class/devfreq/dfrgx/max_freq 320000, 533000 on B0
|
|
* and provides current frequency from:
|
|
* /sys/class/devfreq/dfrgx/cur_freq
|
|
*
|
|
* With current development silicon, instability is a real possibility
|
|
* at 400 MHz and higher.
|
|
*
|
|
* While the driver is informed that a thermal condition exists, it
|
|
* reduces the gpu frequency to 200 MHz.
|
|
*
|
|
* Temporary:
|
|
* No use of performance counters.
|
|
* No utilization computation.
|
|
* Uses governor "devfreq_powersave", although with throttling if hot.
|
|
*
|
|
* It would be nice to have more sysfs or debugfs files for testing purposes.
|
|
*
|
|
* All DEBUG printk messages start with "dfrgx:" for easy searching of
|
|
* dmesg output.
|
|
*
|
|
* To test with the module: insmod /lib/modules/dfrgx.ko
|
|
* To unload module: rmmod dfrgx
|
|
*
|
|
* See files under /sys/class/devfreq/dfrgx .
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/suspend.h>
|
|
|
|
#include <linux/thermal.h>
|
|
#include <asm/errno.h>
|
|
|
|
#include <linux/opp.h>
|
|
#include <linux/devfreq.h>
|
|
|
|
#include <governor.h>
|
|
|
|
#include <rgxdf.h>
|
|
#include <ospm/gfx_freq.h>
|
|
#include "dev_freq_debug.h"
|
|
#include "dev_freq_graphics_pm.h"
|
|
#include "df_rgx_defs.h"
|
|
#include "df_rgx_burst.h"
|
|
#define DFRGX_GLOBAL_ENABLE_DEFAULT 1
|
|
|
|
#define DF_RGX_NAME_DEV "dfrgx"
|
|
#define DF_RGX_NAME_DRIVER "dfrgxdrv"
|
|
|
|
#define DFRGX_HEADING DF_RGX_NAME_DEV ": "
|
|
|
|
/* DF_RGX_POLLING_INTERVAL_MS - Polling interval in milliseconds.
|
|
* FIXME - Need to have this be 5 ms, but have to workaround HZ tick usage.
|
|
*/
|
|
#define DF_RGX_POLLING_INTERVAL_MS 50
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
|
|
/**
|
|
* Potential governors:
|
|
* #define GOVERNOR_TO_USE "performance"
|
|
* #define GOVERNOR_TO_USE "simple_ondemand"
|
|
* #define GOVERNOR_TO_USE "userspace"
|
|
* #define GOVERNOR_TO_USE "powersave"
|
|
*/
|
|
#define GOVERNOR_TO_USE "simple_ondemand"
|
|
#else
|
|
/**
|
|
* Potential governors:
|
|
* #define GOVERNOR_TO_USE devfreq_simple_ondemand
|
|
* #define GOVERNOR_TO_USE devfreq_performance
|
|
* #define GOVERNOR_TO_USE devfreq_powersave
|
|
*/
|
|
#define GOVERNOR_TO_USE devfreq_simple_ondemand
|
|
#endif
|
|
|
|
|
|
/*is tng a0 hw*/
|
|
extern int is_tng_a0;
|
|
|
|
/* df_rgx_created_dev - Pointer to created device, if any. */
|
|
static struct platform_device *df_rgx_created_dev;
|
|
|
|
void df_rgx_init_available_freq_table(struct device *dev);
|
|
int opp_add(struct device *dev, unsigned long freq, unsigned long u_volt);
|
|
|
|
|
|
|
|
/**
|
|
* Module parameters:
|
|
*
|
|
* - can be updated (if permission allows) via writing:
|
|
* /sys/module/dfrgx/parameters/<name>
|
|
* - can be set at module load time:
|
|
* insmod /lib/modules/dfrgx.ko enable=0
|
|
* - For built-in drivers, can be on kernel command line:
|
|
* dfrgx.enable=0
|
|
*/
|
|
|
|
/**
|
|
* module parameter "enable" is not writable in sysfs as there is presently
|
|
* no code to detect the transition between 0 and 1.
|
|
*/
|
|
static unsigned int mprm_enable = DFRGX_GLOBAL_ENABLE_DEFAULT;
|
|
module_param_named(enable, mprm_enable, uint, S_IRUGO);
|
|
|
|
static unsigned int mprm_verbosity = 2;
|
|
module_param_named(verbosity, mprm_verbosity, uint, S_IRUGO|S_IWUSR);
|
|
|
|
|
|
#define DRIVER_AUTHOR "Intel Corporation"
|
|
#define DRIVER_DESC "devfreq driver for rgx graphics"
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_LICENSE("GPL");
|
|
|
|
/**
|
|
* MODULE_VERSION - Allows specification of a module version.
|
|
* Version of form [<epoch>:]<version>[-<extra-version>].
|
|
* Or for CVS/RCS ID version, everything but the number is stripped.
|
|
* <epoch>: A (small) unsigned integer which allows you to start versions
|
|
* anew. If not mentioned, it's zero. eg. "2:1.0" is after
|
|
* "1:2.0".
|
|
* <version>: The <version> may contain only alphanumerics and the
|
|
* character `.'. Ordered by numeric sort for numeric parts,
|
|
* ascii sort for ascii parts (as per RPM or DEB algorithm).
|
|
* <extraversion>: Like <version>, but inserted for local
|
|
* customizations, eg "rh3" or "rusty1".
|
|
|
|
* Using this automatically adds a checksum of the .c files and the
|
|
* local headers in "srcversion".
|
|
*
|
|
* Also, if the module is under drivers/staging, this causes a warning to
|
|
* be issued:
|
|
* <mname>: module is from the staging directory, the quality is unknown,
|
|
* you have been warned.
|
|
*
|
|
* Example invocation:
|
|
* MODULE_VERSION("0.1");
|
|
*/
|
|
|
|
/**
|
|
* df_rgx_bus_target - Request setting of a new frequency.
|
|
* @*p_freq: Input: desired frequency in KHz, output: realized freq in KHz.
|
|
* @flags: DEVFREQ_FLAG_* - not used by this implementation.
|
|
*/
|
|
static int df_rgx_bus_target(struct device *dev, unsigned long *p_freq,
|
|
u32 flags)
|
|
{
|
|
struct platform_device *pdev;
|
|
struct busfreq_data *bfdata;
|
|
struct df_rgx_data_s *pdfrgx_data;
|
|
struct devfreq *df;
|
|
unsigned long desired_freq = 0;
|
|
int ret = 0;
|
|
int adjust_curfreq = 0;
|
|
int set_freq = 0;
|
|
(void) flags;
|
|
|
|
pdev = container_of(dev, struct platform_device, dev);
|
|
bfdata = platform_get_drvdata(pdev);
|
|
|
|
if (bfdata && bfdata->devfreq) {
|
|
int gpu_defer_req = 0;
|
|
df = bfdata->devfreq;
|
|
pdfrgx_data = &bfdata->g_dfrgx_data;
|
|
if (!pdfrgx_data || !pdfrgx_data->g_initialized)
|
|
goto out;
|
|
|
|
desired_freq = *p_freq;
|
|
|
|
/* Governor changed, will be updated after updatedevfreq() */
|
|
if (strncmp(df->governor->name,
|
|
bfdata->prev_governor, DEVFREQ_NAME_LEN)) {
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: Governor changed,"
|
|
" prev : %s, current : %s!\n",
|
|
__func__,
|
|
bfdata->prev_governor,
|
|
df->governor->name);
|
|
|
|
if (dfrgx_burst_is_enabled(pdfrgx_data))
|
|
dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);
|
|
|
|
df_rgx_set_governor_profile(df->governor->name, pdfrgx_data);
|
|
strncpy(bfdata->prev_governor, df->governor->name, DEVFREQ_NAME_LEN);
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: Governors should be "
|
|
"the same now, prev : %s, current : %s!\n",
|
|
__func__,
|
|
bfdata->prev_governor,
|
|
df->governor->name);
|
|
|
|
set_freq = 1;
|
|
} else if (df->min_freq != pdfrgx_data->g_freq_mhz_min) {
|
|
int new_index = -1;
|
|
|
|
if (dfrgx_burst_is_enabled(pdfrgx_data))
|
|
dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);
|
|
|
|
new_index = df_rgx_get_util_record_index_by_freq(df->min_freq);
|
|
if (new_index > -1) {
|
|
mutex_lock(&pdfrgx_data->g_mutex_sts);
|
|
pdfrgx_data->g_freq_mhz_min = df->min_freq;
|
|
bfdata->gbp_cooldv_latest_freq_min = df->min_freq;
|
|
pdfrgx_data->g_min_freq_index = new_index;
|
|
mutex_unlock(&pdfrgx_data->g_mutex_sts);
|
|
}
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:Min freq changed!,"
|
|
" prev_freq %lu, min_freq %lu\n",
|
|
__func__,
|
|
df->previous_freq,
|
|
df->min_freq);
|
|
|
|
if (df->previous_freq < df->min_freq) {
|
|
desired_freq = df->min_freq;
|
|
adjust_curfreq = 1;
|
|
}
|
|
} else if (df->max_freq != pdfrgx_data->g_freq_mhz_max) {
|
|
int new_index = -1;
|
|
|
|
if (dfrgx_burst_is_enabled(pdfrgx_data))
|
|
dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);
|
|
|
|
new_index = df_rgx_get_util_record_index_by_freq(df->max_freq);
|
|
if (new_index > -1) {
|
|
mutex_lock(&pdfrgx_data->g_mutex_sts);
|
|
pdfrgx_data->g_freq_mhz_max = df->max_freq;
|
|
bfdata->gbp_cooldv_latest_freq_max = df->max_freq;
|
|
pdfrgx_data->g_max_freq_index = new_index;
|
|
mutex_unlock(&pdfrgx_data->g_mutex_sts);
|
|
}
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:Max freq changed!,"
|
|
" prev_freq %lu, max_freq %lu\n",
|
|
__func__,
|
|
df->previous_freq,
|
|
df->max_freq);
|
|
|
|
if (df->previous_freq > df->max_freq) {
|
|
desired_freq = df->max_freq;
|
|
adjust_curfreq = 1;
|
|
}
|
|
} else if (!strncmp(df->governor->name,
|
|
"simple_ondemand", DEVFREQ_NAME_LEN)) {
|
|
*p_freq = df->previous_freq;
|
|
goto out;
|
|
}
|
|
|
|
/* set_freq changed on userspace governor*/
|
|
if (!strncmp(df->governor->name, "userspace", DEVFREQ_NAME_LEN)) {
|
|
/* update userspace freq*/
|
|
struct userspace_gov_data *data = df->data;
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:userspace governor,"
|
|
" desired %lu, data->user_frequency %lu, input_freq = %lu\n",
|
|
__func__,
|
|
desired_freq,
|
|
data->user_frequency,
|
|
*p_freq);
|
|
|
|
data->valid = 1;
|
|
data->user_frequency = desired_freq;
|
|
set_freq = 1;
|
|
}
|
|
|
|
if (adjust_curfreq)
|
|
set_freq = 1;
|
|
|
|
if (set_freq) {
|
|
/* Freq will be reflected once GPU is back on*/
|
|
if (!df_rgx_is_active()) {
|
|
bfdata->bf_desired_freq = desired_freq;
|
|
mutex_lock(&bfdata->lock);
|
|
bfdata->b_need_freq_update = 1;
|
|
mutex_unlock(&bfdata->lock);
|
|
*p_freq = desired_freq;
|
|
gpu_defer_req = 1;
|
|
} else {
|
|
ret = df_rgx_set_freq_khz(bfdata, desired_freq);
|
|
if (ret > 0) {
|
|
*p_freq = ret;
|
|
ret = 0;
|
|
}
|
|
}
|
|
} else {
|
|
*p_freq = df->previous_freq;
|
|
}
|
|
|
|
if ((!strncmp(df->governor->name,
|
|
"simple_ondemand", DEVFREQ_NAME_LEN)
|
|
&& !dfrgx_burst_is_enabled(&bfdata->g_dfrgx_data))
|
|
|| gpu_defer_req)
|
|
dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 1);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* df_rgx_bus_get_dev_status() - Update current status, including:
|
|
* - stat->current_frequency - Frequency in KHz.
|
|
* - stat->total_time
|
|
* - stat->busy_time
|
|
* Note: total_time and busy_time have arbitrary units, as they are
|
|
* used only as ratios.
|
|
* Utilization is busy_time / total_time .
|
|
*/
|
|
static int df_rgx_bus_get_dev_status(struct device *dev,
|
|
struct devfreq_dev_status *stat)
|
|
{
|
|
struct busfreq_data *bfdata = dev_get_drvdata(dev);
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);
|
|
|
|
stat->current_frequency = bfdata->bf_freq_mhz_rlzd * 1000;
|
|
|
|
/* FIXME - Compute real utilization statistics. */
|
|
stat->total_time = 100;
|
|
stat->busy_time = 50;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tcd_get_max_state() - thermal cooling device callback get_max_state.
|
|
* @tcd: Thermal cooling device structure.
|
|
* @pms: Pointer to integer through which output value is stored.
|
|
*
|
|
* Invoked via interrupt/callback.
|
|
* Function return value: 0 if success, otherwise -error.
|
|
* Execution context: non-atomic
|
|
*/
|
|
static int tcd_get_max_state(struct thermal_cooling_device *tcd,
|
|
unsigned long *pms)
|
|
{
|
|
*pms = THERMAL_COOLING_DEVICE_MAX_STATE - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tcd_get_cur_state() - thermal cooling device callback get_cur_state.
|
|
* @tcd: Thermal cooling device structure.
|
|
* @pcs: Pointer to integer through which output value is stored.
|
|
*
|
|
* Invoked via interrupt/callback.
|
|
* Function return value: 0 if success, otherwise -error.
|
|
* Execution context: non-atomic
|
|
*/
|
|
static int tcd_get_cur_state(struct thermal_cooling_device *tcd,
|
|
unsigned long *pcs)
|
|
{
|
|
struct busfreq_data *bfdata = (struct busfreq_data *) tcd->devdata;
|
|
*pcs = bfdata->gbp_cooldv_state_cur;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tcd_set_cur_state() - thermal cooling
|
|
* device callback set_cur_state.
|
|
* @tcd: Thermal cooling device structure.
|
|
* @cs: Input state.
|
|
*
|
|
* Invoked via interrupt/callback.
|
|
* Function return value: 0 if success, otherwise -error.
|
|
* Execution context: non-atomic
|
|
*/
|
|
static int tcd_set_cur_state(struct thermal_cooling_device *tcd,
|
|
unsigned long cs)
|
|
{
|
|
struct busfreq_data *bfdata;
|
|
struct devfreq *df;
|
|
int ret = 0;
|
|
|
|
bfdata = (struct busfreq_data *) tcd->devdata;
|
|
|
|
if (cs >= THERMAL_COOLING_DEVICE_MAX_STATE)
|
|
cs = THERMAL_COOLING_DEVICE_MAX_STATE - 1;
|
|
|
|
/*If different state*/
|
|
if (bfdata->gbp_cooldv_state_cur != cs) {
|
|
int new_index = -1;
|
|
|
|
/* Dynamic turbo is not enabled so try
|
|
* to change the state
|
|
*/
|
|
if (!bfdata->g_dfrgx_data.g_enable) {
|
|
|
|
if(!df_rgx_is_active()) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* If thermal state is specified explicitely
|
|
* then suspend burst/unburst thread
|
|
* because the user needs the GPU to run
|
|
* at specific frequency/thermal state level
|
|
*/
|
|
|
|
ret = df_rgx_set_freq_khz(bfdata,
|
|
bfdata->gpudata[cs].freq_limit);
|
|
if (ret <= 0)
|
|
return ret;
|
|
} else {
|
|
/* In this case we want to limit the max_freq
|
|
* to the thermal state limit
|
|
*/
|
|
int b_update_freq = 0;
|
|
df = bfdata->devfreq;
|
|
|
|
if (!cs) {
|
|
/* We are back in normal operation so set initial values*/
|
|
df->max_freq = bfdata->gbp_cooldv_latest_freq_max;
|
|
df->min_freq = bfdata->gbp_cooldv_latest_freq_min;
|
|
b_update_freq = 1;
|
|
}
|
|
else {
|
|
dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);
|
|
df->max_freq = bfdata->gpudata[cs].freq_limit;
|
|
|
|
if (df->previous_freq > df->max_freq)
|
|
b_update_freq = 1;
|
|
|
|
if (bfdata->gpudata[cs].freq_limit < df->min_freq) {
|
|
df->min_freq = bfdata->gpudata[cs].freq_limit;
|
|
new_index = df_rgx_get_util_record_index_by_freq(df->min_freq);
|
|
|
|
if (new_index > -1) {
|
|
bfdata->g_dfrgx_data.g_freq_mhz_min = df->min_freq;
|
|
bfdata->g_dfrgx_data.g_min_freq_index = new_index;
|
|
}
|
|
b_update_freq = 1;
|
|
}
|
|
|
|
new_index = df_rgx_get_util_record_index_by_freq(df->max_freq);
|
|
|
|
if (new_index > -1) {
|
|
bfdata->g_dfrgx_data.g_freq_mhz_max = df->max_freq;
|
|
bfdata->g_dfrgx_data.g_max_freq_index = new_index;
|
|
}
|
|
|
|
dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 1);
|
|
}
|
|
|
|
if (b_update_freq) {
|
|
/* Pick the min freq this time*/
|
|
bfdata->bf_desired_freq = df->min_freq;
|
|
mutex_lock(&bfdata->lock);
|
|
bfdata->b_need_freq_update = 1;
|
|
mutex_unlock(&bfdata->lock);
|
|
}
|
|
}
|
|
|
|
bfdata->gbp_cooldv_state_prev = bfdata->gbp_cooldv_state_cur;
|
|
bfdata->gbp_cooldv_state_cur = cs;
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "Thermal state changed from %d to %d\n",
|
|
bfdata->gbp_cooldv_state_prev,
|
|
bfdata->gbp_cooldv_state_cur);
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
unsigned long voltage_gfx = 0.95;
|
|
void df_rgx_init_available_freq_table(struct device *dev)
|
|
{
|
|
int i = 0;
|
|
int n_states = sku_levels();
|
|
|
|
for (i = 0; i < n_states; i++)
|
|
opp_add(dev, a_available_state_freq[i].freq, voltage_gfx);
|
|
}
|
|
/**
|
|
* tcd_get_available_states() - thermal cooling device
|
|
* callback get_available_states.
|
|
* @tcd: Thermal cooling device structure.
|
|
* @pcs: Pointer to char through which output values are stored.
|
|
*
|
|
* Invoked via interrupt/callback.
|
|
* Function return value: 0 if success, otherwise -error.
|
|
* Execution context: non-atomic
|
|
*/
|
|
static int tcd_get_available_states(struct thermal_cooling_device *tcd,
|
|
char *buf)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
int n_states = sku_levels();
|
|
|
|
for (i = 0; i < n_states; i++)
|
|
ret += scnprintf(buf + ret, (PAGE_SIZE - ret), "%lu ",
|
|
a_available_state_freq[i].freq);
|
|
|
|
/* Remove trailing space and add newline */
|
|
if ((ret > 0) && (buf[ret-1] == ' '))
|
|
ret--;
|
|
ret += scnprintf(buf + ret, (PAGE_SIZE - ret), "\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(THERMAL_DEBUG)
|
|
/**
|
|
* tcd_get_force_state_override() - thermal cooling
|
|
* device callback get_force_state_override.
|
|
* @tcd: Thermal cooling device structure.
|
|
* @pcs: Pointer to char through which output values are stored.
|
|
*
|
|
* Invoked via interrupt/callback.
|
|
* Function return value: 0 if success, otherwise -error.
|
|
* Execution context: non-atomic
|
|
*/
|
|
static int tcd_get_force_state_override(struct thermal_cooling_device *tcd,
|
|
char *buf)
|
|
{
|
|
struct busfreq_data *bfdata = (struct busfreq_data *) tcd->devdata;
|
|
|
|
return scnprintf(buf, PAGE_SIZE,
|
|
"%lu %lu %lu %lu\n",
|
|
bfdata->gpudata[0].freq_limit,
|
|
bfdata->gpudata[1].freq_limit,
|
|
bfdata->gpudata[2].freq_limit,
|
|
bfdata->gpudata[3].freq_limit);
|
|
}
|
|
|
|
/**
|
|
* tcd_set_force_state_override() - thermal cooling device
|
|
* callback set_force_state_override.
|
|
* @tcd: Thermal cooling device structure.
|
|
* @pcs: Pointer to char containing the input values.
|
|
*
|
|
* Invoked via interrupt/callback.
|
|
* Function return value: 0 if success, otherwise -error.
|
|
* Execution context: non-atomic
|
|
*/
|
|
static int tcd_set_force_state_override(struct thermal_cooling_device *tcd,
|
|
char *buf)
|
|
{
|
|
struct busfreq_data *bfdata = (struct busfreq_data *) tcd->devdata;
|
|
unsigned long int freqs[THERMAL_COOLING_DEVICE_MAX_STATE];
|
|
unsigned long int prev_freq = DFRGX_FREQ_533_MHZ;
|
|
int i = 0;
|
|
|
|
if (is_tng_a0)
|
|
prev_freq = DFRGX_FREQ_320_MHZ;
|
|
if (df_rgx_is_max_fuse_set())
|
|
prev_freq = DFRGX_FREQ_640_MHZ;
|
|
|
|
sscanf(buf, "%lu %lu %lu %lu\n", &freqs[0],
|
|
&freqs[1],
|
|
&freqs[2],
|
|
&freqs[3]);
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s values: %lu %lu %lu %lu\n", __func__,
|
|
freqs[0],
|
|
freqs[1],
|
|
freqs[2],
|
|
freqs[3]);
|
|
|
|
for (i = 0; (i < THERMAL_COOLING_DEVICE_MAX_STATE) &&
|
|
df_rgx_is_valid_freq(freqs[i]) &&
|
|
prev_freq >= freqs[i]; i++) {
|
|
prev_freq = freqs[i];
|
|
}
|
|
|
|
if (i < THERMAL_COOLING_DEVICE_MAX_STATE)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < THERMAL_COOLING_DEVICE_MAX_STATE; i++)
|
|
bfdata->gpudata[i].freq_limit = freqs[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /*THERMAL_DEBUG*/
|
|
|
|
/**
|
|
* df_rgx_bus_exit() - An optional callback that is called when devfreq is
|
|
* removing the devfreq object due to error or from devfreq_remove_device()
|
|
* call. If the user has registered devfreq->nb at a notifier-head, this is
|
|
* the time to unregister it.
|
|
*/
|
|
static void df_rgx_bus_exit(struct device *dev)
|
|
{
|
|
struct busfreq_data *bfdata = dev_get_drvdata(dev);
|
|
(void) bfdata;
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);
|
|
|
|
/* devfreq_unregister_opp_notifier(dev, bfdata->devfreq); */
|
|
}
|
|
|
|
|
|
static struct devfreq_dev_profile df_rgx_devfreq_profile = {
|
|
.initial_freq = DF_RGX_INITIAL_FREQ_KHZ,
|
|
.polling_ms = DF_RGX_POLLING_INTERVAL_MS,
|
|
.target = df_rgx_bus_target,
|
|
.get_dev_status = df_rgx_bus_get_dev_status,
|
|
.exit = df_rgx_bus_exit,
|
|
};
|
|
|
|
|
|
/**
|
|
* busfreq_mon_reset() - Initialize or reset monitoring
|
|
* hardware state as desired.
|
|
*/
|
|
static void busfreq_mon_reset(struct busfreq_data *bfdata)
|
|
{
|
|
/* FIXME - reset monitoring? */
|
|
}
|
|
|
|
|
|
static int df_rgx_busfreq_pm_notifier_event(struct notifier_block *this,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct busfreq_data *bfdata = container_of(this, struct busfreq_data,
|
|
pm_notifier);
|
|
DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);
|
|
|
|
switch (event) {
|
|
case PM_SUSPEND_PREPARE:
|
|
/* Set Fastest and Deactivate DVFS */
|
|
mutex_lock(&bfdata->lock);
|
|
bfdata->disabled = true;
|
|
mutex_unlock(&bfdata->lock);
|
|
return NOTIFY_OK;
|
|
case PM_POST_RESTORE:
|
|
case PM_POST_SUSPEND:
|
|
/* Reactivate */
|
|
mutex_lock(&bfdata->lock);
|
|
bfdata->disabled = false;
|
|
mutex_unlock(&bfdata->lock);
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int df_rgx_busfreq_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct busfreq_data *bfdata;
|
|
struct devfreq *df;
|
|
int error = 0;
|
|
int sts = 0;
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);
|
|
|
|
/* dev_err(dev, "example.\n"); */
|
|
|
|
bfdata = kzalloc(sizeof(struct busfreq_data), GFP_KERNEL);
|
|
if (bfdata == NULL) {
|
|
dev_err(dev, "Cannot allocate memory.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bfdata->pm_notifier.notifier_call = df_rgx_busfreq_pm_notifier_event;
|
|
bfdata->dev = dev;
|
|
mutex_init(&bfdata->lock);
|
|
|
|
platform_set_drvdata(pdev, bfdata);
|
|
|
|
busfreq_mon_reset(bfdata);
|
|
|
|
df = devfreq_add_device(dev, &df_rgx_devfreq_profile,
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
|
|
GOVERNOR_TO_USE, NULL);
|
|
#else
|
|
&GOVERNOR_TO_USE, NULL);
|
|
#endif
|
|
|
|
if (IS_ERR(df)) {
|
|
sts = PTR_ERR(bfdata->devfreq);
|
|
goto err_000;
|
|
}
|
|
|
|
strncpy(bfdata->prev_governor, df->governor->name, DEVFREQ_NAME_LEN);
|
|
|
|
bfdata->devfreq = df;
|
|
|
|
df->previous_freq = DF_RGX_FREQ_KHZ_MIN_INITIAL;
|
|
bfdata->bf_prev_freq_rlzd = DF_RGX_FREQ_KHZ_MIN_INITIAL;
|
|
|
|
/* Set min/max freq depending on stepping/SKU */
|
|
if (is_tng_a0) {
|
|
df->min_freq = DFRGX_FREQ_200_MHZ;
|
|
df->max_freq = DFRGX_FREQ_320_MHZ;
|
|
}
|
|
if (df_rgx_is_max_fuse_set()) {
|
|
df->min_freq = DFRGX_FREQ_457_MHZ;
|
|
df->max_freq = DFRGX_FREQ_640_MHZ;
|
|
}
|
|
else {
|
|
df->min_freq = DFRGX_FREQ_457_MHZ;
|
|
df->max_freq = DFRGX_FREQ_533_MHZ;
|
|
}
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: dev_id = 0x%x, min_freq = %lu, max_freq = %lu\n",
|
|
__func__, RGXGetDRMDeviceID(), df->min_freq, df->max_freq);
|
|
|
|
bfdata->gbp_cooldv_state_override = -1;
|
|
|
|
/* Thermal freq-state mapping after characterization */
|
|
if (df_rgx_is_max_fuse_set())
|
|
bfdata->gpudata[0].freq_limit = DFRGX_FREQ_640_MHZ;
|
|
else
|
|
bfdata->gpudata[0].freq_limit = DFRGX_FREQ_533_MHZ;
|
|
bfdata->gpudata[1].freq_limit = DFRGX_FREQ_457_MHZ;
|
|
bfdata->gpudata[2].freq_limit = DFRGX_FREQ_200_MHZ;
|
|
bfdata->gpudata[3].freq_limit = DFRGX_FREQ_200_MHZ;
|
|
|
|
|
|
df_rgx_init_available_freq_table(dev);
|
|
|
|
|
|
{
|
|
static const char *tcd_type = "gpu_burst";
|
|
static const struct thermal_cooling_device_ops tcd_ops = {
|
|
.get_max_state = tcd_get_max_state,
|
|
.get_cur_state = tcd_get_cur_state,
|
|
.set_cur_state = tcd_set_cur_state,
|
|
#if defined(THERMAL_DEBUG)
|
|
.get_force_state_override =
|
|
tcd_get_force_state_override,
|
|
.set_force_state_override =
|
|
tcd_set_force_state_override,
|
|
#else
|
|
.get_force_state_override = NULL,
|
|
.set_force_state_override = NULL,
|
|
#endif
|
|
.get_available_states =
|
|
tcd_get_available_states,
|
|
};
|
|
struct thermal_cooling_device *tcdhdl;
|
|
|
|
/**
|
|
* Example: Thermal zone "type"s and temps milli-deg-C.
|
|
* These are just examples and are not specific
|
|
*to our usage.
|
|
* type temp
|
|
* -------- -------
|
|
* skin0 15944
|
|
* skin1 22407
|
|
* msicdie 37672
|
|
*
|
|
* See /sys/class/thermal/thermal_zone<i>
|
|
* See /sys/class/thermal/cooling_device<i>
|
|
*/
|
|
|
|
tcdhdl = thermal_cooling_device_register(
|
|
(char *) tcd_type, bfdata, &tcd_ops);
|
|
if (IS_ERR(tcdhdl)) {
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "Cooling device"
|
|
" registration failed: %ld\n",
|
|
-PTR_ERR(tcdhdl));
|
|
sts = PTR_ERR(tcdhdl);
|
|
goto err_001;
|
|
}
|
|
bfdata->gbp_cooldv_hdl = tcdhdl;
|
|
}
|
|
|
|
sts = register_pm_notifier(&bfdata->pm_notifier);
|
|
if (sts) {
|
|
dev_err(dev, "Failed to setup pm notifier\n");
|
|
goto err_002;
|
|
}
|
|
|
|
bfdata->g_dfrgx_data.bus_freq_data = bfdata;
|
|
bfdata->g_dfrgx_data.g_enable = mprm_enable;
|
|
bfdata->g_dfrgx_data.gpu_utilization_record_index =
|
|
df_rgx_get_util_record_index_by_freq(df->min_freq);
|
|
bfdata->g_dfrgx_data.g_min_freq_index =
|
|
df_rgx_get_util_record_index_by_freq(df->min_freq);
|
|
bfdata->g_dfrgx_data.g_freq_mhz_min = df->min_freq;
|
|
bfdata->g_dfrgx_data.g_max_freq_index =
|
|
df_rgx_get_util_record_index_by_freq(df->max_freq);
|
|
bfdata->g_dfrgx_data.g_freq_mhz_max = df->max_freq;
|
|
bfdata->gbp_cooldv_latest_freq_min = df->min_freq;
|
|
bfdata->gbp_cooldv_latest_freq_max = df->max_freq;
|
|
|
|
df_rgx_set_governor_profile(df->governor->name,
|
|
&bfdata->g_dfrgx_data);
|
|
|
|
error = dfrgx_burst_init(&bfdata->g_dfrgx_data);
|
|
|
|
if (error) {
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: dfrgx_burst_init failed!"
|
|
", no utilization data\n", __func__);
|
|
sts = -1;
|
|
goto err_002;
|
|
}
|
|
|
|
/*Set the initial frequency at 457MHZ in B0/ 200MHZ otherwise*/
|
|
{
|
|
int ret = 0;
|
|
if (!df_rgx_is_active()) {
|
|
/*Change the freq once it is active*/
|
|
bfdata->bf_desired_freq = df->min_freq;
|
|
mutex_lock(&bfdata->lock);
|
|
bfdata->b_need_freq_update = 1;
|
|
mutex_unlock(&bfdata->lock);
|
|
} else {
|
|
ret = df_rgx_set_freq_khz(bfdata, df->min_freq);
|
|
if (ret < 0) {
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH,
|
|
"%s: could not initialize freq: %0x error\n",
|
|
__func__, ret);
|
|
}
|
|
}
|
|
}
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: success\n", __func__);
|
|
|
|
return 0;
|
|
|
|
err_002:
|
|
thermal_cooling_device_unregister(bfdata->gbp_cooldv_hdl);
|
|
bfdata->gbp_cooldv_hdl = NULL;
|
|
err_001:
|
|
devfreq_remove_device(bfdata->devfreq);
|
|
err_000:
|
|
platform_set_drvdata(pdev, NULL);
|
|
mutex_destroy(&bfdata->lock);
|
|
kfree(bfdata);
|
|
return sts;
|
|
}
|
|
|
|
static int df_rgx_busfreq_remove(struct platform_device *pdev)
|
|
{
|
|
struct busfreq_data *bfdata = platform_get_drvdata(pdev);
|
|
|
|
dfrgx_burst_deinit(&bfdata->g_dfrgx_data);
|
|
|
|
unregister_pm_notifier(&bfdata->pm_notifier);
|
|
devfreq_remove_device(bfdata->devfreq);
|
|
mutex_destroy(&bfdata->lock);
|
|
kfree(bfdata);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int df_rgx_busfreq_resume(struct device *dev)
|
|
{
|
|
struct busfreq_data *bfdata = dev_get_drvdata(dev);
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);
|
|
|
|
busfreq_mon_reset(bfdata);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct dev_pm_ops df_rgx_busfreq_pm = {
|
|
.resume = df_rgx_busfreq_resume,
|
|
};
|
|
|
|
static const struct platform_device_id df_rgx_busfreq_id[] = {
|
|
{ DF_RGX_NAME_DEV, 0 },
|
|
{ "", 0 },
|
|
};
|
|
|
|
|
|
static struct platform_driver df_rgx_busfreq_driver = {
|
|
.probe = df_rgx_busfreq_probe,
|
|
.remove = df_rgx_busfreq_remove,
|
|
.id_table = df_rgx_busfreq_id,
|
|
.driver = {
|
|
.name = DF_RGX_NAME_DRIVER,
|
|
.owner = THIS_MODULE,
|
|
.pm = &df_rgx_busfreq_pm,
|
|
},
|
|
};
|
|
|
|
|
|
static struct platform_device * __init df_rgx_busfreq_device_create(void)
|
|
{
|
|
struct platform_device *pdev;
|
|
int ret;
|
|
|
|
pdev = platform_device_alloc(DF_RGX_NAME_DEV, -1);
|
|
if (!pdev) {
|
|
pr_err("%s: platform_device_alloc failed\n",
|
|
DF_RGX_NAME_DEV);
|
|
return NULL;
|
|
}
|
|
|
|
ret = platform_device_add(pdev);
|
|
if (ret < 0) {
|
|
pr_err("%s: platform_device_add failed\n",
|
|
DF_RGX_NAME_DEV);
|
|
platform_device_put(pdev);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return pdev;
|
|
}
|
|
|
|
static int __init df_rgx_busfreq_init(void)
|
|
{
|
|
struct platform_device *pdev;
|
|
int ret;
|
|
|
|
if (!mprm_enable) {
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: %s: disabled\n",
|
|
DF_RGX_NAME_DRIVER, __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: %s: starting\n",
|
|
DF_RGX_NAME_DRIVER, __func__);
|
|
|
|
pdev = df_rgx_busfreq_device_create();
|
|
if (IS_ERR(pdev))
|
|
return PTR_ERR(pdev);
|
|
if (!pdev)
|
|
return -ENOMEM;
|
|
|
|
df_rgx_created_dev = pdev;
|
|
|
|
ret = platform_driver_register(&df_rgx_busfreq_driver);
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: %s: success\n",
|
|
DF_RGX_NAME_DRIVER, __func__);
|
|
|
|
return ret;
|
|
}
|
|
late_initcall(df_rgx_busfreq_init);
|
|
|
|
static void __exit df_rgx_busfreq_exit(void)
|
|
{
|
|
struct platform_device *pdev = df_rgx_created_dev;
|
|
struct busfreq_data *bfdata = platform_get_drvdata(pdev);
|
|
|
|
DFRGX_DPF(DFRGX_DEBUG_LOW, "%s:\n", __func__);
|
|
|
|
if (bfdata && bfdata->gbp_cooldv_hdl) {
|
|
thermal_cooling_device_unregister(bfdata->gbp_cooldv_hdl);
|
|
bfdata->gbp_cooldv_hdl = NULL;
|
|
}
|
|
|
|
platform_driver_unregister(&df_rgx_busfreq_driver);
|
|
|
|
/* Most state reset is done by function df_rgx_busfreq_remove,
|
|
* including invocation of:
|
|
* - unregister_pm_notifier
|
|
* - devfreq_remove_device
|
|
* - mutex_destroy(&bfdata->lock);
|
|
* - kfree(bfdata);
|
|
*/
|
|
|
|
if (pdev)
|
|
platform_device_unregister(pdev);
|
|
}
|
|
module_exit(df_rgx_busfreq_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("RGX busfreq driver with devfreq framework");
|
|
MODULE_AUTHOR("Dale B Stimson <dale.b.stimson@intel.com>");
|