Vincent Cuissard | 3194c68 | 2015-10-26 10:27:39 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Marvell NFC driver: Firmware downloader |
| 3 | * |
| 4 | * Copyright (C) 2015, Marvell International Ltd. |
| 5 | * |
| 6 | * This software file (the "File") is distributed by Marvell International |
| 7 | * Ltd. under the terms of the GNU General Public License Version 2, June 1991 |
| 8 | * (the "License"). You may use, redistribute and/or modify this File in |
| 9 | * accordance with the terms and conditions of the License, a copy of which |
| 10 | * is available on the worldwide web at |
| 11 | * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. |
| 12 | * |
| 13 | * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE |
| 14 | * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE |
| 15 | * ARE EXPRESSLY DISCLAIMED. The License provides additional details about |
| 16 | * this warranty disclaimer. |
| 17 | */ |
| 18 | |
| 19 | #include <linux/module.h> |
| 20 | #include <linux/unaligned/access_ok.h> |
| 21 | #include <linux/firmware.h> |
| 22 | #include <linux/nfc.h> |
| 23 | #include <net/nfc/nci.h> |
| 24 | #include <net/nfc/nci_core.h> |
| 25 | #include "nfcmrvl.h" |
| 26 | |
| 27 | #define FW_DNLD_TIMEOUT 15000 |
| 28 | |
| 29 | #define NCI_OP_PROPRIETARY_BOOT_CMD nci_opcode_pack(NCI_GID_PROPRIETARY, \ |
| 30 | NCI_OP_PROP_BOOT_CMD) |
| 31 | |
| 32 | /* FW download states */ |
| 33 | |
| 34 | enum { |
| 35 | STATE_RESET = 0, |
| 36 | STATE_INIT, |
| 37 | STATE_SET_REF_CLOCK, |
| 38 | STATE_SET_HI_CONFIG, |
| 39 | STATE_OPEN_LC, |
| 40 | STATE_FW_DNLD, |
| 41 | STATE_CLOSE_LC, |
| 42 | STATE_BOOT |
| 43 | }; |
| 44 | |
| 45 | enum { |
| 46 | SUBSTATE_WAIT_COMMAND = 0, |
| 47 | SUBSTATE_WAIT_ACK_CREDIT, |
| 48 | SUBSTATE_WAIT_NACK_CREDIT, |
| 49 | SUBSTATE_WAIT_DATA_CREDIT, |
| 50 | }; |
| 51 | |
| 52 | /* |
| 53 | ** Patterns for responses |
| 54 | */ |
| 55 | |
| 56 | static const uint8_t nci_pattern_core_reset_ntf[] = { |
| 57 | 0x60, 0x00, 0x02, 0xA0, 0x01 |
| 58 | }; |
| 59 | |
| 60 | static const uint8_t nci_pattern_core_init_rsp[] = { |
| 61 | 0x40, 0x01, 0x11 |
| 62 | }; |
| 63 | |
| 64 | static const uint8_t nci_pattern_core_set_config_rsp[] = { |
| 65 | 0x40, 0x02, 0x02, 0x00, 0x00 |
| 66 | }; |
| 67 | |
| 68 | static const uint8_t nci_pattern_core_conn_create_rsp[] = { |
| 69 | 0x40, 0x04, 0x04, 0x00 |
| 70 | }; |
| 71 | |
| 72 | static const uint8_t nci_pattern_core_conn_close_rsp[] = { |
| 73 | 0x40, 0x05, 0x01, 0x00 |
| 74 | }; |
| 75 | |
| 76 | static const uint8_t nci_pattern_core_conn_credits_ntf[] = { |
| 77 | 0x60, 0x06, 0x03, 0x01, NCI_CORE_LC_CONNID_PROP_FW_DL, 0x01 |
| 78 | }; |
| 79 | |
| 80 | static const uint8_t nci_pattern_proprietary_boot_rsp[] = { |
| 81 | 0x4F, 0x3A, 0x01, 0x00 |
| 82 | }; |
| 83 | |
| 84 | static struct sk_buff *alloc_lc_skb(struct nfcmrvl_private *priv, uint8_t plen) |
| 85 | { |
| 86 | struct sk_buff *skb; |
| 87 | struct nci_data_hdr *hdr; |
| 88 | |
| 89 | skb = nci_skb_alloc(priv->ndev, (NCI_DATA_HDR_SIZE + plen), GFP_KERNEL); |
| 90 | if (!skb) { |
| 91 | pr_err("no memory for data\n"); |
| 92 | return NULL; |
| 93 | } |
| 94 | |
| 95 | hdr = (struct nci_data_hdr *) skb_put(skb, NCI_DATA_HDR_SIZE); |
| 96 | hdr->conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; |
| 97 | hdr->rfu = 0; |
| 98 | hdr->plen = plen; |
| 99 | |
| 100 | nci_mt_set((__u8 *)hdr, NCI_MT_DATA_PKT); |
| 101 | nci_pbf_set((__u8 *)hdr, NCI_PBF_LAST); |
| 102 | |
| 103 | return skb; |
| 104 | } |
| 105 | |
| 106 | static void fw_dnld_over(struct nfcmrvl_private *priv, u32 error) |
| 107 | { |
| 108 | if (priv->fw_dnld.fw) { |
| 109 | release_firmware(priv->fw_dnld.fw); |
| 110 | priv->fw_dnld.fw = NULL; |
| 111 | priv->fw_dnld.header = NULL; |
| 112 | priv->fw_dnld.binary_config = NULL; |
| 113 | } |
| 114 | |
| 115 | atomic_set(&priv->ndev->cmd_cnt, 0); |
Vincent Cuissard | 3194c68 | 2015-10-26 10:27:39 +0100 | [diff] [blame] | 116 | |
Vincent Cuissard | 82aff3e | 2015-11-03 19:19:36 +0100 | [diff] [blame] | 117 | if (timer_pending(&priv->ndev->cmd_timer)) |
| 118 | del_timer_sync(&priv->ndev->cmd_timer); |
| 119 | |
| 120 | if (timer_pending(&priv->fw_dnld.timer)) |
| 121 | del_timer_sync(&priv->fw_dnld.timer); |
Vincent Cuissard | 3194c68 | 2015-10-26 10:27:39 +0100 | [diff] [blame] | 122 | |
| 123 | nfc_info(priv->dev, "FW loading over (%d)]\n", error); |
| 124 | |
| 125 | if (error != 0) { |
| 126 | /* failed, halt the chip to avoid power consumption */ |
| 127 | nfcmrvl_chip_halt(priv); |
| 128 | } |
| 129 | |
| 130 | nfc_fw_download_done(priv->ndev->nfc_dev, priv->fw_dnld.name, error); |
| 131 | } |
| 132 | |
| 133 | static void fw_dnld_timeout(unsigned long arg) |
| 134 | { |
| 135 | struct nfcmrvl_private *priv = (struct nfcmrvl_private *) arg; |
| 136 | |
| 137 | nfc_err(priv->dev, "FW loading timeout"); |
| 138 | priv->fw_dnld.state = STATE_RESET; |
| 139 | fw_dnld_over(priv, -ETIMEDOUT); |
| 140 | } |
| 141 | |
| 142 | static int process_state_reset(struct nfcmrvl_private *priv, |
| 143 | struct sk_buff *skb) |
| 144 | { |
| 145 | if (sizeof(nci_pattern_core_reset_ntf) != skb->len || |
| 146 | memcmp(skb->data, nci_pattern_core_reset_ntf, |
| 147 | sizeof(nci_pattern_core_reset_ntf))) |
| 148 | return -EINVAL; |
| 149 | |
| 150 | nfc_info(priv->dev, "BootROM reset, start fw download\n"); |
| 151 | |
| 152 | /* Start FW download state machine */ |
| 153 | priv->fw_dnld.state = STATE_INIT; |
| 154 | nci_send_cmd(priv->ndev, NCI_OP_CORE_INIT_CMD, 0, NULL); |
| 155 | |
| 156 | return 0; |
| 157 | } |
| 158 | |
| 159 | static int process_state_init(struct nfcmrvl_private *priv, struct sk_buff *skb) |
| 160 | { |
| 161 | struct nci_core_set_config_cmd cmd; |
| 162 | |
| 163 | if (sizeof(nci_pattern_core_init_rsp) >= skb->len || |
| 164 | memcmp(skb->data, nci_pattern_core_init_rsp, |
| 165 | sizeof(nci_pattern_core_init_rsp))) |
| 166 | return -EINVAL; |
| 167 | |
| 168 | cmd.num_params = 1; |
| 169 | cmd.param.id = NFCMRVL_PROP_REF_CLOCK; |
| 170 | cmd.param.len = 4; |
| 171 | memcpy(cmd.param.val, &priv->fw_dnld.header->ref_clock, 4); |
| 172 | |
| 173 | nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, |
| 174 | &cmd); |
| 175 | |
| 176 | priv->fw_dnld.state = STATE_SET_REF_CLOCK; |
| 177 | return 0; |
| 178 | } |
| 179 | |
| 180 | static void create_lc(struct nfcmrvl_private *priv) |
| 181 | { |
| 182 | uint8_t param[2] = { NCI_CORE_LC_PROP_FW_DL, 0x0 }; |
| 183 | |
| 184 | priv->fw_dnld.state = STATE_OPEN_LC; |
| 185 | nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CREATE_CMD, 2, param); |
| 186 | } |
| 187 | |
| 188 | static int process_state_set_ref_clock(struct nfcmrvl_private *priv, |
| 189 | struct sk_buff *skb) |
| 190 | { |
| 191 | struct nci_core_set_config_cmd cmd; |
| 192 | |
| 193 | if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || |
| 194 | memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) |
| 195 | return -EINVAL; |
| 196 | |
| 197 | cmd.num_params = 1; |
| 198 | cmd.param.id = NFCMRVL_PROP_SET_HI_CONFIG; |
| 199 | |
| 200 | switch (priv->phy) { |
| 201 | case NFCMRVL_PHY_UART: |
| 202 | cmd.param.len = 5; |
| 203 | memcpy(cmd.param.val, |
| 204 | &priv->fw_dnld.binary_config->uart.baudrate, |
| 205 | 4); |
| 206 | cmd.param.val[4] = |
| 207 | priv->fw_dnld.binary_config->uart.flow_control; |
| 208 | break; |
| 209 | case NFCMRVL_PHY_I2C: |
| 210 | cmd.param.len = 5; |
| 211 | memcpy(cmd.param.val, |
| 212 | &priv->fw_dnld.binary_config->i2c.clk, |
| 213 | 4); |
| 214 | cmd.param.val[4] = 0; |
| 215 | break; |
| 216 | case NFCMRVL_PHY_SPI: |
| 217 | cmd.param.len = 5; |
| 218 | memcpy(cmd.param.val, |
| 219 | &priv->fw_dnld.binary_config->spi.clk, |
| 220 | 4); |
| 221 | cmd.param.val[4] = 0; |
| 222 | break; |
| 223 | default: |
| 224 | create_lc(priv); |
| 225 | return 0; |
| 226 | } |
| 227 | |
| 228 | priv->fw_dnld.state = STATE_SET_HI_CONFIG; |
| 229 | nci_send_cmd(priv->ndev, NCI_OP_CORE_SET_CONFIG_CMD, 3 + cmd.param.len, |
| 230 | &cmd); |
| 231 | return 0; |
| 232 | } |
| 233 | |
| 234 | static int process_state_set_hi_config(struct nfcmrvl_private *priv, |
| 235 | struct sk_buff *skb) |
| 236 | { |
| 237 | if (sizeof(nci_pattern_core_set_config_rsp) != skb->len || |
| 238 | memcmp(skb->data, nci_pattern_core_set_config_rsp, skb->len)) |
| 239 | return -EINVAL; |
| 240 | |
| 241 | create_lc(priv); |
| 242 | return 0; |
| 243 | } |
| 244 | |
| 245 | static int process_state_open_lc(struct nfcmrvl_private *priv, |
| 246 | struct sk_buff *skb) |
| 247 | { |
| 248 | if (sizeof(nci_pattern_core_conn_create_rsp) >= skb->len || |
| 249 | memcmp(skb->data, nci_pattern_core_conn_create_rsp, |
| 250 | sizeof(nci_pattern_core_conn_create_rsp))) |
| 251 | return -EINVAL; |
| 252 | |
| 253 | priv->fw_dnld.state = STATE_FW_DNLD; |
| 254 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; |
| 255 | priv->fw_dnld.offset = priv->fw_dnld.binary_config->offset; |
| 256 | return 0; |
| 257 | } |
| 258 | |
| 259 | static int process_state_fw_dnld(struct nfcmrvl_private *priv, |
| 260 | struct sk_buff *skb) |
| 261 | { |
| 262 | uint16_t len; |
| 263 | uint16_t comp_len; |
| 264 | struct sk_buff *out_skb; |
| 265 | |
| 266 | switch (priv->fw_dnld.substate) { |
| 267 | case SUBSTATE_WAIT_COMMAND: |
| 268 | /* |
| 269 | * Command format: |
| 270 | * B0..2: NCI header |
| 271 | * B3 : Helper command (0xA5) |
| 272 | * B4..5: le16 data size |
| 273 | * B6..7: le16 data size complement (~) |
| 274 | * B8..N: payload |
| 275 | */ |
| 276 | |
| 277 | /* Remove NCI HDR */ |
| 278 | skb_pull(skb, 3); |
| 279 | if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { |
| 280 | nfc_err(priv->dev, "bad command"); |
| 281 | return -EINVAL; |
| 282 | } |
| 283 | skb_pull(skb, 1); |
| 284 | memcpy(&len, skb->data, 2); |
| 285 | skb_pull(skb, 2); |
| 286 | memcpy(&comp_len, skb->data, 2); |
| 287 | skb_pull(skb, 2); |
| 288 | len = get_unaligned_le16(&len); |
| 289 | comp_len = get_unaligned_le16(&comp_len); |
| 290 | if (((~len) & 0xFFFF) != comp_len) { |
| 291 | nfc_err(priv->dev, "bad len complement: %x %x %x", |
| 292 | len, comp_len, (~len & 0xFFFF)); |
| 293 | out_skb = alloc_lc_skb(priv, 1); |
| 294 | if (!out_skb) |
| 295 | return -ENOMEM; |
| 296 | *skb_put(out_skb, 1) = 0xBF; |
| 297 | nci_send_frame(priv->ndev, out_skb); |
| 298 | priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT; |
| 299 | return 0; |
| 300 | } |
| 301 | priv->fw_dnld.chunk_len = len; |
| 302 | out_skb = alloc_lc_skb(priv, 1); |
| 303 | if (!out_skb) |
| 304 | return -ENOMEM; |
| 305 | *skb_put(out_skb, 1) = HELPER_ACK_PACKET_FORMAT; |
| 306 | nci_send_frame(priv->ndev, out_skb); |
| 307 | priv->fw_dnld.substate = SUBSTATE_WAIT_ACK_CREDIT; |
| 308 | break; |
| 309 | |
| 310 | case SUBSTATE_WAIT_ACK_CREDIT: |
| 311 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || |
| 312 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, |
| 313 | skb->len)) { |
| 314 | nfc_err(priv->dev, "bad packet: waiting for credit"); |
| 315 | return -EINVAL; |
| 316 | } |
| 317 | if (priv->fw_dnld.chunk_len == 0) { |
| 318 | /* FW Loading is done */ |
| 319 | uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL; |
| 320 | |
| 321 | priv->fw_dnld.state = STATE_CLOSE_LC; |
| 322 | nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD, |
| 323 | 1, &conn_id); |
| 324 | } else { |
| 325 | out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len); |
| 326 | if (!out_skb) |
| 327 | return -ENOMEM; |
| 328 | memcpy(skb_put(out_skb, priv->fw_dnld.chunk_len), |
| 329 | ((uint8_t *)priv->fw_dnld.fw->data) + |
| 330 | priv->fw_dnld.offset, |
| 331 | priv->fw_dnld.chunk_len); |
| 332 | nci_send_frame(priv->ndev, out_skb); |
| 333 | priv->fw_dnld.substate = SUBSTATE_WAIT_DATA_CREDIT; |
| 334 | } |
| 335 | break; |
| 336 | |
| 337 | case SUBSTATE_WAIT_DATA_CREDIT: |
| 338 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || |
| 339 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, |
| 340 | skb->len)) { |
| 341 | nfc_err(priv->dev, "bad packet: waiting for credit"); |
| 342 | return -EINVAL; |
| 343 | } |
| 344 | priv->fw_dnld.offset += priv->fw_dnld.chunk_len; |
| 345 | priv->fw_dnld.chunk_len = 0; |
| 346 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; |
| 347 | break; |
| 348 | |
| 349 | case SUBSTATE_WAIT_NACK_CREDIT: |
| 350 | if (sizeof(nci_pattern_core_conn_credits_ntf) != skb->len || |
| 351 | memcmp(nci_pattern_core_conn_credits_ntf, skb->data, |
| 352 | skb->len)) { |
| 353 | nfc_err(priv->dev, "bad packet: waiting for credit"); |
| 354 | return -EINVAL; |
| 355 | } |
| 356 | priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND; |
| 357 | break; |
| 358 | } |
| 359 | return 0; |
| 360 | } |
| 361 | |
| 362 | static int process_state_close_lc(struct nfcmrvl_private *priv, |
| 363 | struct sk_buff *skb) |
| 364 | { |
| 365 | if (sizeof(nci_pattern_core_conn_close_rsp) != skb->len || |
| 366 | memcmp(skb->data, nci_pattern_core_conn_close_rsp, skb->len)) |
| 367 | return -EINVAL; |
| 368 | |
| 369 | priv->fw_dnld.state = STATE_BOOT; |
| 370 | nci_send_cmd(priv->ndev, NCI_OP_PROPRIETARY_BOOT_CMD, 0, NULL); |
| 371 | return 0; |
| 372 | } |
| 373 | |
| 374 | static int process_state_boot(struct nfcmrvl_private *priv, struct sk_buff *skb) |
| 375 | { |
| 376 | if (sizeof(nci_pattern_proprietary_boot_rsp) != skb->len || |
| 377 | memcmp(skb->data, nci_pattern_proprietary_boot_rsp, skb->len)) |
| 378 | return -EINVAL; |
| 379 | |
| 380 | /* |
| 381 | * Update HI config to use the right configuration for the next |
| 382 | * data exchanges. |
| 383 | */ |
| 384 | priv->if_ops->nci_update_config(priv, |
| 385 | &priv->fw_dnld.binary_config->config); |
| 386 | |
| 387 | if (priv->fw_dnld.binary_config == &priv->fw_dnld.header->helper) { |
| 388 | /* |
| 389 | * This is the case where an helper was needed and we have |
| 390 | * uploaded it. Now we have to wait the next RESET NTF to start |
| 391 | * FW download. |
| 392 | */ |
| 393 | priv->fw_dnld.state = STATE_RESET; |
| 394 | priv->fw_dnld.binary_config = &priv->fw_dnld.header->firmware; |
| 395 | nfc_info(priv->dev, "FW loading: helper loaded"); |
| 396 | } else { |
| 397 | nfc_info(priv->dev, "FW loading: firmware loaded"); |
| 398 | fw_dnld_over(priv, 0); |
| 399 | } |
| 400 | return 0; |
| 401 | } |
| 402 | |
| 403 | static void fw_dnld_rx_work(struct work_struct *work) |
| 404 | { |
| 405 | int ret; |
| 406 | struct sk_buff *skb; |
| 407 | struct nfcmrvl_fw_dnld *fw_dnld = container_of(work, |
| 408 | struct nfcmrvl_fw_dnld, |
| 409 | rx_work); |
| 410 | struct nfcmrvl_private *priv = container_of(fw_dnld, |
| 411 | struct nfcmrvl_private, |
| 412 | fw_dnld); |
| 413 | |
| 414 | while ((skb = skb_dequeue(&fw_dnld->rx_q))) { |
| 415 | nfc_send_to_raw_sock(priv->ndev->nfc_dev, skb, |
| 416 | RAW_PAYLOAD_NCI, NFC_DIRECTION_RX); |
| 417 | switch (fw_dnld->state) { |
| 418 | case STATE_RESET: |
| 419 | ret = process_state_reset(priv, skb); |
| 420 | break; |
| 421 | case STATE_INIT: |
| 422 | ret = process_state_init(priv, skb); |
| 423 | break; |
| 424 | case STATE_SET_REF_CLOCK: |
| 425 | ret = process_state_set_ref_clock(priv, skb); |
| 426 | break; |
| 427 | case STATE_SET_HI_CONFIG: |
| 428 | ret = process_state_set_hi_config(priv, skb); |
| 429 | break; |
| 430 | case STATE_OPEN_LC: |
| 431 | ret = process_state_open_lc(priv, skb); |
| 432 | break; |
| 433 | case STATE_FW_DNLD: |
| 434 | ret = process_state_fw_dnld(priv, skb); |
| 435 | break; |
| 436 | case STATE_CLOSE_LC: |
| 437 | ret = process_state_close_lc(priv, skb); |
| 438 | break; |
| 439 | case STATE_BOOT: |
| 440 | ret = process_state_boot(priv, skb); |
| 441 | break; |
| 442 | default: |
| 443 | ret = -EFAULT; |
| 444 | } |
| 445 | |
| 446 | kfree_skb(skb); |
| 447 | |
| 448 | if (ret != 0) { |
| 449 | nfc_err(priv->dev, "FW loading error"); |
| 450 | fw_dnld_over(priv, ret); |
| 451 | break; |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | int nfcmrvl_fw_dnld_init(struct nfcmrvl_private *priv) |
| 457 | { |
| 458 | char name[32]; |
| 459 | |
| 460 | INIT_WORK(&priv->fw_dnld.rx_work, fw_dnld_rx_work); |
| 461 | snprintf(name, sizeof(name), "%s_nfcmrvl_fw_dnld_rx_wq", |
| 462 | dev_name(priv->dev)); |
| 463 | priv->fw_dnld.rx_wq = create_singlethread_workqueue(name); |
| 464 | if (!priv->fw_dnld.rx_wq) |
| 465 | return -ENOMEM; |
| 466 | skb_queue_head_init(&priv->fw_dnld.rx_q); |
| 467 | return 0; |
| 468 | } |
| 469 | |
| 470 | void nfcmrvl_fw_dnld_deinit(struct nfcmrvl_private *priv) |
| 471 | { |
| 472 | destroy_workqueue(priv->fw_dnld.rx_wq); |
| 473 | } |
| 474 | |
| 475 | void nfcmrvl_fw_dnld_recv_frame(struct nfcmrvl_private *priv, |
| 476 | struct sk_buff *skb) |
| 477 | { |
Vincent Cuissard | 82aff3e | 2015-11-03 19:19:36 +0100 | [diff] [blame] | 478 | /* Discard command timer */ |
| 479 | if (timer_pending(&priv->ndev->cmd_timer)) |
| 480 | del_timer_sync(&priv->ndev->cmd_timer); |
| 481 | |
Vincent Cuissard | 3194c68 | 2015-10-26 10:27:39 +0100 | [diff] [blame] | 482 | /* Allow next command */ |
| 483 | atomic_set(&priv->ndev->cmd_cnt, 1); |
Vincent Cuissard | 3194c68 | 2015-10-26 10:27:39 +0100 | [diff] [blame] | 484 | |
| 485 | /* Queue and trigger rx work */ |
| 486 | skb_queue_tail(&priv->fw_dnld.rx_q, skb); |
| 487 | queue_work(priv->fw_dnld.rx_wq, &priv->fw_dnld.rx_work); |
| 488 | } |
| 489 | |
| 490 | void nfcmrvl_fw_dnld_abort(struct nfcmrvl_private *priv) |
| 491 | { |
| 492 | fw_dnld_over(priv, -EHOSTDOWN); |
| 493 | } |
| 494 | |
| 495 | int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name) |
| 496 | { |
| 497 | struct nfcmrvl_private *priv = nci_get_drvdata(ndev); |
| 498 | struct nfcmrvl_fw_dnld *fw_dnld = &priv->fw_dnld; |
| 499 | |
| 500 | if (!priv->support_fw_dnld) |
| 501 | return -ENOTSUPP; |
| 502 | |
| 503 | if (!firmware_name || !firmware_name[0]) |
| 504 | return -EINVAL; |
| 505 | |
| 506 | strcpy(fw_dnld->name, firmware_name); |
| 507 | |
| 508 | /* |
| 509 | * Retrieve FW binary file and parse it to initialize FW download |
| 510 | * state machine. |
| 511 | */ |
| 512 | |
| 513 | /* Retrieve FW binary */ |
| 514 | if (request_firmware(&fw_dnld->fw, firmware_name, priv->dev) < 0) { |
| 515 | nfc_err(priv->dev, "failed to retrieve FW %s", firmware_name); |
| 516 | return -ENOENT; |
| 517 | } |
| 518 | |
| 519 | fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data; |
| 520 | |
| 521 | if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC || |
| 522 | fw_dnld->header->phy != priv->phy) { |
| 523 | nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d", |
| 524 | firmware_name, fw_dnld->header->magic, |
| 525 | fw_dnld->header->phy); |
| 526 | release_firmware(fw_dnld->fw); |
| 527 | fw_dnld->header = NULL; |
| 528 | return -EINVAL; |
| 529 | } |
| 530 | |
| 531 | if (fw_dnld->header->helper.offset != 0) { |
| 532 | nfc_info(priv->dev, "loading helper"); |
| 533 | fw_dnld->binary_config = &fw_dnld->header->helper; |
| 534 | } else { |
| 535 | nfc_info(priv->dev, "loading firmware"); |
| 536 | fw_dnld->binary_config = &fw_dnld->header->firmware; |
| 537 | } |
| 538 | |
| 539 | /* Configure a timer for timeout */ |
| 540 | setup_timer(&priv->fw_dnld.timer, fw_dnld_timeout, |
| 541 | (unsigned long) priv); |
| 542 | mod_timer(&priv->fw_dnld.timer, |
| 543 | jiffies + msecs_to_jiffies(FW_DNLD_TIMEOUT)); |
| 544 | |
| 545 | /* Ronfigure HI to be sure that it is the bootrom values */ |
| 546 | priv->if_ops->nci_update_config(priv, |
| 547 | &fw_dnld->header->bootrom.config); |
| 548 | |
| 549 | /* Allow first command */ |
| 550 | atomic_set(&priv->ndev->cmd_cnt, 1); |
| 551 | |
| 552 | /* First, reset the chip */ |
| 553 | priv->fw_dnld.state = STATE_RESET; |
| 554 | nfcmrvl_chip_reset(priv); |
| 555 | |
| 556 | /* Now wait for CORE_RESET_NTF or timeout */ |
| 557 | |
| 558 | return 0; |
| 559 | } |