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