diff options
Diffstat (limited to 'drivers/gpu/arm/utgard/linux/mali_devfreq.c')
-rw-r--r-- | drivers/gpu/arm/utgard/linux/mali_devfreq.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/drivers/gpu/arm/utgard/linux/mali_devfreq.c b/drivers/gpu/arm/utgard/linux/mali_devfreq.c new file mode 100644 index 000000000000..b28f489e2cbf --- /dev/null +++ b/drivers/gpu/arm/utgard/linux/mali_devfreq.c @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2011-2017 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. + * + * A copy of the licence is included with the program, and can also be obtained from Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "mali_osk_mali.h" +#include "mali_kernel_common.h" + +#include <linux/clk.h> +#include <linux/devfreq.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#ifdef CONFIG_DEVFREQ_THERMAL +#include <linux/devfreq_cooling.h> +#endif + +#include <linux/version.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) +#include <linux/pm_opp.h> +#else /* Linux >= 3.13 */ +/* In 3.13 the OPP include header file, types, and functions were all + * renamed. Use the old filename for the include, and define the new names to + * the old, when an old kernel is detected. + */ +#include <linux/opp.h> +#define dev_pm_opp opp +#define dev_pm_opp_get_voltage opp_get_voltage +#define dev_pm_opp_get_opp_count opp_get_opp_count +#define dev_pm_opp_find_freq_ceil opp_find_freq_ceil +#endif /* Linux >= 3.13 */ + +#include "mali_pm_metrics.h" + +static int +mali_devfreq_target(struct device *dev, unsigned long *target_freq, u32 flags) +{ + struct mali_device *mdev = dev_get_drvdata(dev); + struct dev_pm_opp *opp; + unsigned long freq = 0; + unsigned long voltage; + int err; + + freq = *target_freq; + + rcu_read_lock(); + opp = devfreq_recommended_opp(dev, &freq, flags); + voltage = dev_pm_opp_get_voltage(opp); + rcu_read_unlock(); + if (IS_ERR_OR_NULL(opp)) { + MALI_PRINT_ERROR(("Failed to get opp (%ld)\n", PTR_ERR(opp))); + return PTR_ERR(opp); + } + + MALI_DEBUG_PRINT(2, ("mali_devfreq_target:set_freq = %lld flags = 0x%x\n", freq, flags)); + /* + * Only update if there is a change of frequency + */ + if (mdev->current_freq == freq) { + *target_freq = freq; + mali_pm_reset_dvfs_utilisation(mdev); + return 0; + } + +#ifdef CONFIG_REGULATOR + if (mdev->regulator && mdev->current_voltage != voltage + && mdev->current_freq < freq) { + err = regulator_set_voltage(mdev->regulator, voltage, voltage); + if (err) { + MALI_PRINT_ERROR(("Failed to increase voltage (%d)\n", err)); + return err; + } + } +#endif + + err = clk_set_rate(mdev->clock, freq); + if (err) { + MALI_PRINT_ERROR(("Failed to set clock %lu (target %lu)\n", freq, *target_freq)); + return err; + } + +#ifdef CONFIG_REGULATOR + if (mdev->regulator && mdev->current_voltage != voltage + && mdev->current_freq > freq) { + err = regulator_set_voltage(mdev->regulator, voltage, voltage); + if (err) { + MALI_PRINT_ERROR(("Failed to decrease voltage (%d)\n", err)); + return err; + } + } +#endif + + *target_freq = freq; + mdev->current_voltage = voltage; + mdev->current_freq = freq; + + mali_pm_reset_dvfs_utilisation(mdev); + + return err; +} + +static int +mali_devfreq_cur_freq(struct device *dev, unsigned long *freq) +{ + struct mali_device *mdev = dev_get_drvdata(dev); + + *freq = mdev->current_freq; + + MALI_DEBUG_PRINT(2, ("mali_devfreq_cur_freq: freq = %d \n", *freq)); + return 0; +} + +static int +mali_devfreq_status(struct device *dev, struct devfreq_dev_status *stat) +{ + struct mali_device *mdev = dev_get_drvdata(dev); + + stat->current_frequency = mdev->current_freq; + + mali_pm_get_dvfs_utilisation(mdev, + &stat->total_time, &stat->busy_time); + + stat->private_data = NULL; + +#ifdef CONFIG_DEVFREQ_THERMAL + memcpy(&mdev->devfreq->last_status, stat, sizeof(*stat)); +#endif + + return 0; +} + +/* setup platform specific opp in platform.c*/ +int __weak setup_opps(void) +{ + return 0; +} + +/* term platform specific opp in platform.c*/ +int __weak term_opps(struct device *dev) +{ + return 0; +} + +static int mali_devfreq_init_freq_table(struct mali_device *mdev, + struct devfreq_dev_profile *dp) +{ + int err, count; + int i = 0; + unsigned long freq = 0; + struct dev_pm_opp *opp; + + err = setup_opps(); + if (err) + return err; + + rcu_read_lock(); + count = dev_pm_opp_get_opp_count(mdev->dev); + if (count < 0) { + rcu_read_unlock(); + return count; + } + rcu_read_unlock(); + + MALI_DEBUG_PRINT(2, ("mali devfreq table count %d\n", count)); + + dp->freq_table = kmalloc_array(count, sizeof(dp->freq_table[0]), + GFP_KERNEL); + if (!dp->freq_table) + return -ENOMEM; + + rcu_read_lock(); + for (i = 0; i < count; i++, freq++) { + opp = dev_pm_opp_find_freq_ceil(mdev->dev, &freq); + if (IS_ERR(opp)) + break; + + dp->freq_table[i] = freq; + MALI_DEBUG_PRINT(2, ("mali devfreq table array[%d] = %d\n", i, freq)); + } + rcu_read_unlock(); + + if (count != i) + MALI_PRINT_ERROR(("Unable to enumerate all OPPs (%d!=%d)\n", + count, i)); + + dp->max_state = i; + + return 0; +} + +static void mali_devfreq_term_freq_table(struct mali_device *mdev) +{ + struct devfreq_dev_profile *dp = mdev->devfreq->profile; + + kfree(dp->freq_table); + term_opps(mdev->dev); +} + +static void mali_devfreq_exit(struct device *dev) +{ + struct mali_device *mdev = dev_get_drvdata(dev); + + mali_devfreq_term_freq_table(mdev); +} + +int mali_devfreq_init(struct mali_device *mdev) +{ +#ifdef CONFIG_DEVFREQ_THERMAL + struct devfreq_cooling_power *callbacks = NULL; + _mali_osk_device_data data; +#endif + struct devfreq_dev_profile *dp; + int err; + + MALI_DEBUG_PRINT(2, ("Init Mali devfreq\n")); + + if (!mdev->clock) + return -ENODEV; + + mdev->current_freq = clk_get_rate(mdev->clock); + + dp = &mdev->devfreq_profile; + + dp->initial_freq = mdev->current_freq; + dp->polling_ms = 100; + dp->target = mali_devfreq_target; + dp->get_dev_status = mali_devfreq_status; + dp->get_cur_freq = mali_devfreq_cur_freq; + dp->exit = mali_devfreq_exit; + + if (mali_devfreq_init_freq_table(mdev, dp)) + return -EFAULT; + + mdev->devfreq = devfreq_add_device(mdev->dev, dp, + "simple_ondemand", NULL); + if (IS_ERR(mdev->devfreq)) { + mali_devfreq_term_freq_table(mdev); + return PTR_ERR(mdev->devfreq); + } + + err = devfreq_register_opp_notifier(mdev->dev, mdev->devfreq); + if (err) { + MALI_PRINT_ERROR(("Failed to register OPP notifier (%d)\n", err)); + goto opp_notifier_failed; + } + +#ifdef CONFIG_DEVFREQ_THERMAL + /* Initilization last_status it will be used when first power allocate called */ + mdev->devfreq->last_status.current_frequency = mdev->current_freq; + + if (_MALI_OSK_ERR_OK == _mali_osk_device_data_get(&data)) { + if (NULL != data.gpu_cooling_ops) { + callbacks = data.gpu_cooling_ops; + MALI_DEBUG_PRINT(2, ("Mali GPU Thermal: Callback handler installed \n")); + } + } + + if (callbacks) { + mdev->devfreq_cooling = of_devfreq_cooling_register_power( + mdev->dev->of_node, + mdev->devfreq, + callbacks); + if (IS_ERR_OR_NULL(mdev->devfreq_cooling)) { + err = PTR_ERR(mdev->devfreq_cooling); + MALI_PRINT_ERROR(("Failed to register cooling device (%d)\n", err)); + goto cooling_failed; + } else { + MALI_DEBUG_PRINT(2, ("Mali GPU Thermal Cooling installed \n")); + } + } +#endif + + return 0; + +#ifdef CONFIG_DEVFREQ_THERMAL +cooling_failed: + devfreq_unregister_opp_notifier(mdev->dev, mdev->devfreq); +#endif /* CONFIG_DEVFREQ_THERMAL */ +opp_notifier_failed: + err = devfreq_remove_device(mdev->devfreq); + if (err) + MALI_PRINT_ERROR(("Failed to terminate devfreq (%d)\n", err)); + else + mdev->devfreq = NULL; + + return err; +} + +void mali_devfreq_term(struct mali_device *mdev) +{ + int err; + + MALI_DEBUG_PRINT(2, ("Term Mali devfreq\n")); + +#ifdef CONFIG_DEVFREQ_THERMAL + devfreq_cooling_unregister(mdev->devfreq_cooling); +#endif + + devfreq_unregister_opp_notifier(mdev->dev, mdev->devfreq); + + err = devfreq_remove_device(mdev->devfreq); + if (err) + MALI_PRINT_ERROR(("Failed to terminate devfreq (%d)\n", err)); + else + mdev->devfreq = NULL; +} |