diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/thermal.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/thermal.c | 467 | 
1 files changed, 467 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/thermal.c b/sys/contrib/dev/iwlwifi/mld/thermal.c new file mode 100644 index 000000000000..f8a8c35066be --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/thermal.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2024-2025 Intel Corporation + */ +#ifdef CONFIG_THERMAL +#include <linux/sort.h> +#include <linux/thermal.h> +#endif + +#include "fw/api/phy.h" + +#include "thermal.h" +#include "mld.h" +#include "hcmd.h" + +#define IWL_MLD_NUM_CTDP_STEPS		20 +#define IWL_MLD_MIN_CTDP_BUDGET_MW	150 + +#define IWL_MLD_CT_KILL_DURATION (5 * HZ) + +void iwl_mld_handle_ct_kill_notif(struct iwl_mld *mld, +				  struct iwl_rx_packet *pkt) +{ +	const struct ct_kill_notif *notif = (const void *)pkt->data; + +	IWL_ERR(mld, +		"CT Kill notification: temp = %d, DTS = 0x%x, Scheme 0x%x - Enter CT Kill\n", +		le16_to_cpu(notif->temperature), notif->dts, +		notif->scheme); + +	iwl_mld_set_ctkill(mld, true); + +	wiphy_delayed_work_queue(mld->wiphy, &mld->ct_kill_exit_wk, +				 round_jiffies_relative(IWL_MLD_CT_KILL_DURATION)); +} + +static void iwl_mld_exit_ctkill(struct wiphy *wiphy, struct wiphy_work *wk) +{ +	struct iwl_mld *mld; + +	mld = container_of(wk, struct iwl_mld, ct_kill_exit_wk.work); + +	IWL_ERR(mld, "Exit CT Kill\n"); +	iwl_mld_set_ctkill(mld, false); +} + +void iwl_mld_handle_temp_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) +{ +	const struct iwl_dts_measurement_notif *notif = +		(const void *)pkt->data; +	int temp; +	u32 ths_crossed; + +	temp = le32_to_cpu(notif->temp); + +	/* shouldn't be negative, but since it's s32, make sure it isn't */ +	if (IWL_FW_CHECK(mld, temp < 0, "negative temperature %d\n", temp)) +		return; + +	ths_crossed = le32_to_cpu(notif->threshold_idx); + +	/* 0xFF in ths_crossed means the notification is not related +	 * to a trip, so we can ignore it here. +	 */ +	if (ths_crossed == 0xFF) +		return; + +	IWL_DEBUG_TEMP(mld, "Temp = %d Threshold crossed = %d\n", +		       temp, ths_crossed); + +	if (IWL_FW_CHECK(mld, ths_crossed >= IWL_MAX_DTS_TRIPS, +			 "bad threshold: %d\n", ths_crossed)) +		return; + +#ifdef CONFIG_THERMAL +	if (mld->tzone) +		thermal_zone_device_update(mld->tzone, THERMAL_TRIP_VIOLATED); +#endif /* CONFIG_THERMAL */ +} + +#ifdef CONFIG_THERMAL +static int iwl_mld_get_temp(struct iwl_mld *mld, s32 *temp) +{ +	struct iwl_host_cmd cmd = { +		.id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE), +		.flags = CMD_WANT_SKB, +	}; +	const struct iwl_dts_measurement_resp *resp; +	int ret; + +	lockdep_assert_wiphy(mld->wiphy); + +	ret = iwl_mld_send_cmd(mld, &cmd); +	if (ret) { +		IWL_ERR(mld, +			"Failed to send the temperature measurement command (err=%d)\n", +			ret); +		return ret; +	} + +	if (iwl_rx_packet_payload_len(cmd.resp_pkt) < sizeof(*resp)) { +		IWL_ERR(mld, +			"Failed to get a valid response to DTS measurement\n"); +		ret = -EIO; +		goto free_resp; +	} + +	resp = (const void *)cmd.resp_pkt->data; +	*temp = le32_to_cpu(resp->temp); + +	IWL_DEBUG_TEMP(mld, +		       "Got temperature measurement response: temp=%d\n", +		       *temp); + +free_resp: +	iwl_free_resp(&cmd); +	return ret; +} + +static int compare_temps(const void *a, const void *b) +{ +	return ((s16)le16_to_cpu(*(const __le16 *)a) - +		(s16)le16_to_cpu(*(const __le16 *)b)); +} + +struct iwl_trip_walk_data { +	__le16 *thresholds; +	int count; +}; + +static int iwl_trip_temp_iter(struct thermal_trip *trip, void *arg) +{ +	struct iwl_trip_walk_data *twd = arg; + +	if (trip->temperature == THERMAL_TEMP_INVALID) +		return 0; + +	twd->thresholds[twd->count++] = +		cpu_to_le16((s16)(trip->temperature / 1000)); +	return 0; +} +#endif + +int iwl_mld_config_temp_report_ths(struct iwl_mld *mld) +{ +	struct temp_report_ths_cmd cmd = {0}; +	int ret; +#ifdef CONFIG_THERMAL +	struct iwl_trip_walk_data twd = { +		.thresholds = cmd.thresholds, +		.count = 0 +	}; + +	if (!mld->tzone) +		goto send; + +	/* The thermal core holds an array of temperature trips that are +	 * unsorted and uncompressed, the FW should get it compressed and +	 * sorted. +	 */ + +	/* compress trips to cmd array, remove uninitialized values*/ +	for_each_thermal_trip(mld->tzone, iwl_trip_temp_iter, &twd); + +	cmd.num_temps = cpu_to_le32(twd.count); +	if (twd.count) +		sort(cmd.thresholds, twd.count, sizeof(s16), +		     compare_temps, NULL); + +send: +#endif +	lockdep_assert_wiphy(mld->wiphy); + +	ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP, +						TEMP_REPORTING_THRESHOLDS_CMD), +				   &cmd); +	if (ret) +		IWL_ERR(mld, "TEMP_REPORT_THS_CMD command failed (err=%d)\n", +			ret); + +	return ret; +} + +#ifdef CONFIG_THERMAL +static int iwl_mld_tzone_get_temp(struct thermal_zone_device *device, +				  int *temperature) +{ +	struct iwl_mld *mld = thermal_zone_device_priv(device); +	int temp; +	int ret = 0; + +	wiphy_lock(mld->wiphy); + +	if (!mld->fw_status.running) { +		/* Tell the core that there is no valid temperature value to +		 * return, but it need not worry about this. +		 */ +		*temperature = THERMAL_TEMP_INVALID; +		goto unlock; +	} + +	ret = iwl_mld_get_temp(mld, &temp); +	if (ret) +		goto unlock; + +	*temperature = temp * 1000; +unlock: +	wiphy_unlock(mld->wiphy); +	return ret; +} + +static int iwl_mld_tzone_set_trip_temp(struct thermal_zone_device *device, +				       const struct thermal_trip *trip, +				       int temp) +{ +	struct iwl_mld *mld = thermal_zone_device_priv(device); +	int ret; + +	wiphy_lock(mld->wiphy); + +	if (!mld->fw_status.running) { +		ret = -EIO; +		goto unlock; +	} + +	if ((temp / 1000) > S16_MAX) { +		ret = -EINVAL; +		goto unlock; +	} + +	ret = iwl_mld_config_temp_report_ths(mld); +unlock: +	wiphy_unlock(mld->wiphy); +	return ret; +} + +static  struct thermal_zone_device_ops tzone_ops = { +	.get_temp = iwl_mld_tzone_get_temp, +	.set_trip_temp = iwl_mld_tzone_set_trip_temp, +}; + +static void iwl_mld_thermal_zone_register(struct iwl_mld *mld) +{ +	int ret; +	char name[16]; +	static atomic_t counter = ATOMIC_INIT(0); +	struct thermal_trip trips[IWL_MAX_DTS_TRIPS] = { +		[0 ... IWL_MAX_DTS_TRIPS - 1] = { +			.temperature = THERMAL_TEMP_INVALID, +			.type = THERMAL_TRIP_PASSIVE, +			.flags = THERMAL_TRIP_FLAG_RW_TEMP, +		}, +	}; + +	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); + +	sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF); +	mld->tzone = +		thermal_zone_device_register_with_trips(name, trips, +							IWL_MAX_DTS_TRIPS, +							mld, &tzone_ops, +							NULL, 0, 0); +	if (IS_ERR(mld->tzone)) { +		IWL_DEBUG_TEMP(mld, +			       "Failed to register to thermal zone (err = %ld)\n", +			       PTR_ERR(mld->tzone)); +		mld->tzone = NULL; +		return; +	} + +	ret = thermal_zone_device_enable(mld->tzone); +	if (ret) { +		IWL_DEBUG_TEMP(mld, "Failed to enable thermal zone\n"); +		thermal_zone_device_unregister(mld->tzone); +	} +} + +int iwl_mld_config_ctdp(struct iwl_mld *mld, u32 state, +			enum iwl_ctdp_cmd_operation op) +{ +	struct iwl_ctdp_cmd cmd = { +		.operation = cpu_to_le32(op), +		.window_size = 0, +	}; +	u32 budget; +	int ret; + +	lockdep_assert_wiphy(mld->wiphy); + +	/* Do a linear scale from IWL_MLD_MIN_CTDP_BUDGET_MW to the configured +	 * maximum in the predefined number of steps. +	 */ +	budget = ((mld->power_budget_mw - IWL_MLD_MIN_CTDP_BUDGET_MW) * +		  (IWL_MLD_NUM_CTDP_STEPS - 1 - state)) / +		 (IWL_MLD_NUM_CTDP_STEPS - 1) + +		 IWL_MLD_MIN_CTDP_BUDGET_MW; +	cmd.budget = cpu_to_le32(budget); + +	ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(PHY_OPS_GROUP, CTDP_CONFIG_CMD), +				   &cmd); + +	if (ret) { +		IWL_ERR(mld, "cTDP command failed (err=%d)\n", ret); +		return ret; +	} + +	if (op == CTDP_CMD_OPERATION_START) +		mld->cooling_dev.cur_state = state; + +	return 0; +} + +static int iwl_mld_tcool_get_max_state(struct thermal_cooling_device *cdev, +				       unsigned long *state) +{ +	*state = IWL_MLD_NUM_CTDP_STEPS - 1; + +	return 0; +} + +static int iwl_mld_tcool_get_cur_state(struct thermal_cooling_device *cdev, +				       unsigned long *state) +{ +	struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata); + +	*state = mld->cooling_dev.cur_state; + +	return 0; +} + +static int iwl_mld_tcool_set_cur_state(struct thermal_cooling_device *cdev, +				       unsigned long new_state) +{ +	struct iwl_mld *mld = (struct iwl_mld *)(cdev->devdata); +	int ret; + +	wiphy_lock(mld->wiphy); + +	if (!mld->fw_status.running) { +		ret = -EIO; +		goto unlock; +	} + +	if (new_state >= IWL_MLD_NUM_CTDP_STEPS) { +		ret = -EINVAL; +		goto unlock; +	} + +	ret = iwl_mld_config_ctdp(mld, new_state, CTDP_CMD_OPERATION_START); + +unlock: +	wiphy_unlock(mld->wiphy); +	return ret; +} + +static const struct thermal_cooling_device_ops tcooling_ops = { +	.get_max_state = iwl_mld_tcool_get_max_state, +	.get_cur_state = iwl_mld_tcool_get_cur_state, +	.set_cur_state = iwl_mld_tcool_set_cur_state, +}; + +static void iwl_mld_cooling_device_register(struct iwl_mld *mld) +{ +	char name[] = "iwlwifi"; + +	BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); + +	mld->cooling_dev.cdev = +		thermal_cooling_device_register(name, +						mld, +						&tcooling_ops); + +	if (IS_ERR(mld->cooling_dev.cdev)) { +		IWL_DEBUG_TEMP(mld, +			       "Failed to register to cooling device (err = %ld)\n", +			       PTR_ERR(mld->cooling_dev.cdev)); +		mld->cooling_dev.cdev = NULL; +		return; +	} +} + +static void iwl_mld_thermal_zone_unregister(struct iwl_mld *mld) +{ +	if (!mld->tzone) +		return; + +	IWL_DEBUG_TEMP(mld, "Thermal zone device unregister\n"); +	if (mld->tzone) { +		thermal_zone_device_unregister(mld->tzone); +		mld->tzone = NULL; +	} +} + +static void iwl_mld_cooling_device_unregister(struct iwl_mld *mld) +{ +	if (!mld->cooling_dev.cdev) +		return; + +	IWL_DEBUG_TEMP(mld, "Cooling device unregister\n"); +	if (mld->cooling_dev.cdev) { +		thermal_cooling_device_unregister(mld->cooling_dev.cdev); +		mld->cooling_dev.cdev = NULL; +	} +} +#endif /* CONFIG_THERMAL */ + +static u32 iwl_mld_ctdp_get_max_budget(struct iwl_mld *mld) +{ +	u64 bios_power_budget = 0; +	u32 default_power_budget; + +	switch (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id)) { +	case IWL_CFG_RF_TYPE_GF: +		/* dual-radio devices have a higher budget */ +		if (CSR_HW_RFID_IS_CDB(mld->trans->info.hw_rf_id)) +			default_power_budget = 5200; +		else +			default_power_budget = 2880; +		break; +	case IWL_CFG_RF_TYPE_FM: +		default_power_budget = 3450; +		break; +	case IWL_CFG_RF_TYPE_WH: +	case IWL_CFG_RF_TYPE_PE: +	default: +		default_power_budget = 5550; +		break; +	} + +	iwl_bios_get_pwr_limit(&mld->fwrt, &bios_power_budget); + +	/* 32bit in UEFI, 16bit in ACPI; use BIOS value if it is in range */ +	if (bios_power_budget && +	    bios_power_budget != 0xffff && bios_power_budget != 0xffffffff && +	    bios_power_budget >= IWL_MLD_MIN_CTDP_BUDGET_MW && +	    bios_power_budget <= default_power_budget) +		return (u32)bios_power_budget; + +	return default_power_budget; +} + +void iwl_mld_thermal_initialize(struct iwl_mld *mld) +{ +	lockdep_assert_not_held(&mld->wiphy->mtx); + +	wiphy_delayed_work_init(&mld->ct_kill_exit_wk, iwl_mld_exit_ctkill); + +	mld->power_budget_mw = iwl_mld_ctdp_get_max_budget(mld); +	IWL_DEBUG_TEMP(mld, "cTDP power budget: %d mW\n", mld->power_budget_mw); + +#ifdef CONFIG_THERMAL +	iwl_mld_cooling_device_register(mld); +	iwl_mld_thermal_zone_register(mld); +#endif +} + +void iwl_mld_thermal_exit(struct iwl_mld *mld) +{ +	wiphy_lock(mld->wiphy); +	wiphy_delayed_work_cancel(mld->wiphy, &mld->ct_kill_exit_wk); +	wiphy_unlock(mld->wiphy); + +#ifdef CONFIG_THERMAL +	iwl_mld_cooling_device_unregister(mld); +	iwl_mld_thermal_zone_unregister(mld); +#endif +} | 
