// SPDX-License-Identifier: GPL-2.0
/*
 * Exynos Mobile CPUFreq
 *
 * Copyright (C) 2021, Intel Corporation
 * Author: Samsung Electronic. S.LSI CPU Part.
 */

#include <trace/events/ems_debug.h>

#include "../sched.h"
#include "ems.h"

static struct cpufreq_hook_data __percpu *cpufreq_hook_data;

/* register call back when cpufreq governor initialized */
void cpufreq_register_hook(int cpu, int (*func_get_next_cap)(struct tp_env *env,
				struct cpumask *cpus, int dst_cpu),
				int (*func_need_slack_timer)(void))
{
	struct cpufreq_hook_data *data = per_cpu_ptr(cpufreq_hook_data, cpu);

	if (!data)
		return;

	data->func_get_next_cap = func_get_next_cap;
	data->func_need_slack_timer = func_need_slack_timer;
}

/* unregister call back when cpufreq governor initialized */
void cpufreq_unregister_hook(int cpu)
{
	struct cpufreq_hook_data *data = per_cpu_ptr(cpufreq_hook_data, cpu);

	if (!data)
		return;

	data->func_get_next_cap = NULL;
	data->func_need_slack_timer = NULL;
}

/******************************************************************************
 *                           Next capacity callback                           *
 ******************************************************************************/
/*
 * default next capacity for cpu selection
 * it computs next capacity without governor specificaions
 */
static int default_get_next_cap(struct tp_env *env,
			struct cpumask *cpus, int dst_cpu)
{
	unsigned long next_cap = 0;
	int cpu;

	for_each_cpu_and(cpu, cpus, cpu_active_mask) {
		unsigned long util;

		if (cpu == dst_cpu) /* util with task */
			util = env->cpu_stat[cpu].util_with;
		else /* util without task */
			util = env->cpu_stat[cpu].util_wo;

		/*
		 * The cpu in the coregroup has same capacity and the
		 * capacity depends on the cpu with biggest utilization.
		 * Find biggest utilization in the coregroup and use it
		 * as max floor to know what capacity the cpu will have.
		 */
		if (util > next_cap)
			next_cap = util;
	}

	return next_cap;
}

/* return next cap */
int cpufreq_get_next_cap(struct tp_env *env,
			struct cpumask *cpus, int dst_cpu)
{
	struct cpufreq_hook_data *data = per_cpu_ptr(cpufreq_hook_data,
						cpumask_any(cpus));

	if (data && data->func_get_next_cap) {
		int cap = data->func_get_next_cap(env, cpus, dst_cpu);

		if (cap > 0)
			return cap;
	}

	return default_get_next_cap(env, cpus, dst_cpu);
}

/******************************************************************************
 *                                Slack timer                                 *
 ******************************************************************************/
static struct timer_list __percpu *slack_timer;

static void slack_timer_add(struct timer_list *timer, int cpu)
{
	struct cpufreq_hook_data *data = per_cpu_ptr(cpufreq_hook_data, cpu);

	if (!data || !data->func_need_slack_timer)
		return;

	if (data->func_need_slack_timer()) {
		/* slack expired time = 20ms */
		timer->expires = jiffies + msecs_to_jiffies(20);
		add_timer_on(timer, cpu);
	}
}

void slack_timer_cpufreq(int cpu, bool idle_start, bool idle_end)
{
	struct timer_list *timer = per_cpu_ptr(slack_timer, cpu);

	if (idle_start == idle_end)
		return;

	if (timer_pending(timer))
		del_timer_sync(timer);

	if (idle_start)
		slack_timer_add(timer, cpu);
}

static void slack_timer_func(struct timer_list *timer)
{
	int this_cpu = smp_processor_id();

	/*
	 * The purpose of slack-timer is to wake up the CPU from IDLE, in order
	 * to decrease its frequency if it is not set to minimum already.
	 *
	 * This is important for platforms where CPU with higher frequencies
	 * consume higher power even at IDLE.
	 */
	trace_slack_timer(this_cpu);

	/* If slack timer is still needed, add slack timer again */
	slack_timer_add(timer, this_cpu);
}

static void slack_timer_init(void)
{
	int cpu;

	slack_timer = alloc_percpu(struct timer_list);

	for_each_cpu(cpu, cpu_possible_mask)
		timer_setup(per_cpu_ptr(slack_timer, cpu),
				slack_timer_func, TIMER_PINNED);
}

/******************************************************************************
 *                                 Freq clamp                                 *
 ******************************************************************************/
#define FCLAMP_MIN	0
#define FCLAMP_MAX	1

struct fclamp {
	struct fclamp_data fclamp_min;
	struct fclamp_data fclamp_max;
} __percpu **fclamp;

#define per_cpu_fc(cpu)		(*per_cpu_ptr(fclamp, cpu))

static int fclamp_monitor_group[CGROUP_COUNT];

static struct fclamp_data boost_fcd;
static int fclamp_boost;

static int fclamp_skip_monitor_group(int boost, int group)
{
	/* Monitor all cgroups during boost. */
	if (boost)
		return 0;

	return !fclamp_monitor_group[group];
}

static int fclamp_can_release(int cpu, struct fclamp_data *fcd, unsigned int boost)
{
	int period;
	int target_period, target_ratio, type, active_ratio = 0;

	target_period = fcd->target_period;
	target_ratio = fcd->target_ratio;
	type = fcd->type;

	/*
	 * Let's traversal consecutive windows of active ratio by target
	 * period to check whether there's a busy or non-busy window.
	 */
	period = mlt_cur_period(cpu);
	while (target_period) {
		int group;

		for (group = 0; group < CGROUP_COUNT; group++) {
			if (fclamp_skip_monitor_group(boost, group))
				continue;

			active_ratio += mlt_art_cgroup_value(cpu,
						period, group);
		}

		trace_fclamp_can_release(cpu, type, period, target_period,
					target_ratio, active_ratio);

		target_period--;
		period = mlt_prev_period(period);
	};

	/* convert ratio normalized to 1024 to percentage and get average */
	active_ratio = active_ratio * 100 / SCHED_CAPACITY_SCALE;
	active_ratio /= fcd->target_period;

	/*
	 * Release fclamp max if cpu is busier than target and
	 * release fclamp min if cpu is more idle than target.
	 */
	if (type == FCLAMP_MAX && active_ratio > target_ratio)
		return 1;
	if (type == FCLAMP_MIN && active_ratio < target_ratio)
		return 1;

	return 0;
}

unsigned int
fclamp_apply(struct cpufreq_policy *policy, unsigned int orig_freq)
{
	struct fclamp *fc = per_cpu_fc(policy->cpu);
	struct fclamp_data *fcd;
	int cpu, type;
	unsigned int new_freq, count = 0, boost = fclamp_boost;

	/* Select fclamp data according to boost or origin util */
	if (boost) {
		/*
		 * If orig freq is same as max freq, it does not need to
		 * handle fclamp boost
		 */
		if (orig_freq == policy->cpuinfo.max_freq)
			return orig_freq;
		fcd = &boost_fcd;
	} else if (orig_freq > fc->fclamp_max.freq)
		fcd = &fc->fclamp_max;
	else if (orig_freq < fc->fclamp_min.freq)
		fcd = &fc->fclamp_min;
	else
		return orig_freq;

	if (!fcd->target_period)
		return orig_freq;

	type = fcd->type;
	new_freq = fcd->freq;

	for_each_cpu(cpu, policy->cpus) {
		/* check whether release clamping or not */
		if (fclamp_can_release(cpu, fcd, boost)) {
			count++;

			if (type == FCLAMP_MAX)
				break;
		}
	}

	/* Release fclamp min when all cpus in policy are idler than target */
	if (type == FCLAMP_MIN && count == cpumask_weight(policy->cpus))
		new_freq = orig_freq;
	/* Release fclamp max when any cpu in policy is busier than target */
	if (type == FCLAMP_MAX && count > 0)
		new_freq = orig_freq;

	new_freq = clamp_val(new_freq, policy->cpuinfo.min_freq,
				       policy->cpuinfo.max_freq);

	trace_fclamp_apply(policy->cpu, orig_freq, new_freq, boost, fcd);

	return new_freq;
}

static int fclamp_emstune_notifier_call(struct notifier_block *nb,
					unsigned long val, void *v)
{
	struct emstune_set *cur_set = (struct emstune_set *)v;
	int i, cpu;

	for_each_possible_cpu(cpu) {
		struct fclamp *fc = per_cpu_fc(cpu);

		fc->fclamp_min.freq = cur_set->fclamp.min_freq[cpu];
		fc->fclamp_min.target_period =
				cur_set->fclamp.min_target_period[cpu];
		fc->fclamp_min.target_ratio =
				cur_set->fclamp.min_target_ratio[cpu];

		fc->fclamp_max.freq = cur_set->fclamp.max_freq[cpu];
		fc->fclamp_max.target_period =
				cur_set->fclamp.max_target_period[cpu];
		fc->fclamp_max.target_ratio =
				cur_set->fclamp.max_target_ratio[cpu];
	}

	for (i = 0; i < CGROUP_COUNT; i++)
		fclamp_monitor_group[i] = cur_set->fclamp.monitor_group[i];

	return NOTIFY_OK;
}

static struct notifier_block fclamp_emstune_notifier = {
	.notifier_call = fclamp_emstune_notifier_call,
};

static int fclamp_sysbusy_notifier_call(struct notifier_block *nb,
					unsigned long val, void *v)
{
	enum sysbusy_state state = *(enum sysbusy_state *)v;

	if (val != SYSBUSY_STATE_CHANGE)
		return NOTIFY_OK;

	fclamp_boost = state;

	return NOTIFY_OK;
}

static struct notifier_block fclamp_sysbusy_notifier = {
	.notifier_call = fclamp_sysbusy_notifier_call,
};

static int fclamp_init(void)
{
	int cpu;

	fclamp = alloc_percpu(struct fclamp *);
	if (!fclamp) {
		pr_err("failed to allocate fclamp\n");
		return -ENOMEM;
	}

	for_each_possible_cpu(cpu) {
		struct fclamp *fc;
		int i;

		if (cpu != cpumask_first(cpu_coregroup_mask(cpu)))
			continue;

		fc = kzalloc(sizeof(struct fclamp), GFP_KERNEL);
		if (!fc)
			return -ENOMEM;

		fc->fclamp_min.type = FCLAMP_MIN;
		fc->fclamp_max.type = FCLAMP_MAX;

		for_each_cpu(i, cpu_coregroup_mask(cpu))
			per_cpu_fc(i) = fc;
	}

	/* monitor topapp as default */
	fclamp_monitor_group[CGROUP_TOPAPP] = 1;

	boost_fcd.freq = INT_MAX;
	boost_fcd.target_period = 1;
	boost_fcd.target_ratio = 80;
	boost_fcd.type = FCLAMP_MIN;

	emstune_register_notifier(&fclamp_emstune_notifier);
	sysbusy_register_notifier(&fclamp_sysbusy_notifier);

	return 0;
}

int cpufreq_init(void)
{
	cpufreq_hook_data = alloc_percpu(struct cpufreq_hook_data);
	if (!cpufreq_hook_data) {
		pr_err("Failed to allocate cpufreq_hook_data\n");
		return -ENOMEM;
	}

	slack_timer_init();
	fclamp_init();

	return 0;
}