Commit | Line | Data |
---|---|---|
3194c687 VC |
1 | /* |
2 | * Marvell NFC driver: Firmware downloader | |
3 | * | |
4 | * Copyright (C) 2015, Marvell International Ltd. | |
5 | * | |
6 | * This software file (the "File") is distributed by Marvell International | |
7 | * Ltd. under the terms of the GNU General Public License Version 2, June 1991 | |
8 | * (the "License"). You may use, redistribute and/or modify this File in | |
9 | * accordance with the terms and conditions of the License, a copy of which | |
10 | * is available on the worldwide web at | |
11 | * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | |
12 | * | |
13 | * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE | |
14 | * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE | |
15 | * ARE EXPRESSLY DISCLAIMED. The License provides additional details about | |
16 | * this warranty disclaimer. | |
17 | */ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/unaligned/access_ok.h> | |
21 | #include <linux/firmware.h> | |
22 | #include <linux/nfc.h> | |
23 | #include <net/nfc/nci.h> | |
24 | #include <net/nfc/nci_core.h> | |
25 | #include "nfcmrvl.h" | |
26 | ||
27 | #define FW_DNLD_TIMEOUT 15000 | |
28 | ||
29 | #define NCI_OP_PROPRIETARY_BOOT_CMD nci_opcode_pack(NCI_GID_PROPRIETARY, \ | |
30 | NCI_OP_PROP_BOOT_CMD) | |
31 | ||
32 | /* FW download states */ | |
33 | ||
34 | enum { | |
35 | STATE_RESET = 0, | |
36 | STATE_INIT, | |
37 | STATE_SET_REF_CLOCK, | |
38 | STATE_SET_HI_CONFIG, | |
39 | STATE_OPEN_LC, | |
40 | STATE_FW_DNLD, | |
41 | STATE_CLOSE_LC, | |
42 | STATE_BOOT | |
43 | }; | |
44 | ||
45 | enum { | |
46 | SUBSTATE_WAIT_COMMAND = 0, | |
47 | SUBSTATE_WAIT_ACK_CREDIT, | |
48 | SUBSTATE_WAIT_NACK_CREDIT, | |
49 | SUBSTATE_WAIT_DATA_CREDIT, | |
50 | }; | |
51 | ||
52 | /* | |
53 | ** Patterns for responses | |
54 | */ | |
55 | ||
56 | static const uint8_t nci_pattern_core_reset_ntf[] = { | |
57 | 0x60, 0x00, 0x02, 0xA0, 0x01 | |
58 | }; | |
59 | ||
60 | static const uint8_t nci_pattern_core_init_rsp[] = { | |
61 | 0x40, 0x01, 0x11 | |
62 | }; | |
63 | ||
64 | static const uint8_t nci_pattern_core_set_config_rsp[] = { | |
65 | 0x40, 0x02, 0x02, 0x00, 0x00 | |
66 | }; | |
67 | ||
68 | static const uint8_t nci_pattern_core_conn_create_rsp[] = { | |
69 | 0x40, 0x04, 0x04, 0x00 | |
70 | }; | |
71 | ||
72 | static const uint8_t nci_pattern_core_conn_close_rsp[] = { | |
73 | 0x40, 0x05, 0x01, 0x00 | |
74 | }; | |
75 | ||
76 | static const uint8_t nci_pattern_core_conn_credits_ntf[] = { | |
77 | 0x60, 0x06, 0x03, 0x01, NCI_CORE_LC_CONNID_PROP_FW_DL, 0x01 | |
78 | }; | |
79 | ||
80 | static const uint8_t nci_pattern_proprietary_boot_rsp[] = { | |
81 | 0x4F, 0x3A, 0x01, 0x00 | |
82 | }; | |
83 | ||
84 | static struct sk_buff *alloc_lc_skb(struct nfcmrvl_private *priv, uint8_t plen) | |
85 | { | |
86 | struct sk_buff *skb; | |
87 | struct nci_data_hdr *hdr; | |
88 | ||
89 | skb = nci_skb_alloc(priv->ndev, (NCI_DATA_HDR_SIZE + plen), GFP_KERNEL); | |
90 | if (!skb) { | |
91 | pr_err("no memory for data\n"); | |
92 | return NULL; | |
93 | } | |
94 | ||
95 | hdr = (struct nci_data_hdr *) skb_put(skb, NCI_DATA_HDR_SIZE); | |
96 | hdr->conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; | |
97 | hdr->rfu = 0; | |
98 | hdr->plen = plen; | |
99 | ||
100 | nci_mt_set((__u8 *)hdr, NCI_MT_DATA_PKT); | |
101 | nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST); | |
102 | ||
103 | return skb; | |
104 | } | |
105 | ||
106 | static void fw_dnld_over(struct nfcmrvl_private *priv, u32 error) | |
107 | { | |
108 | if (priv->fw_dnld.fw) { | |
109 | release_firmware(priv->fw_dnld.fw); | |
110 | priv->fw_dnld.fw = NULL; | |
111 | priv->fw_dnld.header = NULL; | |
112 | priv->fw_dnld.binary_config = NULL; | |
113 | } | |
114 | ||
115 | atomic_set(&priv->ndev->cmd_cnt, 0); | |
3194c687 | 116 | |
82aff3ea VC |
117 | if (timer_pending(&priv->ndev->cmd_timer)) |
118 | del_timer_sync(&priv->ndev->cmd_timer); | |
119 | ||
120 | if (timer_pending(&priv->fw_dnld.timer)) | |
121 | del_timer_sync(&priv->fw_dnld.timer); | |
3194c687 VC |
122 | |
123 | nfc_info(priv->dev, "FW loading over (%d)]\n", error); | |
124 | ||
125 | if (error != 0) { | |
126 | /* failed, halt the chip to avoid power consumption */ | |
127 | nfcmrvl_chip_halt(priv); | |
128 | } | |
129 | ||
130 | nfc_fw_download_done(priv->ndev->nfc_dev, priv->fw_dnld.name, error); | |
131 | } | |
132 | ||
133 | static void fw_dnld_timeout(unsigned long arg) | |
134 | { | |
135 | struct nfcmrvl_private *priv = (struct nfcmrvl_private *) arg; | |
136 | ||
137 | nfc_err(priv->dev, "FW loading timeout"); | |
138 | priv->fw_dnld.state = STATE_RESET; | |
139 | fw_dnld_over(priv, -ETIMEDOUT); | |
140 | } | |
141 | ||
142 | static int process_state_reset(struct nfcmrvl_private *priv, | |
143 | struct sk_buff *skb) | |
144 | { | |
145 | if (sizeof(nci_pattern_core_reset_ntf) != skb->len || | |
146 | memcmp(skb->data, nci_pattern_core_reset_ntf, | |
147 | sizeof(nci_pattern_core_reset_ntf))) | |
148 | return -EINVAL; | |
149 | ||
150 | nfc_info(priv->dev, "BootROM reset, start fw download\n"); | |
151 | ||
152 | /* Start FW download state machine */ | |
153 | priv->fw_dnld.state = STATE_INIT; | |
154 | nci_send_cmd(priv->ndev, NCI_OP_CORE_INIT_CMD, 0, NULL); | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | static int process_state_init(struct nfcmrvl_private *priv, struct sk_buff *skb) | |
160 | { | |
161 | struct nci_core_set_config_cmd cmd; | |
162 | ||
163 | if (sizeof(nci_pattern_core_init_rsp) >= skb->len || | |
164 | memcmp(skb->data, nci_pattern_core_init_rsp, | |
165 | sizeof(nci_pattern_core_init_rsp))) | |
166 | return -EINVAL; | |
167 | ||
168 | cmd.num_params = 1; | |
169 | cmd.param.id = NFCMRVL_PROP_REF_CLOCK; | |
170 | cmd.param.len = 4; | |
171 | memcpy(cmd.param.val, &priv->fw_dnld.header->ref_clock, 4); | |
172 | ||
173 | nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, | |
174 | &cmd); | |
175 | ||
176 | priv->fw_dnld.state = STATE_SET_REF_CLOCK; | |
177 | return 0; | |
178 | } | |
179 | ||
180 | static void create_lc(struct nfcmrvl_private *priv) | |
181 | { | |
182 | uint8_t param[2] = { NCI_CORE_LC_PROP_FW_DL, 0x0 }; | |
183 | ||
184 | priv->fw_dnld.state = STATE_OPEN_LC; | |
185 | nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CREATE_CMD, 2, param); | |
186 | } | |
187 | ||
188 | static int process_state_set_ref_clock(struct nfcmrvl_private *priv, | |
189 | struct sk_buff *skb) | |
190 | { | |
191 | struct nci_core_set_config_cmd cmd; | |
192 | ||
193 | if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || | |
194 | memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) | |
195 | return -EINVAL; | |
196 | ||
197 | cmd.num_params = 1; | |
198 | cmd.param.id = NFCMRVL_PROP_SET_HI_CONFIG; | |
199 | ||
200 | switch (priv->phy) { | |
201 | case NFCMRVL_PHY_UART: | |
202 | cmd.param.len = 5; | |
203 | memcpy(cmd.param.val, | |
204 | &priv->fw_dnld.binary_config->uart.baudrate, | |
205 | 4); | |
206 | cmd.param.val[4] = | |
207 | priv->fw_dnld.binary_config->uart.flow_control; | |
208 | break; | |
209 | case NFCMRVL_PHY_I2C: | |
210 | cmd.param.len = 5; | |
211 | memcpy(cmd.param.val, | |
212 | &priv->fw_dnld.binary_config->i2c.clk, | |
213 | 4); | |
214 | cmd.param.val[4] = 0; | |
215 | break; | |
216 | case NFCMRVL_PHY_SPI: | |
217 | cmd.param.len = 5; | |
218 | memcpy(cmd.param.val, | |
219 | &priv->fw_dnld.binary_config->spi.clk, | |
220 | 4); | |
221 | cmd.param.val[4] = 0; | |
222 | break; | |
223 | default: | |
224 | create_lc(priv); | |
225 | return 0; | |
226 | } | |
227 | ||
228 | priv->fw_dnld.state = STATE_SET_HI_CONFIG; | |
229 | nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, | |
230 | &cmd); | |
231 | return 0; | |
232 | } | |
233 | ||
234 | static int process_state_set_hi_config(struct nfcmrvl_private *priv, | |
235 | struct sk_buff *skb) | |
236 | { | |
237 | if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || | |
238 | memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) | |
239 | return -EINVAL; | |
240 | ||
241 | create_lc(priv); | |
242 | return 0; | |
243 | } | |
244 | ||
245 | static int process_state_open_lc(struct nfcmrvl_private *priv, | |
246 | struct sk_buff *skb) | |
247 | { | |
248 | if (sizeof(nci_pattern_core_conn_create_rsp) >= skb->len || | |
249 | memcmp(skb->data, nci_pattern_core_conn_create_rsp, | |
250 | sizeof(nci_pattern_core_conn_create_rsp))) | |
251 | return -EINVAL; | |
252 | ||
253 | priv->fw_dnld.state = STATE_FW_DNLD; | |
254 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; | |
255 | priv->fw_dnld.offset = priv->fw_dnld.binary_config->offset; | |
256 | return 0; | |
257 | } | |
258 | ||
259 | static int process_state_fw_dnld(struct nfcmrvl_private *priv, | |
260 | struct sk_buff *skb) | |
261 | { | |
262 | uint16_t len; | |
263 | uint16_t comp_len; | |
264 | struct sk_buff *out_skb; | |
265 | ||
266 | switch (priv->fw_dnld.substate) { | |
267 | case SUBSTATE_WAIT_COMMAND: | |
268 | /* | |
269 | * Command format: | |
270 | * B0..2: NCI header | |
271 | * B3 : Helper command (0xA5) | |
272 | * B4..5: le16 data size | |
273 | * B6..7: le16 data size complement (~) | |
274 | * B8..N: payload | |
275 | */ | |
276 | ||
277 | /* Remove NCI HDR */ | |
278 | skb_pull(skb, 3); | |
279 | if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { | |
280 | nfc_err(priv->dev, "bad command"); | |
281 | return -EINVAL; | |
282 | } | |
283 | skb_pull(skb, 1); | |
284 | memcpy(&len, skb->data, 2); | |
285 | skb_pull(skb, 2); | |
286 | memcpy(&comp_len, skb->data, 2); | |
287 | skb_pull(skb, 2); | |
288 | len = get_unaligned_le16(&len); | |
289 | comp_len = get_unaligned_le16(&comp_len); | |
290 | if (((~len) & 0xFFFF) != comp_len) { | |
291 | nfc_err(priv->dev, "bad len complement: %x %x %x", | |
292 | len, comp_len, (~len & 0xFFFF)); | |
293 | out_skb = alloc_lc_skb(priv, 1); | |
294 | if (!out_skb) | |
295 | return -ENOMEM; | |
296 | *skb_put(out_skb, 1) = 0xBF; | |
297 | nci_send_frame(priv->ndev, out_skb); | |
298 | priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT; | |
299 | return 0; | |
300 | } | |
301 | priv->fw_dnld.chunk_len = len; | |
302 | out_skb = alloc_lc_skb(priv, 1); | |
303 | if (!out_skb) | |
304 | return -ENOMEM; | |
305 | *skb_put(out_skb, 1) = HELPER_ACK_PACKET_FORMAT; | |
306 | nci_send_frame(priv->ndev, out_skb); | |
307 | priv->fw_dnld.substate = SUBSTATE_WAIT_ACK_CREDIT; | |
308 | break; | |
309 | ||
310 | case SUBSTATE_WAIT_ACK_CREDIT: | |
311 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || | |
312 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, | |
313 | skb->len)) { | |
314 | nfc_err(priv->dev, "bad packet: waiting for credit"); | |
315 | return -EINVAL; | |
316 | } | |
317 | if (priv->fw_dnld.chunk_len == 0) { | |
318 | /* FW Loading is done */ | |
319 | uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; | |
320 | ||
321 | priv->fw_dnld.state = STATE_CLOSE_LC; | |
322 | nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD, | |
323 | 1, &conn_id); | |
324 | } else { | |
325 | out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len); | |
326 | if (!out_skb) | |
327 | return -ENOMEM; | |
328 | memcpy(skb_put(out_skb, priv->fw_dnld.chunk_len), | |
329 | ((uint8_t *)priv->fw_dnld.fw->data) + | |
330 | priv->fw_dnld.offset, | |
331 | priv->fw_dnld.chunk_len); | |
332 | nci_send_frame(priv->ndev, out_skb); | |
333 | priv->fw_dnld.substate = SUBSTATE_WAIT_DATA_CREDIT; | |
334 | } | |
335 | break; | |
336 | ||
337 | case SUBSTATE_WAIT_DATA_CREDIT: | |
338 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || | |
339 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, | |
340 | skb->len)) { | |
341 | nfc_err(priv->dev, "bad packet: waiting for credit"); | |
342 | return -EINVAL; | |
343 | } | |
344 | priv->fw_dnld.offset += priv->fw_dnld.chunk_len; | |
345 | priv->fw_dnld.chunk_len = 0; | |
346 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; | |
347 | break; | |
348 | ||
349 | case SUBSTATE_WAIT_NACK_CREDIT: | |
350 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || | |
351 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, | |
352 | skb->len)) { | |
353 | nfc_err(priv->dev, "bad packet: waiting for credit"); | |
354 | return -EINVAL; | |
355 | } | |
356 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; | |
357 | break; | |
358 | } | |
359 | return 0; | |
360 | } | |
361 | ||
362 | static int process_state_close_lc(struct nfcmrvl_private *priv, | |
363 | struct sk_buff *skb) | |
364 | { | |
365 | if (sizeof(nci_pattern_core_conn_close_rsp) != skb->len || | |
366 | memcmp(skb->data, nci_pattern_core_conn_close_rsp, skb->len)) | |
367 | return -EINVAL; | |
368 | ||
369 | priv->fw_dnld.state = STATE_BOOT; | |
370 | nci_send_cmd(priv->ndev, NCI_OP_PROPRIETARY_BOOT_CMD, 0, NULL); | |
371 | return 0; | |
372 | } | |
373 | ||
374 | static int process_state_boot(struct nfcmrvl_private *priv, struct sk_buff *skb) | |
375 | { | |
376 | if (sizeof(nci_pattern_proprietary_boot_rsp) != skb->len || | |
377 | memcmp(skb->data, nci_pattern_proprietary_boot_rsp, skb->len)) | |
378 | return -EINVAL; | |
379 | ||
380 | /* | |
381 | * Update HI config to use the right configuration for the next | |
382 | * data exchanges. | |
383 | */ | |
384 | priv->if_ops->nci_update_config(priv, | |
385 | &priv->fw_dnld.binary_config->config); | |
386 | ||
387 | if (priv->fw_dnld.binary_config == &priv->fw_dnld.header->helper) { | |
388 | /* | |
389 | * This is the case where an helper was needed and we have | |
390 | * uploaded it. Now we have to wait the next RESET NTF to start | |
391 | * FW download. | |
392 | */ | |
393 | priv->fw_dnld.state = STATE_RESET; | |
394 | priv->fw_dnld.binary_config = &priv->fw_dnld.header->firmware; | |
395 | nfc_info(priv->dev, "FW loading: helper loaded"); | |
396 | } else { | |
397 | nfc_info(priv->dev, "FW loading: firmware loaded"); | |
398 | fw_dnld_over(priv, 0); | |
399 | } | |
400 | return 0; | |
401 | } | |
402 | ||
403 | static void fw_dnld_rx_work(struct work_struct *work) | |
404 | { | |
405 | int ret; | |
406 | struct sk_buff *skb; | |
407 | struct nfcmrvl_fw_dnld *fw_dnld = container_of(work, | |
408 | struct nfcmrvl_fw_dnld, | |
409 | rx_work); | |
410 | struct nfcmrvl_private *priv = container_of(fw_dnld, | |
411 | struct nfcmrvl_private, | |
412 | fw_dnld); | |
413 | ||
414 | while ((skb = skb_dequeue(&fw_dnld->rx_q))) { | |
415 | nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb, | |
416 | RAW_PAYLOAD_NCI, NFC_DIRECTION_RX); | |
417 | switch (fw_dnld->state) { | |
418 | case STATE_RESET: | |
419 | ret = process_state_reset(priv, skb); | |
420 | break; | |
421 | case STATE_INIT: | |
422 | ret = process_state_init(priv, skb); | |
423 | break; | |
424 | case STATE_SET_REF_CLOCK: | |
425 | ret = process_state_set_ref_clock(priv, skb); | |
426 | break; | |
427 | case STATE_SET_HI_CONFIG: | |
428 | ret = process_state_set_hi_config(priv, skb); | |
429 | break; | |
430 | case STATE_OPEN_LC: | |
431 | ret = process_state_open_lc(priv, skb); | |
432 | break; | |
433 | case STATE_FW_DNLD: | |
434 | ret = process_state_fw_dnld(priv, skb); | |
435 | break; | |
436 | case STATE_CLOSE_LC: | |
437 | ret = process_state_close_lc(priv, skb); | |
438 | break; | |
439 | case STATE_BOOT: | |
440 | ret = process_state_boot(priv, skb); | |
441 | break; | |
442 | default: | |
443 | ret = -EFAULT; | |
444 | } | |
445 | ||
446 | kfree_skb(skb); | |
447 | ||
448 | if (ret != 0) { | |
449 | nfc_err(priv->dev, "FW loading error"); | |
450 | fw_dnld_over(priv, ret); | |
451 | break; | |
452 | } | |
453 | } | |
454 | } | |
455 | ||
456 | int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv) | |
457 | { | |
458 | char name[32]; | |
459 | ||
460 | INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work); | |
461 | snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq", | |
462 | dev_name(priv->dev)); | |
463 | priv->fw_dnld.rx_wq = create_singlethread_workqueue(name); | |
464 | if (!priv->fw_dnld.rx_wq) | |
465 | return -ENOMEM; | |
466 | skb_queue_head_init(&priv->fw_dnld.rx_q); | |
467 | return 0; | |
468 | } | |
469 | ||
470 | void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv) | |
471 | { | |
472 | destroy_workqueue(priv->fw_dnld.rx_wq); | |
473 | } | |
474 | ||
475 | void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv, | |
476 | struct sk_buff *skb) | |
477 | { | |
82aff3ea VC |
478 | /* Discard command timer */ |
479 | if (timer_pending(&priv->ndev->cmd_timer)) | |
480 | del_timer_sync(&priv->ndev->cmd_timer); | |
481 | ||
3194c687 VC |
482 | /* Allow next command */ |
483 | atomic_set(&priv->ndev->cmd_cnt, 1); | |
3194c687 VC |
484 | |
485 | /* Queue and trigger rx work */ | |
486 | skb_queue_tail(&priv->fw_dnld.rx_q, skb); | |
487 | queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work); | |
488 | } | |
489 | ||
490 | void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv) | |
491 | { | |
492 | fw_dnld_over(priv, -EHOSTDOWN); | |
493 | } | |
494 | ||
495 | int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name) | |
496 | { | |
497 | struct nfcmrvl_private *priv = nci_get_drvdata(ndev); | |
498 | struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld; | |
499 | ||
500 | if (!priv->support_fw_dnld) | |
501 | return -ENOTSUPP; | |
502 | ||
503 | if (!firmware_name || !firmware_name[0]) | |
504 | return -EINVAL; | |
505 | ||
506 | strcpy(fw_dnld->name, firmware_name); | |
507 | ||
508 | /* | |
509 | * Retrieve FW binary file and parse it to initialize FW download | |
510 | * state machine. | |
511 | */ | |
512 | ||
513 | /* Retrieve FW binary */ | |
514 | if (request_firmware(&fw_dnld->fw, firmware_name, priv->dev) < 0) { | |
515 | nfc_err(priv->dev, "failed to retrieve FW %s", firmware_name); | |
516 | return -ENOENT; | |
517 | } | |
518 | ||
519 | fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data; | |
520 | ||
521 | if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC || | |
522 | fw_dnld->header->phy != priv->phy) { | |
523 | nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d", | |
524 | firmware_name, fw_dnld->header->magic, | |
525 | fw_dnld->header->phy); | |
526 | release_firmware(fw_dnld->fw); | |
527 | fw_dnld->header = NULL; | |
528 | return -EINVAL; | |
529 | } | |
530 | ||
531 | if (fw_dnld->header->helper.offset != 0) { | |
532 | nfc_info(priv->dev, "loading helper"); | |
533 | fw_dnld->binary_config = &fw_dnld->header->helper; | |
534 | } else { | |
535 | nfc_info(priv->dev, "loading firmware"); | |
536 | fw_dnld->binary_config = &fw_dnld->header->firmware; | |
537 | } | |
538 | ||
539 | /* Configure a timer for timeout */ | |
540 | setup_timer(&priv->fw_dnld.timer, fw_dnld_timeout, | |
541 | (unsigned long) priv); | |
542 | mod_timer(&priv->fw_dnld.timer, | |
543 | jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT)); | |
544 | ||
545 | /* Ronfigure HI to be sure that it is the bootrom values */ | |
546 | priv->if_ops->nci_update_config(priv, | |
547 | &fw_dnld->header->bootrom.config); | |
548 | ||
549 | /* Allow first command */ | |
550 | atomic_set(&priv->ndev->cmd_cnt, 1); | |
551 | ||
552 | /* First, reset the chip */ | |
553 | priv->fw_dnld.state = STATE_RESET; | |
554 | nfcmrvl_chip_reset(priv); | |
555 | ||
556 | /* Now wait for CORE_RESET_NTF or timeout */ | |
557 | ||
558 | return 0; | |
559 | } |