| /* |
| * |
| * (C) COPYRIGHT 2016-2019 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 licence. |
| * |
| * 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. |
| * |
| * SPDX-License-Identifier: GPL-2.0 |
| * |
| */ |
| #include <linux/thermal.h> |
| #include <linux/devfreq_cooling.h> |
| #include <linux/of.h> |
| #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" |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) |
| #include <linux/pm_opp.h> |
| #else |
| #include <linux/opp.h> |
| #define dev_pm_opp_find_freq_exact opp_find_freq_exact |
| #define dev_pm_opp_get_voltage opp_get_voltage |
| #define dev_pm_opp opp |
| #endif |
| |
| #define KBASE_IPA_FALLBACK_MODEL_NAME "mali-simple-power-model" |
| |
| static const struct kbase_ipa_model_ops *kbase_ipa_all_model_ops[] = { |
| &kbase_simple_ipa_model_ops, |
| &kbase_g71_ipa_model_ops, |
| &kbase_g72_ipa_model_ops, |
| &kbase_g76_ipa_model_ops, |
| &kbase_g52_ipa_model_ops, |
| &kbase_g52_r1_ipa_model_ops, |
| &kbase_g51_ipa_model_ops, |
| &kbase_g77_ipa_model_ops, |
| &kbase_tnax_ipa_model_ops, |
| &kbase_tbex_ipa_model_ops |
| }; |
| |
| 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) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(kbase_ipa_all_model_ops); ++i) { |
| const struct kbase_ipa_model_ops *ops = kbase_ipa_all_model_ops[i]; |
| |
| if (!strcmp(ops->name, name)) |
| return ops; |
| } |
| |
| dev_err(kbdev->dev, "power model \'%s\' not found\n", name); |
| |
| return NULL; |
| } |
| KBASE_EXPORT_TEST_API(kbase_ipa_model_ops_find); |
| |
| const char *kbase_ipa_model_name_from_id(u32 gpu_id) |
| { |
| const u32 prod_id = (gpu_id & GPU_ID_VERSION_PRODUCT_ID) >> |
| GPU_ID_VERSION_PRODUCT_ID_SHIFT; |
| |
| switch (GPU_ID2_MODEL_MATCH_VALUE(prod_id)) { |
| case GPU_ID2_PRODUCT_TMIX: |
| return "mali-g71-power-model"; |
| case GPU_ID2_PRODUCT_THEX: |
| return "mali-g72-power-model"; |
| case GPU_ID2_PRODUCT_TNOX: |
| return "mali-g76-power-model"; |
| case GPU_ID2_PRODUCT_TSIX: |
| return "mali-g51-power-model"; |
| case GPU_ID2_PRODUCT_TGOX: |
| if ((gpu_id & GPU_ID2_VERSION_MAJOR) == |
| (0 << GPU_ID2_VERSION_MAJOR_SHIFT)) |
| /* g52 aliased to g76 power-model's ops */ |
| return "mali-g52-power-model"; |
| else |
| return "mali-g52_r1-power-model"; |
| case GPU_ID2_PRODUCT_TNAX: |
| return "mali-tnax-power-model"; |
| case GPU_ID2_PRODUCT_TTRX: |
| return "mali-g77-power-model"; |
| case GPU_ID2_PRODUCT_TBEX: |
| return "mali-tbex-power-model"; |
| default: |
| return KBASE_IPA_FALLBACK_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) |
| { |
| struct device_node *model_dt_node; |
| char compat_string[64]; |
| |
| snprintf(compat_string, sizeof(compat_string), "arm,%s", |
| model->ops->name); |
| |
| /* 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) { |
| 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, i; |
| struct device_node *model_dt_node = get_model_dt_node(model); |
| char *origin; |
| |
| err = of_property_read_u32_array(model_dt_node, name, 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) |
| snprintf(elem_name, sizeof(elem_name), "%s", name); |
| else |
| snprintf(elem_name, sizeof(elem_name), "%s.%d", |
| name, i); |
| |
| 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); |
| 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) { |
| strncpy(addr, "", size - 1); |
| 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 */ { |
| strncpy(addr, string_prop_value, size - 1); |
| 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 */ |
| u32 gpu_id; |
| |
| gpu_id = kbdev->gpu_props.props.raw_props.gpu_id; |
| model_name = kbase_ipa_model_name_from_id(gpu_id); |
| dev_dbg(kbdev->dev, |
| "Inferring model from GPU ID 0x%x: \'%s\'\n", |
| gpu_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; |
| } |
| |
| 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) |
| */ |
| 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 defined(CONFIG_MALI_PWRSOFT_765) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) |
| 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) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) |
| 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) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)) |
| kbase_release_device(kbdev); |
| #endif |
| |
| return power; |
| } |
| |
| #if defined(CONFIG_MALI_PWRSOFT_765) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) |
| 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; |
| u32 power_coeff = 0, power = 0; |
| int err = 0; |
| #if defined(CONFIG_MALI_PWRSOFT_765) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) |
| 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_coeff); |
| |
| if (!err) |
| power = kbase_scale_dynamic_power(power_coeff, freq, voltage); |
| 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) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)) |
| kbase_release_device(kbdev); |
| #endif |
| |
| return power; |
| } |
| |
| int kbase_get_real_power_locked(struct kbase_device *kbdev, u32 *power, |
| unsigned long freq, |
| unsigned long voltage) |
| { |
| struct kbase_ipa_model *model; |
| u32 power_coeff = 0; |
| int err = 0; |
| struct kbasep_pm_metrics diff; |
| u64 total_time; |
| |
| 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_coeff); |
| |
| /* 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) { |
| model = kbdev->ipa.fallback_model; |
| err = model->ops->get_dynamic_coeff(model, &power_coeff); |
| } |
| |
| if (err) |
| return err; |
| |
| *power = kbase_scale_dynamic_power(power_coeff, freq, voltage); |
| |
| /* 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, voltage); |
| |
| 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); |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) |
| struct devfreq_cooling_ops kbase_ipa_power_model_ops = { |
| #else |
| struct devfreq_cooling_power kbase_ipa_power_model_ops = { |
| #endif |
| .get_static_power = &kbase_get_static_power, |
| .get_dynamic_power = &kbase_get_dynamic_power, |
| #if defined(CONFIG_MALI_PWRSOFT_765) || \ |
| LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) |
| .get_real_power = &kbase_get_real_power, |
| #endif |
| }; |
| KBASE_EXPORT_TEST_API(kbase_ipa_power_model_ops); |