Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c43f30a8 authored by George Shen's avatar George Shen
Browse files

msm: kgsl: Update thermal level via OPP notifier



Subscribes to OPP framework OPP_EVENT_ENABLE and
OPP_EVENT_DISABLE events. The events will be generated
when thermal engine throttling GPU frequency via
disabling/enabling GPU OPP. KGSL uses the event to
update its internal thermal power level data. The data
will be used to ensure no GPU clients, especially CL
applications request higher GPU Frequency than thermal
engine allows.

Change-Id: Iee86ef8060878942d180a2f313da3f4226123d54
Signed-off-by: default avatarGeorge Shen <sqiao@codeaurora.org>
parent 436b6888
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -166,7 +166,8 @@ static unsigned int _adjust_pwrlevel(struct kgsl_pwrctrl *pwr, int level,
{
	unsigned int max_pwrlevel = max_t(unsigned int, pwr->thermal_pwrlevel,
					pwr->max_pwrlevel);
	unsigned int min_pwrlevel = max_t(unsigned int, pwr->thermal_pwrlevel,
	unsigned int min_pwrlevel = min_t(unsigned int,
					pwr->thermal_pwrlevel_floor,
					pwr->min_pwrlevel);

	switch (pwrc->type) {
@@ -2207,6 +2208,7 @@ int kgsl_pwrctrl_init(struct kgsl_device *device)
	pwr->max_pwrlevel = 0;
	pwr->min_pwrlevel = pwr->num_pwrlevels - 2;
	pwr->thermal_pwrlevel = 0;
	pwr->thermal_pwrlevel_floor = pwr->min_pwrlevel;

	pwr->wakeup_maxpwrlevel = 0;

+5 −1
Original line number Diff line number Diff line
/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -125,9 +125,11 @@ struct kgsl_regulator {
 * @grp_clks - Array of clocks structures that we control
 * @power_flags - Control flags for power
 * @pwrlevels - List of supported power levels
 * @nb - Notifier block to receive GPU OPP change event
 * @active_pwrlevel - The currently active power level
 * @previous_pwrlevel - The power level before transition
 * @thermal_pwrlevel - maximum powerlevel constraint from thermal
 * @thermal_pwrlevel_floor - minimum powerlevel constraint from thermal
 * @default_pwrlevel - device wake up power level
 * @max_pwrlevel - maximum allowable powerlevel per the user
 * @min_pwrlevel - minimum allowable powerlevel per the user
@@ -179,9 +181,11 @@ struct kgsl_pwrctrl {
	unsigned long power_flags;
	unsigned long ctrl_flags;
	struct kgsl_pwrlevel pwrlevels[KGSL_MAX_PWRLEVELS];
	struct notifier_block nb;
	unsigned int active_pwrlevel;
	unsigned int previous_pwrlevel;
	unsigned int thermal_pwrlevel;
	unsigned int thermal_pwrlevel_floor;
	unsigned int default_pwrlevel;
	unsigned int wakeup_maxpwrlevel;
	unsigned int max_pwrlevel;
+128 −15
Original line number Diff line number Diff line
/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -15,6 +15,7 @@
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/devfreq_cooling.h>
#include <linux/pm_opp.h>

#include "kgsl.h"
#include "kgsl_pwrscale.h"
@@ -533,7 +534,6 @@ int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags)
	int level;
	unsigned int i;
	unsigned long cur_freq, rec_freq;
	struct dev_pm_opp *opp;

	if (device == NULL)
		return -ENODEV;
@@ -552,20 +552,7 @@ int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags)
		return 0;
	}

	/*
	 * Thermal framework might have disabled/enabled OPP entries
	 * for mitigation. So find the recommended frequency matching
	 * the available opp entries
	 */
	rcu_read_lock();
	rec_freq = *freq;
	opp = devfreq_recommended_opp(dev, &rec_freq, flags);
	if (IS_ERR(opp)) {
		rcu_read_unlock();
		return PTR_ERR(opp);
	}
	rec_freq = dev_pm_opp_get_freq(opp);
	rcu_read_unlock();

	mutex_lock(&device->mutex);
	cur_freq = kgsl_pwrctrl_active_freq(pwr);
@@ -870,6 +857,125 @@ int kgsl_busmon_get_cur_freq(struct device *dev, unsigned long *freq)
	return 0;
}

/*
 * opp_notify - Callback function registered to receive OPP events.
 * @nb: The notifier block
 * @type: The event type. Two OPP events are expected in this function:
 *      - OPP_EVENT_ENABLE: an GPU OPP is enabled. The in_opp parameter
 *	contains the OPP that is enabled
 *	- OPP_EVENT_DISALBE: an GPU OPP is disabled. The in_opp parameter
 *	contains the OPP that is disabled.
 * @in_opp: the GPU OPP whose status is changed and triggered the event
 *
 * GPU OPP event callback function. The function subscribe GPU OPP status
 * change and update thermal power level accordingly.
 */

static int opp_notify(struct notifier_block *nb,
	unsigned long type, void *in_opp)
{
	int result = -EINVAL, level, min_level, max_level;
	struct kgsl_pwrctrl *pwr = container_of(nb, struct kgsl_pwrctrl, nb);
	struct kgsl_device *device = container_of(pwr,
			struct kgsl_device, pwrctrl);
	struct device *dev = &device->pdev->dev;
	struct dev_pm_opp *opp;
	unsigned long min_freq = 0, max_freq = pwr->pwrlevels[0].gpu_freq;

	if (type != OPP_EVENT_ENABLE && type != OPP_EVENT_DISABLE)
		return result;

	rcu_read_lock();
	opp = dev_pm_opp_find_freq_floor(dev, &max_freq);
	if (IS_ERR(opp)) {
		rcu_read_unlock();
		return PTR_ERR(opp);
	}

	max_freq = dev_pm_opp_get_freq(opp);
	if (!max_freq) {
		rcu_read_unlock();
		return result;
	}

	opp = dev_pm_opp_find_freq_ceil(dev, &min_freq);
	if (IS_ERR(opp))
		min_freq = pwr->pwrlevels[pwr->min_pwrlevel].gpu_freq;

	rcu_read_unlock();

	mutex_lock(&device->mutex);

	max_level = pwr->thermal_pwrlevel;
	min_level = pwr->thermal_pwrlevel_floor;

	/* Thermal limit cannot be lower than lowest non-zero operating freq */
	for (level = 0; level < (pwr->num_pwrlevels - 1); level++)
		if (pwr->pwrlevels[level].gpu_freq == max_freq)
			max_level = level;
		if (pwr->pwrlevels[level].gpu_freq == min_freq)
			min_level = level;


	pwr->thermal_pwrlevel = max_level;
	pwr->thermal_pwrlevel_floor = min_level;

	/* Update the current level using the new limit */
	kgsl_pwrctrl_pwrlevel_change(device, pwr->active_pwrlevel);
	mutex_unlock(&device->mutex);

	return 0;
}

/*
 * kgsl_opp_add_notifier - add a fine grained notifier.
 * @dev: The device
 * @nb: Notifier block that will receive updates.
 *
 * Add a notifier to receive GPU OPP_EVENT_* events
 * from the OPP framework.
 */
static int kgsl_opp_add_notifier(struct device *dev,
		struct notifier_block *nb)
{
	struct srcu_notifier_head *nh;
	int ret = 0;

	rcu_read_lock();
	nh = dev_pm_opp_get_notifier(dev);
	if (IS_ERR(nh))
		ret = PTR_ERR(nh);
	rcu_read_unlock();
	if (!ret)
		ret = srcu_notifier_chain_register(nh, nb);

	return ret;
}

/*
 * kgsl_opp_remove_notifier - remove registered opp event notifier.
 * @dev: The device
 * @nb: Notifier block that will receive updates.
 *
 * Remove gpu notifier that receives GPU OPP_EVENT_* events
 * from the OPP framework.
 */
static int kgsl_opp_remove_notifier(struct device *dev,
		struct notifier_block *nb)
{
	struct srcu_notifier_head *nh;
	int ret = 0;

	rcu_read_lock();
	nh = dev_pm_opp_get_notifier(dev);
	if (IS_ERR(nh))
		ret = PTR_ERR(nh);
	rcu_read_unlock();
	if (!ret)
		ret = srcu_notifier_chain_unregister(nh, nb);

	return ret;
}

/*
 * kgsl_pwrscale_init - Initialize pwrscale.
@@ -900,6 +1006,10 @@ int kgsl_pwrscale_init(struct device *dev, const char *governor)
	gpu_profile = &pwrscale->gpu_profile;
	profile = &pwrscale->gpu_profile.profile;

	pwr->nb.notifier_call = opp_notify;

	kgsl_opp_add_notifier(dev, &pwr->nb);

	srcu_init_notifier_head(&pwrscale->nh);

	profile->initial_freq =
@@ -1053,7 +1163,9 @@ void kgsl_pwrscale_close(struct kgsl_device *device)
{
	int i;
	struct kgsl_pwrscale *pwrscale;
	struct kgsl_pwrctrl *pwr;

	pwr = &device->pwrctrl;
	pwrscale = &device->pwrscale;
	if (!pwrscale->devfreqptr)
		return;
@@ -1068,6 +1180,7 @@ void kgsl_pwrscale_close(struct kgsl_device *device)
	kgsl_midframe = NULL;
	device->pwrscale.devfreqptr = NULL;
	srcu_cleanup_notifier_head(&device->pwrscale.nh);
	kgsl_opp_remove_notifier(&device->pdev->dev, &pwr->nb);
	for (i = 0; i < KGSL_PWREVENT_MAX; i++)
		kfree(pwrscale->history[i].events);
}