1222429Snwhitehorn/*-
2222429Snwhitehorn * Copyright (c) 2009-2011 Nathan Whitehorn
3222429Snwhitehorn * All rights reserved.
4222429Snwhitehorn *
5222429Snwhitehorn * Redistribution and use in source and binary forms, with or without
6222429Snwhitehorn * modification, are permitted provided that the following conditions
7222429Snwhitehorn * are met:
8222429Snwhitehorn * 1. Redistributions of source code must retain the above copyright
9222429Snwhitehorn *    notice, this list of conditions and the following disclaimer.
10222429Snwhitehorn * 2. Redistributions in binary form must reproduce the above copyright
11222429Snwhitehorn *    notice, this list of conditions and the following disclaimer in the
12222429Snwhitehorn *    documentation and/or other materials provided with the distribution.
13222429Snwhitehorn *
14222429Snwhitehorn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15222429Snwhitehorn * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16222429Snwhitehorn * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17222429Snwhitehorn * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18222429Snwhitehorn * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19222429Snwhitehorn * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20222429Snwhitehorn * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21222429Snwhitehorn * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22222429Snwhitehorn * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23222429Snwhitehorn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24222429Snwhitehorn * SUCH DAMAGE.
25222429Snwhitehorn */
26222429Snwhitehorn
27222429Snwhitehorn#include <sys/cdefs.h>
28222429Snwhitehorn__FBSDID("$FreeBSD$");
29222429Snwhitehorn
30222429Snwhitehorn#include <sys/param.h>
31222429Snwhitehorn#include <sys/kernel.h>
32222429Snwhitehorn#include <sys/lock.h>
33222429Snwhitehorn#include <sys/mutex.h>
34222429Snwhitehorn#include <sys/systm.h>
35222429Snwhitehorn
36222429Snwhitehorn#include <sys/types.h>
37222429Snwhitehorn#include <sys/kthread.h>
38222429Snwhitehorn#include <sys/malloc.h>
39222429Snwhitehorn#include <sys/reboot.h>
40222429Snwhitehorn#include <sys/sysctl.h>
41222429Snwhitehorn#include <sys/queue.h>
42222429Snwhitehorn
43222429Snwhitehorn#include "powermac_thermal.h"
44222429Snwhitehorn
45279045Sjhibbits/* A 10 second timer for spinning down fans. */
46279045Sjhibbits#define FAN_HYSTERESIS_TIMER	10
47279045Sjhibbits
48222429Snwhitehornstatic void fan_management_proc(void);
49222429Snwhitehornstatic void pmac_therm_manage_fans(void);
50222429Snwhitehorn
51222429Snwhitehornstatic struct proc *pmac_them_proc;
52222429Snwhitehornstatic int enable_pmac_thermal = 1;
53222429Snwhitehorn
54222429Snwhitehornstatic struct kproc_desc pmac_therm_kp = {
55222429Snwhitehorn	"pmac_thermal",
56222429Snwhitehorn	fan_management_proc,
57222429Snwhitehorn	&pmac_them_proc
58222429Snwhitehorn};
59222429Snwhitehorn
60222429SnwhitehornSYSINIT(pmac_therm_setup, SI_SUB_KTHREAD_IDLE, SI_ORDER_ANY, kproc_start,
61222429Snwhitehorn    &pmac_therm_kp);
62222429SnwhitehornSYSCTL_INT(_machdep, OID_AUTO, manage_fans, CTLFLAG_RW | CTLFLAG_TUN,
63222429Snwhitehorn    &enable_pmac_thermal, 1, "Enable automatic fan management");
64227293Sedstatic MALLOC_DEFINE(M_PMACTHERM, "pmactherm", "Powermac Thermal Management");
65222429Snwhitehorn
66222429Snwhitehornstruct pmac_fan_le {
67222429Snwhitehorn	struct pmac_fan			*fan;
68222429Snwhitehorn	int				last_val;
69279045Sjhibbits	int				timer;
70222429Snwhitehorn	SLIST_ENTRY(pmac_fan_le)	entries;
71222429Snwhitehorn};
72222429Snwhitehornstruct pmac_sens_le {
73222429Snwhitehorn	struct pmac_therm		*sensor;
74222429Snwhitehorn	int				last_val;
75257093Snwhitehorn#define MAX_CRITICAL_COUNT 6
76257093Snwhitehorn	int				critical_count;
77222429Snwhitehorn	SLIST_ENTRY(pmac_sens_le)	entries;
78222429Snwhitehorn};
79222429Snwhitehornstatic SLIST_HEAD(pmac_fans, pmac_fan_le) fans = SLIST_HEAD_INITIALIZER(fans);
80222429Snwhitehornstatic SLIST_HEAD(pmac_sensors, pmac_sens_le) sensors =
81222429Snwhitehorn    SLIST_HEAD_INITIALIZER(sensors);
82222429Snwhitehorn
83222429Snwhitehornstatic void
84222429Snwhitehornfan_management_proc(void)
85222429Snwhitehorn{
86222429Snwhitehorn	/* Nothing to manage? */
87222429Snwhitehorn	if (SLIST_EMPTY(&fans))
88222469Snwhitehorn		kproc_exit(0);
89222429Snwhitehorn
90222429Snwhitehorn	while (1) {
91222429Snwhitehorn		pmac_therm_manage_fans();
92222429Snwhitehorn		pause("pmac_therm", hz);
93222429Snwhitehorn	}
94222429Snwhitehorn}
95222429Snwhitehorn
96222429Snwhitehornstatic void
97222429Snwhitehornpmac_therm_manage_fans(void)
98222429Snwhitehorn{
99222429Snwhitehorn	struct pmac_sens_le *sensor;
100222429Snwhitehorn	struct pmac_fan_le *fan;
101222429Snwhitehorn	int average_excess, max_excess_zone, frac_excess;
102279045Sjhibbits	int fan_speed;
103222429Snwhitehorn	int nsens, nsens_zone;
104222463Snwhitehorn	int temp;
105222429Snwhitehorn
106222429Snwhitehorn	if (!enable_pmac_thermal)
107222429Snwhitehorn		return;
108222429Snwhitehorn
109222429Snwhitehorn	/* Read all the sensors */
110222429Snwhitehorn	SLIST_FOREACH(sensor, &sensors, entries) {
111222463Snwhitehorn		temp = sensor->sensor->read(sensor->sensor);
112222463Snwhitehorn		if (temp > 0) /* Use the previous temp in case of error */
113222463Snwhitehorn			sensor->last_val = temp;
114222463Snwhitehorn
115222429Snwhitehorn		if (sensor->last_val > sensor->sensor->max_temp) {
116257093Snwhitehorn			sensor->critical_count++;
117222429Snwhitehorn			printf("WARNING: Current temperature (%s: %d.%d C) "
118257093Snwhitehorn			    "exceeds critical temperature (%d.%d C); "
119257093Snwhitehorn			    "count=%d\n",
120257093Snwhitehorn			    sensor->sensor->name,
121257093Snwhitehorn			    (sensor->last_val - ZERO_C_TO_K) / 10,
122257093Snwhitehorn			    (sensor->last_val - ZERO_C_TO_K) % 10,
123257093Snwhitehorn			    (sensor->sensor->max_temp - ZERO_C_TO_K) / 10,
124257093Snwhitehorn			    (sensor->sensor->max_temp - ZERO_C_TO_K) % 10,
125257093Snwhitehorn			    sensor->critical_count);
126257093Snwhitehorn			if (sensor->critical_count >= MAX_CRITICAL_COUNT) {
127257093Snwhitehorn				printf("WARNING: %s temperature exceeded "
128257093Snwhitehorn				    "critical temperature %d times in a row; "
129257093Snwhitehorn				    "shutting down!\n",
130257093Snwhitehorn				    sensor->sensor->name,
131257093Snwhitehorn				    sensor->critical_count);
132257093Snwhitehorn				shutdown_nice(RB_POWEROFF);
133257093Snwhitehorn			}
134257093Snwhitehorn		} else {
135257093Snwhitehorn			if (sensor->critical_count > 0)
136257093Snwhitehorn				sensor->critical_count--;
137222429Snwhitehorn		}
138222429Snwhitehorn	}
139222429Snwhitehorn
140222429Snwhitehorn	/* Set all the fans */
141222429Snwhitehorn	SLIST_FOREACH(fan, &fans, entries) {
142222429Snwhitehorn		nsens = nsens_zone = 0;
143222429Snwhitehorn		average_excess = max_excess_zone = 0;
144222429Snwhitehorn		SLIST_FOREACH(sensor, &sensors, entries) {
145279045Sjhibbits			temp = imin(sensor->last_val,
146279045Sjhibbits			    sensor->sensor->max_temp);
147279045Sjhibbits			frac_excess = (temp -
148222429Snwhitehorn			    sensor->sensor->target_temp)*100 /
149279045Sjhibbits			    (sensor->sensor->max_temp - temp + 1);
150222460Snwhitehorn			if (frac_excess < 0)
151222460Snwhitehorn				frac_excess = 0;
152222429Snwhitehorn			if (sensor->sensor->zone == fan->fan->zone) {
153222429Snwhitehorn				max_excess_zone = imax(max_excess_zone,
154222429Snwhitehorn				    frac_excess);
155222429Snwhitehorn				nsens_zone++;
156222429Snwhitehorn			}
157222429Snwhitehorn			average_excess += frac_excess;
158222429Snwhitehorn			nsens++;
159222429Snwhitehorn		}
160222429Snwhitehorn		average_excess /= nsens;
161222429Snwhitehorn
162222429Snwhitehorn		/* If there are no sensors in this zone, use the average */
163222429Snwhitehorn		if (nsens_zone == 0)
164222429Snwhitehorn			max_excess_zone = average_excess;
165222429Snwhitehorn		/* No sensors at all? Use default */
166222429Snwhitehorn		if (nsens == 0) {
167222429Snwhitehorn			fan->fan->set(fan->fan, fan->fan->default_rpm);
168222429Snwhitehorn			continue;
169222429Snwhitehorn		}
170222429Snwhitehorn
171222429Snwhitehorn		/*
172222429Snwhitehorn		 * Scale the fan linearly in the max temperature in its
173222429Snwhitehorn		 * thermal zone.
174222429Snwhitehorn		 */
175279045Sjhibbits		max_excess_zone = imin(max_excess_zone, 100);
176279045Sjhibbits		fan_speed = max_excess_zone *
177222429Snwhitehorn		    (fan->fan->max_rpm - fan->fan->min_rpm)/100 +
178279045Sjhibbits		    fan->fan->min_rpm;
179279045Sjhibbits		if (fan_speed >= fan->last_val) {
180279045Sjhibbits		    fan->timer = FAN_HYSTERESIS_TIMER;
181279045Sjhibbits		    fan->last_val = fan_speed;
182279045Sjhibbits		} else {
183279045Sjhibbits		    fan->timer--;
184279045Sjhibbits		    if (fan->timer == 0) {
185279045Sjhibbits		    	fan->last_val = fan_speed;
186279045Sjhibbits		    	fan->timer = FAN_HYSTERESIS_TIMER;
187279045Sjhibbits		    }
188279045Sjhibbits		}
189279045Sjhibbits		fan->fan->set(fan->fan, fan->last_val);
190222429Snwhitehorn	}
191222429Snwhitehorn}
192222429Snwhitehorn
193222429Snwhitehornvoid
194222429Snwhitehornpmac_thermal_fan_register(struct pmac_fan *fan)
195222429Snwhitehorn{
196222429Snwhitehorn	struct pmac_fan_le *list_entry;
197222429Snwhitehorn
198222429Snwhitehorn	list_entry = malloc(sizeof(struct pmac_fan_le), M_PMACTHERM,
199222429Snwhitehorn	    M_ZERO | M_WAITOK);
200222429Snwhitehorn	list_entry->fan = fan;
201222429Snwhitehorn
202222429Snwhitehorn	SLIST_INSERT_HEAD(&fans, list_entry, entries);
203222429Snwhitehorn}
204222429Snwhitehorn
205222429Snwhitehornvoid
206222429Snwhitehornpmac_thermal_sensor_register(struct pmac_therm *sensor)
207222429Snwhitehorn{
208222429Snwhitehorn	struct pmac_sens_le *list_entry;
209222429Snwhitehorn
210222429Snwhitehorn	list_entry = malloc(sizeof(struct pmac_sens_le), M_PMACTHERM,
211222429Snwhitehorn	    M_ZERO | M_WAITOK);
212222429Snwhitehorn	list_entry->sensor = sensor;
213257093Snwhitehorn	list_entry->last_val = 0;
214257093Snwhitehorn	list_entry->critical_count = 0;
215222429Snwhitehorn
216222429Snwhitehorn	SLIST_INSERT_HEAD(&sensors, list_entry, entries);
217222429Snwhitehorn}
218222429Snwhitehorn
219