diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/mvm/nvm.c')
| -rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/nvm.c | 311 | 
1 files changed, 311 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/nvm.c b/drivers/net/wireless/iwlwifi/mvm/nvm.c new file mode 100644 index 00000000000..20016bcbdea --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/nvm.c @@ -0,0 +1,311 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license.  When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + *  Intel Linux Wireless <ilw@linux.intel.com> + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + *  * Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + *  * Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in + *    the documentation and/or other materials provided with the + *    distribution. + *  * Neither the name Intel Corporation nor the names of its + *    contributors may be used to endorse or promote products derived + *    from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include "iwl-trans.h" +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "iwl-eeprom-read.h" +#include "iwl-nvm-parse.h" + +/* list of NVM sections we are allowed/need to read */ +static const int nvm_to_read[] = { +	NVM_SECTION_TYPE_HW, +	NVM_SECTION_TYPE_SW, +	NVM_SECTION_TYPE_CALIBRATION, +	NVM_SECTION_TYPE_PRODUCTION, +}; + +/* used to simplify the shared operations on NCM_ACCESS_CMD versions */ +union iwl_nvm_access_cmd { +	struct iwl_nvm_access_cmd_ver1 ver1; +	struct iwl_nvm_access_cmd_ver2 ver2; +}; +union iwl_nvm_access_resp { +	struct iwl_nvm_access_resp_ver1 ver1; +	struct iwl_nvm_access_resp_ver2 ver2; +}; + +static inline void iwl_nvm_fill_read_ver1(struct iwl_nvm_access_cmd_ver1 *cmd, +					  u16 offset, u16 length) +{ +	cmd->offset = cpu_to_le16(offset); +	cmd->length = cpu_to_le16(length); +	cmd->cache_refresh = 1; +} + +static inline void iwl_nvm_fill_read_ver2(struct iwl_nvm_access_cmd_ver2 *cmd, +					  u16 offset, u16 length, u16 section) +{ +	cmd->offset = cpu_to_le16(offset); +	cmd->length = cpu_to_le16(length); +	cmd->type = cpu_to_le16(section); +} + +static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, +			      u16 offset, u16 length, u8 *data) +{ +	union iwl_nvm_access_cmd nvm_access_cmd; +	union iwl_nvm_access_resp *nvm_resp; +	struct iwl_rx_packet *pkt; +	struct iwl_host_cmd cmd = { +		.id = NVM_ACCESS_CMD, +		.flags = CMD_SYNC | CMD_WANT_SKB, +		.data = { &nvm_access_cmd, }, +	}; +	int ret, bytes_read, offset_read; +	u8 *resp_data; + +	memset(&nvm_access_cmd, 0, sizeof(nvm_access_cmd)); + +	/* TODO: not sure family should be the decider, maybe FW version? */ +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		iwl_nvm_fill_read_ver2(&(nvm_access_cmd.ver2), +				       offset, length, section); +		cmd.len[0] = sizeof(struct iwl_nvm_access_cmd_ver2); +	} else { +		iwl_nvm_fill_read_ver1(&(nvm_access_cmd.ver1), +				       offset, length); +		cmd.len[0] = sizeof(struct iwl_nvm_access_cmd_ver1); +	} + +	ret = iwl_mvm_send_cmd(mvm, &cmd); +	if (ret) +		return ret; + +	pkt = cmd.resp_pkt; +	if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { +		IWL_ERR(mvm, "Bad return from NVM_ACCES_COMMAND (0x%08X)\n", +			pkt->hdr.flags); +		ret = -EIO; +		goto exit; +	} + +	/* Extract NVM response */ +	nvm_resp = (void *)pkt->data; +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		ret = le16_to_cpu(nvm_resp->ver2.status); +		bytes_read = le16_to_cpu(nvm_resp->ver2.length); +		offset_read = le16_to_cpu(nvm_resp->ver2.offset); +		resp_data = nvm_resp->ver2.data; +	} else { +		ret = le16_to_cpu(nvm_resp->ver1.length) <= 0; +		bytes_read = le16_to_cpu(nvm_resp->ver1.length); +		offset_read = le16_to_cpu(nvm_resp->ver1.offset); +		resp_data = nvm_resp->ver1.data; +	} +	if (ret) { +		IWL_ERR(mvm, +			"NVM access command failed with status %d (device: %s)\n", +			ret, mvm->cfg->name); +		ret = -EINVAL; +		goto exit; +	} + +	if (offset_read != offset) { +		IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", +			offset_read); +		ret = -EINVAL; +		goto exit; +	} + +	/* Write data to NVM */ +	memcpy(data + offset, resp_data, bytes_read); +	ret = bytes_read; + +exit: +	iwl_free_resp(&cmd); +	return ret; +} + +/* + * Reads an NVM section completely. + * NICs prior to 7000 family doesn't have a real NVM, but just read + * section 0 which is the EEPROM. Because the EEPROM reading is unlimited + * by uCode, we need to manually check in this case that we don't + * overflow and try to read more than the EEPROM size. + * For 7000 family NICs, we supply the maximal size we can read, and + * the uCode fills the response with as much data as we can, + * without overflowing, so no check is needed. + */ +static int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, +				u8 *data) +{ +	u16 length, offset = 0; +	int ret; +	bool old_eeprom = mvm->cfg->device_family != IWL_DEVICE_FAMILY_7000; + +	length = (iwlwifi_mod_params.amsdu_size_8K ? (8 * 1024) : (4 * 1024)) +		- sizeof(union iwl_nvm_access_cmd) +		- sizeof(struct iwl_rx_packet); +	/* +	 * if length is greater than EEPROM size, truncate it because uCode +	 * doesn't check it by itself, and exit the loop when reached. +	 */ +	if (old_eeprom && length > mvm->cfg->base_params->eeprom_size) +		length = mvm->cfg->base_params->eeprom_size; +	ret = length; + +	/* Read the NVM until exhausted (reading less than requested) */ +	while (ret == length) { +		ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); +		if (ret < 0) { +			IWL_ERR(mvm, +				"Cannot read NVM from section %d offset %d, length %d\n", +				section, offset, length); +			return ret; +		} +		offset += ret; +		if (old_eeprom && offset == mvm->cfg->base_params->eeprom_size) +			break; +	} + +	IWL_INFO(mvm, "NVM section %d read completed\n", section); +	return offset; +} + +static struct iwl_nvm_data * +iwl_parse_nvm_sections(struct iwl_mvm *mvm) +{ +	struct iwl_nvm_section *sections = mvm->nvm_sections; +	const __le16 *hw, *sw, *calib; + +	/* Checking for required sections */ +	if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || +	    !mvm->nvm_sections[NVM_SECTION_TYPE_HW].data) { +		IWL_ERR(mvm, "Can't parse empty NVM sections\n"); +		return NULL; +	} + +	if (WARN_ON(!mvm->cfg)) +		return NULL; + +	hw = (const __le16 *)sections[NVM_SECTION_TYPE_HW].data; +	sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; +	calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; +	return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib); +} + +int iwl_nvm_init(struct iwl_mvm *mvm) +{ +	int ret, i, section; +	u8 *nvm_buffer, *temp; + +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		/* TODO: find correct NVM max size for a section */ +		nvm_buffer = kmalloc(mvm->cfg->base_params->eeprom_size, +				     GFP_KERNEL); +		if (!nvm_buffer) +			return -ENOMEM; +		for (i = 0; i < ARRAY_SIZE(nvm_to_read); i++) { +			section = nvm_to_read[i]; +			/* we override the constness for initial read */ +			ret = iwl_nvm_read_section(mvm, section, nvm_buffer); +			if (ret < 0) +				break; +			temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); +			if (!temp) { +				ret = -ENOMEM; +				break; +			} +			mvm->nvm_sections[section].data = temp; +			mvm->nvm_sections[section].length = ret; +		} +		kfree(nvm_buffer); +		if (ret < 0) +			return ret; +	} else { +		/* allocate eeprom */ +		mvm->eeprom_blob_size = mvm->cfg->base_params->eeprom_size; +		IWL_DEBUG_EEPROM(mvm->trans->dev, "NVM size = %zd\n", +				 mvm->eeprom_blob_size); +		mvm->eeprom_blob = kzalloc(mvm->eeprom_blob_size, GFP_KERNEL); +		if (!mvm->eeprom_blob) +			return -ENOMEM; + +		ret = iwl_nvm_read_section(mvm, 0, mvm->eeprom_blob); +		if (ret != mvm->eeprom_blob_size) { +			IWL_ERR(mvm, "Read partial NVM %d/%zd\n", +				ret, mvm->eeprom_blob_size); +			kfree(mvm->eeprom_blob); +			mvm->eeprom_blob = NULL; +			return -EINVAL; +		} +	} + +	ret = 0; +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) +		mvm->nvm_data = iwl_parse_nvm_sections(mvm); +	else +		mvm->nvm_data = +			iwl_parse_eeprom_data(mvm->trans->dev, +					      mvm->cfg, +					      mvm->eeprom_blob, +					      mvm->eeprom_blob_size); + +	if (!mvm->nvm_data) { +		kfree(mvm->eeprom_blob); +		mvm->eeprom_blob = NULL; +		ret = -ENOMEM; +	} + +	return ret; +}  |