1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2009-2011 Nathan Whitehorn
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/kernel.h>
34 #include <sys/lock.h>
35 #include <sys/mutex.h>
36 #include <sys/systm.h>
37 
38 #include <sys/types.h>
39 #include <sys/kthread.h>
40 #include <sys/malloc.h>
41 #include <sys/reboot.h>
42 #include <sys/sysctl.h>
43 #include <sys/queue.h>
44 
45 #include "powermac_thermal.h"
46 
47 /* A 10 second timer for spinning down fans. */
48 #define FAN_HYSTERESIS_TIMER	10
49 
50 static void fan_management_proc(void);
51 static void pmac_therm_manage_fans(void);
52 
53 static struct proc *pmac_them_proc;
54 static int enable_pmac_thermal = 1;
55 
56 static struct kproc_desc pmac_therm_kp = {
57 	"pmac_thermal",
58 	fan_management_proc,
59 	&pmac_them_proc
60 };
61 
62 SYSINIT(pmac_therm_setup, SI_SUB_KTHREAD_IDLE, SI_ORDER_ANY, kproc_start,
63     &pmac_therm_kp);
64 SYSCTL_INT(_machdep, OID_AUTO, manage_fans, CTLFLAG_RWTUN,
65     &enable_pmac_thermal, 1, "Enable automatic fan management");
66 static MALLOC_DEFINE(M_PMACTHERM, "pmactherm", "Powermac Thermal Management");
67 
68 struct pmac_fan_le {
69 	struct pmac_fan			*fan;
70 	int				last_val;
71 	int				timer;
72 	SLIST_ENTRY(pmac_fan_le)	entries;
73 };
74 struct pmac_sens_le {
75 	struct pmac_therm		*sensor;
76 	int				last_val;
77 #define MAX_CRITICAL_COUNT 6
78 	int				critical_count;
79 	SLIST_ENTRY(pmac_sens_le)	entries;
80 };
81 static SLIST_HEAD(pmac_fans, pmac_fan_le) fans = SLIST_HEAD_INITIALIZER(fans);
82 static SLIST_HEAD(pmac_sensors, pmac_sens_le) sensors =
83     SLIST_HEAD_INITIALIZER(sensors);
84 
85 static void
86 fan_management_proc(void)
87 {
88 	/* Nothing to manage? */
89 	if (SLIST_EMPTY(&fans))
90 		kproc_exit(0);
91 
92 	while (1) {
93 		pmac_therm_manage_fans();
94 		pause("pmac_therm", hz);
95 	}
96 }
97 
98 static void
99 pmac_therm_manage_fans(void)
100 {
101 	struct pmac_sens_le *sensor;
102 	struct pmac_fan_le *fan;
103 	int average_excess, max_excess_zone, frac_excess;
104 	int fan_speed;
105 	int nsens, nsens_zone;
106 	int temp;
107 
108 	if (!enable_pmac_thermal)
109 		return;
110 
111 	/* Read all the sensors */
112 	SLIST_FOREACH(sensor, &sensors, entries) {
113 		temp = sensor->sensor->read(sensor->sensor);
114 		if (temp > 0) /* Use the previous temp in case of error */
115 			sensor->last_val = temp;
116 
117 		if (sensor->last_val > sensor->sensor->max_temp) {
118 			sensor->critical_count++;
119 			printf("WARNING: Current temperature (%s: %d.%d C) "
120 			    "exceeds critical temperature (%d.%d C); "
121 			    "count=%d\n",
122 			    sensor->sensor->name,
123 			    (sensor->last_val - ZERO_C_TO_K) / 10,
124 			    (sensor->last_val - ZERO_C_TO_K) % 10,
125 			    (sensor->sensor->max_temp - ZERO_C_TO_K) / 10,
126 			    (sensor->sensor->max_temp - ZERO_C_TO_K) % 10,
127 			    sensor->critical_count);
128 			if (sensor->critical_count >= MAX_CRITICAL_COUNT) {
129 				printf("WARNING: %s temperature exceeded "
130 				    "critical temperature %d times in a row; "
131 				    "shutting down!\n",
132 				    sensor->sensor->name,
133 				    sensor->critical_count);
134 				shutdown_nice(RB_POWEROFF);
135 			}
136 		} else {
137 			if (sensor->critical_count > 0)
138 				sensor->critical_count--;
139 		}
140 	}
141 
142 	/* Set all the fans */
143 	SLIST_FOREACH(fan, &fans, entries) {
144 		nsens = nsens_zone = 0;
145 		average_excess = max_excess_zone = 0;
146 		SLIST_FOREACH(sensor, &sensors, entries) {
147 			temp = imin(sensor->last_val,
148 			    sensor->sensor->max_temp);
149 			frac_excess = (temp -
150 			    sensor->sensor->target_temp)*100 /
151 			    (sensor->sensor->max_temp - temp + 1);
152 			if (frac_excess < 0)
153 				frac_excess = 0;
154 			if (sensor->sensor->zone == fan->fan->zone) {
155 				max_excess_zone = imax(max_excess_zone,
156 				    frac_excess);
157 				nsens_zone++;
158 			}
159 			average_excess += frac_excess;
160 			nsens++;
161 		}
162 		average_excess /= nsens;
163 
164 		/* If there are no sensors in this zone, use the average */
165 		if (nsens_zone == 0)
166 			max_excess_zone = average_excess;
167 		/* No sensors at all? Use default */
168 		if (nsens == 0) {
169 			fan->fan->set(fan->fan, fan->fan->default_rpm);
170 			continue;
171 		}
172 
173 		/*
174 		 * Scale the fan linearly in the max temperature in its
175 		 * thermal zone.
176 		 */
177 		max_excess_zone = imin(max_excess_zone, 100);
178 		fan_speed = max_excess_zone *
179 		    (fan->fan->max_rpm - fan->fan->min_rpm)/100 +
180 		    fan->fan->min_rpm;
181 		if (fan_speed >= fan->last_val) {
182 		    fan->timer = FAN_HYSTERESIS_TIMER;
183 		    fan->last_val = fan_speed;
184 		} else {
185 		    fan->timer--;
186 		    if (fan->timer == 0) {
187 		    	fan->last_val = fan_speed;
188 		    	fan->timer = FAN_HYSTERESIS_TIMER;
189 		    }
190 		}
191 		fan->fan->set(fan->fan, fan->last_val);
192 	}
193 }
194 
195 void
196 pmac_thermal_fan_register(struct pmac_fan *fan)
197 {
198 	struct pmac_fan_le *list_entry;
199 
200 	list_entry = malloc(sizeof(struct pmac_fan_le), M_PMACTHERM,
201 	    M_ZERO | M_WAITOK);
202 	list_entry->fan = fan;
203 
204 	SLIST_INSERT_HEAD(&fans, list_entry, entries);
205 }
206 
207 void
208 pmac_thermal_sensor_register(struct pmac_therm *sensor)
209 {
210 	struct pmac_sens_le *list_entry;
211 
212 	list_entry = malloc(sizeof(struct pmac_sens_le), M_PMACTHERM,
213 	    M_ZERO | M_WAITOK);
214 	list_entry->sensor = sensor;
215 	list_entry->last_val = 0;
216 	list_entry->critical_count = 0;
217 
218 	SLIST_INSERT_HEAD(&sensors, list_entry, entries);
219 }
220