fw.c 10.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
/*
 * Intel Wireless Multicomm 3200 WiFi driver
 *
 * Copyright (C) 2009 Intel Corporation. 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 of 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.
 *
 *
 * Intel Corporation <ilw@linux.intel.com>
 * Samuel Ortiz <samuel.ortiz@intel.com>
 * Zhu Yi <yi.zhu@intel.com>
 *
 */

#include <linux/kernel.h>
#include <linux/firmware.h>

#include "iwm.h"
#include "bus.h"
#include "hal.h"
#include "umac.h"
#include "debug.h"
#include "fw.h"
#include "commands.h"

static const char fw_barker[] = "*WESTOPFORNOONE*";

/*
 * @op_code: Op code we're looking for.
 * @index: There can be several instances of the same opcode within
 *         the firmware. Index specifies which one we're looking for.
 */
static int iwm_fw_op_offset(struct iwm_priv *iwm, const struct firmware *fw,
			    u16 op_code, u32 index)
{
	int offset = -EINVAL, fw_offset;
	u32 op_index = 0;
	const u8 *fw_ptr;
	struct iwm_fw_hdr_rec *rec;

	fw_offset = 0;
	fw_ptr = fw->data;

	/* We first need to look for the firmware barker */
	if (memcmp(fw_ptr, fw_barker, IWM_HDR_BARKER_LEN)) {
		IWM_ERR(iwm, "No barker string in this FW\n");
		return -EINVAL;
	}

	if (fw->size < IWM_HDR_LEN) {
R
Randy Dunlap 已提交
75
		IWM_ERR(iwm, "FW is too small (%zu)\n", fw->size);
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
		return -EINVAL;
	}

	fw_offset += IWM_HDR_BARKER_LEN;

	while (fw_offset < fw->size) {
		rec = (struct iwm_fw_hdr_rec *)(fw_ptr + fw_offset);

		IWM_DBG_FW(iwm, DBG, "FW: op_code: 0x%x, len: %d @ 0x%x\n",
			   rec->op_code, rec->len, fw_offset);

		if (rec->op_code == IWM_HDR_REC_OP_INVALID) {
			IWM_DBG_FW(iwm, DBG, "Reached INVALID op code\n");
			break;
		}

		if (rec->op_code == op_code) {
			if (op_index == index) {
				fw_offset += sizeof(struct iwm_fw_hdr_rec);
				offset = fw_offset;
				goto out;
			}
			op_index++;
		}

		fw_offset += sizeof(struct iwm_fw_hdr_rec) + rec->len;
	}

 out:
	return offset;
}

static int iwm_load_firmware_chunk(struct iwm_priv *iwm,
				   const struct firmware *fw,
				   struct iwm_fw_img_desc *img_desc)
{
	struct iwm_udma_nonwifi_cmd target_cmd;
	u32 chunk_size;
	const u8 *chunk_ptr;
	int ret = 0;

	IWM_DBG_FW(iwm, INFO, "Loading FW chunk: %d bytes @ 0x%x\n",
		   img_desc->length, img_desc->address);

	target_cmd.opcode = UMAC_HDI_OUT_OPCODE_WRITE;
	target_cmd.handle_by_hw = 1;
	target_cmd.op2 = 0;
	target_cmd.resp = 0;
	target_cmd.eop = 1;

	chunk_size = img_desc->length;
	chunk_ptr = fw->data + img_desc->offset;

	while (chunk_size > 0) {
		u32 tmp_chunk_size;

		tmp_chunk_size = min_t(u32, chunk_size,
				       IWM_MAX_NONWIFI_CMD_BUFF_SIZE);

		target_cmd.addr = cpu_to_le32(img_desc->address +
				    (chunk_ptr - fw->data - img_desc->offset));
		target_cmd.op1_sz = cpu_to_le32(tmp_chunk_size);

		IWM_DBG_FW(iwm, DBG, "\t%d bytes @ 0x%x\n",
			   tmp_chunk_size, target_cmd.addr);

		ret = iwm_hal_send_target_cmd(iwm, &target_cmd, chunk_ptr);
		if (ret < 0) {
			IWM_ERR(iwm, "Couldn't load FW chunk\n");
			break;
		}

		chunk_size -= tmp_chunk_size;
		chunk_ptr += tmp_chunk_size;
	}

	return ret;
}
/*
 * To load a fw image to the target, we basically go through the
 * fw, looking for OP_MEM_DESC records. Once we found one, we
 * pass it to iwm_load_firmware_chunk().
 * The OP_MEM_DESC records contain the actuall memory chunk to be
 * sent, but also the destination address.
 */
static int iwm_load_img(struct iwm_priv *iwm, const char *img_name)
{
	const struct firmware *fw;
	struct iwm_fw_img_desc *img_desc;
	struct iwm_fw_img_ver *ver;
	int ret = 0, fw_offset;
	u32 opcode_idx = 0, build_date;
	char *build_tag;

	ret = request_firmware(&fw, img_name, iwm_to_dev(iwm));
	if (ret) {
		IWM_ERR(iwm, "Request firmware failed");
		return ret;
	}

	IWM_DBG_FW(iwm, INFO, "Start to load FW %s\n", img_name);

	while (1) {
		fw_offset = iwm_fw_op_offset(iwm, fw,
					     IWM_HDR_REC_OP_MEM_DESC,
					     opcode_idx);
		if (fw_offset < 0)
			break;

		img_desc = (struct iwm_fw_img_desc *)(fw->data + fw_offset);
		ret = iwm_load_firmware_chunk(iwm, fw, img_desc);
		if (ret < 0)
			goto err_release_fw;
		opcode_idx++;
	};

	/* Read firmware version */
	fw_offset = iwm_fw_op_offset(iwm, fw, IWM_HDR_REC_OP_SW_VER, 0);
	if (fw_offset < 0)
		goto err_release_fw;

	ver = (struct iwm_fw_img_ver *)(fw->data + fw_offset);

	/* Read build tag */
	fw_offset = iwm_fw_op_offset(iwm, fw, IWM_HDR_REC_OP_BUILD_TAG, 0);
	if (fw_offset < 0)
		goto err_release_fw;

	build_tag = (char *)(fw->data + fw_offset);

	/* Read build date */
	fw_offset = iwm_fw_op_offset(iwm, fw, IWM_HDR_REC_OP_BUILD_DATE, 0);
	if (fw_offset < 0)
		goto err_release_fw;

	build_date = *(u32 *)(fw->data + fw_offset);

	IWM_INFO(iwm, "%s:\n", img_name);
	IWM_INFO(iwm, "\tVersion:    %02X.%02X\n", ver->major, ver->minor);
	IWM_INFO(iwm, "\tBuild tag:  %s\n", build_tag);
	IWM_INFO(iwm, "\tBuild date: %x-%x-%x\n",
		 IWM_BUILD_YEAR(build_date), IWM_BUILD_MONTH(build_date),
		 IWM_BUILD_DAY(build_date));


 err_release_fw:
	release_firmware(fw);

	return ret;
}

static int iwm_load_umac(struct iwm_priv *iwm)
{
	struct iwm_udma_nonwifi_cmd target_cmd;
	int ret;

	ret = iwm_load_img(iwm, iwm->bus_ops->umac_name);
	if (ret < 0)
		return ret;

	/* We've loaded the UMAC, we can tell the target to jump there */
	target_cmd.opcode = UMAC_HDI_OUT_OPCODE_JUMP;
	target_cmd.addr = cpu_to_le32(UMAC_MU_FW_INST_DATA_12_ADDR);
	target_cmd.op1_sz = 0;
	target_cmd.op2 = 0;
	target_cmd.handle_by_hw = 0;
	target_cmd.resp = 1 ;
	target_cmd.eop = 1;

	ret = iwm_hal_send_target_cmd(iwm, &target_cmd, NULL);
	if (ret < 0)
		IWM_ERR(iwm, "Couldn't send JMP command\n");

	return ret;
}

static int iwm_load_lmac(struct iwm_priv *iwm, const char *img_name)
{
	int ret;

	ret = iwm_load_img(iwm, img_name);
	if (ret < 0)
		return ret;

	return iwm_send_umac_reset(iwm,
			cpu_to_le32(UMAC_RST_CTRL_FLG_LARC_CLK_EN), 0);
}

/*
 * We currently have to load 3 FWs:
 * 1) The UMAC (Upper MAC).
 * 2) The calibration LMAC (Lower MAC).
 *    We then send the calibration init command, so that the device can
 *    run a first calibration round.
 * 3) The operational LMAC, which replaces the calibration one when it's
 *    done with the first calibration round.
 *
 * Once those 3 FWs have been loaded, we send the periodic calibration
 * command, and then the device is available for regular 802.11 operations.
 */
int iwm_load_fw(struct iwm_priv *iwm)
{
Z
Zhu Yi 已提交
278
	unsigned long init_calib_map, periodic_calib_map;
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
	int ret;

	/* We first start downloading the UMAC */
	ret = iwm_load_umac(iwm);
	if (ret < 0) {
		IWM_ERR(iwm, "UMAC loading failed\n");
		return ret;
	}

	/* Handle UMAC_ALIVE notification */
	ret = iwm_notif_handle(iwm, UMAC_NOTIFY_OPCODE_ALIVE, IWM_SRC_UMAC,
			       WAIT_NOTIF_TIMEOUT);
	if (ret) {
		IWM_ERR(iwm, "Handle UMAC_ALIVE failed: %d\n", ret);
		return ret;
	}

	/* UMAC is alive, we can download the calibration LMAC */
	ret = iwm_load_lmac(iwm, iwm->bus_ops->calib_lmac_name);
	if (ret) {
		IWM_ERR(iwm, "Calibration LMAC loading failed\n");
		return ret;
	}

	/* Handle UMAC_INIT_COMPLETE notification */
	ret = iwm_notif_handle(iwm, UMAC_NOTIFY_OPCODE_INIT_COMPLETE,
			       IWM_SRC_UMAC, WAIT_NOTIF_TIMEOUT);
	if (ret) {
		IWM_ERR(iwm, "Handle INIT_COMPLETE failed for calibration "
			"LMAC: %d\n", ret);
		return ret;
	}

	/* Read EEPROM data */
	ret = iwm_eeprom_init(iwm);
	if (ret < 0) {
		IWM_ERR(iwm, "Couldn't init eeprom array\n");
		return ret;
	}

Z
Zhu Yi 已提交
319 320 321
	init_calib_map = iwm->conf.calib_map & IWM_CALIB_MAP_INIT_MSK;
	periodic_calib_map = IWM_CALIB_MAP_PER_LMAC(iwm->conf.calib_map);

322
	/* Read RX IQ calibration result from EEPROM */
Z
Zhu Yi 已提交
323
	if (test_bit(PHY_CALIBRATE_RX_IQ_CMD, &init_calib_map)) {
324 325 326 327 328
		iwm_store_rxiq_calib_result(iwm);
		set_bit(PHY_CALIBRATE_RX_IQ_CMD, &iwm->calib_done_map);
	}

	iwm_send_prio_table(iwm);
Z
Zhu Yi 已提交
329
	iwm_send_init_calib_cfg(iwm, init_calib_map);
330

Z
Zhu Yi 已提交
331
	while (iwm->calib_done_map != init_calib_map) {
332 333 334 335 336 337 338 339
		ret = iwm_notif_handle(iwm, CALIBRATION_RES_NOTIFICATION,
				       IWM_SRC_LMAC, WAIT_NOTIF_TIMEOUT);
		if (ret) {
			IWM_ERR(iwm, "Wait for calibration result timeout\n");
			goto out;
		}
		IWM_DBG_FW(iwm, DBG, "Got calibration result. calib_done_map: "
			   "0x%lx, requested calibrations: 0x%lx\n",
Z
Zhu Yi 已提交
340
			   iwm->calib_done_map, init_calib_map);
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
	}

	/* Handle LMAC CALIBRATION_COMPLETE notification */
	ret = iwm_notif_handle(iwm, CALIBRATION_COMPLETE_NOTIFICATION,
			       IWM_SRC_LMAC, WAIT_NOTIF_TIMEOUT);
	if (ret) {
		IWM_ERR(iwm, "Wait for CALIBRATION_COMPLETE timeout\n");
		goto out;
	}

	IWM_INFO(iwm, "LMAC calibration done: 0x%lx\n", iwm->calib_done_map);

	iwm_send_umac_reset(iwm, cpu_to_le32(UMAC_RST_CTRL_FLG_LARC_RESET), 1);

	ret = iwm_notif_handle(iwm, UMAC_CMD_OPCODE_RESET, IWM_SRC_UMAC,
			       WAIT_NOTIF_TIMEOUT);
	if (ret) {
		IWM_ERR(iwm, "Wait for UMAC RESET timeout\n");
		goto out;
	}

	/* Download the operational LMAC */
	ret = iwm_load_lmac(iwm, iwm->bus_ops->lmac_name);
	if (ret) {
		IWM_ERR(iwm, "LMAC loading failed\n");
		goto out;
	}

	ret = iwm_notif_handle(iwm, UMAC_NOTIFY_OPCODE_INIT_COMPLETE,
			       IWM_SRC_UMAC, WAIT_NOTIF_TIMEOUT);
	if (ret) {
		IWM_ERR(iwm, "Handle INIT_COMPLETE failed for LMAC: %d\n", ret);
		goto out;
	}

	iwm_send_prio_table(iwm);
	iwm_send_calib_results(iwm);
Z
Zhu Yi 已提交
378
	iwm_send_periodic_calib_cfg(iwm, periodic_calib_map);
379 380 381 382 383 384 385

	return 0;

 out:
	iwm_eeprom_exit(iwm);
	return ret;
}