diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/iwl-power.c')
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-power.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-power.c b/drivers/net/wireless/iwlwifi/iwl-power.c index f2ea3f05f6e1..d7fdb5825450 100644 --- a/drivers/net/wireless/iwlwifi/iwl-power.c +++ b/drivers/net/wireless/iwlwifi/iwl-power.c | |||
@@ -36,6 +36,7 @@ | |||
36 | #include "iwl-eeprom.h" | 36 | #include "iwl-eeprom.h" |
37 | #include "iwl-dev.h" | 37 | #include "iwl-dev.h" |
38 | #include "iwl-core.h" | 38 | #include "iwl-core.h" |
39 | #include "iwl-io.h" | ||
39 | #include "iwl-commands.h" | 40 | #include "iwl-commands.h" |
40 | #include "iwl-debug.h" | 41 | #include "iwl-debug.h" |
41 | #include "iwl-power.h" | 42 | #include "iwl-power.h" |
@@ -211,6 +212,7 @@ int iwl_power_update_mode(struct iwl_priv *priv, bool force) | |||
211 | { | 212 | { |
212 | struct iwl_power_mgr *setting = &(priv->power_data); | 213 | struct iwl_power_mgr *setting = &(priv->power_data); |
213 | int ret = 0; | 214 | int ret = 0; |
215 | struct iwl_tt_mgmt *tt = &priv->power_data.tt; | ||
214 | u16 uninitialized_var(final_mode); | 216 | u16 uninitialized_var(final_mode); |
215 | bool update_chains; | 217 | bool update_chains; |
216 | 218 | ||
@@ -223,6 +225,10 @@ int iwl_power_update_mode(struct iwl_priv *priv, bool force) | |||
223 | if (setting->power_disabled) | 225 | if (setting->power_disabled) |
224 | final_mode = IWL_POWER_MODE_CAM; | 226 | final_mode = IWL_POWER_MODE_CAM; |
225 | 227 | ||
228 | if (tt->state >= IWL_TI_1) { | ||
229 | /* TT power setting overwrite user & system power setting */ | ||
230 | final_mode = tt->tt_power_mode; | ||
231 | } | ||
226 | if (iwl_is_ready_rf(priv) && | 232 | if (iwl_is_ready_rf(priv) && |
227 | ((setting->power_mode != final_mode) || force)) { | 233 | ((setting->power_mode != final_mode) || force)) { |
228 | struct iwl_powertable_cmd cmd; | 234 | struct iwl_powertable_cmd cmd; |
@@ -267,6 +273,249 @@ int iwl_power_set_user_mode(struct iwl_priv *priv, u16 mode) | |||
267 | } | 273 | } |
268 | EXPORT_SYMBOL(iwl_power_set_user_mode); | 274 | EXPORT_SYMBOL(iwl_power_set_user_mode); |
269 | 275 | ||
276 | #define CT_KILL_EXIT_DURATION (5) /* 5 seconds duration */ | ||
277 | |||
278 | /* | ||
279 | * toggle the bit to wake up uCode and check the temperature | ||
280 | * if the temperature is below CT, uCode will stay awake and send card | ||
281 | * state notification with CT_KILL bit clear to inform Thermal Throttling | ||
282 | * Management to change state. Otherwise, uCode will go back to sleep | ||
283 | * without doing anything, driver should continue the 5 seconds timer | ||
284 | * to wake up uCode for temperature check until temperature drop below CT | ||
285 | */ | ||
286 | static void iwl_tt_check_exit_ct_kill(unsigned long data) | ||
287 | { | ||
288 | struct iwl_priv *priv = (struct iwl_priv *)data; | ||
289 | struct iwl_tt_mgmt *tt = &priv->power_data.tt; | ||
290 | unsigned long flags; | ||
291 | |||
292 | if (test_bit(STATUS_EXIT_PENDING, &priv->status)) | ||
293 | return; | ||
294 | |||
295 | if (tt->state == IWL_TI_CT_KILL) { | ||
296 | if (priv->power_data.ct_kill_toggle) { | ||
297 | iwl_write32(priv, CSR_UCODE_DRV_GP1_CLR, | ||
298 | CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); | ||
299 | priv->power_data.ct_kill_toggle = false; | ||
300 | } else { | ||
301 | iwl_write32(priv, CSR_UCODE_DRV_GP1_SET, | ||
302 | CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); | ||
303 | priv->power_data.ct_kill_toggle = true; | ||
304 | } | ||
305 | iwl_read32(priv, CSR_UCODE_DRV_GP1); | ||
306 | spin_lock_irqsave(&priv->reg_lock, flags); | ||
307 | if (!iwl_grab_nic_access(priv)) | ||
308 | iwl_release_nic_access(priv); | ||
309 | spin_unlock_irqrestore(&priv->reg_lock, flags); | ||
310 | |||
311 | /* Reschedule the ct_kill timer to occur in | ||
312 | * CT_KILL_EXIT_DURATION seconds to ensure we get a | ||
313 | * thermal update */ | ||
314 | mod_timer(&priv->power_data.ct_kill_exit_tm, jiffies + | ||
315 | CT_KILL_EXIT_DURATION * HZ); | ||
316 | } | ||
317 | } | ||
318 | |||
319 | static void iwl_perform_ct_kill_task(struct iwl_priv *priv, | ||
320 | bool stop) | ||
321 | { | ||
322 | if (stop) { | ||
323 | IWL_DEBUG_POWER(priv, "Stop all queues\n"); | ||
324 | if (priv->mac80211_registered) | ||
325 | ieee80211_stop_queues(priv->hw); | ||
326 | IWL_DEBUG_POWER(priv, | ||
327 | "Schedule 5 seconds CT_KILL Timer\n"); | ||
328 | mod_timer(&priv->power_data.ct_kill_exit_tm, jiffies + | ||
329 | CT_KILL_EXIT_DURATION * HZ); | ||
330 | } else { | ||
331 | IWL_DEBUG_POWER(priv, "Wake all queues\n"); | ||
332 | if (priv->mac80211_registered) | ||
333 | ieee80211_wake_queues(priv->hw); | ||
334 | } | ||
335 | } | ||
336 | |||
337 | #define IWL_MINIMAL_POWER_THRESHOLD (CT_KILL_THRESHOLD_LEGACY) | ||
338 | #define IWL_REDUCED_PERFORMANCE_THRESHOLD_2 (100) | ||
339 | #define IWL_REDUCED_PERFORMANCE_THRESHOLD_1 (90) | ||
340 | |||
341 | /* | ||
342 | * Legacy thermal throttling | ||
343 | * 1) Avoid NIC destruction due to high temperatures | ||
344 | * Chip will identify dangerously high temperatures that can | ||
345 | * harm the device and will power down | ||
346 | * 2) Avoid the NIC power down due to high temperature | ||
347 | * Throttle early enough to lower the power consumption before | ||
348 | * drastic steps are needed | ||
349 | */ | ||
350 | static void iwl_legacy_tt_handler(struct iwl_priv *priv, s32 temp) | ||
351 | { | ||
352 | struct iwl_tt_mgmt *tt = &priv->power_data.tt; | ||
353 | enum iwl_tt_state new_state; | ||
354 | struct iwl_power_mgr *setting = &priv->power_data; | ||
355 | |||
356 | #ifdef CONFIG_IWLWIFI_DEBUG | ||
357 | if ((tt->tt_previous_temp) && | ||
358 | (temp > tt->tt_previous_temp) && | ||
359 | ((temp - tt->tt_previous_temp) > | ||
360 | IWL_TT_INCREASE_MARGIN)) { | ||
361 | IWL_DEBUG_POWER(priv, | ||
362 | "Temperature increase %d degree Celsius\n", | ||
363 | (temp - tt->tt_previous_temp)); | ||
364 | } | ||
365 | #endif | ||
366 | /* in Celsius */ | ||
367 | if (temp >= IWL_MINIMAL_POWER_THRESHOLD) | ||
368 | new_state = IWL_TI_CT_KILL; | ||
369 | else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_2) | ||
370 | new_state = IWL_TI_2; | ||
371 | else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_1) | ||
372 | new_state = IWL_TI_1; | ||
373 | else | ||
374 | new_state = IWL_TI_0; | ||
375 | |||
376 | #ifdef CONFIG_IWLWIFI_DEBUG | ||
377 | tt->tt_previous_temp = temp; | ||
378 | #endif | ||
379 | if (tt->state != new_state) { | ||
380 | if (tt->state == IWL_TI_0) { | ||
381 | tt->sys_power_mode = setting->power_mode; | ||
382 | IWL_DEBUG_POWER(priv, "current power mode: %u\n", | ||
383 | setting->power_mode); | ||
384 | } | ||
385 | switch (new_state) { | ||
386 | case IWL_TI_0: | ||
387 | /* when system ready to go back to IWL_TI_0 state | ||
388 | * using system power mode instead of TT power mode | ||
389 | * revert back to the orginal power mode which was saved | ||
390 | * before enter Thermal Throttling state | ||
391 | * update priv->power_data.user_power_setting to the | ||
392 | * required power mode to make sure | ||
393 | * iwl_power_update_mode() will update power correctly. | ||
394 | */ | ||
395 | priv->power_data.user_power_setting = | ||
396 | tt->sys_power_mode; | ||
397 | tt->tt_power_mode = tt->sys_power_mode; | ||
398 | break; | ||
399 | case IWL_TI_1: | ||
400 | tt->tt_power_mode = IWL_POWER_INDEX_3; | ||
401 | break; | ||
402 | case IWL_TI_2: | ||
403 | tt->tt_power_mode = IWL_POWER_INDEX_4; | ||
404 | break; | ||
405 | default: | ||
406 | tt->tt_power_mode = IWL_POWER_INDEX_5; | ||
407 | break; | ||
408 | } | ||
409 | if (iwl_power_update_mode(priv, true)) { | ||
410 | /* TT state not updated | ||
411 | * try again during next temperature read | ||
412 | */ | ||
413 | IWL_ERR(priv, "Cannot update power mode, " | ||
414 | "TT state not updated\n"); | ||
415 | } else { | ||
416 | if (new_state == IWL_TI_CT_KILL) | ||
417 | iwl_perform_ct_kill_task(priv, true); | ||
418 | else if (tt->state == IWL_TI_CT_KILL && | ||
419 | new_state != IWL_TI_CT_KILL) | ||
420 | iwl_perform_ct_kill_task(priv, false); | ||
421 | tt->state = new_state; | ||
422 | IWL_DEBUG_POWER(priv, "Temperature state changed %u\n", | ||
423 | tt->state); | ||
424 | IWL_DEBUG_POWER(priv, "Power Index change to %u\n", | ||
425 | tt->tt_power_mode); | ||
426 | } | ||
427 | } | ||
428 | } | ||
429 | |||
430 | /* Card State Notification indicated reach critical temperature | ||
431 | * if PSP not enable, no Thermal Throttling function will be performed | ||
432 | * just set the GP1 bit to acknowledge the event | ||
433 | * otherwise, go into IWL_TI_CT_KILL state | ||
434 | * since Card State Notification will not provide any temperature reading | ||
435 | * so just pass the CT_KILL temperature to iwl_legacy_tt_handler() | ||
436 | */ | ||
437 | void iwl_tt_enter_ct_kill(struct iwl_priv *priv) | ||
438 | { | ||
439 | struct iwl_tt_mgmt *tt = &priv->power_data.tt; | ||
440 | |||
441 | if (test_bit(STATUS_EXIT_PENDING, &priv->status)) | ||
442 | return; | ||
443 | |||
444 | if (tt->state != IWL_TI_CT_KILL) { | ||
445 | IWL_ERR(priv, "Device reached critical temperature " | ||
446 | "- ucode going to sleep!\n"); | ||
447 | iwl_legacy_tt_handler(priv, IWL_MINIMAL_POWER_THRESHOLD); | ||
448 | } | ||
449 | } | ||
450 | EXPORT_SYMBOL(iwl_tt_enter_ct_kill); | ||
451 | |||
452 | /* Card State Notification indicated out of critical temperature | ||
453 | * since Card State Notification will not provide any temperature reading | ||
454 | * so pass the IWL_REDUCED_PERFORMANCE_THRESHOLD_2 temperature | ||
455 | * to iwl_legacy_tt_handler() to get out of IWL_CT_KILL state | ||
456 | */ | ||
457 | void iwl_tt_exit_ct_kill(struct iwl_priv *priv) | ||
458 | { | ||
459 | struct iwl_tt_mgmt *tt = &priv->power_data.tt; | ||
460 | |||
461 | if (test_bit(STATUS_EXIT_PENDING, &priv->status)) | ||
462 | return; | ||
463 | |||
464 | /* stop ct_kill_exit_tm timer */ | ||
465 | del_timer_sync(&priv->power_data.ct_kill_exit_tm); | ||
466 | |||
467 | if (tt->state == IWL_TI_CT_KILL) { | ||
468 | IWL_ERR(priv, | ||
469 | "Device temperature below critical" | ||
470 | "- ucode awake!\n"); | ||
471 | iwl_legacy_tt_handler(priv, | ||
472 | IWL_REDUCED_PERFORMANCE_THRESHOLD_2); | ||
473 | } | ||
474 | } | ||
475 | EXPORT_SYMBOL(iwl_tt_exit_ct_kill); | ||
476 | |||
477 | void iwl_tt_handler(struct iwl_priv *priv) | ||
478 | { | ||
479 | s32 temp = priv->temperature; /* degrees CELSIUS except 4965 */ | ||
480 | |||
481 | if (test_bit(STATUS_EXIT_PENDING, &priv->status)) | ||
482 | return; | ||
483 | |||
484 | if ((priv->hw_rev & CSR_HW_REV_TYPE_MSK) == CSR_HW_REV_TYPE_4965) | ||
485 | temp = KELVIN_TO_CELSIUS(priv->temperature); | ||
486 | |||
487 | iwl_legacy_tt_handler(priv, temp); | ||
488 | } | ||
489 | EXPORT_SYMBOL(iwl_tt_handler); | ||
490 | |||
491 | /* Thermal throttling initialization | ||
492 | */ | ||
493 | void iwl_tt_initialize(struct iwl_priv *priv) | ||
494 | { | ||
495 | struct iwl_tt_mgmt *tt = &priv->power_data.tt; | ||
496 | struct iwl_power_mgr *setting = &priv->power_data; | ||
497 | |||
498 | IWL_DEBUG_POWER(priv, "Initialize Thermal Throttling \n"); | ||
499 | |||
500 | memset(tt, 0, sizeof(struct iwl_tt_mgmt)); | ||
501 | |||
502 | tt->state = IWL_TI_0; | ||
503 | tt->sys_power_mode = setting->power_mode; | ||
504 | tt->tt_power_mode = tt->sys_power_mode; | ||
505 | init_timer(&priv->power_data.ct_kill_exit_tm); | ||
506 | priv->power_data.ct_kill_exit_tm.data = (unsigned long)priv; | ||
507 | priv->power_data.ct_kill_exit_tm.function = iwl_tt_check_exit_ct_kill; | ||
508 | } | ||
509 | EXPORT_SYMBOL(iwl_tt_initialize); | ||
510 | |||
511 | /* cleanup thermal throttling management related memory and timer */ | ||
512 | void iwl_tt_exit(struct iwl_priv *priv) | ||
513 | { | ||
514 | /* stop ct_kill_exit_tm timer if activated */ | ||
515 | del_timer_sync(&priv->power_data.ct_kill_exit_tm); | ||
516 | } | ||
517 | EXPORT_SYMBOL(iwl_tt_exit); | ||
518 | |||
270 | /* initialize to default */ | 519 | /* initialize to default */ |
271 | void iwl_power_initialize(struct iwl_priv *priv) | 520 | void iwl_power_initialize(struct iwl_priv *priv) |
272 | { | 521 | { |