| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 1 | /* | 
 | 2 |  * This file is part of wl1271 | 
 | 3 |  * | 
| Luciano Coelho | 2f826f5 | 2010-03-26 12:53:21 +0200 | [diff] [blame] | 4 |  * Copyright (C) 2008-2010 Nokia Corporation | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 5 |  * | 
 | 6 |  * Contact: Luciano Coelho <luciano.coelho@nokia.com> | 
 | 7 |  * | 
 | 8 |  * This program is free software; you can redistribute it and/or | 
 | 9 |  * modify it under the terms of the GNU General Public License | 
 | 10 |  * version 2 as published by the Free Software Foundation. | 
 | 11 |  * | 
 | 12 |  * This program is distributed in the hope that it will be useful, but | 
 | 13 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 14 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 | 15 |  * General Public License for more details. | 
 | 16 |  * | 
 | 17 |  * You should have received a copy of the GNU General Public License | 
 | 18 |  * along with this program; if not, write to the Free Software | 
 | 19 |  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | 
 | 20 |  * 02110-1301 USA | 
 | 21 |  * | 
 | 22 |  */ | 
 | 23 |  | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 24 | #include <linux/slab.h> | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 25 | #include <linux/wl12xx.h> | 
| Paul Gortmaker | ee40fa0 | 2011-05-27 16:14:23 -0400 | [diff] [blame] | 26 | #include <linux/export.h> | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 27 |  | 
| Shahar Levi | 00d2010 | 2010-11-08 11:20:10 +0000 | [diff] [blame] | 28 | #include "acx.h" | 
 | 29 | #include "reg.h" | 
 | 30 | #include "boot.h" | 
 | 31 | #include "io.h" | 
 | 32 | #include "event.h" | 
| Arik Nemtsov | ae113b5 | 2010-10-16 18:45:07 +0200 | [diff] [blame] | 33 | #include "rx.h" | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 34 |  | 
 | 35 | static struct wl1271_partition_set part_table[PART_TABLE_LEN] = { | 
 | 36 | 	[PART_DOWN] = { | 
 | 37 | 		.mem = { | 
 | 38 | 			.start = 0x00000000, | 
 | 39 | 			.size  = 0x000177c0 | 
 | 40 | 		}, | 
 | 41 | 		.reg = { | 
 | 42 | 			.start = REGISTERS_BASE, | 
 | 43 | 			.size  = 0x00008800 | 
 | 44 | 		}, | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 45 | 		.mem2 = { | 
 | 46 | 			.start = 0x00000000, | 
 | 47 | 			.size  = 0x00000000 | 
 | 48 | 		}, | 
 | 49 | 		.mem3 = { | 
 | 50 | 			.start = 0x00000000, | 
 | 51 | 			.size  = 0x00000000 | 
 | 52 | 		}, | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 53 | 	}, | 
 | 54 |  | 
 | 55 | 	[PART_WORK] = { | 
 | 56 | 		.mem = { | 
 | 57 | 			.start = 0x00040000, | 
 | 58 | 			.size  = 0x00014fc0 | 
 | 59 | 		}, | 
 | 60 | 		.reg = { | 
 | 61 | 			.start = REGISTERS_BASE, | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 62 | 			.size  = 0x0000a000 | 
 | 63 | 		}, | 
 | 64 | 		.mem2 = { | 
 | 65 | 			.start = 0x003004f8, | 
 | 66 | 			.size  = 0x00000004 | 
 | 67 | 		}, | 
 | 68 | 		.mem3 = { | 
 | 69 | 			.start = 0x00040404, | 
 | 70 | 			.size  = 0x00000000 | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 71 | 		}, | 
 | 72 | 	}, | 
 | 73 |  | 
 | 74 | 	[PART_DRPW] = { | 
 | 75 | 		.mem = { | 
 | 76 | 			.start = 0x00040000, | 
 | 77 | 			.size  = 0x00014fc0 | 
 | 78 | 		}, | 
 | 79 | 		.reg = { | 
 | 80 | 			.start = DRPW_BASE, | 
 | 81 | 			.size  = 0x00006000 | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 82 | 		}, | 
 | 83 | 		.mem2 = { | 
 | 84 | 			.start = 0x00000000, | 
 | 85 | 			.size  = 0x00000000 | 
 | 86 | 		}, | 
 | 87 | 		.mem3 = { | 
 | 88 | 			.start = 0x00000000, | 
 | 89 | 			.size  = 0x00000000 | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 90 | 		} | 
 | 91 | 	} | 
 | 92 | }; | 
 | 93 |  | 
 | 94 | static void wl1271_boot_set_ecpu_ctrl(struct wl1271 *wl, u32 flag) | 
 | 95 | { | 
 | 96 | 	u32 cpu_ctrl; | 
 | 97 |  | 
 | 98 | 	/* 10.5.0 run the firmware (I) */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 99 | 	cpu_ctrl = wl1271_read32(wl, ACX_REG_ECPU_CONTROL); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 100 |  | 
 | 101 | 	/* 10.5.1 run the firmware (II) */ | 
 | 102 | 	cpu_ctrl |= flag; | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 103 | 	wl1271_write32(wl, ACX_REG_ECPU_CONTROL, cpu_ctrl); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 104 | } | 
 | 105 |  | 
| Ido Yariv | 842f1a6 | 2011-06-06 14:57:04 +0300 | [diff] [blame] | 106 | static unsigned int wl12xx_get_fw_ver_quirks(struct wl1271 *wl) | 
 | 107 | { | 
 | 108 | 	unsigned int quirks = 0; | 
 | 109 | 	unsigned int *fw_ver = wl->chip.fw_ver; | 
 | 110 |  | 
| Ido Yariv | 95dac04f | 2011-06-06 14:57:06 +0300 | [diff] [blame] | 111 | 	/* Only new station firmwares support routing fw logs to the host */ | 
 | 112 | 	if ((fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_STA) && | 
 | 113 | 	    (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_FWLOG_STA_MIN)) | 
 | 114 | 		quirks |= WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED; | 
 | 115 |  | 
 | 116 | 	/* This feature is not yet supported for AP mode */ | 
 | 117 | 	if (fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_AP) | 
 | 118 | 		quirks |= WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED; | 
 | 119 |  | 
| Ido Yariv | 842f1a6 | 2011-06-06 14:57:04 +0300 | [diff] [blame] | 120 | 	return quirks; | 
 | 121 | } | 
 | 122 |  | 
| Levi, Shahar | 4b7fac7 | 2011-01-23 07:27:22 +0100 | [diff] [blame] | 123 | static void wl1271_parse_fw_ver(struct wl1271 *wl) | 
 | 124 | { | 
 | 125 | 	int ret; | 
 | 126 |  | 
 | 127 | 	ret = sscanf(wl->chip.fw_ver_str + 4, "%u.%u.%u.%u.%u", | 
 | 128 | 		     &wl->chip.fw_ver[0], &wl->chip.fw_ver[1], | 
 | 129 | 		     &wl->chip.fw_ver[2], &wl->chip.fw_ver[3], | 
 | 130 | 		     &wl->chip.fw_ver[4]); | 
 | 131 |  | 
 | 132 | 	if (ret != 5) { | 
 | 133 | 		wl1271_warning("fw version incorrect value"); | 
 | 134 | 		memset(wl->chip.fw_ver, 0, sizeof(wl->chip.fw_ver)); | 
 | 135 | 		return; | 
 | 136 | 	} | 
| Ido Yariv | 842f1a6 | 2011-06-06 14:57:04 +0300 | [diff] [blame] | 137 |  | 
 | 138 | 	/* Check if any quirks are needed with older fw versions */ | 
 | 139 | 	wl->quirks |= wl12xx_get_fw_ver_quirks(wl); | 
| Levi, Shahar | 4b7fac7 | 2011-01-23 07:27:22 +0100 | [diff] [blame] | 140 | } | 
 | 141 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 142 | static void wl1271_boot_fw_version(struct wl1271 *wl) | 
 | 143 | { | 
 | 144 | 	struct wl1271_static_data static_data; | 
 | 145 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 146 | 	wl1271_read(wl, wl->cmd_box_addr, &static_data, sizeof(static_data), | 
 | 147 | 		    false); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 148 |  | 
| Levi, Shahar | 4b7fac7 | 2011-01-23 07:27:22 +0100 | [diff] [blame] | 149 | 	strncpy(wl->chip.fw_ver_str, static_data.fw_version, | 
 | 150 | 		sizeof(wl->chip.fw_ver_str)); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 151 |  | 
 | 152 | 	/* make sure the string is NULL-terminated */ | 
| Levi, Shahar | 4b7fac7 | 2011-01-23 07:27:22 +0100 | [diff] [blame] | 153 | 	wl->chip.fw_ver_str[sizeof(wl->chip.fw_ver_str) - 1] = '\0'; | 
 | 154 |  | 
 | 155 | 	wl1271_parse_fw_ver(wl); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 156 | } | 
 | 157 |  | 
 | 158 | static int wl1271_boot_upload_firmware_chunk(struct wl1271 *wl, void *buf, | 
 | 159 | 					     size_t fw_data_len, u32 dest) | 
 | 160 | { | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 161 | 	struct wl1271_partition_set partition; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 162 | 	int addr, chunk_num, partition_limit; | 
| Juuso Oikarinen | 1fba497 | 2009-10-08 21:56:32 +0300 | [diff] [blame] | 163 | 	u8 *p, *chunk; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 164 |  | 
 | 165 | 	/* whal_FwCtrl_LoadFwImageSm() */ | 
 | 166 |  | 
 | 167 | 	wl1271_debug(DEBUG_BOOT, "starting firmware upload"); | 
 | 168 |  | 
| Luciano Coelho | 73d0a13 | 2009-08-11 11:58:27 +0300 | [diff] [blame] | 169 | 	wl1271_debug(DEBUG_BOOT, "fw_data_len %zd chunk_size %d", | 
 | 170 | 		     fw_data_len, CHUNK_SIZE); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 171 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 172 | 	if ((fw_data_len % 4) != 0) { | 
 | 173 | 		wl1271_error("firmware length not multiple of four"); | 
 | 174 | 		return -EIO; | 
 | 175 | 	} | 
 | 176 |  | 
| Juuso Oikarinen | 1fba497 | 2009-10-08 21:56:32 +0300 | [diff] [blame] | 177 | 	chunk = kmalloc(CHUNK_SIZE, GFP_KERNEL); | 
| Juuso Oikarinen | ed317788 | 2009-10-13 12:47:57 +0300 | [diff] [blame] | 178 | 	if (!chunk) { | 
| Juuso Oikarinen | 1fba497 | 2009-10-08 21:56:32 +0300 | [diff] [blame] | 179 | 		wl1271_error("allocation for firmware upload chunk failed"); | 
 | 180 | 		return -ENOMEM; | 
 | 181 | 	} | 
 | 182 |  | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 183 | 	memcpy(&partition, &part_table[PART_DOWN], sizeof(partition)); | 
 | 184 | 	partition.mem.start = dest; | 
 | 185 | 	wl1271_set_partition(wl, &partition); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 186 |  | 
 | 187 | 	/* 10.1 set partition limit and chunk num */ | 
 | 188 | 	chunk_num = 0; | 
 | 189 | 	partition_limit = part_table[PART_DOWN].mem.size; | 
 | 190 |  | 
 | 191 | 	while (chunk_num < fw_data_len / CHUNK_SIZE) { | 
 | 192 | 		/* 10.2 update partition, if needed */ | 
 | 193 | 		addr = dest + (chunk_num + 2) * CHUNK_SIZE; | 
 | 194 | 		if (addr > partition_limit) { | 
 | 195 | 			addr = dest + chunk_num * CHUNK_SIZE; | 
 | 196 | 			partition_limit = chunk_num * CHUNK_SIZE + | 
 | 197 | 				part_table[PART_DOWN].mem.size; | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 198 | 			partition.mem.start = addr; | 
 | 199 | 			wl1271_set_partition(wl, &partition); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 200 | 		} | 
 | 201 |  | 
 | 202 | 		/* 10.3 upload the chunk */ | 
 | 203 | 		addr = dest + chunk_num * CHUNK_SIZE; | 
 | 204 | 		p = buf + chunk_num * CHUNK_SIZE; | 
| Juuso Oikarinen | 1fba497 | 2009-10-08 21:56:32 +0300 | [diff] [blame] | 205 | 		memcpy(chunk, p, CHUNK_SIZE); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 206 | 		wl1271_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x", | 
 | 207 | 			     p, addr); | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 208 | 		wl1271_write(wl, addr, chunk, CHUNK_SIZE, false); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 209 |  | 
 | 210 | 		chunk_num++; | 
 | 211 | 	} | 
 | 212 |  | 
 | 213 | 	/* 10.4 upload the last chunk */ | 
 | 214 | 	addr = dest + chunk_num * CHUNK_SIZE; | 
 | 215 | 	p = buf + chunk_num * CHUNK_SIZE; | 
| Juuso Oikarinen | 1fba497 | 2009-10-08 21:56:32 +0300 | [diff] [blame] | 216 | 	memcpy(chunk, p, fw_data_len % CHUNK_SIZE); | 
| Luciano Coelho | 73d0a13 | 2009-08-11 11:58:27 +0300 | [diff] [blame] | 217 | 	wl1271_debug(DEBUG_BOOT, "uploading fw last chunk (%zd B) 0x%p to 0x%x", | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 218 | 		     fw_data_len % CHUNK_SIZE, p, addr); | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 219 | 	wl1271_write(wl, addr, chunk, fw_data_len % CHUNK_SIZE, false); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 220 |  | 
| Juuso Oikarinen | 1fba497 | 2009-10-08 21:56:32 +0300 | [diff] [blame] | 221 | 	kfree(chunk); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 222 | 	return 0; | 
 | 223 | } | 
 | 224 |  | 
 | 225 | static int wl1271_boot_upload_firmware(struct wl1271 *wl) | 
 | 226 | { | 
 | 227 | 	u32 chunks, addr, len; | 
| Juuso Oikarinen | ed317788 | 2009-10-13 12:47:57 +0300 | [diff] [blame] | 228 | 	int ret = 0; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 229 | 	u8 *fw; | 
 | 230 |  | 
 | 231 | 	fw = wl->fw; | 
| Luciano Coelho | d0f63b2 | 2009-10-15 10:33:29 +0300 | [diff] [blame] | 232 | 	chunks = be32_to_cpup((__be32 *) fw); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 233 | 	fw += sizeof(u32); | 
 | 234 |  | 
 | 235 | 	wl1271_debug(DEBUG_BOOT, "firmware chunks to be uploaded: %u", chunks); | 
 | 236 |  | 
 | 237 | 	while (chunks--) { | 
| Luciano Coelho | d0f63b2 | 2009-10-15 10:33:29 +0300 | [diff] [blame] | 238 | 		addr = be32_to_cpup((__be32 *) fw); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 239 | 		fw += sizeof(u32); | 
| Luciano Coelho | d0f63b2 | 2009-10-15 10:33:29 +0300 | [diff] [blame] | 240 | 		len = be32_to_cpup((__be32 *) fw); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 241 | 		fw += sizeof(u32); | 
 | 242 |  | 
 | 243 | 		if (len > 300000) { | 
 | 244 | 			wl1271_info("firmware chunk too long: %u", len); | 
 | 245 | 			return -EINVAL; | 
 | 246 | 		} | 
 | 247 | 		wl1271_debug(DEBUG_BOOT, "chunk %d addr 0x%x len %u", | 
 | 248 | 			     chunks, addr, len); | 
| Juuso Oikarinen | ed317788 | 2009-10-13 12:47:57 +0300 | [diff] [blame] | 249 | 		ret = wl1271_boot_upload_firmware_chunk(wl, fw, len, addr); | 
 | 250 | 		if (ret != 0) | 
 | 251 | 			break; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 252 | 		fw += len; | 
 | 253 | 	} | 
 | 254 |  | 
| Juuso Oikarinen | ed317788 | 2009-10-13 12:47:57 +0300 | [diff] [blame] | 255 | 	return ret; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 256 | } | 
 | 257 |  | 
 | 258 | static int wl1271_boot_upload_nvs(struct wl1271 *wl) | 
 | 259 | { | 
 | 260 | 	size_t nvs_len, burst_len; | 
 | 261 | 	int i; | 
 | 262 | 	u32 dest_addr, val; | 
| Juuso Oikarinen | 152ee6e | 2010-02-18 13:25:42 +0200 | [diff] [blame] | 263 | 	u8 *nvs_ptr, *nvs_aligned; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 264 |  | 
| Juuso Oikarinen | 152ee6e | 2010-02-18 13:25:42 +0200 | [diff] [blame] | 265 | 	if (wl->nvs == NULL) | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 266 | 		return -ENODEV; | 
 | 267 |  | 
| Shahar Levi | bc765bf | 2011-03-06 16:32:10 +0200 | [diff] [blame] | 268 | 	if (wl->chip.id == CHIP_ID_1283_PG20) { | 
 | 269 | 		struct wl128x_nvs_file *nvs = (struct wl128x_nvs_file *)wl->nvs; | 
| Juuso Oikarinen | 02fabb0 | 2010-08-19 04:41:15 +0200 | [diff] [blame] | 270 |  | 
| Shahar Levi | bc765bf | 2011-03-06 16:32:10 +0200 | [diff] [blame] | 271 | 		if (wl->nvs_len == sizeof(struct wl128x_nvs_file)) { | 
 | 272 | 			if (nvs->general_params.dual_mode_select) | 
 | 273 | 				wl->enable_11a = true; | 
 | 274 | 		} else { | 
 | 275 | 			wl1271_error("nvs size is not as expected: %zu != %zu", | 
 | 276 | 				     wl->nvs_len, | 
 | 277 | 				     sizeof(struct wl128x_nvs_file)); | 
 | 278 | 			kfree(wl->nvs); | 
 | 279 | 			wl->nvs = NULL; | 
 | 280 | 			wl->nvs_len = 0; | 
 | 281 | 			return -EILSEQ; | 
 | 282 | 		} | 
| Juuso Oikarinen | 02fabb0 | 2010-08-19 04:41:15 +0200 | [diff] [blame] | 283 |  | 
| Shahar Levi | bc765bf | 2011-03-06 16:32:10 +0200 | [diff] [blame] | 284 | 		/* only the first part of the NVS needs to be uploaded */ | 
 | 285 | 		nvs_len = sizeof(nvs->nvs); | 
 | 286 | 		nvs_ptr = (u8 *)nvs->nvs; | 
 | 287 |  | 
 | 288 | 	} else { | 
 | 289 | 		struct wl1271_nvs_file *nvs = | 
 | 290 | 			(struct wl1271_nvs_file *)wl->nvs; | 
 | 291 | 		/* | 
 | 292 | 		 * FIXME: the LEGACY NVS image support (NVS's missing the 5GHz | 
 | 293 | 		 * band configurations) can be removed when those NVS files stop | 
 | 294 | 		 * floating around. | 
 | 295 | 		 */ | 
 | 296 | 		if (wl->nvs_len == sizeof(struct wl1271_nvs_file) || | 
 | 297 | 		    wl->nvs_len == WL1271_INI_LEGACY_NVS_FILE_SIZE) { | 
| Arik Nemtsov | cabb81c | 2011-08-23 15:56:22 +0300 | [diff] [blame] | 298 | 			if (nvs->general_params.dual_mode_select) | 
| Shahar Levi | bc765bf | 2011-03-06 16:32:10 +0200 | [diff] [blame] | 299 | 				wl->enable_11a = true; | 
 | 300 | 		} | 
 | 301 |  | 
 | 302 | 		if (wl->nvs_len != sizeof(struct wl1271_nvs_file) && | 
 | 303 | 		    (wl->nvs_len != WL1271_INI_LEGACY_NVS_FILE_SIZE || | 
 | 304 | 		     wl->enable_11a)) { | 
 | 305 | 			wl1271_error("nvs size is not as expected: %zu != %zu", | 
 | 306 | 				wl->nvs_len, sizeof(struct wl1271_nvs_file)); | 
 | 307 | 			kfree(wl->nvs); | 
 | 308 | 			wl->nvs = NULL; | 
 | 309 | 			wl->nvs_len = 0; | 
 | 310 | 			return -EILSEQ; | 
 | 311 | 		} | 
 | 312 |  | 
 | 313 | 		/* only the first part of the NVS needs to be uploaded */ | 
 | 314 | 		nvs_len = sizeof(nvs->nvs); | 
 | 315 | 		nvs_ptr = (u8 *) nvs->nvs; | 
 | 316 | 	} | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 317 |  | 
| Juuso Oikarinen | 1b72aec | 2010-03-18 12:26:39 +0200 | [diff] [blame] | 318 | 	/* update current MAC address to NVS */ | 
 | 319 | 	nvs_ptr[11] = wl->mac_addr[0]; | 
 | 320 | 	nvs_ptr[10] = wl->mac_addr[1]; | 
 | 321 | 	nvs_ptr[6] = wl->mac_addr[2]; | 
 | 322 | 	nvs_ptr[5] = wl->mac_addr[3]; | 
 | 323 | 	nvs_ptr[4] = wl->mac_addr[4]; | 
 | 324 | 	nvs_ptr[3] = wl->mac_addr[5]; | 
 | 325 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 326 | 	/* | 
 | 327 | 	 * Layout before the actual NVS tables: | 
 | 328 | 	 * 1 byte : burst length. | 
 | 329 | 	 * 2 bytes: destination address. | 
 | 330 | 	 * n bytes: data to burst copy. | 
 | 331 | 	 * | 
 | 332 | 	 * This is ended by a 0 length, then the NVS tables. | 
 | 333 | 	 */ | 
 | 334 |  | 
 | 335 | 	/* FIXME: Do we need to check here whether the LSB is 1? */ | 
 | 336 | 	while (nvs_ptr[0]) { | 
 | 337 | 		burst_len = nvs_ptr[0]; | 
 | 338 | 		dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8)); | 
 | 339 |  | 
| Juuso Oikarinen | 2f63b01 | 2010-08-10 06:38:35 +0200 | [diff] [blame] | 340 | 		/* | 
 | 341 | 		 * Due to our new wl1271_translate_reg_addr function, | 
 | 342 | 		 * we need to add the REGISTER_BASE to the destination | 
 | 343 | 		 */ | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 344 | 		dest_addr += REGISTERS_BASE; | 
 | 345 |  | 
 | 346 | 		/* We move our pointer to the data */ | 
 | 347 | 		nvs_ptr += 3; | 
 | 348 |  | 
 | 349 | 		for (i = 0; i < burst_len; i++) { | 
 | 350 | 			val = (nvs_ptr[0] | (nvs_ptr[1] << 8) | 
 | 351 | 			       | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); | 
 | 352 |  | 
 | 353 | 			wl1271_debug(DEBUG_BOOT, | 
 | 354 | 				     "nvs burst write 0x%x: 0x%x", | 
 | 355 | 				     dest_addr, val); | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 356 | 			wl1271_write32(wl, dest_addr, val); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 357 |  | 
 | 358 | 			nvs_ptr += 4; | 
 | 359 | 			dest_addr += 4; | 
 | 360 | 		} | 
 | 361 | 	} | 
 | 362 |  | 
 | 363 | 	/* | 
 | 364 | 	 * We've reached the first zero length, the first NVS table | 
| Ido Yariv | 67e0208 | 2010-09-22 09:53:13 +0200 | [diff] [blame] | 365 | 	 * is located at an aligned offset which is at least 7 bytes further. | 
| Shahar Levi | bc765bf | 2011-03-06 16:32:10 +0200 | [diff] [blame] | 366 | 	 * NOTE: The wl->nvs->nvs element must be first, in order to | 
 | 367 | 	 * simplify the casting, we assume it is at the beginning of | 
 | 368 | 	 * the wl->nvs structure. | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 369 | 	 */ | 
| Shahar Levi | bc765bf | 2011-03-06 16:32:10 +0200 | [diff] [blame] | 370 | 	nvs_ptr = (u8 *)wl->nvs + | 
 | 371 | 			ALIGN(nvs_ptr - (u8 *)wl->nvs + 7, 4); | 
 | 372 | 	nvs_len -= nvs_ptr - (u8 *)wl->nvs; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 373 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 374 | 	/* Now we must set the partition correctly */ | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 375 | 	wl1271_set_partition(wl, &part_table[PART_WORK]); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 376 |  | 
 | 377 | 	/* Copy the NVS tables to a new block to ensure alignment */ | 
| Ido Yariv | 67e0208 | 2010-09-22 09:53:13 +0200 | [diff] [blame] | 378 | 	nvs_aligned = kmemdup(nvs_ptr, nvs_len, GFP_KERNEL); | 
 | 379 | 	if (!nvs_aligned) | 
 | 380 | 		return -ENOMEM; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 381 |  | 
 | 382 | 	/* And finally we upload the NVS tables */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 383 | 	wl1271_write(wl, CMD_MBOX_ADDRESS, nvs_aligned, nvs_len, false); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 384 |  | 
 | 385 | 	kfree(nvs_aligned); | 
 | 386 | 	return 0; | 
 | 387 | } | 
 | 388 |  | 
 | 389 | static void wl1271_boot_enable_interrupts(struct wl1271 *wl) | 
 | 390 | { | 
| Teemu Paasikivi | 54f7e50 | 2010-02-22 08:38:22 +0200 | [diff] [blame] | 391 | 	wl1271_enable_interrupts(wl); | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 392 | 	wl1271_write32(wl, ACX_REG_INTERRUPT_MASK, | 
 | 393 | 		       WL1271_ACX_INTR_ALL & ~(WL1271_INTR_MASK)); | 
 | 394 | 	wl1271_write32(wl, HI_CFG, HI_CFG_DEF_VAL); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 395 | } | 
 | 396 |  | 
 | 397 | static int wl1271_boot_soft_reset(struct wl1271 *wl) | 
 | 398 | { | 
 | 399 | 	unsigned long timeout; | 
 | 400 | 	u32 boot_data; | 
 | 401 |  | 
 | 402 | 	/* perform soft reset */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 403 | 	wl1271_write32(wl, ACX_REG_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 404 |  | 
 | 405 | 	/* SOFT_RESET is self clearing */ | 
 | 406 | 	timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME); | 
 | 407 | 	while (1) { | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 408 | 		boot_data = wl1271_read32(wl, ACX_REG_SLV_SOFT_RESET); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 409 | 		wl1271_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data); | 
 | 410 | 		if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0) | 
 | 411 | 			break; | 
 | 412 |  | 
 | 413 | 		if (time_after(jiffies, timeout)) { | 
 | 414 | 			/* 1.2 check pWhalBus->uSelfClearTime if the | 
 | 415 | 			 * timeout was reached */ | 
 | 416 | 			wl1271_error("soft reset timeout"); | 
 | 417 | 			return -1; | 
 | 418 | 		} | 
 | 419 |  | 
 | 420 | 		udelay(SOFT_RESET_STALL_TIME); | 
 | 421 | 	} | 
 | 422 |  | 
 | 423 | 	/* disable Rx/Tx */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 424 | 	wl1271_write32(wl, ENABLE, 0x0); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 425 |  | 
 | 426 | 	/* disable auto calibration on start*/ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 427 | 	wl1271_write32(wl, SPARE_A2, 0xffff); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 428 |  | 
 | 429 | 	return 0; | 
 | 430 | } | 
 | 431 |  | 
 | 432 | static int wl1271_boot_run_firmware(struct wl1271 *wl) | 
 | 433 | { | 
 | 434 | 	int loop, ret; | 
| Luciano Coelho | 23a7a51 | 2010-04-28 09:50:02 +0300 | [diff] [blame] | 435 | 	u32 chip_id, intr; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 436 |  | 
 | 437 | 	wl1271_boot_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT); | 
 | 438 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 439 | 	chip_id = wl1271_read32(wl, CHIP_ID_B); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 440 |  | 
 | 441 | 	wl1271_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id); | 
 | 442 |  | 
 | 443 | 	if (chip_id != wl->chip.id) { | 
 | 444 | 		wl1271_error("chip id doesn't match after firmware boot"); | 
 | 445 | 		return -EIO; | 
 | 446 | 	} | 
 | 447 |  | 
 | 448 | 	/* wait for init to complete */ | 
 | 449 | 	loop = 0; | 
 | 450 | 	while (loop++ < INIT_LOOP) { | 
 | 451 | 		udelay(INIT_LOOP_DELAY); | 
| Luciano Coelho | 23a7a51 | 2010-04-28 09:50:02 +0300 | [diff] [blame] | 452 | 		intr = wl1271_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 453 |  | 
| Luciano Coelho | 23a7a51 | 2010-04-28 09:50:02 +0300 | [diff] [blame] | 454 | 		if (intr == 0xffffffff) { | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 455 | 			wl1271_error("error reading hardware complete " | 
 | 456 | 				     "init indication"); | 
 | 457 | 			return -EIO; | 
 | 458 | 		} | 
 | 459 | 		/* check that ACX_INTR_INIT_COMPLETE is enabled */ | 
| Luciano Coelho | 23a7a51 | 2010-04-28 09:50:02 +0300 | [diff] [blame] | 460 | 		else if (intr & WL1271_ACX_INTR_INIT_COMPLETE) { | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 461 | 			wl1271_write32(wl, ACX_REG_INTERRUPT_ACK, | 
 | 462 | 				       WL1271_ACX_INTR_INIT_COMPLETE); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 463 | 			break; | 
 | 464 | 		} | 
 | 465 | 	} | 
 | 466 |  | 
| Luciano Coelho | e7d17cf | 2009-10-29 13:20:04 +0200 | [diff] [blame] | 467 | 	if (loop > INIT_LOOP) { | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 468 | 		wl1271_error("timeout waiting for the hardware to " | 
 | 469 | 			     "complete initialization"); | 
 | 470 | 		return -EIO; | 
 | 471 | 	} | 
 | 472 |  | 
 | 473 | 	/* get hardware config command mail box */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 474 | 	wl->cmd_box_addr = wl1271_read32(wl, REG_COMMAND_MAILBOX_PTR); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 475 |  | 
 | 476 | 	/* get hardware config event mail box */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 477 | 	wl->event_box_addr = wl1271_read32(wl, REG_EVENT_MAILBOX_PTR); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 478 |  | 
 | 479 | 	/* set the working partition to its "running" mode offset */ | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 480 | 	wl1271_set_partition(wl, &part_table[PART_WORK]); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 481 |  | 
 | 482 | 	wl1271_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x event_box_addr 0x%x", | 
 | 483 | 		     wl->cmd_box_addr, wl->event_box_addr); | 
 | 484 |  | 
 | 485 | 	wl1271_boot_fw_version(wl); | 
 | 486 |  | 
 | 487 | 	/* | 
 | 488 | 	 * in case of full asynchronous mode the firmware event must be | 
 | 489 | 	 * ready to receive event from the command mailbox | 
 | 490 | 	 */ | 
 | 491 |  | 
| Juuso Oikarinen | be823e5 | 2009-10-08 21:56:36 +0300 | [diff] [blame] | 492 | 	/* unmask required mbox events  */ | 
 | 493 | 	wl->event_mask = BSS_LOSE_EVENT_ID | | 
| Juuso Oikarinen | 19ad071 | 2009-11-02 20:22:11 +0200 | [diff] [blame] | 494 | 		SCAN_COMPLETE_EVENT_ID | | 
| Luciano Coelho | 99d84c1 | 2010-03-26 12:53:20 +0200 | [diff] [blame] | 495 | 		PS_REPORT_EVENT_ID | | 
| Juuso Oikarinen | 00236aed | 2010-04-09 11:07:30 +0300 | [diff] [blame] | 496 | 		DISCONNECT_EVENT_COMPLETE_ID | | 
| Juuso Oikarinen | 90494a9 | 2010-07-08 17:50:00 +0300 | [diff] [blame] | 497 | 		RSSI_SNR_TRIGGER_0_EVENT_ID | | 
| Juuso Oikarinen | 8d2ef7b | 2010-07-08 17:50:03 +0300 | [diff] [blame] | 498 | 		PSPOLL_DELIVERY_FAILURE_EVENT_ID | | 
| Luciano Coelho | 6394c01 | 2011-05-10 14:28:27 +0300 | [diff] [blame] | 499 | 		SOFT_GEMINI_SENSE_EVENT_ID | | 
 | 500 | 		PERIODIC_SCAN_REPORT_EVENT_ID | | 
| Eliad Peller | c690ec8 | 2011-08-14 13:17:07 +0300 | [diff] [blame] | 501 | 		PERIODIC_SCAN_COMPLETE_EVENT_ID | | 
 | 502 | 		DUMMY_PACKET_EVENT_ID | | 
 | 503 | 		PEER_REMOVE_COMPLETE_EVENT_ID | | 
 | 504 | 		BA_SESSION_RX_CONSTRAINT_EVENT_ID | | 
 | 505 | 		REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID | | 
 | 506 | 		INACTIVE_STA_EVENT_ID | | 
| Shahar Levi | 6d158ff | 2011-09-08 13:01:33 +0300 | [diff] [blame] | 507 | 		MAX_TX_RETRY_EVENT_ID | | 
 | 508 | 		CHANNEL_SWITCH_COMPLETE_EVENT_ID; | 
| Arik Nemtsov | 203c903 | 2010-10-25 11:17:44 +0200 | [diff] [blame] | 509 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 510 | 	ret = wl1271_event_unmask(wl); | 
 | 511 | 	if (ret < 0) { | 
 | 512 | 		wl1271_error("EVENT mask setting failed"); | 
 | 513 | 		return ret; | 
 | 514 | 	} | 
 | 515 |  | 
 | 516 | 	wl1271_event_mbox_config(wl); | 
 | 517 |  | 
 | 518 | 	/* firmware startup completed */ | 
 | 519 | 	return 0; | 
 | 520 | } | 
 | 521 |  | 
 | 522 | static int wl1271_boot_write_irq_polarity(struct wl1271 *wl) | 
 | 523 | { | 
| Juuso Oikarinen | e8768ee | 2009-10-12 15:08:48 +0300 | [diff] [blame] | 524 | 	u32 polarity; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 525 |  | 
| Juuso Oikarinen | e8768ee | 2009-10-12 15:08:48 +0300 | [diff] [blame] | 526 | 	polarity = wl1271_top_reg_read(wl, OCP_REG_POLARITY); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 527 |  | 
 | 528 | 	/* We use HIGH polarity, so unset the LOW bit */ | 
 | 529 | 	polarity &= ~POLARITY_LOW; | 
| Juuso Oikarinen | e8768ee | 2009-10-12 15:08:48 +0300 | [diff] [blame] | 530 | 	wl1271_top_reg_write(wl, OCP_REG_POLARITY, polarity); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 531 |  | 
 | 532 | 	return 0; | 
 | 533 | } | 
 | 534 |  | 
| Juuso Oikarinen | d717fd6 | 2010-05-07 11:38:58 +0300 | [diff] [blame] | 535 | static void wl1271_boot_hw_version(struct wl1271 *wl) | 
 | 536 | { | 
 | 537 | 	u32 fuse; | 
 | 538 |  | 
| Gery Kahn | 6f07b72 | 2011-07-18 14:21:49 +0300 | [diff] [blame] | 539 | 	if (wl->chip.id == CHIP_ID_1283_PG20) | 
 | 540 | 		fuse = wl1271_top_reg_read(wl, WL128X_REG_FUSE_DATA_2_1); | 
 | 541 | 	else | 
 | 542 | 		fuse = wl1271_top_reg_read(wl, WL127X_REG_FUSE_DATA_2_1); | 
| Juuso Oikarinen | d717fd6 | 2010-05-07 11:38:58 +0300 | [diff] [blame] | 543 | 	fuse = (fuse & PG_VER_MASK) >> PG_VER_OFFSET; | 
 | 544 |  | 
 | 545 | 	wl->hw_pg_ver = (s8)fuse; | 
 | 546 | } | 
 | 547 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 548 | static int wl128x_switch_tcxo_to_fref(struct wl1271 *wl) | 
 | 549 | { | 
 | 550 | 	u16 spare_reg; | 
 | 551 |  | 
 | 552 | 	/* Mask bits [2] & [8:4] in the sys_clk_cfg register */ | 
 | 553 | 	spare_reg = wl1271_top_reg_read(wl, WL_SPARE_REG); | 
 | 554 | 	if (spare_reg == 0xFFFF) | 
 | 555 | 		return -EFAULT; | 
 | 556 | 	spare_reg |= (BIT(3) | BIT(5) | BIT(6)); | 
 | 557 | 	wl1271_top_reg_write(wl, WL_SPARE_REG, spare_reg); | 
 | 558 |  | 
 | 559 | 	/* Enable FREF_CLK_REQ & mux MCS and coex PLLs to FREF */ | 
 | 560 | 	wl1271_top_reg_write(wl, SYS_CLK_CFG_REG, | 
 | 561 | 			     WL_CLK_REQ_TYPE_PG2 | MCS_PLL_CLK_SEL_FREF); | 
 | 562 |  | 
 | 563 | 	/* Delay execution for 15msec, to let the HW settle */ | 
 | 564 | 	mdelay(15); | 
 | 565 |  | 
 | 566 | 	return 0; | 
 | 567 | } | 
 | 568 |  | 
 | 569 | static bool wl128x_is_tcxo_valid(struct wl1271 *wl) | 
 | 570 | { | 
 | 571 | 	u16 tcxo_detection; | 
 | 572 |  | 
 | 573 | 	tcxo_detection = wl1271_top_reg_read(wl, TCXO_CLK_DETECT_REG); | 
 | 574 | 	if (tcxo_detection & TCXO_DET_FAILED) | 
 | 575 | 		return false; | 
 | 576 |  | 
 | 577 | 	return true; | 
 | 578 | } | 
 | 579 |  | 
 | 580 | static bool wl128x_is_fref_valid(struct wl1271 *wl) | 
 | 581 | { | 
 | 582 | 	u16 fref_detection; | 
 | 583 |  | 
 | 584 | 	fref_detection = wl1271_top_reg_read(wl, FREF_CLK_DETECT_REG); | 
 | 585 | 	if (fref_detection & FREF_CLK_DETECT_FAIL) | 
 | 586 | 		return false; | 
 | 587 |  | 
 | 588 | 	return true; | 
 | 589 | } | 
 | 590 |  | 
 | 591 | static int wl128x_manually_configure_mcs_pll(struct wl1271 *wl) | 
 | 592 | { | 
 | 593 | 	wl1271_top_reg_write(wl, MCS_PLL_M_REG, MCS_PLL_M_REG_VAL); | 
 | 594 | 	wl1271_top_reg_write(wl, MCS_PLL_N_REG, MCS_PLL_N_REG_VAL); | 
 | 595 | 	wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, MCS_PLL_CONFIG_REG_VAL); | 
 | 596 |  | 
 | 597 | 	return 0; | 
 | 598 | } | 
 | 599 |  | 
 | 600 | static int wl128x_configure_mcs_pll(struct wl1271 *wl, int clk) | 
 | 601 | { | 
 | 602 | 	u16 spare_reg; | 
 | 603 | 	u16 pll_config; | 
 | 604 | 	u8 input_freq; | 
 | 605 |  | 
 | 606 | 	/* Mask bits [3:1] in the sys_clk_cfg register */ | 
 | 607 | 	spare_reg = wl1271_top_reg_read(wl, WL_SPARE_REG); | 
 | 608 | 	if (spare_reg == 0xFFFF) | 
 | 609 | 		return -EFAULT; | 
 | 610 | 	spare_reg |= BIT(2); | 
 | 611 | 	wl1271_top_reg_write(wl, WL_SPARE_REG, spare_reg); | 
 | 612 |  | 
 | 613 | 	/* Handle special cases of the TCXO clock */ | 
 | 614 | 	if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_8 || | 
 | 615 | 	    wl->tcxo_clock == WL12XX_TCXOCLOCK_33_6) | 
 | 616 | 		return wl128x_manually_configure_mcs_pll(wl); | 
 | 617 |  | 
 | 618 | 	/* Set the input frequency according to the selected clock source */ | 
 | 619 | 	input_freq = (clk & 1) + 1; | 
 | 620 |  | 
 | 621 | 	pll_config = wl1271_top_reg_read(wl, MCS_PLL_CONFIG_REG); | 
 | 622 | 	if (pll_config == 0xFFFF) | 
 | 623 | 		return -EFAULT; | 
 | 624 | 	pll_config |= (input_freq << MCS_SEL_IN_FREQ_SHIFT); | 
 | 625 | 	pll_config |= MCS_PLL_ENABLE_HP; | 
 | 626 | 	wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, pll_config); | 
 | 627 |  | 
 | 628 | 	return 0; | 
 | 629 | } | 
 | 630 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 631 | /* | 
 | 632 |  * WL128x has two clocks input - TCXO and FREF. | 
 | 633 |  * TCXO is the main clock of the device, while FREF is used to sync | 
 | 634 |  * between the GPS and the cellular modem. | 
 | 635 |  * In cases where TCXO is 32.736MHz or 16.368MHz, the FREF will be used | 
 | 636 |  * as the WLAN/BT main clock. | 
 | 637 |  */ | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 638 | static int wl128x_boot_clk(struct wl1271 *wl, int *selected_clock) | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 639 | { | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 640 | 	u16 sys_clk_cfg; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 641 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 642 | 	/* For XTAL-only modes, FREF will be used after switching from TCXO */ | 
 | 643 | 	if (wl->ref_clock == WL12XX_REFCLOCK_26_XTAL || | 
 | 644 | 	    wl->ref_clock == WL12XX_REFCLOCK_38_XTAL) { | 
 | 645 | 		if (!wl128x_switch_tcxo_to_fref(wl)) | 
 | 646 | 			return -EINVAL; | 
 | 647 | 		goto fref_clk; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 648 | 	} | 
 | 649 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 650 | 	/* Query the HW, to determine which clock source we should use */ | 
 | 651 | 	sys_clk_cfg = wl1271_top_reg_read(wl, SYS_CLK_CFG_REG); | 
 | 652 | 	if (sys_clk_cfg == 0xFFFF) | 
 | 653 | 		return -EINVAL; | 
 | 654 | 	if (sys_clk_cfg & PRCM_CM_EN_MUX_WLAN_FREF) | 
 | 655 | 		goto fref_clk; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 656 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 657 | 	/* If TCXO is either 32.736MHz or 16.368MHz, switch to FREF */ | 
 | 658 | 	if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_368 || | 
 | 659 | 	    wl->tcxo_clock == WL12XX_TCXOCLOCK_32_736) { | 
 | 660 | 		if (!wl128x_switch_tcxo_to_fref(wl)) | 
 | 661 | 			return -EINVAL; | 
 | 662 | 		goto fref_clk; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 663 | 	} | 
 | 664 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 665 | 	/* TCXO clock is selected */ | 
 | 666 | 	if (!wl128x_is_tcxo_valid(wl)) | 
 | 667 | 		return -EINVAL; | 
 | 668 | 	*selected_clock = wl->tcxo_clock; | 
 | 669 | 	goto config_mcs_pll; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 670 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 671 | fref_clk: | 
 | 672 | 	/* FREF clock is selected */ | 
 | 673 | 	if (!wl128x_is_fref_valid(wl)) | 
 | 674 | 		return -EINVAL; | 
 | 675 | 	*selected_clock = wl->ref_clock; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 676 |  | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 677 | config_mcs_pll: | 
 | 678 | 	return wl128x_configure_mcs_pll(wl, *selected_clock); | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 679 | } | 
 | 680 |  | 
 | 681 | static int wl127x_boot_clk(struct wl1271 *wl) | 
 | 682 | { | 
 | 683 | 	u32 pause; | 
 | 684 | 	u32 clk; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 685 |  | 
| Gery Kahn | 6f07b72 | 2011-07-18 14:21:49 +0300 | [diff] [blame] | 686 | 	if (((wl->hw_pg_ver & PG_MAJOR_VER_MASK) >> PG_MAJOR_VER_OFFSET) < 3) | 
 | 687 | 		wl->quirks |= WL12XX_QUIRK_END_OF_TRANSACTION; | 
| Juuso Oikarinen | d717fd6 | 2010-05-07 11:38:58 +0300 | [diff] [blame] | 688 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 689 | 	if (wl->ref_clock == CONF_REF_CLK_19_2_E || | 
 | 690 | 	    wl->ref_clock == CONF_REF_CLK_38_4_E || | 
 | 691 | 	    wl->ref_clock == CONF_REF_CLK_38_4_M_XTAL) | 
| Juuso Oikarinen | 284134e | 2009-10-12 15:08:49 +0300 | [diff] [blame] | 692 | 		/* ref clk: 19.2/38.4/38.4-XTAL */ | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 693 | 		clk = 0x3; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 694 | 	else if (wl->ref_clock == CONF_REF_CLK_26_E || | 
 | 695 | 		 wl->ref_clock == CONF_REF_CLK_52_E) | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 696 | 		/* ref clk: 26/52 */ | 
 | 697 | 		clk = 0x5; | 
| Ohad Ben-Cohen | 15cea99 | 2010-09-16 01:31:51 +0200 | [diff] [blame] | 698 | 	else | 
 | 699 | 		return -EINVAL; | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 700 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 701 | 	if (wl->ref_clock != CONF_REF_CLK_19_2_E) { | 
| Juuso Oikarinen | 284134e | 2009-10-12 15:08:49 +0300 | [diff] [blame] | 702 | 		u16 val; | 
| Juuso Oikarinen | 9d4e5bb | 2010-03-26 12:53:15 +0200 | [diff] [blame] | 703 | 		/* Set clock type (open drain) */ | 
| Juuso Oikarinen | 284134e | 2009-10-12 15:08:49 +0300 | [diff] [blame] | 704 | 		val = wl1271_top_reg_read(wl, OCP_REG_CLK_TYPE); | 
 | 705 | 		val &= FREF_CLK_TYPE_BITS; | 
| Juuso Oikarinen | 284134e | 2009-10-12 15:08:49 +0300 | [diff] [blame] | 706 | 		wl1271_top_reg_write(wl, OCP_REG_CLK_TYPE, val); | 
| Juuso Oikarinen | 9d4e5bb | 2010-03-26 12:53:15 +0200 | [diff] [blame] | 707 |  | 
 | 708 | 		/* Set clock pull mode (no pull) */ | 
 | 709 | 		val = wl1271_top_reg_read(wl, OCP_REG_CLK_PULL); | 
 | 710 | 		val |= NO_PULL; | 
 | 711 | 		wl1271_top_reg_write(wl, OCP_REG_CLK_PULL, val); | 
| Juuso Oikarinen | 284134e | 2009-10-12 15:08:49 +0300 | [diff] [blame] | 712 | 	} else { | 
 | 713 | 		u16 val; | 
 | 714 | 		/* Set clock polarity */ | 
 | 715 | 		val = wl1271_top_reg_read(wl, OCP_REG_CLK_POLARITY); | 
 | 716 | 		val &= FREF_CLK_POLARITY_BITS; | 
 | 717 | 		val |= CLK_REQ_OUTN_SEL; | 
 | 718 | 		wl1271_top_reg_write(wl, OCP_REG_CLK_POLARITY, val); | 
 | 719 | 	} | 
 | 720 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 721 | 	wl1271_write32(wl, PLL_PARAMETERS, clk); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 722 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 723 | 	pause = wl1271_read32(wl, PLL_PARAMETERS); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 724 |  | 
 | 725 | 	wl1271_debug(DEBUG_BOOT, "pause1 0x%x", pause); | 
 | 726 |  | 
| Juuso Oikarinen | 2f63b01 | 2010-08-10 06:38:35 +0200 | [diff] [blame] | 727 | 	pause &= ~(WU_COUNTER_PAUSE_VAL); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 728 | 	pause |= WU_COUNTER_PAUSE_VAL; | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 729 | 	wl1271_write32(wl, WU_COUNTER_PAUSE, pause); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 730 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 731 | 	return 0; | 
 | 732 | } | 
 | 733 |  | 
 | 734 | /* uploads NVS and firmware */ | 
 | 735 | int wl1271_load_firmware(struct wl1271 *wl) | 
 | 736 | { | 
 | 737 | 	int ret = 0; | 
 | 738 | 	u32 tmp, clk; | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 739 | 	int selected_clock = -1; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 740 |  | 
| Gery Kahn | 6f07b72 | 2011-07-18 14:21:49 +0300 | [diff] [blame] | 741 | 	wl1271_boot_hw_version(wl); | 
 | 742 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 743 | 	if (wl->chip.id == CHIP_ID_1283_PG20) { | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 744 | 		ret = wl128x_boot_clk(wl, &selected_clock); | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 745 | 		if (ret < 0) | 
 | 746 | 			goto out; | 
 | 747 | 	} else { | 
 | 748 | 		ret = wl127x_boot_clk(wl); | 
 | 749 | 		if (ret < 0) | 
 | 750 | 			goto out; | 
 | 751 | 	} | 
 | 752 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 753 | 	/* Continue the ELP wake up sequence */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 754 | 	wl1271_write32(wl, WELP_ARM_COMMAND, WELP_ARM_COMMAND_VAL); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 755 | 	udelay(500); | 
 | 756 |  | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 757 | 	wl1271_set_partition(wl, &part_table[PART_DRPW]); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 758 |  | 
 | 759 | 	/* Read-modify-write DRPW_SCRATCH_START register (see next state) | 
 | 760 | 	   to be used by DRPw FW. The RTRIM value will be added by the FW | 
 | 761 | 	   before taking DRPw out of reset */ | 
 | 762 |  | 
 | 763 | 	wl1271_debug(DEBUG_BOOT, "DRPW_SCRATCH_START %08x", DRPW_SCRATCH_START); | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 764 | 	clk = wl1271_read32(wl, DRPW_SCRATCH_START); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 765 |  | 
 | 766 | 	wl1271_debug(DEBUG_BOOT, "clk2 0x%x", clk); | 
 | 767 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 768 | 	if (wl->chip.id == CHIP_ID_1283_PG20) { | 
| Ido Yariv | d29633b | 2011-03-31 10:06:57 +0200 | [diff] [blame] | 769 | 		clk |= ((selected_clock & 0x3) << 1) << 4; | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 770 | 	} else { | 
 | 771 | 		clk |= (wl->ref_clock << 1) << 4; | 
 | 772 | 	} | 
 | 773 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 774 | 	wl1271_write32(wl, DRPW_SCRATCH_START, clk); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 775 |  | 
| Juuso Oikarinen | 451de97 | 2009-10-12 15:08:46 +0300 | [diff] [blame] | 776 | 	wl1271_set_partition(wl, &part_table[PART_WORK]); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 777 |  | 
 | 778 | 	/* Disable interrupts */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 779 | 	wl1271_write32(wl, ACX_REG_INTERRUPT_MASK, WL1271_ACX_INTR_ALL); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 780 |  | 
 | 781 | 	ret = wl1271_boot_soft_reset(wl); | 
 | 782 | 	if (ret < 0) | 
 | 783 | 		goto out; | 
 | 784 |  | 
 | 785 | 	/* 2. start processing NVS file */ | 
 | 786 | 	ret = wl1271_boot_upload_nvs(wl); | 
 | 787 | 	if (ret < 0) | 
 | 788 | 		goto out; | 
 | 789 |  | 
 | 790 | 	/* write firmware's last address (ie. it's length) to | 
 | 791 | 	 * ACX_EEPROMLESS_IND_REG */ | 
 | 792 | 	wl1271_debug(DEBUG_BOOT, "ACX_EEPROMLESS_IND_REG"); | 
 | 793 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 794 | 	wl1271_write32(wl, ACX_EEPROMLESS_IND_REG, ACX_EEPROMLESS_IND_REG); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 795 |  | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 796 | 	tmp = wl1271_read32(wl, CHIP_ID_B); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 797 |  | 
 | 798 | 	wl1271_debug(DEBUG_BOOT, "chip id 0x%x", tmp); | 
 | 799 |  | 
 | 800 | 	/* 6. read the EEPROM parameters */ | 
| Teemu Paasikivi | 7b048c5 | 2010-02-18 13:25:55 +0200 | [diff] [blame] | 801 | 	tmp = wl1271_read32(wl, SCR_PAD2); | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 802 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 803 | 	/* WL1271: The reference driver skips steps 7 to 10 (jumps directly | 
 | 804 | 	 * to upload_fw) */ | 
 | 805 |  | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 806 | 	if (wl->chip.id == CHIP_ID_1283_PG20) | 
| Luciano Coelho | afb7d3c | 2011-04-01 20:48:02 +0300 | [diff] [blame] | 807 | 		wl1271_top_reg_write(wl, SDIO_IO_DS, wl->conf.hci_io_ds); | 
| Shahar Levi | 5ea417a | 2011-03-06 16:32:11 +0200 | [diff] [blame] | 808 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 809 | 	ret = wl1271_boot_upload_firmware(wl); | 
 | 810 | 	if (ret < 0) | 
 | 811 | 		goto out; | 
 | 812 |  | 
| Roger Quadros | 870c367 | 2010-11-29 16:24:57 +0200 | [diff] [blame] | 813 | out: | 
 | 814 | 	return ret; | 
 | 815 | } | 
 | 816 | EXPORT_SYMBOL_GPL(wl1271_load_firmware); | 
 | 817 |  | 
 | 818 | int wl1271_boot(struct wl1271 *wl) | 
 | 819 | { | 
 | 820 | 	int ret; | 
 | 821 |  | 
 | 822 | 	/* upload NVS and firmware */ | 
 | 823 | 	ret = wl1271_load_firmware(wl); | 
 | 824 | 	if (ret) | 
 | 825 | 		return ret; | 
 | 826 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 827 | 	/* 10.5 start firmware */ | 
 | 828 | 	ret = wl1271_boot_run_firmware(wl); | 
 | 829 | 	if (ret < 0) | 
 | 830 | 		goto out; | 
 | 831 |  | 
| Shahar Levi | b9b0fde | 2011-03-06 16:32:06 +0200 | [diff] [blame] | 832 | 	ret = wl1271_boot_write_irq_polarity(wl); | 
 | 833 | 	if (ret < 0) | 
 | 834 | 		goto out; | 
 | 835 |  | 
 | 836 | 	wl1271_write32(wl, ACX_REG_INTERRUPT_MASK, | 
 | 837 | 		       WL1271_ACX_ALL_EVENTS_VECTOR); | 
 | 838 |  | 
| Juuso Oikarinen | eb5b28d | 2009-10-13 12:47:45 +0300 | [diff] [blame] | 839 | 	/* Enable firmware interrupts now */ | 
 | 840 | 	wl1271_boot_enable_interrupts(wl); | 
 | 841 |  | 
| Luciano Coelho | f5fc0f8 | 2009-08-06 16:25:28 +0300 | [diff] [blame] | 842 | 	wl1271_event_mbox_config(wl); | 
 | 843 |  | 
 | 844 | out: | 
 | 845 | 	return ret; | 
 | 846 | } |