1be3d162aSKrzysztof Kozlowski // SPDX-License-Identifier: GPL-2.0-only
23194c687SVincent Cuissard /*
33194c687SVincent Cuissard * Marvell NFC driver: Firmware downloader
43194c687SVincent Cuissard *
53194c687SVincent Cuissard * Copyright (C) 2015, Marvell International Ltd.
63194c687SVincent Cuissard */
73194c687SVincent Cuissard
83194c687SVincent Cuissard #include <linux/module.h>
9d916d923STobias Klauser #include <asm/unaligned.h>
103194c687SVincent Cuissard #include <linux/firmware.h>
113194c687SVincent Cuissard #include <linux/nfc.h>
123194c687SVincent Cuissard #include <net/nfc/nci.h>
133194c687SVincent Cuissard #include <net/nfc/nci_core.h>
143194c687SVincent Cuissard #include "nfcmrvl.h"
153194c687SVincent Cuissard
163194c687SVincent Cuissard #define FW_DNLD_TIMEOUT 15000
173194c687SVincent Cuissard
183194c687SVincent Cuissard #define NCI_OP_PROPRIETARY_BOOT_CMD nci_opcode_pack(NCI_GID_PROPRIETARY, \
193194c687SVincent Cuissard NCI_OP_PROP_BOOT_CMD)
203194c687SVincent Cuissard
213194c687SVincent Cuissard /* FW download states */
223194c687SVincent Cuissard
233194c687SVincent Cuissard enum {
243194c687SVincent Cuissard STATE_RESET = 0,
253194c687SVincent Cuissard STATE_INIT,
263194c687SVincent Cuissard STATE_SET_REF_CLOCK,
273194c687SVincent Cuissard STATE_SET_HI_CONFIG,
283194c687SVincent Cuissard STATE_OPEN_LC,
293194c687SVincent Cuissard STATE_FW_DNLD,
303194c687SVincent Cuissard STATE_CLOSE_LC,
313194c687SVincent Cuissard STATE_BOOT
323194c687SVincent Cuissard };
333194c687SVincent Cuissard
343194c687SVincent Cuissard enum {
353194c687SVincent Cuissard SUBSTATE_WAIT_COMMAND = 0,
363194c687SVincent Cuissard SUBSTATE_WAIT_ACK_CREDIT,
373194c687SVincent Cuissard SUBSTATE_WAIT_NACK_CREDIT,
383194c687SVincent Cuissard SUBSTATE_WAIT_DATA_CREDIT,
393194c687SVincent Cuissard };
403194c687SVincent Cuissard
413194c687SVincent Cuissard /*
428f99528eSKrzysztof Kozlowski * Patterns for responses
433194c687SVincent Cuissard */
443194c687SVincent Cuissard
453194c687SVincent Cuissard static const uint8_t nci_pattern_core_reset_ntf[] = {
463194c687SVincent Cuissard 0x60, 0x00, 0x02, 0xA0, 0x01
473194c687SVincent Cuissard };
483194c687SVincent Cuissard
493194c687SVincent Cuissard static const uint8_t nci_pattern_core_init_rsp[] = {
503194c687SVincent Cuissard 0x40, 0x01, 0x11
513194c687SVincent Cuissard };
523194c687SVincent Cuissard
533194c687SVincent Cuissard static const uint8_t nci_pattern_core_set_config_rsp[] = {
543194c687SVincent Cuissard 0x40, 0x02, 0x02, 0x00, 0x00
553194c687SVincent Cuissard };
563194c687SVincent Cuissard
573194c687SVincent Cuissard static const uint8_t nci_pattern_core_conn_create_rsp[] = {
583194c687SVincent Cuissard 0x40, 0x04, 0x04, 0x00
593194c687SVincent Cuissard };
603194c687SVincent Cuissard
613194c687SVincent Cuissard static const uint8_t nci_pattern_core_conn_close_rsp[] = {
623194c687SVincent Cuissard 0x40, 0x05, 0x01, 0x00
633194c687SVincent Cuissard };
643194c687SVincent Cuissard
653194c687SVincent Cuissard static const uint8_t nci_pattern_core_conn_credits_ntf[] = {
663194c687SVincent Cuissard 0x60, 0x06, 0x03, 0x01, NCI_CORE_LC_CONNID_PROP_FW_DL, 0x01
673194c687SVincent Cuissard };
683194c687SVincent Cuissard
693194c687SVincent Cuissard static const uint8_t nci_pattern_proprietary_boot_rsp[] = {
703194c687SVincent Cuissard 0x4F, 0x3A, 0x01, 0x00
713194c687SVincent Cuissard };
723194c687SVincent Cuissard
alloc_lc_skb(struct nfcmrvl_private * priv,uint8_t plen)733194c687SVincent Cuissard static struct sk_buff *alloc_lc_skb(struct nfcmrvl_private *priv, uint8_t plen)
743194c687SVincent Cuissard {
753194c687SVincent Cuissard struct sk_buff *skb;
763194c687SVincent Cuissard struct nci_data_hdr *hdr;
773194c687SVincent Cuissard
783194c687SVincent Cuissard skb = nci_skb_alloc(priv->ndev, (NCI_DATA_HDR_SIZE + plen), GFP_KERNEL);
79*d1c624ebSKrzysztof Kozlowski if (!skb)
803194c687SVincent Cuissard return NULL;
813194c687SVincent Cuissard
824df864c1SJohannes Berg hdr = skb_put(skb, NCI_DATA_HDR_SIZE);
833194c687SVincent Cuissard hdr->conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL;
843194c687SVincent Cuissard hdr->rfu = 0;
853194c687SVincent Cuissard hdr->plen = plen;
863194c687SVincent Cuissard
873194c687SVincent Cuissard nci_mt_set((__u8 *)hdr, NCI_MT_DATA_PKT);
883194c687SVincent Cuissard nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST);
893194c687SVincent Cuissard
903194c687SVincent Cuissard return skb;
913194c687SVincent Cuissard }
923194c687SVincent Cuissard
fw_dnld_over(struct nfcmrvl_private * priv,u32 error)933194c687SVincent Cuissard static void fw_dnld_over(struct nfcmrvl_private *priv, u32 error)
943194c687SVincent Cuissard {
953194c687SVincent Cuissard if (priv->fw_dnld.fw) {
963194c687SVincent Cuissard release_firmware(priv->fw_dnld.fw);
973194c687SVincent Cuissard priv->fw_dnld.fw = NULL;
983194c687SVincent Cuissard priv->fw_dnld.header = NULL;
993194c687SVincent Cuissard priv->fw_dnld.binary_config = NULL;
1003194c687SVincent Cuissard }
1013194c687SVincent Cuissard
1023194c687SVincent Cuissard atomic_set(&priv->ndev->cmd_cnt, 0);
10382aff3eaSVincent Cuissard
10482aff3eaSVincent Cuissard if (timer_pending(&priv->ndev->cmd_timer))
1053194c687SVincent Cuissard del_timer_sync(&priv->ndev->cmd_timer);
1063194c687SVincent Cuissard
10782aff3eaSVincent Cuissard if (timer_pending(&priv->fw_dnld.timer))
1083194c687SVincent Cuissard del_timer_sync(&priv->fw_dnld.timer);
1093194c687SVincent Cuissard
1103194c687SVincent Cuissard nfc_info(priv->dev, "FW loading over (%d)]\n", error);
1113194c687SVincent Cuissard
1123194c687SVincent Cuissard if (error != 0) {
1133194c687SVincent Cuissard /* failed, halt the chip to avoid power consumption */
1143194c687SVincent Cuissard nfcmrvl_chip_halt(priv);
1153194c687SVincent Cuissard }
1163194c687SVincent Cuissard
1173194c687SVincent Cuissard nfc_fw_download_done(priv->ndev->nfc_dev, priv->fw_dnld.name, error);
1183194c687SVincent Cuissard }
1193194c687SVincent Cuissard
fw_dnld_timeout(struct timer_list * t)12086cb30ecSKees Cook static void fw_dnld_timeout(struct timer_list *t)
1213194c687SVincent Cuissard {
12286cb30ecSKees Cook struct nfcmrvl_private *priv = from_timer(priv, t, fw_dnld.timer);
1233194c687SVincent Cuissard
1243194c687SVincent Cuissard nfc_err(priv->dev, "FW loading timeout");
1253194c687SVincent Cuissard priv->fw_dnld.state = STATE_RESET;
1263194c687SVincent Cuissard fw_dnld_over(priv, -ETIMEDOUT);
1273194c687SVincent Cuissard }
1283194c687SVincent Cuissard
process_state_reset(struct nfcmrvl_private * priv,const struct sk_buff * skb)1293194c687SVincent Cuissard static int process_state_reset(struct nfcmrvl_private *priv,
130fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
1313194c687SVincent Cuissard {
1323194c687SVincent Cuissard if (sizeof(nci_pattern_core_reset_ntf) != skb->len ||
1333194c687SVincent Cuissard memcmp(skb->data, nci_pattern_core_reset_ntf,
1343194c687SVincent Cuissard sizeof(nci_pattern_core_reset_ntf)))
1353194c687SVincent Cuissard return -EINVAL;
1363194c687SVincent Cuissard
1373194c687SVincent Cuissard nfc_info(priv->dev, "BootROM reset, start fw download\n");
1383194c687SVincent Cuissard
1393194c687SVincent Cuissard /* Start FW download state machine */
1403194c687SVincent Cuissard priv->fw_dnld.state = STATE_INIT;
1413194c687SVincent Cuissard nci_send_cmd(priv->ndev, NCI_OP_CORE_INIT_CMD, 0, NULL);
1423194c687SVincent Cuissard
1433194c687SVincent Cuissard return 0;
1443194c687SVincent Cuissard }
1453194c687SVincent Cuissard
process_state_init(struct nfcmrvl_private * priv,const struct sk_buff * skb)146fe53159fSKrzysztof Kozlowski static int process_state_init(struct nfcmrvl_private *priv,
147fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
1483194c687SVincent Cuissard {
1493194c687SVincent Cuissard struct nci_core_set_config_cmd cmd;
1503194c687SVincent Cuissard
1513194c687SVincent Cuissard if (sizeof(nci_pattern_core_init_rsp) >= skb->len ||
1523194c687SVincent Cuissard memcmp(skb->data, nci_pattern_core_init_rsp,
1533194c687SVincent Cuissard sizeof(nci_pattern_core_init_rsp)))
1543194c687SVincent Cuissard return -EINVAL;
1553194c687SVincent Cuissard
1563194c687SVincent Cuissard cmd.num_params = 1;
1573194c687SVincent Cuissard cmd.param.id = NFCMRVL_PROP_REF_CLOCK;
1583194c687SVincent Cuissard cmd.param.len = 4;
1593194c687SVincent Cuissard memcpy(cmd.param.val, &priv->fw_dnld.header->ref_clock, 4);
1603194c687SVincent Cuissard
1613194c687SVincent Cuissard nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len,
1623194c687SVincent Cuissard &cmd);
1633194c687SVincent Cuissard
1643194c687SVincent Cuissard priv->fw_dnld.state = STATE_SET_REF_CLOCK;
1653194c687SVincent Cuissard return 0;
1663194c687SVincent Cuissard }
1673194c687SVincent Cuissard
create_lc(struct nfcmrvl_private * priv)1683194c687SVincent Cuissard static void create_lc(struct nfcmrvl_private *priv)
1693194c687SVincent Cuissard {
1703194c687SVincent Cuissard uint8_t param[2] = { NCI_CORE_LC_PROP_FW_DL, 0x0 };
1713194c687SVincent Cuissard
1723194c687SVincent Cuissard priv->fw_dnld.state = STATE_OPEN_LC;
1733194c687SVincent Cuissard nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CREATE_CMD, 2, param);
1743194c687SVincent Cuissard }
1753194c687SVincent Cuissard
process_state_set_ref_clock(struct nfcmrvl_private * priv,const struct sk_buff * skb)1763194c687SVincent Cuissard static int process_state_set_ref_clock(struct nfcmrvl_private *priv,
177fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
1783194c687SVincent Cuissard {
1793194c687SVincent Cuissard struct nci_core_set_config_cmd cmd;
1803194c687SVincent Cuissard
1813194c687SVincent Cuissard if (sizeof(nci_pattern_core_set_config_rsp) != skb->len ||
1823194c687SVincent Cuissard memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len))
1833194c687SVincent Cuissard return -EINVAL;
1843194c687SVincent Cuissard
1853194c687SVincent Cuissard cmd.num_params = 1;
1863194c687SVincent Cuissard cmd.param.id = NFCMRVL_PROP_SET_HI_CONFIG;
1873194c687SVincent Cuissard
1883194c687SVincent Cuissard switch (priv->phy) {
1893194c687SVincent Cuissard case NFCMRVL_PHY_UART:
1903194c687SVincent Cuissard cmd.param.len = 5;
1913194c687SVincent Cuissard memcpy(cmd.param.val,
1923194c687SVincent Cuissard &priv->fw_dnld.binary_config->uart.baudrate,
1933194c687SVincent Cuissard 4);
1943194c687SVincent Cuissard cmd.param.val[4] =
1953194c687SVincent Cuissard priv->fw_dnld.binary_config->uart.flow_control;
1963194c687SVincent Cuissard break;
1973194c687SVincent Cuissard case NFCMRVL_PHY_I2C:
1983194c687SVincent Cuissard cmd.param.len = 5;
1993194c687SVincent Cuissard memcpy(cmd.param.val,
2003194c687SVincent Cuissard &priv->fw_dnld.binary_config->i2c.clk,
2013194c687SVincent Cuissard 4);
2023194c687SVincent Cuissard cmd.param.val[4] = 0;
2033194c687SVincent Cuissard break;
2043194c687SVincent Cuissard case NFCMRVL_PHY_SPI:
2053194c687SVincent Cuissard cmd.param.len = 5;
2063194c687SVincent Cuissard memcpy(cmd.param.val,
2073194c687SVincent Cuissard &priv->fw_dnld.binary_config->spi.clk,
2083194c687SVincent Cuissard 4);
2093194c687SVincent Cuissard cmd.param.val[4] = 0;
2103194c687SVincent Cuissard break;
2113194c687SVincent Cuissard default:
2123194c687SVincent Cuissard create_lc(priv);
2133194c687SVincent Cuissard return 0;
2143194c687SVincent Cuissard }
2153194c687SVincent Cuissard
2163194c687SVincent Cuissard priv->fw_dnld.state = STATE_SET_HI_CONFIG;
2173194c687SVincent Cuissard nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len,
2183194c687SVincent Cuissard &cmd);
2193194c687SVincent Cuissard return 0;
2203194c687SVincent Cuissard }
2213194c687SVincent Cuissard
process_state_set_hi_config(struct nfcmrvl_private * priv,const struct sk_buff * skb)2223194c687SVincent Cuissard static int process_state_set_hi_config(struct nfcmrvl_private *priv,
223fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
2243194c687SVincent Cuissard {
2253194c687SVincent Cuissard if (sizeof(nci_pattern_core_set_config_rsp) != skb->len ||
2263194c687SVincent Cuissard memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len))
2273194c687SVincent Cuissard return -EINVAL;
2283194c687SVincent Cuissard
2293194c687SVincent Cuissard create_lc(priv);
2303194c687SVincent Cuissard return 0;
2313194c687SVincent Cuissard }
2323194c687SVincent Cuissard
process_state_open_lc(struct nfcmrvl_private * priv,const struct sk_buff * skb)2333194c687SVincent Cuissard static int process_state_open_lc(struct nfcmrvl_private *priv,
234fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
2353194c687SVincent Cuissard {
2363194c687SVincent Cuissard if (sizeof(nci_pattern_core_conn_create_rsp) >= skb->len ||
2373194c687SVincent Cuissard memcmp(skb->data, nci_pattern_core_conn_create_rsp,
2383194c687SVincent Cuissard sizeof(nci_pattern_core_conn_create_rsp)))
2393194c687SVincent Cuissard return -EINVAL;
2403194c687SVincent Cuissard
2413194c687SVincent Cuissard priv->fw_dnld.state = STATE_FW_DNLD;
2423194c687SVincent Cuissard priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND;
2433194c687SVincent Cuissard priv->fw_dnld.offset = priv->fw_dnld.binary_config->offset;
2443194c687SVincent Cuissard return 0;
2453194c687SVincent Cuissard }
2463194c687SVincent Cuissard
process_state_fw_dnld(struct nfcmrvl_private * priv,struct sk_buff * skb)2473194c687SVincent Cuissard static int process_state_fw_dnld(struct nfcmrvl_private *priv,
2483194c687SVincent Cuissard struct sk_buff *skb)
2493194c687SVincent Cuissard {
2503194c687SVincent Cuissard uint16_t len;
2513194c687SVincent Cuissard uint16_t comp_len;
2523194c687SVincent Cuissard struct sk_buff *out_skb;
2533194c687SVincent Cuissard
2543194c687SVincent Cuissard switch (priv->fw_dnld.substate) {
2553194c687SVincent Cuissard case SUBSTATE_WAIT_COMMAND:
2563194c687SVincent Cuissard /*
2573194c687SVincent Cuissard * Command format:
2583194c687SVincent Cuissard * B0..2: NCI header
2593194c687SVincent Cuissard * B3 : Helper command (0xA5)
2603194c687SVincent Cuissard * B4..5: le16 data size
2613194c687SVincent Cuissard * B6..7: le16 data size complement (~)
2623194c687SVincent Cuissard * B8..N: payload
2633194c687SVincent Cuissard */
2643194c687SVincent Cuissard
2653194c687SVincent Cuissard /* Remove NCI HDR */
2663194c687SVincent Cuissard skb_pull(skb, 3);
2673194c687SVincent Cuissard if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) {
2683194c687SVincent Cuissard nfc_err(priv->dev, "bad command");
2693194c687SVincent Cuissard return -EINVAL;
2703194c687SVincent Cuissard }
2713194c687SVincent Cuissard skb_pull(skb, 1);
2724ea20639SAl Viro len = get_unaligned_le16(skb->data);
2733194c687SVincent Cuissard skb_pull(skb, 2);
2744ea20639SAl Viro comp_len = get_unaligned_le16(skb->data);
2753194c687SVincent Cuissard memcpy(&comp_len, skb->data, 2);
2763194c687SVincent Cuissard skb_pull(skb, 2);
2773194c687SVincent Cuissard if (((~len) & 0xFFFF) != comp_len) {
2783194c687SVincent Cuissard nfc_err(priv->dev, "bad len complement: %x %x %x",
2793194c687SVincent Cuissard len, comp_len, (~len & 0xFFFF));
2803194c687SVincent Cuissard out_skb = alloc_lc_skb(priv, 1);
2813194c687SVincent Cuissard if (!out_skb)
2823194c687SVincent Cuissard return -ENOMEM;
283634fef61SJohannes Berg skb_put_u8(out_skb, 0xBF);
2843194c687SVincent Cuissard nci_send_frame(priv->ndev, out_skb);
2853194c687SVincent Cuissard priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT;
2863194c687SVincent Cuissard return 0;
2873194c687SVincent Cuissard }
2883194c687SVincent Cuissard priv->fw_dnld.chunk_len = len;
2893194c687SVincent Cuissard out_skb = alloc_lc_skb(priv, 1);
2903194c687SVincent Cuissard if (!out_skb)
2913194c687SVincent Cuissard return -ENOMEM;
292634fef61SJohannes Berg skb_put_u8(out_skb, HELPER_ACK_PACKET_FORMAT);
2933194c687SVincent Cuissard nci_send_frame(priv->ndev, out_skb);
2943194c687SVincent Cuissard priv->fw_dnld.substate = SUBSTATE_WAIT_ACK_CREDIT;
2953194c687SVincent Cuissard break;
2963194c687SVincent Cuissard
2973194c687SVincent Cuissard case SUBSTATE_WAIT_ACK_CREDIT:
2983194c687SVincent Cuissard if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len ||
2993194c687SVincent Cuissard memcmp(nci_pattern_core_conn_credits_ntf, skb->data,
3003194c687SVincent Cuissard skb->len)) {
3013194c687SVincent Cuissard nfc_err(priv->dev, "bad packet: waiting for credit");
3023194c687SVincent Cuissard return -EINVAL;
3033194c687SVincent Cuissard }
3043194c687SVincent Cuissard if (priv->fw_dnld.chunk_len == 0) {
3053194c687SVincent Cuissard /* FW Loading is done */
3063194c687SVincent Cuissard uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL;
3073194c687SVincent Cuissard
3083194c687SVincent Cuissard priv->fw_dnld.state = STATE_CLOSE_LC;
3093194c687SVincent Cuissard nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD,
3103194c687SVincent Cuissard 1, &conn_id);
3113194c687SVincent Cuissard } else {
3123194c687SVincent Cuissard out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len);
3133194c687SVincent Cuissard if (!out_skb)
3143194c687SVincent Cuissard return -ENOMEM;
31559ae1d12SJohannes Berg skb_put_data(out_skb,
31659ae1d12SJohannes Berg ((uint8_t *)priv->fw_dnld.fw->data) + priv->fw_dnld.offset,
3173194c687SVincent Cuissard priv->fw_dnld.chunk_len);
3183194c687SVincent Cuissard nci_send_frame(priv->ndev, out_skb);
3193194c687SVincent Cuissard priv->fw_dnld.substate = SUBSTATE_WAIT_DATA_CREDIT;
3203194c687SVincent Cuissard }
3213194c687SVincent Cuissard break;
3223194c687SVincent Cuissard
3233194c687SVincent Cuissard case SUBSTATE_WAIT_DATA_CREDIT:
3243194c687SVincent Cuissard if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len ||
3253194c687SVincent Cuissard memcmp(nci_pattern_core_conn_credits_ntf, skb->data,
3263194c687SVincent Cuissard skb->len)) {
3273194c687SVincent Cuissard nfc_err(priv->dev, "bad packet: waiting for credit");
3283194c687SVincent Cuissard return -EINVAL;
3293194c687SVincent Cuissard }
3303194c687SVincent Cuissard priv->fw_dnld.offset += priv->fw_dnld.chunk_len;
3313194c687SVincent Cuissard priv->fw_dnld.chunk_len = 0;
3323194c687SVincent Cuissard priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND;
3333194c687SVincent Cuissard break;
3343194c687SVincent Cuissard
3353194c687SVincent Cuissard case SUBSTATE_WAIT_NACK_CREDIT:
3363194c687SVincent Cuissard if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len ||
3373194c687SVincent Cuissard memcmp(nci_pattern_core_conn_credits_ntf, skb->data,
3383194c687SVincent Cuissard skb->len)) {
3393194c687SVincent Cuissard nfc_err(priv->dev, "bad packet: waiting for credit");
3403194c687SVincent Cuissard return -EINVAL;
3413194c687SVincent Cuissard }
3423194c687SVincent Cuissard priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND;
3433194c687SVincent Cuissard break;
3443194c687SVincent Cuissard }
3453194c687SVincent Cuissard return 0;
3463194c687SVincent Cuissard }
3473194c687SVincent Cuissard
process_state_close_lc(struct nfcmrvl_private * priv,const struct sk_buff * skb)3483194c687SVincent Cuissard static int process_state_close_lc(struct nfcmrvl_private *priv,
349fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
3503194c687SVincent Cuissard {
3513194c687SVincent Cuissard if (sizeof(nci_pattern_core_conn_close_rsp) != skb->len ||
3523194c687SVincent Cuissard memcmp(skb->data, nci_pattern_core_conn_close_rsp, skb->len))
3533194c687SVincent Cuissard return -EINVAL;
3543194c687SVincent Cuissard
3553194c687SVincent Cuissard priv->fw_dnld.state = STATE_BOOT;
3563194c687SVincent Cuissard nci_send_cmd(priv->ndev, NCI_OP_PROPRIETARY_BOOT_CMD, 0, NULL);
3573194c687SVincent Cuissard return 0;
3583194c687SVincent Cuissard }
3593194c687SVincent Cuissard
process_state_boot(struct nfcmrvl_private * priv,const struct sk_buff * skb)360fe53159fSKrzysztof Kozlowski static int process_state_boot(struct nfcmrvl_private *priv,
361fe53159fSKrzysztof Kozlowski const struct sk_buff *skb)
3623194c687SVincent Cuissard {
3633194c687SVincent Cuissard if (sizeof(nci_pattern_proprietary_boot_rsp) != skb->len ||
3643194c687SVincent Cuissard memcmp(skb->data, nci_pattern_proprietary_boot_rsp, skb->len))
3653194c687SVincent Cuissard return -EINVAL;
3663194c687SVincent Cuissard
3673194c687SVincent Cuissard /*
3683194c687SVincent Cuissard * Update HI config to use the right configuration for the next
3693194c687SVincent Cuissard * data exchanges.
3703194c687SVincent Cuissard */
3713194c687SVincent Cuissard priv->if_ops->nci_update_config(priv,
3723194c687SVincent Cuissard &priv->fw_dnld.binary_config->config);
3733194c687SVincent Cuissard
3743194c687SVincent Cuissard if (priv->fw_dnld.binary_config == &priv->fw_dnld.header->helper) {
3753194c687SVincent Cuissard /*
3763194c687SVincent Cuissard * This is the case where an helper was needed and we have
3773194c687SVincent Cuissard * uploaded it. Now we have to wait the next RESET NTF to start
3783194c687SVincent Cuissard * FW download.
3793194c687SVincent Cuissard */
3803194c687SVincent Cuissard priv->fw_dnld.state = STATE_RESET;
3813194c687SVincent Cuissard priv->fw_dnld.binary_config = &priv->fw_dnld.header->firmware;
3823194c687SVincent Cuissard nfc_info(priv->dev, "FW loading: helper loaded");
3833194c687SVincent Cuissard } else {
3843194c687SVincent Cuissard nfc_info(priv->dev, "FW loading: firmware loaded");
3853194c687SVincent Cuissard fw_dnld_over(priv, 0);
3863194c687SVincent Cuissard }
3873194c687SVincent Cuissard return 0;
3883194c687SVincent Cuissard }
3893194c687SVincent Cuissard
fw_dnld_rx_work(struct work_struct * work)3903194c687SVincent Cuissard static void fw_dnld_rx_work(struct work_struct *work)
3913194c687SVincent Cuissard {
3923194c687SVincent Cuissard int ret;
3933194c687SVincent Cuissard struct sk_buff *skb;
3943194c687SVincent Cuissard struct nfcmrvl_fw_dnld *fw_dnld = container_of(work,
3953194c687SVincent Cuissard struct nfcmrvl_fw_dnld,
3963194c687SVincent Cuissard rx_work);
3973194c687SVincent Cuissard struct nfcmrvl_private *priv = container_of(fw_dnld,
3983194c687SVincent Cuissard struct nfcmrvl_private,
3993194c687SVincent Cuissard fw_dnld);
4003194c687SVincent Cuissard
4013194c687SVincent Cuissard while ((skb = skb_dequeue(&fw_dnld->rx_q))) {
4023194c687SVincent Cuissard nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb,
4033194c687SVincent Cuissard RAW_PAYLOAD_NCI, NFC_DIRECTION_RX);
4043194c687SVincent Cuissard switch (fw_dnld->state) {
4053194c687SVincent Cuissard case STATE_RESET:
4063194c687SVincent Cuissard ret = process_state_reset(priv, skb);
4073194c687SVincent Cuissard break;
4083194c687SVincent Cuissard case STATE_INIT:
4093194c687SVincent Cuissard ret = process_state_init(priv, skb);
4103194c687SVincent Cuissard break;
4113194c687SVincent Cuissard case STATE_SET_REF_CLOCK:
4123194c687SVincent Cuissard ret = process_state_set_ref_clock(priv, skb);
4133194c687SVincent Cuissard break;
4143194c687SVincent Cuissard case STATE_SET_HI_CONFIG:
4153194c687SVincent Cuissard ret = process_state_set_hi_config(priv, skb);
4163194c687SVincent Cuissard break;
4173194c687SVincent Cuissard case STATE_OPEN_LC:
4183194c687SVincent Cuissard ret = process_state_open_lc(priv, skb);
4193194c687SVincent Cuissard break;
4203194c687SVincent Cuissard case STATE_FW_DNLD:
4213194c687SVincent Cuissard ret = process_state_fw_dnld(priv, skb);
4223194c687SVincent Cuissard break;
4233194c687SVincent Cuissard case STATE_CLOSE_LC:
4243194c687SVincent Cuissard ret = process_state_close_lc(priv, skb);
4253194c687SVincent Cuissard break;
4263194c687SVincent Cuissard case STATE_BOOT:
4273194c687SVincent Cuissard ret = process_state_boot(priv, skb);
4283194c687SVincent Cuissard break;
4293194c687SVincent Cuissard default:
4303194c687SVincent Cuissard ret = -EFAULT;
4313194c687SVincent Cuissard }
4323194c687SVincent Cuissard
4333194c687SVincent Cuissard kfree_skb(skb);
4343194c687SVincent Cuissard
4353194c687SVincent Cuissard if (ret != 0) {
4363194c687SVincent Cuissard nfc_err(priv->dev, "FW loading error");
4373194c687SVincent Cuissard fw_dnld_over(priv, ret);
4383194c687SVincent Cuissard break;
4393194c687SVincent Cuissard }
4403194c687SVincent Cuissard }
4413194c687SVincent Cuissard }
4423194c687SVincent Cuissard
nfcmrvl_fw_dnld_init(struct nfcmrvl_private * priv)4433194c687SVincent Cuissard int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv)
4443194c687SVincent Cuissard {
4453194c687SVincent Cuissard char name[32];
4463194c687SVincent Cuissard
4473194c687SVincent Cuissard INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work);
4483194c687SVincent Cuissard snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq",
449e5834ac2SJohan Hovold dev_name(&priv->ndev->nfc_dev->dev));
4503194c687SVincent Cuissard priv->fw_dnld.rx_wq = create_singlethread_workqueue(name);
4513194c687SVincent Cuissard if (!priv->fw_dnld.rx_wq)
4523194c687SVincent Cuissard return -ENOMEM;
4533194c687SVincent Cuissard skb_queue_head_init(&priv->fw_dnld.rx_q);
4543194c687SVincent Cuissard return 0;
4553194c687SVincent Cuissard }
4563194c687SVincent Cuissard
nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private * priv)4573194c687SVincent Cuissard void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv)
4583194c687SVincent Cuissard {
4593194c687SVincent Cuissard destroy_workqueue(priv->fw_dnld.rx_wq);
4603194c687SVincent Cuissard }
4613194c687SVincent Cuissard
nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private * priv,struct sk_buff * skb)4623194c687SVincent Cuissard void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv,
4633194c687SVincent Cuissard struct sk_buff *skb)
4643194c687SVincent Cuissard {
46582aff3eaSVincent Cuissard /* Discard command timer */
46682aff3eaSVincent Cuissard if (timer_pending(&priv->ndev->cmd_timer))
46782aff3eaSVincent Cuissard del_timer_sync(&priv->ndev->cmd_timer);
46882aff3eaSVincent Cuissard
4693194c687SVincent Cuissard /* Allow next command */
4703194c687SVincent Cuissard atomic_set(&priv->ndev->cmd_cnt, 1);
4713194c687SVincent Cuissard
4723194c687SVincent Cuissard /* Queue and trigger rx work */
4733194c687SVincent Cuissard skb_queue_tail(&priv->fw_dnld.rx_q, skb);
4743194c687SVincent Cuissard queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work);
4753194c687SVincent Cuissard }
4763194c687SVincent Cuissard
nfcmrvl_fw_dnld_abort(struct nfcmrvl_private * priv)4773194c687SVincent Cuissard void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv)
4783194c687SVincent Cuissard {
4793194c687SVincent Cuissard fw_dnld_over(priv, -EHOSTDOWN);
4803194c687SVincent Cuissard }
4813194c687SVincent Cuissard
nfcmrvl_fw_dnld_start(struct nci_dev * ndev,const char * firmware_name)4823194c687SVincent Cuissard int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
4833194c687SVincent Cuissard {
4843194c687SVincent Cuissard struct nfcmrvl_private *priv = nci_get_drvdata(ndev);
4853194c687SVincent Cuissard struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld;
486e5834ac2SJohan Hovold int res;
4873194c687SVincent Cuissard
4883194c687SVincent Cuissard if (!priv->support_fw_dnld)
4893194c687SVincent Cuissard return -ENOTSUPP;
4903194c687SVincent Cuissard
4913194c687SVincent Cuissard if (!firmware_name || !firmware_name[0])
4923194c687SVincent Cuissard return -EINVAL;
4933194c687SVincent Cuissard
4943194c687SVincent Cuissard strcpy(fw_dnld->name, firmware_name);
4953194c687SVincent Cuissard
4963194c687SVincent Cuissard /*
4973194c687SVincent Cuissard * Retrieve FW binary file and parse it to initialize FW download
4983194c687SVincent Cuissard * state machine.
4993194c687SVincent Cuissard */
5003194c687SVincent Cuissard
5013194c687SVincent Cuissard /* Retrieve FW binary */
502e5834ac2SJohan Hovold res = request_firmware(&fw_dnld->fw, firmware_name,
503e5834ac2SJohan Hovold &ndev->nfc_dev->dev);
504e5834ac2SJohan Hovold if (res < 0) {
5053194c687SVincent Cuissard nfc_err(priv->dev, "failed to retrieve FW %s", firmware_name);
5063194c687SVincent Cuissard return -ENOENT;
5073194c687SVincent Cuissard }
5083194c687SVincent Cuissard
5093194c687SVincent Cuissard fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data;
5103194c687SVincent Cuissard
5113194c687SVincent Cuissard if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC ||
5123194c687SVincent Cuissard fw_dnld->header->phy != priv->phy) {
5133194c687SVincent Cuissard nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d",
5143194c687SVincent Cuissard firmware_name, fw_dnld->header->magic,
5153194c687SVincent Cuissard fw_dnld->header->phy);
5163194c687SVincent Cuissard release_firmware(fw_dnld->fw);
5173194c687SVincent Cuissard fw_dnld->header = NULL;
5183194c687SVincent Cuissard return -EINVAL;
5193194c687SVincent Cuissard }
5203194c687SVincent Cuissard
5213194c687SVincent Cuissard if (fw_dnld->header->helper.offset != 0) {
5223194c687SVincent Cuissard nfc_info(priv->dev, "loading helper");
5233194c687SVincent Cuissard fw_dnld->binary_config = &fw_dnld->header->helper;
5243194c687SVincent Cuissard } else {
5253194c687SVincent Cuissard nfc_info(priv->dev, "loading firmware");
5263194c687SVincent Cuissard fw_dnld->binary_config = &fw_dnld->header->firmware;
5273194c687SVincent Cuissard }
5283194c687SVincent Cuissard
5293194c687SVincent Cuissard /* Configure a timer for timeout */
53086cb30ecSKees Cook timer_setup(&priv->fw_dnld.timer, fw_dnld_timeout, 0);
5313194c687SVincent Cuissard mod_timer(&priv->fw_dnld.timer,
5323194c687SVincent Cuissard jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT));
5333194c687SVincent Cuissard
5343194c687SVincent Cuissard /* Ronfigure HI to be sure that it is the bootrom values */
5353194c687SVincent Cuissard priv->if_ops->nci_update_config(priv,
5363194c687SVincent Cuissard &fw_dnld->header->bootrom.config);
5373194c687SVincent Cuissard
5383194c687SVincent Cuissard /* Allow first command */
5393194c687SVincent Cuissard atomic_set(&priv->ndev->cmd_cnt, 1);
5403194c687SVincent Cuissard
5413194c687SVincent Cuissard /* First, reset the chip */
5423194c687SVincent Cuissard priv->fw_dnld.state = STATE_RESET;
5433194c687SVincent Cuissard nfcmrvl_chip_reset(priv);
5443194c687SVincent Cuissard
5453194c687SVincent Cuissard /* Now wait for CORE_RESET_NTF or timeout */
5463194c687SVincent Cuissard
5473194c687SVincent Cuissard return 0;
5483194c687SVincent Cuissard }
549