diff options
Diffstat (limited to 'drivers/net/wireless/intel/iwlwifi/mvm/fw.c')
| -rw-r--r-- | drivers/net/wireless/intel/iwlwifi/mvm/fw.c | 227 | 
1 files changed, 165 insertions, 62 deletions
| diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c index 863fec150e53..6f4690e56a46 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -12,8 +12,6 @@  #include "iwl-op-mode.h"  #include "fw/img.h"  #include "iwl-debug.h" -#include "iwl-csr.h" /* for iwl_mvm_rx_card_state_notif */ -#include "iwl-io.h" /* for iwl_mvm_rx_card_state_notif */  #include "iwl-prph.h"  #include "fw/acpi.h"  #include "fw/pnvm.h" @@ -32,6 +30,9 @@  #define IWL_PPAG_MASK 3  #define IWL_PPAG_ETSI_MASK BIT(0) +#define IWL_TAS_US_MCC 0x5553 +#define IWL_TAS_CANADA_MCC 0x4341 +  struct iwl_mvm_alive_data {  	bool valid;  	u32 scd_base_addr; @@ -123,13 +124,15 @@ static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait,  	struct iwl_lmac_alive *lmac2 = NULL;  	u16 status;  	u32 lmac_error_event_table, umac_error_table; +	u32 version = iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, +					      UCODE_ALIVE_NTFY, 0);  	/*  	 * For v5 and above, we can check the version, for older  	 * versions we need to check the size.  	 */ -	if (iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, -				    UCODE_ALIVE_NTFY, 0) == 5) { +	if (version == 5 || version == 6) { +		/* v5 and v6 are compatible (only IMR addition) */  		struct iwl_alive_ntf_v5 *palive;  		if (pkt_len < sizeof(*palive)) @@ -516,7 +519,6 @@ static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm,  			cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_D);  	}  } -  #else /* CONFIG_ACPI */  static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm, @@ -525,6 +527,49 @@ static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm,  }  #endif /* CONFIG_ACPI */ +#if defined(CONFIG_ACPI) && defined(CONFIG_EFI) +static int iwl_mvm_sgom_init(struct iwl_mvm *mvm) +{ +	u8 cmd_ver; +	int ret; +	struct iwl_host_cmd cmd = { +		.id = WIDE_ID(REGULATORY_AND_NVM_GROUP, +			      SAR_OFFSET_MAPPING_TABLE_CMD), +		.flags = 0, +		.data[0] = &mvm->fwrt.sgom_table, +		.len[0] =  sizeof(mvm->fwrt.sgom_table), +		.dataflags[0] = IWL_HCMD_DFL_NOCOPY, +	}; + +	if (!mvm->fwrt.sgom_enabled) { +		IWL_DEBUG_RADIO(mvm, "SGOM table is disabled\n"); +		return 0; +	} + +	cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, REGULATORY_AND_NVM_GROUP, +					SAR_OFFSET_MAPPING_TABLE_CMD, +					IWL_FW_CMD_VER_UNKNOWN); + +	if (cmd_ver != 2) { +		IWL_DEBUG_RADIO(mvm, "command version is unsupported. version = %d\n", +				cmd_ver); +		return 0; +	} + +	ret = iwl_mvm_send_cmd(mvm, &cmd); +	if (ret < 0) +		IWL_ERR(mvm, "failed to send SAR_OFFSET_MAPPING_CMD (%d)\n", ret); + +	return ret; +} +#else + +static int iwl_mvm_sgom_init(struct iwl_mvm *mvm) +{ +	return 0; +} +#endif +  static int iwl_send_phy_cfg_cmd(struct iwl_mvm *mvm)  {  	struct iwl_phy_cfg_cmd_v3 phy_cfg_cmd; @@ -757,6 +802,8 @@ int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b)  	if (ret)  		return ret; +	iwl_mei_set_power_limit(per_chain); +  	IWL_DEBUG_RADIO(mvm, "Sending REDUCE_TX_POWER_CMD per chain\n");  	return iwl_mvm_send_cmd_pdu(mvm, REDUCE_TX_POWER_CMD, 0, len, &cmd);  } @@ -820,6 +867,7 @@ static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm)  	u16 len;  	u32 n_bands;  	u32 n_profiles; +	u32 sk = 0;  	int ret;  	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP,  					   PER_CHAIN_LIMIT_OFFSET_CMD, @@ -879,19 +927,26 @@ static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm)  	if (ret)  		return 0; +	/* Only set to South Korea if the table revision is 1 */ +	if (mvm->fwrt.geo_rev == 1) +		sk = 1; +  	/* -	 * Set the revision on versions that contain it. +	 * Set the table_revision to South Korea (1) or not (0).  The +	 * element name is misleading, as it doesn't contain the table +	 * revision number, but whether the South Korea variation +	 * should be used.  	 * This must be done after calling iwl_sar_geo_init().  	 */  	if (cmd_ver == 5) -		cmd.v5.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); +		cmd.v5.table_revision = cpu_to_le32(sk);  	else if (cmd_ver == 4) -		cmd.v4.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); +		cmd.v4.table_revision = cpu_to_le32(sk);  	else if (cmd_ver == 3) -		cmd.v3.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); +		cmd.v3.table_revision = cpu_to_le32(sk);  	else if (fw_has_api(&mvm->fwrt.fw->ucode_capa,  			    IWL_UCODE_TLV_API_SAR_TABLE_VER)) -		cmd.v2.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); +		cmd.v2.table_revision = cpu_to_le32(sk);  	return iwl_mvm_send_cmd_pdu(mvm,  				    WIDE_ID(PHY_OPS_GROUP, @@ -904,13 +959,8 @@ static int iwl_mvm_get_ppag_table(struct iwl_mvm *mvm)  	union acpi_object *wifi_pkg, *data, *flags;  	int i, j, ret, tbl_rev, num_sub_bands;  	int idx = 2; -	s8 *gain; -	/* -	 * The 'flags' field is the same in v1 and in v2 so we can just -	 * use v1 to access it. -	 */ -	mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(0); +	mvm->fwrt.ppag_flags = 0;  	data = iwl_acpi_get_object(mvm->dev, ACPI_PPAG_METHOD);  	if (IS_ERR(data)) @@ -922,8 +972,6 @@ static int iwl_mvm_get_ppag_table(struct iwl_mvm *mvm)  	if (!IS_ERR(wifi_pkg)) {  		if (tbl_rev == 1 || tbl_rev == 2) {  			num_sub_bands = IWL_NUM_SUB_BANDS_V2; -			gain = mvm->fwrt.ppag_table.v2.gain[0]; -			mvm->fwrt.ppag_ver = tbl_rev;  			IWL_DEBUG_RADIO(mvm,  					"Reading PPAG table v2 (tbl_rev=%d)\n",  					tbl_rev); @@ -943,8 +991,6 @@ static int iwl_mvm_get_ppag_table(struct iwl_mvm *mvm)  			goto out_free;  		}  		num_sub_bands = IWL_NUM_SUB_BANDS_V1; -		gain = mvm->fwrt.ppag_table.v1.gain[0]; -		mvm->fwrt.ppag_ver = 0;  		IWL_DEBUG_RADIO(mvm, "Reading PPAG table v1 (tbl_rev=0)\n");  		goto read_table;  	} @@ -952,6 +998,7 @@ static int iwl_mvm_get_ppag_table(struct iwl_mvm *mvm)  	goto out_free;  read_table: +	mvm->fwrt.ppag_ver = tbl_rev;  	flags = &wifi_pkg->package.elements[1];  	if (flags->type != ACPI_TYPE_INTEGER) { @@ -959,10 +1006,9 @@ read_table:  		goto out_free;  	} -	mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(flags->integer.value & -						    IWL_PPAG_MASK); +	mvm->fwrt.ppag_flags = flags->integer.value & IWL_PPAG_MASK; -	if (!mvm->fwrt.ppag_table.v1.flags) { +	if (!mvm->fwrt.ppag_flags) {  		ret = 0;  		goto out_free;  	} @@ -982,15 +1028,15 @@ read_table:  				goto out_free;  			} -			gain[i * num_sub_bands + j] = ent->integer.value; +			mvm->fwrt.ppag_chains[i].subbands[j] = ent->integer.value;  			if ((j == 0 && -			     (gain[i * num_sub_bands + j] > ACPI_PPAG_MAX_LB || -			      gain[i * num_sub_bands + j] < ACPI_PPAG_MIN_LB)) || +			     (mvm->fwrt.ppag_chains[i].subbands[j] > ACPI_PPAG_MAX_LB || +			      mvm->fwrt.ppag_chains[i].subbands[j] < ACPI_PPAG_MIN_LB)) ||  			    (j != 0 && -			     (gain[i * num_sub_bands + j] > ACPI_PPAG_MAX_HB || -			      gain[i * num_sub_bands + j] < ACPI_PPAG_MIN_HB))) { -				mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(0); +			     (mvm->fwrt.ppag_chains[i].subbands[j] > ACPI_PPAG_MAX_HB || +			      mvm->fwrt.ppag_chains[i].subbands[j] < ACPI_PPAG_MIN_HB))) { +				mvm->fwrt.ppag_flags = 0;  				ret = -EINVAL;  				goto out_free;  			} @@ -1005,6 +1051,7 @@ out_free:  int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm)  { +	union iwl_ppag_table_cmd cmd;  	u8 cmd_ver;  	int i, j, ret, num_sub_bands, cmd_size;  	s8 *gain; @@ -1014,37 +1061,39 @@ int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm)  				"PPAG capability not supported by FW, command not sent.\n");  		return 0;  	} -	if (!mvm->fwrt.ppag_table.v1.flags) { +	if (!mvm->fwrt.ppag_flags) {  		IWL_DEBUG_RADIO(mvm, "PPAG not enabled, command not sent.\n");  		return 0;  	} +	/* The 'flags' field is the same in v1 and in v2 so we can just +	 * use v1 to access it. +	 */ +	cmd.v1.flags = cpu_to_le32(mvm->fwrt.ppag_flags);  	cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP,  					PER_PLATFORM_ANT_GAIN_CMD,  					IWL_FW_CMD_VER_UNKNOWN);  	if (cmd_ver == 1) {  		num_sub_bands = IWL_NUM_SUB_BANDS_V1; -		gain = mvm->fwrt.ppag_table.v1.gain[0]; -		cmd_size = sizeof(mvm->fwrt.ppag_table.v1); +		gain = cmd.v1.gain[0]; +		cmd_size = sizeof(cmd.v1);  		if (mvm->fwrt.ppag_ver == 1 || mvm->fwrt.ppag_ver == 2) {  			IWL_DEBUG_RADIO(mvm,  					"PPAG table rev is %d but FW supports v1, sending truncated table\n",  					mvm->fwrt.ppag_ver); -			mvm->fwrt.ppag_table.v1.flags &= -				cpu_to_le32(IWL_PPAG_ETSI_MASK); +			cmd.v1.flags &= cpu_to_le32(IWL_PPAG_ETSI_MASK);  		}  	} else if (cmd_ver == 2 || cmd_ver == 3) {  		num_sub_bands = IWL_NUM_SUB_BANDS_V2; -		gain = mvm->fwrt.ppag_table.v2.gain[0]; -		cmd_size = sizeof(mvm->fwrt.ppag_table.v2); +		gain = cmd.v2.gain[0]; +		cmd_size = sizeof(cmd.v2);  		if (mvm->fwrt.ppag_ver == 0) {  			IWL_DEBUG_RADIO(mvm,  					"PPAG table is v1 but FW supports v2, sending padded table\n");  		} else if (cmd_ver == 2 && mvm->fwrt.ppag_ver == 2) {  			IWL_DEBUG_RADIO(mvm,  					"PPAG table is v3 but FW supports v2, sending partial bitmap.\n"); -			mvm->fwrt.ppag_table.v1.flags &= -				cpu_to_le32(IWL_PPAG_ETSI_MASK); +			cmd.v1.flags &= cpu_to_le32(IWL_PPAG_ETSI_MASK);  		}  	} else {  		IWL_DEBUG_RADIO(mvm, "Unsupported PPAG command version\n"); @@ -1053,6 +1102,8 @@ int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm)  	for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) {  		for (j = 0; j < num_sub_bands; j++) { +			gain[i * num_sub_bands + j] = +				mvm->fwrt.ppag_chains[i].subbands[j];  			IWL_DEBUG_RADIO(mvm,  					"PPAG table: chain[%d] band[%d]: gain = %d\n",  					i, j, gain[i * num_sub_bands + j]); @@ -1061,7 +1112,7 @@ int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm)  	IWL_DEBUG_RADIO(mvm, "Sending PER_PLATFORM_ANT_GAIN_CMD\n");  	ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP,  						PER_PLATFORM_ANT_GAIN_CMD), -				   0, cmd_size, &mvm->fwrt.ppag_table); +				   0, cmd_size, &cmd);  	if (ret < 0)  		IWL_ERR(mvm, "failed to send PER_PLATFORM_ANT_GAIN_CMD (%d)\n",  			ret); @@ -1100,18 +1151,63 @@ static int iwl_mvm_ppag_init(struct iwl_mvm *mvm)  		IWL_DEBUG_RADIO(mvm,  				"System vendor '%s' is not in the approved list, disabling PPAG.\n",  				dmi_get_system_info(DMI_SYS_VENDOR)); -		mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(0); +		mvm->fwrt.ppag_flags = 0;  		return 0;  	}  	return iwl_mvm_ppag_send_cmd(mvm);  } +static const struct dmi_system_id dmi_tas_approved_list[] = { +	{ .ident = "HP", +	  .matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "HP"), +		}, +	}, +	{ .ident = "SAMSUNG", +	  .matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), +		}, +	}, +		{ .ident = "LENOVO", +	  .matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "Lenovo"), +		}, +	}, +	{ .ident = "DELL", +	  .matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), +		}, +	}, + +	/* keep last */ +	{} +}; + +static bool iwl_mvm_add_to_tas_block_list(__le32 *list, __le32 *le_size, unsigned int mcc) +{ +	int i; +	u32 size = le32_to_cpu(*le_size); + +	/* Verify that there is room for another country */ +	if (size >= IWL_TAS_BLOCK_LIST_MAX) +		return false; + +	for (i = 0; i < size; i++) { +		if (list[i] == cpu_to_le32(mcc)) +			return true; +	} + +	list[size++] = cpu_to_le32(mcc); +	*le_size = cpu_to_le32(size); +	return true; +} +  static void iwl_mvm_tas_init(struct iwl_mvm *mvm)  {  	int ret; -	struct iwl_tas_config_cmd cmd = {}; -	int list_size; +	struct iwl_tas_config_cmd_v3 cmd = {}; +	int cmd_size;  	BUILD_BUG_ON(ARRAY_SIZE(cmd.block_list_array) <  		     APCI_WTAS_BLACK_LIST_MAX); @@ -1121,7 +1217,7 @@ static void iwl_mvm_tas_init(struct iwl_mvm *mvm)  		return;  	} -	ret = iwl_acpi_get_tas(&mvm->fwrt, cmd.block_list_array, &list_size); +	ret = iwl_acpi_get_tas(&mvm->fwrt, &cmd);  	if (ret < 0) {  		IWL_DEBUG_RADIO(mvm,  				"TAS table invalid or unavailable. (%d)\n", @@ -1129,15 +1225,32 @@ static void iwl_mvm_tas_init(struct iwl_mvm *mvm)  		return;  	} -	if (list_size < 0) +	if (ret == 0)  		return; -	/* list size if TAS enabled can only be non-negative */ -	cmd.block_list_size = cpu_to_le32((u32)list_size); +	if (!dmi_check_system(dmi_tas_approved_list)) { +		IWL_DEBUG_RADIO(mvm, +				"System vendor '%s' is not in the approved list, disabling TAS in US and Canada.\n", +				dmi_get_system_info(DMI_SYS_VENDOR)); +		if ((!iwl_mvm_add_to_tas_block_list(cmd.block_list_array, +						    &cmd.block_list_size, IWL_TAS_US_MCC)) || +		    (!iwl_mvm_add_to_tas_block_list(cmd.block_list_array, +						    &cmd.block_list_size, IWL_TAS_CANADA_MCC))) { +			IWL_DEBUG_RADIO(mvm, +					"Unable to add US/Canada to TAS block list, disabling TAS\n"); +			return; +		} +	} + +	cmd_size = iwl_fw_lookup_cmd_ver(mvm->fw, REGULATORY_AND_NVM_GROUP, +					 TAS_CONFIG, +					 IWL_FW_CMD_VER_UNKNOWN) < 3 ? +		sizeof(struct iwl_tas_config_cmd_v2) : +		sizeof(struct iwl_tas_config_cmd_v3);  	ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(REGULATORY_AND_NVM_GROUP,  						TAS_CONFIG), -				   0, sizeof(cmd), &cmd); +				   0, cmd_size, &cmd);  	if (ret < 0)  		IWL_DEBUG_RADIO(mvm, "failed to send TAS_CONFIG (%d)\n", ret);  } @@ -1336,6 +1449,7 @@ static u8 iwl_mvm_eval_dsm_rfi(struct iwl_mvm *mvm)  void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm)  {  } +  #endif /* CONFIG_ACPI */  void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags) @@ -1401,7 +1515,6 @@ static int iwl_mvm_load_rt_fw(struct iwl_mvm *mvm)  	if (iwl_mvm_has_unified_ucode(mvm))  		return iwl_run_unified_mvm_ucode(mvm); -	WARN_ON(!mvm->nvm_data);  	ret = iwl_run_init_mvm_ucode(mvm);  	if (ret) { @@ -1631,6 +1744,10 @@ int iwl_mvm_up(struct iwl_mvm *mvm)  	else if (ret < 0)  		goto error; +	ret = iwl_mvm_sgom_init(mvm); +	if (ret) +		goto error; +  	iwl_mvm_tas_init(mvm);  	iwl_mvm_leds_sync(mvm); @@ -1705,20 +1822,6 @@ int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm)  	return ret;  } -void iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, -				 struct iwl_rx_cmd_buffer *rxb) -{ -	struct iwl_rx_packet *pkt = rxb_addr(rxb); -	struct iwl_card_state_notif *card_state_notif = (void *)pkt->data; -	u32 flags = le32_to_cpu(card_state_notif->flags); - -	IWL_DEBUG_RF_KILL(mvm, "Card state received: HW:%s SW:%s CT:%s\n", -			  (flags & HW_CARD_DISABLED) ? "Kill" : "On", -			  (flags & SW_CARD_DISABLED) ? "Kill" : "On", -			  (flags & CT_KILL_CARD_DISABLED) ? -			  "Reached" : "Not reached"); -} -  void iwl_mvm_rx_mfuart_notif(struct iwl_mvm *mvm,  			     struct iwl_rx_cmd_buffer *rxb)  { |