diff options
Diffstat (limited to 'drivers/misc/iwmc3200top/fw-download.c')
-rw-r--r-- | drivers/misc/iwmc3200top/fw-download.c | 358 |
1 files changed, 0 insertions, 358 deletions
diff --git a/drivers/misc/iwmc3200top/fw-download.c b/drivers/misc/iwmc3200top/fw-download.c deleted file mode 100644 index e27afde6e99f..000000000000 --- a/drivers/misc/iwmc3200top/fw-download.c +++ /dev/null | |||
@@ -1,358 +0,0 @@ | |||
1 | /* | ||
2 | * iwmc3200top - Intel Wireless MultiCom 3200 Top Driver | ||
3 | * drivers/misc/iwmc3200top/fw-download.c | ||
4 | * | ||
5 | * Copyright (C) 2009 Intel Corporation. All rights reserved. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License version | ||
9 | * 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
19 | * 02110-1301, USA. | ||
20 | * | ||
21 | * | ||
22 | * Author Name: Maxim Grabarnik <maxim.grabarnink@intel.com> | ||
23 | * - | ||
24 | * | ||
25 | */ | ||
26 | |||
27 | #include <linux/firmware.h> | ||
28 | #include <linux/mmc/sdio_func.h> | ||
29 | #include <linux/slab.h> | ||
30 | #include <asm/unaligned.h> | ||
31 | |||
32 | #include "iwmc3200top.h" | ||
33 | #include "log.h" | ||
34 | #include "fw-msg.h" | ||
35 | |||
36 | #define CHECKSUM_BYTES_NUM sizeof(u32) | ||
37 | |||
38 | /** | ||
39 | init parser struct with file | ||
40 | */ | ||
41 | static int iwmct_fw_parser_init(struct iwmct_priv *priv, const u8 *file, | ||
42 | size_t file_size, size_t block_size) | ||
43 | { | ||
44 | struct iwmct_parser *parser = &priv->parser; | ||
45 | struct iwmct_fw_hdr *fw_hdr = &parser->versions; | ||
46 | |||
47 | LOG_TRACE(priv, FW_DOWNLOAD, "-->\n"); | ||
48 | |||
49 | LOG_INFO(priv, FW_DOWNLOAD, "file_size=%zd\n", file_size); | ||
50 | |||
51 | parser->file = file; | ||
52 | parser->file_size = file_size; | ||
53 | parser->cur_pos = 0; | ||
54 | parser->entry_point = 0; | ||
55 | parser->buf = kzalloc(block_size, GFP_KERNEL); | ||
56 | if (!parser->buf) { | ||
57 | LOG_ERROR(priv, FW_DOWNLOAD, "kzalloc error\n"); | ||
58 | return -ENOMEM; | ||
59 | } | ||
60 | parser->buf_size = block_size; | ||
61 | |||
62 | /* extract fw versions */ | ||
63 | memcpy(fw_hdr, parser->file, sizeof(struct iwmct_fw_hdr)); | ||
64 | LOG_INFO(priv, FW_DOWNLOAD, "fw versions are:\n" | ||
65 | "top %u.%u.%u gps %u.%u.%u bt %u.%u.%u tic %s\n", | ||
66 | fw_hdr->top_major, fw_hdr->top_minor, fw_hdr->top_revision, | ||
67 | fw_hdr->gps_major, fw_hdr->gps_minor, fw_hdr->gps_revision, | ||
68 | fw_hdr->bt_major, fw_hdr->bt_minor, fw_hdr->bt_revision, | ||
69 | fw_hdr->tic_name); | ||
70 | |||
71 | parser->cur_pos += sizeof(struct iwmct_fw_hdr); | ||
72 | |||
73 | LOG_TRACE(priv, FW_DOWNLOAD, "<--\n"); | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | static bool iwmct_checksum(struct iwmct_priv *priv) | ||
78 | { | ||
79 | struct iwmct_parser *parser = &priv->parser; | ||
80 | __le32 *file = (__le32 *)parser->file; | ||
81 | int i, pad, steps; | ||
82 | u32 accum = 0; | ||
83 | u32 checksum; | ||
84 | u32 mask = 0xffffffff; | ||
85 | |||
86 | pad = (parser->file_size - CHECKSUM_BYTES_NUM) % 4; | ||
87 | steps = (parser->file_size - CHECKSUM_BYTES_NUM) / 4; | ||
88 | |||
89 | LOG_INFO(priv, FW_DOWNLOAD, "pad=%d steps=%d\n", pad, steps); | ||
90 | |||
91 | for (i = 0; i < steps; i++) | ||
92 | accum += le32_to_cpu(file[i]); | ||
93 | |||
94 | if (pad) { | ||
95 | mask <<= 8 * (4 - pad); | ||
96 | accum += le32_to_cpu(file[steps]) & mask; | ||
97 | } | ||
98 | |||
99 | checksum = get_unaligned_le32((__le32 *)(parser->file + | ||
100 | parser->file_size - CHECKSUM_BYTES_NUM)); | ||
101 | |||
102 | LOG_INFO(priv, FW_DOWNLOAD, | ||
103 | "compare checksum accum=0x%x to checksum=0x%x\n", | ||
104 | accum, checksum); | ||
105 | |||
106 | return checksum == accum; | ||
107 | } | ||
108 | |||
109 | static int iwmct_parse_next_section(struct iwmct_priv *priv, const u8 **p_sec, | ||
110 | size_t *sec_size, __le32 *sec_addr) | ||
111 | { | ||
112 | struct iwmct_parser *parser = &priv->parser; | ||
113 | struct iwmct_dbg *dbg = &priv->dbg; | ||
114 | struct iwmct_fw_sec_hdr *sec_hdr; | ||
115 | |||
116 | LOG_TRACE(priv, FW_DOWNLOAD, "-->\n"); | ||
117 | |||
118 | while (parser->cur_pos + sizeof(struct iwmct_fw_sec_hdr) | ||
119 | <= parser->file_size) { | ||
120 | |||
121 | sec_hdr = (struct iwmct_fw_sec_hdr *) | ||
122 | (parser->file + parser->cur_pos); | ||
123 | parser->cur_pos += sizeof(struct iwmct_fw_sec_hdr); | ||
124 | |||
125 | LOG_INFO(priv, FW_DOWNLOAD, | ||
126 | "sec hdr: type=%s addr=0x%x size=%d\n", | ||
127 | sec_hdr->type, sec_hdr->target_addr, | ||
128 | sec_hdr->data_size); | ||
129 | |||
130 | if (strcmp(sec_hdr->type, "ENT") == 0) | ||
131 | parser->entry_point = le32_to_cpu(sec_hdr->target_addr); | ||
132 | else if (strcmp(sec_hdr->type, "LBL") == 0) | ||
133 | strcpy(dbg->label_fw, parser->file + parser->cur_pos); | ||
134 | else if (((strcmp(sec_hdr->type, "TOP") == 0) && | ||
135 | (priv->barker & BARKER_DNLOAD_TOP_MSK)) || | ||
136 | ((strcmp(sec_hdr->type, "GPS") == 0) && | ||
137 | (priv->barker & BARKER_DNLOAD_GPS_MSK)) || | ||
138 | ((strcmp(sec_hdr->type, "BTH") == 0) && | ||
139 | (priv->barker & BARKER_DNLOAD_BT_MSK))) { | ||
140 | *sec_addr = sec_hdr->target_addr; | ||
141 | *sec_size = le32_to_cpu(sec_hdr->data_size); | ||
142 | *p_sec = parser->file + parser->cur_pos; | ||
143 | parser->cur_pos += le32_to_cpu(sec_hdr->data_size); | ||
144 | return 1; | ||
145 | } else if (strcmp(sec_hdr->type, "LOG") != 0) | ||
146 | LOG_WARNING(priv, FW_DOWNLOAD, | ||
147 | "skipping section type %s\n", | ||
148 | sec_hdr->type); | ||
149 | |||
150 | parser->cur_pos += le32_to_cpu(sec_hdr->data_size); | ||
151 | LOG_INFO(priv, FW_DOWNLOAD, | ||
152 | "finished with section cur_pos=%zd\n", parser->cur_pos); | ||
153 | } | ||
154 | |||
155 | LOG_TRACE(priv, INIT, "<--\n"); | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static int iwmct_download_section(struct iwmct_priv *priv, const u8 *p_sec, | ||
160 | size_t sec_size, __le32 addr) | ||
161 | { | ||
162 | struct iwmct_parser *parser = &priv->parser; | ||
163 | struct iwmct_fw_load_hdr *hdr = (struct iwmct_fw_load_hdr *)parser->buf; | ||
164 | const u8 *cur_block = p_sec; | ||
165 | size_t sent = 0; | ||
166 | int cnt = 0; | ||
167 | int ret = 0; | ||
168 | u32 cmd = 0; | ||
169 | |||
170 | LOG_TRACE(priv, FW_DOWNLOAD, "-->\n"); | ||
171 | LOG_INFO(priv, FW_DOWNLOAD, "Download address 0x%x size 0x%zx\n", | ||
172 | addr, sec_size); | ||
173 | |||
174 | while (sent < sec_size) { | ||
175 | int i; | ||
176 | u32 chksm = 0; | ||
177 | u32 reset = atomic_read(&priv->reset); | ||
178 | /* actual FW data */ | ||
179 | u32 data_size = min(parser->buf_size - sizeof(*hdr), | ||
180 | sec_size - sent); | ||
181 | /* Pad to block size */ | ||
182 | u32 trans_size = (data_size + sizeof(*hdr) + | ||
183 | IWMC_SDIO_BLK_SIZE - 1) & | ||
184 | ~(IWMC_SDIO_BLK_SIZE - 1); | ||
185 | ++cnt; | ||
186 | |||
187 | /* in case of reset, interrupt FW DOWNLAOD */ | ||
188 | if (reset) { | ||
189 | LOG_INFO(priv, FW_DOWNLOAD, | ||
190 | "Reset detected. Abort FW download!!!"); | ||
191 | ret = -ECANCELED; | ||
192 | goto exit; | ||
193 | } | ||
194 | |||
195 | memset(parser->buf, 0, parser->buf_size); | ||
196 | cmd |= IWMC_OPCODE_WRITE << CMD_HDR_OPCODE_POS; | ||
197 | cmd |= IWMC_CMD_SIGNATURE << CMD_HDR_SIGNATURE_POS; | ||
198 | cmd |= (priv->dbg.direct ? 1 : 0) << CMD_HDR_DIRECT_ACCESS_POS; | ||
199 | cmd |= (priv->dbg.checksum ? 1 : 0) << CMD_HDR_USE_CHECKSUM_POS; | ||
200 | hdr->data_size = cpu_to_le32(data_size); | ||
201 | hdr->target_addr = addr; | ||
202 | |||
203 | /* checksum is allowed for sizes divisible by 4 */ | ||
204 | if (data_size & 0x3) | ||
205 | cmd &= ~CMD_HDR_USE_CHECKSUM_MSK; | ||
206 | |||
207 | memcpy(hdr->data, cur_block, data_size); | ||
208 | |||
209 | |||
210 | if (cmd & CMD_HDR_USE_CHECKSUM_MSK) { | ||
211 | |||
212 | chksm = data_size + le32_to_cpu(addr) + cmd; | ||
213 | for (i = 0; i < data_size >> 2; i++) | ||
214 | chksm += ((u32 *)cur_block)[i]; | ||
215 | |||
216 | hdr->block_chksm = cpu_to_le32(chksm); | ||
217 | LOG_INFO(priv, FW_DOWNLOAD, "Checksum = 0x%X\n", | ||
218 | hdr->block_chksm); | ||
219 | } | ||
220 | |||
221 | LOG_INFO(priv, FW_DOWNLOAD, "trans#%d, len=%d, sent=%zd, " | ||
222 | "sec_size=%zd, startAddress 0x%X\n", | ||
223 | cnt, trans_size, sent, sec_size, addr); | ||
224 | |||
225 | if (priv->dbg.dump) | ||
226 | LOG_HEXDUMP(FW_DOWNLOAD, parser->buf, trans_size); | ||
227 | |||
228 | |||
229 | hdr->cmd = cpu_to_le32(cmd); | ||
230 | /* send it down */ | ||
231 | /* TODO: add more proper sending and error checking */ | ||
232 | ret = iwmct_tx(priv, parser->buf, trans_size); | ||
233 | if (ret != 0) { | ||
234 | LOG_INFO(priv, FW_DOWNLOAD, | ||
235 | "iwmct_tx returned %d\n", ret); | ||
236 | goto exit; | ||
237 | } | ||
238 | |||
239 | addr = cpu_to_le32(le32_to_cpu(addr) + data_size); | ||
240 | sent += data_size; | ||
241 | cur_block = p_sec + sent; | ||
242 | |||
243 | if (priv->dbg.blocks && (cnt + 1) >= priv->dbg.blocks) { | ||
244 | LOG_INFO(priv, FW_DOWNLOAD, | ||
245 | "Block number limit is reached [%d]\n", | ||
246 | priv->dbg.blocks); | ||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | |||
251 | if (sent < sec_size) | ||
252 | ret = -EINVAL; | ||
253 | exit: | ||
254 | LOG_TRACE(priv, FW_DOWNLOAD, "<--\n"); | ||
255 | return ret; | ||
256 | } | ||
257 | |||
258 | static int iwmct_kick_fw(struct iwmct_priv *priv, bool jump) | ||
259 | { | ||
260 | struct iwmct_parser *parser = &priv->parser; | ||
261 | struct iwmct_fw_load_hdr *hdr = (struct iwmct_fw_load_hdr *)parser->buf; | ||
262 | int ret; | ||
263 | u32 cmd; | ||
264 | |||
265 | LOG_TRACE(priv, FW_DOWNLOAD, "-->\n"); | ||
266 | |||
267 | memset(parser->buf, 0, parser->buf_size); | ||
268 | cmd = IWMC_CMD_SIGNATURE << CMD_HDR_SIGNATURE_POS; | ||
269 | if (jump) { | ||
270 | cmd |= IWMC_OPCODE_JUMP << CMD_HDR_OPCODE_POS; | ||
271 | hdr->target_addr = cpu_to_le32(parser->entry_point); | ||
272 | LOG_INFO(priv, FW_DOWNLOAD, "jump address 0x%x\n", | ||
273 | parser->entry_point); | ||
274 | } else { | ||
275 | cmd |= IWMC_OPCODE_LAST_COMMAND << CMD_HDR_OPCODE_POS; | ||
276 | LOG_INFO(priv, FW_DOWNLOAD, "last command\n"); | ||
277 | } | ||
278 | |||
279 | hdr->cmd = cpu_to_le32(cmd); | ||
280 | |||
281 | LOG_HEXDUMP(FW_DOWNLOAD, parser->buf, sizeof(*hdr)); | ||
282 | /* send it down */ | ||
283 | /* TODO: add more proper sending and error checking */ | ||
284 | ret = iwmct_tx(priv, parser->buf, IWMC_SDIO_BLK_SIZE); | ||
285 | if (ret) | ||
286 | LOG_INFO(priv, FW_DOWNLOAD, "iwmct_tx returned %d", ret); | ||
287 | |||
288 | LOG_TRACE(priv, FW_DOWNLOAD, "<--\n"); | ||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | int iwmct_fw_load(struct iwmct_priv *priv) | ||
293 | { | ||
294 | const u8 *fw_name = FW_NAME(FW_API_VER); | ||
295 | const struct firmware *raw; | ||
296 | const u8 *pdata; | ||
297 | size_t len; | ||
298 | __le32 addr; | ||
299 | int ret; | ||
300 | |||
301 | |||
302 | LOG_INFO(priv, FW_DOWNLOAD, "barker download request 0x%x is:\n", | ||
303 | priv->barker); | ||
304 | LOG_INFO(priv, FW_DOWNLOAD, "******* Top FW %s requested ********\n", | ||
305 | (priv->barker & BARKER_DNLOAD_TOP_MSK) ? "was" : "not"); | ||
306 | LOG_INFO(priv, FW_DOWNLOAD, "******* GPS FW %s requested ********\n", | ||
307 | (priv->barker & BARKER_DNLOAD_GPS_MSK) ? "was" : "not"); | ||
308 | LOG_INFO(priv, FW_DOWNLOAD, "******* BT FW %s requested ********\n", | ||
309 | (priv->barker & BARKER_DNLOAD_BT_MSK) ? "was" : "not"); | ||
310 | |||
311 | |||
312 | /* get the firmware */ | ||
313 | ret = request_firmware(&raw, fw_name, &priv->func->dev); | ||
314 | if (ret < 0) { | ||
315 | LOG_ERROR(priv, FW_DOWNLOAD, "%s request_firmware failed %d\n", | ||
316 | fw_name, ret); | ||
317 | goto exit; | ||
318 | } | ||
319 | |||
320 | if (raw->size < sizeof(struct iwmct_fw_sec_hdr)) { | ||
321 | LOG_ERROR(priv, FW_DOWNLOAD, "%s smaller then (%zd) (%zd)\n", | ||
322 | fw_name, sizeof(struct iwmct_fw_sec_hdr), raw->size); | ||
323 | goto exit; | ||
324 | } | ||
325 | |||
326 | LOG_INFO(priv, FW_DOWNLOAD, "Read firmware '%s'\n", fw_name); | ||
327 | |||
328 | /* clear parser struct */ | ||
329 | ret = iwmct_fw_parser_init(priv, raw->data, raw->size, priv->trans_len); | ||
330 | if (ret < 0) { | ||
331 | LOG_ERROR(priv, FW_DOWNLOAD, | ||
332 | "iwmct_parser_init failed: Reason %d\n", ret); | ||
333 | goto exit; | ||
334 | } | ||
335 | |||
336 | if (!iwmct_checksum(priv)) { | ||
337 | LOG_ERROR(priv, FW_DOWNLOAD, "checksum error\n"); | ||
338 | ret = -EINVAL; | ||
339 | goto exit; | ||
340 | } | ||
341 | |||
342 | /* download firmware to device */ | ||
343 | while (iwmct_parse_next_section(priv, &pdata, &len, &addr)) { | ||
344 | ret = iwmct_download_section(priv, pdata, len, addr); | ||
345 | if (ret) { | ||
346 | LOG_ERROR(priv, FW_DOWNLOAD, | ||
347 | "%s download section failed\n", fw_name); | ||
348 | goto exit; | ||
349 | } | ||
350 | } | ||
351 | |||
352 | ret = iwmct_kick_fw(priv, !!(priv->barker & BARKER_DNLOAD_JUMP_MSK)); | ||
353 | |||
354 | exit: | ||
355 | kfree(priv->parser.buf); | ||
356 | release_firmware(raw); | ||
357 | return ret; | ||
358 | } | ||