/************************************************************************** * 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 * Jari Luoma-aho * Jari Nippula */ /** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GBURST_DEBUG 1 #include "utilf.h" #include #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/ * - 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 [:][-]. * Or for CVS/RCS ID version, everything but the number is stripped. * : 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". * : The 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). * : Like , 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: * : 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 .. * 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 .. * 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 * See /sys/class/thermal/cooling_device */ 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) */