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