Commit | Line | Data |
---|---|---|
aa1b9b48 MP |
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 | */ | |
e1404611 | 24 | #include "priv.h" |
aa1b9b48 | 25 | |
57113c01 BS |
26 | int |
27 | nvkm_therm_temp_get(struct nvkm_therm *therm) | |
28 | { | |
29 | if (therm->func->temp_get) | |
30 | return therm->func->temp_get(therm); | |
31 | return -ENODEV; | |
32 | } | |
33 | ||
694472f4 | 34 | static int |
57113c01 | 35 | nvkm_therm_update_trip(struct nvkm_therm *therm) |
694472f4 | 36 | { |
da06b46b | 37 | struct nvbios_therm_trip_point *trip = therm->fan->bios.trip, |
d390b480 | 38 | *cur_trip = NULL, |
da06b46b | 39 | *last_trip = therm->last_trip; |
57113c01 | 40 | u8 temp = therm->func->temp_get(therm); |
694472f4 MP |
41 | u16 duty, i; |
42 | ||
43 | /* look for the trip point corresponding to the current temperature */ | |
44 | cur_trip = NULL; | |
da06b46b | 45 | for (i = 0; i < therm->fan->bios.nr_fan_trip; i++) { |
694472f4 MP |
46 | if (temp >= trip[i].temp) |
47 | cur_trip = &trip[i]; | |
48 | } | |
49 | ||
50 | /* account for the hysteresis cycle */ | |
51 | if (last_trip && temp <= (last_trip->temp) && | |
52 | temp > (last_trip->temp - last_trip->hysteresis)) | |
53 | cur_trip = last_trip; | |
54 | ||
55 | if (cur_trip) { | |
56 | duty = cur_trip->fan_duty; | |
da06b46b | 57 | therm->last_trip = cur_trip; |
694472f4 MP |
58 | } else { |
59 | duty = 0; | |
da06b46b | 60 | therm->last_trip = NULL; |
694472f4 MP |
61 | } |
62 | ||
63 | return duty; | |
64 | } | |
65 | ||
66 | static int | |
57113c01 | 67 | nvkm_therm_update_linear(struct nvkm_therm *therm) |
694472f4 | 68 | { |
da06b46b BS |
69 | u8 linear_min_temp = therm->fan->bios.linear_min_temp; |
70 | u8 linear_max_temp = therm->fan->bios.linear_max_temp; | |
57113c01 | 71 | u8 temp = therm->func->temp_get(therm); |
694472f4 MP |
72 | u16 duty; |
73 | ||
a624bafb MP |
74 | /* handle the non-linear part first */ |
75 | if (temp < linear_min_temp) | |
da06b46b | 76 | return therm->fan->bios.min_duty; |
a624bafb | 77 | else if (temp > linear_max_temp) |
da06b46b | 78 | return therm->fan->bios.max_duty; |
a624bafb MP |
79 | |
80 | /* we are in the linear zone */ | |
694472f4 | 81 | duty = (temp - linear_min_temp); |
da06b46b | 82 | duty *= (therm->fan->bios.max_duty - therm->fan->bios.min_duty); |
694472f4 | 83 | duty /= (linear_max_temp - linear_min_temp); |
da06b46b | 84 | duty += therm->fan->bios.min_duty; |
694472f4 MP |
85 | return duty; |
86 | } | |
87 | ||
88 | static void | |
57113c01 | 89 | nvkm_therm_update(struct nvkm_therm *therm, int mode) |
694472f4 | 90 | { |
57113c01 | 91 | struct nvkm_subdev *subdev = &therm->subdev; |
b3c418bb | 92 | struct nvkm_timer *tmr = subdev->device->timer; |
694472f4 | 93 | unsigned long flags; |
682b1fc7 | 94 | bool immd = true; |
36faa2fc | 95 | bool poll = true; |
682b1fc7 | 96 | int duty = -1; |
694472f4 | 97 | |
da06b46b | 98 | spin_lock_irqsave(&therm->lock, flags); |
694472f4 | 99 | if (mode < 0) |
da06b46b BS |
100 | mode = therm->mode; |
101 | therm->mode = mode; | |
694472f4 MP |
102 | |
103 | switch (mode) { | |
e1404611 | 104 | case NVKM_THERM_CTRL_MANUAL: |
cb8bb9ce | 105 | tmr->alarm_cancel(tmr, &therm->alarm); |
57113c01 | 106 | duty = nvkm_therm_fan_get(therm); |
1a22274b BS |
107 | if (duty < 0) |
108 | duty = 100; | |
36faa2fc | 109 | poll = false; |
694472f4 | 110 | break; |
e1404611 | 111 | case NVKM_THERM_CTRL_AUTO: |
da06b46b | 112 | switch(therm->fan->bios.fan_mode) { |
0e994d64 | 113 | case NVBIOS_THERM_FAN_TRIP: |
57113c01 | 114 | duty = nvkm_therm_update_trip(therm); |
0e994d64 MP |
115 | break; |
116 | case NVBIOS_THERM_FAN_LINEAR: | |
57113c01 | 117 | duty = nvkm_therm_update_linear(therm); |
0e994d64 MP |
118 | break; |
119 | case NVBIOS_THERM_FAN_OTHER: | |
da06b46b BS |
120 | if (therm->cstate) |
121 | duty = therm->cstate; | |
36faa2fc | 122 | poll = false; |
0e994d64 | 123 | break; |
36faa2fc | 124 | } |
682b1fc7 | 125 | immd = false; |
694472f4 | 126 | break; |
e1404611 | 127 | case NVKM_THERM_CTRL_NONE: |
694472f4 | 128 | default: |
cb8bb9ce | 129 | tmr->alarm_cancel(tmr, &therm->alarm); |
36faa2fc | 130 | poll = false; |
694472f4 MP |
131 | } |
132 | ||
da06b46b | 133 | if (list_empty(&therm->alarm.head) && poll) |
cb8bb9ce | 134 | tmr->alarm(tmr, 1000000000ULL, &therm->alarm); |
da06b46b | 135 | spin_unlock_irqrestore(&therm->lock, flags); |
682b1fc7 BS |
136 | |
137 | if (duty >= 0) { | |
b3c418bb | 138 | nvkm_debug(subdev, "FAN target request: %d%%\n", duty); |
57113c01 | 139 | nvkm_therm_fan_set(therm, immd, duty); |
682b1fc7 | 140 | } |
694472f4 MP |
141 | } |
142 | ||
6387e2cb | 143 | int |
57113c01 | 144 | nvkm_therm_cstate(struct nvkm_therm *therm, int fan, int dir) |
6387e2cb | 145 | { |
57113c01 | 146 | struct nvkm_subdev *subdev = &therm->subdev; |
da06b46b BS |
147 | if (!dir || (dir < 0 && fan < therm->cstate) || |
148 | (dir > 0 && fan > therm->cstate)) { | |
b3c418bb | 149 | nvkm_debug(subdev, "default fan speed -> %d%%\n", fan); |
da06b46b | 150 | therm->cstate = fan; |
57113c01 | 151 | nvkm_therm_update(therm, -1); |
6387e2cb BS |
152 | } |
153 | return 0; | |
154 | } | |
155 | ||
694472f4 | 156 | static void |
e1404611 | 157 | nvkm_therm_alarm(struct nvkm_alarm *alarm) |
694472f4 | 158 | { |
57113c01 BS |
159 | struct nvkm_therm *therm = |
160 | container_of(alarm, struct nvkm_therm, alarm); | |
161 | nvkm_therm_update(therm, -1); | |
694472f4 MP |
162 | } |
163 | ||
0083b91d | 164 | int |
57113c01 | 165 | nvkm_therm_fan_mode(struct nvkm_therm *therm, int mode) |
694472f4 | 166 | { |
57113c01 | 167 | struct nvkm_subdev *subdev = &therm->subdev; |
b3c418bb | 168 | struct nvkm_device *device = subdev->device; |
1a22274b BS |
169 | static const char *name[] = { |
170 | "disabled", | |
171 | "manual", | |
172 | "automatic" | |
173 | }; | |
694472f4 | 174 | |
09b8d73b | 175 | /* The default PPWR ucode on fermi interferes with fan management */ |
1a22274b | 176 | if ((mode >= ARRAY_SIZE(name)) || |
e1404611 | 177 | (mode != NVKM_THERM_CTRL_NONE && device->card_type >= NV_C0 && |
57113c01 | 178 | !device->pmu)) |
694472f4 MP |
179 | return -EINVAL; |
180 | ||
98ee7c7c MP |
181 | /* do not allow automatic fan management if the thermal sensor is |
182 | * not available */ | |
da06b46b | 183 | if (mode == NVKM_THERM_CTRL_AUTO && |
57113c01 | 184 | therm->func->temp_get(therm) < 0) |
98ee7c7c MP |
185 | return -EINVAL; |
186 | ||
da06b46b | 187 | if (therm->mode == mode) |
1a22274b | 188 | return 0; |
694472f4 | 189 | |
b3c418bb | 190 | nvkm_debug(subdev, "fan management: %s\n", name[mode]); |
57113c01 | 191 | nvkm_therm_update(therm, mode); |
694472f4 MP |
192 | return 0; |
193 | } | |
194 | ||
aa1b9b48 | 195 | int |
57113c01 | 196 | nvkm_therm_attr_get(struct nvkm_therm *therm, enum nvkm_therm_attr_type type) |
aa1b9b48 | 197 | { |
aa1b9b48 | 198 | switch (type) { |
e1404611 | 199 | case NVKM_THERM_ATTR_FAN_MIN_DUTY: |
da06b46b | 200 | return therm->fan->bios.min_duty; |
e1404611 | 201 | case NVKM_THERM_ATTR_FAN_MAX_DUTY: |
da06b46b | 202 | return therm->fan->bios.max_duty; |
e1404611 | 203 | case NVKM_THERM_ATTR_FAN_MODE: |
da06b46b | 204 | return therm->mode; |
e1404611 | 205 | case NVKM_THERM_ATTR_THRS_FAN_BOOST: |
da06b46b | 206 | return therm->bios_sensor.thrs_fan_boost.temp; |
e1404611 | 207 | case NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST: |
da06b46b | 208 | return therm->bios_sensor.thrs_fan_boost.hysteresis; |
e1404611 | 209 | case NVKM_THERM_ATTR_THRS_DOWN_CLK: |
da06b46b | 210 | return therm->bios_sensor.thrs_down_clock.temp; |
e1404611 | 211 | case NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST: |
da06b46b | 212 | return therm->bios_sensor.thrs_down_clock.hysteresis; |
e1404611 | 213 | case NVKM_THERM_ATTR_THRS_CRITICAL: |
da06b46b | 214 | return therm->bios_sensor.thrs_critical.temp; |
e1404611 | 215 | case NVKM_THERM_ATTR_THRS_CRITICAL_HYST: |
da06b46b | 216 | return therm->bios_sensor.thrs_critical.hysteresis; |
e1404611 | 217 | case NVKM_THERM_ATTR_THRS_SHUTDOWN: |
da06b46b | 218 | return therm->bios_sensor.thrs_shutdown.temp; |
e1404611 | 219 | case NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST: |
da06b46b | 220 | return therm->bios_sensor.thrs_shutdown.hysteresis; |
aa1b9b48 MP |
221 | } |
222 | ||
223 | return -EINVAL; | |
224 | } | |
225 | ||
226 | int | |
57113c01 | 227 | nvkm_therm_attr_set(struct nvkm_therm *therm, |
e1404611 | 228 | enum nvkm_therm_attr_type type, int value) |
aa1b9b48 | 229 | { |
aa1b9b48 | 230 | switch (type) { |
e1404611 | 231 | case NVKM_THERM_ATTR_FAN_MIN_DUTY: |
aa1b9b48 MP |
232 | if (value < 0) |
233 | value = 0; | |
da06b46b BS |
234 | if (value > therm->fan->bios.max_duty) |
235 | value = therm->fan->bios.max_duty; | |
236 | therm->fan->bios.min_duty = value; | |
aa1b9b48 | 237 | return 0; |
e1404611 | 238 | case NVKM_THERM_ATTR_FAN_MAX_DUTY: |
aa1b9b48 MP |
239 | if (value < 0) |
240 | value = 0; | |
da06b46b BS |
241 | if (value < therm->fan->bios.min_duty) |
242 | value = therm->fan->bios.min_duty; | |
243 | therm->fan->bios.max_duty = value; | |
aa1b9b48 | 244 | return 0; |
e1404611 | 245 | case NVKM_THERM_ATTR_FAN_MODE: |
57113c01 | 246 | return nvkm_therm_fan_mode(therm, value); |
e1404611 | 247 | case NVKM_THERM_ATTR_THRS_FAN_BOOST: |
da06b46b | 248 | therm->bios_sensor.thrs_fan_boost.temp = value; |
57113c01 | 249 | therm->func->program_alarms(therm); |
aa1b9b48 | 250 | return 0; |
e1404611 | 251 | case NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST: |
da06b46b | 252 | therm->bios_sensor.thrs_fan_boost.hysteresis = value; |
57113c01 | 253 | therm->func->program_alarms(therm); |
aa1b9b48 | 254 | return 0; |
e1404611 | 255 | case NVKM_THERM_ATTR_THRS_DOWN_CLK: |
da06b46b | 256 | therm->bios_sensor.thrs_down_clock.temp = value; |
57113c01 | 257 | therm->func->program_alarms(therm); |
aa1b9b48 | 258 | return 0; |
e1404611 | 259 | case NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST: |
da06b46b | 260 | therm->bios_sensor.thrs_down_clock.hysteresis = value; |
57113c01 | 261 | therm->func->program_alarms(therm); |
aa1b9b48 | 262 | return 0; |
e1404611 | 263 | case NVKM_THERM_ATTR_THRS_CRITICAL: |
da06b46b | 264 | therm->bios_sensor.thrs_critical.temp = value; |
57113c01 | 265 | therm->func->program_alarms(therm); |
aa1b9b48 | 266 | return 0; |
e1404611 | 267 | case NVKM_THERM_ATTR_THRS_CRITICAL_HYST: |
da06b46b | 268 | therm->bios_sensor.thrs_critical.hysteresis = value; |
57113c01 | 269 | therm->func->program_alarms(therm); |
aa1b9b48 | 270 | return 0; |
e1404611 | 271 | case NVKM_THERM_ATTR_THRS_SHUTDOWN: |
da06b46b | 272 | therm->bios_sensor.thrs_shutdown.temp = value; |
57113c01 | 273 | therm->func->program_alarms(therm); |
aa1b9b48 | 274 | return 0; |
e1404611 | 275 | case NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST: |
da06b46b | 276 | therm->bios_sensor.thrs_shutdown.hysteresis = value; |
57113c01 | 277 | therm->func->program_alarms(therm); |
aa1b9b48 MP |
278 | return 0; |
279 | } | |
280 | ||
281 | return -EINVAL; | |
282 | } | |
283 | ||
57113c01 BS |
284 | static void |
285 | nvkm_therm_intr(struct nvkm_subdev *subdev) | |
aa1b9b48 | 286 | { |
57113c01 BS |
287 | struct nvkm_therm *therm = nvkm_therm(subdev); |
288 | if (therm->func->intr) | |
289 | therm->func->intr(therm); | |
aa1b9b48 MP |
290 | } |
291 | ||
57113c01 BS |
292 | static int |
293 | nvkm_therm_fini(struct nvkm_subdev *subdev, bool suspend) | |
aa1b9b48 | 294 | { |
57113c01 BS |
295 | struct nvkm_therm *therm = nvkm_therm(subdev); |
296 | ||
297 | if (therm->func->fini) | |
298 | therm->func->fini(therm); | |
299 | ||
300 | nvkm_therm_fan_fini(therm, suspend); | |
301 | nvkm_therm_sensor_fini(therm, suspend); | |
aa1b9b48 | 302 | |
1a22274b | 303 | if (suspend) { |
da06b46b BS |
304 | therm->suspend = therm->mode; |
305 | therm->mode = NVKM_THERM_CTRL_NONE; | |
1a22274b | 306 | } |
aa1b9b48 | 307 | |
5f066c32 BS |
308 | return 0; |
309 | } | |
9c3bd3a5 | 310 | |
57113c01 BS |
311 | static int |
312 | nvkm_therm_oneinit(struct nvkm_subdev *subdev) | |
9c3bd3a5 | 313 | { |
57113c01 | 314 | struct nvkm_therm *therm = nvkm_therm(subdev); |
e1404611 BS |
315 | nvkm_therm_sensor_ctor(therm); |
316 | nvkm_therm_ic_ctor(therm); | |
317 | nvkm_therm_fan_ctor(therm); | |
e1404611 BS |
318 | nvkm_therm_fan_mode(therm, NVKM_THERM_CTRL_AUTO); |
319 | nvkm_therm_sensor_preinit(therm); | |
9c3bd3a5 BS |
320 | return 0; |
321 | } | |
322 | ||
57113c01 BS |
323 | static int |
324 | nvkm_therm_init(struct nvkm_subdev *subdev) | |
325 | { | |
326 | struct nvkm_therm *therm = nvkm_therm(subdev); | |
327 | ||
328 | therm->func->init(therm); | |
329 | ||
330 | if (therm->suspend >= 0) { | |
331 | /* restore the pwm value only when on manual or auto mode */ | |
332 | if (therm->suspend > 0) | |
333 | nvkm_therm_fan_set(therm, true, therm->fan->percent); | |
334 | ||
335 | nvkm_therm_fan_mode(therm, therm->suspend); | |
336 | } | |
337 | ||
338 | nvkm_therm_sensor_init(therm); | |
339 | nvkm_therm_fan_init(therm); | |
340 | return 0; | |
341 | } | |
342 | ||
343 | static void * | |
344 | nvkm_therm_dtor(struct nvkm_subdev *subdev) | |
9c3bd3a5 | 345 | { |
57113c01 | 346 | struct nvkm_therm *therm = nvkm_therm(subdev); |
da06b46b | 347 | kfree(therm->fan); |
57113c01 BS |
348 | return therm; |
349 | } | |
350 | ||
351 | static const struct nvkm_subdev_func | |
352 | nvkm_therm = { | |
353 | .dtor = nvkm_therm_dtor, | |
354 | .oneinit = nvkm_therm_oneinit, | |
355 | .init = nvkm_therm_init, | |
356 | .fini = nvkm_therm_fini, | |
357 | .intr = nvkm_therm_intr, | |
358 | }; | |
359 | ||
360 | int | |
361 | nvkm_therm_new_(const struct nvkm_therm_func *func, struct nvkm_device *device, | |
362 | int index, struct nvkm_therm **ptherm) | |
363 | { | |
364 | struct nvkm_therm *therm; | |
365 | ||
366 | if (!(therm = *ptherm = kzalloc(sizeof(*therm), GFP_KERNEL))) | |
367 | return -ENOMEM; | |
368 | ||
369 | nvkm_subdev_ctor(&nvkm_therm, device, index, 0, &therm->subdev); | |
370 | therm->func = func; | |
371 | ||
372 | nvkm_alarm_init(&therm->alarm, nvkm_therm_alarm); | |
373 | spin_lock_init(&therm->lock); | |
374 | spin_lock_init(&therm->sensor.alarm_program_lock); | |
375 | ||
376 | therm->fan_get = nvkm_therm_fan_user_get; | |
377 | therm->fan_set = nvkm_therm_fan_user_set; | |
378 | therm->attr_get = nvkm_therm_attr_get; | |
379 | therm->attr_set = nvkm_therm_attr_set; | |
380 | therm->mode = therm->suspend = -1; /* undefined */ | |
381 | return 0; | |
9c3bd3a5 | 382 | } |