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 <asm/unaligned.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 = 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); |
116 | |
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); |
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(struct timer_list *t) |
134 | { |
135 | struct nfcmrvl_private *priv = from_timer(priv, t, fw_dnld.timer); |
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 | len = get_unaligned_le16(skb->data); |
285 | skb_pull(skb, 2); |
286 | comp_len = get_unaligned_le16(skb->data); |
287 | memcpy(&comp_len, skb->data, 2); |
288 | skb_pull(skb, 2); |
289 | if (((~len) & 0xFFFF) != comp_len) { |
290 | nfc_err(priv->dev, "bad len complement: %x %x %x" , |
291 | len, comp_len, (~len & 0xFFFF)); |
292 | out_skb = alloc_lc_skb(priv, 1); |
293 | if (!out_skb) |
294 | return -ENOMEM; |
295 | skb_put_u8(out_skb, 0xBF); |
296 | nci_send_frame(priv->ndev, out_skb); |
297 | priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT; |
298 | return 0; |
299 | } |
300 | priv->fw_dnld.chunk_len = len; |
301 | out_skb = alloc_lc_skb(priv, 1); |
302 | if (!out_skb) |
303 | return -ENOMEM; |
304 | skb_put_u8(out_skb, HELPER_ACK_PACKET_FORMAT); |
305 | nci_send_frame(priv->ndev, out_skb); |
306 | priv->fw_dnld.substate = SUBSTATE_WAIT_ACK_CREDIT; |
307 | break; |
308 | |
309 | case SUBSTATE_WAIT_ACK_CREDIT: |
310 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || |
311 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, |
312 | skb->len)) { |
313 | nfc_err(priv->dev, "bad packet: waiting for credit" ); |
314 | return -EINVAL; |
315 | } |
316 | if (priv->fw_dnld.chunk_len == 0) { |
317 | /* FW Loading is done */ |
318 | uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; |
319 | |
320 | priv->fw_dnld.state = STATE_CLOSE_LC; |
321 | nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD, |
322 | 1, &conn_id); |
323 | } else { |
324 | out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len); |
325 | if (!out_skb) |
326 | return -ENOMEM; |
327 | skb_put_data(out_skb, |
328 | ((uint8_t *)priv->fw_dnld.fw->data) + priv->fw_dnld.offset, |
329 | priv->fw_dnld.chunk_len); |
330 | nci_send_frame(priv->ndev, out_skb); |
331 | priv->fw_dnld.substate = SUBSTATE_WAIT_DATA_CREDIT; |
332 | } |
333 | break; |
334 | |
335 | case SUBSTATE_WAIT_DATA_CREDIT: |
336 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || |
337 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, |
338 | skb->len)) { |
339 | nfc_err(priv->dev, "bad packet: waiting for credit" ); |
340 | return -EINVAL; |
341 | } |
342 | priv->fw_dnld.offset += priv->fw_dnld.chunk_len; |
343 | priv->fw_dnld.chunk_len = 0; |
344 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; |
345 | break; |
346 | |
347 | case SUBSTATE_WAIT_NACK_CREDIT: |
348 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || |
349 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, |
350 | skb->len)) { |
351 | nfc_err(priv->dev, "bad packet: waiting for credit" ); |
352 | return -EINVAL; |
353 | } |
354 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; |
355 | break; |
356 | } |
357 | return 0; |
358 | } |
359 | |
360 | static int process_state_close_lc(struct nfcmrvl_private *priv, |
361 | struct sk_buff *skb) |
362 | { |
363 | if (sizeof(nci_pattern_core_conn_close_rsp) != skb->len || |
364 | memcmp(skb->data, nci_pattern_core_conn_close_rsp, skb->len)) |
365 | return -EINVAL; |
366 | |
367 | priv->fw_dnld.state = STATE_BOOT; |
368 | nci_send_cmd(priv->ndev, NCI_OP_PROPRIETARY_BOOT_CMD, 0, NULL); |
369 | return 0; |
370 | } |
371 | |
372 | static int process_state_boot(struct nfcmrvl_private *priv, struct sk_buff *skb) |
373 | { |
374 | if (sizeof(nci_pattern_proprietary_boot_rsp) != skb->len || |
375 | memcmp(skb->data, nci_pattern_proprietary_boot_rsp, skb->len)) |
376 | return -EINVAL; |
377 | |
378 | /* |
379 | * Update HI config to use the right configuration for the next |
380 | * data exchanges. |
381 | */ |
382 | priv->if_ops->nci_update_config(priv, |
383 | &priv->fw_dnld.binary_config->config); |
384 | |
385 | if (priv->fw_dnld.binary_config == &priv->fw_dnld.header->helper) { |
386 | /* |
387 | * This is the case where an helper was needed and we have |
388 | * uploaded it. Now we have to wait the next RESET NTF to start |
389 | * FW download. |
390 | */ |
391 | priv->fw_dnld.state = STATE_RESET; |
392 | priv->fw_dnld.binary_config = &priv->fw_dnld.header->firmware; |
393 | nfc_info(priv->dev, "FW loading: helper loaded" ); |
394 | } else { |
395 | nfc_info(priv->dev, "FW loading: firmware loaded" ); |
396 | fw_dnld_over(priv, 0); |
397 | } |
398 | return 0; |
399 | } |
400 | |
401 | static void fw_dnld_rx_work(struct work_struct *work) |
402 | { |
403 | int ret; |
404 | struct sk_buff *skb; |
405 | struct nfcmrvl_fw_dnld *fw_dnld = container_of(work, |
406 | struct nfcmrvl_fw_dnld, |
407 | rx_work); |
408 | struct nfcmrvl_private *priv = container_of(fw_dnld, |
409 | struct nfcmrvl_private, |
410 | fw_dnld); |
411 | |
412 | while ((skb = skb_dequeue(&fw_dnld->rx_q))) { |
413 | nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb, |
414 | RAW_PAYLOAD_NCI, NFC_DIRECTION_RX); |
415 | switch (fw_dnld->state) { |
416 | case STATE_RESET: |
417 | ret = process_state_reset(priv, skb); |
418 | break; |
419 | case STATE_INIT: |
420 | ret = process_state_init(priv, skb); |
421 | break; |
422 | case STATE_SET_REF_CLOCK: |
423 | ret = process_state_set_ref_clock(priv, skb); |
424 | break; |
425 | case STATE_SET_HI_CONFIG: |
426 | ret = process_state_set_hi_config(priv, skb); |
427 | break; |
428 | case STATE_OPEN_LC: |
429 | ret = process_state_open_lc(priv, skb); |
430 | break; |
431 | case STATE_FW_DNLD: |
432 | ret = process_state_fw_dnld(priv, skb); |
433 | break; |
434 | case STATE_CLOSE_LC: |
435 | ret = process_state_close_lc(priv, skb); |
436 | break; |
437 | case STATE_BOOT: |
438 | ret = process_state_boot(priv, skb); |
439 | break; |
440 | default: |
441 | ret = -EFAULT; |
442 | } |
443 | |
444 | kfree_skb(skb); |
445 | |
446 | if (ret != 0) { |
447 | nfc_err(priv->dev, "FW loading error" ); |
448 | fw_dnld_over(priv, ret); |
449 | break; |
450 | } |
451 | } |
452 | } |
453 | |
454 | int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv) |
455 | { |
456 | char name[32]; |
457 | |
458 | INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work); |
459 | snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq" , |
460 | dev_name(&priv->ndev->nfc_dev->dev)); |
461 | priv->fw_dnld.rx_wq = create_singlethread_workqueue(name); |
462 | if (!priv->fw_dnld.rx_wq) |
463 | return -ENOMEM; |
464 | skb_queue_head_init(&priv->fw_dnld.rx_q); |
465 | return 0; |
466 | } |
467 | |
468 | void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv) |
469 | { |
470 | destroy_workqueue(priv->fw_dnld.rx_wq); |
471 | } |
472 | |
473 | void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv, |
474 | struct sk_buff *skb) |
475 | { |
476 | /* Discard command timer */ |
477 | if (timer_pending(&priv->ndev->cmd_timer)) |
478 | del_timer_sync(&priv->ndev->cmd_timer); |
479 | |
480 | /* Allow next command */ |
481 | atomic_set(&priv->ndev->cmd_cnt, 1); |
482 | |
483 | /* Queue and trigger rx work */ |
484 | skb_queue_tail(&priv->fw_dnld.rx_q, skb); |
485 | queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work); |
486 | } |
487 | |
488 | void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv) |
489 | { |
490 | fw_dnld_over(priv, -EHOSTDOWN); |
491 | } |
492 | |
493 | int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name) |
494 | { |
495 | struct nfcmrvl_private *priv = nci_get_drvdata(ndev); |
496 | struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld; |
497 | int res; |
498 | |
499 | if (!priv->support_fw_dnld) |
500 | return -ENOTSUPP; |
501 | |
502 | if (!firmware_name || !firmware_name[0]) |
503 | return -EINVAL; |
504 | |
505 | strcpy(fw_dnld->name, firmware_name); |
506 | |
507 | /* |
508 | * Retrieve FW binary file and parse it to initialize FW download |
509 | * state machine. |
510 | */ |
511 | |
512 | /* Retrieve FW binary */ |
513 | res = request_firmware(&fw_dnld->fw, firmware_name, |
514 | &ndev->nfc_dev->dev); |
515 | if (res < 0) { |
516 | nfc_err(priv->dev, "failed to retrieve FW %s" , firmware_name); |
517 | return -ENOENT; |
518 | } |
519 | |
520 | fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data; |
521 | |
522 | if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC || |
523 | fw_dnld->header->phy != priv->phy) { |
524 | nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d" , |
525 | firmware_name, fw_dnld->header->magic, |
526 | fw_dnld->header->phy); |
527 | release_firmware(fw_dnld->fw); |
528 | fw_dnld->header = NULL; |
529 | return -EINVAL; |
530 | } |
531 | |
532 | if (fw_dnld->header->helper.offset != 0) { |
533 | nfc_info(priv->dev, "loading helper" ); |
534 | fw_dnld->binary_config = &fw_dnld->header->helper; |
535 | } else { |
536 | nfc_info(priv->dev, "loading firmware" ); |
537 | fw_dnld->binary_config = &fw_dnld->header->firmware; |
538 | } |
539 | |
540 | /* Configure a timer for timeout */ |
541 | timer_setup(&priv->fw_dnld.timer, fw_dnld_timeout, 0); |
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 | } |
560 | |