162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * spu aware cpufreq governor for the cell processor
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * © Copyright IBM Corporation 2006-2008
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Christian Krafft <krafft@de.ibm.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/cpufreq.h>
1162306a36Sopenharmony_ci#include <linux/sched.h>
1262306a36Sopenharmony_ci#include <linux/sched/loadavg.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/timer.h>
1562306a36Sopenharmony_ci#include <linux/workqueue.h>
1662306a36Sopenharmony_ci#include <linux/atomic.h>
1762306a36Sopenharmony_ci#include <asm/machdep.h>
1862306a36Sopenharmony_ci#include <asm/spu.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define POLL_TIME	100000		/* in µs */
2162306a36Sopenharmony_ci#define EXP		753		/* exp(-1) in fixed-point */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct spu_gov_info_struct {
2462306a36Sopenharmony_ci	unsigned long busy_spus;	/* fixed-point */
2562306a36Sopenharmony_ci	struct cpufreq_policy *policy;
2662306a36Sopenharmony_ci	struct delayed_work work;
2762306a36Sopenharmony_ci	unsigned int poll_int;		/* µs */
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct spu_gov_info_struct, spu_gov_info);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic int calc_freq(struct spu_gov_info_struct *info)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	int cpu;
3462306a36Sopenharmony_ci	int busy_spus;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	cpu = info->policy->cpu;
3762306a36Sopenharmony_ci	busy_spus = atomic_read(&cbe_spu_info[cpu_to_node(cpu)].busy_spus);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	info->busy_spus = calc_load(info->busy_spus, EXP, busy_spus * FIXED_1);
4062306a36Sopenharmony_ci	pr_debug("cpu %d: busy_spus=%d, info->busy_spus=%ld\n",
4162306a36Sopenharmony_ci			cpu, busy_spus, info->busy_spus);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return info->policy->max * info->busy_spus / FIXED_1;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void spu_gov_work(struct work_struct *work)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct spu_gov_info_struct *info;
4962306a36Sopenharmony_ci	int delay;
5062306a36Sopenharmony_ci	unsigned long target_freq;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	info = container_of(work, struct spu_gov_info_struct, work.work);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	/* after cancel_delayed_work_sync we unset info->policy */
5562306a36Sopenharmony_ci	BUG_ON(info->policy == NULL);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	target_freq = calc_freq(info);
5862306a36Sopenharmony_ci	__cpufreq_driver_target(info->policy, target_freq, CPUFREQ_RELATION_H);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	delay = usecs_to_jiffies(info->poll_int);
6162306a36Sopenharmony_ci	schedule_delayed_work_on(info->policy->cpu, &info->work, delay);
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic void spu_gov_init_work(struct spu_gov_info_struct *info)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	int delay = usecs_to_jiffies(info->poll_int);
6762306a36Sopenharmony_ci	INIT_DEFERRABLE_WORK(&info->work, spu_gov_work);
6862306a36Sopenharmony_ci	schedule_delayed_work_on(info->policy->cpu, &info->work, delay);
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic void spu_gov_cancel_work(struct spu_gov_info_struct *info)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	cancel_delayed_work_sync(&info->work);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic int spu_gov_start(struct cpufreq_policy *policy)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	unsigned int cpu = policy->cpu;
7962306a36Sopenharmony_ci	struct spu_gov_info_struct *info = &per_cpu(spu_gov_info, cpu);
8062306a36Sopenharmony_ci	struct spu_gov_info_struct *affected_info;
8162306a36Sopenharmony_ci	int i;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (!cpu_online(cpu)) {
8462306a36Sopenharmony_ci		printk(KERN_ERR "cpu %d is not online\n", cpu);
8562306a36Sopenharmony_ci		return -EINVAL;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (!policy->cur) {
8962306a36Sopenharmony_ci		printk(KERN_ERR "no cpu specified in policy\n");
9062306a36Sopenharmony_ci		return -EINVAL;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/* initialize spu_gov_info for all affected cpus */
9462306a36Sopenharmony_ci	for_each_cpu(i, policy->cpus) {
9562306a36Sopenharmony_ci		affected_info = &per_cpu(spu_gov_info, i);
9662306a36Sopenharmony_ci		affected_info->policy = policy;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	info->poll_int = POLL_TIME;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* setup timer */
10262306a36Sopenharmony_ci	spu_gov_init_work(info);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return 0;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic void spu_gov_stop(struct cpufreq_policy *policy)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	unsigned int cpu = policy->cpu;
11062306a36Sopenharmony_ci	struct spu_gov_info_struct *info = &per_cpu(spu_gov_info, cpu);
11162306a36Sopenharmony_ci	int i;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/* cancel timer */
11462306a36Sopenharmony_ci	spu_gov_cancel_work(info);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* clean spu_gov_info for all affected cpus */
11762306a36Sopenharmony_ci	for_each_cpu (i, policy->cpus) {
11862306a36Sopenharmony_ci		info = &per_cpu(spu_gov_info, i);
11962306a36Sopenharmony_ci		info->policy = NULL;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic struct cpufreq_governor spu_governor = {
12462306a36Sopenharmony_ci	.name = "spudemand",
12562306a36Sopenharmony_ci	.start = spu_gov_start,
12662306a36Sopenharmony_ci	.stop = spu_gov_stop,
12762306a36Sopenharmony_ci	.owner = THIS_MODULE,
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_cicpufreq_governor_init(spu_governor);
13062306a36Sopenharmony_cicpufreq_governor_exit(spu_governor);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
13362306a36Sopenharmony_ciMODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>");
134