android_kernel_modules_leno.../intel_media/graphics/gburst/gburstm.c

3340 lines
95 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>
* Jari Luoma-aho <jari.luoma-aho@intel.com>
* Jari Nippula <jari.nippula@intel.com>
*/
/**
* To-do:
* - Select appropriate loglevel for each printk, instead of all ALERT.
* - Verify thermal cooling device properly bound to thermal zones.
* At the moment, this is waiting on kernel work external to this driver.
* - The four temperature states known to the firmware are
* normal, warning, alert, and critical.
* - Check all smp_rmb, smp_wmb, smp_mb
* - Access to gbprv->gbp_task protected enough?
* - Comment functions with particular requirements for execution context:
* - Execution context: non-atomic
* - Execution context: hard irq level
if (gbprv->gbp_task)
* - Preference "long battery life" should disable burst.
* - Low battery state should disable burst.
* - Check TSC freq. properly (timestamp function). Now assumes 2GHz.
*/
#if (defined CONFIG_GPU_BURST) || (defined CONFIG_GPU_BURST_MODULE)
#include <linux/types.h>
#include <linux/ctype.h>
#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/ktime.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/thermal.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <asm/intel-mid.h>
#include <linux/proc_fs.h>
#include <linux/module.h>
#define GBURST_DEBUG 1
#include "utilf.h"
#include <gburst_interface.h>
#include "gburst.h"
#define GBURST_GLOBAL_ENABLE_DEFAULT 1
#define GBURST_DRIVER_NAME "gburst"
#define GBURST_HEADING GBURST_DRIVER_NAME ": "
#define GBURST_ALERT KERN_ALERT GBURST_DRIVER_NAME ": "
#define GBURST_VERBOSITY_WHYMSG 3
/**
* GBURST_PFS_NAME_DIR_GBURST - directory name under /proc for gburst.
*/
#define GBURST_PFS_NAME_DIR_GBURST "gburst"
/**
* GBURST_IRQ_LEVEL - could possibly be obtained dynamically via access to
* platform device or via call to arch/x86/platform/intel-mid/mrst.c.
*/
#define GBURST_IRQ_LEVEL 73
/**
* GBURST_GPU_FREQ_* - frequency specifications.
* Request is in PWRGT_CNT and PWRGT_STS, bits 27:24.
* As realized is in PWRGT_STS, bits 23:20.
*
* Values for request or realized:
* 0001b = Graphics Clock is 533 MHz Unthrottled
* 0000b = Graphics Clock is 400 MHz Unthrottled
*
* Values only used for realized in PWRGT_STS (not in PWRGT_CNT):
* 1001b = Graphics Clock is 400 MHz at 12.5% Throttled (350 MHz effective)
* 1010b = Graphics Clock is 400 MHz at 25% Throttled (300 MHz effective)
* 1011b = Graphics Clock is 400 MHz at 37.5% Throttled (250 MHz effective)
* 1100b = Graphics Clock is 400 MHz at 50% Throttled (200 MHz effective)
* 1101b = Graphics Clock is 400 MHz at 62.5% Throttled (150 MHz effective)
* 1110b = Graphics Clock is 400 MHz at 75% Throttled (100 MHz effective)
* 1111b = Graphics Clock is 400 MHz at 87.5% Throttled ( 50 MHz effective)
*/
#define GBURST_GPU_FREQ_400 0x00
#define GBURST_GPU_FREQ_533 0x01
/* The following names are not fully descriptive, but oh, well. */
#define GBURST_GPU_FREQ_350 0x09
#define GBURST_GPU_FREQ_300 0x0a
#define GBURST_GPU_FREQ_250 0x0b
#define GBURST_GPU_FREQ_200 0x0c
#define GBURST_GPU_FREQ_150 0x0d
#define GBURST_GPU_FREQ_100 0x0e
#define GBURST_GPU_FREQ_50 0x0f
#define GBURST_GPU_FREQ_LEN 0x10
/* MS bit of realized frequency field indicates throttled frequency. */
#define GBURST_GPU_FREQ_THROTTLE_BIT 0x08
/**
* gpu burst register addresses.
*/
#define PWRGT_CNT_PORT 0x4
#define PWRGT_CNT_ADDR 0x60
#define PWRGT_STS_PORT 0x4
#define PWRGT_STS_ADDR 0x61
/**
* gpu burst register PWRGT_CNT bits and fields.
* From the perspective of the OS, this register is intended only for
* writing and should be read only if necessary to obtain the current state
* of the toggle bit.
*/
/* PWRGT_CNT_TOGGLE_BIT - toggle on every write so fw can detect change. */
#define PWRGT_CNT_TOGGLE_BIT 0x80000000
/* PWRGT_CNT_INT_ENABLE_BIT - enable interrupt for freq chg notification */
#define PWRGT_CNT_INT_ENABLE_BIT 0x40000000
#define PWRGT_CNT_RESERVED_1_BIT 0x20000000
/**
* PWRGT_CNT_ENABLE_AUTO_BURST_ENTRY_BIT -
* If set and the driver has requested gpu burst mode, but the request was
* denied by the firmware due to burst mode inhibitors (such as high temp),
* then when the inhibitors go away, automatically enter the previously
* requested mode.
* If not set, do not automatically enter the burst mode in that case.
*/
#define PWRGT_CNT_ENABLE_AUTO_BURST_ENTRY_BIT 0x10000000
/**
* PWRGT_CNT_BURST_REQUEST_* - Burst entry/exit request from OS to fw,
* bits 27:24
*/
#define PWRGT_CNT_BURST_REQUEST_M 0x0F000000
#define PWRGT_CNT_BURST_REQUEST_S 4
#define PWRGT_CNT_BURST_REQUEST_P 24
/* Values in the field are: GBURST_GPU_FREQ_*. */
#define PWRGT_CNT_BURST_REQUEST_M_400 \
(GBURST_GPU_FREQ_400 << PWRGT_CNT_BURST_REQUEST_P)
#define PWRGT_CNT_BURST_REQUEST_M_533 \
(GBURST_GPU_FREQ_533 << PWRGT_CNT_BURST_REQUEST_P)
/* All other PWRGT_CNT bits are reserved. */
/**
* gpu burst register PWRGT_STS bits and fields.
* From the perspective of the OS, this register is intended only for
* reading. Except for bit 31 and new field 23:20, it more or less
* reflects the state of what was written to PWRGT_CNT as so far *realized*
* by the firmware.
*/
#define PWRGT_STS_BURST_SUPPORT_PRESENT_BIT 0x80000000
/* PWRGT_STS_INT_ENABLE_BIT - interrupt enabled for freq chg notification */
#define PWRGT_STS_INT_ENABLE_BIT 0x40000000
#define PWRGT_STS_RESERVED_1_BIT 0x20000000
/**
* PWRGT_STS_ENABLE_AUTO_BURST_ENTRY_BIT - Reflects previously set value
* of PWRGT_CNT_ENABLE_AUTO_BURST_ENTRY_BIT in PWRGT_CNT.
* See description of PWRGT_CNT_ENABLE_AUTO_BURST_ENTRY_BIT.
*/
#define PWRGT_STS_ENABLE_AUTO_BURST_ENTRY_BIT 0x10000000
/**
* PWRGT_STS_BURST_REQUEST_M - Field containing GBURST_GPU_FREQ_*.
* as requested via PWRGT_CNT, bits 27:24
*/
#define PWRGT_STS_BURST_REQUEST_M 0x0F000000
#define PWRGT_STS_BURST_REQUEST_S 4
#define PWRGT_STS_BURST_REQUEST_P 24
/**
* PWRGT_STS_BURST_REALIZED_M - Field containing GBURST_GPU_FREQ_*.
* as realized, based on request and firmware decisions,
* bits 23:20
*/
#define PWRGT_STS_BURST_REALIZED_M 0x00F00000
#define PWRGT_STS_BURST_REALIZED_S 4
#define PWRGT_STS_BURST_REALIZED_P 20
#define PWRGT_STS_BURST_REALIZED_M_400 \
(GBURST_GPU_FREQ_400 << PWRGT_STS_BURST_REALIZED_P)
#define PWRGT_STS_BURST_REALIZED_M_533 \
(GBURST_GPU_FREQ_533 << PWRGT_STS_BURST_REALIZED_P)
#define PWRGT_STS_FREQ_THROTTLE_M (GBURST_GPU_FREQ_THROTTLE_BIT << \
PWRGT_STS_BURST_REALIZED_P)
/* Macros to test for states */
#define GBURST_BURST_REQUESTED(gbprv) ((gbprv->gbp_pwrgt_cnt_last_written \
& PWRGT_CNT_BURST_REQUEST_M) == PWRGT_CNT_BURST_REQUEST_M_533)
#define GBURST_BURST_REALIZED(gbprv) ((gbprv->gbp_pwrgt_sts_last_read \
& PWRGT_STS_BURST_REALIZED_M) == PWRGT_STS_BURST_REALIZED_M_533)
#define GBURST_BURST_THROTTLED(gbprv) (gbprv->gbp_pwrgt_sts_last_read \
& PWRGT_STS_FREQ_THROTTLE_M)
/**
* THERMAL_COOLING_DEVICE_MAX_STATE - The maximum cooling state that
* gburst (as a thermal cooling device by non-bursting) support.
*/
#define THERMAL_COOLING_DEVICE_MAX_STATE 1
#define GBURST_TIMER_PERIOD_DEFAULT_USECS 5000
#define GBURST_THRESHOLD_DEFAULT_HIGH 80
#define GBURST_THRESHOLD_DEFAULT_DOWN_DIFF 20
/**
* Burst dynamic control parameters
* VSYNC_FRAMES - number of frames rendered within vsync time before
* we deny burst request even if utilization would indicate otherwise
* FRAME_TIME_BUFFER - additional buffer after frame 'ready' before the
* next vsync event (as CPU cycles)
* FRAME_DURATION - frame time as max. CPU freq cycles, given system
* latencies this value is taken as 17ms
* OFFSCREEN_TIME - time to last resume even after which we infer that
* we have an offscreen rendering case (or a very long frame rendering)
*/
#define VSYNC_FRAMES 1
#define FRAME_TIME_BUFFER ((unsigned long long)(0))
#define OFFSCREEN_FRAMES ((unsigned long long)(20))
#define FRAME_DURATION ((unsigned long long)(34000000))
#define OFFSCREEN_TIME (OFFSCREEN_FRAMES*FRAME_DURATION)
/**
* timestamp - Helper used when storing internal timestamps used for burst control
*/
unsigned long long timestamp(void)
{
unsigned int a, d;
__asm__ volatile("rdtsc" : "=a" (a), "=d" (d));
return ((unsigned long long)a) | (((unsigned long long)d) << 32);
}
/**
* pfs_data - Structure to describe one file under /proc/gburst.
*/
struct pfs_data {
const char *pfd_file_name;
ssize_t (*pfd_func_read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*pfd_func_write) (struct file *, const char __user *, size_t, loff_t *);
mode_t pfd_mode;
};
/*
* Forward references for procfs read and write functions:
*/
static int pfs_debug_message_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_debug_message_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_disable_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_disable_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_dump_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_enable_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_enable_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_gpu_monitored_counters_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_gpu_monitored_counters_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_pwrgt_sts_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_state_times_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_state_times_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_thermal_override_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_thermal_override_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_thermal_state_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_gb_threshold_down_diff_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_gb_threshold_down_diff_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_gb_threshold_high_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_gb_threshold_high_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_gb_threshold_low_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_gb_threshold_low_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_timer_period_usecs_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_timer_period_usecs_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_utilization_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_utilization_override_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_utilization_override_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
static int pfs_verbosity_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos);
static int pfs_verbosity_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos);
/**
* pfs_tab -- table specifying each gburst file under /proc/gburst.
*/
static const struct pfs_data pfs_tab[] = {
{ "debug_message",
pfs_debug_message_read,
pfs_debug_message_write,
0644, },
{ "disable",
pfs_disable_read,
pfs_disable_write,
0666, },
{ "dump",
pfs_dump_read,
NULL,
0644, },
{ "enable",
pfs_enable_read,
pfs_enable_write,
0644, },
{ "monitored",
pfs_gpu_monitored_counters_read,
pfs_gpu_monitored_counters_write,
0644, },
{ "pwrgt_sts",
pfs_pwrgt_sts_read,
NULL,
0644, },
{ "state_times",
pfs_state_times_read,
pfs_state_times_write,
0644, },
{ "thermal_override",
pfs_thermal_override_read,
pfs_thermal_override_write,
0644, },
{ "thermal_state",
pfs_thermal_state_read,
NULL,
0644, },
{ "threshold_down_diff",
pfs_gb_threshold_down_diff_read,
pfs_gb_threshold_down_diff_write,
0644, },
{ "threshold_high",
pfs_gb_threshold_high_read,
pfs_gb_threshold_high_write,
0644, },
{ "threshold_low",
pfs_gb_threshold_low_read,
pfs_gb_threshold_low_write,
0644, },
{ "timer_period_usecs",
pfs_timer_period_usecs_read,
pfs_timer_period_usecs_write,
0644, },
{ "utilization",
pfs_utilization_read,
NULL,
0644, },
{ "utilization_override",
pfs_utilization_override_read,
pfs_utilization_override_write,
0644, },
{ "verbosity",
pfs_verbosity_read,
pfs_verbosity_write,
0644, },
};
/**
* freq_mhz_table - "local data" array translating from frequency code to
* associated frequency in MHz.
*/
static const int freq_mhz_table[GBURST_GPU_FREQ_LEN] = {
[GBURST_GPU_FREQ_400] = 400,
[GBURST_GPU_FREQ_533] = 533,
[GBURST_GPU_FREQ_350] = 350,
[GBURST_GPU_FREQ_300] = 300,
[GBURST_GPU_FREQ_250] = 250,
[GBURST_GPU_FREQ_200] = 200,
[GBURST_GPU_FREQ_150] = 150,
[GBURST_GPU_FREQ_100] = 100,
[GBURST_GPU_FREQ_50] = 50,
};
struct gb_state_times_s {
struct timespec gst_uptime;
struct timespec gst_time_gfx_power;
struct timespec gst_time_burst_requested;
struct timespec gst_time_burst_realized;
struct timespec gst_time_throttled;
};
/**
* struct gburst_pvt_s - gburst private data
*/
struct gburst_pvt_s {
struct hrtimer gbp_timer;
struct proc_dir_entry *gbp_proc_parent;
struct proc_dir_entry *gbp_proc_gburst;
struct thermal_cooling_device *gbp_cooldv_hdl;
/* gbp_task - pointer to task structure for work thread or NULL. */
struct task_struct *gbp_task;
struct mutex gbp_mutex_pwrgt_sts;
/* gbp_hrt_period - Period for timer interrupts as a ktime_t. */
ktime_t gbp_hrt_period;
/* gbp_pfs_handle */
struct proc_dir_entry *gbp_pfs_handle[ARRAY_SIZE(pfs_tab)];
/**
* Multipe time values, all updated at once.
* All access to these times protected by mutex.
*/
struct mutex gbp_state_times_mutex;
struct gb_state_times_s gbp_state_times;
int gbp_state_time_header;
int gbp_initialized;
int gbp_suspended;
int gbp_thread_check_utilization;
#if GBURST_DEBUG
unsigned int gbp_interrupt_count;
unsigned int gbp_thread_work_count;
unsigned int gbp_thermal_state_change_count;
unsigned int gbp_suspend_count;
unsigned int gbp_resume_count;
#endif /* if GBURST_DEBUG */
int gbp_cooldv_state_cur;
int gbp_cooldv_state_prev;
int gbp_cooldv_state_highest;
int gbp_cooldv_state_override;
/* 1 if disable requested via /proc/gburst/disable */
int gbp_request_disable;
/* 1 if enable requested via /proc/gburst/enable */
int gbp_request_enable;
/* gbp_enable - Usually 1. If 0, gpu burst is disabled. */
int gbp_enable;
int gbp_timer_is_enabled;
/**
* Utilization and threshold values, in percent, 0 to 100, or
* -1 if utilization not yet read.
*/
int gbp_utilization_percentage;
int gbp_utilization_override;
int gbp_burst_th_high;
/**
* gbp_burst_th_down_diff and gbp_burst_th_low
* are related. One of them (selected by gbp_thold_via) is definitive
* and the other is computed from the high and definitive values.
*/
enum {
GBP_THOLD_VIA_LOW,
GBP_THOLD_VIA_DOWN_DIFF
} gbp_thold_via;
int gbp_burst_th_down_diff;
int gbp_burst_th_low;
u32 gbp_pwrgt_cnt_toggle_bit;
u32 gbp_pwrgt_sts_last_read;
u32 gbp_pwrgt_cnt_last_written;
/**
* Burst dynamic control parameters
*/
unsigned long long gbp_resume_time;
int gbp_offscreen_rendering;
int gbp_num_of_vsync_limited_frames;
};
/* Global variables. */
int gburst_debug_msg_on;
static struct gburst_pvt_s gburst_private_data;
/**
* gburst_private_ptr - Static place to save handle for access at module unload.
* There will never be more than a single instantiation of this driver.
*/
static struct gburst_pvt_s *gburst_private_ptr;
/**
* Module parameters:
*
* - can be updated (if permission allows) via writing:
* /sys/module/gburst/parameters/<name>
* - can be set at module load time:
* insmod /lib/modules/gburst.ko enable=0
* - For built-in modules, can be on kernel command line:
* gburst.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 = GBURST_GLOBAL_ENABLE_DEFAULT;
module_param_named(enable, mprm_enable, uint, S_IRUGO);
static unsigned int mprm_verbosity = 1;
module_param_named(verbosity, mprm_verbosity, uint, S_IRUGO|S_IWUSR);
#define DRIVER_AUTHOR "Intel Corporation"
#define DRIVER_DESC "gpu burst driver for Intel Clover Trail Plus"
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");
*/
/**
* update_state_times() - Uptime times, current states.
* @gbprv: gb handle.
* @gst: NULL or pointer to receive struct gb_state_times_s output.
*/
static void update_state_times(struct gburst_pvt_s *gbprv,
struct gb_state_times_s *gst)
{
struct timespec ts;
time_t delta_sec;
s64 delta_nsec;
struct gb_state_times_s *pst = &gbprv->gbp_state_times;
mutex_lock(&gbprv->gbp_state_times_mutex);
get_monotonic_boottime(&ts);
delta_sec = ts.tv_sec - pst->gst_uptime.tv_sec;
delta_nsec = ts.tv_nsec - pst->gst_uptime.tv_nsec;
pst->gst_uptime = ts;
if (GBURST_BURST_REQUESTED(gbprv)) {
set_normalized_timespec(&pst->gst_time_burst_requested,
pst->gst_time_burst_requested.tv_sec + delta_sec,
pst->gst_time_burst_requested.tv_nsec + delta_nsec);
}
if (!gbprv->gbp_suspended) {
set_normalized_timespec(&pst->gst_time_gfx_power,
pst->gst_time_gfx_power.tv_sec + delta_sec,
pst->gst_time_gfx_power.tv_nsec + delta_nsec);
}
if (GBURST_BURST_REALIZED(gbprv)) {
set_normalized_timespec(&pst->gst_time_burst_realized,
pst->gst_time_burst_realized.tv_sec + delta_sec,
pst->gst_time_burst_realized.tv_nsec + delta_nsec);
}
if (GBURST_BURST_THROTTLED(gbprv)) {
set_normalized_timespec(&pst->gst_time_throttled,
pst->gst_time_throttled.tv_sec + delta_sec,
pst->gst_time_throttled.tv_nsec + delta_nsec);
}
if (gst)
*gst = *pst;
mutex_unlock(&gbprv->gbp_state_times_mutex);
}
/**
* write_PWRGT_CNT() - Write PUnit register PWRGT_CNT via MBI
* (Message Bus Interface).
* @gbprv: gb handle.
* @value: value to be written to the register.
*/
static void write_PWRGT_CNT(struct gburst_pvt_s *gbprv, u32 value)
{
u32 wvl;
/*
* Change the state of the toggle bit. Its state must be reversed
* for every write to this register.
*/
gbprv->gbp_pwrgt_cnt_toggle_bit ^= PWRGT_CNT_TOGGLE_BIT;
wvl = (value & ~PWRGT_CNT_TOGGLE_BIT) | gbprv->gbp_pwrgt_cnt_toggle_bit;
intel_mid_msgbus_write32(PWRGT_CNT_PORT, PWRGT_CNT_ADDR, wvl);
/**
* If the requested burst state is being changed from before, update
* the cumulative times spent in particular states.
*/
if ((wvl ^ gbprv->gbp_pwrgt_cnt_last_written)
& PWRGT_CNT_BURST_REQUEST_M) {
/**
* Uptime cumulative times.
* Important: Not yet changed:
* gbprv->gbp_pwrgt_cnt_last_written
*/
update_state_times(gbprv, NULL);
}
gbprv->gbp_pwrgt_cnt_last_written = wvl;
}
/**
* read_PWRGT_CNT_toggle() - Read PUnit register PWRGT_CNT via MBI
* (Message Bus Interface).
* Warning: The HAS specifies that this register may be read only in
* order to determine the current setting of the toggle bit (bit 31).
* @gbprv: gb handle.
*/
static void read_PWRGT_CNT_toggle(struct gburst_pvt_s *gbprv)
{
u32 uval;
uval = intel_mid_msgbus_read32(PWRGT_CNT_PORT, PWRGT_CNT_ADDR);
gbprv->gbp_pwrgt_cnt_toggle_bit = (uval & PWRGT_CNT_TOGGLE_BIT);
}
/**
* generate_freq_string() - Convert frequency enum to a string.
* @freq_enum: Frequency enum: GBURST_GPU_FREQ_* .
* Must be in the range 0 to 15.
* @sbuf: Buffer in which string result is returned.
* @slen: size of sbuf.
* Function return value: String describing the frequency.
*/
static const char *generate_freq_string(u32 freq_enum, char *sbuf, int slen)
{
if (freq_mhz_table[freq_enum] != 0)
snprintf(sbuf, slen, "%d MHz", freq_mhz_table[freq_enum]);
else
snprintf(sbuf, slen, "unrecognized_code_%u", freq_enum);
return sbuf;
}
/**
* read_PWRGT_STS_simple() - Read register PWRGT_STS.
* Restriction to non-atomic context by intel_mid_msgbus_read32 use of
* pci_get_bus_and_slot.
* Execution context: non-atomic
*/
static inline u32 read_PWRGT_STS_simple(void)
{
return intel_mid_msgbus_read32(PWRGT_STS_PORT, PWRGT_STS_ADDR);
}
/**
* hrt_start() - start (or restart) timer.
* @gbprv: gb handle.
*/
static void hrt_start(struct gburst_pvt_s *gbprv)
{
if (gbprv->gbp_enable) {
if (gbprv->gbp_timer_is_enabled) {
/* Due to the gbp_timer is auto-restart timer
* in most case, we must use hrtimer_cancel
* it at first if it is in active state, to avoid
* hitting the BUG_ON(timer->state !=
* HRTIMER_STATE_CALLBACK) in hrtimer.c.
*/
hrtimer_cancel(&gbprv->gbp_timer);
} else {
gbprv->gbp_timer_is_enabled = 1;
}
hrtimer_start(&gbprv->gbp_timer, gbprv->gbp_hrt_period,
HRTIMER_MODE_REL);
}
}
/**
* hrt_cancel() - cancel a timer.
* @gbprv: gb handle.
*/
static void hrt_cancel(struct gburst_pvt_s *gbprv)
{
/* The timer can be restarted with hrtimer_start. */
hrtimer_cancel(&gbprv->gbp_timer);
gbprv->gbp_timer_is_enabled = 0;
}
/**
* set_state_pwrgt_cnt() - write bits to pwrgt control register.
* @gbprv: gb handle.
* @more_bits_to_set: Additional bits to set, beyond those that are set
* automatically.
*/
static void set_state_pwrgt_cnt(struct gburst_pvt_s *gbprv,
u32 more_bits_to_set)
{
u32 gt_cnt;
gt_cnt = 0;
smp_rmb();
if (gbprv->gbp_initialized && gbprv->gbp_enable) {
gt_cnt |= PWRGT_CNT_INT_ENABLE_BIT | more_bits_to_set;
if ((gt_cnt & PWRGT_CNT_BURST_REQUEST_M)
== PWRGT_CNT_BURST_REQUEST_M_533)
gt_cnt |= PWRGT_CNT_ENABLE_AUTO_BURST_ENTRY_BIT;
}
write_PWRGT_CNT(gbprv, gt_cnt);
}
/**
* read_and_process_PWRGT_STS() - Read PUnit register PWRGT_STS
* @gbprv: gb handle.
*/
static u32 read_and_process_PWRGT_STS(struct gburst_pvt_s *gbprv)
{
u32 uval;
u32 valprv;
mutex_lock(&gbprv->gbp_mutex_pwrgt_sts);
uval = read_PWRGT_STS_simple();
valprv = gbprv->gbp_pwrgt_sts_last_read;
/**
* If either the burst_request or the burst_realized states have
* changed (as evidenced by their bit fields within this register),
* then process the new state.
*/
if ((uval ^ valprv) & (PWRGT_STS_BURST_REQUEST_M
| PWRGT_STS_BURST_REALIZED_M)) {
int freq_code;
int freq_mhz;
/**
* Uptime cumulative times.
* Important: Not yet changed: gbprv->gbp_pwrgt_sts_last_read
*/
update_state_times(gbprv, NULL);
freq_code = (uval & PWRGT_STS_BURST_REALIZED_M) >>
PWRGT_STS_BURST_REALIZED_P;
freq_mhz = freq_mhz_table[freq_code];
/**
* If either the burst_realized state has changed (as
* evidenced by their bit fields within this register), then
* process the new state.
*/
if (!gbprv->gbp_suspended &&
((uval ^ valprv) & PWRGT_STS_BURST_REALIZED_M)
&& (freq_mhz != 0)) {
#if GBURST_UPDATE_GPU_TIMING
PVRSRV_ERROR eError;
eError = PVRSRVPowerLock(KERNEL_ID, IMG_FALSE);
if (eError == PVRSRV_OK) {
/**
* Tell graphics subsystem the updated frequency,
* including both pvr km and utilization computations
* (which may or may not use the information).
*/
gburst_stats_gpu_freq_mhz_info(freq_mhz);
PVRSRVPowerUnlock(KERNEL_ID);
}
#else
/**
* Tell graphics subsystem the updated frequency,
* including both pvr km and utilization computations
* (which may or may not use the information).
*/
gburst_stats_gpu_freq_mhz_info(freq_mhz);
#endif
}
if (mprm_verbosity >= 2) {
int freq_code_req;
freq_code_req = (uval & PWRGT_STS_BURST_REQUEST_M) >>
PWRGT_STS_BURST_REQUEST_P;
if (freq_code_req != freq_code) {
printk(GBURST_ALERT
"freq req/rlzd = %d/%d MHz\n",
freq_mhz_table[freq_code_req],
freq_mhz);
} else {
printk(GBURST_ALERT "freq = %d MHz\n",
freq_mhz);
}
}
/* If GPU clock throttling is entered or exited... */
if ((valprv ^ uval) & PWRGT_STS_FREQ_THROTTLE_M) {
if (uval & PWRGT_STS_FREQ_THROTTLE_M) {
/* GPU clock throttling state entered... */
hrt_cancel(gbprv);
if ((gbprv->gbp_pwrgt_cnt_last_written
& PWRGT_CNT_BURST_REQUEST_M)
!= PWRGT_CNT_BURST_REQUEST_M_400) {
/**
* Remove any outstanding burst
* request.
*/
set_state_pwrgt_cnt(gbprv,
PWRGT_CNT_BURST_REQUEST_M_400);
}
} else {
/* GPU clock throttling state exited... */
hrt_start(gbprv);
}
}
}
gbprv->gbp_pwrgt_sts_last_read = uval;
mutex_unlock(&gbprv->gbp_mutex_pwrgt_sts);
return uval;
}
#define GBURST_VERBOSE_EXPLANATION 1
/**
* desired_burst_state_query() - determine desired burst state.
* @gbprv: gb handle.
* @p_whymsg: An explanatory string.
* @sbuf: A buffer that may be used to store a string.
* @slen: length of sbuf.
*
* Function return values:
* 0 -> request un-burst,
* 1 -> request burst,
* 2 -> no change.
*/
static int desired_burst_state_query(struct gburst_pvt_s *gbprv,
const char **p_whymsg
#if GBURST_VERBOSE_EXPLANATION
, char *sbuf, int slen
#endif /* if GBURST_VERBOSE_EXPLANATION */
)
{
int utilpct;
int thermal_state;
if (gbprv->gbp_utilization_override >= 0)
utilpct = gbprv->gbp_utilization_override;
else
utilpct = gbprv->gbp_utilization_percentage;
if (gbprv->gbp_cooldv_state_override >= 0)
thermal_state = gbprv->gbp_cooldv_state_override;
else
thermal_state = gbprv->gbp_cooldv_state_cur;
smp_rmb();
if (!gbprv->gbp_initialized) {
*p_whymsg = "!gbprv->gbp_initialized";
return 0;
}
if (!gbprv->gbp_enable) {
*p_whymsg = "!enable";
return 0;
}
if (gbprv->gbp_suspended) {
*p_whymsg = "suspended";
return 0;
}
if (thermal_state != 0) {
#if GBURST_VERBOSE_EXPLANATION
if (mprm_verbosity >= GBURST_VERBOSITY_WHYMSG) {
if (gbprv->gbp_cooldv_state_override >= 0) {
snprintf(sbuf, slen,
"thermal_state = %d (%d)",
gbprv->gbp_cooldv_state_override,
gbprv->gbp_cooldv_state_cur);
} else {
snprintf(sbuf, slen,
"thermal_state = %d",
gbprv->gbp_cooldv_state_cur);
}
*p_whymsg = sbuf;
} else
#endif /* if !GBURST_VERBOSE_EXPLANATION */
{
*p_whymsg = "thermal_state != 0";
}
return 0;
}
/**
* Utilization values and utilization thresholds are represented as
* a number from 0 through 100, which is considered a percentage of
* nominal maximum utilization.
*
* When current utilization (range 0..100) falls below
* gbprv->gbp_burst_th_low (which is known as threshold_low), then
* this driver removes any outstanding request for gpu clock burst.
*
* When current utilization (range 0..100) rises above
* gbprv->gbp_burst_th_high (which is known as threshold_high), then
* (if not already doing so) this driver submits a request for gpu
* clock burst.
*
* Normally, the threshold_low is less than threshold_high,and the
* difference between them represents a range of values that provide
* hystersis.
*
* In order to facilitate testing and validation, the following
* special case "magic" numbers are implemented:
*
* threshold_low == threshold_high == 0
* Force a request for burst mode.
* threshold_low == threshold_high == 100
* Force no request for burst mode.
*/
if ((gbprv->gbp_burst_th_low == 100)
&& (gbprv->gbp_burst_th_high == 100)) {
/**
* Threshold values (normal range 0 to 100) are both set to
* 100, so force that no request be made for burst.
*/
#if GBURST_VERBOSE_EXPLANATION
if (mprm_verbosity >= GBURST_VERBOSITY_WHYMSG) {
snprintf(sbuf, slen,
"util == %d, forced_non_burst_request ",
gbprv->gbp_utilization_percentage);
*p_whymsg = sbuf;
} else
#endif
{
*p_whymsg = "forced_non_burst_request";
}
return 0;
}
if ((gbprv->gbp_burst_th_low == 0)
&& (gbprv->gbp_burst_th_high == 0)) {
/**
* Threshold values (normal range 0 to 100) are both set to
* 0, so force that a request will be made for burst.
*/
#if GBURST_VERBOSE_EXPLANATION
if (mprm_verbosity >= GBURST_VERBOSITY_WHYMSG) {
snprintf(sbuf, slen,
"util == %d, forced_burst_request ",
gbprv->gbp_utilization_percentage);
*p_whymsg = sbuf;
} else
#endif
{
*p_whymsg = "forced_burst_request";
}
return 1;
}
if (utilpct <= gbprv->gbp_burst_th_low) {
#if GBURST_VERBOSE_EXPLANATION
if (mprm_verbosity >= GBURST_VERBOSITY_WHYMSG) {
if (gbprv->gbp_utilization_override >= 0) {
snprintf(sbuf, slen,
"util (%d (%d)) <= threshold_low (%d)",
gbprv->gbp_utilization_override,
gbprv->gbp_utilization_percentage,
gbprv->gbp_burst_th_low);
} else {
snprintf(sbuf, slen,
"util (%d) <= threshold_low (%d)",
gbprv->gbp_utilization_percentage,
gbprv->gbp_burst_th_low);
}
*p_whymsg = sbuf;
} else
#endif
{
*p_whymsg = "below threshold_low";
}
return 0;
}
if (utilpct >= gbprv->gbp_burst_th_high) {
#if GBURST_VERBOSE_EXPLANATION
if (mprm_verbosity >= GBURST_VERBOSITY_WHYMSG) {
if (gbprv->gbp_utilization_override >= 0) {
snprintf(sbuf, slen,
"util (%d (%d)) >= threshold_high (%d)",
gbprv->gbp_utilization_override,
gbprv->gbp_utilization_percentage,
gbprv->gbp_burst_th_high);
} else {
snprintf(sbuf, slen,
"util (%d) >= threshold_high (%d)",
gbprv->gbp_utilization_percentage,
gbprv->gbp_burst_th_high);
}
*p_whymsg = sbuf;
} else
#endif
{
*p_whymsg = "above threshold_high";
}
return 1;
}
/* No change, return same as before. */
*p_whymsg = "same as before";
return 2;
}
/**
* request_desired_burst_mode() - Determine and issue a request for the
* desired burst state.
* @gbprv: gb handle.
*/
static void request_desired_burst_mode(struct gburst_pvt_s *gbprv)
{
int rva;
u32 reqbits;
const char *whymsg;
int burst_request_prev;
#if GBURST_VERBOSE_EXPLANATION
char sbuf[64];
#endif /* if GBURST_VERBOSE_EXPLANATION */
smp_rmb();
if (!gbprv->gbp_initialized)
return;
if (gbprv->gbp_offscreen_rendering) {
if (!GBURST_BURST_REQUESTED(gbprv)) {
reqbits = PWRGT_CNT_BURST_REQUEST_M_533;
set_state_pwrgt_cnt(gbprv, reqbits);
}
} else {
rva = desired_burst_state_query(gbprv, &whymsg
#if GBURST_VERBOSE_EXPLANATION
, sbuf, sizeof(sbuf)
#endif /* if GBURST_VERBOSE_EXPLANATION */
);
/**
* The value returned by desired_burst_state_query indicates
* the desired burst state, vis a vis the presentvburst state.
* 0 -> request un-burst
* 1 -> request burst
* 2 -> no change.
*/
/* Get previous burst_request state. */
burst_request_prev = GBURST_BURST_REQUESTED(gbprv);
/**
* If desired burst_request state changed, then issue the request.
*/
if ((rva != 2) && (rva != burst_request_prev)) {
if ((rva) && (gbprv->gbp_num_of_vsync_limited_frames < VSYNC_FRAMES))
reqbits = PWRGT_CNT_BURST_REQUEST_M_533;
else
reqbits = PWRGT_CNT_BURST_REQUEST_M_400;
set_state_pwrgt_cnt(gbprv, reqbits);
}
}
}
/**
* wake_thread() - Wake the work thread.
* @gbprv: gb handle.
*/
static void wake_thread(struct gburst_pvt_s *gbprv)
{
if (gbprv->gbp_task)
wake_up_process(gbprv->gbp_task);
}
/**
* hrt_event_processor() - Process timer-driven things.
* Called by kernel hrtimer system when the timer expires.
* @hrthdl: Pointer to the associated hrtimer struct.
*
* Execution context: hard irq level.
* Invoked via interrupt/callback.
*/
static enum hrtimer_restart hrt_event_processor(struct hrtimer *hrthdl)
{
struct gburst_pvt_s *gbprv =
container_of(hrthdl, struct gburst_pvt_s, gbp_timer);
ktime_t mc_now;
smp_rmb();
if (gbprv->gbp_initialized && gbprv->gbp_enable &&
!gbprv->gbp_suspended) {
gbprv->gbp_thread_check_utilization = 1;
smp_wmb();
wake_thread(gbprv);
}
if (!gbprv->gbp_timer_is_enabled)
return HRTIMER_NORESTART;
mc_now = ktime_get();
hrtimer_forward(hrthdl, mc_now, gbprv->gbp_hrt_period);
return HRTIMER_RESTART;
}
/**
* thread_action() - Perform desired thread actions when woken due to
* interrupt or timer expiration.
* @gbprv: gb handle.
*
* Called only from work_thread_loop.
* Function return value:
* 0 to request thread exit. Either an error or gpu burst is disabled.
* 1 otherwise.
*/
static int thread_action(struct gburst_pvt_s *gbprv)
{
int gpustate;
int utilpct;
unsigned long long ctime;
unsigned long long delta;
smp_rmb();
if (!gbprv->gbp_initialized || gbprv->gbp_suspended)
return 1;
#if GBURST_DEBUG
gbprv->gbp_thread_work_count++;
#endif /* if GBURST_DEBUG */
if (!gbprv->gbp_thread_check_utilization) {
read_and_process_PWRGT_STS(gbprv);
return 1;
}
gbprv->gbp_thread_check_utilization = 0;
ctime = timestamp();
delta = ctime - gbprv->gbp_resume_time;
if (delta > FRAME_DURATION)
gbprv->gbp_num_of_vsync_limited_frames = 0;
if (delta > OFFSCREEN_TIME) {
gbprv->gbp_offscreen_rendering = 1;
hrt_cancel(gbprv);
}
if (!gbprv->gbp_offscreen_rendering) {
utilpct = gburst_stats_gfx_hw_perf_record();
if (mprm_verbosity >= 4)
printk(GBURST_ALERT "util: %d %%\n", utilpct);
if (utilpct < 0) {
/**
* This should only fail if not initialized and is
* most likely because some initialization has
* yet to complete.
*/
if (gbprv->gbp_utilization_percentage >= 0) {
/* Only fail if already succeeded once. */
printk(GBURST_ALERT "obtaining counters failed\n");
return 0;
}
} else
gbprv->gbp_utilization_percentage = utilpct;
}
/* Read current status. */
read_and_process_PWRGT_STS(gbprv);
request_desired_burst_mode(gbprv);
return 1;
}
/**
* work_thread_loop() - the main loop for the worker thread.
* @pvd: The "void *" private data provided to kthread_create.
* This can be cast to the gbprv handle.
*
* Upon return, thread will exit.
*/
static int work_thread_loop(void *pvd)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *) pvd;
int rva;
if (mprm_verbosity >= 4)
printk(GBURST_ALERT "kernel thread started\n");
for ( ; ; ) {
/**
* Synchronization is via a call to:
* int wake_up_process(struct task_struct *p)
*/
set_current_state(TASK_INTERRUPTIBLE);
if (kthread_should_stop())
break;
schedule();
if (kthread_should_stop())
break;
rva = thread_action(gbprv);
if (rva == 0) {
/* Thread exit requested */
break;
}
}
if (mprm_verbosity >= 4)
printk(GBURST_ALERT "kernel thread stopping\n");
return 0;
}
/**
* work_thread_create() - Create work thread.
* @gbprv: gb handle.
*
* This thread is not truely a "real-time" thread, in that there will be no
* catastrophe if its execution is somewhat delayed. However, knowing that
* the nominal execution interval for this timer-woken thread is 5 msecs and
* knowing that the thread execution will be very short, it seems appropriate
* to request an elevated scheduling priority. Perhaps a consensus will be
* reached as to whether or not this is truely a good idea.
*
* Function return value: < 0 if error, otherwise 0.
*/
static int work_thread_create(struct gburst_pvt_s *gbprv)
{
struct task_struct *tskhdl;
if (!gbprv->gbp_task) {
tskhdl = kthread_create(work_thread_loop,
(void *) gbprv, "kernel_gburst");
/* Keep a reference on task structure. */
get_task_struct(tskhdl);
if (IS_ERR(tskhdl)) {
printk(GBURST_ALERT "kernel thread create fail\n");
return PTR_ERR(tskhdl);
}
{
/**
* Potential values for policy:
* SCHED_NORMAL
* SCHED_FIFO
* SCHED_RR
* SCHED_BATCH
* SCHED_IDLE
* optionally OR'd with
* SCHED_RESET_ON_FORK
* Valid priorities for SCHED_FIFO and SCHED_RR are
* 1..MAX_USER_RT_PRIO-1,
* valid priority for SCHED_NORMAL, SCHED_BATCH, and
* SCHED_IDLE is 0.
*/
/**
* An alternative should normal thread priority be
* desired would be the following.
* static const int sc_policy = SCHED_NORMAL;
* static const struct sched_param sc_param = {
* .sched_priority = 0,
* };
*/
/**
* It seems advisable to run our kernel thread
* at elevated priority.
*/
static const int sc_policy = SCHED_RR;
static const struct sched_param sc_param = {
.sched_priority = 1,
};
int rva;
rva = sched_setscheduler_nocheck(tskhdl,
sc_policy, &sc_param);
if (rva < 0) {
printk(GBURST_ALERT
"task priority set failed,"
" code: %d\n", -rva);
}
}
gbprv->gbp_task = tskhdl;
wake_up_process(tskhdl);
}
return 0;
}
/**
* gburst_thread_stop - kill the worker thread.
* @gbprv: gb handle.
*/
static void gburst_thread_stop(struct gburst_pvt_s *gbprv)
{
if (gbprv->gbp_task) {
/* kthread_stop will not return until the thread is gone. */
kthread_stop(gbprv->gbp_task);
put_task_struct(gbprv->gbp_task);
gbprv->gbp_task = NULL;
}
}
/**
* gburst_enable_set() - gburst enable/disable.
* @gbprv: gb handle.
*
* Typically triggered through a /proc reference.
* When disabled, overhead is reduced by turning off the kernel thread
* and timer.
*/
static void gburst_enable_set(struct gburst_pvt_s *gbprv)
{
int flgenb;
flgenb = gbprv->gbp_request_enable &&
!gbprv->gbp_request_disable;
if (gbprv->gbp_enable == flgenb)
return;
gbprv->gbp_enable = flgenb;
if (!gbprv->gbp_enable) {
hrt_cancel(gbprv);
/* stop the thread */
if (gbprv->gbp_task) {
/* Stop thread. */
gburst_thread_stop(gbprv);
}
} else {
work_thread_create(gbprv);
hrt_start(gbprv);
}
request_desired_burst_mode(gbprv);
}
/**
* state_times_to_string - Return string describing state_times.
* @gbprv: data structure handle
* @prhdg: non-zero to include a heading.
* @slen: length of buffer
* @sbuf: buffer to receive string.
*/
static int state_times_to_string(struct gburst_pvt_s *gbprv, const char *pfx,
int prhdg, int ix, int slen, char *sbuf)
{
struct gb_state_times_s gst;
struct gb_state_times_s *pst = &gst;
update_state_times(gbprv, pst);
if (prhdg) {
ix = ut_isnprintf(ix, sbuf, slen, "%s%s\n", pfx,
" uptime gfx_power burst_request "
" burst_realized gpu_throttled");
}
if (prhdg == 2) {
ix = ut_isnprintf(ix, sbuf, slen, "%s%s\n",
pfx,
"-------------- -------------- --------------"
" -------------- --------------");
}
ix = ut_isnprintf(ix, sbuf, slen,
"%s%7lu.%06lu %7lu.%06lu %7lu.%06lu %7lu.%06lu %7lu.%06lu\n",
pfx,
pst->gst_uptime.tv_sec,
pst->gst_uptime.tv_nsec / NSEC_PER_USEC,
pst->gst_time_gfx_power.tv_sec,
pst->gst_time_gfx_power.tv_nsec / NSEC_PER_USEC,
pst->gst_time_burst_requested.tv_sec,
pst->gst_time_burst_requested.tv_nsec / NSEC_PER_USEC,
pst->gst_time_burst_realized.tv_sec,
pst->gst_time_burst_realized.tv_nsec / NSEC_PER_USEC,
pst->gst_time_throttled.tv_sec,
pst->gst_time_throttled.tv_nsec / NSEC_PER_USEC);
return ix;
}
/**
* copy_from_user_nt() - Like copy_from_user, but ensures null termination,
* plus accepts as an input the size of the destination buffer.
* @tbuf: kernel buffer to receive data
* @tlen: length of kernel buffer
* @ubuf: user-space buffer
* @ucnt: Number of bytes in ubuf.
*/
static int copy_from_user_nt(char *tbuf, size_t tlen,
const void __user *ubuf, unsigned long ucnt)
{
if ((ucnt >= tlen) || copy_from_user(tbuf, ubuf, ucnt))
return -EINVAL;
tbuf[ucnt] = '\0';
return 0;
}
/**
* threshold_derive_low() - Compute low, given high and down_diff.
* @gbprv: gb handle.
*
* The bottom threshold may be specified either as a specific value ("low") or
* as a delta ("down_diff") below the high threshold's current value. This is
* done because of differing desires of testers versus those characterizing
* utilization numbers. Ultimately, the "low" value is computed and used
* in utilization comparisons.
*/
static void threshold_derive_low(struct gburst_pvt_s *gbprv)
{
int tlow;
tlow = gbprv->gbp_burst_th_high - gbprv->gbp_burst_th_down_diff;
if (tlow < 0)
tlow = 0;
gbprv->gbp_burst_th_low = tlow;
}
/**
* threshold_derive_down_diff() - Compute down_diff, given high and low.
* @gbprv: gb handle.
*
* The bottom threshold may be specified either as a specific value ("low") or
* as a delta ("down_diff") below the high threshold's current value. This is
* done because of differing desires of testers versus those characterizing
* utilization numbers. Ultimately, the "low" value is computed and used
* in utilization comparisons.
*/
static void threshold_derive_down_diff(struct gburst_pvt_s *gbprv)
{
int tdd;
tdd = gbprv->gbp_burst_th_high - gbprv->gbp_burst_th_low;
if (tdd < 0)
tdd = 0;
gbprv->gbp_burst_th_down_diff = tdd;
}
/**
* threshold_derive_either() - Compute either down_diff or low, given
* high and the other.
* @gbprv: gb handle.
*
* The bottom threshold may be specified either as a specific value ("low") or
* as a delta ("down_diff") below the high threshold's current value. This is
* done because of differing desires of testers versus those characterizing
* utilization numbers. Ultimately, the "low" value is computed and used
* in utilization comparisons.
*/
static void threshold_derive_either(struct gburst_pvt_s *gbprv)
{
if (gbprv->gbp_thold_via == GBP_THOLD_VIA_LOW)
threshold_derive_down_diff(gbprv);
else /* if (gbprv->gbp_thold_via == GBP_THOLD_VIA_DOWN_DIFF) */
threshold_derive_low(gbprv);
}
/**
* generate_dump_string() - Dump all status variables for gpu burst.
* Useful during development and test.
* @gbprv: gb handle.
* @buflen: Length of buf.
* @buf: Buffer to receive output string.
*
* Function return value: negative if error or number of character stored
* in buf.
*
* Side effect: Output of dump string via printk.
* Useful during development. Candidate for removal later.
*/
static int generate_dump_string(struct gburst_pvt_s *gbprv, size_t buflen,
char *buf)
{
u32 pwrgt_sts;
u32 tmpv0;
int ix;
ix = 0;
/* Get Punit Status Register */
pwrgt_sts = read_and_process_PWRGT_STS(gbprv);
if (!gbprv->gbp_enable) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"enable = %d\n",
gbprv->gbp_enable);
}
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"thermal_state = %d\n",
gbprv->gbp_cooldv_state_cur);
if (gbprv->gbp_cooldv_state_highest > gbprv->gbp_cooldv_state_cur) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"thermal_state_highest = %d\n",
gbprv->gbp_cooldv_state_highest);
}
if (gbprv->gbp_cooldv_state_override >= 0) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"thermal_override = %d\n",
gbprv->gbp_cooldv_state_override);
}
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"utilization_threshold_low = %d\n",
gbprv->gbp_burst_th_low);
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"utilization_threshold_high = %d\n",
gbprv->gbp_burst_th_high);
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"utilization = %d\n",
gbprv->gbp_utilization_percentage);
if (gbprv->gbp_utilization_override >= 0) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"utilization_override = %d\n",
gbprv->gbp_utilization_override);
}
ix = state_times_to_string(gbprv, GBURST_HEADING, 1, ix, buflen, buf);
if (!gbprv->gbp_task) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"task_handle = %p\n",
gbprv->gbp_task);
}
#if GBURST_DEBUG
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"counts (thrd work, intr, thrm_chg) = %u, %u, %u\n",
gbprv->gbp_thread_work_count,
gbprv->gbp_interrupt_count,
gbprv->gbp_thermal_state_change_count);
if (gbprv->gbp_suspend_count || gbprv->gbp_resume_count) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"suspend_resume_count = %u, %u\n",
gbprv->gbp_suspend_count,
gbprv->gbp_resume_count);
}
if (gbprv->gbp_suspended) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"gfx_suspended = %u\n",
gbprv->gbp_suspended);
}
#endif /* if GBURST_DEBUG */
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING "hrt_enable_state = %d\n",
gbprv->gbp_timer_is_enabled);
tmpv0 = hrtimer_active(&gbprv->gbp_timer);
if (gbprv->gbp_timer_is_enabled != tmpv0) {
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING "hrt_enable_state_actual = %d\n",
tmpv0);
}
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"vsync_limited_frames mode = %d\n",
(gbprv->gbp_num_of_vsync_limited_frames >= VSYNC_FRAMES));
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"offscreen_rendering mode = %d\n",
gbprv->gbp_offscreen_rendering);
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"PWRGT_CNT_last_write = %#8.8x\n",
gbprv->gbp_pwrgt_cnt_last_written);
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"PWRGT_STS = %#8.8x\n", pwrgt_sts);
{
u32 tmpv1;
char sbuf0[32];
char sbuf1[32];
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"gpu_burst_interrupt enable = %d\n",
!!(pwrgt_sts & PWRGT_STS_INT_ENABLE_BIT));
if (pwrgt_sts & PWRGT_STS_RESERVED_1_BIT)
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"reserved_bit_29 = 1\n");
if (pwrgt_sts & PWRGT_STS_ENABLE_AUTO_BURST_ENTRY_BIT)
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"ENABLE_AUTO_BURST_ENTRY = 1\n");
tmpv0 = (pwrgt_sts & PWRGT_STS_BURST_REQUEST_M) >>
PWRGT_STS_BURST_REQUEST_P;
tmpv1 = (pwrgt_sts & PWRGT_STS_BURST_REALIZED_M) >>
PWRGT_STS_BURST_REALIZED_P;
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"frequency_requested = %s\n",
generate_freq_string(tmpv0, sbuf0, sizeof(sbuf0)));
ix = ut_isnprintf(ix, buf, buflen,
GBURST_HEADING
"frequency_realized = %s\n",
generate_freq_string(tmpv1, sbuf1, sizeof(sbuf1)));
}
if (ix >= buflen)
return -EINVAL;
printk(GBURST_ALERT
"Begin - Read from /proc/gburst/dump\n"
"%s"
GBURST_HEADING
"End - Read from /proc/gburst/dump\n",
buf);
return ix;
}
/**
* pfs_debug_message_read - Procfs read function for
* /proc/gburst/debug_message read
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Return current enable state (0 or 1) of some debug messages.
* Associated variable to control debug messages: gburst_debug_msg_on
*/
static int pfs_debug_message_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
res = scnprintf(msg, sizeof(msg), "%d\n", gburst_debug_msg_on);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_debug_message_write() - Procfs writer function for
* /proc/gburst/debug_message write
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* Control for some debug messages: Non-zero to enable, zero to disable.
* Associated variable to control debug messages: gburst_debug_msg_on
*/
static int pfs_debug_message_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
gburst_debug_msg_on = uval;
return count;
}
/**
* pfs_disable_read - Procfs read function for
* /proc/gburst/disable -- Global and non-privileged disable for gpu burst.
* This is a non-privileged override that provides a way for any application
* (especially those that require exclusive use of the performance counters)
* to disable gburst.
*
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns current state of this *disable* flag as "0" or "1".
* Write:
* 0: Revert any previous disable done via /proc/gburst/disable.
* Also happens when writing 1 to /proc/gburst/enable .
* 1: Disable gpu burst mode. Stop the timer. Stop the background thread.
*/
static int pfs_disable_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_request_disable);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_disable_write - Procfs write function for
* /proc/gburst/disable -- Global and non-privileged disable for gpu burst.
* This is a non-privileged override that provides a way for any application
* (especially those that require exclusive use of the performance counters)
* to disable gburst.
*
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns current state of this *disable* flag as "0" or "1".
* Write:
* 0: Revert any previous disable done via /proc/gburst/disable.
* Also happens when writing 1 to /proc/gburst/enable .
* 1: Disable gpu burst mode. Stop the timer. Stop the background thread.
*/
static int pfs_disable_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
if (gbprv->gbp_request_disable != uval) {
gbprv->gbp_request_disable = uval;
gburst_enable_set(gbprv);
}
return count;
}
/**
* pfs_dump_read() - Procfs read function for
* /proc/gburst/dump
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* read: return a verbose textual status dump.
* write: null.
*/
static int pfs_dump_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[4096];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = generate_dump_string(gbprv, nbytes, msg);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_enable_read - Procfs read function for
* /proc/gburst/enable -- Global enable/disable for gpu burst.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns current state as "0" or "1".
* Write:
* 0: Disable gpu burst mode. Stop the timer. Stop the background thread.
* 1: Enable gpu burst mode.
* Also, revert any previous disable done via /proc/gburst/disable.
*/
static int pfs_enable_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_enable);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_enable_write - Procfs write function for
* /proc/gburst/enable -- Global enable/disable for gpu burst.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns current state as "0" or "1".
* Write:
* 0: Disable gpu burst mode. Stop the timer. Stop the background thread.
* 1: Enable gpu burst mode.
* Also, revert any previous disable done via /proc/gburst/disable.
*/
static int pfs_enable_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
/* Setting enable also clears disable. */
if ((uval && gbprv->gbp_request_disable) ||
(gbprv->gbp_request_enable != uval)) {
gbprv->gbp_request_enable = uval;
if (uval)
gbprv->gbp_request_disable = 0;
gburst_enable_set(gbprv);
if (!gbprv->gbp_enable)
printk(GBURST_ALERT "gpu burst globally disabled via procfs.\n");
else
printk(GBURST_ALERT "gpu burst globally enabled via procfs.\n");
}
return count;
}
/**
* pfs_gpu_monitored_counters_read - Procfs read function for
* /proc/gburst/monitored_counters -- write or read which hardware counters are
* monitored and therefore available to be considered in performance
* calculation.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* The output string is formatted in a way acceptable to use as an input
* string for writing to this proc file.
*
* See function pfs_gpu_monitored_counters_write.
*/
static int pfs_gpu_monitored_counters_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[1024];
int ix;
ix = gburst_stats_gfx_hw_perf_counters_to_string(0, msg, sizeof(msg));
if (ix < 0)
return ix;
if (ix >= sizeof(msg))
return -EINVAL;
return simple_read_from_buffer(buf, nbytes, ppos, msg, ix);
}
/**
* pfs_gpu_monitored_counters_write - Procfs write function for
* /proc/gburst/monitored_counters -- write or read which hardware counters are
* monitored and therefore available to be considered in performance
* calculation.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* See comments at function gburst_stats_gfx_hw_perf_counters_set
* definition, from which the following format description is excerpted:
*
* The input string specifies which counters are to be visible as
* whitespace-separated (e.g., space, tab, newline) groups of: %u:%u:%u:%u
* which correspond to counter:group:bit:coeff .
* then assigns the values into data structures for all cores.
* These per-counter values are:
* 1. counter - An index into this module's counter data arrays.
* 2. group -- The hardware "group" from which this counter is taken.
* 3. bit -- The hardware bit (for this group) that selects this counter.
* 4. coeff -- A counter specific increment value.
* Example input string: "1:0:1:16 6:0:24:32"
*/
static int pfs_gpu_monitored_counters_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
char buf[128];
int rva;
if (copy_from_user_nt(buf, sizeof(buf), buffer, count))
return -EINVAL;
rva = gburst_stats_gfx_hw_perf_counters_set(buf);
if (rva < 0)
return rva;
return count;
}
/**
* pfs_pwrgt_sts_read() - Procfs read function for
* /proc/gburst/pwrgt_sts -- Return register PWRGT_STS contents.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns register PWRGT_STS content as a hex number
* with a leading "0x".
* Write: N/A
*/
static int pfs_pwrgt_sts_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
read_and_process_PWRGT_STS(gbprv);
res = scnprintf(msg, sizeof(msg), "%#x\n", gbprv->gbp_pwrgt_sts_last_read);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_thermal_override_read() - Procfs read function for
* /proc/gburst/thermal_override -- thermal state override. For testing.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* The maximum of the current thermal state and the thermal override state
* are used to make thermal decisions in the driver.
* Read: Returns thermal override state. 0 is base state, > 0 indicates
* hot. Currently 0..1.
* Initial value is 0.
* Write: >= 0 to set thermal override state, -1 to reset.
* Silently limited to range -1, 0..1.
* Warning: setting override to 0 disables OS gpu burst thermal controls,
* although firmware controls will still be in effect.
*/
static int pfs_thermal_override_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_cooldv_state_override);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_thermal_override_write() - Procfs write function for
* /proc/gburst/thermal_override -- thermal state override. For testing.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* The maximum of the current thermal state and the thermal override state
* are used to make thermal decisions in the driver.
* Read: Returns thermal override state. 0 is base state, > 0 indicates
* hot. Currently 0..1.
* Initial value is 0.
* Write: >= 0 to set thermal override state, -1 to reset.
* Silently limited to range -1, 0..1.
* Warning: setting override to 0 disables OS gpu burst thermal controls,
* although firmware controls will still be in effect.
*/
static int pfs_thermal_override_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
int sval;
int rva;
rva = kstrtoint_from_user(buffer, count, 0, &sval);
if (rva < 0)
return rva;
if (sval < 0) {
gbprv->gbp_cooldv_state_override = -1;
} else if (sval <= 1) {
gbprv->gbp_cooldv_state_override = sval;
} else {
gbprv->gbp_cooldv_state_override = -1;
return -EINVAL;
}
return count;
}
/**
* pfs_thermal_state_read() - Procfs read function for
* /proc/gburst/thermal_state -- current thermal state.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns current thermal state. 0 is base state, > 0 indicates
* hot. Currently 0..1.
* This value read does not reflect any override that may be in effect.
* Write: N/A
*/
static int pfs_thermal_state_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_cooldv_state_cur);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_gb_threshold_down_diff_read() - Procfs read function for
* /proc/gburst/threshold_down_diff -- read/write un-burst trigger threshold
* as a "down_diff" from the high threshold. (0-100).
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*/
static int pfs_gb_threshold_down_diff_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_burst_th_down_diff);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_gb_threshold_down_diff_write() - Procfs write function for
* /proc/gburst/threshold_down_diff -- read/write un-burst trigger threshold
* as a "down_diff" from the high threshold. (0-100).
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
*/
static int pfs_gb_threshold_down_diff_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
if (uval > 100)
return -EINVAL;
printk(GBURST_ALERT "Changing threshold_down_diff from %d to %d\n",
gbprv->gbp_burst_th_down_diff, uval);
gbprv->gbp_burst_th_down_diff = uval;
gbprv->gbp_thold_via = GBP_THOLD_VIA_DOWN_DIFF;
threshold_derive_low(gbprv);
return count;
}
/**
* pfs_gb_threshold_high_write - Procfs write function for
* /proc/gburst/threshold_high -- read/write burst trigger threshold (0-100).
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
*/
static int pfs_gb_threshold_high_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
if (uval > 100)
return -EINVAL;
if (gbprv->gbp_burst_th_high != uval) {
printk(GBURST_ALERT "Changing threshold_high from %u to %u\n",
gbprv->gbp_burst_th_high, uval);
}
gbprv->gbp_burst_th_high = uval;
threshold_derive_either(gbprv);
return count;
}
/**
* pfs_gb_threshold_high_read - Procfs read function for
* /proc/gburst/threshold_high -- read/write burst trigger threshold (0-100).
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
*/
static int pfs_gb_threshold_high_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_burst_th_high);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_gb_threshold_low_read() - Procfs read function for
* /proc/gburst/threshold_low -- read/write un-burst trigger threshold (0-100).
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*/
static int pfs_gb_threshold_low_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_burst_th_low);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_gb_threshold_low_write() - Procfs write function for
* /proc/gburst/threshold_low -- read/write un-burst trigger threshold (0-100).
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*/
static int pfs_gb_threshold_low_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
if (uval > 100)
return -EINVAL;
printk(GBURST_ALERT "Changing threshold_low from %d to %d\n",
gbprv->gbp_burst_th_low, uval);
gbprv->gbp_burst_th_low = uval;
gbprv->gbp_thold_via = GBP_THOLD_VIA_LOW;
threshold_derive_down_diff(gbprv);
return count;
}
/**
* pfs_state_times_read() - Procfs read function for
* /proc/gburst/state_times -- uptime and times in states, seconds.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Write: 0, 1, 2 for increasing verbosity of header line output.
* Read:
* Three times, which all include time when system or device is suspended:
* - uptime - time since boot
* - cumulative time that burst has been requested.
* - cumulative time that burst has been realized.
* - cumulative time that gpu frequency has been throttled.
*
* The time values are stored internally with nanosecond resolution,
* but are returned by this read function to microsecond resolution.
* The times are expressed for this interface as <seconds>.<fraction>.
* Example output:
* " 12721.541788 1.349854 1.349790 0.000000"
*/
static int pfs_state_times_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = state_times_to_string(gbprv, "", 0, gbprv->gbp_state_time_header,
sizeof(msg), msg);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_state_times_write() - Procfs write function for
* /proc/gburst/state_times -- uptime and times in states, seconds.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* Write: 0, 1, 2 for increasing verbosity of header line output.
* Read:
* Three times, which all include time when system or device is suspended:
* - uptime - time since boot
* - cumulative time that burst has been requested.
* - cumulative time that burst has been realized.
* - cumulative time that gpu frequency has been throttled.
*
* The time values are stored internally with nanosecond resolution,
* but are returned by this read function to microsecond resolution.
* The times are expressed for this interface as <seconds>.<fraction>.
* Example output:
* " 12721.541788 1.349854 1.349790 0.000000"
*/
static int pfs_state_times_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
gbprv->gbp_state_time_header = uval;
return count;
}
/**
* pfs_timer_period_usecs_read() - Procfs read function for
* /proc/gburst/timer_period_usecs -- Kernel thread's timer period, usecs.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Read/write.
*/
static int pfs_timer_period_usecs_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
int period_usecs;
period_usecs = ktime_to_us(gbprv->gbp_hrt_period);
if (mprm_verbosity >= 3)
printk(GBURST_ALERT "timer period = %u microseconds\n",
period_usecs);
res = scnprintf(msg, sizeof(msg), "%u microseconds\n", period_usecs);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_timer_period_usecs_write() - Procfs write function for
* /proc/gburst/timer_period_usecs -- Kernel thread's timer period, usecs.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* Read/write.
*/
static int pfs_timer_period_usecs_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
unsigned int uval;
int rva;
/* Get period in microseconds. */
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
if (uval > USEC_PER_SEC)
uval = USEC_PER_SEC;
gbprv->gbp_hrt_period = ktime_set(0, uval * NSEC_PER_USEC);
/**
* Change the time immediately, abandoning current interval.
* If this call was not made, then change would take place
* after the next timer expiration.
*/
hrt_start(gbprv);
return count;
}
/**
* pfs_utilization_read() - Procfs read function for
* /proc/gburst/utilization -- current utilization. (0 to 100).
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Read: Returns current utilization as a number from 0 to 100 or -1
* if no value is available.
* This value read does not reflect any override that may be in effect.
* Write: N/A
*/
static int pfs_utilization_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_utilization_percentage);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_utilization_override_read() - Procfs read function for
* /proc/gburst/utilization_override -- utilization override. For testing.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* If set, a utilization override value to be used instead of actual
* utilization.
* Read: Returns current utilization override as a number from 0 to 100
* or -1 if override state is not set.
* Write:
* 0 to 100 - utilization override value or -1 to reset override.
*/
static int pfs_utilization_override_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
res = scnprintf(msg, sizeof(msg), "%d\n", gbprv->gbp_utilization_override);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_utilization_override_write - Procfs write function for
* /proc/gburst/utilization_override -- utilization override. For testing.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* If set, a utilization override value to be used instead of actual
* utilization.
* Read: Returns current utilization override as a number from 0 to 100
* or -1 if override state is not set.
* Write:
* 0 to 100 - utilization override value or -1 to reset override.
*/
static int pfs_utilization_override_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *)PDE_DATA(file_inode(file));
int sval;
int rva;
rva = kstrtoint_from_user(buffer, count, 0, &sval);
if (rva < 0) {
printk(GBURST_ALERT "read int failed\n");
return rva;
}
if (sval < 0) {
gbprv->gbp_utilization_override = -1;
} else if (sval <= 100) {
gbprv->gbp_utilization_override = sval;
} else {
if (mprm_verbosity >= 4)
printk(GBURST_ALERT "invalid value\n");
return -EINVAL;
}
return count;
}
/**
* pfs_verbosity_read() - Procfs read function for
* /proc/gburst/verbosity -- Specify routine verbosity.
* Parameters are the standard ones for procfs read functions.
* @buf: buffer into which output can be provided for read function.
* @start: May be used to provide multiple buffers of output.
* @offset: May be used to provide multiple buffers of output.
* @breq: Number of bytes available in buf.
* @eof: Set by this function to indicate EOF.
* @pvd: Private data (in this case, gbprv).
*
* Associated variable to control verbosity of message issuance.
* read: number >= 0 indicating verbosity, with 0 being most quiet.
* write: number >= 0 indicating verbosity, with 0 being most quiet.
*/
static int pfs_verbosity_read(struct file *file, char __user *buf,
size_t nbytes,loff_t *ppos)
{
char msg[128];
int res;
res = scnprintf(msg, sizeof(msg), "%d\n", mprm_verbosity);
return simple_read_from_buffer(buf, nbytes, ppos, msg, res);
}
/**
* pfs_verbosity_write() - Procfs write function for
* /proc/gburst/verbosity -- Specify routine verbosity.
* Parameters are the standard ones for procfs read functions.
* @file: Pointer to procfs associated struct file.
* @buffer: buffer with data written to the proc file, input to this function.
* @count: number of bytes of data present in buffer.
* @pvd: Private data (in this case, gbprv).
*
* Associated variable to control verbosity of message issuance.
* read: number >= 0 indicating verbosity, with 0 being most quiet.
* write: number >= 0 indicating verbosity, with 0 being most quiet.
*/
static int pfs_verbosity_write(struct file *file,
const char *buffer, size_t count, loff_t *ppos)
{
unsigned int uval;
int rva;
rva = kstrtouint_from_user(buffer, count, 0, &uval);
if (rva < 0)
return rva;
mprm_verbosity = uval;
return count;
}
/**
* pfs_init() - Create all /proc/gburst entries.
* @gbprv: gb handle.
*
* Table driven from pfs_tab.
*/
static void pfs_init(struct gburst_pvt_s *gbprv)
{
struct proc_dir_entry *pdehdl;
const struct pfs_data *pfsdat;
int fmode;
int ix;
struct file_operations *pfd_proc_fops;
/* Create /proc/gburst */
if (!gbprv->gbp_proc_gburst) {
/**
* gbprv->gbp_proc_parent will be NULL at this point,
* which means to create at the top level of /proc.
*/
gbprv->gbp_proc_gburst = proc_mkdir(GBURST_PFS_NAME_DIR_GBURST,
gbprv->gbp_proc_parent);
if (!gbprv->gbp_proc_gburst) {
printk(GBURST_ALERT "Error creating gburst proc dir: %s\n",
GBURST_PFS_NAME_DIR_GBURST);
return;
}
}
for (ix = 0; ix < ARRAY_SIZE(pfs_tab); ix++) {
pfsdat = pfs_tab + ix;
fmode = pfsdat->pfd_mode;
if (!pfsdat->pfd_func_read)
fmode &= ~0444;
if (!pfsdat->pfd_func_write)
fmode &= ~0222;
pfd_proc_fops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
if(!pfd_proc_fops) {
printk(GBURST_ALERT "Error creating gburst proc file: %s\n",
pfsdat->pfd_file_name);
continue;
}
pfd_proc_fops->read = pfsdat->pfd_func_read;
pfd_proc_fops->write = pfsdat->pfd_func_write;
pdehdl = proc_create_data(pfsdat->pfd_file_name, fmode,
gbprv->gbp_proc_gburst, pfd_proc_fops, gbprv);
gbprv->gbp_pfs_handle[ix] = pdehdl;
if (!pdehdl) {
printk(GBURST_ALERT "Error creating gburst proc file: %s\n",
pfsdat->pfd_file_name);
}
}
}
/**
* pfs_cleanup() - Remove /proc/gburst files (e.g., at module exit).
* @gbprv: gb handle.
*/
static void pfs_cleanup(struct gburst_pvt_s *gbprv)
{
struct proc_dir_entry *pde_root = gbprv->gbp_proc_gburst;
int ix;
if (!pde_root)
return;
for (ix = 0; ix < ARRAY_SIZE(pfs_tab); ix++) {
if (gbprv->gbp_pfs_handle[ix]) {
const struct pfs_data *pfsdat = pfs_tab + ix;
/**
* Function remove_proc_entry may wait for completion
* of in-progress operations on the corresponding
* proc file.
*/
remove_proc_entry(pfsdat->pfd_file_name, pde_root);
gbprv->gbp_pfs_handle[ix] = NULL;
}
}
remove_proc_entry(GBURST_PFS_NAME_DIR_GBURST, gbprv->gbp_proc_parent);
}
/**
* gburst_irq_handler() - Interrupt handler for frequency change interrupts.
* @irq: The irq for this interrupt.
* @pvd: Private data for this interrupt. In this case, gbprv.
*
* Execution context: hard irq level
*
* Warning: Currently, reading or writing registers (via intel_mid_msgbus_read32
* and intel_mid_msgbus_write32 family) must not be done at interrupt level.
*/
static irqreturn_t gburst_irq_handler(int irq, void *pvd)
{
struct gburst_pvt_s *gbprv = (struct gburst_pvt_s *) pvd;
smp_rmb();
if (!gbprv->gbp_initialized)
return IRQ_HANDLED;
#if GBURST_DEBUG
gbprv->gbp_interrupt_count++;
#endif /* if GBURST_DEBUG */
if (gbprv->gbp_suspended) {
/* Interrupt while suspended is not an abnormal event. */
return IRQ_HANDLED;
}
wake_thread(gbprv);
/**
* Potential return values from an interrupt handler:
* IRQ_NONE -- Not our interrupt
* IRQ_HANDLED -- Interrupt handled.
* IRQ_WAKE_THREAD -- Pass on to this interrupt's kernel thread.
*/
return IRQ_HANDLED;
}
/**
* gburst_suspend() - Callback for gfx hw transition to state D3.
* @gbprv: gb handle.
*
* Invoked via interrupt/callback.
* Execution context: non-atomic
*/
static int gburst_suspend(struct gburst_pvt_s *gbprv)
{
unsigned long long ctime;
smp_rmb();
if (!gbprv || !gbprv->gbp_initialized)
return 0;
if (mprm_verbosity >= 3)
printk(GBURST_ALERT "suspend\n");
#if GBURST_DEBUG
gbprv->gbp_suspend_count++;
#endif
/* Must update times before changing flag. */
update_state_times(gbprv, NULL);
mutex_lock(&gbprv->gbp_mutex_pwrgt_sts);
gbprv->gbp_suspended = 1;
mutex_unlock(&gbprv->gbp_mutex_pwrgt_sts);
smp_wmb();
/* Cancel timer events. */
hrt_cancel(gbprv);
ctime = timestamp();
if ((ctime-gbprv->gbp_resume_time+FRAME_TIME_BUFFER) < FRAME_DURATION)
gbprv->gbp_num_of_vsync_limited_frames += 1;
else
gbprv->gbp_num_of_vsync_limited_frames = 0;
write_PWRGT_CNT(gbprv, 0);
/* GBURST_TIMING_BEFORE_SUSPEND - Code currently under test. */
#define GBURST_TIMING_BEFORE_SUSPEND 1
#if GBURST_TIMING_BEFORE_SUSPEND
/*
* Before suspend takes place, tell gfx hw driver that gpu
* frequency is normal. It is hoped that this will lead to
* better stability.
* No additional call to update_state_times is desired here, as
* the delta time should be insignificant from the previous call.
*/
mutex_lock(&gbprv->gbp_mutex_pwrgt_sts);
if ((gbprv->gbp_pwrgt_sts_last_read & PWRGT_STS_BURST_REALIZED_M)
!= PWRGT_STS_BURST_REALIZED_M_400) {
/* Notify driver. */
gburst_stats_gpu_freq_mhz_info(
freq_mhz_table[GBURST_GPU_FREQ_400]);
}
gbprv->gbp_pwrgt_sts_last_read = (gbprv->gbp_pwrgt_sts_last_read &
~(PWRGT_STS_BURST_REQUEST_M | PWRGT_STS_BURST_REALIZED_M))
| (GBURST_GPU_FREQ_400 << PWRGT_STS_BURST_REQUEST_P)
| (GBURST_GPU_FREQ_400 << PWRGT_STS_BURST_REALIZED_P);
mutex_unlock(&gbprv->gbp_mutex_pwrgt_sts);
#endif /* if GBURST_TIMING_BEFORE_SUSPEND */
/* Clear current utilization value before suspend as there has
been 5ms GPU idle period anyway due to suspend request */
if (gbprv->gbp_utilization_percentage > 0)
gbprv->gbp_utilization_percentage = 0;
/* Clear utilization check request to avoid extra calculation
on next freq change wakeup in case timer expires during suspend
sequence */
gbprv->gbp_thread_check_utilization = 0;
/* Clean up GFX load information storage from old and obsolete data */
gburst_stats_cleanup_gfx_load_data();
return 0;
}
/**
* gburst_resume() - Callback for gfx hw transition from state D3.
* @gbprv: gb handle.
*
* Device power on. Assume the device has retained no state.
* Invoked via interrupt/callback.
* Execution context: non-atomic
*/
static int gburst_resume(struct gburst_pvt_s *gbprv)
{
smp_rmb();
if (!gbprv || !gbprv->gbp_initialized)
return 0;
if (mprm_verbosity >= 3)
printk(GBURST_ALERT "resume\n");
#if GBURST_DEBUG
gbprv->gbp_resume_count++;
#endif /* if GBURST_DEBUG */
update_state_times(gbprv, NULL);
read_PWRGT_CNT_toggle(gbprv);
/* Assume thermal state is current or will be updated soon. */
/* PWRGT_CNT to 0 except toggle and interrupt enable. */
if ((gbprv->gbp_burst_th_low == 0) &&
(gbprv->gbp_burst_th_high == 0)) {
set_state_pwrgt_cnt(gbprv, PWRGT_CNT_BURST_REQUEST_M_533);
} else {
set_state_pwrgt_cnt(gbprv, PWRGT_CNT_BURST_REQUEST_M_400);
}
gbprv->gbp_suspended = 0;
gbprv->gbp_resume_time = timestamp();
gbprv->gbp_offscreen_rendering = 0;
smp_wmb();
mutex_lock(&gbprv->gbp_mutex_pwrgt_sts);
if (!(read_PWRGT_STS_simple() & PWRGT_STS_FREQ_THROTTLE_M)) {
hrt_start(gbprv);
}
mutex_unlock(&gbprv->gbp_mutex_pwrgt_sts);
return 0;
}
/**
* gburst_power_state_set() - Callback informing of gfx hw power state change.
* @gbprv: gb handle.
* @st_on: 1 if powering on, 0 if powering down.
*/
static void gburst_power_state_set(struct gburst_pvt_s *gbprv, int st_on)
{
if (gbprv->gbp_enable) {
if (st_on)
gburst_resume(gbprv);
else
gburst_suspend(gbprv);
}
}
/**
* 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;
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 gburst_pvt_s *gbprv = (struct gburst_pvt_s *) tcd->devdata;
*pcs = gbprv->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 gburst_pvt_s *gbprv = (struct gburst_pvt_s *) tcd->devdata;
if (cs > THERMAL_COOLING_DEVICE_MAX_STATE)
cs = THERMAL_COOLING_DEVICE_MAX_STATE;
/* If state change between zero and non-zero... */
if (!!gbprv->gbp_cooldv_state_cur != !!cs) {
gbprv->gbp_cooldv_state_prev = gbprv->gbp_cooldv_state_cur;
gbprv->gbp_cooldv_state_cur = cs;
if (gbprv->gbp_cooldv_state_highest <
gbprv->gbp_cooldv_state_cur) {
gbprv->gbp_cooldv_state_highest =
gbprv->gbp_cooldv_state_cur;
}
#if GBURST_DEBUG
gbprv->gbp_thermal_state_change_count++;
#endif
if (mprm_verbosity >= 2)
printk(GBURST_ALERT "Thermal state changed from %d to %d\n",
gbprv->gbp_cooldv_state_prev,
gbprv->gbp_cooldv_state_cur);
request_desired_burst_mode(gbprv);
}
return 0;
}
/**
* gburst_cleanup() -- Clean up module data structures, release resources, etc.
* @gbprv: gb handle.
*/
static void __exit gburst_cleanup(struct gburst_pvt_s *gbprv)
{
if (!gbprv)
return;
gbprv->gbp_initialized = 0;
smp_mb();
{
struct gburst_interface_s gd_interface;
memset(&gd_interface, 0, sizeof(struct gburst_interface_s));
gburst_interface_set_data(&gd_interface);
}
if (gbprv->gbp_cooldv_hdl) {
thermal_cooling_device_unregister(gbprv->gbp_cooldv_hdl);
gbprv->gbp_cooldv_hdl = NULL;
}
write_PWRGT_CNT(gbprv, 0);
/**
* Interestingly, free_irq will (if defined CONFIG_DEBUG_SHIRQ,
* for shared interrupts) call the irq handler spuriously
* just in order to make sure the driver handles the
* spurious call correctly.
*/
free_irq(GBURST_IRQ_LEVEL, gbprv);
/* De-install /proc/gburst entries. */
if (gbprv->gbp_proc_gburst)
pfs_cleanup(gbprv);
hrtimer_cancel(&gbprv->gbp_timer);
/* stop the thread */
if (gbprv->gbp_task)
gburst_thread_stop(gbprv);
mutex_destroy(&gbprv->gbp_state_times_mutex);
mutex_destroy(&gbprv->gbp_mutex_pwrgt_sts);
}
/**
* gburst_init() - gburst module initialization.
* @gbprv: gb handle.
*
* Invokes sub-function to initialize. If failure, invokes cleanup.
*
* Function return value: negative to abort module installation.
*/
static int gburst_init(struct gburst_pvt_s *gbprv)
{
u32 gt_sts;
int sts;
gt_sts = read_PWRGT_STS_simple();
if (!(gt_sts & PWRGT_STS_BURST_SUPPORT_PRESENT_BIT)) {
printk(GBURST_ALERT "gpu burst mode is not supported\n");
return -ENODEV;
}
printk(GBURST_ALERT "gpu burst mode initialization -- begin\n");
mutex_init(&gbprv->gbp_mutex_pwrgt_sts);
mutex_init(&gbprv->gbp_state_times_mutex);
gbprv->gbp_request_disable = 0;
gbprv->gbp_request_enable = mprm_enable;
gbprv->gbp_enable = gbprv->gbp_request_enable &&
!gbprv->gbp_request_disable;
gburst_debug_msg_on = 0;
/* -1 indicates that no utilization value has been seen. */
gbprv->gbp_utilization_percentage = -1;
/* -1 indicates no override is in effect. */
gbprv->gbp_utilization_override = -1;
/* No 3D activity is initialized to run until kernel finish
* its initialization. so here make the gburst status consistent
* with HW
* */
gbprv->gbp_suspended = 1;
read_PWRGT_CNT_toggle(gbprv);
{ /* Not a shared interrupt, so no IRQF_SHARED. */
const unsigned long request_flags = IRQF_TRIGGER_RISING;
sts = request_irq(GBURST_IRQ_LEVEL, gburst_irq_handler,
request_flags, GBURST_DRIVER_NAME, gbprv);
if (sts != 0) {
printk(GBURST_ALERT "Interrupt assignment failed: %d\n",
GBURST_IRQ_LEVEL);
sts = -ENXIO;
goto err_interrupt;
}
}
/* Set default values for GPU burst control and counters monitored */
gbprv->gbp_burst_th_high = GBURST_THRESHOLD_DEFAULT_HIGH;
gbprv->gbp_thold_via = GBP_THOLD_VIA_DOWN_DIFF;
gbprv->gbp_burst_th_down_diff = GBURST_THRESHOLD_DEFAULT_DOWN_DIFF;
threshold_derive_either(gbprv);
gbprv->gbp_hrt_period = ktime_set(0,
GBURST_TIMER_PERIOD_DEFAULT_USECS * NSEC_PER_USEC);
{
struct gburst_interface_s gd_interface;
gd_interface.gbs_priv = gbprv;
gd_interface.gbs_power_state_set = gburst_power_state_set;
gburst_interface_set_data(&gd_interface);
}
gbprv->gbp_cooldv_state_override = -1;
/* Burst dynamic control parameters*/
gbprv->gbp_resume_time = 0;
gbprv->gbp_offscreen_rendering = 0;
gbprv->gbp_num_of_vsync_limited_frames = 0;
/* Initialize timer. This does not start the timer. */
hrtimer_init(&gbprv->gbp_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
gbprv->gbp_timer.function = hrt_event_processor;
{
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,
};
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, gbprv, &tcd_ops);
if (IS_ERR(tcdhdl)) {
printk(GBURST_ALERT "Cooling device registration failed: %ld\n",
-PTR_ERR(tcdhdl));
sts = PTR_ERR(tcdhdl);
goto err_thermal;
}
gbprv->gbp_cooldv_hdl = tcdhdl;
}
if (gbprv->gbp_enable) {
sts = work_thread_create(gbprv);
if (sts < 0) {
/* abort init if unable to create thread. */
printk(GBURST_ALERT "Thread creation failed: %d\n",
-sts);
goto err_thread_creation;
}
}
/**
* Ensure that all preceding stores are realized before
* any following stores.
*/
smp_wmb();
gbprv->gbp_initialized = 1;
smp_wmb();
pfs_init(gbprv);
printk(GBURST_ALERT "gpu burst mode initialization -- done\n");
return 0;
err_thread_creation:
thermal_cooling_device_unregister(gbprv->gbp_cooldv_hdl);
gbprv->gbp_cooldv_hdl = NULL;
err_thermal:
{
struct gburst_interface_s gd_interface;
memset(&gd_interface, 0, sizeof(struct gburst_interface_s));
gburst_interface_set_data(&gd_interface);
}
free_irq(GBURST_IRQ_LEVEL, gbprv);
err_interrupt:
mutex_destroy(&gbprv->gbp_state_times_mutex);
mutex_destroy(&gbprv->gbp_mutex_pwrgt_sts);
return sts;
}
/**
* gburst_module_init() - Classic module init function.
*
* Calls lower-level initialization.
*
* Function return value: negative to abort module installation.
*/
int gburst_module_init(void)
{
struct gburst_pvt_s *gbprv;
int rva;
memset(&gburst_private_data, 0, sizeof(gburst_private_data));
gburst_private_ptr = &gburst_private_data;
gbprv = gburst_private_ptr;
rva = gburst_init(gbprv);
return rva;
}
EXPORT_SYMBOL(gburst_module_init);
/**
* gburst_module_exit() - Classic module exit function.
*/
void __exit gburst_module_exit(void)
{
gburst_cleanup(gburst_private_ptr);
}
EXPORT_SYMBOL(gburst_module_exit);
#if 0
#if (defined MODULE)
module_init(gburst_module_init);
#else
/**
* Ensure that init happens for this module after init happens for the
* graphics driver, which in turn is observed to potentially be done late
* via late_initcall (which is in order right before the late_initcall_sync
* that this module may use).
*/
late_initcall_sync(gburst_module_init);
#endif
#endif
/* module_exit(gburst_module_exit); */
#endif /* if (defined CONFIG_GPU_BURST) || (defined CONFIG_GPU_BURST_MODULE) */