Commit 06c660340f1e142b607541ece3520fff3f5d2c39
Committed by
Samuel Ortiz
1 parent
8bd7fc8995
Exists in
smarc-imx_3.14.28_1.0.0_ga
and in
1 other branch
NFC: pn544: i2c: Add firmware download implementation for pn544
The pn544 can enter a firmware update mode where firmware blobs can be pushed through the i2c line and flashed on the target. A special command allows to verify that blobs are correctly flashed and this is what we do for every downloaded firmware blob. Signed-off-by: Eric Lapuyade <eric.lapuyade@intel.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Showing 1 changed file with 334 additions and 12 deletions Side-by-side Diff
drivers/nfc/pn544/i2c.c
... | ... | @@ -25,11 +25,14 @@ |
25 | 25 | #include <linux/miscdevice.h> |
26 | 26 | #include <linux/interrupt.h> |
27 | 27 | #include <linux/delay.h> |
28 | - | |
28 | +#include <linux/nfc.h> | |
29 | +#include <linux/firmware.h> | |
30 | +#include <linux/unaligned/access_ok.h> | |
29 | 31 | #include <linux/platform_data/pn544.h> |
30 | 32 | |
31 | 33 | #include <net/nfc/hci.h> |
32 | 34 | #include <net/nfc/llc.h> |
35 | +#include <net/nfc/nfc.h> | |
33 | 36 | |
34 | 37 | #include "pn544.h" |
35 | 38 | |
... | ... | @@ -55,6 +58,58 @@ |
55 | 58 | |
56 | 59 | #define PN544_HCI_I2C_DRIVER_NAME "pn544_hci_i2c" |
57 | 60 | |
61 | +#define PN544_FW_CMD_WRITE 0x08 | |
62 | +#define PN544_FW_CMD_CHECK 0x06 | |
63 | + | |
64 | +struct pn544_i2c_fw_frame_write { | |
65 | + u8 cmd; | |
66 | + u16 be_length; | |
67 | + u8 be_dest_addr[3]; | |
68 | + u16 be_datalen; | |
69 | + u8 data[]; | |
70 | +} __packed; | |
71 | + | |
72 | +struct pn544_i2c_fw_frame_check { | |
73 | + u8 cmd; | |
74 | + u16 be_length; | |
75 | + u8 be_start_addr[3]; | |
76 | + u16 be_datalen; | |
77 | + u16 be_crc; | |
78 | +} __packed; | |
79 | + | |
80 | +struct pn544_i2c_fw_frame_response { | |
81 | + u8 status; | |
82 | + u16 be_length; | |
83 | +} __packed; | |
84 | + | |
85 | +struct pn544_i2c_fw_blob { | |
86 | + u32 be_size; | |
87 | + u32 be_destaddr; | |
88 | + u8 data[]; | |
89 | +}; | |
90 | + | |
91 | +#define PN544_FW_CMD_RESULT_TIMEOUT 0x01 | |
92 | +#define PN544_FW_CMD_RESULT_BAD_CRC 0x02 | |
93 | +#define PN544_FW_CMD_RESULT_ACCESS_DENIED 0x08 | |
94 | +#define PN544_FW_CMD_RESULT_PROTOCOL_ERROR 0x0B | |
95 | +#define PN544_FW_CMD_RESULT_INVALID_PARAMETER 0x11 | |
96 | +#define PN544_FW_CMD_RESULT_INVALID_LENGTH 0x18 | |
97 | +#define PN544_FW_CMD_RESULT_WRITE_FAILED 0x74 | |
98 | + | |
99 | +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) | |
100 | + | |
101 | +#define PN544_FW_WRITE_BUFFER_MAX_LEN 0x9f7 | |
102 | +#define PN544_FW_I2C_MAX_PAYLOAD PN544_HCI_I2C_LLC_MAX_SIZE | |
103 | +#define PN544_FW_I2C_WRITE_FRAME_HEADER_LEN 8 | |
104 | +#define PN544_FW_I2C_WRITE_DATA_MAX_LEN MIN((PN544_FW_I2C_MAX_PAYLOAD -\ | |
105 | + PN544_FW_I2C_WRITE_FRAME_HEADER_LEN),\ | |
106 | + PN544_FW_WRITE_BUFFER_MAX_LEN) | |
107 | + | |
108 | +#define FW_WORK_STATE_IDLE 1 | |
109 | +#define FW_WORK_STATE_START 2 | |
110 | +#define FW_WORK_STATE_WAIT_WRITE_ANSWER 3 | |
111 | +#define FW_WORK_STATE_WAIT_CHECK_ANSWER 4 | |
112 | + | |
58 | 113 | struct pn544_i2c_phy { |
59 | 114 | struct i2c_client *i2c_dev; |
60 | 115 | struct nfc_hci_dev *hdev; |
... | ... | @@ -64,6 +119,16 @@ |
64 | 119 | unsigned int gpio_fw; |
65 | 120 | unsigned int en_polarity; |
66 | 121 | |
122 | + struct work_struct fw_work; | |
123 | + int fw_work_state; | |
124 | + char firmware_name[NFC_FIRMWARE_NAME_MAXSIZE + 1]; | |
125 | + const struct firmware *fw; | |
126 | + u32 fw_blob_dest_addr; | |
127 | + size_t fw_blob_size; | |
128 | + const u8 *fw_blob_data; | |
129 | + size_t fw_written; | |
130 | + int fw_cmd_result; | |
131 | + | |
67 | 132 | int powered; |
68 | 133 | int run_mode; |
69 | 134 | |
... | ... | @@ -313,6 +378,42 @@ |
313 | 378 | return r; |
314 | 379 | } |
315 | 380 | |
381 | +static int pn544_hci_i2c_fw_read_status(struct pn544_i2c_phy *phy) | |
382 | +{ | |
383 | + int r; | |
384 | + struct pn544_i2c_fw_frame_response response; | |
385 | + struct i2c_client *client = phy->i2c_dev; | |
386 | + | |
387 | + r = i2c_master_recv(client, (char *) &response, sizeof(response)); | |
388 | + if (r != sizeof(response)) { | |
389 | + dev_err(&client->dev, "cannot read fw status\n"); | |
390 | + return -EIO; | |
391 | + } | |
392 | + | |
393 | + usleep_range(3000, 6000); | |
394 | + | |
395 | + switch (response.status) { | |
396 | + case 0: | |
397 | + return 0; | |
398 | + case PN544_FW_CMD_RESULT_TIMEOUT: | |
399 | + return -ETIMEDOUT; | |
400 | + case PN544_FW_CMD_RESULT_BAD_CRC: | |
401 | + return -ENODATA; | |
402 | + case PN544_FW_CMD_RESULT_ACCESS_DENIED: | |
403 | + return -EACCES; | |
404 | + case PN544_FW_CMD_RESULT_PROTOCOL_ERROR: | |
405 | + return -EPROTO; | |
406 | + case PN544_FW_CMD_RESULT_INVALID_PARAMETER: | |
407 | + return -EINVAL; | |
408 | + case PN544_FW_CMD_RESULT_INVALID_LENGTH: | |
409 | + return -EBADMSG; | |
410 | + case PN544_FW_CMD_RESULT_WRITE_FAILED: | |
411 | + return -EIO; | |
412 | + default: | |
413 | + return -EIO; | |
414 | + } | |
415 | +} | |
416 | + | |
316 | 417 | /* |
317 | 418 | * Reads an shdlc frame from the chip. This is not as straightforward as it |
318 | 419 | * seems. There are cases where we could loose the frame start synchronization. |
319 | 420 | |
320 | 421 | |
321 | 422 | |
... | ... | @@ -347,19 +448,23 @@ |
347 | 448 | if (phy->hard_fault != 0) |
348 | 449 | return IRQ_HANDLED; |
349 | 450 | |
350 | - r = pn544_hci_i2c_read(phy, &skb); | |
351 | - if (r == -EREMOTEIO) { | |
352 | - phy->hard_fault = r; | |
451 | + if (phy->run_mode == PN544_FW_MODE) { | |
452 | + phy->fw_cmd_result = pn544_hci_i2c_fw_read_status(phy); | |
453 | + schedule_work(&phy->fw_work); | |
454 | + } else { | |
455 | + r = pn544_hci_i2c_read(phy, &skb); | |
456 | + if (r == -EREMOTEIO) { | |
457 | + phy->hard_fault = r; | |
353 | 458 | |
354 | - nfc_hci_recv_frame(phy->hdev, NULL); | |
459 | + nfc_hci_recv_frame(phy->hdev, NULL); | |
355 | 460 | |
356 | - return IRQ_HANDLED; | |
357 | - } else if ((r == -ENOMEM) || (r == -EBADMSG)) { | |
358 | - return IRQ_HANDLED; | |
359 | - } | |
461 | + return IRQ_HANDLED; | |
462 | + } else if ((r == -ENOMEM) || (r == -EBADMSG)) { | |
463 | + return IRQ_HANDLED; | |
464 | + } | |
360 | 465 | |
361 | - nfc_hci_recv_frame(phy->hdev, skb); | |
362 | - | |
466 | + nfc_hci_recv_frame(phy->hdev, skb); | |
467 | + } | |
363 | 468 | return IRQ_HANDLED; |
364 | 469 | } |
365 | 470 | |
... | ... | @@ -369,6 +474,215 @@ |
369 | 474 | .disable = pn544_hci_i2c_disable, |
370 | 475 | }; |
371 | 476 | |
477 | +static int pn544_hci_i2c_fw_download(void *phy_id, const char *firmware_name) | |
478 | +{ | |
479 | + struct pn544_i2c_phy *phy = phy_id; | |
480 | + | |
481 | + pr_info(DRIVER_DESC ": Starting Firmware Download (%s)\n", | |
482 | + firmware_name); | |
483 | + | |
484 | + strcpy(phy->firmware_name, firmware_name); | |
485 | + | |
486 | + phy->fw_work_state = FW_WORK_STATE_START; | |
487 | + | |
488 | + schedule_work(&phy->fw_work); | |
489 | + | |
490 | + return 0; | |
491 | +} | |
492 | + | |
493 | +static void pn544_hci_i2c_fw_work_complete(struct pn544_i2c_phy *phy, | |
494 | + int result) | |
495 | +{ | |
496 | + pr_info(DRIVER_DESC ": Firmware Download Complete, result=%d\n", result); | |
497 | + | |
498 | + pn544_hci_i2c_disable(phy); | |
499 | + | |
500 | + phy->fw_work_state = FW_WORK_STATE_IDLE; | |
501 | + | |
502 | + if (phy->fw) { | |
503 | + release_firmware(phy->fw); | |
504 | + phy->fw = NULL; | |
505 | + } | |
506 | + | |
507 | + nfc_fw_download_done(phy->hdev->ndev, phy->firmware_name, (u32) -result); | |
508 | +} | |
509 | + | |
510 | +static int pn544_hci_i2c_fw_write_cmd(struct i2c_client *client, u32 dest_addr, | |
511 | + const u8 *data, u16 datalen) | |
512 | +{ | |
513 | + u8 frame[PN544_FW_I2C_MAX_PAYLOAD]; | |
514 | + struct pn544_i2c_fw_frame_write *framep; | |
515 | + u16 params_len; | |
516 | + int framelen; | |
517 | + int r; | |
518 | + | |
519 | + if (datalen > PN544_FW_I2C_WRITE_DATA_MAX_LEN) | |
520 | + datalen = PN544_FW_I2C_WRITE_DATA_MAX_LEN; | |
521 | + | |
522 | + framep = (struct pn544_i2c_fw_frame_write *) frame; | |
523 | + | |
524 | + params_len = sizeof(framep->be_dest_addr) + | |
525 | + sizeof(framep->be_datalen) + datalen; | |
526 | + framelen = params_len + sizeof(framep->cmd) + | |
527 | + sizeof(framep->be_length); | |
528 | + | |
529 | + framep->cmd = PN544_FW_CMD_WRITE; | |
530 | + | |
531 | + put_unaligned_be16(params_len, &framep->be_length); | |
532 | + | |
533 | + framep->be_dest_addr[0] = (dest_addr & 0xff0000) >> 16; | |
534 | + framep->be_dest_addr[1] = (dest_addr & 0xff00) >> 8; | |
535 | + framep->be_dest_addr[2] = dest_addr & 0xff; | |
536 | + | |
537 | + put_unaligned_be16(datalen, &framep->be_datalen); | |
538 | + | |
539 | + memcpy(framep->data, data, datalen); | |
540 | + | |
541 | + r = i2c_master_send(client, frame, framelen); | |
542 | + | |
543 | + if (r == framelen) | |
544 | + return datalen; | |
545 | + else if (r < 0) | |
546 | + return r; | |
547 | + else | |
548 | + return -EIO; | |
549 | +} | |
550 | + | |
551 | +static int pn544_hci_i2c_fw_check_cmd(struct i2c_client *client, u32 start_addr, | |
552 | + const u8 *data, u16 datalen) | |
553 | +{ | |
554 | + struct pn544_i2c_fw_frame_check frame; | |
555 | + int r; | |
556 | + u16 crc; | |
557 | + | |
558 | + /* calculate local crc for the data we want to check */ | |
559 | + crc = crc_ccitt(0xffff, data, datalen); | |
560 | + | |
561 | + frame.cmd = PN544_FW_CMD_CHECK; | |
562 | + | |
563 | + put_unaligned_be16(sizeof(frame.be_start_addr) + | |
564 | + sizeof(frame.be_datalen) + sizeof(frame.be_crc), | |
565 | + &frame.be_length); | |
566 | + | |
567 | + /* tell the chip the memory region to which our crc applies */ | |
568 | + frame.be_start_addr[0] = (start_addr & 0xff0000) >> 16; | |
569 | + frame.be_start_addr[1] = (start_addr & 0xff00) >> 8; | |
570 | + frame.be_start_addr[2] = start_addr & 0xff; | |
571 | + | |
572 | + put_unaligned_be16(datalen, &frame.be_datalen); | |
573 | + | |
574 | + /* | |
575 | + * and give our local crc. Chip will calculate its own crc for the | |
576 | + * region and compare with ours. | |
577 | + */ | |
578 | + put_unaligned_be16(crc, &frame.be_crc); | |
579 | + | |
580 | + r = i2c_master_send(client, (const char *) &frame, sizeof(frame)); | |
581 | + | |
582 | + if (r == sizeof(frame)) | |
583 | + return 0; | |
584 | + else if (r < 0) | |
585 | + return r; | |
586 | + else | |
587 | + return -EIO; | |
588 | +} | |
589 | + | |
590 | +static int pn544_hci_i2c_fw_write_chunk(struct pn544_i2c_phy *phy) | |
591 | +{ | |
592 | + int r; | |
593 | + | |
594 | + r = pn544_hci_i2c_fw_write_cmd(phy->i2c_dev, | |
595 | + phy->fw_blob_dest_addr + phy->fw_written, | |
596 | + phy->fw_blob_data + phy->fw_written, | |
597 | + phy->fw_blob_size - phy->fw_written); | |
598 | + if (r < 0) | |
599 | + return r; | |
600 | + | |
601 | + phy->fw_written += r; | |
602 | + phy->fw_work_state = FW_WORK_STATE_WAIT_WRITE_ANSWER; | |
603 | + | |
604 | + return 0; | |
605 | +} | |
606 | + | |
607 | +static void pn544_hci_i2c_fw_work(struct work_struct *work) | |
608 | +{ | |
609 | + struct pn544_i2c_phy *phy = container_of(work, struct pn544_i2c_phy, | |
610 | + fw_work); | |
611 | + int r; | |
612 | + struct pn544_i2c_fw_blob *blob; | |
613 | + | |
614 | + switch (phy->fw_work_state) { | |
615 | + case FW_WORK_STATE_START: | |
616 | + pn544_hci_i2c_enable_mode(phy, PN544_FW_MODE); | |
617 | + | |
618 | + r = request_firmware(&phy->fw, phy->firmware_name, | |
619 | + &phy->i2c_dev->dev); | |
620 | + if (r < 0) | |
621 | + goto exit_state_start; | |
622 | + | |
623 | + blob = (struct pn544_i2c_fw_blob *) phy->fw->data; | |
624 | + phy->fw_blob_size = get_unaligned_be32(&blob->be_size); | |
625 | + phy->fw_blob_dest_addr = get_unaligned_be32(&blob->be_destaddr); | |
626 | + phy->fw_blob_data = blob->data; | |
627 | + | |
628 | + phy->fw_written = 0; | |
629 | + r = pn544_hci_i2c_fw_write_chunk(phy); | |
630 | + | |
631 | +exit_state_start: | |
632 | + if (r < 0) | |
633 | + pn544_hci_i2c_fw_work_complete(phy, r); | |
634 | + break; | |
635 | + | |
636 | + case FW_WORK_STATE_WAIT_WRITE_ANSWER: | |
637 | + r = phy->fw_cmd_result; | |
638 | + if (r < 0) | |
639 | + goto exit_state_wait_write_answer; | |
640 | + | |
641 | + if (phy->fw_written == phy->fw_blob_size) { | |
642 | + r = pn544_hci_i2c_fw_check_cmd(phy->i2c_dev, | |
643 | + phy->fw_blob_dest_addr, | |
644 | + phy->fw_blob_data, | |
645 | + phy->fw_blob_size); | |
646 | + if (r < 0) | |
647 | + goto exit_state_wait_write_answer; | |
648 | + phy->fw_work_state = FW_WORK_STATE_WAIT_CHECK_ANSWER; | |
649 | + break; | |
650 | + } | |
651 | + | |
652 | + r = pn544_hci_i2c_fw_write_chunk(phy); | |
653 | + | |
654 | +exit_state_wait_write_answer: | |
655 | + if (r < 0) | |
656 | + pn544_hci_i2c_fw_work_complete(phy, r); | |
657 | + break; | |
658 | + | |
659 | + case FW_WORK_STATE_WAIT_CHECK_ANSWER: | |
660 | + r = phy->fw_cmd_result; | |
661 | + if (r < 0) | |
662 | + goto exit_state_wait_check_answer; | |
663 | + | |
664 | + blob = (struct pn544_i2c_fw_blob *) (phy->fw_blob_data + | |
665 | + phy->fw_blob_size); | |
666 | + phy->fw_blob_size = get_unaligned_be32(&blob->be_size); | |
667 | + if (phy->fw_blob_size != 0) { | |
668 | + phy->fw_blob_dest_addr = | |
669 | + get_unaligned_be32(&blob->be_destaddr); | |
670 | + phy->fw_blob_data = blob->data; | |
671 | + | |
672 | + phy->fw_written = 0; | |
673 | + r = pn544_hci_i2c_fw_write_chunk(phy); | |
674 | + } | |
675 | + | |
676 | +exit_state_wait_check_answer: | |
677 | + if (r < 0 || phy->fw_blob_size == 0) | |
678 | + pn544_hci_i2c_fw_work_complete(phy, r); | |
679 | + break; | |
680 | + | |
681 | + default: | |
682 | + break; | |
683 | + } | |
684 | +} | |
685 | + | |
372 | 686 | static int pn544_hci_i2c_probe(struct i2c_client *client, |
373 | 687 | const struct i2c_device_id *id) |
374 | 688 | { |
... | ... | @@ -392,6 +706,9 @@ |
392 | 706 | return -ENOMEM; |
393 | 707 | } |
394 | 708 | |
709 | + INIT_WORK(&phy->fw_work, pn544_hci_i2c_fw_work); | |
710 | + phy->fw_work_state = FW_WORK_STATE_IDLE; | |
711 | + | |
395 | 712 | phy->i2c_dev = client; |
396 | 713 | i2c_set_clientdata(client, phy); |
397 | 714 | |
... | ... | @@ -428,7 +745,8 @@ |
428 | 745 | |
429 | 746 | r = pn544_hci_probe(phy, &i2c_phy_ops, LLC_SHDLC_NAME, |
430 | 747 | PN544_I2C_FRAME_HEADROOM, PN544_I2C_FRAME_TAILROOM, |
431 | - PN544_HCI_I2C_LLC_MAX_PAYLOAD, NULL, &phy->hdev); | |
748 | + PN544_HCI_I2C_LLC_MAX_PAYLOAD, | |
749 | + pn544_hci_i2c_fw_download, &phy->hdev); | |
432 | 750 | if (r < 0) |
433 | 751 | goto err_hci; |
434 | 752 | |
... | ... | @@ -450,6 +768,10 @@ |
450 | 768 | struct pn544_nfc_platform_data *pdata = client->dev.platform_data; |
451 | 769 | |
452 | 770 | dev_dbg(&client->dev, "%s\n", __func__); |
771 | + | |
772 | + cancel_work_sync(&phy->fw_work); | |
773 | + if (phy->fw_work_state != FW_WORK_STATE_IDLE) | |
774 | + pn544_hci_i2c_fw_work_complete(phy, -ENODEV); | |
453 | 775 | |
454 | 776 | pn544_hci_remove(phy->hdev); |
455 | 777 |