xref: /linux/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c (revision 2ba9268dd603d23e17643437b2246acb6844953b)
1 /*
2  * Copyright 2012 The Nouveau community
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20  * OTHER DEALINGS IN THE SOFTWARE.
21  *
22  * Authors: Martin Peres
23  */
24 #include "priv.h"
25 
26 #include <core/device.h>
27 
28 static int
29 nvkm_therm_update_trip(struct nvkm_therm *therm)
30 {
31 	struct nvkm_therm_priv *priv = (void *)therm;
32 	struct nvbios_therm_trip_point *trip = priv->fan->bios.trip,
33 				       *cur_trip = NULL,
34 				       *last_trip = priv->last_trip;
35 	u8  temp = therm->temp_get(therm);
36 	u16 duty, i;
37 
38 	/* look for the trip point corresponding to the current temperature */
39 	cur_trip = NULL;
40 	for (i = 0; i < priv->fan->bios.nr_fan_trip; i++) {
41 		if (temp >= trip[i].temp)
42 			cur_trip = &trip[i];
43 	}
44 
45 	/* account for the hysteresis cycle */
46 	if (last_trip && temp <= (last_trip->temp) &&
47 	    temp > (last_trip->temp - last_trip->hysteresis))
48 		cur_trip = last_trip;
49 
50 	if (cur_trip) {
51 		duty = cur_trip->fan_duty;
52 		priv->last_trip = cur_trip;
53 	} else {
54 		duty = 0;
55 		priv->last_trip = NULL;
56 	}
57 
58 	return duty;
59 }
60 
61 static int
62 nvkm_therm_update_linear(struct nvkm_therm *therm)
63 {
64 	struct nvkm_therm_priv *priv = (void *)therm;
65 	u8  linear_min_temp = priv->fan->bios.linear_min_temp;
66 	u8  linear_max_temp = priv->fan->bios.linear_max_temp;
67 	u8  temp = therm->temp_get(therm);
68 	u16 duty;
69 
70 	/* handle the non-linear part first */
71 	if (temp < linear_min_temp)
72 		return priv->fan->bios.min_duty;
73 	else if (temp > linear_max_temp)
74 		return priv->fan->bios.max_duty;
75 
76 	/* we are in the linear zone */
77 	duty  = (temp - linear_min_temp);
78 	duty *= (priv->fan->bios.max_duty - priv->fan->bios.min_duty);
79 	duty /= (linear_max_temp - linear_min_temp);
80 	duty += priv->fan->bios.min_duty;
81 	return duty;
82 }
83 
84 static void
85 nvkm_therm_update(struct nvkm_therm *therm, int mode)
86 {
87 	struct nvkm_timer *ptimer = nvkm_timer(therm);
88 	struct nvkm_therm_priv *priv = (void *)therm;
89 	unsigned long flags;
90 	bool immd = true;
91 	bool poll = true;
92 	int duty = -1;
93 
94 	spin_lock_irqsave(&priv->lock, flags);
95 	if (mode < 0)
96 		mode = priv->mode;
97 	priv->mode = mode;
98 
99 	switch (mode) {
100 	case NVKM_THERM_CTRL_MANUAL:
101 		ptimer->alarm_cancel(ptimer, &priv->alarm);
102 		duty = nvkm_therm_fan_get(therm);
103 		if (duty < 0)
104 			duty = 100;
105 		poll = false;
106 		break;
107 	case NVKM_THERM_CTRL_AUTO:
108 		switch(priv->fan->bios.fan_mode) {
109 		case NVBIOS_THERM_FAN_TRIP:
110 			duty = nvkm_therm_update_trip(therm);
111 			break;
112 		case NVBIOS_THERM_FAN_LINEAR:
113 			duty = nvkm_therm_update_linear(therm);
114 			break;
115 		case NVBIOS_THERM_FAN_OTHER:
116 			if (priv->cstate)
117 				duty = priv->cstate;
118 			poll = false;
119 			break;
120 		}
121 		immd = false;
122 		break;
123 	case NVKM_THERM_CTRL_NONE:
124 	default:
125 		ptimer->alarm_cancel(ptimer, &priv->alarm);
126 		poll = false;
127 	}
128 
129 	if (list_empty(&priv->alarm.head) && poll)
130 		ptimer->alarm(ptimer, 1000000000ULL, &priv->alarm);
131 	spin_unlock_irqrestore(&priv->lock, flags);
132 
133 	if (duty >= 0) {
134 		nv_debug(therm, "FAN target request: %d%%\n", duty);
135 		nvkm_therm_fan_set(therm, immd, duty);
136 	}
137 }
138 
139 int
140 nvkm_therm_cstate(struct nvkm_therm *ptherm, int fan, int dir)
141 {
142 	struct nvkm_therm_priv *priv = (void *)ptherm;
143 	if (!dir || (dir < 0 && fan < priv->cstate) ||
144 		    (dir > 0 && fan > priv->cstate)) {
145 		nv_debug(ptherm, "default fan speed -> %d%%\n", fan);
146 		priv->cstate = fan;
147 		nvkm_therm_update(ptherm, -1);
148 	}
149 	return 0;
150 }
151 
152 static void
153 nvkm_therm_alarm(struct nvkm_alarm *alarm)
154 {
155 	struct nvkm_therm_priv *priv =
156 	       container_of(alarm, struct nvkm_therm_priv, alarm);
157 	nvkm_therm_update(&priv->base, -1);
158 }
159 
160 int
161 nvkm_therm_fan_mode(struct nvkm_therm *therm, int mode)
162 {
163 	struct nvkm_therm_priv *priv = (void *)therm;
164 	struct nvkm_device *device = nv_device(therm);
165 	static const char *name[] = {
166 		"disabled",
167 		"manual",
168 		"automatic"
169 	};
170 
171 	/* The default PPWR ucode on fermi interferes with fan management */
172 	if ((mode >= ARRAY_SIZE(name)) ||
173 	    (mode != NVKM_THERM_CTRL_NONE && device->card_type >= NV_C0 &&
174 	     !nvkm_subdev(device, NVDEV_SUBDEV_PMU)))
175 		return -EINVAL;
176 
177 	/* do not allow automatic fan management if the thermal sensor is
178 	 * not available */
179 	if (mode == NVKM_THERM_CTRL_AUTO && therm->temp_get(therm) < 0)
180 		return -EINVAL;
181 
182 	if (priv->mode == mode)
183 		return 0;
184 
185 	nv_info(therm, "fan management: %s\n", name[mode]);
186 	nvkm_therm_update(therm, mode);
187 	return 0;
188 }
189 
190 int
191 nvkm_therm_attr_get(struct nvkm_therm *therm,
192 		       enum nvkm_therm_attr_type type)
193 {
194 	struct nvkm_therm_priv *priv = (void *)therm;
195 
196 	switch (type) {
197 	case NVKM_THERM_ATTR_FAN_MIN_DUTY:
198 		return priv->fan->bios.min_duty;
199 	case NVKM_THERM_ATTR_FAN_MAX_DUTY:
200 		return priv->fan->bios.max_duty;
201 	case NVKM_THERM_ATTR_FAN_MODE:
202 		return priv->mode;
203 	case NVKM_THERM_ATTR_THRS_FAN_BOOST:
204 		return priv->bios_sensor.thrs_fan_boost.temp;
205 	case NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST:
206 		return priv->bios_sensor.thrs_fan_boost.hysteresis;
207 	case NVKM_THERM_ATTR_THRS_DOWN_CLK:
208 		return priv->bios_sensor.thrs_down_clock.temp;
209 	case NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST:
210 		return priv->bios_sensor.thrs_down_clock.hysteresis;
211 	case NVKM_THERM_ATTR_THRS_CRITICAL:
212 		return priv->bios_sensor.thrs_critical.temp;
213 	case NVKM_THERM_ATTR_THRS_CRITICAL_HYST:
214 		return priv->bios_sensor.thrs_critical.hysteresis;
215 	case NVKM_THERM_ATTR_THRS_SHUTDOWN:
216 		return priv->bios_sensor.thrs_shutdown.temp;
217 	case NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST:
218 		return priv->bios_sensor.thrs_shutdown.hysteresis;
219 	}
220 
221 	return -EINVAL;
222 }
223 
224 int
225 nvkm_therm_attr_set(struct nvkm_therm *therm,
226 		    enum nvkm_therm_attr_type type, int value)
227 {
228 	struct nvkm_therm_priv *priv = (void *)therm;
229 
230 	switch (type) {
231 	case NVKM_THERM_ATTR_FAN_MIN_DUTY:
232 		if (value < 0)
233 			value = 0;
234 		if (value > priv->fan->bios.max_duty)
235 			value = priv->fan->bios.max_duty;
236 		priv->fan->bios.min_duty = value;
237 		return 0;
238 	case NVKM_THERM_ATTR_FAN_MAX_DUTY:
239 		if (value < 0)
240 			value = 0;
241 		if (value < priv->fan->bios.min_duty)
242 			value = priv->fan->bios.min_duty;
243 		priv->fan->bios.max_duty = value;
244 		return 0;
245 	case NVKM_THERM_ATTR_FAN_MODE:
246 		return nvkm_therm_fan_mode(therm, value);
247 	case NVKM_THERM_ATTR_THRS_FAN_BOOST:
248 		priv->bios_sensor.thrs_fan_boost.temp = value;
249 		priv->sensor.program_alarms(therm);
250 		return 0;
251 	case NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST:
252 		priv->bios_sensor.thrs_fan_boost.hysteresis = value;
253 		priv->sensor.program_alarms(therm);
254 		return 0;
255 	case NVKM_THERM_ATTR_THRS_DOWN_CLK:
256 		priv->bios_sensor.thrs_down_clock.temp = value;
257 		priv->sensor.program_alarms(therm);
258 		return 0;
259 	case NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST:
260 		priv->bios_sensor.thrs_down_clock.hysteresis = value;
261 		priv->sensor.program_alarms(therm);
262 		return 0;
263 	case NVKM_THERM_ATTR_THRS_CRITICAL:
264 		priv->bios_sensor.thrs_critical.temp = value;
265 		priv->sensor.program_alarms(therm);
266 		return 0;
267 	case NVKM_THERM_ATTR_THRS_CRITICAL_HYST:
268 		priv->bios_sensor.thrs_critical.hysteresis = value;
269 		priv->sensor.program_alarms(therm);
270 		return 0;
271 	case NVKM_THERM_ATTR_THRS_SHUTDOWN:
272 		priv->bios_sensor.thrs_shutdown.temp = value;
273 		priv->sensor.program_alarms(therm);
274 		return 0;
275 	case NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST:
276 		priv->bios_sensor.thrs_shutdown.hysteresis = value;
277 		priv->sensor.program_alarms(therm);
278 		return 0;
279 	}
280 
281 	return -EINVAL;
282 }
283 
284 int
285 _nvkm_therm_init(struct nvkm_object *object)
286 {
287 	struct nvkm_therm *therm = (void *)object;
288 	struct nvkm_therm_priv *priv = (void *)therm;
289 	int ret;
290 
291 	ret = nvkm_subdev_init(&therm->base);
292 	if (ret)
293 		return ret;
294 
295 	if (priv->suspend >= 0) {
296 		/* restore the pwm value only when on manual or auto mode */
297 		if (priv->suspend > 0)
298 			nvkm_therm_fan_set(therm, true, priv->fan->percent);
299 
300 		nvkm_therm_fan_mode(therm, priv->suspend);
301 	}
302 	nvkm_therm_sensor_init(therm);
303 	nvkm_therm_fan_init(therm);
304 	return 0;
305 }
306 
307 int
308 _nvkm_therm_fini(struct nvkm_object *object, bool suspend)
309 {
310 	struct nvkm_therm *therm = (void *)object;
311 	struct nvkm_therm_priv *priv = (void *)therm;
312 
313 	nvkm_therm_fan_fini(therm, suspend);
314 	nvkm_therm_sensor_fini(therm, suspend);
315 	if (suspend) {
316 		priv->suspend = priv->mode;
317 		priv->mode = NVKM_THERM_CTRL_NONE;
318 	}
319 
320 	return nvkm_subdev_fini(&therm->base, suspend);
321 }
322 
323 int
324 nvkm_therm_create_(struct nvkm_object *parent, struct nvkm_object *engine,
325 		   struct nvkm_oclass *oclass, int length, void **pobject)
326 {
327 	struct nvkm_therm_priv *priv;
328 	int ret;
329 
330 	ret = nvkm_subdev_create_(parent, engine, oclass, 0, "PTHERM",
331 				  "therm", length, pobject);
332 	priv = *pobject;
333 	if (ret)
334 		return ret;
335 
336 	nvkm_alarm_init(&priv->alarm, nvkm_therm_alarm);
337 	spin_lock_init(&priv->lock);
338 	spin_lock_init(&priv->sensor.alarm_program_lock);
339 
340 	priv->base.fan_get = nvkm_therm_fan_user_get;
341 	priv->base.fan_set = nvkm_therm_fan_user_set;
342 	priv->base.fan_sense = nvkm_therm_fan_sense;
343 	priv->base.attr_get = nvkm_therm_attr_get;
344 	priv->base.attr_set = nvkm_therm_attr_set;
345 	priv->mode = priv->suspend = -1; /* undefined */
346 	return 0;
347 }
348 
349 int
350 nvkm_therm_preinit(struct nvkm_therm *therm)
351 {
352 	nvkm_therm_sensor_ctor(therm);
353 	nvkm_therm_ic_ctor(therm);
354 	nvkm_therm_fan_ctor(therm);
355 
356 	nvkm_therm_fan_mode(therm, NVKM_THERM_CTRL_AUTO);
357 	nvkm_therm_sensor_preinit(therm);
358 	return 0;
359 }
360 
361 void
362 _nvkm_therm_dtor(struct nvkm_object *object)
363 {
364 	struct nvkm_therm_priv *priv = (void *)object;
365 	kfree(priv->fan);
366 	nvkm_subdev_destroy(&priv->base.base);
367 }
368