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

Commit e6bbd976 authored by Lucille Sylvester's avatar Lucille Sylvester Committed by Gerrit - the friendly Code Review server
Browse files

msm: kgsl: Power Optimizer Push-Pop



A new feature for GPU power saving.  Using the output of normal
GPU DCVS delicately try pushing to a slightly lower than recommended
power level.  Pop up immediately if the lower level is non-sustainable.
Use statistic shaping to the point of DDR increase to help maintain
the lower GPU frequency.

Change-Id: I38c187aaf52114664ccea27b2cc3637cd0fd366f
Signed-off-by: default avatarLucille Sylvester <lsylvest@codeaurora.org>
parent 649e8188
Loading
Loading
Loading
Loading
+1 −15
Original line number Diff line number Diff line
/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2015, 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
@@ -358,17 +358,6 @@ static int tz_stop(struct devfreq *devfreq)
	return 0;
}


static int tz_resume(struct devfreq *devfreq)
{
	struct devfreq_dev_profile *profile = devfreq->profile;
	unsigned long freq;

	freq = profile->initial_freq;

	return profile->target(devfreq->dev.parent, &freq, 0);
}

static int tz_suspend(struct devfreq *devfreq)
{
	struct devfreq_msm_adreno_tz_data *priv = devfreq->data;
@@ -403,9 +392,6 @@ static int tz_handler(struct devfreq *devfreq, unsigned int event, void *data)
		break;

	case DEVFREQ_GOV_RESUME:
		result = tz_resume(devfreq);
		break;

	case DEVFREQ_GOV_INTERVAL:
		/* ignored, this governor doesn't use polling */
	default:
+54 −4
Original line number Diff line number Diff line
@@ -188,7 +188,8 @@ static void _ab_buslevel_update(struct kgsl_pwrctrl *pwr,
 * constraint if one exists.
 */
static unsigned int _adjust_pwrlevel(struct kgsl_pwrctrl *pwr, int level,
					struct kgsl_pwr_constraint *pwrc)
					struct kgsl_pwr_constraint *pwrc,
					int popp)
{
	unsigned int max_pwrlevel = max_t(unsigned int, pwr->thermal_pwrlevel,
		pwr->max_pwrlevel);
@@ -211,6 +212,9 @@ static unsigned int _adjust_pwrlevel(struct kgsl_pwrctrl *pwr, int level,
	break;
	}

	if (popp && (max_pwrlevel < pwr->active_pwrlevel))
		max_pwrlevel = pwr->active_pwrlevel;

	if (level < max_pwrlevel)
		return max_pwrlevel;
	if (level > min_pwrlevel)
@@ -360,7 +364,8 @@ void kgsl_pwrctrl_pwrlevel_change(struct kgsl_device *device,
	 * Adjust the power level if required by thermal, max/min,
	 * constraints, etc
	 */
	new_level = _adjust_pwrlevel(pwr, new_level, &pwr->constraint);
	new_level = _adjust_pwrlevel(pwr, new_level, &pwr->constraint,
					device->pwrscale.popp_level);

	/*
	 * If thermal cycling is required and the new level hits the
@@ -401,6 +406,9 @@ void kgsl_pwrctrl_pwrlevel_change(struct kgsl_device *device,
			pwrlevel->gpu_freq);
	/* Change register settings if any AFTER pwrlevel change*/
	kgsl_pwrctrl_pwrlevel_change_settings(device, 1, 0);

	/* Timestamp the frequency change */
	device->pwrscale.freq_change_time = ktime_to_ms(ktime_get());
}
EXPORT_SYMBOL(kgsl_pwrctrl_pwrlevel_change);

@@ -424,7 +432,7 @@ void kgsl_pwrctrl_set_constraint(struct kgsl_device *device,
	if (device == NULL || pwrc == NULL)
		return;
	constraint = _adjust_pwrlevel(&device->pwrctrl,
				device->pwrctrl.active_pwrlevel, pwrc);
				device->pwrctrl.active_pwrlevel, pwrc, 0);
	pwrc_old = &device->pwrctrl.constraint;

	/*
@@ -1040,6 +1048,43 @@ done:
	return count;
}


static ssize_t kgsl_popp_store(struct device *dev,
					struct device_attribute *attr,
					const char *buf, size_t count)
{
	unsigned int val = 0;
	struct kgsl_device *device = kgsl_device_from_dev(dev);
	int ret;

	if (device == NULL)
		return 0;

	ret = kgsl_sysfs_store(buf, &val);
	if (ret)
		return ret;

	mutex_lock(&device->mutex);
	if (val)
		set_bit(POPP_ON, &device->pwrscale.popp_state);
	else
		clear_bit(POPP_ON, &device->pwrscale.popp_state);
	mutex_unlock(&device->mutex);

	return count;
}

static ssize_t kgsl_popp_show(struct device *dev,
					   struct device_attribute *attr,
					   char *buf)
{
	struct kgsl_device *device = kgsl_device_from_dev(dev);
	if (device == NULL)
		return 0;
	return snprintf(buf, PAGE_SIZE, "%d\n",
		test_bit(POPP_ON, &device->pwrscale.popp_state));
}

static DEVICE_ATTR(gpuclk, 0644, kgsl_pwrctrl_gpuclk_show,
	kgsl_pwrctrl_gpuclk_store);
static DEVICE_ATTR(max_gpuclk, 0644, kgsl_pwrctrl_max_gpuclk_show,
@@ -1084,6 +1129,7 @@ static DEVICE_ATTR(bus_split, 0644,
static DEVICE_ATTR(default_pwrlevel, 0644,
	kgsl_pwrctrl_default_pwrlevel_show,
	kgsl_pwrctrl_default_pwrlevel_store);
static DEVICE_ATTR(popp, 0644, kgsl_popp_show, kgsl_popp_store);

static const struct device_attribute *pwrctrl_attr_list[] = {
	&dev_attr_gpuclk,
@@ -1102,6 +1148,7 @@ static const struct device_attribute *pwrctrl_attr_list[] = {
	&dev_attr_force_rail_on,
	&dev_attr_bus_split,
	&dev_attr_default_pwrlevel,
	&dev_attr_popp,
	NULL
};

@@ -1722,8 +1769,11 @@ static int kgsl_pwrctrl_enable(struct kgsl_device *device)
	if (pwr->wakeup_maxpwrlevel) {
		level = pwr->max_pwrlevel;
		pwr->wakeup_maxpwrlevel = 0;
	} else
	} else if (kgsl_popp_check(device)) {
		level = pwr->active_pwrlevel;
	} else {
		level = pwr->default_pwrlevel;
	}

	kgsl_pwrctrl_pwrlevel_change(device, level);

+163 −2
Original line number Diff line number Diff line
@@ -22,6 +22,13 @@
#define FAST_BUS 1
#define SLOW_BUS -1

static struct kgsl_popp popp_param[POPP_MAX] = {
	{0, 0},
	{-5, 20},
	{-5, 0},
	{0, 0},
};

static void do_devfreq_suspend(struct work_struct *work);
static void do_devfreq_resume(struct work_struct *work);
static void do_devfreq_notify(struct work_struct *work);
@@ -108,14 +115,26 @@ EXPORT_SYMBOL(kgsl_pwrscale_busy);
 */
void kgsl_pwrscale_update_stats(struct kgsl_device *device)
{
	struct kgsl_pwrscale *psc = &device->pwrscale;
	BUG_ON(!mutex_is_locked(&device->mutex));

	if (!device->pwrscale.enabled)
	if (!psc->enabled)
		return;

	if (device->state == KGSL_STATE_ACTIVE) {
		struct kgsl_power_stats stats;
		device->ftbl->power_stats(device, &stats);
		if (psc->popp_level) {
			u64 x = stats.busy_time;
			u64 y = stats.ram_time;
			do_div(x, 100);
			do_div(y, 100);
			x *= popp_param[psc->popp_level].gpu_x;
			y *= popp_param[psc->popp_level].ddr_y;
			trace_kgsl_popp_mod(device, x, y);
			stats.busy_time += x;
			stats.ram_time += y;
		}
		device->pwrscale.accum_stats.busy_time += stats.busy_time;
		device->pwrscale.accum_stats.ram_time += stats.ram_time;
		device->pwrscale.accum_stats.ram_wait += stats.ram_wait;
@@ -213,6 +232,145 @@ static int _thermal_adjust(struct kgsl_pwrctrl *pwr, int level)
	return level;
}

/*
 * Use various metrics including level stability, NAP intervals, and
 * overall GPU freq / DDR freq combination to decide if POPP should
 * be activated.
 */
static bool popp_stable(struct kgsl_device *device)
{
	s64 t;
	struct kgsl_pwrctrl *pwr = &device->pwrctrl;
	struct kgsl_pwrscale *psc = &device->pwrscale;

	if (!test_bit(POPP_ON, &psc->popp_state))
		return false;

	/* If running at turbo, min, or already pushed don't change levels */
	if (test_bit(POPP_PUSH, &psc->popp_state) ||
			pwr->active_pwrlevel == 0 ||
			(!psc->popp_level &&
			pwr->active_pwrlevel == pwr->min_pwrlevel))
		return false;

	t = ktime_to_ms(ktime_get());
	if ((device->pwrscale.freq_change_time + STABLE_TIME) < t) {
		device->pwrscale.freq_change_time = t;
		return true;
	}
	return false;
}

bool kgsl_popp_check(struct kgsl_device *device)
{
	int i;
	struct kgsl_pwrscale *psc = &device->pwrscale;
	struct kgsl_pwr_event *e;

	if (!test_bit(POPP_ON, &psc->popp_state))
		return false;
	if (!test_bit(POPP_PUSH, &psc->popp_state))
		return false;
	if (psc->history[KGSL_PWREVENT_STATE].events == NULL) {
		clear_bit(POPP_PUSH, &psc->popp_state);
		return false;
	}

	e = &psc->history[KGSL_PWREVENT_STATE].
			events[psc->history[KGSL_PWREVENT_STATE].index];
	if (e->data == KGSL_STATE_SLUMBER)
		e->duration = ktime_us_delta(ktime_get(), e->start);

	/* If there's been a long SLUMBER in recent history, clear the _PUSH */
	for (i = 0; i < psc->history[KGSL_PWREVENT_STATE].size; i++) {
		e = &psc->history[KGSL_PWREVENT_STATE].events[i];
		if ((e->data == KGSL_STATE_SLUMBER) &&
			 (e->duration > POPP_RESET_TIME)) {
			clear_bit(POPP_PUSH, &psc->popp_state);
			return false;
		}
	}
	return true;
}

/*
 * The GPU has been running at the current frequency for a while.  Attempt
 * to lower the frequency for boarderline cases.
 */
static void popp_trans1(struct kgsl_device *device)
{
	struct kgsl_pwrctrl *pwr = &device->pwrctrl;
	struct kgsl_pwrscale *psc = &device->pwrscale;
	int old_level = psc->popp_level;

	switch (old_level) {
	case 0:
		psc->popp_level = 2;
		kgsl_pwrctrl_pwrlevel_change(device, pwr->active_pwrlevel + 1);
		break;
	case 1:
	case 2:
		psc->popp_level++;
		break;
	case 3:
		set_bit(POPP_PUSH, &psc->popp_state);
		psc->popp_level = 0;
		break;
	case POPP_MAX:
	default:
		psc->popp_level = 0;
		break;
	}

	trace_kgsl_popp_level(device, old_level, psc->popp_level);
}

/*
 * The GPU DCVS algorithm recommends a level change.  Apply any
 * POPP restrictions and update the level accordingly
 */
static int popp_trans2(struct kgsl_device *device, int level)
{
	struct kgsl_pwrctrl *pwr = &device->pwrctrl;
	struct kgsl_pwrscale *psc = &device->pwrscale;
	int old_level = psc->popp_level;

	if (!test_bit(POPP_ON, &psc->popp_state))
		return level;

	clear_bit(POPP_PUSH, &psc->popp_state);
	/* If the governor recommends going down, do it! */
	if (pwr->active_pwrlevel < level) {
		psc->popp_level = 0;
		trace_kgsl_popp_level(device, old_level, psc->popp_level);
		return level;
	}

	switch (psc->popp_level) {
	case 0:
		/* If the feature isn't engaged, go up immediately */
		break;
	case 1:
		/* Turn off mitigation, and go up a level */
		psc->popp_level = 0;
		break;
	case 2:
	case 3:
		/* Try a more aggressive mitigation */
		psc->popp_level--;
		level++;
		break;
	case POPP_MAX:
	default:
		psc->popp_level = 0;
		break;
	}

	trace_kgsl_popp_level(device, old_level, psc->popp_level);

	return level;
}

/*
 * kgsl_devfreq_target - devfreq_dev_profile.target callback
 * @dev: see devfreq.h
@@ -259,11 +417,13 @@ int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags)
				if (pwr->thermal_cycle == CYCLE_ACTIVE)
					level = _thermal_adjust(pwr, i);
				else
					level = i;
					level = popp_trans2(device, i);
				break;
			}
		if (level != pwr->active_pwrlevel)
			kgsl_pwrctrl_pwrlevel_change(device, level);
	} else if (popp_stable(device)) {
		popp_trans1(device);
	}

	*freq = kgsl_pwrctrl_active_freq(pwr);
@@ -617,6 +777,7 @@ int kgsl_pwrscale_init(struct device *dev, const char *governor)
				sizeof(struct kgsl_pwr_event), GFP_KERNEL);
		pwrscale->history[i].type = i;
	}
	set_bit(POPP_ON, &pwrscale->popp_state);

	return 0;
}
+49 −0
Original line number Diff line number Diff line
@@ -27,6 +27,27 @@
#define KGSL_PWREVENT_POPP	3
#define KGSL_PWREVENT_MAX	4

/**
 * Amount of time running at a level to be considered
 * "stable" in msec
 */
#define STABLE_TIME	150

/* Amount of idle time needed to re-set stability in usec */
#define POPP_RESET_TIME	1000000

/* Number of POPP levels */
#define POPP_MAX	4

/* POPP state bits */
#define POPP_ON		BIT(0)
#define POPP_PUSH	BIT(1)

struct kgsl_popp {
	int gpu_x;
	int ddr_y;
};

struct kgsl_power_stats {
	u64 busy_time;
	u64 ram_time;
@@ -46,6 +67,29 @@ struct kgsl_pwr_history {
	unsigned int size;
};

/**
 * struct kgsl_pwrscale - Power scaling settings for a KGSL device
 * @devfreqptr - Pointer to the devfreq device
 * @gpu_profile - GPU profile data for the devfreq device
 * @bus_profile - Bus specific data for the bus devfreq device
 * @freq_table - GPU frequencies for the DCVS algorithm
 * @last_governor - Prior devfreq governor
 * @accum_stats - Accumulated statistics for various frequency calculations
 * @enabled - Whether or not power scaling is enabled
 * @time - Last submitted sample timestamp
 * @on_time - Timestamp when gpu busy begins
 * @freq_change_time - Timestamp of last freq change or popp update
 * @nh - Notifier for the partner devfreq bus device
 * @devfreq_wq - Main devfreq workqueue
 * @devfreq_suspend_ws - Pass device suspension to devfreq
 * @devfreq_resume_ws - Pass device resume to devfreq
 * @devfreq_notify_ws - Notify devfreq to update sampling
 * @next_governor_call - Timestamp after which the governor may be notified of
 * a new sample
 * @history - History of power events with timestamps and durations
 * @popp_level - Current level of POPP mitigation
 * @popp_state - Control state for POPP, on/off, recently pushed, etc
 */
struct kgsl_pwrscale {
	struct devfreq *devfreqptr;
	struct msm_adreno_extended_profile gpu_profile;
@@ -56,6 +100,7 @@ struct kgsl_pwrscale {
	bool enabled;
	ktime_t time;
	s64 on_time;
	s64 freq_change_time;
	struct srcu_notifier_head nh;
	struct workqueue_struct *devfreq_wq;
	struct work_struct devfreq_suspend_ws;
@@ -63,6 +108,8 @@ struct kgsl_pwrscale {
	struct work_struct devfreq_notify_ws;
	ktime_t next_governor_call;
	struct kgsl_pwr_history history[KGSL_PWREVENT_MAX];
	int popp_level;
	unsigned long popp_state;
};

int kgsl_pwrscale_init(struct device *dev, const char *governor);
@@ -85,6 +132,8 @@ int kgsl_busmon_target(struct device *dev, unsigned long *freq, u32 flags);
int kgsl_busmon_get_dev_status(struct device *, struct devfreq_dev_status *);
int kgsl_busmon_get_cur_freq(struct device *dev, unsigned long *freq);

bool kgsl_popp_check(struct kgsl_device *device);


#define KGSL_PWRSCALE_INIT(_priv_data) { \
	.enabled = true, \
+47 −1
Original line number Diff line number Diff line
/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
/* Copyright (c) 2011-2015, 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
@@ -854,6 +854,52 @@ TRACE_EVENT(kgsl_regwrite,
	)
);

TRACE_EVENT(kgsl_popp_level,

	TP_PROTO(struct kgsl_device *device, int level1, int level2),

	TP_ARGS(device, level1, level2),

	TP_STRUCT__entry(
		__string(device_name, device->name)
		__field(int, level1)
		__field(int, level2)
	),

	TP_fast_assign(
		__assign_str(device_name, device->name);
		__entry->level1 = level1;
		__entry->level2 = level2;
	),

	TP_printk(
		"d_name=%s old level=%d new level=%d",
		__get_str(device_name), __entry->level1, __entry->level2)
);

TRACE_EVENT(kgsl_popp_mod,

	TP_PROTO(struct kgsl_device *device, int x, int y),

	TP_ARGS(device, x, y),

	TP_STRUCT__entry(
		__string(device_name, device->name)
		__field(int, x)
		__field(int, y)
	),

	TP_fast_assign(
		__assign_str(device_name, device->name);
		__entry->x = x;
		__entry->y = y;
	),

	TP_printk(
		"d_name=%s GPU busy mod=%d bus busy mod=%d",
		__get_str(device_name), __entry->x, __entry->y)
);

TRACE_EVENT(kgsl_register_event,
		TP_PROTO(unsigned int id, unsigned int timestamp, void *func),
		TP_ARGS(id, timestamp, func),