162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Marvell NFC driver: Firmware downloader 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2015, Marvell International Ltd. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <asm/unaligned.h> 1062306a36Sopenharmony_ci#include <linux/firmware.h> 1162306a36Sopenharmony_ci#include <linux/nfc.h> 1262306a36Sopenharmony_ci#include <net/nfc/nci.h> 1362306a36Sopenharmony_ci#include <net/nfc/nci_core.h> 1462306a36Sopenharmony_ci#include "nfcmrvl.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define FW_DNLD_TIMEOUT 15000 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define NCI_OP_PROPRIETARY_BOOT_CMD nci_opcode_pack(NCI_GID_PROPRIETARY, \ 1962306a36Sopenharmony_ci NCI_OP_PROP_BOOT_CMD) 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* FW download states */ 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cienum { 2462306a36Sopenharmony_ci STATE_RESET = 0, 2562306a36Sopenharmony_ci STATE_INIT, 2662306a36Sopenharmony_ci STATE_SET_REF_CLOCK, 2762306a36Sopenharmony_ci STATE_SET_HI_CONFIG, 2862306a36Sopenharmony_ci STATE_OPEN_LC, 2962306a36Sopenharmony_ci STATE_FW_DNLD, 3062306a36Sopenharmony_ci STATE_CLOSE_LC, 3162306a36Sopenharmony_ci STATE_BOOT 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cienum { 3562306a36Sopenharmony_ci SUBSTATE_WAIT_COMMAND = 0, 3662306a36Sopenharmony_ci SUBSTATE_WAIT_ACK_CREDIT, 3762306a36Sopenharmony_ci SUBSTATE_WAIT_NACK_CREDIT, 3862306a36Sopenharmony_ci SUBSTATE_WAIT_DATA_CREDIT, 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci/* 4262306a36Sopenharmony_ci * Patterns for responses 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic const uint8_t nci_pattern_core_reset_ntf[] = { 4662306a36Sopenharmony_ci 0x60, 0x00, 0x02, 0xA0, 0x01 4762306a36Sopenharmony_ci}; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic const uint8_t nci_pattern_core_init_rsp[] = { 5062306a36Sopenharmony_ci 0x40, 0x01, 0x11 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic const uint8_t nci_pattern_core_set_config_rsp[] = { 5462306a36Sopenharmony_ci 0x40, 0x02, 0x02, 0x00, 0x00 5562306a36Sopenharmony_ci}; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic const uint8_t nci_pattern_core_conn_create_rsp[] = { 5862306a36Sopenharmony_ci 0x40, 0x04, 0x04, 0x00 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic const uint8_t nci_pattern_core_conn_close_rsp[] = { 6262306a36Sopenharmony_ci 0x40, 0x05, 0x01, 0x00 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic const uint8_t nci_pattern_core_conn_credits_ntf[] = { 6662306a36Sopenharmony_ci 0x60, 0x06, 0x03, 0x01, NCI_CORE_LC_CONNID_PROP_FW_DL, 0x01 6762306a36Sopenharmony_ci}; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic const uint8_t nci_pattern_proprietary_boot_rsp[] = { 7062306a36Sopenharmony_ci 0x4F, 0x3A, 0x01, 0x00 7162306a36Sopenharmony_ci}; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic struct sk_buff *alloc_lc_skb(struct nfcmrvl_private *priv, uint8_t plen) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci struct sk_buff *skb; 7662306a36Sopenharmony_ci struct nci_data_hdr *hdr; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci skb = nci_skb_alloc(priv->ndev, (NCI_DATA_HDR_SIZE + plen), GFP_KERNEL); 7962306a36Sopenharmony_ci if (!skb) 8062306a36Sopenharmony_ci return NULL; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci hdr = skb_put(skb, NCI_DATA_HDR_SIZE); 8362306a36Sopenharmony_ci hdr->conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; 8462306a36Sopenharmony_ci hdr->rfu = 0; 8562306a36Sopenharmony_ci hdr->plen = plen; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci nci_mt_set((__u8 *)hdr, NCI_MT_DATA_PKT); 8862306a36Sopenharmony_ci nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci return skb; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic void fw_dnld_over(struct nfcmrvl_private *priv, u32 error) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci if (priv->fw_dnld.fw) { 9662306a36Sopenharmony_ci release_firmware(priv->fw_dnld.fw); 9762306a36Sopenharmony_ci priv->fw_dnld.fw = NULL; 9862306a36Sopenharmony_ci priv->fw_dnld.header = NULL; 9962306a36Sopenharmony_ci priv->fw_dnld.binary_config = NULL; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci atomic_set(&priv->ndev->cmd_cnt, 0); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (timer_pending(&priv->ndev->cmd_timer)) 10562306a36Sopenharmony_ci del_timer_sync(&priv->ndev->cmd_timer); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (timer_pending(&priv->fw_dnld.timer)) 10862306a36Sopenharmony_ci del_timer_sync(&priv->fw_dnld.timer); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci nfc_info(priv->dev, "FW loading over (%d)]\n", error); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (error != 0) { 11362306a36Sopenharmony_ci /* failed, halt the chip to avoid power consumption */ 11462306a36Sopenharmony_ci nfcmrvl_chip_halt(priv); 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci nfc_fw_download_done(priv->ndev->nfc_dev, priv->fw_dnld.name, error); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void fw_dnld_timeout(struct timer_list *t) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct nfcmrvl_private *priv = from_timer(priv, t, fw_dnld.timer); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci nfc_err(priv->dev, "FW loading timeout"); 12562306a36Sopenharmony_ci priv->fw_dnld.state = STATE_RESET; 12662306a36Sopenharmony_ci fw_dnld_over(priv, -ETIMEDOUT); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int process_state_reset(struct nfcmrvl_private *priv, 13062306a36Sopenharmony_ci const struct sk_buff *skb) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci if (sizeof(nci_pattern_core_reset_ntf) != skb->len || 13362306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_core_reset_ntf, 13462306a36Sopenharmony_ci sizeof(nci_pattern_core_reset_ntf))) 13562306a36Sopenharmony_ci return -EINVAL; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci nfc_info(priv->dev, "BootROM reset, start fw download\n"); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Start FW download state machine */ 14062306a36Sopenharmony_ci priv->fw_dnld.state = STATE_INIT; 14162306a36Sopenharmony_ci nci_send_cmd(priv->ndev, NCI_OP_CORE_INIT_CMD, 0, NULL); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return 0; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic int process_state_init(struct nfcmrvl_private *priv, 14762306a36Sopenharmony_ci const struct sk_buff *skb) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct nci_core_set_config_cmd cmd; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (sizeof(nci_pattern_core_init_rsp) >= skb->len || 15262306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_core_init_rsp, 15362306a36Sopenharmony_ci sizeof(nci_pattern_core_init_rsp))) 15462306a36Sopenharmony_ci return -EINVAL; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci cmd.num_params = 1; 15762306a36Sopenharmony_ci cmd.param.id = NFCMRVL_PROP_REF_CLOCK; 15862306a36Sopenharmony_ci cmd.param.len = 4; 15962306a36Sopenharmony_ci memcpy(cmd.param.val, &priv->fw_dnld.header->ref_clock, 4); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, 16262306a36Sopenharmony_ci &cmd); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci priv->fw_dnld.state = STATE_SET_REF_CLOCK; 16562306a36Sopenharmony_ci return 0; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic void create_lc(struct nfcmrvl_private *priv) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci uint8_t param[2] = { NCI_CORE_LC_PROP_FW_DL, 0x0 }; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci priv->fw_dnld.state = STATE_OPEN_LC; 17362306a36Sopenharmony_ci nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CREATE_CMD, 2, param); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int process_state_set_ref_clock(struct nfcmrvl_private *priv, 17762306a36Sopenharmony_ci const struct sk_buff *skb) 17862306a36Sopenharmony_ci{ 17962306a36Sopenharmony_ci struct nci_core_set_config_cmd cmd; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || 18262306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) 18362306a36Sopenharmony_ci return -EINVAL; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci cmd.num_params = 1; 18662306a36Sopenharmony_ci cmd.param.id = NFCMRVL_PROP_SET_HI_CONFIG; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci switch (priv->phy) { 18962306a36Sopenharmony_ci case NFCMRVL_PHY_UART: 19062306a36Sopenharmony_ci cmd.param.len = 5; 19162306a36Sopenharmony_ci memcpy(cmd.param.val, 19262306a36Sopenharmony_ci &priv->fw_dnld.binary_config->uart.baudrate, 19362306a36Sopenharmony_ci 4); 19462306a36Sopenharmony_ci cmd.param.val[4] = 19562306a36Sopenharmony_ci priv->fw_dnld.binary_config->uart.flow_control; 19662306a36Sopenharmony_ci break; 19762306a36Sopenharmony_ci case NFCMRVL_PHY_I2C: 19862306a36Sopenharmony_ci cmd.param.len = 5; 19962306a36Sopenharmony_ci memcpy(cmd.param.val, 20062306a36Sopenharmony_ci &priv->fw_dnld.binary_config->i2c.clk, 20162306a36Sopenharmony_ci 4); 20262306a36Sopenharmony_ci cmd.param.val[4] = 0; 20362306a36Sopenharmony_ci break; 20462306a36Sopenharmony_ci case NFCMRVL_PHY_SPI: 20562306a36Sopenharmony_ci cmd.param.len = 5; 20662306a36Sopenharmony_ci memcpy(cmd.param.val, 20762306a36Sopenharmony_ci &priv->fw_dnld.binary_config->spi.clk, 20862306a36Sopenharmony_ci 4); 20962306a36Sopenharmony_ci cmd.param.val[4] = 0; 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci default: 21262306a36Sopenharmony_ci create_lc(priv); 21362306a36Sopenharmony_ci return 0; 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci priv->fw_dnld.state = STATE_SET_HI_CONFIG; 21762306a36Sopenharmony_ci nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, 21862306a36Sopenharmony_ci &cmd); 21962306a36Sopenharmony_ci return 0; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int process_state_set_hi_config(struct nfcmrvl_private *priv, 22362306a36Sopenharmony_ci const struct sk_buff *skb) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || 22662306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) 22762306a36Sopenharmony_ci return -EINVAL; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci create_lc(priv); 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic int process_state_open_lc(struct nfcmrvl_private *priv, 23462306a36Sopenharmony_ci const struct sk_buff *skb) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci if (sizeof(nci_pattern_core_conn_create_rsp) >= skb->len || 23762306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_core_conn_create_rsp, 23862306a36Sopenharmony_ci sizeof(nci_pattern_core_conn_create_rsp))) 23962306a36Sopenharmony_ci return -EINVAL; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci priv->fw_dnld.state = STATE_FW_DNLD; 24262306a36Sopenharmony_ci priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; 24362306a36Sopenharmony_ci priv->fw_dnld.offset = priv->fw_dnld.binary_config->offset; 24462306a36Sopenharmony_ci return 0; 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_cistatic int process_state_fw_dnld(struct nfcmrvl_private *priv, 24862306a36Sopenharmony_ci struct sk_buff *skb) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci uint16_t len; 25162306a36Sopenharmony_ci uint16_t comp_len; 25262306a36Sopenharmony_ci struct sk_buff *out_skb; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci switch (priv->fw_dnld.substate) { 25562306a36Sopenharmony_ci case SUBSTATE_WAIT_COMMAND: 25662306a36Sopenharmony_ci /* 25762306a36Sopenharmony_ci * Command format: 25862306a36Sopenharmony_ci * B0..2: NCI header 25962306a36Sopenharmony_ci * B3 : Helper command (0xA5) 26062306a36Sopenharmony_ci * B4..5: le16 data size 26162306a36Sopenharmony_ci * B6..7: le16 data size complement (~) 26262306a36Sopenharmony_ci * B8..N: payload 26362306a36Sopenharmony_ci */ 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* Remove NCI HDR */ 26662306a36Sopenharmony_ci skb_pull(skb, 3); 26762306a36Sopenharmony_ci if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { 26862306a36Sopenharmony_ci nfc_err(priv->dev, "bad command"); 26962306a36Sopenharmony_ci return -EINVAL; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci skb_pull(skb, 1); 27262306a36Sopenharmony_ci len = get_unaligned_le16(skb->data); 27362306a36Sopenharmony_ci skb_pull(skb, 2); 27462306a36Sopenharmony_ci comp_len = get_unaligned_le16(skb->data); 27562306a36Sopenharmony_ci memcpy(&comp_len, skb->data, 2); 27662306a36Sopenharmony_ci skb_pull(skb, 2); 27762306a36Sopenharmony_ci if (((~len) & 0xFFFF) != comp_len) { 27862306a36Sopenharmony_ci nfc_err(priv->dev, "bad len complement: %x %x %x", 27962306a36Sopenharmony_ci len, comp_len, (~len & 0xFFFF)); 28062306a36Sopenharmony_ci out_skb = alloc_lc_skb(priv, 1); 28162306a36Sopenharmony_ci if (!out_skb) 28262306a36Sopenharmony_ci return -ENOMEM; 28362306a36Sopenharmony_ci skb_put_u8(out_skb, 0xBF); 28462306a36Sopenharmony_ci nci_send_frame(priv->ndev, out_skb); 28562306a36Sopenharmony_ci priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT; 28662306a36Sopenharmony_ci return 0; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci priv->fw_dnld.chunk_len = len; 28962306a36Sopenharmony_ci out_skb = alloc_lc_skb(priv, 1); 29062306a36Sopenharmony_ci if (!out_skb) 29162306a36Sopenharmony_ci return -ENOMEM; 29262306a36Sopenharmony_ci skb_put_u8(out_skb, HELPER_ACK_PACKET_FORMAT); 29362306a36Sopenharmony_ci nci_send_frame(priv->ndev, out_skb); 29462306a36Sopenharmony_ci priv->fw_dnld.substate = SUBSTATE_WAIT_ACK_CREDIT; 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci case SUBSTATE_WAIT_ACK_CREDIT: 29862306a36Sopenharmony_ci if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || 29962306a36Sopenharmony_ci memcmp(nci_pattern_core_conn_credits_ntf, skb->data, 30062306a36Sopenharmony_ci skb->len)) { 30162306a36Sopenharmony_ci nfc_err(priv->dev, "bad packet: waiting for credit"); 30262306a36Sopenharmony_ci return -EINVAL; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci if (priv->fw_dnld.chunk_len == 0) { 30562306a36Sopenharmony_ci /* FW Loading is done */ 30662306a36Sopenharmony_ci uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci priv->fw_dnld.state = STATE_CLOSE_LC; 30962306a36Sopenharmony_ci nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD, 31062306a36Sopenharmony_ci 1, &conn_id); 31162306a36Sopenharmony_ci } else { 31262306a36Sopenharmony_ci out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len); 31362306a36Sopenharmony_ci if (!out_skb) 31462306a36Sopenharmony_ci return -ENOMEM; 31562306a36Sopenharmony_ci skb_put_data(out_skb, 31662306a36Sopenharmony_ci ((uint8_t *)priv->fw_dnld.fw->data) + priv->fw_dnld.offset, 31762306a36Sopenharmony_ci priv->fw_dnld.chunk_len); 31862306a36Sopenharmony_ci nci_send_frame(priv->ndev, out_skb); 31962306a36Sopenharmony_ci priv->fw_dnld.substate = SUBSTATE_WAIT_DATA_CREDIT; 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci break; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci case SUBSTATE_WAIT_DATA_CREDIT: 32462306a36Sopenharmony_ci if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || 32562306a36Sopenharmony_ci memcmp(nci_pattern_core_conn_credits_ntf, skb->data, 32662306a36Sopenharmony_ci skb->len)) { 32762306a36Sopenharmony_ci nfc_err(priv->dev, "bad packet: waiting for credit"); 32862306a36Sopenharmony_ci return -EINVAL; 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci priv->fw_dnld.offset += priv->fw_dnld.chunk_len; 33162306a36Sopenharmony_ci priv->fw_dnld.chunk_len = 0; 33262306a36Sopenharmony_ci priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; 33362306a36Sopenharmony_ci break; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci case SUBSTATE_WAIT_NACK_CREDIT: 33662306a36Sopenharmony_ci if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || 33762306a36Sopenharmony_ci memcmp(nci_pattern_core_conn_credits_ntf, skb->data, 33862306a36Sopenharmony_ci skb->len)) { 33962306a36Sopenharmony_ci nfc_err(priv->dev, "bad packet: waiting for credit"); 34062306a36Sopenharmony_ci return -EINVAL; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; 34362306a36Sopenharmony_ci break; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci return 0; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic int process_state_close_lc(struct nfcmrvl_private *priv, 34962306a36Sopenharmony_ci const struct sk_buff *skb) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci if (sizeof(nci_pattern_core_conn_close_rsp) != skb->len || 35262306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_core_conn_close_rsp, skb->len)) 35362306a36Sopenharmony_ci return -EINVAL; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci priv->fw_dnld.state = STATE_BOOT; 35662306a36Sopenharmony_ci nci_send_cmd(priv->ndev, NCI_OP_PROPRIETARY_BOOT_CMD, 0, NULL); 35762306a36Sopenharmony_ci return 0; 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_cistatic int process_state_boot(struct nfcmrvl_private *priv, 36162306a36Sopenharmony_ci const struct sk_buff *skb) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci if (sizeof(nci_pattern_proprietary_boot_rsp) != skb->len || 36462306a36Sopenharmony_ci memcmp(skb->data, nci_pattern_proprietary_boot_rsp, skb->len)) 36562306a36Sopenharmony_ci return -EINVAL; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci /* 36862306a36Sopenharmony_ci * Update HI config to use the right configuration for the next 36962306a36Sopenharmony_ci * data exchanges. 37062306a36Sopenharmony_ci */ 37162306a36Sopenharmony_ci priv->if_ops->nci_update_config(priv, 37262306a36Sopenharmony_ci &priv->fw_dnld.binary_config->config); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (priv->fw_dnld.binary_config == &priv->fw_dnld.header->helper) { 37562306a36Sopenharmony_ci /* 37662306a36Sopenharmony_ci * This is the case where an helper was needed and we have 37762306a36Sopenharmony_ci * uploaded it. Now we have to wait the next RESET NTF to start 37862306a36Sopenharmony_ci * FW download. 37962306a36Sopenharmony_ci */ 38062306a36Sopenharmony_ci priv->fw_dnld.state = STATE_RESET; 38162306a36Sopenharmony_ci priv->fw_dnld.binary_config = &priv->fw_dnld.header->firmware; 38262306a36Sopenharmony_ci nfc_info(priv->dev, "FW loading: helper loaded"); 38362306a36Sopenharmony_ci } else { 38462306a36Sopenharmony_ci nfc_info(priv->dev, "FW loading: firmware loaded"); 38562306a36Sopenharmony_ci fw_dnld_over(priv, 0); 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci return 0; 38862306a36Sopenharmony_ci} 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_cistatic void fw_dnld_rx_work(struct work_struct *work) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci int ret; 39362306a36Sopenharmony_ci struct sk_buff *skb; 39462306a36Sopenharmony_ci struct nfcmrvl_fw_dnld *fw_dnld = container_of(work, 39562306a36Sopenharmony_ci struct nfcmrvl_fw_dnld, 39662306a36Sopenharmony_ci rx_work); 39762306a36Sopenharmony_ci struct nfcmrvl_private *priv = container_of(fw_dnld, 39862306a36Sopenharmony_ci struct nfcmrvl_private, 39962306a36Sopenharmony_ci fw_dnld); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci while ((skb = skb_dequeue(&fw_dnld->rx_q))) { 40262306a36Sopenharmony_ci nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb, 40362306a36Sopenharmony_ci RAW_PAYLOAD_NCI, NFC_DIRECTION_RX); 40462306a36Sopenharmony_ci switch (fw_dnld->state) { 40562306a36Sopenharmony_ci case STATE_RESET: 40662306a36Sopenharmony_ci ret = process_state_reset(priv, skb); 40762306a36Sopenharmony_ci break; 40862306a36Sopenharmony_ci case STATE_INIT: 40962306a36Sopenharmony_ci ret = process_state_init(priv, skb); 41062306a36Sopenharmony_ci break; 41162306a36Sopenharmony_ci case STATE_SET_REF_CLOCK: 41262306a36Sopenharmony_ci ret = process_state_set_ref_clock(priv, skb); 41362306a36Sopenharmony_ci break; 41462306a36Sopenharmony_ci case STATE_SET_HI_CONFIG: 41562306a36Sopenharmony_ci ret = process_state_set_hi_config(priv, skb); 41662306a36Sopenharmony_ci break; 41762306a36Sopenharmony_ci case STATE_OPEN_LC: 41862306a36Sopenharmony_ci ret = process_state_open_lc(priv, skb); 41962306a36Sopenharmony_ci break; 42062306a36Sopenharmony_ci case STATE_FW_DNLD: 42162306a36Sopenharmony_ci ret = process_state_fw_dnld(priv, skb); 42262306a36Sopenharmony_ci break; 42362306a36Sopenharmony_ci case STATE_CLOSE_LC: 42462306a36Sopenharmony_ci ret = process_state_close_lc(priv, skb); 42562306a36Sopenharmony_ci break; 42662306a36Sopenharmony_ci case STATE_BOOT: 42762306a36Sopenharmony_ci ret = process_state_boot(priv, skb); 42862306a36Sopenharmony_ci break; 42962306a36Sopenharmony_ci default: 43062306a36Sopenharmony_ci ret = -EFAULT; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci kfree_skb(skb); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (ret != 0) { 43662306a36Sopenharmony_ci nfc_err(priv->dev, "FW loading error"); 43762306a36Sopenharmony_ci fw_dnld_over(priv, ret); 43862306a36Sopenharmony_ci break; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci } 44162306a36Sopenharmony_ci} 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ciint nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv) 44462306a36Sopenharmony_ci{ 44562306a36Sopenharmony_ci char name[32]; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work); 44862306a36Sopenharmony_ci snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq", 44962306a36Sopenharmony_ci dev_name(&priv->ndev->nfc_dev->dev)); 45062306a36Sopenharmony_ci priv->fw_dnld.rx_wq = create_singlethread_workqueue(name); 45162306a36Sopenharmony_ci if (!priv->fw_dnld.rx_wq) 45262306a36Sopenharmony_ci return -ENOMEM; 45362306a36Sopenharmony_ci skb_queue_head_init(&priv->fw_dnld.rx_q); 45462306a36Sopenharmony_ci return 0; 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_civoid nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv) 45862306a36Sopenharmony_ci{ 45962306a36Sopenharmony_ci destroy_workqueue(priv->fw_dnld.rx_wq); 46062306a36Sopenharmony_ci} 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_civoid nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv, 46362306a36Sopenharmony_ci struct sk_buff *skb) 46462306a36Sopenharmony_ci{ 46562306a36Sopenharmony_ci /* Discard command timer */ 46662306a36Sopenharmony_ci if (timer_pending(&priv->ndev->cmd_timer)) 46762306a36Sopenharmony_ci del_timer_sync(&priv->ndev->cmd_timer); 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci /* Allow next command */ 47062306a36Sopenharmony_ci atomic_set(&priv->ndev->cmd_cnt, 1); 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci /* Queue and trigger rx work */ 47362306a36Sopenharmony_ci skb_queue_tail(&priv->fw_dnld.rx_q, skb); 47462306a36Sopenharmony_ci queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work); 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_civoid nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci fw_dnld_over(priv, -EHOSTDOWN); 48062306a36Sopenharmony_ci} 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ciint nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name) 48362306a36Sopenharmony_ci{ 48462306a36Sopenharmony_ci struct nfcmrvl_private *priv = nci_get_drvdata(ndev); 48562306a36Sopenharmony_ci struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld; 48662306a36Sopenharmony_ci int res; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci if (!priv->support_fw_dnld) 48962306a36Sopenharmony_ci return -ENOTSUPP; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci if (!firmware_name || !firmware_name[0]) 49262306a36Sopenharmony_ci return -EINVAL; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci strcpy(fw_dnld->name, firmware_name); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci /* 49762306a36Sopenharmony_ci * Retrieve FW binary file and parse it to initialize FW download 49862306a36Sopenharmony_ci * state machine. 49962306a36Sopenharmony_ci */ 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci /* Retrieve FW binary */ 50262306a36Sopenharmony_ci res = request_firmware(&fw_dnld->fw, firmware_name, 50362306a36Sopenharmony_ci &ndev->nfc_dev->dev); 50462306a36Sopenharmony_ci if (res < 0) { 50562306a36Sopenharmony_ci nfc_err(priv->dev, "failed to retrieve FW %s", firmware_name); 50662306a36Sopenharmony_ci return -ENOENT; 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC || 51262306a36Sopenharmony_ci fw_dnld->header->phy != priv->phy) { 51362306a36Sopenharmony_ci nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d", 51462306a36Sopenharmony_ci firmware_name, fw_dnld->header->magic, 51562306a36Sopenharmony_ci fw_dnld->header->phy); 51662306a36Sopenharmony_ci release_firmware(fw_dnld->fw); 51762306a36Sopenharmony_ci fw_dnld->header = NULL; 51862306a36Sopenharmony_ci return -EINVAL; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (fw_dnld->header->helper.offset != 0) { 52262306a36Sopenharmony_ci nfc_info(priv->dev, "loading helper"); 52362306a36Sopenharmony_ci fw_dnld->binary_config = &fw_dnld->header->helper; 52462306a36Sopenharmony_ci } else { 52562306a36Sopenharmony_ci nfc_info(priv->dev, "loading firmware"); 52662306a36Sopenharmony_ci fw_dnld->binary_config = &fw_dnld->header->firmware; 52762306a36Sopenharmony_ci } 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci /* Configure a timer for timeout */ 53062306a36Sopenharmony_ci timer_setup(&priv->fw_dnld.timer, fw_dnld_timeout, 0); 53162306a36Sopenharmony_ci mod_timer(&priv->fw_dnld.timer, 53262306a36Sopenharmony_ci jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT)); 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci /* Ronfigure HI to be sure that it is the bootrom values */ 53562306a36Sopenharmony_ci priv->if_ops->nci_update_config(priv, 53662306a36Sopenharmony_ci &fw_dnld->header->bootrom.config); 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci /* Allow first command */ 53962306a36Sopenharmony_ci atomic_set(&priv->ndev->cmd_cnt, 1); 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci /* First, reset the chip */ 54262306a36Sopenharmony_ci priv->fw_dnld.state = STATE_RESET; 54362306a36Sopenharmony_ci nfcmrvl_chip_reset(priv); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci /* Now wait for CORE_RESET_NTF or timeout */ 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci return 0; 54862306a36Sopenharmony_ci} 549