18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Windfarm PowerMac thermal control. Generic PID helpers
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
68c2ecf20Sopenharmony_ci *                    <benh@kernel.crashing.org>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/types.h>
108c2ecf20Sopenharmony_ci#include <linux/errno.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/string.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include "windfarm_pid.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#undef DEBUG
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#ifdef DEBUG
208c2ecf20Sopenharmony_ci#define DBG(args...)	printk(args)
218c2ecf20Sopenharmony_ci#else
228c2ecf20Sopenharmony_ci#define DBG(args...)	do { } while(0)
238c2ecf20Sopenharmony_ci#endif
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_civoid wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	memset(st, 0, sizeof(struct wf_pid_state));
288c2ecf20Sopenharmony_ci	st->param = *param;
298c2ecf20Sopenharmony_ci	st->first = 1;
308c2ecf20Sopenharmony_ci}
318c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wf_pid_init);
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cis32 wf_pid_run(struct wf_pid_state *st, s32 new_sample)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	s64	error, integ, deriv;
368c2ecf20Sopenharmony_ci	s32	target;
378c2ecf20Sopenharmony_ci	int	i, hlen = st->param.history_len;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	/* Calculate error term */
408c2ecf20Sopenharmony_ci	error = new_sample - st->param.itarget;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	/* Get samples into our history buffer */
438c2ecf20Sopenharmony_ci	if (st->first) {
448c2ecf20Sopenharmony_ci		for (i = 0; i < hlen; i++) {
458c2ecf20Sopenharmony_ci			st->samples[i] = new_sample;
468c2ecf20Sopenharmony_ci			st->errors[i] = error;
478c2ecf20Sopenharmony_ci		}
488c2ecf20Sopenharmony_ci		st->first = 0;
498c2ecf20Sopenharmony_ci		st->index = 0;
508c2ecf20Sopenharmony_ci	} else {
518c2ecf20Sopenharmony_ci		st->index = (st->index + 1) % hlen;
528c2ecf20Sopenharmony_ci		st->samples[st->index] = new_sample;
538c2ecf20Sopenharmony_ci		st->errors[st->index] = error;
548c2ecf20Sopenharmony_ci	}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	/* Calculate integral term */
578c2ecf20Sopenharmony_ci	for (i = 0, integ = 0; i < hlen; i++)
588c2ecf20Sopenharmony_ci		integ += st->errors[(st->index + hlen - i) % hlen];
598c2ecf20Sopenharmony_ci	integ *= st->param.interval;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	/* Calculate derivative term */
628c2ecf20Sopenharmony_ci	deriv = st->errors[st->index] -
638c2ecf20Sopenharmony_ci		st->errors[(st->index + hlen - 1) % hlen];
648c2ecf20Sopenharmony_ci	deriv /= st->param.interval;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	/* Calculate target */
678c2ecf20Sopenharmony_ci	target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd +
688c2ecf20Sopenharmony_ci		  error * (s64)st->param.gp) >> 36);
698c2ecf20Sopenharmony_ci	if (st->param.additive)
708c2ecf20Sopenharmony_ci		target += st->target;
718c2ecf20Sopenharmony_ci	target = max(target, st->param.min);
728c2ecf20Sopenharmony_ci	target = min(target, st->param.max);
738c2ecf20Sopenharmony_ci	st->target = target;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	return st->target;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wf_pid_run);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_civoid wf_cpu_pid_init(struct wf_cpu_pid_state *st,
808c2ecf20Sopenharmony_ci		     struct wf_cpu_pid_param *param)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	memset(st, 0, sizeof(struct wf_cpu_pid_state));
838c2ecf20Sopenharmony_ci	st->param = *param;
848c2ecf20Sopenharmony_ci	st->first = 1;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wf_cpu_pid_init);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cis32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	s64	integ, deriv, prop;
918c2ecf20Sopenharmony_ci	s32	error, target, sval, adj;
928c2ecf20Sopenharmony_ci	int	i, hlen = st->param.history_len;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	/* Calculate error term */
958c2ecf20Sopenharmony_ci	error = st->param.pmaxadj - new_power;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/* Get samples into our history buffer */
988c2ecf20Sopenharmony_ci	if (st->first) {
998c2ecf20Sopenharmony_ci		for (i = 0; i < hlen; i++) {
1008c2ecf20Sopenharmony_ci			st->powers[i] = new_power;
1018c2ecf20Sopenharmony_ci			st->errors[i] = error;
1028c2ecf20Sopenharmony_ci		}
1038c2ecf20Sopenharmony_ci		st->temps[0] = st->temps[1] = new_temp;
1048c2ecf20Sopenharmony_ci		st->first = 0;
1058c2ecf20Sopenharmony_ci		st->index = st->tindex = 0;
1068c2ecf20Sopenharmony_ci	} else {
1078c2ecf20Sopenharmony_ci		st->index = (st->index + 1) % hlen;
1088c2ecf20Sopenharmony_ci		st->powers[st->index] = new_power;
1098c2ecf20Sopenharmony_ci		st->errors[st->index] = error;
1108c2ecf20Sopenharmony_ci		st->tindex = (st->tindex + 1) % 2;
1118c2ecf20Sopenharmony_ci		st->temps[st->tindex] = new_temp;
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	/* Calculate integral term */
1158c2ecf20Sopenharmony_ci	for (i = 0, integ = 0; i < hlen; i++)
1168c2ecf20Sopenharmony_ci		integ += st->errors[(st->index + hlen - i) % hlen];
1178c2ecf20Sopenharmony_ci	integ *= st->param.interval;
1188c2ecf20Sopenharmony_ci	integ *= st->param.gr;
1198c2ecf20Sopenharmony_ci	sval = st->param.tmax - (s32)(integ >> 20);
1208c2ecf20Sopenharmony_ci	adj = min(st->param.ttarget, sval);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	/* Calculate derivative term */
1258c2ecf20Sopenharmony_ci	deriv = st->temps[st->tindex] -
1268c2ecf20Sopenharmony_ci		st->temps[(st->tindex + 2 - 1) % 2];
1278c2ecf20Sopenharmony_ci	deriv /= st->param.interval;
1288c2ecf20Sopenharmony_ci	deriv *= st->param.gd;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/* Calculate proportional term */
1318c2ecf20Sopenharmony_ci	prop = st->last_delta = (new_temp - adj);
1328c2ecf20Sopenharmony_ci	prop *= st->param.gp;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	DBG("deriv: %lx, prop: %lx\n", deriv, prop);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	/* Calculate target */
1378c2ecf20Sopenharmony_ci	target = st->target + (s32)((deriv + prop) >> 36);
1388c2ecf20Sopenharmony_ci	target = max(target, st->param.min);
1398c2ecf20Sopenharmony_ci	target = min(target, st->param.max);
1408c2ecf20Sopenharmony_ci	st->target = target;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return st->target;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wf_cpu_pid_run);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
1478c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PID algorithm for PowerMacs thermal control");
1488c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
149