// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2016-2023 ARM Limited. All rights reserved. * * This program is free software and is provided to you under the terms of the * GNU General Public License version 2 as published by the Free Software * Foundation, and any use by you of this program is subject to the terms * of such GNU license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you can access it online at * http://www.gnu.org/licenses/gpl-2.0.html. * */ #include #include #include #include "mali_kbase.h" #include "mali_kbase_ipa.h" #include "mali_kbase_ipa_debugfs.h" #include "mali_kbase_ipa_simple.h" #include "backend/gpu/mali_kbase_pm_internal.h" #include "backend/gpu/mali_kbase_devfreq.h" #include #define KBASE_IPA_FALLBACK_MODEL_NAME "mali-simple-power-model" /* Polling by thermal governor starts when the temperature exceeds the certain * trip point. In order to have meaningful value for the counters, when the * polling starts and first call to kbase_get_real_power() is made, it is * required to reset the counter values every now and then. * It is reasonable to do the reset every second if no polling is being done, * the counter model implementation also assumes max sampling interval of 1 sec. */ #define RESET_INTERVAL_MS ((s64)1000) int kbase_ipa_model_recalculate(struct kbase_ipa_model *model) { int err = 0; lockdep_assert_held(&model->kbdev->ipa.lock); if (model->ops->recalculate) { err = model->ops->recalculate(model); if (err) { dev_err(model->kbdev->dev, "recalculation of power model %s returned error %d\n", model->ops->name, err); } } return err; } const struct kbase_ipa_model_ops *kbase_ipa_model_ops_find(struct kbase_device *kbdev, const char *name) { if (!strcmp(name, kbase_simple_ipa_model_ops.name)) return &kbase_simple_ipa_model_ops; return kbase_ipa_counter_model_ops_find(kbdev, name); } KBASE_EXPORT_TEST_API(kbase_ipa_model_ops_find); const char *kbase_ipa_model_name_from_id(struct kbase_gpu_id_props *gpu_id) { const char *model_name = kbase_ipa_counter_model_name_from_id(gpu_id); if (!model_name) return KBASE_IPA_FALLBACK_MODEL_NAME; else return model_name; } KBASE_EXPORT_TEST_API(kbase_ipa_model_name_from_id); static struct device_node *get_model_dt_node(struct kbase_ipa_model *model, bool dt_required) { struct device_node *model_dt_node = NULL; char compat_string[64]; if (unlikely(!scnprintf(compat_string, sizeof(compat_string), "arm,%s", model->ops->name))) return NULL; /* of_find_compatible_node() will call of_node_put() on the root node, * so take a reference on it first. */ of_node_get(model->kbdev->dev->of_node); model_dt_node = of_find_compatible_node(model->kbdev->dev->of_node, NULL, compat_string); if (!model_dt_node && !model->missing_dt_node_warning) { if (dt_required) dev_warn(model->kbdev->dev, "Couldn't find power_model DT node matching \'%s\'\n", compat_string); model->missing_dt_node_warning = true; } return model_dt_node; } int kbase_ipa_model_add_param_s32(struct kbase_ipa_model *model, const char *name, s32 *addr, size_t num_elems, bool dt_required) { int err = -EINVAL; size_t i; struct device_node *model_dt_node = get_model_dt_node(model, dt_required); char *origin; err = of_property_read_u32_array(model_dt_node, name, (u32 *)addr, num_elems); /* We're done with model_dt_node now, so drop the reference taken in * get_model_dt_node()/of_find_compatible_node(). */ of_node_put(model_dt_node); if (err && dt_required) { memset(addr, 0, sizeof(s32) * num_elems); dev_warn(model->kbdev->dev, "Error %d, no DT entry: %s.%s = %zu*[0]\n", err, model->ops->name, name, num_elems); origin = "zero"; } else if (err && !dt_required) { origin = "default"; } else /* !err */ { origin = "DT"; } /* Create a unique debugfs entry for each element */ for (i = 0; i < num_elems; ++i) { char elem_name[32]; if (num_elems == 1) { if (unlikely(!scnprintf(elem_name, sizeof(elem_name), "%s", name))) { err = -ENOMEM; goto exit; } } else { if (unlikely(!scnprintf(elem_name, sizeof(elem_name), "%s.%d", name, (uint32_t)i))) { err = -ENOMEM; goto exit; } } dev_dbg(model->kbdev->dev, "%s.%s = %d (%s)\n", model->ops->name, elem_name, addr[i], origin); err = kbase_ipa_model_param_add(model, elem_name, &addr[i], sizeof(s32), PARAM_TYPE_S32); if (err) goto exit; } exit: return err; } int kbase_ipa_model_add_param_string(struct kbase_ipa_model *model, const char *name, char *addr, size_t size, bool dt_required) { int err; struct device_node *model_dt_node = get_model_dt_node(model, dt_required); const char *string_prop_value = ""; char *origin; err = of_property_read_string(model_dt_node, name, &string_prop_value); /* We're done with model_dt_node now, so drop the reference taken in * get_model_dt_node()/of_find_compatible_node(). */ of_node_put(model_dt_node); if (err && dt_required) { strscpy(addr, "", size); dev_warn(model->kbdev->dev, "Error %d, no DT entry: %s.%s = \'%s\'\n", err, model->ops->name, name, addr); err = 0; origin = "zero"; } else if (err && !dt_required) { origin = "default"; } else /* !err */ { strscpy(addr, string_prop_value, size); origin = "DT"; } addr[size - 1] = '\0'; dev_dbg(model->kbdev->dev, "%s.%s = \'%s\' (%s)\n", model->ops->name, name, string_prop_value, origin); err = kbase_ipa_model_param_add(model, name, addr, size, PARAM_TYPE_STRING); return err; } void kbase_ipa_term_model(struct kbase_ipa_model *model) { if (!model) return; lockdep_assert_held(&model->kbdev->ipa.lock); if (model->ops->term) model->ops->term(model); kbase_ipa_model_param_free_all(model); kfree(model); } KBASE_EXPORT_TEST_API(kbase_ipa_term_model); struct kbase_ipa_model *kbase_ipa_init_model(struct kbase_device *kbdev, const struct kbase_ipa_model_ops *ops) { struct kbase_ipa_model *model; int err; lockdep_assert_held(&kbdev->ipa.lock); if (!ops || !ops->name) return NULL; model = kzalloc(sizeof(struct kbase_ipa_model), GFP_KERNEL); if (!model) return NULL; model->kbdev = kbdev; model->ops = ops; INIT_LIST_HEAD(&model->params); err = model->ops->init(model); if (err) { dev_err(kbdev->dev, "init of power model \'%s\' returned error %d\n", ops->name, err); kfree(model); return NULL; } err = kbase_ipa_model_recalculate(model); if (err) { kbase_ipa_term_model(model); return NULL; } return model; } KBASE_EXPORT_TEST_API(kbase_ipa_init_model); static void kbase_ipa_term_locked(struct kbase_device *kbdev) { lockdep_assert_held(&kbdev->ipa.lock); /* Clean up the models */ if (kbdev->ipa.configured_model != kbdev->ipa.fallback_model) kbase_ipa_term_model(kbdev->ipa.configured_model); kbase_ipa_term_model(kbdev->ipa.fallback_model); kbdev->ipa.configured_model = NULL; kbdev->ipa.fallback_model = NULL; } int kbase_ipa_init(struct kbase_device *kbdev) { const char *model_name; const struct kbase_ipa_model_ops *ops; struct kbase_ipa_model *default_model = NULL; int err; mutex_init(&kbdev->ipa.lock); /* * Lock during init to avoid warnings from lockdep_assert_held (there * shouldn't be any concurrent access yet). */ mutex_lock(&kbdev->ipa.lock); /* The simple IPA model must *always* be present.*/ ops = kbase_ipa_model_ops_find(kbdev, KBASE_IPA_FALLBACK_MODEL_NAME); default_model = kbase_ipa_init_model(kbdev, ops); if (!default_model) { err = -EINVAL; goto end; } kbdev->ipa.fallback_model = default_model; err = of_property_read_string(kbdev->dev->of_node, "ipa-model", &model_name); if (err) { /* Attempt to load a match from GPU-ID */ model_name = kbase_ipa_model_name_from_id(&kbdev->gpu_props.gpu_id); dev_dbg(kbdev->dev, "Inferring model from GPU Product ID 0x%x: \'%s\'\n", kbdev->gpu_props.gpu_id.product_id, model_name); err = 0; } else { dev_dbg(kbdev->dev, "Using ipa-model parameter from DT: \'%s\'\n", model_name); } if (strcmp(KBASE_IPA_FALLBACK_MODEL_NAME, model_name) != 0) { ops = kbase_ipa_model_ops_find(kbdev, model_name); kbdev->ipa.configured_model = kbase_ipa_init_model(kbdev, ops); if (!kbdev->ipa.configured_model) { dev_warn(kbdev->dev, "Failed to initialize ipa-model: \'%s\'\n" "Falling back on default model\n", model_name); kbdev->ipa.configured_model = default_model; } } else { kbdev->ipa.configured_model = default_model; } kbdev->ipa.last_sample_time = ktime_get_raw(); end: if (err) kbase_ipa_term_locked(kbdev); else dev_info(kbdev->dev, "Using configured power model %s, and fallback %s\n", kbdev->ipa.configured_model->ops->name, kbdev->ipa.fallback_model->ops->name); mutex_unlock(&kbdev->ipa.lock); return err; } KBASE_EXPORT_TEST_API(kbase_ipa_init); void kbase_ipa_term(struct kbase_device *kbdev) { mutex_lock(&kbdev->ipa.lock); kbase_ipa_term_locked(kbdev); mutex_unlock(&kbdev->ipa.lock); mutex_destroy(&kbdev->ipa.lock); } KBASE_EXPORT_TEST_API(kbase_ipa_term); /** * kbase_scale_dynamic_power() - Scale a dynamic power coefficient to an OPP * @c: Dynamic model coefficient, in pW/(Hz V^2). Should be in range * 0 < c < 2^26 to prevent overflow. * @freq: Frequency, in Hz. Range: 2^23 < freq < 2^30 (~8MHz to ~1GHz) * @voltage: Voltage, in mV. Range: 2^9 < voltage < 2^13 (~0.5V to ~8V) * * Keep a record of the approximate range of each value at every stage of the * calculation, to ensure we don't overflow. This makes heavy use of the * approximations 1000 = 2^10 and 1000000 = 2^20, but does the actual * calculations in decimal for increased accuracy. * * Return: Power consumption, in mW. Range: 0 < p < 2^13 (0W to ~8W) */ static u32 kbase_scale_dynamic_power(const u32 c, const u32 freq, const u32 voltage) { /* Range: 2^8 < v2 < 2^16 m(V^2) */ const u32 v2 = (voltage * voltage) / 1000; /* Range: 2^3 < f_MHz < 2^10 MHz */ const u32 f_MHz = freq / 1000000; /* Range: 2^11 < v2f_big < 2^26 kHz V^2 */ const u32 v2f_big = v2 * f_MHz; /* Range: 2^1 < v2f < 2^16 MHz V^2 */ const u32 v2f = v2f_big / 1000; /* Range (working backwards from next line): 0 < v2fc < 2^23 uW. * Must be < 2^42 to avoid overflowing the return value. */ const u64 v2fc = (u64)c * (u64)v2f; /* Range: 0 < v2fc / 1000 < 2^13 mW */ return div_u64(v2fc, 1000); } /** * kbase_scale_static_power() - Scale a static power coefficient to an OPP * @c: Static model coefficient, in uW/V^3. Should be in range * 0 < c < 2^32 to prevent overflow. * @voltage: Voltage, in mV. Range: 2^9 < voltage < 2^13 (~0.5V to ~8V) * * Return: Power consumption, in mW. Range: 0 < p < 2^13 (0W to ~8W) */ static u32 kbase_scale_static_power(const u32 c, const u32 voltage) { /* Range: 2^8 < v2 < 2^16 m(V^2) */ const u32 v2 = (voltage * voltage) / 1000; /* Range: 2^17 < v3_big < 2^29 m(V^2) mV */ const u32 v3_big = v2 * voltage; /* Range: 2^7 < v3 < 2^19 m(V^3) */ const u32 v3 = v3_big / 1000; /* * Range (working backwards from next line): 0 < v3c_big < 2^33 nW. * The result should be < 2^52 to avoid overflowing the return value. */ const u64 v3c_big = (u64)c * (u64)v3; /* Range: 0 < v3c_big / 1000000 < 2^13 mW */ return div_u64(v3c_big, 1000000); } void kbase_ipa_protection_mode_switch_event(struct kbase_device *kbdev) { lockdep_assert_held(&kbdev->hwaccess_lock); /* Record the event of GPU entering protected mode. */ kbdev->ipa_protection_mode_switched = true; } static struct kbase_ipa_model *get_current_model(struct kbase_device *kbdev) { struct kbase_ipa_model *model; unsigned long flags; lockdep_assert_held(&kbdev->ipa.lock); spin_lock_irqsave(&kbdev->hwaccess_lock, flags); if (kbdev->ipa_protection_mode_switched || kbdev->ipa.force_fallback_model) model = kbdev->ipa.fallback_model; else model = kbdev->ipa.configured_model; /* * Having taken cognizance of the fact that whether GPU earlier * protected mode or not, the event can be now reset (if GPU is not * currently in protected mode) so that configured model is used * for the next sample. */ if (!kbdev->protected_mode) kbdev->ipa_protection_mode_switched = false; spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); return model; } static u32 get_static_power_locked(struct kbase_device *kbdev, struct kbase_ipa_model *model, unsigned long voltage) { u32 power = 0; int err; u32 power_coeff; lockdep_assert_held(&model->kbdev->ipa.lock); if (!model->ops->get_static_coeff) model = kbdev->ipa.fallback_model; if (model->ops->get_static_coeff) { err = model->ops->get_static_coeff(model, &power_coeff); if (!err) power = kbase_scale_static_power(power_coeff, (u32)voltage); } return power; } #if KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE #if defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE static unsigned long kbase_get_static_power(struct devfreq *df, unsigned long voltage) #else static unsigned long kbase_get_static_power(unsigned long voltage) #endif { struct kbase_ipa_model *model; u32 power = 0; #if defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE struct kbase_device *kbdev = dev_get_drvdata(&df->dev); #else struct kbase_device *kbdev = kbase_find_device(-1); #endif if (!kbdev) return 0ul; mutex_lock(&kbdev->ipa.lock); model = get_current_model(kbdev); power = get_static_power_locked(kbdev, model, voltage); mutex_unlock(&kbdev->ipa.lock); #if !(defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE) kbase_release_device(kbdev); #endif return power; } #endif /* KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE */ /** * opp_translate_freq_voltage() - Translate nominal OPP frequency from * devicetree into the real frequency for * top-level and shader cores. * @kbdev: Device pointer * @nominal_freq: Nominal frequency in Hz. * @nominal_voltage: Nominal voltage, in mV. * @freqs: Pointer to array of real frequency values. * @volts: Pointer to array of voltages. * * If there are 2 clock domains, then top-level and shader cores can operate * at different frequency and voltage level. The nominal frequency ("opp-hz") * used by devfreq from the devicetree may not be same as the real frequency * at which top-level and shader cores are operating, so a translation is * needed. * Nominal voltage shall always be same as the real voltage for top-level. */ static void opp_translate_freq_voltage(struct kbase_device *kbdev, unsigned long nominal_freq, unsigned long nominal_voltage, unsigned long *freqs, unsigned long *volts) { #if IS_ENABLED(CONFIG_MALI_BIFROST_NO_MALI) /* An arbitrary voltage and frequency value can be chosen for testing * in no mali configuration which may not match with any OPP level. */ CSTD_UNUSED(kbdev); freqs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL] = nominal_freq; volts[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL] = nominal_voltage; freqs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES] = nominal_freq; volts[KBASE_IPA_BLOCK_TYPE_SHADER_CORES] = nominal_voltage; #else u64 core_mask; unsigned int i; CSTD_UNUSED(nominal_voltage); kbase_devfreq_opp_translate(kbdev, nominal_freq, &core_mask, freqs, volts); CSTD_UNUSED(core_mask); /* Convert micro volts to milli volts */ for (i = 0; i < kbdev->nr_clocks; i++) volts[i] /= 1000; if (kbdev->nr_clocks == 1) { freqs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES] = freqs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL]; volts[KBASE_IPA_BLOCK_TYPE_SHADER_CORES] = volts[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL]; } #endif } #if KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE #if defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE static unsigned long kbase_get_dynamic_power(struct devfreq *df, unsigned long freq, unsigned long voltage) #else static unsigned long kbase_get_dynamic_power(unsigned long freq, unsigned long voltage) #endif { struct kbase_ipa_model *model; unsigned long freqs[KBASE_IPA_BLOCK_TYPE_NUM] = { 0 }; unsigned long volts[KBASE_IPA_BLOCK_TYPE_NUM] = { 0 }; u32 power_coeffs[KBASE_IPA_BLOCK_TYPE_NUM] = { 0 }; u32 power = 0; int err = 0; #if defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE struct kbase_device *kbdev = dev_get_drvdata(&df->dev); #else struct kbase_device *kbdev = kbase_find_device(-1); #endif if (!kbdev) return 0ul; mutex_lock(&kbdev->ipa.lock); model = kbdev->ipa.fallback_model; err = model->ops->get_dynamic_coeff(model, power_coeffs); if (!err) { opp_translate_freq_voltage(kbdev, freq, voltage, freqs, volts); power = kbase_scale_dynamic_power(power_coeffs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL], freqs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL], volts[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL]); /* Here unlike kbase_get_real_power(), shader core frequency is * used for the scaling as simple power model is used to obtain * the value of dynamic coefficient (which is a fixed value * retrieved from the device tree). */ power += kbase_scale_dynamic_power(power_coeffs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES], freqs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES], volts[KBASE_IPA_BLOCK_TYPE_SHADER_CORES]); } else dev_err_ratelimited(kbdev->dev, "Model %s returned error code %d\n", model->ops->name, err); mutex_unlock(&kbdev->ipa.lock); #if !(defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE) kbase_release_device(kbdev); #endif return power; } #endif /* KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE */ int kbase_get_real_power_locked(struct kbase_device *kbdev, u32 *power, unsigned long freq, unsigned long voltage) { struct kbase_ipa_model *model; unsigned long freqs[KBASE_IPA_BLOCK_TYPE_NUM] = { 0 }; unsigned long volts[KBASE_IPA_BLOCK_TYPE_NUM] = { 0 }; u32 power_coeffs[KBASE_IPA_BLOCK_TYPE_NUM] = { 0 }; struct kbasep_pm_metrics diff; u64 total_time; bool skip_utilization_scaling = false; int err = 0; lockdep_assert_held(&kbdev->ipa.lock); kbase_pm_get_dvfs_metrics(kbdev, &kbdev->ipa.last_metrics, &diff); model = get_current_model(kbdev); err = model->ops->get_dynamic_coeff(model, power_coeffs); /* If the counter model returns an error (e.g. switching back to * protected mode and failing to read counters, or a counter sample * with too few cycles), revert to the fallback model. */ if (err && model != kbdev->ipa.fallback_model) { /* No meaningful scaling for GPU utilization can be done if * the sampling interval was too long. This is equivalent to * assuming GPU was busy throughout (similar to what is done * during protected mode). */ if (err == -EOVERFLOW) skip_utilization_scaling = true; model = kbdev->ipa.fallback_model; err = model->ops->get_dynamic_coeff(model, power_coeffs); } if (WARN_ON(err)) return err; opp_translate_freq_voltage(kbdev, freq, voltage, freqs, volts); *power = kbase_scale_dynamic_power(power_coeffs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL], freqs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL], volts[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL]); if (power_coeffs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES]) { unsigned long freq = freqs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES]; /* As per the HW team, the top-level frequency needs to be used * for the scaling if the counter based model was used as * counter values are normalized with the GPU_ACTIVE counter * value, which increments at the rate of top-level frequency. */ if (model != kbdev->ipa.fallback_model) freq = freqs[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL]; *power += kbase_scale_dynamic_power(power_coeffs[KBASE_IPA_BLOCK_TYPE_SHADER_CORES], freq, volts[KBASE_IPA_BLOCK_TYPE_SHADER_CORES]); } if (!skip_utilization_scaling) { /* time_busy / total_time cannot be >1, so assigning the 64-bit * result of div_u64 to *power cannot overflow. */ total_time = diff.time_busy + (u64)diff.time_idle; *power = div_u64(*power * (u64)diff.time_busy, max(total_time, 1ull)); } *power += get_static_power_locked(kbdev, model, volts[KBASE_IPA_BLOCK_TYPE_TOP_LEVEL]); return err; } KBASE_EXPORT_TEST_API(kbase_get_real_power_locked); int kbase_get_real_power(struct devfreq *df, u32 *power, unsigned long freq, unsigned long voltage) { int ret; struct kbase_device *kbdev = dev_get_drvdata(&df->dev); if (!kbdev) return -ENODEV; mutex_lock(&kbdev->ipa.lock); ret = kbase_get_real_power_locked(kbdev, power, freq, voltage); mutex_unlock(&kbdev->ipa.lock); return ret; } KBASE_EXPORT_TEST_API(kbase_get_real_power); struct devfreq_cooling_power kbase_ipa_power_model_ops = { #if KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE .get_static_power = &kbase_get_static_power, .get_dynamic_power = &kbase_get_dynamic_power, #endif /* KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE */ #if defined(CONFIG_MALI_PWRSOFT_765) || KERNEL_VERSION(4, 10, 0) <= LINUX_VERSION_CODE .get_real_power = &kbase_get_real_power, #endif }; KBASE_EXPORT_TEST_API(kbase_ipa_power_model_ops); void kbase_ipa_reset_data(struct kbase_device *kbdev) { ktime_t now, diff; s64 elapsed_time; mutex_lock(&kbdev->ipa.lock); now = ktime_get_raw(); diff = ktime_sub(now, kbdev->ipa.last_sample_time); elapsed_time = ktime_to_ms(diff); if (elapsed_time > RESET_INTERVAL_MS) { struct kbasep_pm_metrics diff; struct kbase_ipa_model *model; kbase_pm_get_dvfs_metrics(kbdev, &kbdev->ipa.last_metrics, &diff); model = get_current_model(kbdev); if (model != kbdev->ipa.fallback_model) model->ops->reset_counter_data(model); kbdev->ipa.last_sample_time = ktime_get_raw(); } mutex_unlock(&kbdev->ipa.lock); }