1*d1e879ecSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2*d1e879ecSMiri Korenblit /* 3*d1e879ecSMiri Korenblit * Copyright (C) 2024-2025 Intel Corporation 4*d1e879ecSMiri Korenblit */ 5*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 6*d1e879ecSMiri Korenblit #include <linux/sort.h> 7*d1e879ecSMiri Korenblit #include <linux/thermal.h> 8*d1e879ecSMiri Korenblit #endif 9*d1e879ecSMiri Korenblit 10*d1e879ecSMiri Korenblit #include "fw/api/phy.h" 11*d1e879ecSMiri Korenblit 12*d1e879ecSMiri Korenblit #include "thermal.h" 13*d1e879ecSMiri Korenblit #include "mld.h" 14*d1e879ecSMiri Korenblit #include "hcmd.h" 15*d1e879ecSMiri Korenblit 16*d1e879ecSMiri Korenblit #define IWL_MLD_CT_KILL_DURATION (5 * HZ) 17*d1e879ecSMiri Korenblit 18*d1e879ecSMiri Korenblit void iwl_mld_handle_ct_kill_notif(struct iwl_mld *mld, 19*d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt) 20*d1e879ecSMiri Korenblit { 21*d1e879ecSMiri Korenblit const struct ct_kill_notif *notif = (const void *)pkt->data; 22*d1e879ecSMiri Korenblit 23*d1e879ecSMiri Korenblit IWL_ERR(mld, 24*d1e879ecSMiri Korenblit "CT Kill notification: temp = %d, DTS = 0x%x, Scheme 0x%x - Enter CT Kill\n", 25*d1e879ecSMiri Korenblit le16_to_cpu(notif->temperature), notif->dts, 26*d1e879ecSMiri Korenblit notif->scheme); 27*d1e879ecSMiri Korenblit 28*d1e879ecSMiri Korenblit iwl_mld_set_ctkill(mld, true); 29*d1e879ecSMiri Korenblit 30*d1e879ecSMiri Korenblit wiphy_delayed_work_queue(mld->wiphy, &mld->ct_kill_exit_wk, 31*d1e879ecSMiri Korenblit round_jiffies_relative(IWL_MLD_CT_KILL_DURATION)); 32*d1e879ecSMiri Korenblit } 33*d1e879ecSMiri Korenblit 34*d1e879ecSMiri Korenblit static void iwl_mld_exit_ctkill(struct wiphy *wiphy, struct wiphy_work *wk) 35*d1e879ecSMiri Korenblit { 36*d1e879ecSMiri Korenblit struct iwl_mld *mld; 37*d1e879ecSMiri Korenblit 38*d1e879ecSMiri Korenblit mld = container_of(wk, struct iwl_mld, ct_kill_exit_wk.work); 39*d1e879ecSMiri Korenblit 40*d1e879ecSMiri Korenblit IWL_ERR(mld, "Exit CT Kill\n"); 41*d1e879ecSMiri Korenblit iwl_mld_set_ctkill(mld, false); 42*d1e879ecSMiri Korenblit } 43*d1e879ecSMiri Korenblit 44*d1e879ecSMiri Korenblit void iwl_mld_handle_temp_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) 45*d1e879ecSMiri Korenblit { 46*d1e879ecSMiri Korenblit const struct iwl_dts_measurement_notif *notif = 47*d1e879ecSMiri Korenblit (const void *)pkt->data; 48*d1e879ecSMiri Korenblit int temp; 49*d1e879ecSMiri Korenblit u32 ths_crossed; 50*d1e879ecSMiri Korenblit 51*d1e879ecSMiri Korenblit temp = le32_to_cpu(notif->temp); 52*d1e879ecSMiri Korenblit 53*d1e879ecSMiri Korenblit /* shouldn't be negative, but since it's s32, make sure it isn't */ 54*d1e879ecSMiri Korenblit if (IWL_FW_CHECK(mld, temp < 0, "negative temperature %d\n", temp)) 55*d1e879ecSMiri Korenblit return; 56*d1e879ecSMiri Korenblit 57*d1e879ecSMiri Korenblit ths_crossed = le32_to_cpu(notif->threshold_idx); 58*d1e879ecSMiri Korenblit 59*d1e879ecSMiri Korenblit /* 0xFF in ths_crossed means the notification is not related 60*d1e879ecSMiri Korenblit * to a trip, so we can ignore it here. 61*d1e879ecSMiri Korenblit */ 62*d1e879ecSMiri Korenblit if (ths_crossed == 0xFF) 63*d1e879ecSMiri Korenblit return; 64*d1e879ecSMiri Korenblit 65*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, "Temp = %d Threshold crossed = %d\n", 66*d1e879ecSMiri Korenblit temp, ths_crossed); 67*d1e879ecSMiri Korenblit 68*d1e879ecSMiri Korenblit if (IWL_FW_CHECK(mld, ths_crossed >= IWL_MAX_DTS_TRIPS, 69*d1e879ecSMiri Korenblit "bad threshold: %d\n", ths_crossed)) 70*d1e879ecSMiri Korenblit return; 71*d1e879ecSMiri Korenblit 72*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 73*d1e879ecSMiri Korenblit if (mld->tzone) 74*d1e879ecSMiri Korenblit thermal_zone_device_update(mld->tzone, THERMAL_TRIP_VIOLATED); 75*d1e879ecSMiri Korenblit #endif /* CONFIG_THERMAL */ 76*d1e879ecSMiri Korenblit } 77*d1e879ecSMiri Korenblit 78*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 79*d1e879ecSMiri Korenblit static int iwl_mld_get_temp(struct iwl_mld *mld, s32 *temp) 80*d1e879ecSMiri Korenblit { 81*d1e879ecSMiri Korenblit struct iwl_host_cmd cmd = { 82*d1e879ecSMiri Korenblit .id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE), 83*d1e879ecSMiri Korenblit .flags = CMD_WANT_SKB, 84*d1e879ecSMiri Korenblit }; 85*d1e879ecSMiri Korenblit const struct iwl_dts_measurement_resp *resp; 86*d1e879ecSMiri Korenblit int ret; 87*d1e879ecSMiri Korenblit 88*d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 89*d1e879ecSMiri Korenblit 90*d1e879ecSMiri Korenblit ret = iwl_mld_send_cmd(mld, &cmd); 91*d1e879ecSMiri Korenblit if (ret) { 92*d1e879ecSMiri Korenblit IWL_ERR(mld, 93*d1e879ecSMiri Korenblit "Failed to send the temperature measurement command (err=%d)\n", 94*d1e879ecSMiri Korenblit ret); 95*d1e879ecSMiri Korenblit return ret; 96*d1e879ecSMiri Korenblit } 97*d1e879ecSMiri Korenblit 98*d1e879ecSMiri Korenblit if (iwl_rx_packet_payload_len(cmd.resp_pkt) < sizeof(*resp)) { 99*d1e879ecSMiri Korenblit IWL_ERR(mld, 100*d1e879ecSMiri Korenblit "Failed to get a valid response to DTS measurement\n"); 101*d1e879ecSMiri Korenblit ret = -EIO; 102*d1e879ecSMiri Korenblit goto free_resp; 103*d1e879ecSMiri Korenblit } 104*d1e879ecSMiri Korenblit 105*d1e879ecSMiri Korenblit resp = (const void *)cmd.resp_pkt->data; 106*d1e879ecSMiri Korenblit *temp = le32_to_cpu(resp->temp); 107*d1e879ecSMiri Korenblit 108*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, 109*d1e879ecSMiri Korenblit "Got temperature measurement response: temp=%d\n", 110*d1e879ecSMiri Korenblit *temp); 111*d1e879ecSMiri Korenblit 112*d1e879ecSMiri Korenblit free_resp: 113*d1e879ecSMiri Korenblit iwl_free_resp(&cmd); 114*d1e879ecSMiri Korenblit return ret; 115*d1e879ecSMiri Korenblit } 116*d1e879ecSMiri Korenblit 117*d1e879ecSMiri Korenblit static int compare_temps(const void *a, const void *b) 118*d1e879ecSMiri Korenblit { 119*d1e879ecSMiri Korenblit return ((s16)le16_to_cpu(*(__le16 *)a) - 120*d1e879ecSMiri Korenblit (s16)le16_to_cpu(*(__le16 *)b)); 121*d1e879ecSMiri Korenblit } 122*d1e879ecSMiri Korenblit 123*d1e879ecSMiri Korenblit struct iwl_trip_walk_data { 124*d1e879ecSMiri Korenblit __le16 *thresholds; 125*d1e879ecSMiri Korenblit int count; 126*d1e879ecSMiri Korenblit }; 127*d1e879ecSMiri Korenblit 128*d1e879ecSMiri Korenblit static int iwl_trip_temp_iter(struct thermal_trip *trip, void *arg) 129*d1e879ecSMiri Korenblit { 130*d1e879ecSMiri Korenblit struct iwl_trip_walk_data *twd = arg; 131*d1e879ecSMiri Korenblit 132*d1e879ecSMiri Korenblit if (trip->temperature == THERMAL_TEMP_INVALID) 133*d1e879ecSMiri Korenblit return 0; 134*d1e879ecSMiri Korenblit 135*d1e879ecSMiri Korenblit twd->thresholds[twd->count++] = 136*d1e879ecSMiri Korenblit cpu_to_le16((s16)(trip->temperature / 1000)); 137*d1e879ecSMiri Korenblit return 0; 138*d1e879ecSMiri Korenblit } 139*d1e879ecSMiri Korenblit #endif 140*d1e879ecSMiri Korenblit 141*d1e879ecSMiri Korenblit int iwl_mld_config_temp_report_ths(struct iwl_mld *mld) 142*d1e879ecSMiri Korenblit { 143*d1e879ecSMiri Korenblit struct temp_report_ths_cmd cmd = {0}; 144*d1e879ecSMiri Korenblit int ret; 145*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 146*d1e879ecSMiri Korenblit struct iwl_trip_walk_data twd = { 147*d1e879ecSMiri Korenblit .thresholds = cmd.thresholds, 148*d1e879ecSMiri Korenblit .count = 0 149*d1e879ecSMiri Korenblit }; 150*d1e879ecSMiri Korenblit 151*d1e879ecSMiri Korenblit if (!mld->tzone) 152*d1e879ecSMiri Korenblit goto send; 153*d1e879ecSMiri Korenblit 154*d1e879ecSMiri Korenblit /* The thermal core holds an array of temperature trips that are 155*d1e879ecSMiri Korenblit * unsorted and uncompressed, the FW should get it compressed and 156*d1e879ecSMiri Korenblit * sorted. 157*d1e879ecSMiri Korenblit */ 158*d1e879ecSMiri Korenblit 159*d1e879ecSMiri Korenblit /* compress trips to cmd array, remove uninitialized values*/ 160*d1e879ecSMiri Korenblit for_each_thermal_trip(mld->tzone, iwl_trip_temp_iter, &twd); 161*d1e879ecSMiri Korenblit 162*d1e879ecSMiri Korenblit cmd.num_temps = cpu_to_le32(twd.count); 163*d1e879ecSMiri Korenblit if (twd.count) 164*d1e879ecSMiri Korenblit sort(cmd.thresholds, twd.count, sizeof(s16), 165*d1e879ecSMiri Korenblit compare_temps, NULL); 166*d1e879ecSMiri Korenblit 167*d1e879ecSMiri Korenblit send: 168*d1e879ecSMiri Korenblit #endif 169*d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 170*d1e879ecSMiri Korenblit 171*d1e879ecSMiri Korenblit ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP, 172*d1e879ecSMiri Korenblit TEMP_REPORTING_THRESHOLDS_CMD), 173*d1e879ecSMiri Korenblit &cmd); 174*d1e879ecSMiri Korenblit if (ret) 175*d1e879ecSMiri Korenblit IWL_ERR(mld, "TEMP_REPORT_THS_CMD command failed (err=%d)\n", 176*d1e879ecSMiri Korenblit ret); 177*d1e879ecSMiri Korenblit 178*d1e879ecSMiri Korenblit return ret; 179*d1e879ecSMiri Korenblit } 180*d1e879ecSMiri Korenblit 181*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 182*d1e879ecSMiri Korenblit static int iwl_mld_tzone_get_temp(struct thermal_zone_device *device, 183*d1e879ecSMiri Korenblit int *temperature) 184*d1e879ecSMiri Korenblit { 185*d1e879ecSMiri Korenblit struct iwl_mld *mld = thermal_zone_device_priv(device); 186*d1e879ecSMiri Korenblit int temp; 187*d1e879ecSMiri Korenblit int ret = 0; 188*d1e879ecSMiri Korenblit 189*d1e879ecSMiri Korenblit wiphy_lock(mld->wiphy); 190*d1e879ecSMiri Korenblit 191*d1e879ecSMiri Korenblit if (!mld->fw_status.running) { 192*d1e879ecSMiri Korenblit /* Tell the core that there is no valid temperature value to 193*d1e879ecSMiri Korenblit * return, but it need not worry about this. 194*d1e879ecSMiri Korenblit */ 195*d1e879ecSMiri Korenblit *temperature = THERMAL_TEMP_INVALID; 196*d1e879ecSMiri Korenblit goto unlock; 197*d1e879ecSMiri Korenblit } 198*d1e879ecSMiri Korenblit 199*d1e879ecSMiri Korenblit ret = iwl_mld_get_temp(mld, &temp); 200*d1e879ecSMiri Korenblit if (ret) 201*d1e879ecSMiri Korenblit goto unlock; 202*d1e879ecSMiri Korenblit 203*d1e879ecSMiri Korenblit *temperature = temp * 1000; 204*d1e879ecSMiri Korenblit unlock: 205*d1e879ecSMiri Korenblit wiphy_unlock(mld->wiphy); 206*d1e879ecSMiri Korenblit return ret; 207*d1e879ecSMiri Korenblit } 208*d1e879ecSMiri Korenblit 209*d1e879ecSMiri Korenblit static int iwl_mld_tzone_set_trip_temp(struct thermal_zone_device *device, 210*d1e879ecSMiri Korenblit const struct thermal_trip *trip, 211*d1e879ecSMiri Korenblit int temp) 212*d1e879ecSMiri Korenblit { 213*d1e879ecSMiri Korenblit struct iwl_mld *mld = thermal_zone_device_priv(device); 214*d1e879ecSMiri Korenblit int ret; 215*d1e879ecSMiri Korenblit 216*d1e879ecSMiri Korenblit wiphy_lock(mld->wiphy); 217*d1e879ecSMiri Korenblit 218*d1e879ecSMiri Korenblit if (!mld->fw_status.running) { 219*d1e879ecSMiri Korenblit ret = -EIO; 220*d1e879ecSMiri Korenblit goto unlock; 221*d1e879ecSMiri Korenblit } 222*d1e879ecSMiri Korenblit 223*d1e879ecSMiri Korenblit if ((temp / 1000) > S16_MAX) { 224*d1e879ecSMiri Korenblit ret = -EINVAL; 225*d1e879ecSMiri Korenblit goto unlock; 226*d1e879ecSMiri Korenblit } 227*d1e879ecSMiri Korenblit 228*d1e879ecSMiri Korenblit ret = iwl_mld_config_temp_report_ths(mld); 229*d1e879ecSMiri Korenblit unlock: 230*d1e879ecSMiri Korenblit wiphy_unlock(mld->wiphy); 231*d1e879ecSMiri Korenblit return ret; 232*d1e879ecSMiri Korenblit } 233*d1e879ecSMiri Korenblit 234*d1e879ecSMiri Korenblit static struct thermal_zone_device_ops tzone_ops = { 235*d1e879ecSMiri Korenblit .get_temp = iwl_mld_tzone_get_temp, 236*d1e879ecSMiri Korenblit .set_trip_temp = iwl_mld_tzone_set_trip_temp, 237*d1e879ecSMiri Korenblit }; 238*d1e879ecSMiri Korenblit 239*d1e879ecSMiri Korenblit static void iwl_mld_thermal_zone_register(struct iwl_mld *mld) 240*d1e879ecSMiri Korenblit { 241*d1e879ecSMiri Korenblit int ret; 242*d1e879ecSMiri Korenblit char name[16]; 243*d1e879ecSMiri Korenblit static atomic_t counter = ATOMIC_INIT(0); 244*d1e879ecSMiri Korenblit struct thermal_trip trips[IWL_MAX_DTS_TRIPS] = { 245*d1e879ecSMiri Korenblit [0 ... IWL_MAX_DTS_TRIPS - 1] = { 246*d1e879ecSMiri Korenblit .temperature = THERMAL_TEMP_INVALID, 247*d1e879ecSMiri Korenblit .type = THERMAL_TRIP_PASSIVE, 248*d1e879ecSMiri Korenblit .flags = THERMAL_TRIP_FLAG_RW_TEMP, 249*d1e879ecSMiri Korenblit }, 250*d1e879ecSMiri Korenblit }; 251*d1e879ecSMiri Korenblit 252*d1e879ecSMiri Korenblit BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); 253*d1e879ecSMiri Korenblit 254*d1e879ecSMiri Korenblit sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF); 255*d1e879ecSMiri Korenblit mld->tzone = 256*d1e879ecSMiri Korenblit thermal_zone_device_register_with_trips(name, trips, 257*d1e879ecSMiri Korenblit IWL_MAX_DTS_TRIPS, 258*d1e879ecSMiri Korenblit mld, &tzone_ops, 259*d1e879ecSMiri Korenblit NULL, 0, 0); 260*d1e879ecSMiri Korenblit if (IS_ERR(mld->tzone)) { 261*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, 262*d1e879ecSMiri Korenblit "Failed to register to thermal zone (err = %ld)\n", 263*d1e879ecSMiri Korenblit PTR_ERR(mld->tzone)); 264*d1e879ecSMiri Korenblit mld->tzone = NULL; 265*d1e879ecSMiri Korenblit return; 266*d1e879ecSMiri Korenblit } 267*d1e879ecSMiri Korenblit 268*d1e879ecSMiri Korenblit ret = thermal_zone_device_enable(mld->tzone); 269*d1e879ecSMiri Korenblit if (ret) { 270*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, "Failed to enable thermal zone\n"); 271*d1e879ecSMiri Korenblit thermal_zone_device_unregister(mld->tzone); 272*d1e879ecSMiri Korenblit } 273*d1e879ecSMiri Korenblit } 274*d1e879ecSMiri Korenblit 275*d1e879ecSMiri Korenblit /* budget in mWatt */ 276*d1e879ecSMiri Korenblit static const u32 iwl_mld_cdev_budgets[] = { 277*d1e879ecSMiri Korenblit 2400, /* cooling state 0 */ 278*d1e879ecSMiri Korenblit 2000, /* cooling state 1 */ 279*d1e879ecSMiri Korenblit 1800, /* cooling state 2 */ 280*d1e879ecSMiri Korenblit 1600, /* cooling state 3 */ 281*d1e879ecSMiri Korenblit 1400, /* cooling state 4 */ 282*d1e879ecSMiri Korenblit 1200, /* cooling state 5 */ 283*d1e879ecSMiri Korenblit 1000, /* cooling state 6 */ 284*d1e879ecSMiri Korenblit 900, /* cooling state 7 */ 285*d1e879ecSMiri Korenblit 800, /* cooling state 8 */ 286*d1e879ecSMiri Korenblit 700, /* cooling state 9 */ 287*d1e879ecSMiri Korenblit 650, /* cooling state 10 */ 288*d1e879ecSMiri Korenblit 600, /* cooling state 11 */ 289*d1e879ecSMiri Korenblit 550, /* cooling state 12 */ 290*d1e879ecSMiri Korenblit 500, /* cooling state 13 */ 291*d1e879ecSMiri Korenblit 450, /* cooling state 14 */ 292*d1e879ecSMiri Korenblit 400, /* cooling state 15 */ 293*d1e879ecSMiri Korenblit 350, /* cooling state 16 */ 294*d1e879ecSMiri Korenblit 300, /* cooling state 17 */ 295*d1e879ecSMiri Korenblit 250, /* cooling state 18 */ 296*d1e879ecSMiri Korenblit 200, /* cooling state 19 */ 297*d1e879ecSMiri Korenblit 150, /* cooling state 20 */ 298*d1e879ecSMiri Korenblit }; 299*d1e879ecSMiri Korenblit 300*d1e879ecSMiri Korenblit int iwl_mld_config_ctdp(struct iwl_mld *mld, u32 state, 301*d1e879ecSMiri Korenblit enum iwl_ctdp_cmd_operation op) 302*d1e879ecSMiri Korenblit { 303*d1e879ecSMiri Korenblit struct iwl_ctdp_cmd cmd = { 304*d1e879ecSMiri Korenblit .operation = cpu_to_le32(op), 305*d1e879ecSMiri Korenblit .budget = cpu_to_le32(iwl_mld_cdev_budgets[state]), 306*d1e879ecSMiri Korenblit .window_size = 0, 307*d1e879ecSMiri Korenblit }; 308*d1e879ecSMiri Korenblit int ret; 309*d1e879ecSMiri Korenblit 310*d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 311*d1e879ecSMiri Korenblit 312*d1e879ecSMiri Korenblit ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP, CTDP_CONFIG_CMD), 313*d1e879ecSMiri Korenblit &cmd); 314*d1e879ecSMiri Korenblit 315*d1e879ecSMiri Korenblit if (ret) { 316*d1e879ecSMiri Korenblit IWL_ERR(mld, "cTDP command failed (err=%d)\n", ret); 317*d1e879ecSMiri Korenblit return ret; 318*d1e879ecSMiri Korenblit } 319*d1e879ecSMiri Korenblit 320*d1e879ecSMiri Korenblit if (op == CTDP_CMD_OPERATION_START) 321*d1e879ecSMiri Korenblit mld->cooling_dev.cur_state = state; 322*d1e879ecSMiri Korenblit 323*d1e879ecSMiri Korenblit return 0; 324*d1e879ecSMiri Korenblit } 325*d1e879ecSMiri Korenblit 326*d1e879ecSMiri Korenblit static int iwl_mld_tcool_get_max_state(struct thermal_cooling_device *cdev, 327*d1e879ecSMiri Korenblit unsigned long *state) 328*d1e879ecSMiri Korenblit { 329*d1e879ecSMiri Korenblit *state = ARRAY_SIZE(iwl_mld_cdev_budgets) - 1; 330*d1e879ecSMiri Korenblit 331*d1e879ecSMiri Korenblit return 0; 332*d1e879ecSMiri Korenblit } 333*d1e879ecSMiri Korenblit 334*d1e879ecSMiri Korenblit static int iwl_mld_tcool_get_cur_state(struct thermal_cooling_device *cdev, 335*d1e879ecSMiri Korenblit unsigned long *state) 336*d1e879ecSMiri Korenblit { 337*d1e879ecSMiri Korenblit struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata); 338*d1e879ecSMiri Korenblit 339*d1e879ecSMiri Korenblit *state = mld->cooling_dev.cur_state; 340*d1e879ecSMiri Korenblit 341*d1e879ecSMiri Korenblit return 0; 342*d1e879ecSMiri Korenblit } 343*d1e879ecSMiri Korenblit 344*d1e879ecSMiri Korenblit static int iwl_mld_tcool_set_cur_state(struct thermal_cooling_device *cdev, 345*d1e879ecSMiri Korenblit unsigned long new_state) 346*d1e879ecSMiri Korenblit { 347*d1e879ecSMiri Korenblit struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata); 348*d1e879ecSMiri Korenblit int ret; 349*d1e879ecSMiri Korenblit 350*d1e879ecSMiri Korenblit wiphy_lock(mld->wiphy); 351*d1e879ecSMiri Korenblit 352*d1e879ecSMiri Korenblit if (!mld->fw_status.running) { 353*d1e879ecSMiri Korenblit ret = -EIO; 354*d1e879ecSMiri Korenblit goto unlock; 355*d1e879ecSMiri Korenblit } 356*d1e879ecSMiri Korenblit 357*d1e879ecSMiri Korenblit if (new_state >= ARRAY_SIZE(iwl_mld_cdev_budgets)) { 358*d1e879ecSMiri Korenblit ret = -EINVAL; 359*d1e879ecSMiri Korenblit goto unlock; 360*d1e879ecSMiri Korenblit } 361*d1e879ecSMiri Korenblit 362*d1e879ecSMiri Korenblit ret = iwl_mld_config_ctdp(mld, new_state, CTDP_CMD_OPERATION_START); 363*d1e879ecSMiri Korenblit 364*d1e879ecSMiri Korenblit unlock: 365*d1e879ecSMiri Korenblit wiphy_unlock(mld->wiphy); 366*d1e879ecSMiri Korenblit return ret; 367*d1e879ecSMiri Korenblit } 368*d1e879ecSMiri Korenblit 369*d1e879ecSMiri Korenblit static const struct thermal_cooling_device_ops tcooling_ops = { 370*d1e879ecSMiri Korenblit .get_max_state = iwl_mld_tcool_get_max_state, 371*d1e879ecSMiri Korenblit .get_cur_state = iwl_mld_tcool_get_cur_state, 372*d1e879ecSMiri Korenblit .set_cur_state = iwl_mld_tcool_set_cur_state, 373*d1e879ecSMiri Korenblit }; 374*d1e879ecSMiri Korenblit 375*d1e879ecSMiri Korenblit static void iwl_mld_cooling_device_register(struct iwl_mld *mld) 376*d1e879ecSMiri Korenblit { 377*d1e879ecSMiri Korenblit char name[] = "iwlwifi"; 378*d1e879ecSMiri Korenblit 379*d1e879ecSMiri Korenblit BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); 380*d1e879ecSMiri Korenblit 381*d1e879ecSMiri Korenblit mld->cooling_dev.cdev = 382*d1e879ecSMiri Korenblit thermal_cooling_device_register(name, 383*d1e879ecSMiri Korenblit mld, 384*d1e879ecSMiri Korenblit &tcooling_ops); 385*d1e879ecSMiri Korenblit 386*d1e879ecSMiri Korenblit if (IS_ERR(mld->cooling_dev.cdev)) { 387*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, 388*d1e879ecSMiri Korenblit "Failed to register to cooling device (err = %ld)\n", 389*d1e879ecSMiri Korenblit PTR_ERR(mld->cooling_dev.cdev)); 390*d1e879ecSMiri Korenblit mld->cooling_dev.cdev = NULL; 391*d1e879ecSMiri Korenblit return; 392*d1e879ecSMiri Korenblit } 393*d1e879ecSMiri Korenblit } 394*d1e879ecSMiri Korenblit 395*d1e879ecSMiri Korenblit static void iwl_mld_thermal_zone_unregister(struct iwl_mld *mld) 396*d1e879ecSMiri Korenblit { 397*d1e879ecSMiri Korenblit if (!mld->tzone) 398*d1e879ecSMiri Korenblit return; 399*d1e879ecSMiri Korenblit 400*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, "Thermal zone device unregister\n"); 401*d1e879ecSMiri Korenblit if (mld->tzone) { 402*d1e879ecSMiri Korenblit thermal_zone_device_unregister(mld->tzone); 403*d1e879ecSMiri Korenblit mld->tzone = NULL; 404*d1e879ecSMiri Korenblit } 405*d1e879ecSMiri Korenblit } 406*d1e879ecSMiri Korenblit 407*d1e879ecSMiri Korenblit static void iwl_mld_cooling_device_unregister(struct iwl_mld *mld) 408*d1e879ecSMiri Korenblit { 409*d1e879ecSMiri Korenblit if (!mld->cooling_dev.cdev) 410*d1e879ecSMiri Korenblit return; 411*d1e879ecSMiri Korenblit 412*d1e879ecSMiri Korenblit IWL_DEBUG_TEMP(mld, "Cooling device unregister\n"); 413*d1e879ecSMiri Korenblit if (mld->cooling_dev.cdev) { 414*d1e879ecSMiri Korenblit thermal_cooling_device_unregister(mld->cooling_dev.cdev); 415*d1e879ecSMiri Korenblit mld->cooling_dev.cdev = NULL; 416*d1e879ecSMiri Korenblit } 417*d1e879ecSMiri Korenblit } 418*d1e879ecSMiri Korenblit #endif /* CONFIG_THERMAL */ 419*d1e879ecSMiri Korenblit 420*d1e879ecSMiri Korenblit void iwl_mld_thermal_initialize(struct iwl_mld *mld) 421*d1e879ecSMiri Korenblit { 422*d1e879ecSMiri Korenblit wiphy_delayed_work_init(&mld->ct_kill_exit_wk, iwl_mld_exit_ctkill); 423*d1e879ecSMiri Korenblit 424*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 425*d1e879ecSMiri Korenblit iwl_mld_cooling_device_register(mld); 426*d1e879ecSMiri Korenblit iwl_mld_thermal_zone_register(mld); 427*d1e879ecSMiri Korenblit #endif 428*d1e879ecSMiri Korenblit } 429*d1e879ecSMiri Korenblit 430*d1e879ecSMiri Korenblit void iwl_mld_thermal_exit(struct iwl_mld *mld) 431*d1e879ecSMiri Korenblit { 432*d1e879ecSMiri Korenblit wiphy_delayed_work_cancel(mld->wiphy, &mld->ct_kill_exit_wk); 433*d1e879ecSMiri Korenblit 434*d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL 435*d1e879ecSMiri Korenblit iwl_mld_cooling_device_unregister(mld); 436*d1e879ecSMiri Korenblit iwl_mld_thermal_zone_unregister(mld); 437*d1e879ecSMiri Korenblit #endif 438*d1e879ecSMiri Korenblit } 439