| 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 | |