xref: /openbmc/linux/drivers/nfc/nfcmrvl/fw_dnld.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
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