brintos

brintos / linux-shallow public Read only

0
0
Text · 7.4 KiB · c6d3c32 Raw
284 lines · c
1// SPDX-License-Identifier: GPL-2.0 or MIT2/* Copyright 2019 Collabora ltd. */3 4#include <linux/clk.h>5#include <linux/devfreq.h>6#include <linux/devfreq_cooling.h>7#include <linux/platform_device.h>8#include <linux/pm_opp.h>9 10#include <drm/drm_managed.h>11 12#include "panthor_devfreq.h"13#include "panthor_device.h"14 15/**16 * struct panthor_devfreq - Device frequency management17 */18struct panthor_devfreq {19	/** @devfreq: devfreq device. */20	struct devfreq *devfreq;21 22	/** @gov_data: Governor data. */23	struct devfreq_simple_ondemand_data gov_data;24 25	/** @busy_time: Busy time. */26	ktime_t busy_time;27 28	/** @idle_time: Idle time. */29	ktime_t idle_time;30 31	/** @time_last_update: Last update time. */32	ktime_t time_last_update;33 34	/** @last_busy_state: True if the GPU was busy last time we updated the state. */35	bool last_busy_state;36 37	/**38	 * @lock: Lock used to protect busy_time, idle_time, time_last_update and39	 * last_busy_state.40	 *41	 * These fields can be accessed concurrently by panthor_devfreq_get_dev_status()42	 * and panthor_devfreq_record_{busy,idle}().43	 */44	spinlock_t lock;45};46 47static void panthor_devfreq_update_utilization(struct panthor_devfreq *pdevfreq)48{49	ktime_t now, last;50 51	now = ktime_get();52	last = pdevfreq->time_last_update;53 54	if (pdevfreq->last_busy_state)55		pdevfreq->busy_time += ktime_sub(now, last);56	else57		pdevfreq->idle_time += ktime_sub(now, last);58 59	pdevfreq->time_last_update = now;60}61 62static int panthor_devfreq_target(struct device *dev, unsigned long *freq,63				  u32 flags)64{65	struct dev_pm_opp *opp;66 67	opp = devfreq_recommended_opp(dev, freq, flags);68	if (IS_ERR(opp))69		return PTR_ERR(opp);70	dev_pm_opp_put(opp);71 72	return dev_pm_opp_set_rate(dev, *freq);73}74 75static void panthor_devfreq_reset(struct panthor_devfreq *pdevfreq)76{77	pdevfreq->busy_time = 0;78	pdevfreq->idle_time = 0;79	pdevfreq->time_last_update = ktime_get();80}81 82static int panthor_devfreq_get_dev_status(struct device *dev,83					  struct devfreq_dev_status *status)84{85	struct panthor_device *ptdev = dev_get_drvdata(dev);86	struct panthor_devfreq *pdevfreq = ptdev->devfreq;87	unsigned long irqflags;88 89	status->current_frequency = clk_get_rate(ptdev->clks.core);90 91	spin_lock_irqsave(&pdevfreq->lock, irqflags);92 93	panthor_devfreq_update_utilization(pdevfreq);94 95	status->total_time = ktime_to_ns(ktime_add(pdevfreq->busy_time,96						   pdevfreq->idle_time));97 98	status->busy_time = ktime_to_ns(pdevfreq->busy_time);99 100	panthor_devfreq_reset(pdevfreq);101 102	spin_unlock_irqrestore(&pdevfreq->lock, irqflags);103 104	drm_dbg(&ptdev->base, "busy %lu total %lu %lu %% freq %lu MHz\n",105		status->busy_time, status->total_time,106		status->busy_time / (status->total_time / 100),107		status->current_frequency / 1000 / 1000);108 109	return 0;110}111 112static struct devfreq_dev_profile panthor_devfreq_profile = {113	.timer = DEVFREQ_TIMER_DELAYED,114	.polling_ms = 50, /* ~3 frames */115	.target = panthor_devfreq_target,116	.get_dev_status = panthor_devfreq_get_dev_status,117};118 119int panthor_devfreq_init(struct panthor_device *ptdev)120{121	/* There's actually 2 regulators (mali and sram), but the OPP core only122	 * supports one.123	 *124	 * We assume the sram regulator is coupled with the mali one and let125	 * the coupling logic deal with voltage updates.126	 */127	static const char * const reg_names[] = { "mali", NULL };128	struct thermal_cooling_device *cooling;129	struct device *dev = ptdev->base.dev;130	struct panthor_devfreq *pdevfreq;131	struct dev_pm_opp *opp;132	unsigned long cur_freq;133	int ret;134 135	pdevfreq = drmm_kzalloc(&ptdev->base, sizeof(*ptdev->devfreq), GFP_KERNEL);136	if (!pdevfreq)137		return -ENOMEM;138 139	ptdev->devfreq = pdevfreq;140 141	ret = devm_pm_opp_set_regulators(dev, reg_names);142	if (ret) {143		if (ret != -EPROBE_DEFER)144			DRM_DEV_ERROR(dev, "Couldn't set OPP regulators\n");145 146		return ret;147	}148 149	ret = devm_pm_opp_of_add_table(dev);150	if (ret)151		return ret;152 153	spin_lock_init(&pdevfreq->lock);154 155	panthor_devfreq_reset(pdevfreq);156 157	cur_freq = clk_get_rate(ptdev->clks.core);158 159	opp = devfreq_recommended_opp(dev, &cur_freq, 0);160	if (IS_ERR(opp))161		return PTR_ERR(opp);162 163	panthor_devfreq_profile.initial_freq = cur_freq;164 165	/* Regulator coupling only takes care of synchronizing/balancing voltage166	 * updates, but the coupled regulator needs to be enabled manually.167	 *168	 * We use devm_regulator_get_enable_optional() and keep the sram supply169	 * enabled until the device is removed, just like we do for the mali170	 * supply, which is enabled when dev_pm_opp_set_opp(dev, opp) is called,171	 * and disabled when the opp_table is torn down, using the devm action.172	 *173	 * If we really care about disabling regulators on suspend, we should:174	 * - use devm_regulator_get_optional() here175	 * - call dev_pm_opp_set_opp(dev, NULL) before leaving this function176	 *   (this disables the regulator passed to the OPP layer)177	 * - call dev_pm_opp_set_opp(dev, NULL) and178	 *   regulator_disable(ptdev->regulators.sram) in179	 *   panthor_devfreq_suspend()180	 * - call dev_pm_opp_set_opp(dev, default_opp) and181	 *   regulator_enable(ptdev->regulators.sram) in182	 *   panthor_devfreq_resume()183	 *184	 * But without knowing if it's beneficial or not (in term of power185	 * consumption), or how much it slows down the suspend/resume steps,186	 * let's just keep regulators enabled for the device lifetime.187	 */188	ret = devm_regulator_get_enable_optional(dev, "sram");189	if (ret && ret != -ENODEV) {190		if (ret != -EPROBE_DEFER)191			DRM_DEV_ERROR(dev, "Couldn't retrieve/enable sram supply\n");192		return ret;193	}194 195	/*196	 * Set the recommend OPP this will enable and configure the regulator197	 * if any and will avoid a switch off by regulator_late_cleanup()198	 */199	ret = dev_pm_opp_set_opp(dev, opp);200	if (ret) {201		DRM_DEV_ERROR(dev, "Couldn't set recommended OPP\n");202		return ret;203	}204 205	dev_pm_opp_put(opp);206 207	/*208	 * Setup default thresholds for the simple_ondemand governor.209	 * The values are chosen based on experiments.210	 */211	pdevfreq->gov_data.upthreshold = 45;212	pdevfreq->gov_data.downdifferential = 5;213 214	pdevfreq->devfreq = devm_devfreq_add_device(dev, &panthor_devfreq_profile,215						    DEVFREQ_GOV_SIMPLE_ONDEMAND,216						    &pdevfreq->gov_data);217	if (IS_ERR(pdevfreq->devfreq)) {218		DRM_DEV_ERROR(dev, "Couldn't initialize GPU devfreq\n");219		ret = PTR_ERR(pdevfreq->devfreq);220		pdevfreq->devfreq = NULL;221		return ret;222	}223 224	cooling = devfreq_cooling_em_register(pdevfreq->devfreq, NULL);225	if (IS_ERR(cooling))226		DRM_DEV_INFO(dev, "Failed to register cooling device\n");227 228	return 0;229}230 231int panthor_devfreq_resume(struct panthor_device *ptdev)232{233	struct panthor_devfreq *pdevfreq = ptdev->devfreq;234 235	if (!pdevfreq->devfreq)236		return 0;237 238	panthor_devfreq_reset(pdevfreq);239 240	return devfreq_resume_device(pdevfreq->devfreq);241}242 243int panthor_devfreq_suspend(struct panthor_device *ptdev)244{245	struct panthor_devfreq *pdevfreq = ptdev->devfreq;246 247	if (!pdevfreq->devfreq)248		return 0;249 250	return devfreq_suspend_device(pdevfreq->devfreq);251}252 253void panthor_devfreq_record_busy(struct panthor_device *ptdev)254{255	struct panthor_devfreq *pdevfreq = ptdev->devfreq;256	unsigned long irqflags;257 258	if (!pdevfreq->devfreq)259		return;260 261	spin_lock_irqsave(&pdevfreq->lock, irqflags);262 263	panthor_devfreq_update_utilization(pdevfreq);264	pdevfreq->last_busy_state = true;265 266	spin_unlock_irqrestore(&pdevfreq->lock, irqflags);267}268 269void panthor_devfreq_record_idle(struct panthor_device *ptdev)270{271	struct panthor_devfreq *pdevfreq = ptdev->devfreq;272	unsigned long irqflags;273 274	if (!pdevfreq->devfreq)275		return;276 277	spin_lock_irqsave(&pdevfreq->lock, irqflags);278 279	panthor_devfreq_update_utilization(pdevfreq);280	pdevfreq->last_busy_state = false;281 282	spin_unlock_irqrestore(&pdevfreq->lock, irqflags);283}284