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