18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Windfarm PowerMac thermal control. iMac G5 iSight
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (c) Copyright 2007 Étienne Bersac <bersace@gmail.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Bits & pieces from windfarm_pm81.c by (c) Copyright 2005 Benjamin
88c2ecf20Sopenharmony_ci * Herrenschmidt, IBM Corp. <benh@kernel.crashing.org>
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * PowerMac12,1
118c2ecf20Sopenharmony_ci * ============
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * The algorithm used is the PID control algorithm, used the same way
148c2ecf20Sopenharmony_ci * the published Darwin code does, using the same values that are
158c2ecf20Sopenharmony_ci * present in the Darwin 8.10 snapshot property lists (note however
168c2ecf20Sopenharmony_ci * that none of the code has been re-used, it's a complete
178c2ecf20Sopenharmony_ci * re-implementation
188c2ecf20Sopenharmony_ci *
198c2ecf20Sopenharmony_ci * There is two models using PowerMac12,1. Model 2 is iMac G5 iSight
208c2ecf20Sopenharmony_ci * 17" while Model 3 is iMac G5 20". They do have both the same
218c2ecf20Sopenharmony_ci * controls with a tiny difference. The control-ids of hard-drive-fan
228c2ecf20Sopenharmony_ci * and cpu-fan is swapped.
238c2ecf20Sopenharmony_ci *
248c2ecf20Sopenharmony_ci * Target Correction :
258c2ecf20Sopenharmony_ci *
268c2ecf20Sopenharmony_ci * controls have a target correction calculated as :
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * new_min = ((((average_power * slope) >> 16) + offset) >> 16) + min_value
298c2ecf20Sopenharmony_ci * new_value = max(new_value, max(new_min, 0))
308c2ecf20Sopenharmony_ci *
318c2ecf20Sopenharmony_ci * OD Fan control correction.
328c2ecf20Sopenharmony_ci *
338c2ecf20Sopenharmony_ci * # model_id: 2
348c2ecf20Sopenharmony_ci *   offset		: -19563152
358c2ecf20Sopenharmony_ci *   slope		:  1956315
368c2ecf20Sopenharmony_ci *
378c2ecf20Sopenharmony_ci * # model_id: 3
388c2ecf20Sopenharmony_ci *   offset		: -15650652
398c2ecf20Sopenharmony_ci *   slope		:  1565065
408c2ecf20Sopenharmony_ci *
418c2ecf20Sopenharmony_ci * HD Fan control correction.
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * # model_id: 2
448c2ecf20Sopenharmony_ci *   offset		: -15650652
458c2ecf20Sopenharmony_ci *   slope		:  1565065
468c2ecf20Sopenharmony_ci *
478c2ecf20Sopenharmony_ci * # model_id: 3
488c2ecf20Sopenharmony_ci *   offset		: -19563152
498c2ecf20Sopenharmony_ci *   slope		:  1956315
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * CPU Fan control correction.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * # model_id: 2
548c2ecf20Sopenharmony_ci *   offset		: -25431900
558c2ecf20Sopenharmony_ci *   slope		:  2543190
568c2ecf20Sopenharmony_ci *
578c2ecf20Sopenharmony_ci * # model_id: 3
588c2ecf20Sopenharmony_ci *   offset		: -15650652
598c2ecf20Sopenharmony_ci *   slope		:  1565065
608c2ecf20Sopenharmony_ci *
618c2ecf20Sopenharmony_ci * Target rubber-banding :
628c2ecf20Sopenharmony_ci *
638c2ecf20Sopenharmony_ci * Some controls have a target correction which depends on another
648c2ecf20Sopenharmony_ci * control value. The correction is computed in the following way :
658c2ecf20Sopenharmony_ci *
668c2ecf20Sopenharmony_ci * new_min = ref_value * slope + offset
678c2ecf20Sopenharmony_ci *
688c2ecf20Sopenharmony_ci * ref_value is the value of the reference control. If new_min is
698c2ecf20Sopenharmony_ci * greater than 0, then we correct the target value using :
708c2ecf20Sopenharmony_ci *
718c2ecf20Sopenharmony_ci * new_target = max (new_target, new_min >> 16)
728c2ecf20Sopenharmony_ci *
738c2ecf20Sopenharmony_ci * # model_id : 2
748c2ecf20Sopenharmony_ci *   control	: cpu-fan
758c2ecf20Sopenharmony_ci *   ref	: optical-drive-fan
768c2ecf20Sopenharmony_ci *   offset	: -15650652
778c2ecf20Sopenharmony_ci *   slope	: 1565065
788c2ecf20Sopenharmony_ci *
798c2ecf20Sopenharmony_ci * # model_id : 3
808c2ecf20Sopenharmony_ci *   control	: optical-drive-fan
818c2ecf20Sopenharmony_ci *   ref	: hard-drive-fan
828c2ecf20Sopenharmony_ci *   offset	: -32768000
838c2ecf20Sopenharmony_ci *   slope	: 65536
848c2ecf20Sopenharmony_ci *
858c2ecf20Sopenharmony_ci * In order to have the moste efficient correction with those
868c2ecf20Sopenharmony_ci * dependencies, we must trigger HD loop before OD loop before CPU
878c2ecf20Sopenharmony_ci * loop.
888c2ecf20Sopenharmony_ci *
898c2ecf20Sopenharmony_ci * The various control loops found in Darwin config file are:
908c2ecf20Sopenharmony_ci *
918c2ecf20Sopenharmony_ci * HD Fan control loop.
928c2ecf20Sopenharmony_ci *
938c2ecf20Sopenharmony_ci * # model_id: 2
948c2ecf20Sopenharmony_ci *   control        : hard-drive-fan
958c2ecf20Sopenharmony_ci *   sensor         : hard-drive-temp
968c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
978c2ecf20Sopenharmony_ci *                    G_p = 0x002D70A3
988c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
998c2ecf20Sopenharmony_ci *                    History = 2 entries
1008c2ecf20Sopenharmony_ci *                    Input target = 0x370000
1018c2ecf20Sopenharmony_ci *                    Interval = 5s
1028c2ecf20Sopenharmony_ci *
1038c2ecf20Sopenharmony_ci * # model_id: 3
1048c2ecf20Sopenharmony_ci *   control        : hard-drive-fan
1058c2ecf20Sopenharmony_ci *   sensor         : hard-drive-temp
1068c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1078c2ecf20Sopenharmony_ci *                    G_p = 0x002170A3
1088c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1098c2ecf20Sopenharmony_ci *                    History = 2 entries
1108c2ecf20Sopenharmony_ci *                    Input target = 0x370000
1118c2ecf20Sopenharmony_ci *                    Interval = 5s
1128c2ecf20Sopenharmony_ci *
1138c2ecf20Sopenharmony_ci * OD Fan control loop.
1148c2ecf20Sopenharmony_ci *
1158c2ecf20Sopenharmony_ci * # model_id: 2
1168c2ecf20Sopenharmony_ci *   control        : optical-drive-fan
1178c2ecf20Sopenharmony_ci *   sensor         : optical-drive-temp
1188c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1198c2ecf20Sopenharmony_ci *                    G_p = 0x001FAE14
1208c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1218c2ecf20Sopenharmony_ci *                    History = 2 entries
1228c2ecf20Sopenharmony_ci *                    Input target = 0x320000
1238c2ecf20Sopenharmony_ci *                    Interval = 5s
1248c2ecf20Sopenharmony_ci *
1258c2ecf20Sopenharmony_ci * # model_id: 3
1268c2ecf20Sopenharmony_ci *   control        : optical-drive-fan
1278c2ecf20Sopenharmony_ci *   sensor         : optical-drive-temp
1288c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1298c2ecf20Sopenharmony_ci *                    G_p = 0x001FAE14
1308c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1318c2ecf20Sopenharmony_ci *                    History = 2 entries
1328c2ecf20Sopenharmony_ci *                    Input target = 0x320000
1338c2ecf20Sopenharmony_ci *                    Interval = 5s
1348c2ecf20Sopenharmony_ci *
1358c2ecf20Sopenharmony_ci * GPU Fan control loop.
1368c2ecf20Sopenharmony_ci *
1378c2ecf20Sopenharmony_ci * # model_id: 2
1388c2ecf20Sopenharmony_ci *   control        : hard-drive-fan
1398c2ecf20Sopenharmony_ci *   sensor         : gpu-temp
1408c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1418c2ecf20Sopenharmony_ci *                    G_p = 0x002A6666
1428c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1438c2ecf20Sopenharmony_ci *                    History = 2 entries
1448c2ecf20Sopenharmony_ci *                    Input target = 0x5A0000
1458c2ecf20Sopenharmony_ci *                    Interval = 5s
1468c2ecf20Sopenharmony_ci *
1478c2ecf20Sopenharmony_ci * # model_id: 3
1488c2ecf20Sopenharmony_ci *   control        : cpu-fan
1498c2ecf20Sopenharmony_ci *   sensor         : gpu-temp
1508c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1518c2ecf20Sopenharmony_ci *                    G_p = 0x0010CCCC
1528c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1538c2ecf20Sopenharmony_ci *                    History = 2 entries
1548c2ecf20Sopenharmony_ci *                    Input target = 0x500000
1558c2ecf20Sopenharmony_ci *                    Interval = 5s
1568c2ecf20Sopenharmony_ci *
1578c2ecf20Sopenharmony_ci * KODIAK (aka northbridge) Fan control loop.
1588c2ecf20Sopenharmony_ci *
1598c2ecf20Sopenharmony_ci * # model_id: 2
1608c2ecf20Sopenharmony_ci *   control        : optical-drive-fan
1618c2ecf20Sopenharmony_ci *   sensor         : north-bridge-temp
1628c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1638c2ecf20Sopenharmony_ci *                    G_p = 0x003BD70A
1648c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1658c2ecf20Sopenharmony_ci *                    History = 2 entries
1668c2ecf20Sopenharmony_ci *                    Input target = 0x550000
1678c2ecf20Sopenharmony_ci *                    Interval = 5s
1688c2ecf20Sopenharmony_ci *
1698c2ecf20Sopenharmony_ci * # model_id: 3
1708c2ecf20Sopenharmony_ci *   control        : hard-drive-fan
1718c2ecf20Sopenharmony_ci *   sensor         : north-bridge-temp
1728c2ecf20Sopenharmony_ci *   PID params     : G_d = 0x00000000
1738c2ecf20Sopenharmony_ci *                    G_p = 0x0030F5C2
1748c2ecf20Sopenharmony_ci *                    G_r = 0x00019999
1758c2ecf20Sopenharmony_ci *                    History = 2 entries
1768c2ecf20Sopenharmony_ci *                    Input target = 0x550000
1778c2ecf20Sopenharmony_ci *                    Interval = 5s
1788c2ecf20Sopenharmony_ci *
1798c2ecf20Sopenharmony_ci * CPU Fan control loop.
1808c2ecf20Sopenharmony_ci *
1818c2ecf20Sopenharmony_ci *   control        : cpu-fan
1828c2ecf20Sopenharmony_ci *   sensors        : cpu-temp, cpu-power
1838c2ecf20Sopenharmony_ci *   PID params     : from SDB partition
1848c2ecf20Sopenharmony_ci *
1858c2ecf20Sopenharmony_ci * CPU Slew control loop.
1868c2ecf20Sopenharmony_ci *
1878c2ecf20Sopenharmony_ci *   control        : cpufreq-clamp
1888c2ecf20Sopenharmony_ci *   sensor         : cpu-temp
1898c2ecf20Sopenharmony_ci */
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci#undef	DEBUG
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci#include <linux/types.h>
1948c2ecf20Sopenharmony_ci#include <linux/errno.h>
1958c2ecf20Sopenharmony_ci#include <linux/kernel.h>
1968c2ecf20Sopenharmony_ci#include <linux/delay.h>
1978c2ecf20Sopenharmony_ci#include <linux/slab.h>
1988c2ecf20Sopenharmony_ci#include <linux/init.h>
1998c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
2008c2ecf20Sopenharmony_ci#include <linux/wait.h>
2018c2ecf20Sopenharmony_ci#include <linux/kmod.h>
2028c2ecf20Sopenharmony_ci#include <linux/device.h>
2038c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
2048c2ecf20Sopenharmony_ci#include <asm/prom.h>
2058c2ecf20Sopenharmony_ci#include <asm/machdep.h>
2068c2ecf20Sopenharmony_ci#include <asm/io.h>
2078c2ecf20Sopenharmony_ci#include <asm/sections.h>
2088c2ecf20Sopenharmony_ci#include <asm/smu.h>
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci#include "windfarm.h"
2118c2ecf20Sopenharmony_ci#include "windfarm_pid.h"
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci#define VERSION "0.3"
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic int pm121_mach_model;	/* machine model id */
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci/* Controls & sensors */
2188c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_cpu_power;
2198c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_cpu_temp;
2208c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_cpu_voltage;
2218c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_cpu_current;
2228c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_gpu_temp;
2238c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_north_bridge_temp;
2248c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_hard_drive_temp;
2258c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_optical_drive_temp;
2268c2ecf20Sopenharmony_cistatic struct wf_sensor	*sensor_incoming_air_temp; /* unused ! */
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_cienum {
2298c2ecf20Sopenharmony_ci	FAN_CPU,
2308c2ecf20Sopenharmony_ci	FAN_HD,
2318c2ecf20Sopenharmony_ci	FAN_OD,
2328c2ecf20Sopenharmony_ci	CPUFREQ,
2338c2ecf20Sopenharmony_ci	N_CONTROLS
2348c2ecf20Sopenharmony_ci};
2358c2ecf20Sopenharmony_cistatic struct wf_control *controls[N_CONTROLS] = {};
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci/* Set to kick the control loop into life */
2388c2ecf20Sopenharmony_cistatic int pm121_all_controls_ok, pm121_all_sensors_ok;
2398c2ecf20Sopenharmony_cistatic bool pm121_started;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cienum {
2428c2ecf20Sopenharmony_ci	FAILURE_FAN		= 1 << 0,
2438c2ecf20Sopenharmony_ci	FAILURE_SENSOR		= 1 << 1,
2448c2ecf20Sopenharmony_ci	FAILURE_OVERTEMP	= 1 << 2
2458c2ecf20Sopenharmony_ci};
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci/* All sys loops. Note the HD before the OD loop in order to have it
2488c2ecf20Sopenharmony_ci   run before. */
2498c2ecf20Sopenharmony_cienum {
2508c2ecf20Sopenharmony_ci	LOOP_GPU,		/* control = hd or cpu, but luckily,
2518c2ecf20Sopenharmony_ci				   it doesn't matter */
2528c2ecf20Sopenharmony_ci	LOOP_HD,		/* control = hd */
2538c2ecf20Sopenharmony_ci	LOOP_KODIAK,		/* control = hd or od */
2548c2ecf20Sopenharmony_ci	LOOP_OD,		/* control = od */
2558c2ecf20Sopenharmony_ci	N_LOOPS
2568c2ecf20Sopenharmony_ci};
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_cistatic const char *loop_names[N_LOOPS] = {
2598c2ecf20Sopenharmony_ci	"GPU",
2608c2ecf20Sopenharmony_ci	"HD",
2618c2ecf20Sopenharmony_ci	"KODIAK",
2628c2ecf20Sopenharmony_ci	"OD",
2638c2ecf20Sopenharmony_ci};
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci#define	PM121_NUM_CONFIGS	2
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cistatic unsigned int pm121_failure_state;
2688c2ecf20Sopenharmony_cistatic int pm121_readjust, pm121_skipping;
2698c2ecf20Sopenharmony_cistatic bool pm121_overtemp;
2708c2ecf20Sopenharmony_cistatic s32 average_power;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_cistruct pm121_correction {
2738c2ecf20Sopenharmony_ci	int	offset;
2748c2ecf20Sopenharmony_ci	int	slope;
2758c2ecf20Sopenharmony_ci};
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_cistatic struct pm121_correction corrections[N_CONTROLS][PM121_NUM_CONFIGS] = {
2788c2ecf20Sopenharmony_ci	/* FAN_OD */
2798c2ecf20Sopenharmony_ci	{
2808c2ecf20Sopenharmony_ci		/* MODEL 2 */
2818c2ecf20Sopenharmony_ci		{ .offset	= -19563152,
2828c2ecf20Sopenharmony_ci		  .slope	=  1956315
2838c2ecf20Sopenharmony_ci		},
2848c2ecf20Sopenharmony_ci		/* MODEL 3 */
2858c2ecf20Sopenharmony_ci		{ .offset	= -15650652,
2868c2ecf20Sopenharmony_ci		  .slope	=  1565065
2878c2ecf20Sopenharmony_ci		},
2888c2ecf20Sopenharmony_ci	},
2898c2ecf20Sopenharmony_ci	/* FAN_HD */
2908c2ecf20Sopenharmony_ci	{
2918c2ecf20Sopenharmony_ci		/* MODEL 2 */
2928c2ecf20Sopenharmony_ci		{ .offset	= -15650652,
2938c2ecf20Sopenharmony_ci		  .slope	=  1565065
2948c2ecf20Sopenharmony_ci		},
2958c2ecf20Sopenharmony_ci		/* MODEL 3 */
2968c2ecf20Sopenharmony_ci		{ .offset	= -19563152,
2978c2ecf20Sopenharmony_ci		  .slope	=  1956315
2988c2ecf20Sopenharmony_ci		},
2998c2ecf20Sopenharmony_ci	},
3008c2ecf20Sopenharmony_ci	/* FAN_CPU */
3018c2ecf20Sopenharmony_ci	{
3028c2ecf20Sopenharmony_ci		/* MODEL 2 */
3038c2ecf20Sopenharmony_ci		{ .offset	= -25431900,
3048c2ecf20Sopenharmony_ci		  .slope	=  2543190
3058c2ecf20Sopenharmony_ci		},
3068c2ecf20Sopenharmony_ci		/* MODEL 3 */
3078c2ecf20Sopenharmony_ci		{ .offset	= -15650652,
3088c2ecf20Sopenharmony_ci		  .slope	=  1565065
3098c2ecf20Sopenharmony_ci		},
3108c2ecf20Sopenharmony_ci	},
3118c2ecf20Sopenharmony_ci	/* CPUFREQ has no correction (and is not implemented at all) */
3128c2ecf20Sopenharmony_ci};
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_cistruct pm121_connection {
3158c2ecf20Sopenharmony_ci	unsigned int	control_id;
3168c2ecf20Sopenharmony_ci	unsigned int	ref_id;
3178c2ecf20Sopenharmony_ci	struct pm121_correction	correction;
3188c2ecf20Sopenharmony_ci};
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_cistatic struct pm121_connection pm121_connections[] = {
3218c2ecf20Sopenharmony_ci	/* MODEL 2 */
3228c2ecf20Sopenharmony_ci	{ .control_id	= FAN_CPU,
3238c2ecf20Sopenharmony_ci	  .ref_id	= FAN_OD,
3248c2ecf20Sopenharmony_ci	  { .offset	= -32768000,
3258c2ecf20Sopenharmony_ci	    .slope	=  65536
3268c2ecf20Sopenharmony_ci	  }
3278c2ecf20Sopenharmony_ci	},
3288c2ecf20Sopenharmony_ci	/* MODEL 3 */
3298c2ecf20Sopenharmony_ci	{ .control_id	= FAN_OD,
3308c2ecf20Sopenharmony_ci	  .ref_id	= FAN_HD,
3318c2ecf20Sopenharmony_ci	  { .offset	= -32768000,
3328c2ecf20Sopenharmony_ci	    .slope	=  65536
3338c2ecf20Sopenharmony_ci	  }
3348c2ecf20Sopenharmony_ci	},
3358c2ecf20Sopenharmony_ci};
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci/* pointer to the current model connection */
3388c2ecf20Sopenharmony_cistatic struct pm121_connection *pm121_connection;
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci/*
3418c2ecf20Sopenharmony_ci * ****** System Fans Control Loop ******
3428c2ecf20Sopenharmony_ci *
3438c2ecf20Sopenharmony_ci */
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci/* Since each loop handles only one control and we want to avoid
3468c2ecf20Sopenharmony_ci * writing virtual control, we store the control correction with the
3478c2ecf20Sopenharmony_ci * loop params. Some data are not set, there are common to all loop
3488c2ecf20Sopenharmony_ci * and thus, hardcoded.
3498c2ecf20Sopenharmony_ci */
3508c2ecf20Sopenharmony_cistruct pm121_sys_param {
3518c2ecf20Sopenharmony_ci	/* purely informative since we use mach_model-2 as index */
3528c2ecf20Sopenharmony_ci	int			model_id;
3538c2ecf20Sopenharmony_ci	struct wf_sensor	**sensor; /* use sensor_id instead ? */
3548c2ecf20Sopenharmony_ci	s32			gp, itarget;
3558c2ecf20Sopenharmony_ci	unsigned int		control_id;
3568c2ecf20Sopenharmony_ci};
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_cistatic struct pm121_sys_param
3598c2ecf20Sopenharmony_cipm121_sys_all_params[N_LOOPS][PM121_NUM_CONFIGS] = {
3608c2ecf20Sopenharmony_ci	/* GPU Fan control loop */
3618c2ecf20Sopenharmony_ci	{
3628c2ecf20Sopenharmony_ci		{ .model_id	= 2,
3638c2ecf20Sopenharmony_ci		  .sensor	= &sensor_gpu_temp,
3648c2ecf20Sopenharmony_ci		  .gp		= 0x002A6666,
3658c2ecf20Sopenharmony_ci		  .itarget	= 0x5A0000,
3668c2ecf20Sopenharmony_ci		  .control_id	= FAN_HD,
3678c2ecf20Sopenharmony_ci		},
3688c2ecf20Sopenharmony_ci		{ .model_id	= 3,
3698c2ecf20Sopenharmony_ci		  .sensor	= &sensor_gpu_temp,
3708c2ecf20Sopenharmony_ci		  .gp		= 0x0010CCCC,
3718c2ecf20Sopenharmony_ci		  .itarget	= 0x500000,
3728c2ecf20Sopenharmony_ci		  .control_id	= FAN_CPU,
3738c2ecf20Sopenharmony_ci		},
3748c2ecf20Sopenharmony_ci	},
3758c2ecf20Sopenharmony_ci	/* HD Fan control loop */
3768c2ecf20Sopenharmony_ci	{
3778c2ecf20Sopenharmony_ci		{ .model_id	= 2,
3788c2ecf20Sopenharmony_ci		  .sensor	= &sensor_hard_drive_temp,
3798c2ecf20Sopenharmony_ci		  .gp		= 0x002D70A3,
3808c2ecf20Sopenharmony_ci		  .itarget	= 0x370000,
3818c2ecf20Sopenharmony_ci		  .control_id	= FAN_HD,
3828c2ecf20Sopenharmony_ci		},
3838c2ecf20Sopenharmony_ci		{ .model_id	= 3,
3848c2ecf20Sopenharmony_ci		  .sensor	= &sensor_hard_drive_temp,
3858c2ecf20Sopenharmony_ci		  .gp		= 0x002170A3,
3868c2ecf20Sopenharmony_ci		  .itarget	= 0x370000,
3878c2ecf20Sopenharmony_ci		  .control_id	= FAN_HD,
3888c2ecf20Sopenharmony_ci		},
3898c2ecf20Sopenharmony_ci	},
3908c2ecf20Sopenharmony_ci	/* KODIAK Fan control loop */
3918c2ecf20Sopenharmony_ci	{
3928c2ecf20Sopenharmony_ci		{ .model_id	= 2,
3938c2ecf20Sopenharmony_ci		  .sensor	= &sensor_north_bridge_temp,
3948c2ecf20Sopenharmony_ci		  .gp		= 0x003BD70A,
3958c2ecf20Sopenharmony_ci		  .itarget	= 0x550000,
3968c2ecf20Sopenharmony_ci		  .control_id	= FAN_OD,
3978c2ecf20Sopenharmony_ci		},
3988c2ecf20Sopenharmony_ci		{ .model_id	= 3,
3998c2ecf20Sopenharmony_ci		  .sensor	= &sensor_north_bridge_temp,
4008c2ecf20Sopenharmony_ci		  .gp		= 0x0030F5C2,
4018c2ecf20Sopenharmony_ci		  .itarget	= 0x550000,
4028c2ecf20Sopenharmony_ci		  .control_id	= FAN_HD,
4038c2ecf20Sopenharmony_ci		},
4048c2ecf20Sopenharmony_ci	},
4058c2ecf20Sopenharmony_ci	/* OD Fan control loop */
4068c2ecf20Sopenharmony_ci	{
4078c2ecf20Sopenharmony_ci		{ .model_id	= 2,
4088c2ecf20Sopenharmony_ci		  .sensor	= &sensor_optical_drive_temp,
4098c2ecf20Sopenharmony_ci		  .gp		= 0x001FAE14,
4108c2ecf20Sopenharmony_ci		  .itarget	= 0x320000,
4118c2ecf20Sopenharmony_ci		  .control_id	= FAN_OD,
4128c2ecf20Sopenharmony_ci		},
4138c2ecf20Sopenharmony_ci		{ .model_id	= 3,
4148c2ecf20Sopenharmony_ci		  .sensor	= &sensor_optical_drive_temp,
4158c2ecf20Sopenharmony_ci		  .gp		= 0x001FAE14,
4168c2ecf20Sopenharmony_ci		  .itarget	= 0x320000,
4178c2ecf20Sopenharmony_ci		  .control_id	= FAN_OD,
4188c2ecf20Sopenharmony_ci		},
4198c2ecf20Sopenharmony_ci	},
4208c2ecf20Sopenharmony_ci};
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci/* the hardcoded values */
4238c2ecf20Sopenharmony_ci#define	PM121_SYS_GD		0x00000000
4248c2ecf20Sopenharmony_ci#define	PM121_SYS_GR		0x00019999
4258c2ecf20Sopenharmony_ci#define	PM121_SYS_HISTORY_SIZE	2
4268c2ecf20Sopenharmony_ci#define	PM121_SYS_INTERVAL	5
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci/* State data used by the system fans control loop
4298c2ecf20Sopenharmony_ci */
4308c2ecf20Sopenharmony_cistruct pm121_sys_state {
4318c2ecf20Sopenharmony_ci	int			ticks;
4328c2ecf20Sopenharmony_ci	s32			setpoint;
4338c2ecf20Sopenharmony_ci	struct wf_pid_state	pid;
4348c2ecf20Sopenharmony_ci};
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_cistruct pm121_sys_state *pm121_sys_state[N_LOOPS] = {};
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci/*
4398c2ecf20Sopenharmony_ci * ****** CPU Fans Control Loop ******
4408c2ecf20Sopenharmony_ci *
4418c2ecf20Sopenharmony_ci */
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_ci#define PM121_CPU_INTERVAL	1
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci/* State data used by the cpu fans control loop
4468c2ecf20Sopenharmony_ci */
4478c2ecf20Sopenharmony_cistruct pm121_cpu_state {
4488c2ecf20Sopenharmony_ci	int			ticks;
4498c2ecf20Sopenharmony_ci	s32			setpoint;
4508c2ecf20Sopenharmony_ci	struct wf_cpu_pid_state	pid;
4518c2ecf20Sopenharmony_ci};
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_cistatic struct pm121_cpu_state *pm121_cpu_state;
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci/*
4588c2ecf20Sopenharmony_ci * ***** Implementation *****
4598c2ecf20Sopenharmony_ci *
4608c2ecf20Sopenharmony_ci */
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci/* correction the value using the output-low-bound correction algo */
4638c2ecf20Sopenharmony_cistatic s32 pm121_correct(s32 new_setpoint,
4648c2ecf20Sopenharmony_ci			 unsigned int control_id,
4658c2ecf20Sopenharmony_ci			 s32 min)
4668c2ecf20Sopenharmony_ci{
4678c2ecf20Sopenharmony_ci	s32 new_min;
4688c2ecf20Sopenharmony_ci	struct pm121_correction *correction;
4698c2ecf20Sopenharmony_ci	correction = &corrections[control_id][pm121_mach_model - 2];
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	new_min = (average_power * correction->slope) >> 16;
4728c2ecf20Sopenharmony_ci	new_min += correction->offset;
4738c2ecf20Sopenharmony_ci	new_min = (new_min >> 16) + min;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	return max3(new_setpoint, new_min, 0);
4768c2ecf20Sopenharmony_ci}
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_cistatic s32 pm121_connect(unsigned int control_id, s32 setpoint)
4798c2ecf20Sopenharmony_ci{
4808c2ecf20Sopenharmony_ci	s32 new_min, value, new_setpoint;
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	if (pm121_connection->control_id == control_id) {
4838c2ecf20Sopenharmony_ci		controls[control_id]->ops->get_value(controls[control_id],
4848c2ecf20Sopenharmony_ci						     &value);
4858c2ecf20Sopenharmony_ci		new_min = value * pm121_connection->correction.slope;
4868c2ecf20Sopenharmony_ci		new_min += pm121_connection->correction.offset;
4878c2ecf20Sopenharmony_ci		if (new_min > 0) {
4888c2ecf20Sopenharmony_ci			new_setpoint = max(setpoint, (new_min >> 16));
4898c2ecf20Sopenharmony_ci			if (new_setpoint != setpoint) {
4908c2ecf20Sopenharmony_ci				pr_debug("pm121: %s depending on %s, "
4918c2ecf20Sopenharmony_ci					 "corrected from %d to %d RPM\n",
4928c2ecf20Sopenharmony_ci					 controls[control_id]->name,
4938c2ecf20Sopenharmony_ci					 controls[pm121_connection->ref_id]->name,
4948c2ecf20Sopenharmony_ci					 (int) setpoint, (int) new_setpoint);
4958c2ecf20Sopenharmony_ci			}
4968c2ecf20Sopenharmony_ci		} else
4978c2ecf20Sopenharmony_ci			new_setpoint = setpoint;
4988c2ecf20Sopenharmony_ci	}
4998c2ecf20Sopenharmony_ci	/* no connection */
5008c2ecf20Sopenharmony_ci	else
5018c2ecf20Sopenharmony_ci		new_setpoint = setpoint;
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	return new_setpoint;
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci/* FAN LOOPS */
5078c2ecf20Sopenharmony_cistatic void pm121_create_sys_fans(int loop_id)
5088c2ecf20Sopenharmony_ci{
5098c2ecf20Sopenharmony_ci	struct pm121_sys_param *param = NULL;
5108c2ecf20Sopenharmony_ci	struct wf_pid_param pid_param;
5118c2ecf20Sopenharmony_ci	struct wf_control *control = NULL;
5128c2ecf20Sopenharmony_ci	int i;
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci	/* First, locate the params for this model */
5158c2ecf20Sopenharmony_ci	for (i = 0; i < PM121_NUM_CONFIGS; i++) {
5168c2ecf20Sopenharmony_ci		if (pm121_sys_all_params[loop_id][i].model_id == pm121_mach_model) {
5178c2ecf20Sopenharmony_ci			param = &(pm121_sys_all_params[loop_id][i]);
5188c2ecf20Sopenharmony_ci			break;
5198c2ecf20Sopenharmony_ci		}
5208c2ecf20Sopenharmony_ci	}
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	/* No params found, put fans to max */
5238c2ecf20Sopenharmony_ci	if (param == NULL) {
5248c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pm121: %s fan config not found "
5258c2ecf20Sopenharmony_ci		       " for this machine model\n",
5268c2ecf20Sopenharmony_ci		       loop_names[loop_id]);
5278c2ecf20Sopenharmony_ci		goto fail;
5288c2ecf20Sopenharmony_ci	}
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci	control = controls[param->control_id];
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	/* Alloc & initialize state */
5338c2ecf20Sopenharmony_ci	pm121_sys_state[loop_id] = kmalloc(sizeof(struct pm121_sys_state),
5348c2ecf20Sopenharmony_ci					   GFP_KERNEL);
5358c2ecf20Sopenharmony_ci	if (pm121_sys_state[loop_id] == NULL) {
5368c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pm121: Memory allocation error\n");
5378c2ecf20Sopenharmony_ci		goto fail;
5388c2ecf20Sopenharmony_ci	}
5398c2ecf20Sopenharmony_ci	pm121_sys_state[loop_id]->ticks = 1;
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	/* Fill PID params */
5428c2ecf20Sopenharmony_ci	pid_param.gd		= PM121_SYS_GD;
5438c2ecf20Sopenharmony_ci	pid_param.gp		= param->gp;
5448c2ecf20Sopenharmony_ci	pid_param.gr		= PM121_SYS_GR;
5458c2ecf20Sopenharmony_ci	pid_param.interval	= PM121_SYS_INTERVAL;
5468c2ecf20Sopenharmony_ci	pid_param.history_len	= PM121_SYS_HISTORY_SIZE;
5478c2ecf20Sopenharmony_ci	pid_param.itarget	= param->itarget;
5488c2ecf20Sopenharmony_ci	if(control)
5498c2ecf20Sopenharmony_ci	{
5508c2ecf20Sopenharmony_ci		pid_param.min		= control->ops->get_min(control);
5518c2ecf20Sopenharmony_ci		pid_param.max		= control->ops->get_max(control);
5528c2ecf20Sopenharmony_ci	} else {
5538c2ecf20Sopenharmony_ci		/*
5548c2ecf20Sopenharmony_ci		 * This is probably not the right!?
5558c2ecf20Sopenharmony_ci		 * Perhaps goto fail  if control == NULL  above?
5568c2ecf20Sopenharmony_ci		 */
5578c2ecf20Sopenharmony_ci		pid_param.min		= 0;
5588c2ecf20Sopenharmony_ci		pid_param.max		= 0;
5598c2ecf20Sopenharmony_ci	}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	wf_pid_init(&pm121_sys_state[loop_id]->pid, &pid_param);
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	pr_debug("pm121: %s Fan control loop initialized.\n"
5648c2ecf20Sopenharmony_ci		 "       itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
5658c2ecf20Sopenharmony_ci		 loop_names[loop_id], FIX32TOPRINT(pid_param.itarget),
5668c2ecf20Sopenharmony_ci		 pid_param.min, pid_param.max);
5678c2ecf20Sopenharmony_ci	return;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci fail:
5708c2ecf20Sopenharmony_ci	/* note that this is not optimal since another loop may still
5718c2ecf20Sopenharmony_ci	   control the same control */
5728c2ecf20Sopenharmony_ci	printk(KERN_WARNING "pm121: failed to set up %s loop "
5738c2ecf20Sopenharmony_ci	       "setting \"%s\" to max speed.\n",
5748c2ecf20Sopenharmony_ci	       loop_names[loop_id], control ? control->name : "uninitialized value");
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci	if (control)
5778c2ecf20Sopenharmony_ci		wf_control_set_max(control);
5788c2ecf20Sopenharmony_ci}
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_cistatic void pm121_sys_fans_tick(int loop_id)
5818c2ecf20Sopenharmony_ci{
5828c2ecf20Sopenharmony_ci	struct pm121_sys_param *param;
5838c2ecf20Sopenharmony_ci	struct pm121_sys_state *st;
5848c2ecf20Sopenharmony_ci	struct wf_sensor *sensor;
5858c2ecf20Sopenharmony_ci	struct wf_control *control;
5868c2ecf20Sopenharmony_ci	s32 temp, new_setpoint;
5878c2ecf20Sopenharmony_ci	int rc;
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	param = &(pm121_sys_all_params[loop_id][pm121_mach_model-2]);
5908c2ecf20Sopenharmony_ci	st = pm121_sys_state[loop_id];
5918c2ecf20Sopenharmony_ci	sensor = *(param->sensor);
5928c2ecf20Sopenharmony_ci	control = controls[param->control_id];
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	if (--st->ticks != 0) {
5958c2ecf20Sopenharmony_ci		if (pm121_readjust)
5968c2ecf20Sopenharmony_ci			goto readjust;
5978c2ecf20Sopenharmony_ci		return;
5988c2ecf20Sopenharmony_ci	}
5998c2ecf20Sopenharmony_ci	st->ticks = PM121_SYS_INTERVAL;
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	rc = sensor->ops->get_value(sensor, &temp);
6028c2ecf20Sopenharmony_ci	if (rc) {
6038c2ecf20Sopenharmony_ci		printk(KERN_WARNING "windfarm: %s sensor error %d\n",
6048c2ecf20Sopenharmony_ci		       sensor->name, rc);
6058c2ecf20Sopenharmony_ci		pm121_failure_state |= FAILURE_SENSOR;
6068c2ecf20Sopenharmony_ci		return;
6078c2ecf20Sopenharmony_ci	}
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	pr_debug("pm121: %s Fan tick ! %s: %d.%03d\n",
6108c2ecf20Sopenharmony_ci		 loop_names[loop_id], sensor->name,
6118c2ecf20Sopenharmony_ci		 FIX32TOPRINT(temp));
6128c2ecf20Sopenharmony_ci
6138c2ecf20Sopenharmony_ci	new_setpoint = wf_pid_run(&st->pid, temp);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	/* correction */
6168c2ecf20Sopenharmony_ci	new_setpoint = pm121_correct(new_setpoint,
6178c2ecf20Sopenharmony_ci				     param->control_id,
6188c2ecf20Sopenharmony_ci				     st->pid.param.min);
6198c2ecf20Sopenharmony_ci	/* linked corretion */
6208c2ecf20Sopenharmony_ci	new_setpoint = pm121_connect(param->control_id, new_setpoint);
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_ci	if (new_setpoint == st->setpoint)
6238c2ecf20Sopenharmony_ci		return;
6248c2ecf20Sopenharmony_ci	st->setpoint = new_setpoint;
6258c2ecf20Sopenharmony_ci	pr_debug("pm121: %s corrected setpoint: %d RPM\n",
6268c2ecf20Sopenharmony_ci		 control->name, (int)new_setpoint);
6278c2ecf20Sopenharmony_ci readjust:
6288c2ecf20Sopenharmony_ci	if (control && pm121_failure_state == 0) {
6298c2ecf20Sopenharmony_ci		rc = control->ops->set_value(control, st->setpoint);
6308c2ecf20Sopenharmony_ci		if (rc) {
6318c2ecf20Sopenharmony_ci			printk(KERN_WARNING "windfarm: %s fan error %d\n",
6328c2ecf20Sopenharmony_ci			       control->name, rc);
6338c2ecf20Sopenharmony_ci			pm121_failure_state |= FAILURE_FAN;
6348c2ecf20Sopenharmony_ci		}
6358c2ecf20Sopenharmony_ci	}
6368c2ecf20Sopenharmony_ci}
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci/* CPU LOOP */
6408c2ecf20Sopenharmony_cistatic void pm121_create_cpu_fans(void)
6418c2ecf20Sopenharmony_ci{
6428c2ecf20Sopenharmony_ci	struct wf_cpu_pid_param pid_param;
6438c2ecf20Sopenharmony_ci	const struct smu_sdbp_header *hdr;
6448c2ecf20Sopenharmony_ci	struct smu_sdbp_cpupiddata *piddata;
6458c2ecf20Sopenharmony_ci	struct smu_sdbp_fvt *fvt;
6468c2ecf20Sopenharmony_ci	struct wf_control *fan_cpu;
6478c2ecf20Sopenharmony_ci	s32 tmax, tdelta, maxpow, powadj;
6488c2ecf20Sopenharmony_ci
6498c2ecf20Sopenharmony_ci	fan_cpu = controls[FAN_CPU];
6508c2ecf20Sopenharmony_ci
6518c2ecf20Sopenharmony_ci	/* First, locate the PID params in SMU SBD */
6528c2ecf20Sopenharmony_ci	hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
6538c2ecf20Sopenharmony_ci	if (hdr == 0) {
6548c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pm121: CPU PID fan config not found.\n");
6558c2ecf20Sopenharmony_ci		goto fail;
6568c2ecf20Sopenharmony_ci	}
6578c2ecf20Sopenharmony_ci	piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	/* Get the FVT params for operating point 0 (the only supported one
6608c2ecf20Sopenharmony_ci	 * for now) in order to get tmax
6618c2ecf20Sopenharmony_ci	 */
6628c2ecf20Sopenharmony_ci	hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
6638c2ecf20Sopenharmony_ci	if (hdr) {
6648c2ecf20Sopenharmony_ci		fvt = (struct smu_sdbp_fvt *)&hdr[1];
6658c2ecf20Sopenharmony_ci		tmax = ((s32)fvt->maxtemp) << 16;
6668c2ecf20Sopenharmony_ci	} else
6678c2ecf20Sopenharmony_ci		tmax = 0x5e0000; /* 94 degree default */
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ci	/* Alloc & initialize state */
6708c2ecf20Sopenharmony_ci	pm121_cpu_state = kmalloc(sizeof(struct pm121_cpu_state),
6718c2ecf20Sopenharmony_ci				  GFP_KERNEL);
6728c2ecf20Sopenharmony_ci	if (pm121_cpu_state == NULL)
6738c2ecf20Sopenharmony_ci		goto fail;
6748c2ecf20Sopenharmony_ci	pm121_cpu_state->ticks = 1;
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci	/* Fill PID params */
6778c2ecf20Sopenharmony_ci	pid_param.interval = PM121_CPU_INTERVAL;
6788c2ecf20Sopenharmony_ci	pid_param.history_len = piddata->history_len;
6798c2ecf20Sopenharmony_ci	if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
6808c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pm121: History size overflow on "
6818c2ecf20Sopenharmony_ci		       "CPU control loop (%d)\n", piddata->history_len);
6828c2ecf20Sopenharmony_ci		pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
6838c2ecf20Sopenharmony_ci	}
6848c2ecf20Sopenharmony_ci	pid_param.gd = piddata->gd;
6858c2ecf20Sopenharmony_ci	pid_param.gp = piddata->gp;
6868c2ecf20Sopenharmony_ci	pid_param.gr = piddata->gr / pid_param.history_len;
6878c2ecf20Sopenharmony_ci
6888c2ecf20Sopenharmony_ci	tdelta = ((s32)piddata->target_temp_delta) << 16;
6898c2ecf20Sopenharmony_ci	maxpow = ((s32)piddata->max_power) << 16;
6908c2ecf20Sopenharmony_ci	powadj = ((s32)piddata->power_adj) << 16;
6918c2ecf20Sopenharmony_ci
6928c2ecf20Sopenharmony_ci	pid_param.tmax = tmax;
6938c2ecf20Sopenharmony_ci	pid_param.ttarget = tmax - tdelta;
6948c2ecf20Sopenharmony_ci	pid_param.pmaxadj = maxpow - powadj;
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	pid_param.min = fan_cpu->ops->get_min(fan_cpu);
6978c2ecf20Sopenharmony_ci	pid_param.max = fan_cpu->ops->get_max(fan_cpu);
6988c2ecf20Sopenharmony_ci
6998c2ecf20Sopenharmony_ci	wf_cpu_pid_init(&pm121_cpu_state->pid, &pid_param);
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci	pr_debug("pm121: CPU Fan control initialized.\n");
7028c2ecf20Sopenharmony_ci	pr_debug("       ttarget=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM,\n",
7038c2ecf20Sopenharmony_ci		 FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
7048c2ecf20Sopenharmony_ci		 pid_param.min, pid_param.max);
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	return;
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_ci fail:
7098c2ecf20Sopenharmony_ci	printk(KERN_WARNING "pm121: CPU fan config not found, max fan speed\n");
7108c2ecf20Sopenharmony_ci
7118c2ecf20Sopenharmony_ci	if (controls[CPUFREQ])
7128c2ecf20Sopenharmony_ci		wf_control_set_max(controls[CPUFREQ]);
7138c2ecf20Sopenharmony_ci	if (fan_cpu)
7148c2ecf20Sopenharmony_ci		wf_control_set_max(fan_cpu);
7158c2ecf20Sopenharmony_ci}
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_cistatic void pm121_cpu_fans_tick(struct pm121_cpu_state *st)
7198c2ecf20Sopenharmony_ci{
7208c2ecf20Sopenharmony_ci	s32 new_setpoint, temp, power;
7218c2ecf20Sopenharmony_ci	struct wf_control *fan_cpu = NULL;
7228c2ecf20Sopenharmony_ci	int rc;
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci	if (--st->ticks != 0) {
7258c2ecf20Sopenharmony_ci		if (pm121_readjust)
7268c2ecf20Sopenharmony_ci			goto readjust;
7278c2ecf20Sopenharmony_ci		return;
7288c2ecf20Sopenharmony_ci	}
7298c2ecf20Sopenharmony_ci	st->ticks = PM121_CPU_INTERVAL;
7308c2ecf20Sopenharmony_ci
7318c2ecf20Sopenharmony_ci	fan_cpu = controls[FAN_CPU];
7328c2ecf20Sopenharmony_ci
7338c2ecf20Sopenharmony_ci	rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
7348c2ecf20Sopenharmony_ci	if (rc) {
7358c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pm121: CPU temp sensor error %d\n",
7368c2ecf20Sopenharmony_ci		       rc);
7378c2ecf20Sopenharmony_ci		pm121_failure_state |= FAILURE_SENSOR;
7388c2ecf20Sopenharmony_ci		return;
7398c2ecf20Sopenharmony_ci	}
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
7428c2ecf20Sopenharmony_ci	if (rc) {
7438c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pm121: CPU power sensor error %d\n",
7448c2ecf20Sopenharmony_ci		       rc);
7458c2ecf20Sopenharmony_ci		pm121_failure_state |= FAILURE_SENSOR;
7468c2ecf20Sopenharmony_ci		return;
7478c2ecf20Sopenharmony_ci	}
7488c2ecf20Sopenharmony_ci
7498c2ecf20Sopenharmony_ci	pr_debug("pm121: CPU Fans tick ! CPU temp: %d.%03d°C, power: %d.%03d\n",
7508c2ecf20Sopenharmony_ci		 FIX32TOPRINT(temp), FIX32TOPRINT(power));
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_ci	if (temp > st->pid.param.tmax)
7538c2ecf20Sopenharmony_ci		pm121_failure_state |= FAILURE_OVERTEMP;
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci	new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	/* correction */
7588c2ecf20Sopenharmony_ci	new_setpoint = pm121_correct(new_setpoint,
7598c2ecf20Sopenharmony_ci				     FAN_CPU,
7608c2ecf20Sopenharmony_ci				     st->pid.param.min);
7618c2ecf20Sopenharmony_ci
7628c2ecf20Sopenharmony_ci	/* connected correction */
7638c2ecf20Sopenharmony_ci	new_setpoint = pm121_connect(FAN_CPU, new_setpoint);
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_ci	if (st->setpoint == new_setpoint)
7668c2ecf20Sopenharmony_ci		return;
7678c2ecf20Sopenharmony_ci	st->setpoint = new_setpoint;
7688c2ecf20Sopenharmony_ci	pr_debug("pm121: CPU corrected setpoint: %d RPM\n", (int)new_setpoint);
7698c2ecf20Sopenharmony_ci
7708c2ecf20Sopenharmony_ci readjust:
7718c2ecf20Sopenharmony_ci	if (fan_cpu && pm121_failure_state == 0) {
7728c2ecf20Sopenharmony_ci		rc = fan_cpu->ops->set_value(fan_cpu, st->setpoint);
7738c2ecf20Sopenharmony_ci		if (rc) {
7748c2ecf20Sopenharmony_ci			printk(KERN_WARNING "pm121: %s fan error %d\n",
7758c2ecf20Sopenharmony_ci			       fan_cpu->name, rc);
7768c2ecf20Sopenharmony_ci			pm121_failure_state |= FAILURE_FAN;
7778c2ecf20Sopenharmony_ci		}
7788c2ecf20Sopenharmony_ci	}
7798c2ecf20Sopenharmony_ci}
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_ci/*
7828c2ecf20Sopenharmony_ci * ****** Common ******
7838c2ecf20Sopenharmony_ci *
7848c2ecf20Sopenharmony_ci */
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_cistatic void pm121_tick(void)
7878c2ecf20Sopenharmony_ci{
7888c2ecf20Sopenharmony_ci	unsigned int last_failure = pm121_failure_state;
7898c2ecf20Sopenharmony_ci	unsigned int new_failure;
7908c2ecf20Sopenharmony_ci	s32 total_power;
7918c2ecf20Sopenharmony_ci	int i;
7928c2ecf20Sopenharmony_ci
7938c2ecf20Sopenharmony_ci	if (!pm121_started) {
7948c2ecf20Sopenharmony_ci		pr_debug("pm121: creating control loops !\n");
7958c2ecf20Sopenharmony_ci		for (i = 0; i < N_LOOPS; i++)
7968c2ecf20Sopenharmony_ci			pm121_create_sys_fans(i);
7978c2ecf20Sopenharmony_ci
7988c2ecf20Sopenharmony_ci		pm121_create_cpu_fans();
7998c2ecf20Sopenharmony_ci		pm121_started = true;
8008c2ecf20Sopenharmony_ci	}
8018c2ecf20Sopenharmony_ci
8028c2ecf20Sopenharmony_ci	/* skipping ticks */
8038c2ecf20Sopenharmony_ci	if (pm121_skipping && --pm121_skipping)
8048c2ecf20Sopenharmony_ci		return;
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ci	/* compute average power */
8078c2ecf20Sopenharmony_ci	total_power = 0;
8088c2ecf20Sopenharmony_ci	for (i = 0; i < pm121_cpu_state->pid.param.history_len; i++)
8098c2ecf20Sopenharmony_ci		total_power += pm121_cpu_state->pid.powers[i];
8108c2ecf20Sopenharmony_ci
8118c2ecf20Sopenharmony_ci	average_power = total_power / pm121_cpu_state->pid.param.history_len;
8128c2ecf20Sopenharmony_ci
8138c2ecf20Sopenharmony_ci
8148c2ecf20Sopenharmony_ci	pm121_failure_state = 0;
8158c2ecf20Sopenharmony_ci	for (i = 0 ; i < N_LOOPS; i++) {
8168c2ecf20Sopenharmony_ci		if (pm121_sys_state[i])
8178c2ecf20Sopenharmony_ci			pm121_sys_fans_tick(i);
8188c2ecf20Sopenharmony_ci	}
8198c2ecf20Sopenharmony_ci
8208c2ecf20Sopenharmony_ci	if (pm121_cpu_state)
8218c2ecf20Sopenharmony_ci		pm121_cpu_fans_tick(pm121_cpu_state);
8228c2ecf20Sopenharmony_ci
8238c2ecf20Sopenharmony_ci	pm121_readjust = 0;
8248c2ecf20Sopenharmony_ci	new_failure = pm121_failure_state & ~last_failure;
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_ci	/* If entering failure mode, clamp cpufreq and ramp all
8278c2ecf20Sopenharmony_ci	 * fans to full speed.
8288c2ecf20Sopenharmony_ci	 */
8298c2ecf20Sopenharmony_ci	if (pm121_failure_state && !last_failure) {
8308c2ecf20Sopenharmony_ci		for (i = 0; i < N_CONTROLS; i++) {
8318c2ecf20Sopenharmony_ci			if (controls[i])
8328c2ecf20Sopenharmony_ci				wf_control_set_max(controls[i]);
8338c2ecf20Sopenharmony_ci		}
8348c2ecf20Sopenharmony_ci	}
8358c2ecf20Sopenharmony_ci
8368c2ecf20Sopenharmony_ci	/* If leaving failure mode, unclamp cpufreq and readjust
8378c2ecf20Sopenharmony_ci	 * all fans on next iteration
8388c2ecf20Sopenharmony_ci	 */
8398c2ecf20Sopenharmony_ci	if (!pm121_failure_state && last_failure) {
8408c2ecf20Sopenharmony_ci		if (controls[CPUFREQ])
8418c2ecf20Sopenharmony_ci			wf_control_set_min(controls[CPUFREQ]);
8428c2ecf20Sopenharmony_ci		pm121_readjust = 1;
8438c2ecf20Sopenharmony_ci	}
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci	/* Overtemp condition detected, notify and start skipping a couple
8468c2ecf20Sopenharmony_ci	 * ticks to let the temperature go down
8478c2ecf20Sopenharmony_ci	 */
8488c2ecf20Sopenharmony_ci	if (new_failure & FAILURE_OVERTEMP) {
8498c2ecf20Sopenharmony_ci		wf_set_overtemp();
8508c2ecf20Sopenharmony_ci		pm121_skipping = 2;
8518c2ecf20Sopenharmony_ci		pm121_overtemp = true;
8528c2ecf20Sopenharmony_ci	}
8538c2ecf20Sopenharmony_ci
8548c2ecf20Sopenharmony_ci	/* We only clear the overtemp condition if overtemp is cleared
8558c2ecf20Sopenharmony_ci	 * _and_ no other failure is present. Since a sensor error will
8568c2ecf20Sopenharmony_ci	 * clear the overtemp condition (can't measure temperature) at
8578c2ecf20Sopenharmony_ci	 * the control loop levels, but we don't want to keep it clear
8588c2ecf20Sopenharmony_ci	 * here in this case
8598c2ecf20Sopenharmony_ci	 */
8608c2ecf20Sopenharmony_ci	if (!pm121_failure_state && pm121_overtemp) {
8618c2ecf20Sopenharmony_ci		wf_clear_overtemp();
8628c2ecf20Sopenharmony_ci		pm121_overtemp = false;
8638c2ecf20Sopenharmony_ci	}
8648c2ecf20Sopenharmony_ci}
8658c2ecf20Sopenharmony_ci
8668c2ecf20Sopenharmony_ci
8678c2ecf20Sopenharmony_cistatic struct wf_control* pm121_register_control(struct wf_control *ct,
8688c2ecf20Sopenharmony_ci						 const char *match,
8698c2ecf20Sopenharmony_ci						 unsigned int id)
8708c2ecf20Sopenharmony_ci{
8718c2ecf20Sopenharmony_ci	if (controls[id] == NULL && !strcmp(ct->name, match)) {
8728c2ecf20Sopenharmony_ci		if (wf_get_control(ct) == 0)
8738c2ecf20Sopenharmony_ci			controls[id] = ct;
8748c2ecf20Sopenharmony_ci	}
8758c2ecf20Sopenharmony_ci	return controls[id];
8768c2ecf20Sopenharmony_ci}
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_cistatic void pm121_new_control(struct wf_control *ct)
8798c2ecf20Sopenharmony_ci{
8808c2ecf20Sopenharmony_ci	int all = 1;
8818c2ecf20Sopenharmony_ci
8828c2ecf20Sopenharmony_ci	if (pm121_all_controls_ok)
8838c2ecf20Sopenharmony_ci		return;
8848c2ecf20Sopenharmony_ci
8858c2ecf20Sopenharmony_ci	all = pm121_register_control(ct, "optical-drive-fan", FAN_OD) && all;
8868c2ecf20Sopenharmony_ci	all = pm121_register_control(ct, "hard-drive-fan", FAN_HD) && all;
8878c2ecf20Sopenharmony_ci	all = pm121_register_control(ct, "cpu-fan", FAN_CPU) && all;
8888c2ecf20Sopenharmony_ci	all = pm121_register_control(ct, "cpufreq-clamp", CPUFREQ) && all;
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_ci	if (all)
8918c2ecf20Sopenharmony_ci		pm121_all_controls_ok = 1;
8928c2ecf20Sopenharmony_ci}
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_cistatic struct wf_sensor* pm121_register_sensor(struct wf_sensor *sensor,
8988c2ecf20Sopenharmony_ci					       const char *match,
8998c2ecf20Sopenharmony_ci					       struct wf_sensor **var)
9008c2ecf20Sopenharmony_ci{
9018c2ecf20Sopenharmony_ci	if (*var == NULL && !strcmp(sensor->name, match)) {
9028c2ecf20Sopenharmony_ci		if (wf_get_sensor(sensor) == 0)
9038c2ecf20Sopenharmony_ci			*var = sensor;
9048c2ecf20Sopenharmony_ci	}
9058c2ecf20Sopenharmony_ci	return *var;
9068c2ecf20Sopenharmony_ci}
9078c2ecf20Sopenharmony_ci
9088c2ecf20Sopenharmony_cistatic void pm121_new_sensor(struct wf_sensor *sr)
9098c2ecf20Sopenharmony_ci{
9108c2ecf20Sopenharmony_ci	int all = 1;
9118c2ecf20Sopenharmony_ci
9128c2ecf20Sopenharmony_ci	if (pm121_all_sensors_ok)
9138c2ecf20Sopenharmony_ci		return;
9148c2ecf20Sopenharmony_ci
9158c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "cpu-temp",
9168c2ecf20Sopenharmony_ci				    &sensor_cpu_temp) && all;
9178c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "cpu-current",
9188c2ecf20Sopenharmony_ci				    &sensor_cpu_current) && all;
9198c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "cpu-voltage",
9208c2ecf20Sopenharmony_ci				    &sensor_cpu_voltage) && all;
9218c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "cpu-power",
9228c2ecf20Sopenharmony_ci				    &sensor_cpu_power) && all;
9238c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "hard-drive-temp",
9248c2ecf20Sopenharmony_ci				    &sensor_hard_drive_temp) && all;
9258c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "optical-drive-temp",
9268c2ecf20Sopenharmony_ci				    &sensor_optical_drive_temp) && all;
9278c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "incoming-air-temp",
9288c2ecf20Sopenharmony_ci				    &sensor_incoming_air_temp) && all;
9298c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "north-bridge-temp",
9308c2ecf20Sopenharmony_ci				    &sensor_north_bridge_temp) && all;
9318c2ecf20Sopenharmony_ci	all = pm121_register_sensor(sr, "gpu-temp",
9328c2ecf20Sopenharmony_ci				    &sensor_gpu_temp) && all;
9338c2ecf20Sopenharmony_ci
9348c2ecf20Sopenharmony_ci	if (all)
9358c2ecf20Sopenharmony_ci		pm121_all_sensors_ok = 1;
9368c2ecf20Sopenharmony_ci}
9378c2ecf20Sopenharmony_ci
9388c2ecf20Sopenharmony_ci
9398c2ecf20Sopenharmony_ci
9408c2ecf20Sopenharmony_cistatic int pm121_notify(struct notifier_block *self,
9418c2ecf20Sopenharmony_ci			unsigned long event, void *data)
9428c2ecf20Sopenharmony_ci{
9438c2ecf20Sopenharmony_ci	switch (event) {
9448c2ecf20Sopenharmony_ci	case WF_EVENT_NEW_CONTROL:
9458c2ecf20Sopenharmony_ci		pr_debug("pm121: new control %s detected\n",
9468c2ecf20Sopenharmony_ci			 ((struct wf_control *)data)->name);
9478c2ecf20Sopenharmony_ci		pm121_new_control(data);
9488c2ecf20Sopenharmony_ci		break;
9498c2ecf20Sopenharmony_ci	case WF_EVENT_NEW_SENSOR:
9508c2ecf20Sopenharmony_ci		pr_debug("pm121: new sensor %s detected\n",
9518c2ecf20Sopenharmony_ci			 ((struct wf_sensor *)data)->name);
9528c2ecf20Sopenharmony_ci		pm121_new_sensor(data);
9538c2ecf20Sopenharmony_ci		break;
9548c2ecf20Sopenharmony_ci	case WF_EVENT_TICK:
9558c2ecf20Sopenharmony_ci		if (pm121_all_controls_ok && pm121_all_sensors_ok)
9568c2ecf20Sopenharmony_ci			pm121_tick();
9578c2ecf20Sopenharmony_ci		break;
9588c2ecf20Sopenharmony_ci	}
9598c2ecf20Sopenharmony_ci
9608c2ecf20Sopenharmony_ci	return 0;
9618c2ecf20Sopenharmony_ci}
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_cistatic struct notifier_block pm121_events = {
9648c2ecf20Sopenharmony_ci	.notifier_call	= pm121_notify,
9658c2ecf20Sopenharmony_ci};
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_cistatic int pm121_init_pm(void)
9688c2ecf20Sopenharmony_ci{
9698c2ecf20Sopenharmony_ci	const struct smu_sdbp_header *hdr;
9708c2ecf20Sopenharmony_ci
9718c2ecf20Sopenharmony_ci	hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
9728c2ecf20Sopenharmony_ci	if (hdr != 0) {
9738c2ecf20Sopenharmony_ci		struct smu_sdbp_sensortree *st =
9748c2ecf20Sopenharmony_ci			(struct smu_sdbp_sensortree *)&hdr[1];
9758c2ecf20Sopenharmony_ci		pm121_mach_model = st->model_id;
9768c2ecf20Sopenharmony_ci	}
9778c2ecf20Sopenharmony_ci
9788c2ecf20Sopenharmony_ci	pm121_connection = &pm121_connections[pm121_mach_model - 2];
9798c2ecf20Sopenharmony_ci
9808c2ecf20Sopenharmony_ci	printk(KERN_INFO "pm121: Initializing for iMac G5 iSight model ID %d\n",
9818c2ecf20Sopenharmony_ci	       pm121_mach_model);
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci	return 0;
9848c2ecf20Sopenharmony_ci}
9858c2ecf20Sopenharmony_ci
9868c2ecf20Sopenharmony_ci
9878c2ecf20Sopenharmony_cistatic int pm121_probe(struct platform_device *ddev)
9888c2ecf20Sopenharmony_ci{
9898c2ecf20Sopenharmony_ci	wf_register_client(&pm121_events);
9908c2ecf20Sopenharmony_ci
9918c2ecf20Sopenharmony_ci	return 0;
9928c2ecf20Sopenharmony_ci}
9938c2ecf20Sopenharmony_ci
9948c2ecf20Sopenharmony_cistatic int pm121_remove(struct platform_device *ddev)
9958c2ecf20Sopenharmony_ci{
9968c2ecf20Sopenharmony_ci	wf_unregister_client(&pm121_events);
9978c2ecf20Sopenharmony_ci	return 0;
9988c2ecf20Sopenharmony_ci}
9998c2ecf20Sopenharmony_ci
10008c2ecf20Sopenharmony_cistatic struct platform_driver pm121_driver = {
10018c2ecf20Sopenharmony_ci	.probe = pm121_probe,
10028c2ecf20Sopenharmony_ci	.remove = pm121_remove,
10038c2ecf20Sopenharmony_ci	.driver = {
10048c2ecf20Sopenharmony_ci		.name = "windfarm",
10058c2ecf20Sopenharmony_ci		.bus = &platform_bus_type,
10068c2ecf20Sopenharmony_ci	},
10078c2ecf20Sopenharmony_ci};
10088c2ecf20Sopenharmony_ci
10098c2ecf20Sopenharmony_ci
10108c2ecf20Sopenharmony_cistatic int __init pm121_init(void)
10118c2ecf20Sopenharmony_ci{
10128c2ecf20Sopenharmony_ci	int rc = -ENODEV;
10138c2ecf20Sopenharmony_ci
10148c2ecf20Sopenharmony_ci	if (of_machine_is_compatible("PowerMac12,1"))
10158c2ecf20Sopenharmony_ci		rc = pm121_init_pm();
10168c2ecf20Sopenharmony_ci
10178c2ecf20Sopenharmony_ci	if (rc == 0) {
10188c2ecf20Sopenharmony_ci		request_module("windfarm_smu_controls");
10198c2ecf20Sopenharmony_ci		request_module("windfarm_smu_sensors");
10208c2ecf20Sopenharmony_ci		request_module("windfarm_smu_sat");
10218c2ecf20Sopenharmony_ci		request_module("windfarm_lm75_sensor");
10228c2ecf20Sopenharmony_ci		request_module("windfarm_max6690_sensor");
10238c2ecf20Sopenharmony_ci		request_module("windfarm_cpufreq_clamp");
10248c2ecf20Sopenharmony_ci		platform_driver_register(&pm121_driver);
10258c2ecf20Sopenharmony_ci	}
10268c2ecf20Sopenharmony_ci
10278c2ecf20Sopenharmony_ci	return rc;
10288c2ecf20Sopenharmony_ci}
10298c2ecf20Sopenharmony_ci
10308c2ecf20Sopenharmony_cistatic void __exit pm121_exit(void)
10318c2ecf20Sopenharmony_ci{
10328c2ecf20Sopenharmony_ci
10338c2ecf20Sopenharmony_ci	platform_driver_unregister(&pm121_driver);
10348c2ecf20Sopenharmony_ci}
10358c2ecf20Sopenharmony_ci
10368c2ecf20Sopenharmony_ci
10378c2ecf20Sopenharmony_cimodule_init(pm121_init);
10388c2ecf20Sopenharmony_cimodule_exit(pm121_exit);
10398c2ecf20Sopenharmony_ci
10408c2ecf20Sopenharmony_ciMODULE_AUTHOR("Étienne Bersac <bersace@gmail.com>");
10418c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Thermal control logic for iMac G5 (iSight)");
10428c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
10438c2ecf20Sopenharmony_ci
1044